@@ -17,20 +17,38 @@ public object CobsFraming {
1717
1818 /* *
1919 * Encodes [packet] (with COBS/R when [reduced] is true, otherwise basic
20- * COBS) and appends the [COBS_DELIMITER], producing a self-delimiting frame.
20+ * COBS) and appends the delimiter, producing a self-delimiting frame.
21+ *
22+ * [sentinel] selects the delimiter byte (and the byte the encoding avoids);
23+ * it defaults to the `0x00` [COBS_DELIMITER]. The packet is encoded with the
24+ * same [sentinel], so the output never contains it before the trailing
25+ * delimiter.
2126 */
2227 @JvmStatic
2328 @JvmOverloads
24- public fun frame (packet : ByteArray , reduced : Boolean = false): ByteArray {
25- val encoded = if (reduced) Cobsr .encode(packet) else Cobs .encode(packet)
26- // copyOf pads the extra byte with 0x00, which is the delimiter.
27- return encoded.copyOf(encoded.size + 1 )
29+ public fun frame (
30+ packet : ByteArray ,
31+ reduced : Boolean = false,
32+ sentinel : Byte = COBS_DELIMITER ,
33+ ): ByteArray {
34+ val encoded = if (reduced) {
35+ Cobsr .encodeWithSentinel(packet, sentinel)
36+ } else {
37+ Cobs .encodeWithSentinel(packet, sentinel)
38+ }
39+ val framed = encoded.copyOf(encoded.size + 1 )
40+ framed[encoded.size] = sentinel
41+ return framed
2842 }
2943
3044 /* *
31- * Splits [data] on the [COBS_DELIMITER] and decodes each frame, returning
32- * the recovered packets. Trailing bytes after the final delimiter are
33- * ignored. Empty frames are skipped when [skipEmpty] is true.
45+ * Splits [data] on the delimiter and decodes each frame, returning the
46+ * recovered packets. Trailing bytes after the final delimiter are ignored.
47+ * Empty frames are skipped when [skipEmpty] is true.
48+ *
49+ * [sentinel] selects the delimiter byte (and the byte each frame was encoded
50+ * to avoid); it defaults to the `0x00` [COBS_DELIMITER] and must match the
51+ * sentinel used to frame.
3452 *
3553 * @throws CobsDecodeException if a complete frame is not valid encoded data.
3654 */
@@ -40,16 +58,23 @@ public object CobsFraming {
4058 data : ByteArray ,
4159 reduced : Boolean = false,
4260 skipEmpty : Boolean = true,
61+ sentinel : Byte = COBS_DELIMITER ,
4362 ): List <ByteArray > {
4463 val frames = ArrayList <ByteArray >()
4564 var start = 0
4665 for (i in data.indices) {
47- if (data[i].toInt() != 0 ) continue
66+ if (data[i] != sentinel ) continue
4867 if (i == start) {
4968 if (! skipEmpty) frames.add(ByteArray (0 ))
5069 } else {
5170 val slice = data.copyOfRange(start, i)
52- frames.add(if (reduced) Cobsr .decode(slice) else Cobs .decode(slice))
71+ frames.add(
72+ if (reduced) {
73+ Cobsr .decodeWithSentinel(slice, sentinel)
74+ } else {
75+ Cobs .decodeWithSentinel(slice, sentinel)
76+ },
77+ )
5378 }
5479 start = i + 1
5580 }
@@ -58,22 +83,27 @@ public object CobsFraming {
5883}
5984
6085/* *
61- * A stateful, incremental decoder for a stream of [COBS_DELIMITER] -framed data,
62- * for reading COBS packets from a serial/UART link where bytes arrive in
63- * arbitrarily sized chunks that do not align with frame boundaries.
86+ * A stateful, incremental decoder for a stream of delimiter -framed data, for
87+ * reading COBS packets from a serial/UART link where bytes arrive in arbitrarily
88+ * sized chunks that do not align with frame boundaries.
6489 *
6590 * Feed raw bytes with [feed]; it returns any packets completed by a delimiter,
6691 * buffering the rest until a later call. [maxFrameLength] (when greater than 0)
6792 * bounds how many bytes are buffered for a single unterminated frame, guarding
6893 * against unbounded memory use on a noisy link that never sends the delimiter.
6994 * A frame that fails to decode is passed to [onInvalidFrame] if provided
7095 * (decoding then continues), otherwise the [CobsDecodeException] is thrown.
96+ *
97+ * [sentinel] selects the delimiter byte (and the byte each frame was encoded to
98+ * avoid); it defaults to the `0x00` [COBS_DELIMITER] and must match the sentinel
99+ * used to frame the stream.
71100 */
72101public class CobsStreamDecoder @JvmOverloads constructor(
73102 private val reduced : Boolean = false ,
74103 private val skipEmpty : Boolean = true ,
75104 private val maxFrameLength : Int = 0 ,
76105 private val onInvalidFrame : ((CobsDecodeException , ByteArray ) -> Unit )? = null ,
106+ private val sentinel : Byte = COBS_DELIMITER ,
77107) {
78108 // Concatenation allocates a fresh array, so buffered bytes never alias a
79109 // caller-supplied chunk that may be reused for the next read.
@@ -86,7 +116,7 @@ public class CobsStreamDecoder @JvmOverloads constructor(
86116 val out = ArrayList <ByteArray >()
87117 var start = 0
88118 for (i in chunk.indices) {
89- if (chunk[i].toInt() != 0 ) continue
119+ if (chunk[i] != sentinel ) continue
90120 val frame = buffer + chunk.copyOfRange(start, i)
91121 buffer = ByteArray (0 )
92122 start = i + 1
@@ -95,7 +125,13 @@ public class CobsStreamDecoder @JvmOverloads constructor(
95125 continue
96126 }
97127 try {
98- out .add(if (reduced) Cobsr .decode(frame) else Cobs .decode(frame))
128+ out .add(
129+ if (reduced) {
130+ Cobsr .decodeWithSentinel(frame, sentinel)
131+ } else {
132+ Cobs .decodeWithSentinel(frame, sentinel)
133+ },
134+ )
99135 } catch (e: CobsDecodeException ) {
100136 val handler = onInvalidFrame ? : throw e
101137 handler(e, frame)
0 commit comments