@@ -121,6 +121,41 @@ pub fn deserialize(buf: &mut &[u8]) -> Result<Bytes, DeserializeError> {
121121 } )
122122}
123123
124+ /// Returns the position just past the terminator that closes a
125+ /// terminated-bytes-encoded segment within `bytes`, starting the scan at
126+ /// `start`.
127+ ///
128+ /// Walks the encoded stream byte-by-byte, treating `ESCAPE_BYTE` (`0x01`)
129+ /// as an escape that consumes the following byte regardless of value, and
130+ /// stopping at the first `TERMINATOR_BYTE` (`0x00`) outside an escape
131+ /// sequence. Returns `None` if the input ends before the terminator is
132+ /// reached, including the case where the last byte is the start of an
133+ /// escape sequence (truncation-unsafe — the next byte could shift the
134+ /// terminator's position).
135+ ///
136+ /// Unlike [`deserialize`], this function does not decode the payload — it
137+ /// only locates the boundary. Callers that need just the end offset (e.g.
138+ /// a prefix extractor sizing a hashable prefix) can use this without
139+ /// allocating.
140+ pub fn find_terminator_end ( bytes : & [ u8 ] , start : usize ) -> Option < usize > {
141+ let mut i = start;
142+ while i < bytes. len ( ) {
143+ match bytes[ i] {
144+ TERMINATOR_BYTE => return Some ( i + 1 ) ,
145+ ESCAPE_BYTE => {
146+ // Escape byte consumes the next byte. If the stream ends
147+ // mid-escape we cannot decide where the terminator lands.
148+ i += 2 ;
149+ if i > bytes. len ( ) {
150+ return None ;
151+ }
152+ }
153+ _ => i += 1 ,
154+ }
155+ }
156+ None
157+ }
158+
124159/// Creates a [`BytesRange`] for scanning all keys with the given logical prefix.
125160///
126161/// The prefix is first incremented using `lex_increment`, then both bounds
@@ -260,6 +295,83 @@ mod tests {
260295 assert_eq ! ( slice, & [ 0xDE , 0xAD ] ) ;
261296 }
262297
298+ #[ test]
299+ fn should_find_terminator_end_on_simple_input ( ) {
300+ // given — serialize "abc" so we know exactly where the terminator lands
301+ let mut buf = BytesMut :: new ( ) ;
302+ serialize ( b"abc" , & mut buf) ;
303+ // expected layout: 'a' 'b' 'c' 0x00 (4 bytes)
304+
305+ // when / then — the terminator end sits one byte past the terminator
306+ assert_eq ! ( find_terminator_end( & buf, 0 ) , Some ( 4 ) ) ;
307+ }
308+
309+ #[ test]
310+ fn should_find_terminator_end_skipping_escaped_zero_byte ( ) {
311+ // given — payload contains 0x00 which encodes to 0x01 0x01, then "x",
312+ // then the real terminator. The inner 0x01 must NOT be mistaken for
313+ // the terminator.
314+ let mut buf = BytesMut :: new ( ) ;
315+ serialize ( & [ 0x00 , b'x' ] , & mut buf) ;
316+ // layout: 0x01 0x01 'x' 0x00 (4 bytes)
317+
318+ // when / then
319+ assert_eq ! ( find_terminator_end( & buf, 0 ) , Some ( 4 ) ) ;
320+ }
321+
322+ #[ test]
323+ fn should_find_terminator_end_skipping_escaped_escape_byte ( ) {
324+ // given — payload contains 0x01 which encodes to 0x01 0x02. The 0x02
325+ // looks innocuous but the escape pair must be consumed atomically so
326+ // the position is correctly tracked.
327+ let mut buf = BytesMut :: new ( ) ;
328+ serialize ( & [ 0x01 , b'x' ] , & mut buf) ;
329+ // layout: 0x01 0x02 'x' 0x00 (4 bytes)
330+
331+ // when / then
332+ assert_eq ! ( find_terminator_end( & buf, 0 ) , Some ( 4 ) ) ;
333+ }
334+
335+ #[ test]
336+ fn should_find_terminator_end_from_offset_start ( ) {
337+ // given — a synthetic header followed by an encoded payload. Searching
338+ // from offset 3 skips past the header bytes (which may legally contain
339+ // 0x00).
340+ let mut buf = BytesMut :: from ( & [ 0x00 , 0xFF , 0x42 ] [ ..] ) ;
341+ serialize ( b"key" , & mut buf) ;
342+ // layout: [00 FF 42] [k e y 00]; payload terminator at index 6, end at 7
343+
344+ // when / then
345+ assert_eq ! ( find_terminator_end( & buf, 3 ) , Some ( 7 ) ) ;
346+ }
347+
348+ #[ test]
349+ fn should_return_none_when_terminator_absent ( ) {
350+ // given — bytes without a terminator
351+ let bytes: & [ u8 ] = b"abc" ;
352+
353+ // when / then
354+ assert_eq ! ( find_terminator_end( bytes, 0 ) , None ) ;
355+ }
356+
357+ #[ test]
358+ fn should_return_none_when_input_ends_mid_escape ( ) {
359+ // given — last byte is the escape byte itself
360+ let bytes: & [ u8 ] = & [ b'a' , ESCAPE_BYTE ] ;
361+
362+ // when / then
363+ assert_eq ! ( find_terminator_end( bytes, 0 ) , None ) ;
364+ }
365+
366+ #[ test]
367+ fn should_return_none_when_start_is_past_end ( ) {
368+ // given
369+ let bytes: & [ u8 ] = & [ 0x00 ] ;
370+
371+ // when / then
372+ assert_eq ! ( find_terminator_end( bytes, 5 ) , None ) ;
373+ }
374+
263375 #[ test]
264376 fn should_not_have_encoded_prefix_collision ( ) {
265377 // Encoded "a" should not be a prefix of encoded "ab"
0 commit comments