@@ -69,6 +69,85 @@ class FeaturesTest {
6969 }
7070 }
7171
72+ // Golden COBS/R encodings ported from the reference suite: valid wire
73+ // sequences, including reduced final blocks (e.g. `bytes(0x02)`), embedded
74+ // zeros, and maximal `0xFF` length codes.
75+ private val cobsrGolden: List <ByteArray > = listOf (
76+ bytes(0x01 ),
77+ bytes(0x02 , 0x01 ),
78+ bytes(0x02 ),
79+ bytes(0x03 ),
80+ bytes(0x7E ),
81+ bytes(0x7F ),
82+ bytes(0x80 ),
83+ bytes(0xD5 ),
84+ bytes(0xFE ),
85+ bytes(0xFF ),
86+ cat(bytes(0x03 ), ascii(" a" ), bytes(0x02 )),
87+ cat(bytes(0x03 ), ascii(" a" )),
88+ cat(bytes(0xFF ), ascii(" a" )),
89+ bytes(0x06 , 0x05 , 0x04 , 0x03 , 0x02 , 0x01 ),
90+ cat(bytes(0x35 ), ascii(" 1234" )),
91+ cat(bytes(0x06 ), ascii(" 12345" ), bytes(0x05 , 0x04 , 0x03 , 0x02 , 0x01 )),
92+ cat(bytes(0x06 ), ascii(" 12345" ), ascii(" 9678" )),
93+ cat(bytes(0x01 , 0x06 ), ascii(" 12345" ), ascii(" 9678" )),
94+ cat(bytes(0x06 ), ascii(" 12345" ), bytes(0x05 ), ascii(" 6789" ), bytes(0x01 )),
95+ bytes(0x01 , 0x01 ),
96+ bytes(0x01 , 0x01 , 0x01 ),
97+ bytes(0x01 , 0x01 , 0x01 , 0x01 ),
98+ cat(bytes(0xFE ), range(1 , 254 )),
99+ cat(bytes(0xFF ), range(1 , 255 )),
100+ cat(bytes(0xFF ), range(1 , 255 ), bytes(0xFF )),
101+ cat(bytes(0x01 , 0xFF ), range(1 , 255 ), bytes(0xFF )),
102+ cat(bytes(0xFF ), range(2 , 255 )),
103+ )
104+
105+ /* *
106+ * The in-place COBS/R decoder must be byte-identical to the slice decoder on
107+ * every input and agree on accept/reject. Checked over the golden vectors and
108+ * a large seeded-random corpus: half well-formed encodings of random payloads
109+ * (including reduced final blocks) and half arbitrary bytes, which stress the
110+ * reject paths with embedded zeros and length codes running past the end.
111+ */
112+ @Test
113+ fun cobsrDecodeInPlaceMatchesDecode () {
114+ for (encoded in cobsrGolden) {
115+ for (s in sentinels) assertCobsrInPlaceMatchesSlice(encoded, s)
116+ }
117+
118+ val rng = Random (0x0C0B5A17 )
119+ repeat(20000 ) {
120+ val wire = if (rng.nextBoolean()) {
121+ val len = rng.nextInt(0 , 701 )
122+ Cobsr .encode(ByteArray (len) { rng.nextInt(0 , 256 ).toByte() })
123+ } else {
124+ val len = rng.nextInt(0 , 40 )
125+ ByteArray (len) { rng.nextInt(0 , 256 ).toByte() }
126+ }
127+ for (s in sentinels) assertCobsrInPlaceMatchesSlice(wire, s)
128+ }
129+ }
130+
131+ /* * Asserts in-place COBS/R decode agrees with the slice decoder: value and error. */
132+ private fun assertCobsrInPlaceMatchesSlice (wire : ByteArray , sentinel : Byte ) {
133+ val slice = runCatching { Cobsr .decodeWithSentinel(wire, sentinel) }
134+ val buf = wire.copyOf()
135+ val inPlace = runCatching { Cobsr .decodeInPlace(buf, sentinel) }
136+
137+ if (slice.isSuccess) {
138+ assertTrue(
139+ " in-place must accept what the slice decoder accepts" ,
140+ inPlace.isSuccess,
141+ )
142+ assertArrayEquals(slice.getOrThrow(), buf.copyOf(inPlace.getOrThrow()))
143+ } else {
144+ assertTrue(
145+ " in-place must reject what the slice decoder rejects" ,
146+ inPlace.exceptionOrNull() is CobsDecodeException ,
147+ )
148+ }
149+ }
150+
72151 @Test
73152 fun framingWithSentinelRoundTrips () {
74153 val rng = Random (0x57EA9000 )
0 commit comments