RPNX::Compress
Self-contained C++20 compression and ZIP library
 
Loading...
Searching...
No Matches
bzip2.hpp
Go to the documentation of this file.
1#ifndef RPNX_COMPRESSION_IMPLEMENTATION_BZIP2_HPP
2#define RPNX_COMPRESSION_IMPLEMENTATION_BZIP2_HPP
3
4#include <algorithm>
5#include <array>
6#include <bit>
7#include <cstddef>
8#include <cstdint>
9#include <iterator>
10#include <limits>
11#include <optional>
12#include <span>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
18
19/**
20 * @file
21 * @brief Native bzip2 block transforms, entropy coding, and stream adapters.
22 */
23
24/** @brief Internal implementation of bzip2 compression and decompression. */
26{
27
28 /** One node in a canonical bzip2 Huffman decoding tree. */
30 {
31 /** @brief Child selected by a zero bit, if present. */
32 std::optional< std::uint16_t > zero_child;
33 /** @brief Child selected by a one bit, if present. */
34 std::optional< std::uint16_t > one_child;
35 /** @brief Decoded symbol for a leaf node. */
36 std::optional< std::uint16_t > symbol;
37 };
38
39 /** Canonical bzip2 Huffman decoding tree. */
41 {
42 /** @brief Tree nodes with the root at index zero. */
43 std::vector< huffman_node > nodes;
44 };
45
46 /** Burrows-Wheeler last column and its original rotation index. */
48 {
49 /** @brief Final byte of each lexicographically sorted cyclic rotation. */
50 std::vector< std::byte > last_column;
51 /** @brief Row containing the unrotated source block. */
52 std::size_t original_pointer;
53 };
54
55 /**
56 * @brief Computes the non-reflected CRC-32 variant used by bzip2 blocks.
57 * @param input Uncompressed block bytes.
58 * @return Finalized bzip2 CRC-32.
59 */
60 [[nodiscard]] inline std::uint32_t crc32(std::span< std::byte const > input) noexcept
61 {
62 std::uint32_t crc = 0xffffffffU;
63 for (std::byte value : input)
64 {
65 crc ^= static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(value)) << 24U;
66 for (std::uint8_t bit = 0U; bit < 8U; ++bit)
67 {
68 crc = (crc & 0x80000000U) != 0U ? (crc << 1U) ^ 0x04c11db7U : crc << 1U;
69 }
70 }
71 return ~crc;
72 }
73
74 /**
75 * @brief Builds a canonical Huffman tree from bzip2 code lengths.
76 * @param lengths Code length for each symbol in symbol order.
77 * @return Validated decoding tree.
78 * @throws compression_error If a length is invalid or the tree is oversubscribed.
79 */
80 [[nodiscard]] inline huffman_table build_huffman_table(std::span< std::uint8_t const > lengths)
81 {
82 std::uint8_t minimum_length = 21U;
83 std::uint8_t maximum_length = 0U;
84 for (std::uint8_t length : lengths)
85 {
86 if (length == 0U || length > 20U)
87 {
88 throw compression_error(error_code::invalid_data, format::bzip2, "invalid bzip2 Huffman code length");
89 }
90 minimum_length = std::min(minimum_length, length);
91 maximum_length = std::max(maximum_length, length);
92 }
93
94 huffman_table table{{huffman_node{}}};
95 std::uint32_t code = 0U;
96 for (std::uint8_t length = minimum_length; length <= maximum_length; ++length)
97 {
98 if (length != minimum_length)
99 {
100 code <<= 1U;
101 }
102 for (std::size_t symbol = 0U; symbol < lengths.size(); ++symbol)
103 {
104 if (lengths[symbol] != length)
105 {
106 continue;
107 }
108 if (code >= (std::uint32_t{1U} << length))
109 {
110 throw compression_error(error_code::invalid_data, format::bzip2, "oversubscribed bzip2 Huffman tree");
111 }
112 std::size_t node_index = 0U;
113 for (std::uint8_t depth = 0U; depth < length; ++depth)
114 {
115 if (table.nodes[node_index].symbol.has_value())
116 {
117 throw compression_error(error_code::invalid_data, format::bzip2, "overlapping bzip2 Huffman prefix codes");
118 }
119 std::uint8_t const bit = static_cast< std::uint8_t >((code >> (length - depth - 1U)) & 1U);
120 std::optional< std::uint16_t >& child = bit == 0U ? table.nodes[node_index].zero_child : table.nodes[node_index].one_child;
121 if (!child.has_value())
122 {
123 if (table.nodes.size() >= std::numeric_limits< std::uint16_t >::max())
124 {
125 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 Huffman tree is too large");
126 }
127 std::uint16_t const new_node = static_cast< std::uint16_t >(table.nodes.size());
128 child = new_node;
129 node_index = new_node;
130 table.nodes.push_back(huffman_node{});
131 }
132 else
133 {
134 node_index = *child;
135 }
136 }
137 if (table.nodes[node_index].symbol.has_value() || table.nodes[node_index].zero_child.has_value() || table.nodes[node_index].one_child.has_value())
138 {
139 throw compression_error(error_code::invalid_data, format::bzip2, "duplicate bzip2 Huffman prefix code");
140 }
141 table.nodes[node_index].symbol = static_cast< std::uint16_t >(symbol);
142 ++code;
143 }
144 }
145 return table;
146 }
147
148 /**
149 * @brief Decodes one symbol with a canonical bzip2 Huffman tree.
150 * @tparam reader_type MSB-first reader providing read_bits().
151 * @param reader Source bit reader.
152 * @param table Validated decoding tree.
153 * @return Decoded symbol index.
154 * @throws compression_error If the bit sequence does not identify a leaf.
155 */
156 template < typename reader_type >
157 [[nodiscard]] std::uint16_t decode_huffman_symbol(reader_type& reader, huffman_table const& table)
158 {
159 std::size_t node_index = 0U;
160 while (!table.nodes[node_index].symbol.has_value())
161 {
162 std::uint8_t const bit = static_cast< std::uint8_t >(reader.read_bits(1U));
163 std::optional< std::uint16_t > const child = bit == 0U ? table.nodes[node_index].zero_child : table.nodes[node_index].one_child;
164 if (!child.has_value() || *child >= table.nodes.size())
165 {
166 throw compression_error(error_code::invalid_data, format::bzip2, "invalid bzip2 Huffman code");
167 }
168 node_index = *child;
169 }
170 return *table.nodes[node_index].symbol;
171 }
172
173 /**
174 * @brief Applies bzip2's first run-length transform to one source block.
175 * @param input Source block.
176 * @return Run-length transformed bytes.
177 */
178 [[nodiscard]] inline std::vector< std::byte > encode_first_run_length(std::span< std::byte const > input)
179 {
180 std::vector< std::byte > output;
181 output.reserve(input.size());
182 std::size_t position = 0U;
183 while (position < input.size())
184 {
185 std::size_t run_end = position + 1U;
186 while (run_end < input.size() && input[run_end] == input[position])
187 {
188 ++run_end;
189 }
190 std::size_t remaining = run_end - position;
191 while (remaining != 0U)
192 {
193 std::size_t const chunk_size = std::min< std::size_t >(remaining, 259U);
194 if (chunk_size < 4U)
195 {
196 output.insert(output.end(), chunk_size, input[position]);
197 }
198 else
199 {
200 output.insert(output.end(), 4U, input[position]);
201 output.push_back(static_cast< std::byte >(chunk_size - 4U));
202 }
203 remaining -= chunk_size;
204 }
205 position = run_end;
206 }
207 return output;
208 }
209
210 /**
211 * @brief Sorts cyclic rotations and produces the Burrows-Wheeler last column.
212 * @param input Run-length transformed block.
213 * @return Last-column bytes and the original row index.
214 * @throws compression_error If the rotation table loses the original row.
215 */
216 [[nodiscard]] inline burrows_wheeler_result burrows_wheeler_transform(std::span< std::byte const > input)
217 {
218 if (input.empty())
219 {
220 throw compression_error(error_code::invalid_data, format::bzip2, "cannot transform an empty bzip2 block");
221 }
222 std::size_t const size = input.size();
223 std::vector< std::size_t > order(size, 0U);
224 std::vector< std::size_t > classes(size, 0U);
225 std::array< std::size_t, 256U > byte_counts{};
226 for (std::byte value : input)
227 {
228 ++byte_counts[std::to_integer< std::uint8_t >(value)];
229 }
230 std::array< std::size_t, 256U > byte_positions{};
231 for (std::size_t byte = 1U; byte < byte_positions.size(); ++byte)
232 {
233 byte_positions[byte] = byte_positions[byte - 1U] + byte_counts[byte - 1U];
234 }
235 for (std::size_t index = 0U; index < size; ++index)
236 {
237 std::uint8_t const value = std::to_integer< std::uint8_t >(input[index]);
238 order[byte_positions[value]++] = index;
239 }
240 std::size_t class_count = 1U;
241 classes[order[0U]] = 0U;
242 for (std::size_t index = 1U; index < size; ++index)
243 {
244 if (input[order[index]] != input[order[index - 1U]])
245 {
246 ++class_count;
247 }
248 classes[order[index]] = class_count - 1U;
249 }
250
251 std::vector< std::size_t > shifted(size, 0U);
252 std::vector< std::size_t > new_classes(size, 0U);
253 for (std::size_t shift = 1U; shift < size; shift <<= 1U)
254 {
255 for (std::size_t index = 0U; index < size; ++index)
256 {
257 shifted[index] = order[index] >= shift ? order[index] - shift : order[index] + size - shift;
258 }
259 std::vector< std::size_t > counts(class_count, 0U);
260 for (std::size_t index : shifted)
261 {
262 ++counts[classes[index]];
263 }
264 std::vector< std::size_t > positions(class_count, 0U);
265 for (std::size_t class_index = 1U; class_index < class_count; ++class_index)
266 {
267 positions[class_index] = positions[class_index - 1U] + counts[class_index - 1U];
268 }
269 for (std::size_t index : shifted)
270 {
271 std::size_t const class_index = classes[index];
272 order[positions[class_index]++] = index;
273 }
274
275 std::size_t new_class_count = 1U;
276 new_classes[order[0U]] = 0U;
277 for (std::size_t index = 1U; index < size; ++index)
278 {
279 std::size_t const current = order[index];
280 std::size_t const previous = order[index - 1U];
281 if (classes[current] != classes[previous] || classes[(current + shift) % size] != classes[(previous + shift) % size])
282 {
283 ++new_class_count;
284 }
285 new_classes[current] = new_class_count - 1U;
286 }
287 classes.swap(new_classes);
288 class_count = new_class_count;
289 if (shift > size / 2U)
290 {
291 break;
292 }
293 }
294
295 std::vector< std::byte > last_column;
296 last_column.reserve(size);
297 std::optional< std::size_t > original_pointer;
298 for (std::size_t row = 0U; row < size; ++row)
299 {
300 std::size_t const rotation = order[row];
301 if (rotation == 0U)
302 {
303 original_pointer = row;
304 }
305 last_column.push_back(input[rotation == 0U ? size - 1U : rotation - 1U]);
306 }
307 if (!original_pointer.has_value())
308 {
309 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 rotation sort lost the original row");
310 }
311 return burrows_wheeler_result{std::move(last_column), *original_pointer};
312 }
313
314 /** Encode one bzip2 block with deterministic canonical Huffman tables. */
315 /**
316 * @brief Encodes one bzip2 block and updates the stream checksum.
317 * @tparam writer_type MSB-first writer providing write_bits().
318 * @param writer Destination bit writer.
319 * @param input Uncompressed source block.
320 * @param combined_crc Rolling stream checksum updated in place.
321 */
322 template < typename writer_type >
323 void compress_block(writer_type& writer, std::span< std::byte const > input, std::uint32_t& combined_crc)
324 {
325 std::uint32_t const block_crc = crc32(input);
326 combined_crc = ((combined_crc << 1U) | (combined_crc >> 31U)) ^ block_crc;
327 std::vector< std::byte > run_length_encoded = encode_first_run_length(input);
328 burrows_wheeler_result transformed = burrows_wheeler_transform(run_length_encoded);
329
330 std::array< bool, 256U > used{};
331 for (std::byte value : transformed.last_column)
332 {
333 used[std::to_integer< std::uint8_t >(value)] = true;
334 }
335 std::vector< std::uint8_t > alphabet;
336 for (std::size_t symbol = 0U; symbol < used.size(); ++symbol)
337 {
338 if (used[symbol])
339 {
340 alphabet.push_back(static_cast< std::uint8_t >(symbol));
341 }
342 }
343
344 std::vector< std::uint8_t > move_to_front = alphabet;
345 std::vector< std::uint16_t > codes;
346 codes.reserve(transformed.last_column.size() + 1U);
347 std::size_t zero_run = 0U;
348 auto flush_zero_run = [&]
349 {
350 if (zero_run == 0U)
351 {
352 return;
353 }
354 std::size_t value = zero_run - 1U;
355 while (true)
356 {
357 codes.push_back(static_cast< std::uint16_t >((value & 1U) != 0U ? 1U : 0U));
358 if (value < 2U)
359 {
360 break;
361 }
362 value = (value - 2U) / 2U;
363 }
364 zero_run = 0U;
365 };
366 for (std::byte byte : transformed.last_column)
367 {
368 std::uint8_t const symbol = std::to_integer< std::uint8_t >(byte);
369 std::size_t mtf_index = 0U;
370 while (mtf_index < move_to_front.size() && move_to_front[mtf_index] != symbol)
371 {
372 ++mtf_index;
373 }
374 if (mtf_index == move_to_front.size())
375 {
376 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 MTF encoder lost a block symbol");
377 }
378 if (mtf_index == 0U)
379 {
380 ++zero_run;
381 continue;
382 }
383 flush_zero_run();
384 codes.push_back(static_cast< std::uint16_t >(mtf_index + 1U));
385 for (std::size_t index = mtf_index; index > 0U; --index)
386 {
387 move_to_front[index] = move_to_front[index - 1U];
388 }
389 move_to_front[0U] = symbol;
390 }
391 flush_zero_run();
392 std::size_t const alphabet_size = alphabet.size() + 2U;
393 codes.push_back(static_cast< std::uint16_t >(alphabet_size - 1U));
394
395 writer.write_bits(0x314159265359ULL, 48U);
396 writer.write_bits(block_crc, 32U);
397 writer.write_bits(0U, 1U);
398 writer.write_bits(transformed.original_pointer, 24U);
399 std::uint16_t range_mask = 0U;
400 std::array< std::uint16_t, 16U > symbol_masks{};
401 for (std::size_t symbol = 0U; symbol < used.size(); ++symbol)
402 {
403 if (used[symbol])
404 {
405 std::size_t const range = symbol / 16U;
406 range_mask |= static_cast< std::uint16_t >(std::uint16_t{1U} << (15U - range));
407 symbol_masks[range] |= static_cast< std::uint16_t >(std::uint16_t{1U} << (15U - symbol % 16U));
408 }
409 }
410 writer.write_bits(range_mask, 16U);
411 for (std::size_t range = 0U; range < symbol_masks.size(); ++range)
412 {
413 if (symbol_masks[range] != 0U)
414 {
415 writer.write_bits(symbol_masks[range], 16U);
416 }
417 }
418
419 writer.write_bits(2U, 3U);
420 std::size_t const selector_count = (codes.size() + 49U) / 50U;
421 if (selector_count == 0U || selector_count > 18002U)
422 {
423 throw compression_error(error_code::invalid_data, format::bzip2, "native bzip2 selector count exceeds the format limit");
424 }
425 writer.write_bits(selector_count, 15U);
426 for (std::size_t selector = 0U; selector < selector_count; ++selector)
427 {
428 writer.write_bits(0U, 1U);
429 }
430
431 std::uint8_t const code_length = static_cast< std::uint8_t >(std::bit_width(alphabet_size - 1U));
432 for (std::size_t group = 0U; group < 2U; ++group)
433 {
434 writer.write_bits(code_length, 5U);
435 for (std::size_t symbol = 0U; symbol < alphabet_size; ++symbol)
436 {
437 writer.write_bits(0U, 1U);
438 }
439 }
440 for (std::uint16_t code : codes)
441 {
442 writer.write_bits(code, code_length);
443 }
444 }
445
446 /** Decode one bzip2 block after its block magic. */
447 /**
448 * @brief Decodes one bzip2 block and updates the stream checksum.
449 * @tparam reader_type MSB-first reader providing read_bits().
450 * @param reader Source bit reader positioned after the block magic.
451 * @param block_size_limit Maximum transformed block size declared by the stream.
452 * @param maximum_output_size Maximum uncompressed bytes this block may produce.
453 * @param combined_crc Rolling stream checksum updated in place.
454 * @return Uncompressed block data.
455 * @throws compression_error If entropy data, transforms, size, or checksum is invalid.
456 */
457 template < typename reader_type >
458 [[nodiscard]] std::vector< std::byte > decompress_block(reader_type& reader, std::size_t block_size_limit, std::size_t maximum_output_size, std::uint32_t& combined_crc)
459 {
460 std::uint32_t const expected_block_crc = static_cast< std::uint32_t >(reader.read_bits(32U));
461 if (reader.read_bits(1U) != 0U)
462 {
463 throw compression_error(error_code::unsupported_feature, format::bzip2, "deprecated randomized bzip2 blocks are not supported");
464 }
465 std::size_t const original_pointer = static_cast< std::size_t >(reader.read_bits(24U));
466
467 std::uint16_t const used_ranges = static_cast< std::uint16_t >(reader.read_bits(16U));
468 std::vector< std::uint8_t > symbols;
469 symbols.reserve(256U);
470 for (std::uint8_t range = 0U; range < 16U; ++range)
471 {
472 if ((used_ranges & (std::uint16_t{1U} << (15U - range))) == 0U)
473 {
474 continue;
475 }
476 std::uint16_t const used_symbols = static_cast< std::uint16_t >(reader.read_bits(16U));
477 for (std::uint8_t symbol = 0U; symbol < 16U; ++symbol)
478 {
479 if ((used_symbols & (std::uint16_t{1U} << (15U - symbol))) != 0U)
480 {
481 symbols.push_back(static_cast< std::uint8_t >(range * 16U + symbol));
482 }
483 }
484 }
485 if (symbols.empty())
486 {
487 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 block has no symbol alphabet");
488 }
489
490 std::size_t const group_count = static_cast< std::size_t >(reader.read_bits(3U));
491 if (group_count < 2U || group_count > 6U)
492 {
493 throw compression_error(error_code::invalid_data, format::bzip2, "invalid number of bzip2 Huffman groups");
494 }
495 std::size_t const selector_count = static_cast< std::size_t >(reader.read_bits(15U));
496 if (selector_count == 0U || selector_count > 18002U)
497 {
498 throw compression_error(error_code::invalid_data, format::bzip2, "invalid number of bzip2 Huffman selectors");
499 }
500 std::vector< std::uint8_t > selector_mtf(group_count);
501 for (std::size_t index = 0U; index < group_count; ++index)
502 {
503 selector_mtf[index] = static_cast< std::uint8_t >(index);
504 }
505 std::vector< std::uint8_t > selectors;
506 selectors.reserve(selector_count);
507 for (std::size_t selector = 0U; selector < selector_count; ++selector)
508 {
509 std::size_t mtf_index = 0U;
510 while (reader.read_bits(1U) != 0U)
511 {
512 ++mtf_index;
513 if (mtf_index >= group_count)
514 {
515 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 selector exceeds the Huffman group count");
516 }
517 }
518 std::uint8_t const group = selector_mtf[mtf_index];
519 for (std::size_t index = mtf_index; index > 0U; --index)
520 {
521 selector_mtf[index] = selector_mtf[index - 1U];
522 }
523 selector_mtf[0U] = group;
524 selectors.push_back(group);
525 }
526
527 std::size_t const alphabet_size = symbols.size() + 2U;
528 std::vector< huffman_table > tables;
529 tables.reserve(group_count);
530 for (std::size_t group = 0U; group < group_count; ++group)
531 {
532 std::int16_t length = static_cast< std::int16_t >(reader.read_bits(5U));
533 std::vector< std::uint8_t > lengths;
534 lengths.reserve(alphabet_size);
535 for (std::size_t symbol = 0U; symbol < alphabet_size; ++symbol)
536 {
537 while (reader.read_bits(1U) != 0U)
538 {
539 if (reader.read_bits(1U) != 0U)
540 {
541 --length;
542 }
543 else
544 {
545 ++length;
546 }
547 if (length < 1 || length > 20)
548 {
549 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 Huffman length exceeds its format limit");
550 }
551 }
552 lengths.push_back(static_cast< std::uint8_t >(length));
553 }
554 tables.push_back(build_huffman_table(lengths));
555 }
556
557 std::vector< std::uint8_t > move_to_front = symbols;
558 std::vector< std::byte > transformed;
559 transformed.reserve(block_size_limit);
560 std::size_t selector_position = 0U;
561 std::size_t symbols_in_group = 0U;
562 std::size_t repeat_count = 0U;
563 std::size_t repeat_power = 0U;
564 while (true)
565 {
566 if (symbols_in_group == 0U)
567 {
568 if (selector_position >= selectors.size())
569 {
570 throw compression_error(error_code::invalid_data, format::bzip2, "insufficient bzip2 Huffman selectors");
571 }
572 symbols_in_group = 50U;
573 }
574 std::uint8_t const group = selectors[selector_position];
575 std::uint16_t const value = decode_huffman_symbol(reader, tables[group]);
576 --symbols_in_group;
577 if (symbols_in_group == 0U)
578 {
579 ++selector_position;
580 }
581
582 if (value < 2U)
583 {
584 if (repeat_count == 0U)
585 {
586 repeat_power = 1U;
587 }
588 if (repeat_power > block_size_limit || (value != 0U && repeat_power > (block_size_limit - repeat_count) / 2U))
589 {
590 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 RUNA/RUNB count exceeds the block size");
591 }
592 repeat_count += repeat_power << value;
593 repeat_power <<= 1U;
594 if (repeat_count > block_size_limit)
595 {
596 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 RUNA/RUNB count exceeds the block size");
597 }
598 continue;
599 }
600
601 if (repeat_count != 0U)
602 {
603 if (repeat_count > block_size_limit - transformed.size())
604 {
605 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 run exceeds the block size");
606 }
607 transformed.insert(transformed.end(), repeat_count, static_cast< std::byte >(move_to_front[0U]));
608 repeat_count = 0U;
609 }
610 if (value == alphabet_size - 1U)
611 {
612 break;
613 }
614 std::size_t const mtf_index = value - 1U;
615 if (mtf_index >= move_to_front.size())
616 {
617 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 MTF index exceeds its alphabet");
618 }
619 std::uint8_t const symbol = move_to_front[mtf_index];
620 for (std::size_t index = mtf_index; index > 0U; --index)
621 {
622 move_to_front[index] = move_to_front[index - 1U];
623 }
624 move_to_front[0U] = symbol;
625 if (transformed.size() == block_size_limit)
626 {
627 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 transformed data exceeds the block size");
628 }
629 transformed.push_back(static_cast< std::byte >(symbol));
630 }
631
632 if (transformed.empty() || original_pointer >= transformed.size())
633 {
634 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 original pointer exceeds the transformed block");
635 }
636 std::array< std::size_t, 256U > counts{};
637 for (std::byte value : transformed)
638 {
639 ++counts[std::to_integer< std::uint8_t >(value)];
640 }
641 std::array< std::size_t, 256U > positions{};
642 std::size_t cumulative = 0U;
643 for (std::size_t symbol = 0U; symbol < counts.size(); ++symbol)
644 {
645 positions[symbol] = cumulative;
646 cumulative += counts[symbol];
647 }
648 std::vector< std::size_t > next(transformed.size(), 0U);
649 for (std::size_t index = 0U; index < transformed.size(); ++index)
650 {
651 std::uint8_t const symbol = std::to_integer< std::uint8_t >(transformed[index]);
652 next[positions[symbol]++] = index;
653 }
654
655 std::vector< std::byte > output;
656 output.reserve(std::min(block_size_limit, maximum_output_size));
657 std::size_t row = original_pointer;
658 std::optional< std::byte > previous_byte;
659 std::uint8_t repeated_bytes = 0U;
660 for (std::size_t index = 0U; index < transformed.size(); ++index)
661 {
662 row = next[row];
663 std::byte const value = transformed[row];
664 if (repeated_bytes == 3U)
665 {
666 std::size_t const additional_count = std::to_integer< std::uint8_t >(value);
667 if (!previous_byte.has_value() || output.size() > maximum_output_size || additional_count > maximum_output_size - output.size())
668 {
669 throw compression_error(error_code::output_limit_exceeded, format::bzip2, "decompressed bzip2 block exceeds its limit");
670 }
671 output.insert(output.end(), additional_count, *previous_byte);
672 previous_byte.reset();
673 repeated_bytes = 0U;
674 continue;
675 }
676 if (output.size() >= maximum_output_size)
677 {
678 throw compression_error(error_code::output_limit_exceeded, format::bzip2, "decompressed bzip2 block exceeds its limit");
679 }
680 output.push_back(value);
681 if (previous_byte.has_value() && *previous_byte == value)
682 {
683 ++repeated_bytes;
684 }
685 else
686 {
687 previous_byte = value;
688 repeated_bytes = 0U;
689 }
690 }
691
692 std::uint32_t const actual_block_crc = crc32(output);
693 if (actual_block_crc != expected_block_crc)
694 {
695 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 block checksum mismatch");
696 }
697 combined_crc = ((combined_crc << 1U) | (combined_crc >> 31U)) ^ actual_block_crc;
698 return output;
699 }
700
701 /**
702 * @brief MSB-first bit writer backed by an STL output iterator.
703 * @tparam output_iterator Destination accepting byte assignments.
704 */
705 template < typename output_iterator >
707 {
708 public:
709 /**
710 * @brief Constructs a writer owning an output iterator.
711 * @param output Destination iterator.
712 */
713 explicit iterator_bit_writer(output_iterator output) : m_output(std::move(output))
714 {
715 }
716
717 /**
718 * @brief Appends a big-endian bit field.
719 * @param value Value whose low @p count bits are emitted most significant first.
720 * @param count Number of bits to emit, from zero through 64.
721 * @throws compression_error If @p count exceeds 64.
722 */
723 void write_bits(std::uint64_t value, std::uint8_t count)
724 {
725 if (count > 64U)
726 {
727 throw compression_error(error_code::invalid_data, format::bzip2, "oversized bzip2 output bit field");
728 }
729 for (std::uint8_t remaining = count; remaining > 0U; --remaining)
730 {
731 m_pending = static_cast< std::uint8_t >(m_pending | static_cast< std::uint8_t >(((value >> (remaining - 1U)) & 1U) << (7U - m_bit_count)));
732 ++m_bit_count;
733 if (m_bit_count == 8U)
734 {
735 implementation::write_byte(m_output, static_cast< std::byte >(m_pending));
736 m_pending = 0U;
737 m_bit_count = 0U;
738 }
739 }
740 }
741
742 /**
743 * @brief Pads the final byte with zeros and releases the output iterator.
744 * @return Destination iterator advanced past all emitted bytes.
745 */
746 [[nodiscard]] output_iterator finish()
747 {
748 if (m_bit_count != 0U)
749 {
750 implementation::write_byte(m_output, static_cast< std::byte >(m_pending));
751 m_pending = 0U;
752 m_bit_count = 0U;
753 }
754 return std::move(m_output);
755 }
756
757 private:
758 output_iterator m_output;
759 std::uint8_t m_pending = 0U;
760 std::uint8_t m_bit_count = 0U;
761 };
762
763 /**
764 * @brief MSB-first bit reader backed by a single-pass byte reader.
765 * @tparam byte_reader_type Source reader providing read().
766 */
767 template < typename byte_reader_type >
769 {
770 public:
771 /**
772 * @brief Constructs a bit reader over a byte reader.
773 * @param input Borrowed reader that must outlive this object.
774 */
775 explicit iterator_bit_reader(byte_reader_type& input) noexcept : m_input(input)
776 {
777 }
778
779 /**
780 * @brief Consumes a big-endian bit field containing at most 64 bits.
781 * @param count Number of bits to consume.
782 * @return Field value with the earliest bit as the most significant bit.
783 * @throws compression_error If the count exceeds 64 or input is truncated.
784 */
785 [[nodiscard]] std::uint64_t read_bits(std::uint8_t count)
786 {
787 if (count > 64U)
788 {
789 throw compression_error(error_code::invalid_data, format::bzip2, "oversized bzip2 input bit field");
790 }
791 std::uint64_t result = 0U;
792 for (std::uint8_t index = 0U; index < count; ++index)
793 {
794 if (m_bits_remaining == 0U)
795 {
796 std::byte value{};
797 if (!m_input.read(value))
798 {
799 throw compression_error(error_code::invalid_data, format::bzip2, "truncated bzip2 bitstream");
800 }
801 m_current = std::to_integer< std::uint8_t >(value);
802 m_bits_remaining = 8U;
803 }
804 result = (result << 1U) | ((m_current >> (m_bits_remaining - 1U)) & 1U);
805 --m_bits_remaining;
806 }
807 return result;
808 }
809
810 /** Discard padding through the next byte boundary. */
811 void align_to_byte() noexcept
812 {
813 m_bits_remaining = 0U;
814 }
815
816 private:
817 byte_reader_type& m_input;
818 std::uint8_t m_current = 0U;
819 std::uint8_t m_bits_remaining = 0U;
820 };
821
822 /**
823 * @brief Compresses an iterator range as a native bzip2 stream.
824 * @tparam input_iterator Single-pass byte iterator.
825 * @tparam sentinel Sentinel for @p first.
826 * @tparam output_iterator Destination byte iterator.
827 * @param first First source byte.
828 * @param last Sentinel past the source.
829 * @param output Destination iterator.
830 * @param options Compression level from 1 through 9.
831 * @return Destination advanced past the stream trailer.
832 */
833 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
834 output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const& options)
835 {
836 std::int32_t const level = options.level.value_or(9);
837 if (level < 1 || level > 9)
838 {
839 throw compression_error(error_code::invalid_option, format::bzip2, "bzip2 compression level must be between 1 and 9");
840 }
841 iterator_bit_writer< output_iterator > writer(std::move(output));
842 writer.write_bits(0x425a68U, 24U);
843 writer.write_bits(static_cast< std::uint8_t >('0' + level), 8U);
844 std::size_t const transformed_limit = static_cast< std::size_t >(level) * 100000U - 20U;
845 std::size_t const source_block_limit = transformed_limit * 4U / 5U;
846 std::vector< std::byte > block;
847 block.reserve(source_block_limit);
848 std::uint32_t combined_crc = 0U;
849 while (first != last)
850 {
851 block.clear();
852 while (first != last && block.size() < source_block_limit)
853 {
854 block.push_back(implementation::to_byte(*first));
855 ++first;
856 }
857 compress_block(writer, block, combined_crc);
858 }
859 writer.write_bits(0x177245385090ULL, 48U);
860 writer.write_bits(combined_crc, 32U);
861 return writer.finish();
862 }
863
864 /**
865 * @brief Decompresses one or more native bzip2 streams.
866 * @tparam input_iterator Single-pass byte iterator.
867 * @tparam sentinel Sentinel for @p first.
868 * @tparam output_iterator Destination byte iterator.
869 * @param first First compressed byte.
870 * @param last Sentinel past the compressed input.
871 * @param output Destination iterator.
872 * @param options Output limit and concatenated-stream policy.
873 * @return Destination advanced past the uncompressed data.
874 */
875 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
876 output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const& options)
877 {
878 implementation::byte_reader< input_iterator, sentinel > source(std::move(first), std::move(last));
879 std::size_t total_output = 0U;
880 bool decoded_stream = false;
881 do
882 {
883 if (decoded_stream && !options.allow_concatenated_streams)
884 {
885 throw compression_error(error_code::trailing_data, format::bzip2, "bzip2 stream contains trailing data");
886 }
887 iterator_bit_reader< decltype(source) > reader(source);
888 if (reader.read_bits(16U) != 0x425aU || reader.read_bits(8U) != 0x68U)
889 {
890 throw compression_error(error_code::invalid_data, format::bzip2, "invalid bzip2 stream header");
891 }
892 std::uint8_t const level_byte = static_cast< std::uint8_t >(reader.read_bits(8U));
893 if (level_byte < static_cast< std::uint8_t >('1') || level_byte > static_cast< std::uint8_t >('9'))
894 {
895 throw compression_error(error_code::invalid_data, format::bzip2, "invalid bzip2 block-size level");
896 }
897 std::size_t const block_size_limit = static_cast< std::size_t >(level_byte - static_cast< std::uint8_t >('0')) * 100000U;
898 std::uint32_t combined_crc = 0U;
899 while (true)
900 {
901 std::uint64_t const magic = reader.read_bits(48U);
902 if (magic == 0x177245385090ULL)
903 {
904 if (combined_crc != static_cast< std::uint32_t >(reader.read_bits(32U)))
905 {
906 throw compression_error(error_code::invalid_data, format::bzip2, "bzip2 stream checksum mismatch");
907 }
908 reader.align_to_byte();
909 break;
910 }
911 if (magic != 0x314159265359ULL)
912 {
913 throw compression_error(error_code::invalid_data, format::bzip2, "invalid bzip2 block magic");
914 }
915 std::vector< std::byte > block = decompress_block(reader, block_size_limit, options.maximum_output_size - std::min(total_output, options.maximum_output_size), combined_crc);
916 for (std::byte value : block)
917 {
918 if (total_output == options.maximum_output_size)
919 {
920 throw compression_error(error_code::output_limit_exceeded, format::bzip2, "decompressed bzip2 output exceeds its limit");
921 }
922 implementation::write_byte(output, value);
923 ++total_output;
924 }
925 }
926 decoded_stream = true;
927 } while (!source.empty());
928 return output;
929 }
930
931} // namespace rpnx::compression::bzip2_codec
932
933#endif
MSB-first bit reader backed by a single-pass byte reader.
Definition bzip2.hpp:769
std::uint64_t read_bits(std::uint8_t count)
Consumes a big-endian bit field containing at most 64 bits.
Definition bzip2.hpp:785
iterator_bit_reader(byte_reader_type &input) noexcept
Constructs a bit reader over a byte reader.
Definition bzip2.hpp:775
void align_to_byte() noexcept
Discard padding through the next byte boundary.
Definition bzip2.hpp:811
MSB-first bit writer backed by an STL output iterator.
Definition bzip2.hpp:707
iterator_bit_writer(output_iterator output)
Constructs a writer owning an output iterator.
Definition bzip2.hpp:713
output_iterator finish()
Pads the final byte with zeros and releases the output iterator.
Definition bzip2.hpp:746
void write_bits(std::uint64_t value, std::uint8_t count)
Appends a big-endian bit field.
Definition bzip2.hpp:723
Exception raised for malformed streams, invalid options, and codec failures.
Single-pass byte reader over an input iterator and sentinel.
Definition io.hpp:71
bool empty() const
Tests whether no unread byte remains.
Definition io.hpp:86
Shared iterator, byte-conversion, and checksum primitives.
Internal implementation of bzip2 compression and decompression.
Definition bzip2.hpp:26
std::vector< std::byte > decompress_block(reader_type &reader, std::size_t block_size_limit, std::size_t maximum_output_size, std::uint32_t &combined_crc)
Decode one bzip2 block after its block magic.
Definition bzip2.hpp:458
std::vector< std::byte > encode_first_run_length(std::span< std::byte const > input)
Applies bzip2's first run-length transform to one source block.
Definition bzip2.hpp:178
output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const &options)
Compresses an iterator range as a native bzip2 stream.
Definition bzip2.hpp:834
burrows_wheeler_result burrows_wheeler_transform(std::span< std::byte const > input)
Sorts cyclic rotations and produces the Burrows-Wheeler last column.
Definition bzip2.hpp:216
void compress_block(writer_type &writer, std::span< std::byte const > input, std::uint32_t &combined_crc)
Encode one bzip2 block with deterministic canonical Huffman tables.
Definition bzip2.hpp:323
output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const &options)
Decompresses one or more native bzip2 streams.
Definition bzip2.hpp:876
huffman_table build_huffman_table(std::span< std::uint8_t const > lengths)
Builds a canonical Huffman tree from bzip2 code lengths.
Definition bzip2.hpp:80
std::uint16_t decode_huffman_symbol(reader_type &reader, huffman_table const &table)
Decodes one symbol with a canonical bzip2 Huffman tree.
Definition bzip2.hpp:157
std::uint32_t crc32(std::span< std::byte const > input) noexcept
Computes the non-reflected CRC-32 variant used by bzip2 blocks.
Definition bzip2.hpp:60
void write_byte(output_iterator &output, std::byte value)
Writes one byte through an output iterator and advances it.
Definition io.hpp:48
constexpr std::byte to_byte(value_type value) noexcept
Converts one supported iterator value to std::byte.
Definition io.hpp:27
@ trailing_data
Bytes remain after the permitted stream members.
@ output_limit_exceeded
Decoding would exceed a configured resource limit.
@ invalid_option
An option or format value is outside its accepted range.
@ unsupported_feature
Valid input requires a format feature not implemented by the library.
@ invalid_data
The input does not conform to the selected format.
Burrows-Wheeler last column and its original rotation index.
Definition bzip2.hpp:48
std::vector< std::byte > last_column
Final byte of each lexicographically sorted cyclic rotation.
Definition bzip2.hpp:50
std::size_t original_pointer
Row containing the unrotated source block.
Definition bzip2.hpp:52
One node in a canonical bzip2 Huffman decoding tree.
Definition bzip2.hpp:30
std::optional< std::uint16_t > zero_child
Child selected by a zero bit, if present.
Definition bzip2.hpp:32
std::optional< std::uint16_t > one_child
Child selected by a one bit, if present.
Definition bzip2.hpp:34
std::optional< std::uint16_t > symbol
Decoded symbol for a leaf node.
Definition bzip2.hpp:36
Canonical bzip2 Huffman decoding tree.
Definition bzip2.hpp:41
std::vector< huffman_node > nodes
Tree nodes with the root at index zero.
Definition bzip2.hpp:43
Options shared by compression operations.
std::optional< std::int32_t > level
Optional format-specific compression level.
Resource and stream-validation policy for decompression operations.
std::size_t maximum_output_size
Maximum total number of bytes the operation may emit.
bool allow_concatenated_streams
Whether to decode adjacent members for formats that define concatenation.