RPNX::Compress
Self-contained C++20 compression and ZIP library
 
Loading...
Searching...
No Matches
xz.hpp
Go to the documentation of this file.
1#ifndef RPNX_COMPRESSION_IMPLEMENTATION_XZ_HPP
2#define RPNX_COMPRESSION_IMPLEMENTATION_XZ_HPP
3
4#include <algorithm>
5#include <array>
6#include <cstddef>
7#include <cstdint>
8#include <iterator>
9#include <limits>
10#include <optional>
11#include <span>
12#include <string>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
18
19/**
20 * @file
21 * @brief Native xz container, LZMA2, and range-coding implementation.
22 */
23
24/** @brief Internal implementation of xz and its LZMA2 payload format. */
26{
27
28 /** @brief LZMA probability-model total. */
29 inline constexpr std::uint32_t probability_total = 1U << 11U;
30 /** @brief Adaptation shift applied after every probability decision. */
31 inline constexpr std::uint32_t probability_move_bits = 5U;
32 /** @brief Range threshold below which the arithmetic coder normalizes. */
33 inline constexpr std::uint32_t range_top = 1U << 24U;
34 /** @brief Number of LZMA state-machine states. */
35 inline constexpr std::size_t state_count = 12U;
36 /** @brief Maximum number of position states. */
37 inline constexpr std::size_t position_state_count = 16U;
38 /** @brief Number of literal contexts retained by the supported properties. */
39 inline constexpr std::size_t literal_coder_count = 16U;
40 /** @brief Probability count in one LZMA literal coder. */
41 inline constexpr std::size_t literal_coder_size = 0x300U;
42
43 /** Size metadata collected while decoding one xz block. */
45 {
46 std::uint64_t unpadded_size; ///< Header, payload, and check size before four-byte padding.
47 std::uint64_t uncompressed_size; ///< Number of bytes produced by the block.
48 };
49
50 /** Adaptive probability tables and state used by LZMA. */
52 {
53 /** Probability tables for the three LZMA match-length ranges. */
55 {
56 std::uint16_t choice = probability_total / 2U; ///< Selects the low range.
57 std::uint16_t choice2 = probability_total / 2U; ///< Selects the middle or high range.
58 std::array< std::array< std::uint16_t, 8U >, position_state_count > low{}; ///< Low-range trees by position state.
59 std::array< std::array< std::uint16_t, 8U >, position_state_count > mid{}; ///< Middle-range trees by position state.
60 std::array< std::uint16_t, 256U > high{}; ///< High-range probability tree.
61 };
62
63 std::uint32_t rep0 = 0U; ///< Most recent repeated match distance minus one.
64 std::uint32_t rep1 = 0U; ///< Second most recent repeated distance minus one.
65 std::uint32_t rep2 = 0U; ///< Third most recent repeated distance minus one.
66 std::uint32_t rep3 = 0U; ///< Fourth most recent repeated distance minus one.
67 std::uint8_t state = 0U; ///< Current LZMA literal/match state.
68 std::uint8_t literal_context_bits = 3U; ///< Number of high previous-byte bits in a literal context.
69 std::uint8_t literal_position_bits = 0U; ///< Number of low position bits in a literal context.
70 std::uint8_t position_bits = 2U; ///< Number of low position bits selecting a position state.
71 std::array< std::array< std::uint16_t, position_state_count >, state_count > is_match{}; ///< Literal-versus-match probabilities.
72 std::array< std::uint16_t, state_count > is_rep{}; ///< New-versus-repeated match probabilities.
73 std::array< std::uint16_t, state_count > is_rep0{}; ///< Most-recent-distance probabilities.
74 std::array< std::uint16_t, state_count > is_rep1{}; ///< Second-distance probabilities.
75 std::array< std::uint16_t, state_count > is_rep2{}; ///< Third-versus-fourth distance probabilities.
76 std::array< std::array< std::uint16_t, position_state_count >, state_count > is_rep0_long{}; ///< Short repeated-match probabilities.
77 std::array< std::array< std::uint16_t, 64U >, 4U > distance_slot{}; ///< Distance-slot trees by match-length state.
78 std::array< std::uint16_t, 114U > distance_special{}; ///< Probability trees for middle distance bits.
79 std::array< std::uint16_t, 16U > distance_align{}; ///< Reversed tree for low aligned distance bits.
80 length_model match_length; ///< Length model for new matches.
81 length_model repeated_length; ///< Length model for repeated matches.
82 std::array< std::array< std::uint16_t, literal_coder_size >, literal_coder_count > literal{}; ///< Literal probability trees by context.
83
84 /** Reset the probability model and recent-match state. */
85 void reset()
86 {
87 rep0 = 0U;
88 rep1 = 0U;
89 rep2 = 0U;
90 rep3 = 0U;
91 state = 0U;
92 fill_probabilities();
93 }
94
95 /**
96 * @brief Configures lc, lp, and pb from one LZMA properties byte.
97 * @param properties Encoded LZMA lc/lp/pb value.
98 * @throws compression_error If the value or supported context sum is invalid.
99 */
100 void set_properties(std::uint8_t properties)
101 {
102 if (properties > 224U)
103 {
104 throw compression_error(error_code::invalid_data, format::xz, "invalid LZMA properties byte");
105 }
106 position_bits = static_cast< std::uint8_t >(properties / 45U);
107 std::uint8_t const remainder = static_cast< std::uint8_t >(properties % 45U);
108 literal_position_bits = static_cast< std::uint8_t >(remainder / 9U);
109 literal_context_bits = static_cast< std::uint8_t >(remainder % 9U);
111 {
112 throw compression_error(error_code::invalid_data, format::xz, "invalid LZMA literal context properties");
113 }
114 reset();
115 }
116
117 private:
118 /** Initialize every adaptive probability to one half. */
119 void fill_probabilities()
120 {
121 constexpr std::uint16_t initial = probability_total / 2U;
122 for (std::array< std::uint16_t, position_state_count >& probabilities : is_match)
123 {
124 probabilities.fill(initial);
125 }
126 is_rep.fill(initial);
127 is_rep0.fill(initial);
128 is_rep1.fill(initial);
129 is_rep2.fill(initial);
130 for (std::array< std::uint16_t, position_state_count >& probabilities : is_rep0_long)
131 {
132 probabilities.fill(initial);
133 }
134 for (std::array< std::uint16_t, 64U >& probabilities : distance_slot)
135 {
136 probabilities.fill(initial);
137 }
138 distance_special.fill(initial);
139 distance_align.fill(initial);
140 initialize_length(match_length);
141 initialize_length(repeated_length);
142 for (std::array< std::uint16_t, literal_coder_size >& probabilities : literal)
143 {
144 probabilities.fill(initial);
145 }
146 }
147
148 /** Initialize one match-length probability table. */
149 static void initialize_length(length_model& model)
150 {
151 constexpr std::uint16_t initial = probability_total / 2U;
152 model.choice = initial;
153 model.choice2 = initial;
154 for (std::array< std::uint16_t, 8U >& probabilities : model.low)
155 {
156 probabilities.fill(initial);
157 }
158 for (std::array< std::uint16_t, 8U >& probabilities : model.mid)
159 {
160 probabilities.fill(initial);
161 }
162 model.high.fill(initial);
163 }
164 };
165
166 /** One-shot LZMA range decoder. */
168 {
169 public:
170 /**
171 * @brief Initializes a decoder from one complete LZMA chunk.
172 * @param input Complete range-coded chunk retained for this object's lifetime.
173 */
174 explicit range_decoder(std::span< std::byte const > input) : m_input(input)
175 {
176 if (input.size() < 5U)
177 {
178 throw compression_error(error_code::invalid_data, format::xz, "truncated LZMA range-coded chunk");
179 }
180 for (std::size_t index = 0U; index < 5U; ++index)
181 {
182 m_code = (m_code << 8U) | read_byte();
183 }
184 }
185
186 /**
187 * @brief Decodes one adaptive binary symbol.
188 * @param probability Probability updated in place after the decision.
189 * @return Zero or one.
190 */
191 [[nodiscard]] std::uint8_t decode_bit(std::uint16_t& probability)
192 {
193 normalize();
194 std::uint32_t const bound = (m_range >> 11U) * probability;
195 if (m_code < bound)
196 {
197 m_range = bound;
198 probability = static_cast< std::uint16_t >(probability + ((probability_total - probability) >> probability_move_bits));
199 return 0U;
200 }
201 m_range -= bound;
202 m_code -= bound;
203 probability = static_cast< std::uint16_t >(probability - (probability >> probability_move_bits));
204 return 1U;
205 }
206
207 /**
208 * @brief Decodes a most-significant-bit-first probability tree.
209 * @param probabilities Tree probabilities updated in place.
210 * @param leaf_base First leaf index, which must be a power of two.
211 * @return Tree leaf index including @p leaf_base.
212 */
213 [[nodiscard]] std::uint32_t decode_tree(std::span< std::uint16_t > probabilities, std::uint32_t leaf_base)
214 {
215 std::uint32_t symbol = 1U;
216 while (symbol < leaf_base)
217 {
218 if (symbol >= probabilities.size())
219 {
220 throw compression_error(error_code::invalid_data, format::xz, "invalid LZMA probability tree");
221 }
222 symbol = (symbol << 1U) | decode_bit(probabilities[symbol]);
223 }
224 return symbol;
225 }
226
227 /**
228 * @brief Decodes a least-significant-bit-first probability tree.
229 * @param probabilities Backing probability table updated in place.
230 * @param base Offset immediately before the tree's root.
231 * @param bit_count Tree depth.
232 * @return Reversed decoded value.
233 */
234 [[nodiscard]] std::uint32_t decode_reverse_tree(std::span< std::uint16_t > probabilities, std::ptrdiff_t base, std::uint8_t bit_count)
235 {
236 std::uint32_t symbol = 1U;
237 std::uint32_t value = 0U;
238 for (std::uint8_t bit_index = 0U; bit_index < bit_count; ++bit_index)
239 {
240 std::ptrdiff_t const probability_index = base + static_cast< std::ptrdiff_t >(symbol);
241 if (probability_index < 0 || static_cast< std::size_t >(probability_index) >= probabilities.size())
242 {
243 throw compression_error(error_code::invalid_data, format::xz, "invalid reversed LZMA probability tree");
244 }
245 std::uint8_t const bit = decode_bit(probabilities[static_cast< std::size_t >(probability_index)]);
246 symbol = (symbol << 1U) | bit;
247 value |= static_cast< std::uint32_t >(bit) << bit_index;
248 }
249 return value;
250 }
251
252 /**
253 * @brief Decodes equiprobable direct bits.
254 * @param bit_count Number of bits to decode.
255 * @return Decoded most-significant-bit-first value.
256 */
257 [[nodiscard]] std::uint32_t decode_direct(std::uint8_t bit_count)
258 {
259 std::uint32_t value = 0U;
260 for (std::uint8_t bit_index = 0U; bit_index < bit_count; ++bit_index)
261 {
262 normalize();
263 m_range >>= 1U;
264 std::uint8_t bit = 0U;
265 if (m_code >= m_range)
266 {
267 m_code -= m_range;
268 bit = 1U;
269 }
270 value = (value << 1U) | bit;
271 }
272 return value;
273 }
274
275 /** Validate exact consumption and the LZMA terminal range state. */
276 void finish()
277 {
278 normalize();
279 if (m_position != m_input.size() || m_code != 0U)
280 {
281 throw compression_error(error_code::invalid_data, format::xz, "invalid LZMA chunk termination");
282 }
283 }
284
285 private:
286 [[nodiscard]] std::uint8_t read_byte()
287 {
288 if (m_position >= m_input.size())
289 {
290 throw compression_error(error_code::invalid_data, format::xz, "truncated LZMA range-coded chunk");
291 }
292 return std::to_integer< std::uint8_t >(m_input[m_position++]);
293 }
294
295 void normalize()
296 {
297 if (m_range < range_top)
298 {
299 m_range <<= 8U;
300 m_code = (m_code << 8U) | read_byte();
301 }
302 }
303
304 std::span< std::byte const > m_input;
305 std::size_t m_position = 0U;
306 std::uint32_t m_range = std::numeric_limits< std::uint32_t >::max();
307 std::uint32_t m_code = 0U;
308 };
309
310 /** One-shot LZMA range encoder used by the deterministic literal encoder. */
312 {
313 public:
314 /**
315 * @brief Encodes one adaptive binary symbol.
316 * @param probability Probability updated in place after encoding.
317 * @param bit Symbol value, either zero or one.
318 */
319 void encode_bit(std::uint16_t& probability, std::uint8_t bit)
320 {
321 std::uint32_t const bound = (m_range >> 11U) * probability;
322 if (bit == 0U)
323 {
324 m_range = bound;
325 probability = static_cast< std::uint16_t >(probability + ((probability_total - probability) >> probability_move_bits));
326 }
327 else
328 {
329 m_low += bound;
330 m_range -= bound;
331 probability = static_cast< std::uint16_t >(probability - (probability >> probability_move_bits));
332 }
333 if (m_range < range_top)
334 {
335 m_range <<= 8U;
336 shift_low();
337 }
338 }
339
340 /**
341 * @brief Finishes the range stream.
342 * @return Five-byte-terminated encoded representation.
343 */
344 [[nodiscard]] std::vector< std::byte > finish()
345 {
346 for (std::size_t index = 0U; index < 5U; ++index)
347 {
348 shift_low();
349 }
350 return std::move(m_output);
351 }
352
353 private:
354 void shift_low()
355 {
356 std::uint32_t const low = static_cast< std::uint32_t >(m_low);
357 std::uint32_t const high = static_cast< std::uint32_t >(m_low >> 32U);
358 if (low < 0xff000000U || high != 0U)
359 {
360 std::uint8_t cached = m_cache;
361 do
362 {
363 m_output.push_back(static_cast< std::byte >(cached + high));
364 cached = 0xffU;
365 } while (--m_cache_size != 0U);
366 m_cache = static_cast< std::uint8_t >(low >> 24U);
367 }
368 ++m_cache_size;
369 m_low = static_cast< std::uint64_t >(low & 0x00ffffffU) << 8U;
370 }
371
372 std::vector< std::byte > m_output;
373 std::uint64_t m_low = 0U;
374 std::uint32_t m_range = std::numeric_limits< std::uint32_t >::max();
375 std::uint8_t m_cache = 0U;
376 std::size_t m_cache_size = 1U;
377 };
378
379 /**
380 * @brief Computes the reflected CRC-32 used by xz metadata and checks.
381 * @param input Bytes to checksum.
382 * @return Finalized CRC-32.
383 */
384 [[nodiscard]] inline std::uint32_t crc32(std::span< std::byte const > input) noexcept
385 {
386 std::uint32_t crc = 0xffffffffU;
387 for (std::byte value : input)
388 {
389 crc ^= std::to_integer< std::uint8_t >(value);
390 for (std::uint8_t bit = 0U; bit < 8U; ++bit)
391 {
392 crc = (crc & 1U) != 0U ? (crc >> 1U) ^ 0xedb88320U : crc >> 1U;
393 }
394 }
395 return ~crc;
396 }
397
398 /** Incremental CRC-64/XZ accumulator for streamed block data. */
400 {
401 public:
402 /**
403 * @brief Includes one byte in the checksum.
404 * @param value Next byte in stream order.
405 */
406 void update(std::byte value) noexcept
407 {
408 m_crc ^= std::to_integer< std::uint8_t >(value);
409 for (std::uint8_t bit = 0U; bit < 8U; ++bit)
410 {
411 m_crc = (m_crc & 1U) != 0U ? (m_crc >> 1U) ^ 0xc96c5795d7870f42ULL : m_crc >> 1U;
412 }
413 }
414
415 /**
416 * @brief Returns the checksum for all supplied bytes.
417 * @return Finalized CRC-64/XZ value.
418 */
419 [[nodiscard]] std::uint64_t value() const noexcept
420 {
421 return ~m_crc;
422 }
423
424 private:
425 std::uint64_t m_crc = std::numeric_limits< std::uint64_t >::max();
426 };
427
428 /**
429 * @brief Reads a fixed-width little-endian integer.
430 * @param input Source bytes.
431 * @param position Current position, advanced by @p byte_count.
432 * @param byte_count Width from zero through eight bytes.
433 * @return Decoded value.
434 */
435 [[nodiscard]] inline std::uint64_t read_little_endian(std::span< std::byte const > input, std::size_t& position, std::uint8_t byte_count)
436 {
437 if (position > input.size() || byte_count > input.size() - position)
438 {
439 throw compression_error(error_code::invalid_data, format::xz, "truncated xz integer");
440 }
441 std::uint64_t value = 0U;
442 for (std::uint8_t byte_index = 0U; byte_index < byte_count; ++byte_index)
443 {
444 value |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(input[position++])) << (byte_index * 8U);
445 }
446 return value;
447 }
448
449 /**
450 * @brief Decodes one minimal xz variable-length integer.
451 * @param input Source bytes.
452 * @param position Current position, advanced past the encoded integer.
453 * @return Decoded unsigned value.
454 * @throws compression_error If the integer is truncated, non-minimal, or oversized.
455 */
456 [[nodiscard]] inline std::uint64_t read_variable_integer(std::span< std::byte const > input, std::size_t& position)
457 {
458 std::uint64_t value = 0U;
459 for (std::uint8_t byte_index = 0U; byte_index < 9U; ++byte_index)
460 {
461 if (position >= input.size())
462 {
463 throw compression_error(error_code::invalid_data, format::xz, "truncated xz variable-length integer");
464 }
465 std::uint8_t const byte = std::to_integer< std::uint8_t >(input[position++]);
466 if (byte_index != 0U && byte == 0U)
467 {
468 throw compression_error(error_code::invalid_data, format::xz, "non-minimal xz variable-length integer");
469 }
470 value |= static_cast< std::uint64_t >(byte & 0x7fU) << (byte_index * 7U);
471 if ((byte & 0x80U) == 0U)
472 {
473 return value;
474 }
475 }
476 throw compression_error(error_code::invalid_data, format::xz, "oversized xz variable-length integer");
477 }
478
479 /**
480 * @brief Returns the number of bytes in an xz integrity check.
481 * @param check_identifier Four-bit xz check identifier.
482 * @return Encoded check size.
483 */
484 [[nodiscard]] inline std::size_t check_size(std::uint8_t check_identifier)
485 {
486 constexpr std::array< std::size_t, 16U > sizes{0U, 4U, 4U, 4U, 8U, 8U, 8U, 16U, 16U, 16U, 32U, 32U, 32U, 64U, 64U, 64U};
487 return sizes[check_identifier];
488 }
489
490 /**
491 * @brief Updates an LZMA state after decoding a literal.
492 * @param state State-machine value updated in place.
493 */
494 inline void update_literal_state(std::uint8_t& state) noexcept
495 {
496 if (state <= 3U)
497 {
498 state = 0U;
499 }
500 else if (state <= 9U)
501 {
502 state = static_cast< std::uint8_t >(state - 3U);
503 }
504 else
505 {
506 state = static_cast< std::uint8_t >(state - 6U);
507 }
508 }
509
510 /**
511 * @brief Decodes one LZMA match length.
512 * @param decoder Range decoder.
513 * @param model Match or repeated-match length model updated in place.
514 * @param position_state Current low dictionary-position state.
515 * @return Match length of at least two bytes.
516 */
517 [[nodiscard]] inline std::uint32_t decode_length(range_decoder& decoder, lzma_model::length_model& model, std::size_t position_state)
518 {
519 if (decoder.decode_bit(model.choice) == 0U)
520 {
521 return 2U + decoder.decode_tree(model.low[position_state], 8U) - 8U;
522 }
523 if (decoder.decode_bit(model.choice2) == 0U)
524 {
525 return 10U + decoder.decode_tree(model.mid[position_state], 8U) - 8U;
526 }
527 return 18U + decoder.decode_tree(model.high, 256U) - 256U;
528 }
529
530 /**
531 * @brief Copies one validated LZMA match into the output dictionary.
532 * @param output Stream output and dictionary storage.
533 * @param history_begin Start of the current LZMA2 dictionary history.
534 * @param dictionary_size Declared maximum dictionary size.
535 * @param distance Zero-based match distance.
536 * @param length Number of bytes to reproduce.
537 * @param output_limit Absolute output-size limit.
538 */
539 inline void copy_match(std::vector< std::byte >& output, std::size_t history_begin, std::uint32_t dictionary_size, std::uint32_t distance, std::uint32_t length, std::size_t output_limit)
540 {
541 std::size_t const history_size = output.size() - history_begin;
542 if (distance >= history_size || distance >= dictionary_size)
543 {
544 throw compression_error(error_code::invalid_data, format::xz, "LZMA match distance " + std::to_string(distance) + " exceeds history " + std::to_string(history_size));
545 }
546 if (output.size() > output_limit || length > output_limit - output.size())
547 {
548 throw compression_error(error_code::output_limit_exceeded, format::xz, "decompressed xz output exceeds its limit");
549 }
550 for (std::uint32_t index = 0U; index < length; ++index)
551 {
552 output.push_back(output[output.size() - distance - 1U]);
553 }
554 }
555
556 /**
557 * @brief Decodes one LZMA range-coded chunk into an LZMA2 dictionary.
558 * @param encoded Complete range-coded chunk.
559 * @param uncompressed_size Exact number of bytes the chunk must produce.
560 * @param model Adaptive model retained across eligible chunks.
561 * @param output Stream output and dictionary storage.
562 * @param history_begin Start of the current LZMA2 dictionary history.
563 * @param dictionary_size Declared maximum dictionary size.
564 * @param output_limit Absolute output-size limit.
565 * @param dictionary_position_offset Position adjustment for chunk-local buffers.
566 */
567 inline void decode_lzma_chunk(std::span< std::byte const > encoded, std::size_t uncompressed_size, lzma_model& model, std::vector< std::byte >& output, std::size_t history_begin, std::uint32_t dictionary_size, std::size_t output_limit, std::size_t dictionary_position_offset = 0U)
568 {
569 if (output.size() > output_limit || uncompressed_size > output_limit - output.size())
570 {
571 throw compression_error(error_code::output_limit_exceeded, format::xz, "decompressed xz output exceeds its limit");
572 }
573 std::size_t const target_size = output.size() + uncompressed_size;
574 range_decoder decoder(encoded);
575 while (output.size() < target_size)
576 {
577 std::size_t const dictionary_position = dictionary_position_offset + output.size() - history_begin;
578 std::size_t const position_state = dictionary_position & ((std::size_t{1U} << model.position_bits) - 1U);
579 if (decoder.decode_bit(model.is_match[model.state][position_state]) == 0U)
580 {
581 std::uint8_t const previous = dictionary_position == 0U ? 0U : std::to_integer< std::uint8_t >(output.back());
582 std::size_t const literal_context = ((dictionary_position & ((std::size_t{1U} << model.literal_position_bits) - 1U)) << model.literal_context_bits) | (previous >> (8U - model.literal_context_bits));
583 std::array< std::uint16_t, literal_coder_size >& probabilities = model.literal[literal_context];
584 std::uint32_t symbol = 1U;
585 if (model.state < 7U)
586 {
587 symbol = decoder.decode_tree(probabilities, 0x100U);
588 }
589 else
590 {
591 if (model.rep0 >= dictionary_position || model.rep0 >= dictionary_size)
592 {
593 throw compression_error(error_code::invalid_data, format::xz, "LZMA literal match byte exceeds history");
594 }
595 std::uint32_t match_byte = static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(output[output.size() - model.rep0 - 1U])) << 1U;
596 std::uint32_t offset = 0x100U;
597 while (symbol < 0x100U)
598 {
599 std::uint32_t const match_bit = match_byte & offset;
600 match_byte <<= 1U;
601 std::size_t const probability_index = offset + match_bit + symbol;
602 std::uint8_t const bit = decoder.decode_bit(probabilities[probability_index]);
603 symbol = (symbol << 1U) | bit;
604 offset &= bit != 0U ? match_bit : ~match_bit;
605 }
606 }
607 output.push_back(static_cast< std::byte >(symbol));
609 continue;
610 }
611
612 std::uint32_t length = 0U;
613 if (decoder.decode_bit(model.is_rep[model.state]) == 0U)
614 {
615 model.state = model.state < 7U ? 7U : 10U;
616 model.rep3 = model.rep2;
617 model.rep2 = model.rep1;
618 model.rep1 = model.rep0;
619 length = decode_length(decoder, model.match_length, position_state);
620 std::size_t const distance_state = std::min< std::size_t >(length - 2U, 3U);
621 std::uint32_t const slot = decoder.decode_tree(model.distance_slot[distance_state], 64U) - 64U;
622 if (slot < 4U)
623 {
624 model.rep0 = slot;
625 }
626 else
627 {
628 std::uint8_t const additional_bits = static_cast< std::uint8_t >((slot >> 1U) - 1U);
629 model.rep0 = 2U + (slot & 1U);
630 if (slot < 14U)
631 {
632 model.rep0 <<= additional_bits;
633 std::ptrdiff_t const base = static_cast< std::ptrdiff_t >(model.rep0) - static_cast< std::ptrdiff_t >(slot) - 1;
634 model.rep0 += decoder.decode_reverse_tree(model.distance_special, base, additional_bits);
635 }
636 else
637 {
638 std::uint8_t const direct_bits = static_cast< std::uint8_t >(additional_bits - 4U);
639 model.rep0 = (model.rep0 << direct_bits) | decoder.decode_direct(direct_bits);
640 model.rep0 <<= 4U;
641 model.rep0 += decoder.decode_reverse_tree(model.distance_align, 0, 4U);
642 }
643 }
644 }
645 else
646 {
647 if (decoder.decode_bit(model.is_rep0[model.state]) == 0U)
648 {
649 if (decoder.decode_bit(model.is_rep0_long[model.state][position_state]) == 0U)
650 {
651 model.state = model.state < 7U ? 9U : 11U;
652 length = 1U;
653 }
654 }
655 else
656 {
657 std::uint32_t distance = 0U;
658 if (decoder.decode_bit(model.is_rep1[model.state]) == 0U)
659 {
660 distance = model.rep1;
661 }
662 else
663 {
664 if (decoder.decode_bit(model.is_rep2[model.state]) == 0U)
665 {
666 distance = model.rep2;
667 }
668 else
669 {
670 distance = model.rep3;
671 model.rep3 = model.rep2;
672 }
673 model.rep2 = model.rep1;
674 }
675 model.rep1 = model.rep0;
676 model.rep0 = distance;
677 }
678 if (length == 0U)
679 {
680 model.state = model.state < 7U ? 8U : 11U;
681 length = decode_length(decoder, model.repeated_length, position_state);
682 }
683 }
684 if (length > target_size - output.size())
685 {
686 throw compression_error(error_code::invalid_data, format::xz, "LZMA match exceeds its chunk output size");
687 }
688 copy_match(output, history_begin, dictionary_size, model.rep0, length, output_limit);
689 }
690 decoder.finish();
691 }
692
693 /**
694 * @brief Encodes a literal-only LZMA range-coded chunk.
695 * @param input Source literals.
696 * @param model Adaptive model updated in place.
697 * @param dictionary_position Absolute dictionary position of the first literal.
698 * @param previous_byte Byte preceding the chunk, or zero at dictionary start.
699 * @return Range-coded chunk payload.
700 */
701 [[nodiscard]] inline std::vector< std::byte > compress_lzma_literals(std::span< std::byte const > input, lzma_model& model, std::size_t dictionary_position, std::uint8_t previous_byte)
702 {
703 range_encoder encoder;
704 for (std::byte value : input)
705 {
706 std::size_t const position_state = dictionary_position & ((std::size_t{1U} << model.position_bits) - 1U);
707 encoder.encode_bit(model.is_match[model.state][position_state], 0U);
708 std::size_t const literal_context = ((dictionary_position & ((std::size_t{1U} << model.literal_position_bits) - 1U)) << model.literal_context_bits) | (previous_byte >> (8U - model.literal_context_bits));
709 std::array< std::uint16_t, literal_coder_size >& probabilities = model.literal[literal_context];
710 std::uint32_t symbol = 1U;
711 std::uint8_t const byte = std::to_integer< std::uint8_t >(value);
712 for (std::uint8_t bit_index = 0U; bit_index < 8U; ++bit_index)
713 {
714 std::uint8_t const bit = static_cast< std::uint8_t >((byte >> (7U - bit_index)) & 1U);
715 encoder.encode_bit(probabilities[symbol], bit);
716 symbol = (symbol << 1U) | bit;
717 }
719 previous_byte = byte;
720 ++dictionary_position;
721 }
722 return encoder.finish();
723 }
724
725 /**
726 * @brief Decodes the one-byte LZMA2 dictionary-size property.
727 * @param properties Encoded property from the xz block filter flags.
728 * @return Dictionary size in bytes.
729 */
730 [[nodiscard]] inline std::uint32_t decode_dictionary_size(std::uint8_t properties)
731 {
732 if (properties > 40U)
733 {
734 throw compression_error(error_code::unsupported_feature, format::xz, "xz LZMA2 dictionary exceeds 4 GiB");
735 }
736 if (properties == 40U)
737 {
738 return std::numeric_limits< std::uint32_t >::max();
739 }
740 std::uint32_t size = 2U | (properties & 1U);
741 size <<= static_cast< std::uint8_t >(properties / 2U + 11U);
742 return size;
743 }
744
745 /**
746 * @brief Compresses an iterator range as an xz stream containing LZMA2.
747 * @tparam input_iterator Single-pass byte iterator.
748 * @tparam sentinel Sentinel for @p first.
749 * @tparam output_iterator Destination byte iterator.
750 * @param first First source byte.
751 * @param last Sentinel past the source.
752 * @param output Destination iterator.
753 * @param options Compression level from 0 through 9.
754 * @return Destination advanced past the xz footer.
755 */
756 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
757 output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const& options)
758 {
759 std::int32_t const level = options.level.value_or(6);
760 if (level < 0 || level > 9)
761 {
762 throw compression_error(error_code::invalid_option, format::xz, "xz compression level must be between 0 and 9");
763 }
764 auto write_bytes = [&](std::span< std::byte const > bytes)
765 {
766 for (std::byte value : bytes)
767 {
768 implementation::write_byte(output, value);
769 }
770 };
771 auto write_little_endian = [&](std::uint64_t value, std::uint8_t count)
772 {
773 for (std::uint8_t index = 0U; index < count; ++index)
774 {
775 implementation::write_byte(output, static_cast< std::byte >(value >> (index * 8U)));
776 }
777 };
778
779 constexpr std::array< std::byte, 6U > magic{std::byte{0xfd}, std::byte{'7'}, std::byte{'z'}, std::byte{'X'}, std::byte{'Z'}, std::byte{0}};
780 write_bytes(magic);
781 constexpr std::array< std::byte, 2U > stream_flags{std::byte{0}, std::byte{4}};
782 write_bytes(stream_flags);
783 write_little_endian(crc32(stream_flags), 4U);
784
785 std::array< std::byte, 12U > header{std::byte{2}, std::byte{0}, std::byte{0x21}, std::byte{1}, std::byte{22}, std::byte{0}, std::byte{0}, std::byte{0}};
786 std::uint32_t const header_crc = crc32(std::span< std::byte const >(header).first(8U));
787 for (std::uint8_t index = 0U; index < 4U; ++index)
788 {
789 header[8U + index] = static_cast< std::byte >(header_crc >> (index * 8U));
790 }
791 write_bytes(header);
792
793 lzma_model model;
794 model.set_properties(0x5dU);
795 bool dictionary_reset_needed = true;
796 bool properties_needed = true;
797 std::size_t dictionary_position = 0U;
798 std::uint8_t previous_byte = 0U;
799 std::uint64_t uncompressed_size = 0U;
800 std::uint64_t compressed_size = 0U;
801 crc64_accumulator block_crc;
802 std::vector< std::byte > chunk;
803 chunk.reserve(60000U);
804 while (first != last)
805 {
806 chunk.clear();
807 while (first != last && chunk.size() < 60000U)
808 {
809 std::byte const value = implementation::to_byte(*first);
810 ++first;
811 chunk.push_back(value);
812 block_crc.update(value);
813 ++uncompressed_size;
814 }
815 lzma_model candidate_model = model;
816 if (properties_needed)
817 {
818 candidate_model.set_properties(0x5dU);
819 }
820 std::vector< std::byte > compressed;
821 if (level != 0)
822 {
823 compressed = compress_lzma_literals(chunk, candidate_model, dictionary_position, previous_byte);
824 }
825 std::size_t const compressed_header_size = properties_needed ? 6U : 5U;
826 bool const use_compressed = level != 0 && compressed.size() <= 65536U && compressed.size() + compressed_header_size < chunk.size() + 3U;
827 if (use_compressed)
828 {
829 std::uint8_t control = dictionary_reset_needed ? 0xe0U : properties_needed ? 0xc0U : 0x80U;
830 control |= static_cast< std::uint8_t >((chunk.size() - 1U) >> 16U);
831 implementation::write_byte(output, static_cast< std::byte >(control));
832 implementation::write_byte(output, static_cast< std::byte >((chunk.size() - 1U) >> 8U));
833 implementation::write_byte(output, static_cast< std::byte >(chunk.size() - 1U));
834 implementation::write_byte(output, static_cast< std::byte >((compressed.size() - 1U) >> 8U));
835 implementation::write_byte(output, static_cast< std::byte >(compressed.size() - 1U));
836 compressed_size += 5U;
837 if (properties_needed)
838 {
839 implementation::write_byte(output, std::byte{0x5d});
840 ++compressed_size;
841 }
842 write_bytes(compressed);
843 compressed_size += compressed.size();
844 model = std::move(candidate_model);
845 dictionary_reset_needed = false;
846 properties_needed = false;
847 }
848 else
849 {
850 implementation::write_byte(output, dictionary_reset_needed ? std::byte{0x01} : std::byte{0x02});
851 implementation::write_byte(output, static_cast< std::byte >((chunk.size() - 1U) >> 8U));
852 implementation::write_byte(output, static_cast< std::byte >(chunk.size() - 1U));
853 write_bytes(chunk);
854 compressed_size += chunk.size() + 3U;
855 if (dictionary_reset_needed)
856 {
857 dictionary_reset_needed = false;
858 properties_needed = true;
859 dictionary_position = 0U;
860 }
861 }
862 dictionary_position += chunk.size();
863 previous_byte = std::to_integer< std::uint8_t >(chunk.back());
864 }
865 implementation::write_byte(output, std::byte{0});
866 ++compressed_size;
867 for (std::size_t padding = static_cast< std::size_t >((4U - compressed_size % 4U) % 4U); padding > 0U; --padding)
868 {
869 implementation::write_byte(output, std::byte{0});
870 }
871 write_little_endian(block_crc.value(), 8U);
872
873 std::uint64_t const unpadded_size = header.size() + compressed_size + 8U;
875 std::size_t index_size = 0U;
876 auto write_index_byte = [&](std::byte value)
877 {
878 implementation::write_byte(output, value);
879 index_crc.update(value);
880 ++index_size;
881 };
882 auto write_index_integer = [&](std::uint64_t value)
883 {
884 do
885 {
886 std::uint8_t byte = static_cast< std::uint8_t >(value & 0x7fU);
887 value >>= 7U;
888 if (value != 0U)
889 {
890 byte |= 0x80U;
891 }
892 write_index_byte(static_cast< std::byte >(byte));
893 } while (value != 0U);
894 };
895 write_index_byte(std::byte{0});
896 write_index_integer(1U);
897 write_index_integer(unpadded_size);
898 write_index_integer(uncompressed_size);
899 while (index_size % 4U != 0U)
900 {
901 write_index_byte(std::byte{0});
902 }
903 write_little_endian(index_crc.value(), 4U);
904
905 std::array< std::byte, 6U > footer{static_cast< std::byte >((index_size + 4U) / 4U - 1U), static_cast< std::byte >(((index_size + 4U) / 4U - 1U) >> 8U), static_cast< std::byte >(((index_size + 4U) / 4U - 1U) >> 16U), static_cast< std::byte >(((index_size + 4U) / 4U - 1U) >> 24U), std::byte{0}, std::byte{4}};
906 write_little_endian(crc32(footer), 4U);
907 write_bytes(footer);
908 implementation::write_byte(output, std::byte{'Y'});
909 implementation::write_byte(output, std::byte{'Z'});
910 return output;
911 }
912
913 /**
914 * @brief Decompresses one or more xz streams containing LZMA2.
915 * @tparam input_iterator Single-pass byte iterator.
916 * @tparam sentinel Sentinel for @p first.
917 * @tparam output_iterator Destination byte iterator.
918 * @param first First compressed byte.
919 * @param last Sentinel past the compressed input.
920 * @param output Destination iterator.
921 * @param options Output limit and concatenated-stream policy.
922 * @return Destination advanced past the uncompressed data.
923 */
924 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
925 output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const& options)
926 {
927 implementation::byte_reader< input_iterator, sentinel > source(std::move(first), std::move(last));
928 auto read_byte = [&]() -> std::byte
929 {
930 std::byte value{};
931 if (!source.read(value))
932 {
933 throw compression_error(error_code::invalid_data, format::xz, "truncated xz stream");
934 }
935 return value;
936 };
937 auto read_little_endian_source = [&](std::uint8_t count) -> std::uint64_t
938 {
939 std::uint64_t value = 0U;
940 for (std::uint8_t index = 0U; index < count; ++index)
941 {
942 value |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(read_byte())) << (index * 8U);
943 }
944 return value;
945 };
946 std::size_t total_output = 0U;
947 bool decoded_stream = false;
948 do
949 {
950 if (decoded_stream && !options.allow_concatenated_streams)
951 {
952 throw compression_error(error_code::trailing_data, format::xz, "xz stream contains trailing data");
953 }
954 constexpr std::array< std::byte, 6U > magic{std::byte{0xfd}, std::byte{'7'}, std::byte{'z'}, std::byte{'X'}, std::byte{'Z'}, std::byte{0}};
955 for (std::byte expected : magic)
956 {
957 if (read_byte() != expected)
958 {
959 throw compression_error(error_code::invalid_data, format::xz, "invalid xz stream header");
960 }
961 }
962 std::array< std::byte, 2U > const stream_flags{read_byte(), read_byte()};
963 if (stream_flags[0U] != std::byte{0} || (std::to_integer< std::uint8_t >(stream_flags[1U]) & 0xf0U) != 0U)
964 {
965 throw compression_error(error_code::unsupported_feature, format::xz, "unsupported xz stream flags");
966 }
967 if (read_little_endian_source(4U) != crc32(stream_flags))
968 {
969 throw compression_error(error_code::invalid_data, format::xz, "xz stream header checksum mismatch");
970 }
971 std::uint8_t const check_identifier = std::to_integer< std::uint8_t >(stream_flags[1U]);
972 if (check_identifier != 0U && check_identifier != 1U && check_identifier != 4U)
973 {
974 throw compression_error(error_code::unsupported_feature, format::xz, "unsupported xz integrity check");
975 }
976
977 std::vector< block_record > records;
978 while (true)
979 {
980 std::uint8_t const encoded_header_size = std::to_integer< std::uint8_t >(read_byte());
981 if (encoded_header_size == 0U)
982 {
983 break;
984 }
985 std::size_t const header_size = (static_cast< std::size_t >(encoded_header_size) + 1U) * 4U;
986 if (header_size < 8U)
987 {
988 throw compression_error(error_code::invalid_data, format::xz, "invalid xz block header size");
989 }
990 std::vector< std::byte > header;
991 header.reserve(header_size);
992 header.push_back(static_cast< std::byte >(encoded_header_size));
993 for (std::size_t index = 1U; index < header_size; ++index)
994 {
995 header.push_back(read_byte());
996 }
997 std::size_t header_crc_position = header_size - 4U;
998 if (read_little_endian(header, header_crc_position, 4U) != crc32(std::span< std::byte const >(header).first(header_size - 4U)))
999 {
1000 throw compression_error(error_code::invalid_data, format::xz, "xz block header checksum mismatch");
1001 }
1002 std::size_t header_position = 1U;
1003 std::uint8_t const block_flags = std::to_integer< std::uint8_t >(header[header_position++]);
1004 if ((block_flags & 0x3cU) != 0U)
1005 {
1006 throw compression_error(error_code::unsupported_feature, format::xz, "unsupported xz block flags");
1007 }
1008 std::size_t const filter_count = (block_flags & 0x03U) + 1U;
1009 std::optional< std::uint64_t > declared_compressed_size;
1010 std::optional< std::uint64_t > declared_uncompressed_size;
1011 std::span< std::byte const > const header_fields = std::span< std::byte const >(header).first(header_size - 4U);
1012 if ((block_flags & 0x40U) != 0U)
1013 {
1014 declared_compressed_size = read_variable_integer(header_fields, header_position);
1015 }
1016 if ((block_flags & 0x80U) != 0U)
1017 {
1018 declared_uncompressed_size = read_variable_integer(header_fields, header_position);
1019 }
1020 if (filter_count != 1U || read_variable_integer(header_fields, header_position) != 0x21U || read_variable_integer(header_fields, header_position) != 1U || header_position >= header_size - 4U)
1021 {
1022 throw compression_error(error_code::unsupported_feature, format::xz, "xz block does not contain a supported LZMA2 filter");
1023 }
1024 std::uint32_t const dictionary_size = decode_dictionary_size(std::to_integer< std::uint8_t >(header[header_position++]));
1025 while (header_position < header_size - 4U)
1026 {
1027 if (header[header_position++] != std::byte{0})
1028 {
1029 throw compression_error(error_code::invalid_data, format::xz, "nonzero xz block-header padding");
1030 }
1031 }
1032
1033 std::vector< std::byte > history;
1034 lzma_model model;
1035 bool need_dictionary_reset = true;
1036 bool need_properties = true;
1037 std::size_t dictionary_position = 0U;
1038 std::uint64_t compressed_size = 0U;
1039 std::uint64_t block_output_size = 0U;
1041 crc64_accumulator block_crc64;
1042 auto emit = [&](std::byte value)
1043 {
1044 if (total_output == options.maximum_output_size)
1045 {
1046 throw compression_error(error_code::output_limit_exceeded, format::xz, "decompressed xz output exceeds its limit");
1047 }
1048 block_crc32.update(value);
1049 block_crc64.update(value);
1050 implementation::write_byte(output, value);
1051 ++total_output;
1052 ++block_output_size;
1053 };
1054
1055 while (true)
1056 {
1057 std::uint8_t const control = std::to_integer< std::uint8_t >(read_byte());
1058 ++compressed_size;
1059 if (control == 0U)
1060 {
1061 break;
1062 }
1063 if (control >= 0xe0U || control == 0x01U)
1064 {
1065 history.clear();
1066 dictionary_position = 0U;
1067 need_dictionary_reset = false;
1068 need_properties = true;
1069 }
1070 else if (need_dictionary_reset)
1071 {
1072 throw compression_error(error_code::invalid_data, format::xz, "LZMA2 stream does not begin with a dictionary reset");
1073 }
1074 if (control < 0x80U)
1075 {
1076 if (control > 0x02U)
1077 {
1078 throw compression_error(error_code::invalid_data, format::xz, "invalid LZMA2 uncompressed control byte");
1079 }
1080 std::size_t const chunk_size = (static_cast< std::size_t >(std::to_integer< std::uint8_t >(read_byte())) << 8U) | std::to_integer< std::uint8_t >(read_byte());
1081 compressed_size += 2U;
1082 for (std::size_t index = 0U; index <= chunk_size; ++index)
1083 {
1084 std::byte const value = read_byte();
1085 ++compressed_size;
1086 history.push_back(value);
1087 emit(value);
1088 }
1089 dictionary_position += chunk_size + 1U;
1090 if (history.size() > dictionary_size)
1091 {
1092 history.erase(history.begin(), history.begin() + static_cast< std::ptrdiff_t >(history.size() - dictionary_size));
1093 }
1094 continue;
1095 }
1096
1097 std::size_t const uncompressed_size = (static_cast< std::size_t >(control & 0x1fU) << 16U) | (static_cast< std::size_t >(std::to_integer< std::uint8_t >(read_byte())) << 8U) | std::to_integer< std::uint8_t >(read_byte());
1098 std::size_t const compressed_chunk_size = (static_cast< std::size_t >(std::to_integer< std::uint8_t >(read_byte())) << 8U) | std::to_integer< std::uint8_t >(read_byte());
1099 compressed_size += 4U;
1100 if (control >= 0xc0U)
1101 {
1102 model.set_properties(std::to_integer< std::uint8_t >(read_byte()));
1103 ++compressed_size;
1104 need_properties = false;
1105 }
1106 else if (need_properties)
1107 {
1108 throw compression_error(error_code::invalid_data, format::xz, "LZMA2 compressed chunk omits required properties");
1109 }
1110 else if (control >= 0xa0U)
1111 {
1112 model.reset();
1113 }
1114 std::vector< std::byte > encoded;
1115 encoded.reserve(compressed_chunk_size + 1U);
1116 for (std::size_t index = 0U; index <= compressed_chunk_size; ++index)
1117 {
1118 encoded.push_back(read_byte());
1119 }
1120 compressed_size += compressed_chunk_size + 1U;
1121 std::vector< std::byte > working = std::move(history);
1122 std::size_t const history_size = working.size();
1123 decode_lzma_chunk(encoded, uncompressed_size + 1U, model, working, 0U, dictionary_size, history_size + uncompressed_size + 1U, dictionary_position - history_size);
1124 for (std::size_t index = history_size; index < working.size(); ++index)
1125 {
1126 emit(working[index]);
1127 }
1128 dictionary_position += uncompressed_size + 1U;
1129 std::size_t const retained = std::min< std::size_t >(working.size(), dictionary_size);
1130 history.assign(working.end() - static_cast< std::ptrdiff_t >(retained), working.end());
1131 }
1132 if (declared_compressed_size.has_value() && *declared_compressed_size != compressed_size)
1133 {
1134 throw compression_error(error_code::invalid_data, format::xz, "xz block compressed-size mismatch");
1135 }
1136 if (declared_uncompressed_size.has_value() && *declared_uncompressed_size != block_output_size)
1137 {
1138 throw compression_error(error_code::invalid_data, format::xz, "xz block uncompressed-size mismatch");
1139 }
1140 for (std::size_t padding = static_cast< std::size_t >((4U - compressed_size % 4U) % 4U); padding > 0U; --padding)
1141 {
1142 if (read_byte() != std::byte{0})
1143 {
1144 throw compression_error(error_code::invalid_data, format::xz, "invalid xz block padding");
1145 }
1146 }
1147 if (check_identifier == 1U && read_little_endian_source(4U) != block_crc32.value())
1148 {
1149 throw compression_error(error_code::invalid_data, format::xz, "xz block CRC-32 mismatch");
1150 }
1151 if (check_identifier == 4U && read_little_endian_source(8U) != block_crc64.value())
1152 {
1153 throw compression_error(error_code::invalid_data, format::xz, "xz block CRC-64 mismatch");
1154 }
1155 records.push_back(block_record{header_size + compressed_size + check_size(check_identifier), block_output_size});
1156 }
1157
1159 index_crc.update(std::byte{0});
1160 std::size_t index_size = 1U;
1161 auto read_index_byte = [&]() -> std::byte
1162 {
1163 std::byte const value = read_byte();
1164 index_crc.update(value);
1165 ++index_size;
1166 return value;
1167 };
1168 auto read_index_integer = [&]() -> std::uint64_t
1169 {
1170 std::uint64_t value = 0U;
1171 for (std::uint8_t byte_index = 0U; byte_index < 9U; ++byte_index)
1172 {
1173 std::uint8_t const byte = std::to_integer< std::uint8_t >(read_index_byte());
1174 if (byte_index != 0U && byte == 0U)
1175 {
1176 throw compression_error(error_code::invalid_data, format::xz, "non-minimal xz variable-length integer");
1177 }
1178 value |= static_cast< std::uint64_t >(byte & 0x7fU) << (byte_index * 7U);
1179 if ((byte & 0x80U) == 0U)
1180 {
1181 return value;
1182 }
1183 }
1184 throw compression_error(error_code::invalid_data, format::xz, "oversized xz variable-length integer");
1185 };
1186 if (read_index_integer() != records.size())
1187 {
1188 throw compression_error(error_code::invalid_data, format::xz, "xz index block count mismatch");
1189 }
1190 for (block_record const& record : records)
1191 {
1192 if (read_index_integer() != record.unpadded_size || read_index_integer() != record.uncompressed_size)
1193 {
1194 throw compression_error(error_code::invalid_data, format::xz, "xz index record mismatch");
1195 }
1196 }
1197 while (index_size % 4U != 0U)
1198 {
1199 if (read_index_byte() != std::byte{0})
1200 {
1201 throw compression_error(error_code::invalid_data, format::xz, "invalid xz index padding");
1202 }
1203 }
1204 if (read_little_endian_source(4U) != index_crc.value())
1205 {
1206 throw compression_error(error_code::invalid_data, format::xz, "xz index checksum mismatch");
1207 }
1208 index_size += 4U;
1209 std::array< std::byte, 12U > footer{};
1210 for (std::byte& value : footer)
1211 {
1212 value = read_byte();
1213 }
1214 std::size_t footer_position = 0U;
1215 std::uint32_t const expected_footer_crc = static_cast< std::uint32_t >(read_little_endian(footer, footer_position, 4U));
1216 if (crc32(std::span< std::byte const >(footer).subspan(4U, 6U)) != expected_footer_crc)
1217 {
1218 throw compression_error(error_code::invalid_data, format::xz, "xz footer checksum mismatch");
1219 }
1220 std::uint64_t const backward_size = (read_little_endian(footer, footer_position, 4U) + 1U) * 4U;
1221 if (backward_size != index_size || footer[8U] != stream_flags[0U] || footer[9U] != stream_flags[1U] || footer[10U] != std::byte{'Y'} || footer[11U] != std::byte{'Z'})
1222 {
1223 throw compression_error(error_code::invalid_data, format::xz, "invalid xz stream footer");
1224 }
1225 decoded_stream = true;
1226 std::size_t padding_size = 0U;
1227 std::byte next{};
1228 while (source.peek(next) && next == std::byte{0})
1229 {
1230 static_cast< void >(read_byte());
1231 ++padding_size;
1232 }
1233 if (padding_size % 4U != 0U)
1234 {
1235 throw compression_error(error_code::invalid_data, format::xz, "xz stream padding is not a multiple of four bytes");
1236 }
1237 } while (!source.empty());
1238 return output;
1239 }
1240
1241} // namespace rpnx::compression::xz_codec
1242
1243#endif
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 peek(std::byte &value)
Inspects the next byte without consuming it.
Definition io.hpp:96
bool empty() const
Tests whether no unread byte remains.
Definition io.hpp:86
bool read(std::byte &value)
Reads one byte.
Definition io.hpp:116
Incremental reflected IEEE CRC-32 accumulator.
Definition io.hpp:169
std::uint32_t value() const noexcept
Returns the checksum for all bytes supplied so far.
Definition io.hpp:189
void update(std::byte value) noexcept
Includes one byte in the checksum.
Definition io.hpp:175
Incremental CRC-64/XZ accumulator for streamed block data.
Definition xz.hpp:400
std::uint64_t value() const noexcept
Returns the checksum for all supplied bytes.
Definition xz.hpp:419
void update(std::byte value) noexcept
Includes one byte in the checksum.
Definition xz.hpp:406
One-shot LZMA range decoder.
Definition xz.hpp:168
void finish()
Validate exact consumption and the LZMA terminal range state.
Definition xz.hpp:276
range_decoder(std::span< std::byte const > input)
Initializes a decoder from one complete LZMA chunk.
Definition xz.hpp:174
std::uint32_t decode_direct(std::uint8_t bit_count)
Decodes equiprobable direct bits.
Definition xz.hpp:257
std::uint8_t decode_bit(std::uint16_t &probability)
Decodes one adaptive binary symbol.
Definition xz.hpp:191
std::uint32_t decode_tree(std::span< std::uint16_t > probabilities, std::uint32_t leaf_base)
Decodes a most-significant-bit-first probability tree.
Definition xz.hpp:213
std::uint32_t decode_reverse_tree(std::span< std::uint16_t > probabilities, std::ptrdiff_t base, std::uint8_t bit_count)
Decodes a least-significant-bit-first probability tree.
Definition xz.hpp:234
One-shot LZMA range encoder used by the deterministic literal encoder.
Definition xz.hpp:312
void encode_bit(std::uint16_t &probability, std::uint8_t bit)
Encodes one adaptive binary symbol.
Definition xz.hpp:319
std::vector< std::byte > finish()
Finishes the range stream.
Definition xz.hpp:344
Shared iterator, byte-conversion, and checksum primitives.
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
Internal implementation of xz and its LZMA2 payload format.
Definition xz.hpp:26
constexpr std::size_t state_count
Number of LZMA state-machine states.
Definition xz.hpp:35
void decode_lzma_chunk(std::span< std::byte const > encoded, std::size_t uncompressed_size, lzma_model &model, std::vector< std::byte > &output, std::size_t history_begin, std::uint32_t dictionary_size, std::size_t output_limit, std::size_t dictionary_position_offset=0U)
Decodes one LZMA range-coded chunk into an LZMA2 dictionary.
Definition xz.hpp:567
constexpr std::uint32_t probability_total
LZMA probability-model total.
Definition xz.hpp:29
constexpr std::size_t position_state_count
Maximum number of position states.
Definition xz.hpp:37
constexpr std::size_t literal_coder_count
Number of literal contexts retained by the supported properties.
Definition xz.hpp:39
std::uint32_t decode_length(range_decoder &decoder, lzma_model::length_model &model, std::size_t position_state)
Decodes one LZMA match length.
Definition xz.hpp:517
std::size_t check_size(std::uint8_t check_identifier)
Returns the number of bytes in an xz integrity check.
Definition xz.hpp:484
std::uint32_t crc32(std::span< std::byte const > input) noexcept
Computes the reflected CRC-32 used by xz metadata and checks.
Definition xz.hpp:384
constexpr std::size_t literal_coder_size
Probability count in one LZMA literal coder.
Definition xz.hpp:41
constexpr std::uint32_t probability_move_bits
Adaptation shift applied after every probability decision.
Definition xz.hpp:31
output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const &options)
Decompresses one or more xz streams containing LZMA2.
Definition xz.hpp:925
std::uint64_t read_variable_integer(std::span< std::byte const > input, std::size_t &position)
Decodes one minimal xz variable-length integer.
Definition xz.hpp:456
std::uint32_t decode_dictionary_size(std::uint8_t properties)
Decodes the one-byte LZMA2 dictionary-size property.
Definition xz.hpp:730
void copy_match(std::vector< std::byte > &output, std::size_t history_begin, std::uint32_t dictionary_size, std::uint32_t distance, std::uint32_t length, std::size_t output_limit)
Copies one validated LZMA match into the output dictionary.
Definition xz.hpp:539
void update_literal_state(std::uint8_t &state) noexcept
Updates an LZMA state after decoding a literal.
Definition xz.hpp:494
output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const &options)
Compresses an iterator range as an xz stream containing LZMA2.
Definition xz.hpp:757
std::vector< std::byte > compress_lzma_literals(std::span< std::byte const > input, lzma_model &model, std::size_t dictionary_position, std::uint8_t previous_byte)
Encodes a literal-only LZMA range-coded chunk.
Definition xz.hpp:701
std::uint64_t read_little_endian(std::span< std::byte const > input, std::size_t &position, std::uint8_t byte_count)
Reads a fixed-width little-endian integer.
Definition xz.hpp:435
constexpr std::uint32_t range_top
Range threshold below which the arithmetic coder normalizes.
Definition xz.hpp:33
@ xz
xz container containing LZMA2 data.
@ 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.
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.
Size metadata collected while decoding one xz block.
Definition xz.hpp:45
std::uint64_t uncompressed_size
Number of bytes produced by the block.
Definition xz.hpp:47
std::uint64_t unpadded_size
Header, payload, and check size before four-byte padding.
Definition xz.hpp:46
Probability tables for the three LZMA match-length ranges.
Definition xz.hpp:55
std::array< std::array< std::uint16_t, 8U >, position_state_count > mid
Middle-range trees by position state.
Definition xz.hpp:59
std::uint16_t choice2
Selects the middle or high range.
Definition xz.hpp:57
std::array< std::uint16_t, 256U > high
High-range probability tree.
Definition xz.hpp:60
std::uint16_t choice
Selects the low range.
Definition xz.hpp:56
std::array< std::array< std::uint16_t, 8U >, position_state_count > low
Low-range trees by position state.
Definition xz.hpp:58
Adaptive probability tables and state used by LZMA.
Definition xz.hpp:52
std::array< std::uint16_t, state_count > is_rep0
Most-recent-distance probabilities.
Definition xz.hpp:73
std::uint32_t rep2
Third most recent repeated distance minus one.
Definition xz.hpp:65
std::uint8_t state
Current LZMA literal/match state.
Definition xz.hpp:67
std::uint8_t position_bits
Number of low position bits selecting a position state.
Definition xz.hpp:70
length_model match_length
Length model for new matches.
Definition xz.hpp:80
std::array< std::uint16_t, 114U > distance_special
Probability trees for middle distance bits.
Definition xz.hpp:78
std::uint32_t rep0
Most recent repeated match distance minus one.
Definition xz.hpp:63
length_model repeated_length
Length model for repeated matches.
Definition xz.hpp:81
void set_properties(std::uint8_t properties)
Configures lc, lp, and pb from one LZMA properties byte.
Definition xz.hpp:100
std::array< std::array< std::uint16_t, position_state_count >, state_count > is_rep0_long
Short repeated-match probabilities.
Definition xz.hpp:76
std::uint32_t rep1
Second most recent repeated distance minus one.
Definition xz.hpp:64
std::uint8_t literal_position_bits
Number of low position bits in a literal context.
Definition xz.hpp:69
std::array< std::array< std::uint16_t, position_state_count >, state_count > is_match
Literal-versus-match probabilities.
Definition xz.hpp:71
std::array< std::array< std::uint16_t, literal_coder_size >, literal_coder_count > literal
Literal probability trees by context.
Definition xz.hpp:82
std::array< std::uint16_t, 16U > distance_align
Reversed tree for low aligned distance bits.
Definition xz.hpp:79
std::array< std::uint16_t, state_count > is_rep1
Second-distance probabilities.
Definition xz.hpp:74
std::array< std::array< std::uint16_t, 64U >, 4U > distance_slot
Distance-slot trees by match-length state.
Definition xz.hpp:77
std::array< std::uint16_t, state_count > is_rep
New-versus-repeated match probabilities.
Definition xz.hpp:72
std::uint8_t literal_context_bits
Number of high previous-byte bits in a literal context.
Definition xz.hpp:68
std::array< std::uint16_t, state_count > is_rep2
Third-versus-fourth distance probabilities.
Definition xz.hpp:75
std::uint32_t rep3
Fourth most recent repeated distance minus one.
Definition xz.hpp:66
void reset()
Reset the probability model and recent-match state.
Definition xz.hpp:85