@@ -113,12 +113,30 @@ impl TryFrom<u8> for Version {
113113
114114// ---- Bit Manipulation Helpers ----
115115
116+ /// Read bit at position `bit_pos` from byte slice (MSB-first ordering).
117+ fn get_bit ( data : & [ u8 ] , bit_pos : usize ) -> bool {
118+ let byte_idx = bit_pos / 8 ;
119+ let bit_idx = 7 - ( bit_pos % 8 ) ;
120+ ( data[ byte_idx] >> bit_idx) & 1 == 1
121+ }
122+
123+ /// Extract `count` bits starting at `bit_pos` as a u32 (MSB-first).
124+ fn extract_bits ( data : & [ u8 ] , bit_pos : usize , count : usize ) -> u32 {
125+ let mut value = 0u32 ;
126+ for i in 0 ..count {
127+ value = ( value << 1 ) | u32:: from ( get_bit ( data, bit_pos + i) ) ;
128+ }
129+ value
130+ }
131+
116132/// Convert a bit slice (big-endian, MSB first) to a u32.
133+ #[ cfg( test) ]
117134fn bits_to_u32 ( bits : & [ bool ] ) -> u32 {
118135 bits. iter ( ) . fold ( 0u32 , |acc, & b| ( acc << 1 ) | u32:: from ( b) )
119136}
120137
121138/// Convert bytes to a bit vector (big-endian, MSB first).
139+ #[ cfg( test) ]
122140fn bytes_to_bits ( bytes : & [ u8 ] ) -> Vec < bool > {
123141 bytes
124142 . iter ( )
@@ -180,28 +198,31 @@ fn encode_varnibble(value: u32) -> IsccResult<Vec<bool>> {
180198 }
181199}
182200
183- /// Decode the first varnibble from a bit slice.
201+ /// Decode the first varnibble from a byte slice at the given bit position .
184202///
185- /// Returns the decoded integer and the number of bits consumed.
186- fn decode_varnibble ( bits : & [ bool ] ) -> IsccResult < ( u32 , usize ) > {
187- if bits. len ( ) < 4 {
203+ /// Operates directly on `&[u8]` with bitwise extraction, avoiding any
204+ /// intermediate `Vec<bool>` allocation. Returns the decoded integer and
205+ /// the number of bits consumed.
206+ fn decode_varnibble_from_bytes ( data : & [ u8 ] , bit_pos : usize ) -> IsccResult < ( u32 , usize ) > {
207+ let available = data. len ( ) * 8 - bit_pos;
208+ if available < 4 {
188209 return Err ( IsccError :: InvalidInput (
189210 "insufficient bits for varnibble" . into ( ) ,
190211 ) ) ;
191212 }
192213
193- if !bits [ 0 ] {
214+ if !get_bit ( data , bit_pos ) {
194215 // 0xxx — 4 bits, values 0–7
195- Ok ( ( bits_to_u32 ( & bits [ .. 4 ] ) , 4 ) )
196- } else if bits . len ( ) >= 8 && !bits [ 1 ] {
216+ Ok ( ( extract_bits ( data , bit_pos , 4 ) , 4 ) )
217+ } else if available >= 8 && !get_bit ( data , bit_pos + 1 ) {
197218 // 10xxxxxx — 8 bits, values 8–71
198- Ok ( ( bits_to_u32 ( & bits [ 2 .. 8 ] ) + 8 , 8 ) )
199- } else if bits . len ( ) >= 12 && !bits [ 2 ] {
219+ Ok ( ( extract_bits ( data , bit_pos + 2 , 6 ) + 8 , 8 ) )
220+ } else if available >= 12 && !get_bit ( data , bit_pos + 2 ) {
200221 // 110xxxxxxxxx — 12 bits, values 72–583
201- Ok ( ( bits_to_u32 ( & bits [ 3 .. 12 ] ) + 72 , 12 ) )
202- } else if bits . len ( ) >= 16 && !bits [ 3 ] {
222+ Ok ( ( extract_bits ( data , bit_pos + 3 , 9 ) + 72 , 12 ) )
223+ } else if available >= 16 && !get_bit ( data , bit_pos + 3 ) {
203224 // 1110xxxxxxxxxxxx — 16 bits, values 584–4679
204- Ok ( ( bits_to_u32 ( & bits [ 4 .. 16 ] ) + 584 , 16 ) )
225+ Ok ( ( extract_bits ( data , bit_pos + 4 , 12 ) + 584 , 16 ) )
205226 } else {
206227 Err ( IsccError :: InvalidInput (
207228 "invalid varnibble prefix or insufficient bits" . into ( ) ,
@@ -239,35 +260,35 @@ pub fn encode_header(
239260
240261/// Decode ISCC header from bytes.
241262///
242- /// Returns `(MainType, SubType, Version, length, tail_bytes)` where
243- /// `tail_bytes` contains any remaining data after the header.
263+ /// Operates directly on `&[u8]` with bitwise extraction, avoiding any
264+ /// intermediate `Vec<bool>` allocation. Returns `(MainType, SubType,
265+ /// Version, length, tail_bytes)` where `tail_bytes` contains any
266+ /// remaining data after the header.
244267pub fn decode_header ( data : & [ u8 ] ) -> IsccResult < ( MainType , SubType , Version , u32 , Vec < u8 > ) > {
245- let bits = bytes_to_bits ( data) ;
246- let mut pos = 0 ;
268+ let mut bit_pos = 0 ;
247269
248- let ( mtype_val, consumed) = decode_varnibble ( & bits [ pos.. ] ) ?;
249- pos += consumed;
270+ let ( mtype_val, consumed) = decode_varnibble_from_bytes ( data , bit_pos ) ?;
271+ bit_pos += consumed;
250272
251- let ( stype_val, consumed) = decode_varnibble ( & bits [ pos.. ] ) ?;
252- pos += consumed;
273+ let ( stype_val, consumed) = decode_varnibble_from_bytes ( data , bit_pos ) ?;
274+ bit_pos += consumed;
253275
254- let ( version_val, consumed) = decode_varnibble ( & bits [ pos.. ] ) ?;
255- pos += consumed;
276+ let ( version_val, consumed) = decode_varnibble_from_bytes ( data , bit_pos ) ?;
277+ bit_pos += consumed;
256278
257- let ( length, consumed) = decode_varnibble ( & bits [ pos.. ] ) ?;
258- pos += consumed;
279+ let ( length, consumed) = decode_varnibble_from_bytes ( data , bit_pos ) ?;
280+ bit_pos += consumed;
259281
260282 // Strip 4-bit zero padding if header bits are not byte-aligned.
261283 // Since each varnibble is a multiple of 4 bits, misalignment is always 4 bits.
262- if pos % 8 != 0 {
263- let pad_end = pos + 4 ;
264- if pad_end <= bits. len ( ) && bits[ pos..pad_end] . iter ( ) . all ( |& b| !b) {
265- pos = pad_end;
266- }
284+ if bit_pos % 8 != 0 && bit_pos + 4 <= data. len ( ) * 8 && extract_bits ( data, bit_pos, 4 ) == 0 {
285+ bit_pos += 4 ;
267286 }
268287
269- let tail = if pos < bits. len ( ) {
270- bits_to_bytes ( & bits[ pos..] )
288+ // Advance to next byte boundary for tail extraction
289+ let tail_byte_start = bit_pos. div_ceil ( 8 ) ;
290+ let tail = if tail_byte_start < data. len ( ) {
291+ data[ tail_byte_start..] . to_vec ( )
271292 } else {
272293 vec ! [ ]
273294 } ;
@@ -557,7 +578,8 @@ mod tests {
557578 let test_values = [ 0 , 1 , 7 , 8 , 71 , 72 , 583 , 584 , 4679 ] ;
558579 for & value in & test_values {
559580 let bits = encode_varnibble ( value) . unwrap ( ) ;
560- let ( decoded, consumed) = decode_varnibble ( & bits) . unwrap ( ) ;
581+ let bytes = bits_to_bytes ( & bits) ;
582+ let ( decoded, consumed) = decode_varnibble_from_bytes ( & bytes, 0 ) . unwrap ( ) ;
561583 assert_eq ! ( decoded, value, "roundtrip failed for value {value}" ) ;
562584 assert_eq ! ( consumed, bits. len( ) , "consumed mismatch for value {value}" ) ;
563585 }
@@ -600,6 +622,67 @@ mod tests {
600622 ) ; // 10 000000
601623 }
602624
625+ // ---- Bitwise extraction tests ----
626+
627+ #[ test]
628+ fn test_extract_bits_basic ( ) {
629+ // 0xA5 = 1010_0101 in binary
630+ let data = [ 0xA5u8 ] ;
631+ assert_eq ! ( extract_bits( & data, 0 , 4 ) , 0b1010 ) ; // first nibble
632+ assert_eq ! ( extract_bits( & data, 4 , 4 ) , 0b0101 ) ; // second nibble
633+ assert_eq ! ( extract_bits( & data, 0 , 8 ) , 0xA5 ) ; // full byte
634+ assert_eq ! ( extract_bits( & data, 1 , 3 ) , 0b010 ) ; // bits 1-3
635+ assert_eq ! ( extract_bits( & data, 0 , 1 ) , 1 ) ; // MSB
636+ assert_eq ! ( extract_bits( & data, 7 , 1 ) , 1 ) ; // LSB
637+
638+ // Multi-byte: 0xFF 0x00 = 1111_1111 0000_0000
639+ let data2 = [ 0xFF , 0x00 ] ;
640+ assert_eq ! ( extract_bits( & data2, 0 , 8 ) , 0xFF ) ;
641+ assert_eq ! ( extract_bits( & data2, 8 , 8 ) , 0x00 ) ;
642+ assert_eq ! ( extract_bits( & data2, 4 , 8 ) , 0xF0 ) ; // crossing byte boundary
643+ assert_eq ! ( extract_bits( & data2, 6 , 4 ) , 0b1100 ) ; // crossing byte boundary
644+ }
645+
646+ #[ test]
647+ fn test_decode_varnibble_from_bytes_boundary_values ( ) {
648+ // Test decoding at non-zero bit offsets within a byte slice.
649+ // Encode two varnibbles into a single byte sequence and decode both.
650+
651+ // varnibble(3) = 0011 (4 bits) + varnibble(8) = 10_000000 (8 bits) = 12 bits
652+ let bits_3 = encode_varnibble ( 3 ) . unwrap ( ) ;
653+ let bits_8 = encode_varnibble ( 8 ) . unwrap ( ) ;
654+ let mut combined_bits = bits_3. clone ( ) ;
655+ combined_bits. extend ( & bits_8) ;
656+ let bytes = bits_to_bytes ( & combined_bits) ;
657+
658+ // Decode first varnibble at bit 0
659+ let ( val1, consumed1) = decode_varnibble_from_bytes ( & bytes, 0 ) . unwrap ( ) ;
660+ assert_eq ! ( val1, 3 ) ;
661+ assert_eq ! ( consumed1, 4 ) ;
662+
663+ // Decode second varnibble at bit 4 (non-zero offset)
664+ let ( val2, consumed2) = decode_varnibble_from_bytes ( & bytes, 4 ) . unwrap ( ) ;
665+ assert_eq ! ( val2, 8 ) ;
666+ assert_eq ! ( consumed2, 8 ) ;
667+
668+ // Test with a 3-nibble value at offset
669+ // varnibble(0) = 0000 (4 bits) + varnibble(72) = 110_000000000 (12 bits)
670+ let bits_0 = encode_varnibble ( 0 ) . unwrap ( ) ;
671+ let bits_72 = encode_varnibble ( 72 ) . unwrap ( ) ;
672+ let mut combined2 = bits_0;
673+ combined2. extend ( & bits_72) ;
674+ let bytes2 = bits_to_bytes ( & combined2) ;
675+
676+ let ( val3, consumed3) = decode_varnibble_from_bytes ( & bytes2, 4 ) . unwrap ( ) ;
677+ assert_eq ! ( val3, 72 ) ;
678+ assert_eq ! ( consumed3, 12 ) ;
679+
680+ // Test insufficient bits at offset
681+ let single_byte = [ 0x00u8 ] ;
682+ let result = decode_varnibble_from_bytes ( & single_byte, 6 ) ;
683+ assert ! ( result. is_err( ) , "should fail with only 2 bits available" ) ;
684+ }
685+
603686 // ---- Header encoding tests ----
604687
605688 #[ test]
0 commit comments