Bitlevel streams for OCaml
1(** Tests for Bitstream library *)
2
3let test_forward_reader_bytes () =
4 let data = Bytes.of_string "\x12\x34\x56\x78" in
5 let r = Bitstream.Forward_reader.of_bytes data in
6 Alcotest.(check int) "byte 0" 0x12 (Bitstream.Forward_reader.read_byte r);
7 Alcotest.(check int) "byte 1" 0x34 (Bitstream.Forward_reader.read_byte r);
8 Alcotest.(check int) "byte 2" 0x56 (Bitstream.Forward_reader.read_byte r);
9 Alcotest.(check int) "byte 3" 0x78 (Bitstream.Forward_reader.read_byte r);
10 (* Reading past end raises End_of_stream *)
11 Alcotest.check_raises "past end" Bitstream.End_of_stream (fun () ->
12 ignore (Bitstream.Forward_reader.read_byte r))
13
14let test_forward_reader_bits () =
15 (* 0x12 = 0001_0010, 0x34 = 0011_0100 in little-endian bits:
16 Reading 4 bits: 0010 = 2
17 Reading 4 bits: 0001 = 1
18 Reading 8 bits: 0011_0100 = 0x34 *)
19 let data = Bytes.of_string "\x12\x34" in
20 let r = Bitstream.Forward_reader.of_bytes data in
21 Alcotest.(check int) "4 bits" 0x2 (Bitstream.Forward_reader.read_bits r 4);
22 Alcotest.(check int) "4 bits" 0x1 (Bitstream.Forward_reader.read_bits r 4);
23 Alcotest.(check int) "8 bits" 0x34 (Bitstream.Forward_reader.read_bits r 8)
24
25let test_forward_reader_mixed () =
26 let data = Bytes.of_string "\xFF\x00\xAB\xCD" in
27 let r = Bitstream.Forward_reader.of_bytes data in
28 Alcotest.(check int) "16 bits LE" 0x00FF (Bitstream.Forward_reader.read_bits r 16);
29 Alcotest.(check int) "remaining bits" 16 (Bitstream.Forward_reader.remaining r);
30 Alcotest.(check int) "remaining bytes" 2 (Bitstream.Forward_reader.remaining_bytes r);
31 let rest = Bitstream.Forward_reader.get_bytes r 2 in
32 Alcotest.(check string) "get_bytes" "\xAB\xCD" (Bytes.to_string rest)
33
34let test_forward_reader_32bit () =
35 let data = Bytes.of_string "\x78\x56\x34\x12" in
36 let r = Bitstream.Forward_reader.of_bytes data in
37 Alcotest.(check int) "32 bits LE" 0x12345678 (Bitstream.Forward_reader.read_bits r 32)
38
39let test_forward_reader_rewind () =
40 let data = Bytes.of_string "\x12\x34\x56" in
41 let r = Bitstream.Forward_reader.of_bytes data in
42 let v1 = Bitstream.Forward_reader.read_bits r 12 in
43 Alcotest.(check int) "first read 12 bits" 0x412 v1; (* 0x12 + lower 4 of 0x34 *)
44 Bitstream.Forward_reader.rewind_bits r 4;
45 Alcotest.(check int) "remaining after rewind" 16 (Bitstream.Forward_reader.remaining r);
46 let v2 = Bitstream.Forward_reader.read_bits r 8 in
47 Alcotest.(check int) "read after rewind" 0x34 v2 (* now at byte 1 *)
48
49let test_forward_reader_align () =
50 let data = Bytes.of_string "\xFF\xAA\xBB" in
51 let r = Bitstream.Forward_reader.of_bytes data in
52 let _ = Bitstream.Forward_reader.read_bits r 3 in
53 Alcotest.(check bool) "not aligned" false (Bitstream.Forward_reader.is_byte_aligned r);
54 Bitstream.Forward_reader.align r;
55 Alcotest.(check bool) "aligned" true (Bitstream.Forward_reader.is_byte_aligned r);
56 Alcotest.(check int) "after align" 0xAA (Bitstream.Forward_reader.read_byte r)
57
58let test_forward_reader_sub () =
59 let data = Bytes.of_string "\x12\x34\x56\x78\x9A" in
60 let r = Bitstream.Forward_reader.of_bytes data in
61 let _ = Bitstream.Forward_reader.read_byte r in
62 let sub = Bitstream.Forward_reader.sub r 2 in
63 Alcotest.(check int) "sub byte 0" 0x34 (Bitstream.Forward_reader.read_byte sub);
64 Alcotest.(check int) "sub byte 1" 0x56 (Bitstream.Forward_reader.read_byte sub);
65 Alcotest.(check int) "parent continues" 0x78 (Bitstream.Forward_reader.read_byte r)
66
67let test_forward_writer_bytes () =
68 let buf = Bytes.create 8 in
69 let w = Bitstream.Forward_writer.of_bytes buf in
70 Bitstream.Forward_writer.write_byte w 0x12;
71 Bitstream.Forward_writer.write_byte w 0x34;
72 let len = Bitstream.Forward_writer.finalize w in
73 Alcotest.(check int) "length" 2 len;
74 Alcotest.(check int) "byte 0" 0x12 (Bytes.get_uint8 buf 0);
75 Alcotest.(check int) "byte 1" 0x34 (Bytes.get_uint8 buf 1)
76
77let test_forward_writer_bits () =
78 let buf = Bytes.create 8 in
79 let w = Bitstream.Forward_writer.of_bytes buf in
80 Bitstream.Forward_writer.write_bits w 0x2 4; (* lower 4 bits *)
81 Bitstream.Forward_writer.write_bits w 0x1 4; (* upper 4 bits *)
82 Bitstream.Forward_writer.write_bits w 0x34 8;
83 let len = Bitstream.Forward_writer.finalize w in
84 Alcotest.(check int) "length" 2 len;
85 Alcotest.(check int) "byte 0" 0x12 (Bytes.get_uint8 buf 0);
86 Alcotest.(check int) "byte 1" 0x34 (Bytes.get_uint8 buf 1)
87
88let test_forward_writer_32bit () =
89 let buf = Bytes.create 8 in
90 let w = Bitstream.Forward_writer.of_bytes buf in
91 Bitstream.Forward_writer.write_bits w 0x12345678 32;
92 let len = Bitstream.Forward_writer.finalize w in
93 Alcotest.(check int) "length" 4 len;
94 Alcotest.(check int) "byte 0" 0x78 (Bytes.get_uint8 buf 0);
95 Alcotest.(check int) "byte 1" 0x56 (Bytes.get_uint8 buf 1);
96 Alcotest.(check int) "byte 2" 0x34 (Bytes.get_uint8 buf 2);
97 Alcotest.(check int) "byte 3" 0x12 (Bytes.get_uint8 buf 3)
98
99let test_forward_roundtrip () =
100 (* Write various bit patterns, then read them back *)
101 let buf = Bytes.create 64 in
102 let w = Bitstream.Forward_writer.of_bytes buf in
103 Bitstream.Forward_writer.write_bits w 0b101 3;
104 Bitstream.Forward_writer.write_bits w 0b11001 5;
105 Bitstream.Forward_writer.write_bits w 0xABCD 16;
106 Bitstream.Forward_writer.write_bits w 0b1111 4;
107 let len = Bitstream.Forward_writer.finalize w in
108
109 let r = Bitstream.Forward_reader.create buf ~pos:0 ~len in
110 Alcotest.(check int) "3 bits" 0b101 (Bitstream.Forward_reader.read_bits r 3);
111 Alcotest.(check int) "5 bits" 0b11001 (Bitstream.Forward_reader.read_bits r 5);
112 Alcotest.(check int) "16 bits" 0xABCD (Bitstream.Forward_reader.read_bits r 16);
113 Alcotest.(check int) "4 bits" 0b1111 (Bitstream.Forward_reader.read_bits r 4)
114
115let test_backward_roundtrip () =
116 (* Backward streams are read in REVERSE order of writing.
117 This matches FSE: encode in reverse order, decode in forward order. *)
118 let w = Bitstream.Backward_writer.create 64 in
119 Bitstream.Backward_writer.write_bits w 0b101 3;
120 Bitstream.Backward_writer.write_bits w 0b11001 5;
121 Bitstream.Backward_writer.write_bits w 0xAB 8;
122 let data = Bitstream.Backward_writer.finalize w in
123
124 let r = Bitstream.Backward_reader.of_bytes data ~pos:0 ~len:(Bytes.length data) in
125 (* Read in REVERSE order (last written = first read) *)
126 Alcotest.(check int) "8 bits (written last)" 0xAB (Bitstream.Backward_reader.read_bits r 8);
127 Alcotest.(check int) "5 bits" 0b11001 (Bitstream.Backward_reader.read_bits r 5);
128 Alcotest.(check int) "3 bits (written first)" 0b101 (Bitstream.Backward_reader.read_bits r 3)
129
130let test_backward_reader_peek () =
131 (* For backward streams, bits are read from MSB to LSB within accumulated data.
132 0x5A = 0101_1010 binary. Reading 4 bits at a time gives:
133 - First 4 bits (high nibble): 0101 = 0x5
134 - Last 4 bits (low nibble): 1010 = 0xA *)
135 let w = Bitstream.Backward_writer.create 64 in
136 Bitstream.Backward_writer.write_bits w 0x5A 8;
137 let data = Bitstream.Backward_writer.finalize w in
138
139 let r = Bitstream.Backward_reader.of_bytes data ~pos:0 ~len:(Bytes.length data) in
140 Alcotest.(check int) "peek 4 (high nibble)" 0x5 (Bitstream.Backward_reader.peek_bits r 4);
141 Alcotest.(check int) "peek 4 again" 0x5 (Bitstream.Backward_reader.peek_bits r 4);
142 Alcotest.(check int) "read 4" 0x5 (Bitstream.Backward_reader.read_bits r 4);
143 Alcotest.(check int) "read 4 (low nibble)" 0xA (Bitstream.Backward_reader.read_bits r 4)
144
145let test_backward_is_empty () =
146 let w = Bitstream.Backward_writer.create 64 in
147 Bitstream.Backward_writer.write_bits w 0xFF 8;
148 let data = Bitstream.Backward_writer.finalize w in
149
150 let r = Bitstream.Backward_reader.of_bytes data ~pos:0 ~len:(Bytes.length data) in
151 Alcotest.(check bool) "not empty" false (Bitstream.Backward_reader.is_empty r);
152 let _ = Bitstream.Backward_reader.read_bits r 8 in
153 Alcotest.(check bool) "empty after read" true (Bitstream.Backward_reader.is_empty r)
154
155let test_backward_empty_stream () =
156 (* Empty stream should raise End_of_stream *)
157 Alcotest.check_raises "empty stream" Bitstream.End_of_stream (fun () ->
158 ignore (Bitstream.Backward_reader.of_bytes (Bytes.empty) ~pos:0 ~len:0))
159
160let test_backward_invalid_padding () =
161 (* Zero byte has no padding marker - should raise *)
162 let data = Bytes.of_string "\x00" in
163 Alcotest.check_raises "zero padding" (Bitstream.Corrupted_stream "invalid padding marker") (fun () ->
164 ignore (Bitstream.Backward_reader.of_bytes data ~pos:0 ~len:1))
165
166let test_edge_cases () =
167 (* Zero bits *)
168 let buf = Bytes.create 8 in
169 let w = Bitstream.Forward_writer.of_bytes buf in
170 Bitstream.Forward_writer.write_bits w 0 0;
171 let len = Bitstream.Forward_writer.finalize w in
172 Alcotest.(check int) "zero bits" 0 len;
173
174 (* Read zero bits *)
175 let data = Bytes.of_string "\xFF" in
176 let r = Bitstream.Forward_reader.of_bytes data in
177 Alcotest.(check int) "read 0 bits" 0 (Bitstream.Forward_reader.read_bits r 0);
178 Alcotest.(check int) "byte still available" 0xFF (Bitstream.Forward_reader.read_byte r)
179
180let test_not_aligned_errors () =
181 let data = Bytes.of_string "\xFF\xAA" in
182 let r = Bitstream.Forward_reader.of_bytes data in
183 let _ = Bitstream.Forward_reader.read_bits r 3 in
184
185 Alcotest.check_raises "read_byte not aligned"
186 (Bitstream.Invalid_state "read_byte: not byte aligned")
187 (fun () -> ignore (Bitstream.Forward_reader.read_byte r));
188
189 Alcotest.check_raises "byte_position not aligned"
190 (Bitstream.Invalid_state "byte_position: not byte aligned")
191 (fun () -> ignore (Bitstream.Forward_reader.byte_position r));
192
193 Alcotest.check_raises "remaining_bytes not aligned"
194 (Bitstream.Invalid_state "remaining_bytes: not byte aligned")
195 (fun () -> ignore (Bitstream.Forward_reader.remaining_bytes r))
196
197let tests = [
198 "forward reader bytes", `Quick, test_forward_reader_bytes;
199 "forward reader bits", `Quick, test_forward_reader_bits;
200 "forward reader mixed", `Quick, test_forward_reader_mixed;
201 "forward reader 32bit", `Quick, test_forward_reader_32bit;
202 "forward reader rewind", `Quick, test_forward_reader_rewind;
203 "forward reader align", `Quick, test_forward_reader_align;
204 "forward reader sub", `Quick, test_forward_reader_sub;
205 "forward writer bytes", `Quick, test_forward_writer_bytes;
206 "forward writer bits", `Quick, test_forward_writer_bits;
207 "forward writer 32bit", `Quick, test_forward_writer_32bit;
208 "forward roundtrip", `Quick, test_forward_roundtrip;
209 "backward roundtrip", `Quick, test_backward_roundtrip;
210 "backward reader peek", `Quick, test_backward_reader_peek;
211 "backward is_empty", `Quick, test_backward_is_empty;
212 "backward empty stream", `Quick, test_backward_empty_stream;
213 "backward invalid padding", `Quick, test_backward_invalid_padding;
214 "edge cases", `Quick, test_edge_cases;
215 "not aligned errors", `Quick, test_not_aligned_errors;
216]
217
218let () =
219 Alcotest.run "Bitstream" [
220 "bitstream", tests;
221 ]