RPNX::Compress
Self-contained C++20 compression and ZIP library
 
Loading...
Searching...
No Matches
zstandard.hpp
Go to the documentation of this file.
1#ifndef RPNX_COMPRESSION_IMPLEMENTATION_ZSTANDARD_HPP
2#define RPNX_COMPRESSION_IMPLEMENTATION_ZSTANDARD_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 Zstandard frame, entropy-table, and sequence implementation.
22 */
23
24/** @brief Internal implementation of Zstandard compression and decompression. */
26{
27
28 /** One state transition in an FSE decoding table. */
29 struct fse_entry
30 {
31 /** @brief Symbol emitted when this state is decoded. */
32 std::uint8_t symbol;
33 /** @brief Number of bits used to select the next state. */
34 std::uint8_t number_of_bits;
35 /** @brief Baseline added to the decoded transition bits. */
36 std::uint16_t baseline;
37 };
38
39 /** Native FSE decoding table indexed by the current decoder state. */
40 struct fse_table
41 {
42 /** @brief Base-two logarithm of the state count. */
43 std::uint8_t accuracy_log;
44 /** @brief State transitions indexed by current state. */
45 std::vector< fse_entry > entries;
46 };
47
48 /** Parsed normalized FSE probabilities and their encoded byte length. */
50 {
51 /** @brief Accuracy log encoded by the description. */
52 std::uint8_t accuracy_log;
53 /** @brief Normalized probability for every symbol through the last encoded symbol. */
54 std::vector< std::int16_t > probabilities;
55 /** @brief Whole number of source bytes occupied by the description. */
56 std::size_t consumed_bytes;
57 };
58
59 /** Decoded literals and the number of compressed-block bytes they occupy. */
61 {
62 /** @brief Regenerated literal bytes. */
63 std::vector< std::byte > literals;
64 /** @brief Bytes occupied by the literals section in its compressed block. */
65 std::size_t consumed_bytes;
66 };
67
68 /** One node in a native canonical Huffman decoding tree. */
70 {
71 /** @brief Child selected by a zero bit, if present. */
72 std::optional< std::uint16_t > zero_child;
73 /** @brief Child selected by a one bit, if present. */
74 std::optional< std::uint16_t > one_child;
75 /** @brief Literal value for a leaf node. */
76 std::optional< std::uint8_t > symbol;
77 };
78
79 /** Canonical Huffman decoding tree retained for treeless literal blocks. */
81 {
82 /** @brief Tree nodes with the root at index zero. */
83 std::vector< huffman_node > nodes;
84 };
85
86 /** A parsed Huffman tree description and its encoded byte length. */
88 {
89 /** @brief Parsed canonical decoding tree. */
91 /** @brief Bytes occupied by the encoded tree description. */
92 std::size_t consumed_bytes;
93 };
94
95 /** Persistent entropy tables and repeat offsets shared by compressed blocks. */
97 {
98 /** @brief Huffman table retained for treeless literal blocks. */
99 std::optional< huffman_table > huffman_literals_table;
100 /** @brief Most recent literal-length FSE table. */
101 std::optional< fse_table > literal_length_table;
102 /** @brief Most recent offset-code FSE table. */
103 std::optional< fse_table > offset_table;
104 /** @brief Most recent match-length FSE table. */
105 std::optional< fse_table > match_length_table;
106 /** @brief Three repeat offsets in most-recent-first order. */
107 std::array< std::size_t, 3U > repeated_offsets{1U, 4U, 8U};
108 };
109
110 /** Baseline and additional-bit count selected by a sequence length code. */
112 {
113 /** @brief Smallest length represented by this code. */
114 std::uint32_t baseline;
115 /** @brief Number of additional value bits following the code. */
116 std::uint8_t number_of_bits;
117 };
118
119 /** Encoded sequence length code and its additional-bit payload. */
121 {
122 /** @brief Sequence length code. */
123 std::uint8_t code;
124 /** @brief Additional value stored after the code. */
125 std::uint32_t additional_value;
126 /** @brief Number of significant bits in additional_value. */
127 std::uint8_t number_of_bits;
128 };
129
130 /** Literal-length code definitions from the Zstandard format. */
131 inline constexpr std::array< length_code_entry, 36U > literal_length_codes{length_code_entry{0U, 0U}, {1U, 0U}, {2U, 0U}, {3U, 0U}, {4U, 0U}, {5U, 0U}, {6U, 0U}, {7U, 0U}, {8U, 0U}, {9U, 0U}, {10U, 0U}, {11U, 0U}, {12U, 0U}, {13U, 0U}, {14U, 0U}, {15U, 0U}, {16U, 1U}, {18U, 1U}, {20U, 1U}, {22U, 1U}, {24U, 2U}, {28U, 2U}, {32U, 3U}, {40U, 3U}, {48U, 4U}, {64U, 6U}, {128U, 7U}, {256U, 8U}, {512U, 9U}, {1024U, 10U}, {2048U, 11U}, {4096U, 12U}, {8192U, 13U}, {16384U, 14U}, {32768U, 15U}, {65536U, 16U}};
132
133 /** Match-length code definitions from the Zstandard format. */
134 inline constexpr std::array< length_code_entry, 53U > match_length_codes{length_code_entry{3U, 0U}, {4U, 0U}, {5U, 0U}, {6U, 0U}, {7U, 0U}, {8U, 0U}, {9U, 0U}, {10U, 0U}, {11U, 0U}, {12U, 0U}, {13U, 0U}, {14U, 0U}, {15U, 0U}, {16U, 0U}, {17U, 0U}, {18U, 0U}, {19U, 0U}, {20U, 0U}, {21U, 0U}, {22U, 0U}, {23U, 0U}, {24U, 0U}, {25U, 0U}, {26U, 0U}, {27U, 0U}, {28U, 0U}, {29U, 0U}, {30U, 0U}, {31U, 0U}, {32U, 0U}, {33U, 0U}, {34U, 0U}, {35U, 1U}, {37U, 1U}, {39U, 1U}, {41U, 1U}, {43U, 2U}, {47U, 2U}, {51U, 3U}, {59U, 3U}, {67U, 4U}, {83U, 4U}, {99U, 5U}, {131U, 7U}, {259U, 8U}, {515U, 9U}, {1027U, 10U}, {2051U, 11U}, {4099U, 12U}, {8195U, 13U}, {16387U, 14U}, {32771U, 15U}, {65539U, 16U}};
135
136 /** Read a byte-aligned, forward, least-significant-bit-first bitstream. */
138 {
139 public:
140 /**
141 * @brief Constructs a reader over a non-owning byte span.
142 * @param input Complete forward bitstream retained for this object's lifetime.
143 */
144 explicit forward_bit_reader(std::span< std::byte const > input) noexcept : m_input(input)
145 {
146 }
147
148 /**
149 * @brief Returns the next bits without consuming them.
150 * @param count Number of bits, at most 32.
151 * @return Field value with the earliest bit in the least significant position.
152 */
153 [[nodiscard]] std::uint32_t peek_bits(std::uint8_t count) const
154 {
155 if (count > 32U || m_bit_position > m_input.size() * 8U || count > m_input.size() * 8U - m_bit_position)
156 {
157 throw compression_error(error_code::invalid_data, format::zstandard, "truncated forward Zstandard bitstream");
158 }
159 std::uint32_t value = 0U;
160 for (std::uint8_t bit_index = 0U; bit_index < count; ++bit_index)
161 {
162 std::size_t const absolute_bit = m_bit_position + bit_index;
163 std::uint8_t const byte = std::to_integer< std::uint8_t >(m_input[absolute_bit / 8U]);
164 value |= static_cast< std::uint32_t >((byte >> (absolute_bit % 8U)) & 1U) << bit_index;
165 }
166 return value;
167 }
168
169 /**
170 * @brief Consumes and returns the next bits.
171 * @param count Number of bits, at most 32.
172 * @return Field value with the earliest bit in the least significant position.
173 */
174 [[nodiscard]] std::uint32_t read_bits(std::uint8_t count)
175 {
176 std::uint32_t const value = peek_bits(count);
177 m_bit_position += count;
178 return value;
179 }
180
181 /**
182 * @brief Returns the whole number of bytes occupied by consumed bits.
183 * @return Consumed bit count rounded up to bytes.
184 */
185 [[nodiscard]] std::size_t consumed_bytes() const noexcept
186 {
187 return (m_bit_position + 7U) / 8U;
188 }
189
190 private:
191 std::span< std::byte const > m_input;
192 std::size_t m_bit_position = 0U;
193 };
194
195 /** Read a Zstandard entropy bitstream from its end toward its beginning. */
197 {
198 public:
199 /**
200 * @brief Constructs a reader and removes the mandatory final-bit marker.
201 * @param input Complete reverse bitstream retained for this object's lifetime.
202 */
203 explicit reverse_bit_reader(std::span< std::byte const > input) : m_input(input)
204 {
205 if (input.empty() || input.back() == std::byte{0})
206 {
207 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard reverse bitstream marker");
208 }
209 std::uint8_t const last_byte = std::to_integer< std::uint8_t >(input.back());
210 std::uint8_t marker_position = 0U;
211 for (std::uint8_t bit_index = 1U; bit_index < 8U; ++bit_index)
212 {
213 if ((last_byte >> bit_index) != 0U)
214 {
215 marker_position = bit_index;
216 }
217 }
218 m_bit_position = (input.size() - 1U) * 8U + marker_position;
219 }
220
221 /**
222 * @brief Consumes bits preceding the current reverse-stream position.
223 * @param count Number of bits, at most 32.
224 * @return Field value in least-significant-bit-first wire order.
225 */
226 [[nodiscard]] std::uint32_t read_bits(std::uint8_t count)
227 {
228 if (count > 32U || count > m_bit_position)
229 {
230 throw compression_error(error_code::invalid_data, format::zstandard, "truncated reverse Zstandard bitstream");
231 }
232 std::size_t const first_bit = m_bit_position - count;
233 std::uint32_t value = 0U;
234 for (std::uint8_t bit_index = 0U; bit_index < count; ++bit_index)
235 {
236 std::size_t const absolute_bit = first_bit + bit_index;
237 std::uint8_t const byte = std::to_integer< std::uint8_t >(m_input[absolute_bit / 8U]);
238 value |= static_cast< std::uint32_t >((byte >> (absolute_bit % 8U)) & 1U) << bit_index;
239 }
240 m_bit_position = first_bit;
241 return value;
242 }
243
244 /**
245 * @brief Returns the number of useful bits not yet consumed.
246 * @return Remaining bit count before the end marker.
247 */
248 [[nodiscard]] std::size_t remaining_bits() const noexcept
249 {
250 return m_bit_position;
251 }
252
253 private:
254 std::span< std::byte const > m_input;
255 std::size_t m_bit_position = 0U;
256 };
257
258 /**
259 * @brief Rotates a 64-bit value left without compiler-specific intrinsics.
260 * @param value Value to rotate.
261 * @param count Rotation distance in the range 1--63.
262 * @return Rotated value.
263 */
264 [[nodiscard]] inline std::uint64_t rotate_left(std::uint64_t value, std::uint8_t count) noexcept
265 {
266 return (value << count) | (value >> (64U - count));
267 }
268
269 /**
270 * @brief Reads an unchecked little-endian 32-bit word.
271 * @param input Range containing at least four bytes at @p offset.
272 * @param offset Position of the first byte.
273 * @return Decoded word.
274 */
275 [[nodiscard]] inline std::uint32_t read_word32(std::span< std::byte const > input, std::size_t offset) noexcept
276 {
277 return static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(input[offset])) | (static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(input[offset + 1U])) << 8U) | (static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(input[offset + 2U])) << 16U) | (static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(input[offset + 3U])) << 24U);
278 }
279
280 /**
281 * @brief Reads an unchecked little-endian 64-bit word.
282 * @param input Range containing at least eight bytes at @p offset.
283 * @param offset Position of the first byte.
284 * @return Decoded word.
285 */
286 [[nodiscard]] inline std::uint64_t read_word64(std::span< std::byte const > input, std::size_t offset) noexcept
287 {
288 std::uint64_t value = 0U;
289 for (std::uint8_t byte_index = 0U; byte_index < 8U; ++byte_index)
290 {
291 value |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(input[offset + byte_index])) << (byte_index * 8U);
292 }
293 return value;
294 }
295
296 /**
297 * @brief Computes XXH64 as required by the Zstandard frame checksum.
298 * @param input Bytes to hash.
299 * @param seed XXH64 seed.
300 * @return Finalized 64-bit hash.
301 */
302 [[nodiscard]] inline std::uint64_t xxhash64(std::span< std::byte const > input, std::uint64_t seed = 0U) noexcept
303 {
304 constexpr std::uint64_t prime1 = 0x9e3779b185ebca87ULL;
305 constexpr std::uint64_t prime2 = 0xc2b2ae3d27d4eb4fULL;
306 constexpr std::uint64_t prime3 = 0x165667b19e3779f9ULL;
307 constexpr std::uint64_t prime4 = 0x85ebca77c2b2ae63ULL;
308 constexpr std::uint64_t prime5 = 0x27d4eb2f165667c5ULL;
309 auto round = [](std::uint64_t accumulator, std::uint64_t value)
310 {
311 accumulator += value * prime2;
312 accumulator = rotate_left(accumulator, 31U);
313 return accumulator * prime1;
314 };
315 auto merge = [&](std::uint64_t accumulator, std::uint64_t lane)
316 {
317 accumulator ^= round(0U, lane);
318 return accumulator * prime1 + prime4;
319 };
320
321 std::size_t position = 0U;
322 std::uint64_t hash = 0U;
323 if (input.size() >= 32U)
324 {
325 std::uint64_t lane1 = seed + prime1 + prime2;
326 std::uint64_t lane2 = seed + prime2;
327 std::uint64_t lane3 = seed;
328 std::uint64_t lane4 = seed - prime1;
329 while (position + 32U <= input.size())
330 {
331 lane1 = round(lane1, read_word64(input, position));
332 lane2 = round(lane2, read_word64(input, position + 8U));
333 lane3 = round(lane3, read_word64(input, position + 16U));
334 lane4 = round(lane4, read_word64(input, position + 24U));
335 position += 32U;
336 }
337 hash = rotate_left(lane1, 1U) + rotate_left(lane2, 7U) + rotate_left(lane3, 12U) + rotate_left(lane4, 18U);
338 hash = merge(hash, lane1);
339 hash = merge(hash, lane2);
340 hash = merge(hash, lane3);
341 hash = merge(hash, lane4);
342 }
343 else
344 {
345 hash = seed + prime5;
346 }
347 hash += input.size();
348 while (position + 8U <= input.size())
349 {
350 hash ^= round(0U, read_word64(input, position));
351 hash = rotate_left(hash, 27U) * prime1 + prime4;
352 position += 8U;
353 }
354 if (position + 4U <= input.size())
355 {
356 hash ^= static_cast< std::uint64_t >(read_word32(input, position)) * prime1;
357 hash = rotate_left(hash, 23U) * prime2 + prime3;
358 position += 4U;
359 }
360 while (position < input.size())
361 {
362 hash ^= std::to_integer< std::uint8_t >(input[position]) * prime5;
363 hash = rotate_left(hash, 11U) * prime1;
364 ++position;
365 }
366 hash ^= hash >> 33U;
367 hash *= prime2;
368 hash ^= hash >> 29U;
369 hash *= prime3;
370 hash ^= hash >> 32U;
371 return hash;
372 }
373
374 /** Incremental XXH64 accumulator for streamed Zstandard checksums. */
376 {
377 public:
378 /**
379 * @brief Includes one byte in the hash.
380 * @param value Next byte in stream order.
381 */
382 void update(std::byte value) noexcept
383 {
384 m_buffer[m_buffer_size++] = value;
385 ++m_total_size;
386 if (m_buffer_size == m_buffer.size())
387 {
388 m_lane1 = round(m_lane1, read_word64(m_buffer, 0U));
389 m_lane2 = round(m_lane2, read_word64(m_buffer, 8U));
390 m_lane3 = round(m_lane3, read_word64(m_buffer, 16U));
391 m_lane4 = round(m_lane4, read_word64(m_buffer, 24U));
392 m_buffer_size = 0U;
393 }
394 }
395
396 /**
397 * @brief Returns the hash for all supplied bytes.
398 * @return Finalized XXH64 value without changing accumulator state.
399 */
400 [[nodiscard]] std::uint64_t value() const noexcept
401 {
402 constexpr std::uint64_t prime1 = 0x9e3779b185ebca87ULL;
403 constexpr std::uint64_t prime2 = 0xc2b2ae3d27d4eb4fULL;
404 constexpr std::uint64_t prime3 = 0x165667b19e3779f9ULL;
405 constexpr std::uint64_t prime4 = 0x85ebca77c2b2ae63ULL;
406 constexpr std::uint64_t prime5 = 0x27d4eb2f165667c5ULL;
407 std::uint64_t hash = 0U;
408 if (m_total_size >= 32U)
409 {
410 hash = rotate_left(m_lane1, 1U) + rotate_left(m_lane2, 7U) + rotate_left(m_lane3, 12U) + rotate_left(m_lane4, 18U);
411 hash = merge(hash, m_lane1);
412 hash = merge(hash, m_lane2);
413 hash = merge(hash, m_lane3);
414 hash = merge(hash, m_lane4);
415 }
416 else
417 {
418 hash = prime5;
419 }
420 hash += m_total_size;
421 std::size_t position = 0U;
422 std::span< std::byte const > const remaining(m_buffer.data(), m_buffer_size);
423 while (position + 8U <= remaining.size())
424 {
425 hash ^= round(0U, read_word64(remaining, position));
426 hash = rotate_left(hash, 27U) * prime1 + prime4;
427 position += 8U;
428 }
429 if (position + 4U <= remaining.size())
430 {
431 hash ^= static_cast< std::uint64_t >(read_word32(remaining, position)) * prime1;
432 hash = rotate_left(hash, 23U) * prime2 + prime3;
433 position += 4U;
434 }
435 while (position < remaining.size())
436 {
437 hash ^= std::to_integer< std::uint8_t >(remaining[position]) * prime5;
438 hash = rotate_left(hash, 11U) * prime1;
439 ++position;
440 }
441 hash ^= hash >> 33U;
442 hash *= prime2;
443 hash ^= hash >> 29U;
444 hash *= prime3;
445 hash ^= hash >> 32U;
446 return hash;
447 }
448
449 private:
450 /**
451 * @brief Applies one XXH64 lane round.
452 * @param lane Current lane accumulator.
453 * @param value Next little-endian input word.
454 * @return Updated lane accumulator.
455 */
456 [[nodiscard]] static std::uint64_t round(std::uint64_t lane, std::uint64_t value) noexcept
457 {
458 constexpr std::uint64_t prime1 = 0x9e3779b185ebca87ULL;
459 constexpr std::uint64_t prime2 = 0xc2b2ae3d27d4eb4fULL;
460 return rotate_left(lane + value * prime2, 31U) * prime1;
461 }
462
463 /**
464 * @brief Merges one accumulated lane into the final hash.
465 * @param hash Current combined hash.
466 * @param lane Lane to merge.
467 * @return Updated combined hash.
468 */
469 [[nodiscard]] static std::uint64_t merge(std::uint64_t hash, std::uint64_t lane) noexcept
470 {
471 constexpr std::uint64_t prime1 = 0x9e3779b185ebca87ULL;
472 constexpr std::uint64_t prime4 = 0x85ebca77c2b2ae63ULL;
473 hash ^= round(0U, lane);
474 return hash * prime1 + prime4;
475 }
476
477 std::array< std::byte, 32U > m_buffer{};
478 std::size_t m_buffer_size = 0U;
479 std::uint64_t m_total_size = 0U;
480 std::uint64_t m_lane1 = 0x9e3779b185ebca87ULL + 0xc2b2ae3d27d4eb4fULL;
481 std::uint64_t m_lane2 = 0xc2b2ae3d27d4eb4fULL;
482 std::uint64_t m_lane3 = 0U;
483 std::uint64_t m_lane4 = 0U - 0x9e3779b185ebca87ULL;
484 };
485
486 /**
487 * @brief Appends a little-endian integer with a selected byte count.
488 * @param output Buffer receiving encoded bytes.
489 * @param value Value to encode.
490 * @param byte_count Number of low-order bytes to append.
491 */
492 inline void append_integer(std::vector< std::byte >& output, std::uint64_t value, std::uint8_t byte_count)
493 {
494 for (std::uint8_t byte_index = 0U; byte_index < byte_count; ++byte_index)
495 {
496 output.push_back(static_cast< std::byte >((value >> (byte_index * 8U)) & 0xffU));
497 }
498 }
499
500 /**
501 * @brief Reads a checked little-endian integer with a selected byte count.
502 * @param input Source bytes.
503 * @param offset Position of the first byte.
504 * @param byte_count Width from zero through eight bytes.
505 * @return Decoded value.
506 */
507 [[nodiscard]] inline std::uint64_t read_integer(std::span< std::byte const > input, std::size_t offset, std::uint8_t byte_count)
508 {
509 if (offset > input.size() || byte_count > input.size() - offset)
510 {
511 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Zstandard frame field");
512 }
513 std::uint64_t value = 0U;
514 for (std::uint8_t byte_index = 0U; byte_index < byte_count; ++byte_index)
515 {
516 value |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(input[offset + byte_index])) << (byte_index * 8U);
517 }
518 return value;
519 }
520
521 /**
522 * @brief Parses one forward-coded normalized FSE probability description.
523 * @param input Encoded probability description.
524 * @param maximum_symbol Largest permitted symbol value.
525 * @param maximum_accuracy_log Largest permitted FSE accuracy log.
526 * @return Parsed probabilities and occupied byte count.
527 */
528 [[nodiscard]] inline normalized_probability_result parse_normalized_probabilities(std::span< std::byte const > input, std::uint8_t maximum_symbol, std::uint8_t maximum_accuracy_log)
529 {
530 forward_bit_reader reader(input);
531 std::uint8_t const accuracy_log = static_cast< std::uint8_t >(reader.read_bits(4U) + 5U);
532 if (accuracy_log > maximum_accuracy_log)
533 {
534 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE accuracy log exceeds its format limit");
535 }
536
537 std::vector< std::int16_t > probabilities(static_cast< std::size_t >(maximum_symbol) + 1U, 0);
538 std::uint32_t remaining = (1U << accuracy_log) + 1U;
539 std::uint32_t threshold = 1U << accuracy_log;
540 std::uint8_t number_of_bits = static_cast< std::uint8_t >(accuracy_log + 1U);
541 std::size_t symbol = 0U;
542 std::size_t symbols_with_probability = 0U;
543
544 while (remaining > 1U)
545 {
546 if (symbol >= probabilities.size())
547 {
548 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE description has too many symbols");
549 }
550
551 std::uint32_t const low_value = reader.peek_bits(static_cast< std::uint8_t >(number_of_bits - 1U));
552 std::uint32_t const threshold_offset = (2U * threshold - 1U) - remaining;
553 std::uint32_t encoded_probability = 0U;
554 if (low_value < threshold_offset)
555 {
556 encoded_probability = reader.read_bits(static_cast< std::uint8_t >(number_of_bits - 1U));
557 }
558 else
559 {
560 encoded_probability = reader.read_bits(number_of_bits);
561 if (encoded_probability >= threshold)
562 {
563 encoded_probability -= threshold_offset;
564 }
565 }
566
567 std::int16_t const probability = static_cast< std::int16_t >(encoded_probability) - 1;
568 probabilities[symbol] = probability;
569 ++symbol;
570 if (probability != 0)
571 {
572 ++symbols_with_probability;
573 }
574 std::uint32_t const magnitude = probability < 0 ? static_cast< std::uint32_t >(-probability) : static_cast< std::uint32_t >(probability);
575 if (magnitude >= remaining)
576 {
577 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE probabilities exceed the table size");
578 }
579 remaining -= magnitude;
580
581 if (probability == 0)
582 {
583 std::size_t repeated_zero_count = 0U;
584 std::uint32_t repeat_count = 0U;
585 do
586 {
587 repeat_count = reader.read_bits(2U);
588 repeated_zero_count += repeat_count;
589 } while (repeat_count == 3U);
590 if (repeated_zero_count > probabilities.size() - symbol)
591 {
592 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE zero run exceeds the symbol alphabet");
593 }
594 symbol += repeated_zero_count;
595 }
596
597 while (remaining < threshold)
598 {
599 --number_of_bits;
600 threshold >>= 1U;
601 }
602 }
603
604 if (symbols_with_probability < 2U)
605 {
606 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE table requires at least two symbols");
607 }
608 return normalized_probability_result{accuracy_log, std::move(probabilities), reader.consumed_bytes()};
609 }
610
611 /**
612 * @brief Constructs the FSE decoding table for normalized probabilities.
613 * @param probabilities Normalized counts; negative one represents a low-probability symbol.
614 * @param accuracy_log Base-two logarithm of the table size.
615 * @return Validated state-transition table.
616 */
617 [[nodiscard]] inline fse_table build_fse_table(std::span< std::int16_t const > probabilities, std::uint8_t accuracy_log)
618 {
619 if (accuracy_log > 9U)
620 {
621 throw compression_error(error_code::invalid_data, format::zstandard, "unsupported Zstandard FSE table size");
622 }
623 std::size_t const table_size = static_cast< std::size_t >(1U) << accuracy_log;
624 std::size_t probability_total = 0U;
625 for (std::int16_t probability : probabilities)
626 {
627 if (probability < -1)
628 {
629 throw compression_error(error_code::invalid_data, format::zstandard, "invalid negative Zstandard FSE probability");
630 }
631 probability_total += probability < 0 ? 1U : static_cast< std::size_t >(probability);
632 }
633 if (probability_total != table_size)
634 {
635 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard FSE probabilities do not fill the table");
636 }
637
638 std::vector< fse_entry > entries(table_size, fse_entry{0U, 0U, 0U});
639 std::vector< bool > assigned(table_size, false);
640 std::vector< std::uint16_t > next_state(probabilities.size(), 0U);
641 std::size_t high_position = table_size;
642 for (std::size_t symbol = 0U; symbol < probabilities.size(); ++symbol)
643 {
644 std::int16_t const probability = probabilities[symbol];
645 if (probability == -1)
646 {
647 if (high_position == 0U)
648 {
649 throw compression_error(error_code::invalid_data, format::zstandard, "invalid low-probability Zstandard FSE states");
650 }
651 --high_position;
652 entries[high_position].symbol = static_cast< std::uint8_t >(symbol);
653 assigned[high_position] = true;
654 next_state[symbol] = 1U;
655 }
656 else
657 {
658 next_state[symbol] = static_cast< std::uint16_t >(probability);
659 }
660 }
661
662 std::size_t const table_mask = table_size - 1U;
663 std::size_t const spread_step = (table_size >> 1U) + (table_size >> 3U) + 3U;
664 std::size_t position = 0U;
665 for (std::size_t symbol = 0U; symbol < probabilities.size(); ++symbol)
666 {
667 std::int16_t const probability = probabilities[symbol];
668 for (std::int16_t occurrence = 0; occurrence < probability; ++occurrence)
669 {
670 if (assigned[position])
671 {
672 throw compression_error(error_code::invalid_data, format::zstandard, "overlapping Zstandard FSE state spread");
673 }
674 entries[position].symbol = static_cast< std::uint8_t >(symbol);
675 assigned[position] = true;
676 position = (position + spread_step) & table_mask;
677 while (position >= high_position)
678 {
679 position = (position + spread_step) & table_mask;
680 }
681 }
682 }
683 if (position != 0U || std::find(assigned.begin(), assigned.end(), false) != assigned.end())
684 {
685 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard FSE state spread");
686 }
687
688 for (std::size_t state = 0U; state < table_size; ++state)
689 {
690 std::uint8_t const symbol = entries[state].symbol;
691 std::uint16_t const symbol_state = next_state[symbol]++;
692 if (symbol_state == 0U)
693 {
694 throw compression_error(error_code::invalid_data, format::zstandard, "zero-width Zstandard FSE symbol state");
695 }
696 std::uint8_t const state_log = static_cast< std::uint8_t >(std::bit_width(symbol_state) - 1);
697 std::uint8_t const number_of_bits = static_cast< std::uint8_t >(accuracy_log - state_log);
698 entries[state].number_of_bits = number_of_bits;
699 entries[state].baseline = static_cast< std::uint16_t >((static_cast< std::uint32_t >(symbol_state) << number_of_bits) - table_size);
700 }
701 return fse_table{accuracy_log, std::move(entries)};
702 }
703
704 /**
705 * @brief Constructs a one-symbol FSE table for RLE sequence mode.
706 * @param symbol Repeated symbol.
707 * @return Single-state decoding table.
708 */
709 [[nodiscard]] inline fse_table build_rle_fse_table(std::uint8_t symbol)
710 {
711 return fse_table{0U, {fse_entry{symbol, 0U, 0U}}};
712 }
713
714 /**
715 * @brief Builds the format-defined predefined literal-length FSE table.
716 * @return Predefined literal-length state-transition table.
717 */
719 {
720 constexpr std::array< std::int16_t, 36U > probabilities{4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, -1, -1, -1, -1};
721 return build_fse_table(probabilities, 6U);
722 }
723
724 /**
725 * @brief Builds the format-defined predefined match-length FSE table.
726 * @return Predefined match-length state-transition table.
727 */
729 {
730 constexpr std::array< std::int16_t, 53U > probabilities{1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1};
731 return build_fse_table(probabilities, 6U);
732 }
733
734 /**
735 * @brief Builds the format-defined predefined offset-code FSE table.
736 * @return Predefined offset-code state-transition table.
737 */
739 {
740 constexpr std::array< std::int16_t, 29U > probabilities{1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1};
741 return build_fse_table(probabilities, 5U);
742 }
743
744 /**
745 * @brief Decodes the two-state FSE stream used for Huffman weights.
746 * @param input Encoded normalized probabilities followed by reverse-coded states.
747 * @return Explicit Huffman weights.
748 */
749 [[nodiscard]] inline std::vector< std::uint8_t > decode_fse_weights(std::span< std::byte const > input)
750 {
752 if (probabilities.consumed_bytes >= input.size())
753 {
754 throw compression_error(error_code::invalid_data, format::zstandard, "missing FSE-compressed Zstandard Huffman weights");
755 }
756 fse_table const table = build_fse_table(probabilities.probabilities, probabilities.accuracy_log);
757 reverse_bit_reader reader(input.subspan(probabilities.consumed_bytes));
758 std::size_t state1 = reader.read_bits(table.accuracy_log);
759 std::size_t state2 = reader.read_bits(table.accuracy_log);
760 if (state1 >= table.entries.size() || state2 >= table.entries.size())
761 {
762 throw compression_error(error_code::invalid_data, format::zstandard, "invalid initial Huffman-weight FSE state");
763 }
764
765 std::vector< std::uint8_t > weights;
766 weights.reserve(255U);
767 bool finished = false;
768 while (!finished)
769 {
770 if (weights.size() >= 255U)
771 {
772 throw compression_error(error_code::invalid_data, format::zstandard, "too many Zstandard Huffman weights");
773 }
774 fse_entry const transition1 = table.entries[state1];
775 weights.push_back(transition1.symbol);
776 if (transition1.number_of_bits > reader.remaining_bits())
777 {
778 if (weights.size() >= 255U)
779 {
780 throw compression_error(error_code::invalid_data, format::zstandard, "too many Zstandard Huffman weights");
781 }
782 weights.push_back(table.entries[state2].symbol);
783 finished = true;
784 continue;
785 }
786 state1 = transition1.baseline + reader.read_bits(transition1.number_of_bits);
787 if (state1 >= table.entries.size())
788 {
789 throw compression_error(error_code::invalid_data, format::zstandard, "updated Huffman-weight FSE state exceeds its table");
790 }
791
792 if (weights.size() >= 255U)
793 {
794 throw compression_error(error_code::invalid_data, format::zstandard, "too many Zstandard Huffman weights");
795 }
796 fse_entry const transition2 = table.entries[state2];
797 weights.push_back(transition2.symbol);
798 if (transition2.number_of_bits > reader.remaining_bits())
799 {
800 if (weights.size() >= 255U)
801 {
802 throw compression_error(error_code::invalid_data, format::zstandard, "too many Zstandard Huffman weights");
803 }
804 weights.push_back(table.entries[state1].symbol);
805 finished = true;
806 continue;
807 }
808 state2 = transition2.baseline + reader.read_bits(transition2.number_of_bits);
809 if (state2 >= table.entries.size())
810 {
811 throw compression_error(error_code::invalid_data, format::zstandard, "updated Huffman-weight FSE state exceeds its table");
812 }
813 }
814 return weights;
815 }
816
817 /**
818 * @brief Builds the canonical Huffman tree implied by transmitted weights.
819 * @param explicit_weights Weights for all symbols except the inferred final symbol.
820 * @return Validated canonical decoding tree.
821 */
822 [[nodiscard]] inline huffman_table build_huffman_table(std::span< std::uint8_t const > explicit_weights)
823 {
824 if (explicit_weights.empty() || explicit_weights.size() >= 256U)
825 {
826 throw compression_error(error_code::invalid_data, format::zstandard, "invalid number of Zstandard Huffman weights");
827 }
828 std::uint32_t weight_total = 0U;
829 for (std::uint8_t weight : explicit_weights)
830 {
831 if (weight > 11U)
832 {
833 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard Huffman weight exceeds the maximum tree depth");
834 }
835 if (weight != 0U)
836 {
837 weight_total += 1U << (weight - 1U);
838 }
839 }
840 if (weight_total == 0U)
841 {
842 throw compression_error(error_code::invalid_data, format::zstandard, "empty Zstandard Huffman tree");
843 }
844 std::uint32_t const table_size = std::bit_ceil(weight_total + 1U);
845 if (table_size > (1U << 11U))
846 {
847 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard Huffman tree exceeds the maximum depth");
848 }
849 std::uint32_t const final_weight_value = table_size - weight_total;
850 if (!std::has_single_bit(final_weight_value))
851 {
852 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard Huffman weights do not complete a canonical tree");
853 }
854 std::uint8_t const final_weight = static_cast< std::uint8_t >(std::bit_width(final_weight_value));
855 std::uint8_t const table_log = static_cast< std::uint8_t >(std::bit_width(table_size) - 1);
856 std::vector< std::uint8_t > weights(explicit_weights.begin(), explicit_weights.end());
857 weights.push_back(final_weight);
858
859 huffman_table table{{huffman_node{}}};
860 std::uint32_t code_space = 0U;
861 for (std::uint8_t weight = 1U; weight <= table_log; ++weight)
862 {
863 for (std::size_t symbol = 0U; symbol < weights.size(); ++symbol)
864 {
865 if (weights[symbol] != weight)
866 {
867 continue;
868 }
869 std::uint8_t const number_of_bits = static_cast< std::uint8_t >(table_log + 1U - weight);
870 std::uint32_t const code = code_space >> (weight - 1U);
871 std::size_t node_index = 0U;
872 for (std::uint8_t depth = 0U; depth < number_of_bits; ++depth)
873 {
874 if (table.nodes[node_index].symbol.has_value())
875 {
876 throw compression_error(error_code::invalid_data, format::zstandard, "overlapping Zstandard Huffman prefix codes");
877 }
878 std::uint8_t const bit = static_cast< std::uint8_t >((code >> (number_of_bits - depth - 1U)) & 1U);
879 std::optional< std::uint16_t >& child = bit == 0U ? table.nodes[node_index].zero_child : table.nodes[node_index].one_child;
880 if (!child.has_value())
881 {
882 if (table.nodes.size() >= std::numeric_limits< std::uint16_t >::max())
883 {
884 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard Huffman tree is too large");
885 }
886 std::uint16_t const new_node_index = static_cast< std::uint16_t >(table.nodes.size());
887 child = new_node_index;
888 node_index = new_node_index;
889 table.nodes.push_back(huffman_node{});
890 }
891 else
892 {
893 node_index = *child;
894 }
895 }
896 if (table.nodes[node_index].symbol.has_value() || table.nodes[node_index].zero_child.has_value() || table.nodes[node_index].one_child.has_value())
897 {
898 throw compression_error(error_code::invalid_data, format::zstandard, "duplicate Zstandard Huffman prefix code");
899 }
900 table.nodes[node_index].symbol = static_cast< std::uint8_t >(symbol);
901 code_space += 1U << (weight - 1U);
902 }
903 }
904 if (code_space != table_size)
905 {
906 throw compression_error(error_code::invalid_data, format::zstandard, "incomplete Zstandard Huffman tree");
907 }
908 return table;
909 }
910
911 /**
912 * @brief Parses a direct or FSE-compressed Huffman tree description.
913 * @param input Bytes beginning with a Huffman description header.
914 * @return Parsed tree and occupied byte count.
915 */
916 [[nodiscard]] inline huffman_description_result parse_huffman_description(std::span< std::byte const > input)
917 {
918 std::uint8_t const header = static_cast< std::uint8_t >(read_integer(input, 0U, 1U));
919 std::vector< std::uint8_t > weights;
920 std::size_t consumed_bytes = 1U;
921 if (header < 128U)
922 {
923 std::size_t const compressed_size = header;
924 if (compressed_size == 0U || compressed_size > input.size() - 1U)
925 {
926 throw compression_error(error_code::invalid_data, format::zstandard, "truncated FSE-compressed Zstandard Huffman tree");
927 }
928 weights = decode_fse_weights(input.subspan(1U, compressed_size));
929 consumed_bytes += compressed_size;
930 }
931 else
932 {
933 std::size_t const weight_count = static_cast< std::size_t >(header) - 127U;
934 std::size_t const weight_bytes = (weight_count + 1U) / 2U;
935 if (weight_bytes > input.size() - 1U)
936 {
937 throw compression_error(error_code::invalid_data, format::zstandard, "truncated direct Zstandard Huffman tree");
938 }
939 weights.reserve(weight_count);
940 for (std::size_t index = 0U; index < weight_count; ++index)
941 {
942 std::uint8_t const pair = std::to_integer< std::uint8_t >(input[1U + index / 2U]);
943 weights.push_back(index % 2U == 0U ? pair >> 4U : pair & 0x0fU);
944 }
945 consumed_bytes += weight_bytes;
946 }
947 return huffman_description_result{build_huffman_table(weights), consumed_bytes};
948 }
949
950 /**
951 * @brief Decodes one backward Huffman literal stream to an exact byte count.
952 * @param input Reverse-coded Huffman stream.
953 * @param regenerated_size Exact number of literals to produce.
954 * @param table Canonical decoding tree.
955 * @return Regenerated literal bytes.
956 */
957 [[nodiscard]] inline std::vector< std::byte > decode_huffman_stream(std::span< std::byte const > input, std::size_t regenerated_size, huffman_table const& table)
958 {
959 reverse_bit_reader reader(input);
960 std::vector< std::byte > output;
961 output.reserve(regenerated_size);
962 for (std::size_t output_index = 0U; output_index < regenerated_size; ++output_index)
963 {
964 std::size_t node_index = 0U;
965 while (!table.nodes[node_index].symbol.has_value())
966 {
967 std::uint8_t const bit = static_cast< std::uint8_t >(reader.read_bits(1U));
968 std::optional< std::uint16_t > const child = bit == 0U ? table.nodes[node_index].zero_child : table.nodes[node_index].one_child;
969 if (!child.has_value() || *child >= table.nodes.size())
970 {
971 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard Huffman prefix code");
972 }
973 node_index = *child;
974 }
975 output.push_back(static_cast< std::byte >(*table.nodes[node_index].symbol));
976 }
977 if (reader.remaining_bits() != 0U)
978 {
979 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard Huffman stream was not fully consumed");
980 }
981 return output;
982 }
983
984 /**
985 * @brief Decodes the literals section at the front of a compressed block.
986 * @param block Complete compressed block payload.
987 * @param state Frame decoder state retaining a reusable Huffman table.
988 * @return Literals and the number of block bytes they occupied.
989 */
990 [[nodiscard]] inline literals_result decode_literals(std::span< std::byte const > block, decoder_state& state)
991 {
992 std::uint8_t const first_byte = static_cast< std::uint8_t >(read_integer(block, 0U, 1U));
993 std::uint8_t const literals_type = first_byte & 0x03U;
994 std::uint8_t const size_format = static_cast< std::uint8_t >((first_byte >> 2U) & 0x03U);
995 if (literals_type <= 1U)
996 {
997 std::uint8_t header_size = 1U;
998 if (size_format == 1U)
999 {
1000 header_size = 2U;
1001 }
1002 else if (size_format == 3U)
1003 {
1004 header_size = 3U;
1005 }
1006 std::uint64_t const header = read_integer(block, 0U, header_size);
1007 std::size_t const regenerated_size = size_format == 0U || size_format == 2U ? static_cast< std::size_t >(header >> 3U) : static_cast< std::size_t >(header >> 4U);
1008 std::size_t const content_size = literals_type == 0U ? regenerated_size : 1U;
1009 if (header_size > block.size() || content_size > block.size() - header_size)
1010 {
1011 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Zstandard literals section");
1012 }
1013
1014 std::vector< std::byte > literals;
1015 if (literals_type == 0U)
1016 {
1017 literals.insert(literals.end(), block.begin() + static_cast< std::ptrdiff_t >(header_size), block.begin() + static_cast< std::ptrdiff_t >(header_size + content_size));
1018 }
1019 else
1020 {
1021 literals.insert(literals.end(), regenerated_size, block[header_size]);
1022 }
1023 return literals_result{std::move(literals), header_size + content_size};
1024 }
1025
1026 std::uint8_t header_size = 3U;
1027 std::uint8_t size_bits = 10U;
1028 std::uint8_t compressed_shift = 14U;
1029 if (size_format == 2U)
1030 {
1031 header_size = 4U;
1032 size_bits = 14U;
1033 compressed_shift = 18U;
1034 }
1035 else if (size_format == 3U)
1036 {
1037 header_size = 5U;
1038 size_bits = 18U;
1039 compressed_shift = 22U;
1040 }
1041 std::uint64_t const header = read_integer(block, 0U, header_size);
1042 std::uint64_t const size_mask = (std::uint64_t{1U} << size_bits) - 1U;
1043 std::size_t const regenerated_size = static_cast< std::size_t >((header >> 4U) & size_mask);
1044 std::size_t const compressed_size = static_cast< std::size_t >((header >> compressed_shift) & size_mask);
1045 if (header_size > block.size() || compressed_size > block.size() - header_size)
1046 {
1047 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Huffman-coded Zstandard literals section");
1048 }
1049 std::span< std::byte const > const content = block.subspan(header_size, compressed_size);
1050 std::size_t stream_position = 0U;
1051 if (literals_type == 2U)
1052 {
1054 state.huffman_literals_table = std::move(description.table);
1055 stream_position = description.consumed_bytes;
1056 }
1057 else if (!state.huffman_literals_table.has_value())
1058 {
1059 throw compression_error(error_code::invalid_data, format::zstandard, "treeless Zstandard literals have no previous Huffman table");
1060 }
1061 if (stream_position >= content.size())
1062 {
1063 throw compression_error(error_code::invalid_data, format::zstandard, "missing Zstandard Huffman literal stream");
1064 }
1065
1066 std::vector< std::byte > literals;
1067 if (size_format == 0U)
1068 {
1069 literals = decode_huffman_stream(content.subspan(stream_position), regenerated_size, *state.huffman_literals_table);
1070 }
1071 else
1072 {
1073 if (content.size() - stream_position < 6U)
1074 {
1075 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Zstandard Huffman jump table");
1076 }
1077 std::size_t const stream1_size = static_cast< std::size_t >(read_integer(content, stream_position, 2U));
1078 std::size_t const stream2_size = static_cast< std::size_t >(read_integer(content, stream_position + 2U, 2U));
1079 std::size_t const stream3_size = static_cast< std::size_t >(read_integer(content, stream_position + 4U, 2U));
1080 std::size_t const streams_size = content.size() - stream_position;
1081 if (stream1_size > streams_size - 6U || stream2_size > streams_size - 6U - stream1_size || stream3_size > streams_size - 6U - stream1_size - stream2_size)
1082 {
1083 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard Huffman stream sizes");
1084 }
1085 std::size_t const stream4_size = streams_size - 6U - stream1_size - stream2_size - stream3_size;
1086 std::size_t const segment_size = (regenerated_size + 3U) / 4U;
1087 if (segment_size * 3U > regenerated_size)
1088 {
1089 throw compression_error(error_code::invalid_data, format::zstandard, "invalid four-stream Zstandard literal size");
1090 }
1091 std::size_t const stream1_position = stream_position + 6U;
1092 std::size_t const stream2_position = stream1_position + stream1_size;
1093 std::size_t const stream3_position = stream2_position + stream2_size;
1094 std::size_t const stream4_position = stream3_position + stream3_size;
1095 std::vector< std::byte > decoded1 = decode_huffman_stream(content.subspan(stream1_position, stream1_size), segment_size, *state.huffman_literals_table);
1096 std::vector< std::byte > decoded2 = decode_huffman_stream(content.subspan(stream2_position, stream2_size), segment_size, *state.huffman_literals_table);
1097 std::vector< std::byte > decoded3 = decode_huffman_stream(content.subspan(stream3_position, stream3_size), segment_size, *state.huffman_literals_table);
1098 std::vector< std::byte > decoded4 = decode_huffman_stream(content.subspan(stream4_position, stream4_size), regenerated_size - segment_size * 3U, *state.huffman_literals_table);
1099 literals.reserve(regenerated_size);
1100 literals.insert(literals.end(), decoded1.begin(), decoded1.end());
1101 literals.insert(literals.end(), decoded2.begin(), decoded2.end());
1102 literals.insert(literals.end(), decoded3.begin(), decoded3.end());
1103 literals.insert(literals.end(), decoded4.begin(), decoded4.end());
1104 }
1105 return literals_result{std::move(literals), header_size + compressed_size};
1106 }
1107
1108 /**
1109 * @brief Decodes one compressed block into frame output.
1110 * @param block Complete compressed block payload.
1111 * @param window_size Declared maximum backward-match window.
1112 * @param maximum_output_size Absolute output-size limit.
1113 * @param state Entropy tables and repeat offsets retained across blocks.
1114 * @param output Frame output and match-history storage.
1115 */
1116 inline void decompress_compressed_block(std::span< std::byte const > block, std::size_t window_size, std::size_t maximum_output_size, decoder_state& state, std::vector< std::byte >& output)
1117 {
1118 literals_result literals = decode_literals(block, state);
1119 std::size_t position = literals.consumed_bytes;
1120 std::uint8_t const first_sequence_count = static_cast< std::uint8_t >(read_integer(block, position, 1U));
1121 ++position;
1122 std::size_t sequence_count = first_sequence_count;
1123 if (first_sequence_count >= 128U && first_sequence_count < 255U)
1124 {
1125 std::uint8_t const second_byte = static_cast< std::uint8_t >(read_integer(block, position, 1U));
1126 ++position;
1127 sequence_count = (static_cast< std::size_t >(first_sequence_count - 128U) << 8U) + second_byte;
1128 }
1129 else if (first_sequence_count == 255U)
1130 {
1131 sequence_count = static_cast< std::size_t >(read_integer(block, position, 2U)) + 0x7f00U;
1132 position += 2U;
1133 }
1134
1135 std::size_t const block_output_start = output.size();
1136 std::size_t const block_output_limit = std::min< std::size_t >(window_size, 128U * 1024U);
1137 if (sequence_count == 0U)
1138 {
1139 if (position != block.size())
1140 {
1141 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard compressed block contains trailing sequence data");
1142 }
1143 if (literals.literals.size() > block_output_limit)
1144 {
1145 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard compressed block exceeds its regenerated-size limit");
1146 }
1147 if (output.size() > maximum_output_size || literals.literals.size() > maximum_output_size - output.size())
1148 {
1149 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "decompressed output exceeds configured limit");
1150 }
1151 output.insert(output.end(), literals.literals.begin(), literals.literals.end());
1152 return;
1153 }
1154
1155 std::uint8_t const modes = static_cast< std::uint8_t >(read_integer(block, position, 1U));
1156 ++position;
1157 if ((modes & 0x03U) != 0U)
1158 {
1159 throw compression_error(error_code::invalid_data, format::zstandard, "reserved Zstandard sequence mode bits are set");
1160 }
1161
1162 auto read_table = [&](std::uint8_t mode, std::uint8_t maximum_symbol, std::uint8_t maximum_accuracy_log, fse_table predefined_table, std::optional< fse_table >& previous_table)
1163 {
1164 if (mode == 0U)
1165 {
1166 previous_table = std::move(predefined_table);
1167 return;
1168 }
1169 if (mode == 1U)
1170 {
1171 std::uint8_t const symbol = static_cast< std::uint8_t >(read_integer(block, position, 1U));
1172 ++position;
1173 if (symbol > maximum_symbol)
1174 {
1175 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard RLE sequence symbol exceeds its alphabet");
1176 }
1177 previous_table = build_rle_fse_table(symbol);
1178 return;
1179 }
1180 if (mode == 2U)
1181 {
1182 if (position > block.size())
1183 {
1184 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Zstandard sequence table");
1185 }
1186 normalized_probability_result probabilities = parse_normalized_probabilities(block.subspan(position), maximum_symbol, maximum_accuracy_log);
1187 position += probabilities.consumed_bytes;
1188 previous_table = build_fse_table(probabilities.probabilities, probabilities.accuracy_log);
1189 return;
1190 }
1191 if (!previous_table.has_value())
1192 {
1193 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard repeat mode has no previous FSE table");
1194 }
1195 };
1196
1197 read_table(static_cast< std::uint8_t >(modes >> 6U), 35U, 9U, build_predefined_literal_length_table(), state.literal_length_table);
1198 read_table(static_cast< std::uint8_t >((modes >> 4U) & 0x03U), 31U, 8U, build_predefined_offset_table(), state.offset_table);
1199 read_table(static_cast< std::uint8_t >((modes >> 2U) & 0x03U), 52U, 9U, build_predefined_match_length_table(), state.match_length_table);
1200
1201 if (position >= block.size())
1202 {
1203 throw compression_error(error_code::invalid_data, format::zstandard, "missing Zstandard sequence bitstream");
1204 }
1205 fse_table const& literal_length_table = *state.literal_length_table;
1206 fse_table const& offset_table = *state.offset_table;
1207 fse_table const& match_length_table = *state.match_length_table;
1208 reverse_bit_reader reader(block.subspan(position));
1209 std::size_t literal_length_state = reader.read_bits(literal_length_table.accuracy_log);
1210 std::size_t offset_state = reader.read_bits(offset_table.accuracy_log);
1211 std::size_t match_length_state = reader.read_bits(match_length_table.accuracy_log);
1212 if (literal_length_state >= literal_length_table.entries.size() || offset_state >= offset_table.entries.size() || match_length_state >= match_length_table.entries.size())
1213 {
1214 throw compression_error(error_code::invalid_data, format::zstandard, "invalid initial Zstandard FSE state");
1215 }
1216
1217 std::size_t literal_position = 0U;
1218 for (std::size_t sequence_index = 0U; sequence_index < sequence_count; ++sequence_index)
1219 {
1220 fse_entry const literal_length_transition = literal_length_table.entries[literal_length_state];
1221 fse_entry const offset_transition = offset_table.entries[offset_state];
1222 fse_entry const match_length_transition = match_length_table.entries[match_length_state];
1223 if (literal_length_transition.symbol >= literal_length_codes.size() || match_length_transition.symbol >= match_length_codes.size() || offset_transition.symbol > 31U)
1224 {
1225 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard sequence code exceeds its alphabet");
1226 }
1227
1228 length_code_entry const literal_length_code = literal_length_codes[literal_length_transition.symbol];
1229 length_code_entry const match_length_code = match_length_codes[match_length_transition.symbol];
1230 std::uint64_t const offset_value_64 = (std::uint64_t{1U} << offset_transition.symbol) + reader.read_bits(offset_transition.symbol);
1231 std::size_t const match_length = static_cast< std::size_t >(match_length_code.baseline + reader.read_bits(match_length_code.number_of_bits));
1232 std::size_t const literal_length = static_cast< std::size_t >(literal_length_code.baseline + reader.read_bits(literal_length_code.number_of_bits));
1233 if (offset_value_64 > std::numeric_limits< std::size_t >::max())
1234 {
1235 throw compression_error(error_code::unsupported_feature, format::zstandard, "Zstandard offset exceeds the platform size");
1236 }
1237 std::size_t const offset_value = static_cast< std::size_t >(offset_value_64);
1238 if (literal_position > literals.literals.size() || literal_length > literals.literals.size() - literal_position)
1239 {
1240 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard sequence consumes too many literals");
1241 }
1242 if (output.size() > maximum_output_size || literal_length > maximum_output_size - output.size())
1243 {
1244 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "decompressed output exceeds configured limit");
1245 }
1246 output.insert(output.end(), literals.literals.begin() + static_cast< std::ptrdiff_t >(literal_position), literals.literals.begin() + static_cast< std::ptrdiff_t >(literal_position + literal_length));
1247 literal_position += literal_length;
1248
1249 std::size_t resolved_offset = 0U;
1250 bool insert_new_offset = false;
1251 std::size_t repeated_index = 0U;
1252 if (offset_value > 3U)
1253 {
1254 resolved_offset = offset_value - 3U;
1255 insert_new_offset = true;
1256 }
1257 else if (literal_length == 0U && offset_value == 3U)
1258 {
1259 if (state.repeated_offsets[0U] <= 1U)
1260 {
1261 throw compression_error(error_code::invalid_data, format::zstandard, "invalid decremented Zstandard repeat offset");
1262 }
1263 resolved_offset = state.repeated_offsets[0U] - 1U;
1264 insert_new_offset = true;
1265 }
1266 else
1267 {
1268 repeated_index = literal_length == 0U ? offset_value : offset_value - 1U;
1269 if (repeated_index >= state.repeated_offsets.size())
1270 {
1271 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard repeat offset code");
1272 }
1273 resolved_offset = state.repeated_offsets[repeated_index];
1274 }
1275
1276 if (insert_new_offset)
1277 {
1278 state.repeated_offsets[2U] = state.repeated_offsets[1U];
1279 state.repeated_offsets[1U] = state.repeated_offsets[0U];
1280 state.repeated_offsets[0U] = resolved_offset;
1281 }
1282 else
1283 {
1284 for (std::size_t index = repeated_index; index > 0U; --index)
1285 {
1286 state.repeated_offsets[index] = state.repeated_offsets[index - 1U];
1287 }
1288 state.repeated_offsets[0U] = resolved_offset;
1289 }
1290 if (resolved_offset == 0U || resolved_offset > output.size() || resolved_offset > window_size)
1291 {
1292 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard match offset exceeds available history");
1293 }
1294 if (output.size() > maximum_output_size || match_length > maximum_output_size - output.size())
1295 {
1296 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "decompressed output exceeds configured limit");
1297 }
1298 for (std::size_t match_index = 0U; match_index < match_length; ++match_index)
1299 {
1300 output.push_back(output[output.size() - resolved_offset]);
1301 }
1302
1303 if (sequence_index + 1U < sequence_count)
1304 {
1305 literal_length_state = literal_length_transition.baseline + reader.read_bits(literal_length_transition.number_of_bits);
1306 match_length_state = match_length_transition.baseline + reader.read_bits(match_length_transition.number_of_bits);
1307 offset_state = offset_transition.baseline + reader.read_bits(offset_transition.number_of_bits);
1308 if (literal_length_state >= literal_length_table.entries.size() || offset_state >= offset_table.entries.size() || match_length_state >= match_length_table.entries.size())
1309 {
1310 throw compression_error(error_code::invalid_data, format::zstandard, "updated Zstandard FSE state exceeds its table");
1311 }
1312 }
1313 }
1314
1315 if (reader.remaining_bits() != 0U)
1316 {
1317 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard sequence bitstream was not fully consumed");
1318 }
1319 std::size_t const remaining_literals = literals.literals.size() - literal_position;
1320 if (output.size() > maximum_output_size || remaining_literals > maximum_output_size - output.size())
1321 {
1322 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "decompressed output exceeds configured limit");
1323 }
1324 output.insert(output.end(), literals.literals.begin() + static_cast< std::ptrdiff_t >(literal_position), literals.literals.end());
1325 if (output.size() - block_output_start > block_output_limit)
1326 {
1327 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard compressed block exceeds its regenerated-size limit");
1328 }
1329 }
1330
1331 /**
1332 * @brief Selects the sequence length code covering one exact length.
1333 * @tparam code_count Number of entries in @p codes.
1334 * @param length Exact sequence length.
1335 * @param codes Ordered baseline and additional-bit definitions.
1336 * @return Wire code and additional value bits.
1337 */
1338 template < std::size_t code_count >
1339 [[nodiscard]] inline encoded_length encode_length(std::size_t length, std::array< length_code_entry, code_count > const& codes)
1340 {
1341 for (std::size_t code = 0U; code < codes.size(); ++code)
1342 {
1343 length_code_entry const definition = codes[code];
1344 std::uint32_t const maximum_additional_value = definition.number_of_bits == 0U ? 0U : (std::uint32_t{1U} << definition.number_of_bits) - 1U;
1345 if (length >= definition.baseline && length - definition.baseline <= maximum_additional_value)
1346 {
1347 return encoded_length{static_cast< std::uint8_t >(code), static_cast< std::uint32_t >(length - definition.baseline), definition.number_of_bits};
1348 }
1349 }
1350 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard sequence length has no wire code");
1351 }
1352
1353 /**
1354 * @brief Finds a block prefix whose repetition regenerates the suffix.
1355 * @param block Candidate periodic block.
1356 * @return Repeating period, or no value if no useful repeated suffix exists.
1357 */
1358 [[nodiscard]] inline std::optional< std::size_t > find_repeating_period(std::span< std::byte const > block)
1359 {
1360 if (block.size() < 4U)
1361 {
1362 return std::nullopt;
1363 }
1364 std::vector< std::size_t > prefix_lengths(block.size(), 0U);
1365 for (std::size_t position = 1U; position < block.size(); ++position)
1366 {
1367 std::size_t prefix_length = prefix_lengths[position - 1U];
1368 while (prefix_length != 0U && block[position] != block[prefix_length])
1369 {
1370 prefix_length = prefix_lengths[prefix_length - 1U];
1371 }
1372 if (block[position] == block[prefix_length])
1373 {
1374 ++prefix_length;
1375 }
1376 prefix_lengths[position] = prefix_length;
1377 }
1378 std::size_t const match_length = prefix_lengths.back();
1379 if (match_length < 3U)
1380 {
1381 return std::nullopt;
1382 }
1383 return block.size() - match_length;
1384 }
1385
1386 /**
1387 * @brief Encodes a periodic block as raw literals plus one match sequence.
1388 * @param block Candidate block.
1389 * @return Compressed payload when smaller than the source, otherwise no value.
1390 */
1391 [[nodiscard]] inline std::optional< std::vector< std::byte > > compress_periodic_block(std::span< std::byte const > block)
1392 {
1393 std::optional< std::size_t > const period = find_repeating_period(block);
1394 if (!period.has_value())
1395 {
1396 return std::nullopt;
1397 }
1398 std::size_t const match_length = block.size() - *period;
1399 encoded_length const literal_length = encode_length(*period, literal_length_codes);
1400 encoded_length const encoded_match_length = encode_length(match_length, match_length_codes);
1401 std::uint64_t const offset_value = static_cast< std::uint64_t >(*period) + 3U;
1402 std::uint8_t const offset_code = static_cast< std::uint8_t >(std::bit_width(offset_value) - 1);
1403 std::uint64_t const offset_additional_value = offset_value - (std::uint64_t{1U} << offset_code);
1404
1405 std::vector< std::byte > encoded;
1406 if (*period <= 31U)
1407 {
1408 encoded.push_back(static_cast< std::byte >(*period << 3U));
1409 }
1410 else if (*period <= 4095U)
1411 {
1412 append_integer(encoded, (*period << 4U) | 0x04U, 2U);
1413 }
1414 else
1415 {
1416 append_integer(encoded, (*period << 4U) | 0x0cU, 3U);
1417 }
1418 encoded.insert(encoded.end(), block.begin(), block.begin() + static_cast< std::ptrdiff_t >(*period));
1419 encoded.push_back(std::byte{0x01});
1420 encoded.push_back(std::byte{0x54});
1421 encoded.push_back(static_cast< std::byte >(literal_length.code));
1422 encoded.push_back(static_cast< std::byte >(offset_code));
1423 encoded.push_back(static_cast< std::byte >(encoded_match_length.code));
1424
1425 std::size_t const bit_count = literal_length.number_of_bits + encoded_match_length.number_of_bits + offset_code;
1426 std::vector< std::byte > bitstream((bit_count + 1U + 7U) / 8U, std::byte{0});
1427 std::size_t bit_position = 0U;
1428 auto append_bits = [&](std::uint64_t value, std::uint8_t count)
1429 {
1430 for (std::uint8_t bit_index = 0U; bit_index < count; ++bit_index)
1431 {
1432 if (((value >> bit_index) & 1U) != 0U)
1433 {
1434 bitstream[bit_position / 8U] |= static_cast< std::byte >(1U << (bit_position % 8U));
1435 }
1436 ++bit_position;
1437 }
1438 };
1439 append_bits(literal_length.additional_value, literal_length.number_of_bits);
1440 append_bits(encoded_match_length.additional_value, encoded_match_length.number_of_bits);
1441 append_bits(offset_additional_value, offset_code);
1442 bitstream[bit_position / 8U] |= static_cast< std::byte >(1U << (bit_position % 8U));
1443 encoded.insert(encoded.end(), bitstream.begin(), bitstream.end());
1444 if (encoded.size() >= block.size())
1445 {
1446 return std::nullopt;
1447 }
1448 return encoded;
1449 }
1450
1451 /**
1452 * @brief Compresses an iterator range as a Zstandard frame.
1453 * @tparam input_iterator Single-pass byte iterator.
1454 * @tparam sentinel Sentinel for @p first.
1455 * @tparam output_iterator Destination byte iterator.
1456 * @param first First source byte.
1457 * @param last Sentinel past the source.
1458 * @param output Destination iterator.
1459 * @param options Compression level from 0 through 22.
1460 * @return Destination advanced past the frame checksum.
1461 */
1462 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
1463 output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const& options)
1464 {
1465 std::int32_t const level = options.level.value_or(3);
1466 if (level < 0 || level > 22)
1467 {
1468 throw compression_error(error_code::invalid_option, format::zstandard, "Zstandard compression level must be between 0 and 22");
1469 }
1470 auto write_integer = [&](std::uint64_t value, std::uint8_t byte_count)
1471 {
1472 for (std::uint8_t index = 0U; index < byte_count; ++index)
1473 {
1474 implementation::write_byte(output, static_cast< std::byte >(value >> (index * 8U)));
1475 }
1476 };
1477 write_integer(0xfd2fb528U, 4U);
1478 implementation::write_byte(output, std::byte{0x00});
1479 implementation::write_byte(output, std::byte{0x38});
1480
1481 constexpr std::size_t block_limit = 128U * 1024U;
1482 std::vector< std::byte > block;
1483 block.reserve(block_limit);
1484 bool emitted_block = false;
1485 do
1486 {
1487 block.clear();
1488 while (first != last && block.size() < block_limit)
1489 {
1490 block.push_back(implementation::to_byte(*first));
1491 ++first;
1492 }
1493 bool const last_block = first == last;
1494 bool rle_block = !block.empty();
1495 for (std::size_t index = 1U; index < block.size() && rle_block; ++index)
1496 {
1497 rle_block = block[index] == block[0U];
1498 }
1499 std::optional< std::vector< std::byte > > encoded;
1500 if (!rle_block && level != 0)
1501 {
1502 encoded = compress_periodic_block(block);
1503 }
1504 if (rle_block)
1505 {
1506 std::uint32_t const header = static_cast< std::uint32_t >(last_block ? 1U : 0U) | 2U | (static_cast< std::uint32_t >(block.size()) << 3U);
1507 write_integer(header, 3U);
1508 implementation::write_byte(output, block[0U]);
1509 }
1510 else if (encoded.has_value())
1511 {
1512 std::uint32_t const header = static_cast< std::uint32_t >(last_block ? 1U : 0U) | 4U | (static_cast< std::uint32_t >(encoded->size()) << 3U);
1513 write_integer(header, 3U);
1514 for (std::byte value : *encoded)
1515 {
1516 implementation::write_byte(output, value);
1517 }
1518 }
1519 else
1520 {
1521 std::uint32_t const header = static_cast< std::uint32_t >(last_block ? 1U : 0U) | (static_cast< std::uint32_t >(block.size()) << 3U);
1522 write_integer(header, 3U);
1523 for (std::byte value : block)
1524 {
1525 implementation::write_byte(output, value);
1526 }
1527 }
1528 emitted_block = true;
1529 if (last_block)
1530 {
1531 break;
1532 }
1533 } while (!emitted_block || first != last);
1534 return output;
1535 }
1536
1537 /**
1538 * @brief Decompresses one or more Zstandard or skippable frames.
1539 * @tparam input_iterator Single-pass byte iterator.
1540 * @tparam sentinel Sentinel for @p first.
1541 * @tparam output_iterator Destination byte iterator.
1542 * @param first First compressed byte.
1543 * @param last Sentinel past the compressed input.
1544 * @param output Destination iterator.
1545 * @param options Output limit and concatenated-frame policy.
1546 * @return Destination advanced past the uncompressed data.
1547 */
1548 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
1549 output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const& options)
1550 {
1551 implementation::byte_reader< input_iterator, sentinel > reader(std::move(first), std::move(last));
1552 auto read_byte = [&]() -> std::byte
1553 {
1554 std::byte value{};
1555 if (!reader.read(value))
1556 {
1557 throw compression_error(error_code::invalid_data, format::zstandard, "truncated Zstandard frame");
1558 }
1559 return value;
1560 };
1561 auto read_integer = [&](std::uint8_t count) -> std::uint64_t
1562 {
1563 std::uint64_t value = 0U;
1564 for (std::uint8_t index = 0U; index < count; ++index)
1565 {
1566 value |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(read_byte())) << (index * 8U);
1567 }
1568 return value;
1569 };
1570
1571 std::size_t total_output = 0U;
1572 bool decoded_frame = false;
1573 while (!reader.empty())
1574 {
1575 std::uint32_t const magic = static_cast< std::uint32_t >(read_integer(4U));
1576 if (magic >= 0x184d2a50U && magic <= 0x184d2a5fU)
1577 {
1578 std::size_t const skip_size = static_cast< std::size_t >(read_integer(4U));
1579 for (std::size_t index = 0U; index < skip_size; ++index)
1580 {
1581 static_cast< void >(read_byte());
1582 }
1583 continue;
1584 }
1585 if (decoded_frame && !options.allow_concatenated_streams)
1586 {
1587 throw compression_error(error_code::trailing_data, format::zstandard, "Zstandard frame contains trailing data");
1588 }
1589 if (magic != 0xfd2fb528U)
1590 {
1591 throw compression_error(error_code::invalid_data, format::zstandard, "invalid Zstandard frame magic");
1592 }
1593 std::uint8_t const descriptor = std::to_integer< std::uint8_t >(read_byte());
1594 if ((descriptor & 0x08U) != 0U)
1595 {
1596 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard frame uses reserved descriptor bits");
1597 }
1598 bool const single_segment = (descriptor & 0x20U) != 0U;
1599 bool const checksum_present = (descriptor & 0x04U) != 0U;
1600 std::uint8_t const dictionary_flag = descriptor & 0x03U;
1601 std::uint8_t const content_size_flag = descriptor >> 6U;
1602 std::optional< std::size_t > window_size;
1603 if (!single_segment)
1604 {
1605 std::uint8_t const window_descriptor = std::to_integer< std::uint8_t >(read_byte());
1606 std::uint8_t const window_log = static_cast< std::uint8_t >(10U + (window_descriptor >> 3U));
1607 if (window_log >= std::numeric_limits< std::size_t >::digits)
1608 {
1609 throw compression_error(error_code::unsupported_feature, format::zstandard, "Zstandard window exceeds the platform size");
1610 }
1611 std::size_t const base = static_cast< std::size_t >(1U) << window_log;
1612 window_size = base + (base >> 3U) * (window_descriptor & 0x07U);
1613 }
1614 constexpr std::array< std::uint8_t, 4U > dictionary_sizes{0U, 1U, 2U, 4U};
1615 if (read_integer(dictionary_sizes[dictionary_flag]) != 0U)
1616 {
1617 throw compression_error(error_code::unsupported_feature, format::zstandard, "dictionary-based Zstandard frames are unsupported");
1618 }
1619 std::uint8_t content_size_bytes = 0U;
1620 if (content_size_flag == 0U)
1621 {
1622 content_size_bytes = single_segment ? 1U : 0U;
1623 }
1624 else
1625 {
1626 constexpr std::array< std::uint8_t, 4U > content_sizes{0U, 2U, 4U, 8U};
1627 content_size_bytes = content_sizes[content_size_flag];
1628 }
1629 std::optional< std::uint64_t > content_size;
1630 if (content_size_bytes != 0U)
1631 {
1632 content_size = read_integer(content_size_bytes);
1633 if (content_size_flag == 1U)
1634 {
1635 *content_size += 256U;
1636 }
1637 if (*content_size > options.maximum_output_size - std::min(total_output, options.maximum_output_size))
1638 {
1639 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "Zstandard content size exceeds configured limit");
1640 }
1641 }
1642 if (single_segment)
1643 {
1644 window_size = static_cast< std::size_t >(content_size.value_or(0U));
1645 }
1646 std::size_t const effective_window = window_size.value_or(128U * 1024U);
1647 std::vector< std::byte > history;
1648 history.reserve(std::min< std::size_t >(effective_window, options.maximum_output_size));
1649 xxhash64_accumulator checksum;
1650 decoder_state state;
1651 std::size_t const frame_begin = total_output;
1652 auto emit_output = [&](std::byte value)
1653 {
1654 if (total_output == options.maximum_output_size)
1655 {
1656 throw compression_error(error_code::output_limit_exceeded, format::zstandard, "decompressed output exceeds configured limit");
1657 }
1658 if (checksum_present)
1659 {
1660 checksum.update(value);
1661 }
1662 ++total_output;
1663 implementation::write_byte(output, value);
1664 };
1665 auto emit = [&](std::byte value)
1666 {
1667 history.push_back(value);
1668 emit_output(value);
1669 };
1670
1671 bool last_block = false;
1672 while (!last_block)
1673 {
1674 std::uint32_t const header = static_cast< std::uint32_t >(read_integer(3U));
1675 last_block = (header & 1U) != 0U;
1676 std::uint8_t const block_type = static_cast< std::uint8_t >((header >> 1U) & 0x03U);
1677 std::size_t const block_size = header >> 3U;
1678 std::size_t const maximum_block = std::min< std::size_t >(effective_window, 128U * 1024U);
1679 if (block_size > maximum_block)
1680 {
1681 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard block exceeds the frame block-size limit");
1682 }
1683 if (block_type == 0U)
1684 {
1685 for (std::size_t index = 0U; index < block_size; ++index)
1686 {
1687 emit(read_byte());
1688 }
1689 }
1690 else if (block_type == 1U)
1691 {
1692 std::byte const value = read_byte();
1693 for (std::size_t index = 0U; index < block_size; ++index)
1694 {
1695 emit(value);
1696 }
1697 }
1698 else if (block_type == 2U)
1699 {
1700 std::vector< std::byte > block;
1701 block.reserve(block_size);
1702 for (std::size_t index = 0U; index < block_size; ++index)
1703 {
1704 block.push_back(read_byte());
1705 }
1706 std::vector< std::byte > working = std::move(history);
1707 std::size_t const history_size = working.size();
1708 std::size_t const available = options.maximum_output_size - total_output;
1709 decompress_compressed_block(block, effective_window, history_size + available, state, working);
1710 for (std::size_t index = history_size; index < working.size(); ++index)
1711 {
1712 emit_output(working[index]);
1713 }
1714 std::size_t const retained = std::min< std::size_t >(working.size(), effective_window);
1715 history.assign(working.end() - static_cast< std::ptrdiff_t >(retained), working.end());
1716 }
1717 else
1718 {
1719 throw compression_error(error_code::invalid_data, format::zstandard, "reserved Zstandard block type");
1720 }
1721 if (history.size() > effective_window)
1722 {
1723 history.erase(history.begin(), history.begin() + static_cast< std::ptrdiff_t >(history.size() - effective_window));
1724 }
1725 }
1726 if (checksum_present && static_cast< std::uint32_t >(read_integer(4U)) != static_cast< std::uint32_t >(checksum.value()))
1727 {
1728 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard content checksum mismatch");
1729 }
1730 if (content_size.has_value() && total_output - frame_begin != *content_size)
1731 {
1732 throw compression_error(error_code::invalid_data, format::zstandard, "Zstandard content size mismatch");
1733 }
1734 decoded_frame = true;
1735 }
1736 if (!decoded_frame)
1737 {
1738 throw compression_error(error_code::invalid_data, format::zstandard, "empty Zstandard input");
1739 }
1740 return output;
1741 }
1742
1743} // namespace rpnx::compression::zstandard_codec
1744
1745#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 empty() const
Tests whether no unread byte remains.
Definition io.hpp:86
bool read(std::byte &value)
Reads one byte.
Definition io.hpp:116
Read a byte-aligned, forward, least-significant-bit-first bitstream.
forward_bit_reader(std::span< std::byte const > input) noexcept
Constructs a reader over a non-owning byte span.
std::uint32_t read_bits(std::uint8_t count)
Consumes and returns the next bits.
std::uint32_t peek_bits(std::uint8_t count) const
Returns the next bits without consuming them.
std::size_t consumed_bytes() const noexcept
Returns the whole number of bytes occupied by consumed bits.
Read a Zstandard entropy bitstream from its end toward its beginning.
std::size_t remaining_bits() const noexcept
Returns the number of useful bits not yet consumed.
reverse_bit_reader(std::span< std::byte const > input)
Constructs a reader and removes the mandatory final-bit marker.
std::uint32_t read_bits(std::uint8_t count)
Consumes bits preceding the current reverse-stream position.
Incremental XXH64 accumulator for streamed Zstandard checksums.
void update(std::byte value) noexcept
Includes one byte in the hash.
std::uint64_t value() const noexcept
Returns the hash for all supplied bytes.
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 Zstandard compression and decompression.
Definition zstandard.hpp:26
output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const &options)
Decompresses one or more Zstandard or skippable frames.
encoded_length encode_length(std::size_t length, std::array< length_code_entry, code_count > const &codes)
Selects the sequence length code covering one exact length.
literals_result decode_literals(std::span< std::byte const > block, decoder_state &state)
Decodes the literals section at the front of a compressed block.
normalized_probability_result parse_normalized_probabilities(std::span< std::byte const > input, std::uint8_t maximum_symbol, std::uint8_t maximum_accuracy_log)
Parses one forward-coded normalized FSE probability description.
std::uint64_t read_word64(std::span< std::byte const > input, std::size_t offset) noexcept
Reads an unchecked little-endian 64-bit word.
std::vector< std::uint8_t > decode_fse_weights(std::span< std::byte const > input)
Decodes the two-state FSE stream used for Huffman weights.
std::vector< std::byte > decode_huffman_stream(std::span< std::byte const > input, std::size_t regenerated_size, huffman_table const &table)
Decodes one backward Huffman literal stream to an exact byte count.
void decompress_compressed_block(std::span< std::byte const > block, std::size_t window_size, std::size_t maximum_output_size, decoder_state &state, std::vector< std::byte > &output)
Decodes one compressed block into frame output.
huffman_description_result parse_huffman_description(std::span< std::byte const > input)
Parses a direct or FSE-compressed Huffman tree description.
std::optional< std::vector< std::byte > > compress_periodic_block(std::span< std::byte const > block)
Encodes a periodic block as raw literals plus one match sequence.
std::uint32_t read_word32(std::span< std::byte const > input, std::size_t offset) noexcept
Reads an unchecked little-endian 32-bit word.
fse_table build_fse_table(std::span< std::int16_t const > probabilities, std::uint8_t accuracy_log)
Constructs the FSE decoding table for normalized probabilities.
void append_integer(std::vector< std::byte > &output, std::uint64_t value, std::uint8_t byte_count)
Appends a little-endian integer with a selected byte count.
std::optional< std::size_t > find_repeating_period(std::span< std::byte const > block)
Finds a block prefix whose repetition regenerates the suffix.
output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const &options)
Compresses an iterator range as a Zstandard frame.
huffman_table build_huffman_table(std::span< std::uint8_t const > explicit_weights)
Builds the canonical Huffman tree implied by transmitted weights.
std::uint64_t rotate_left(std::uint64_t value, std::uint8_t count) noexcept
Rotates a 64-bit value left without compiler-specific intrinsics.
constexpr std::array< length_code_entry, 53U > match_length_codes
Match-length code definitions from the Zstandard format.
std::uint64_t read_integer(std::span< std::byte const > input, std::size_t offset, std::uint8_t byte_count)
Reads a checked little-endian integer with a selected byte count.
fse_table build_predefined_match_length_table()
Builds the format-defined predefined match-length FSE table.
constexpr std::array< length_code_entry, 36U > literal_length_codes
Literal-length code definitions from the Zstandard format.
fse_table build_predefined_literal_length_table()
Builds the format-defined predefined literal-length FSE table.
std::uint64_t xxhash64(std::span< std::byte const > input, std::uint64_t seed=0U) noexcept
Computes XXH64 as required by the Zstandard frame checksum.
fse_table build_rle_fse_table(std::uint8_t symbol)
Constructs a one-symbol FSE table for RLE sequence mode.
fse_table build_predefined_offset_table()
Builds the format-defined predefined offset-code FSE table.
@ zstandard
Zstandard frame.
@ 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.
Persistent entropy tables and repeat offsets shared by compressed blocks.
Definition zstandard.hpp:97
std::optional< fse_table > offset_table
Most recent offset-code FSE table.
std::optional< fse_table > literal_length_table
Most recent literal-length FSE table.
std::array< std::size_t, 3U > repeated_offsets
Three repeat offsets in most-recent-first order.
std::optional< huffman_table > huffman_literals_table
Huffman table retained for treeless literal blocks.
Definition zstandard.hpp:99
std::optional< fse_table > match_length_table
Most recent match-length FSE table.
Encoded sequence length code and its additional-bit payload.
std::uint8_t number_of_bits
Number of significant bits in additional_value.
std::uint32_t additional_value
Additional value stored after the code.
std::uint8_t code
Sequence length code.
One state transition in an FSE decoding table.
Definition zstandard.hpp:30
std::uint8_t number_of_bits
Number of bits used to select the next state.
Definition zstandard.hpp:34
std::uint16_t baseline
Baseline added to the decoded transition bits.
Definition zstandard.hpp:36
std::uint8_t symbol
Symbol emitted when this state is decoded.
Definition zstandard.hpp:32
Native FSE decoding table indexed by the current decoder state.
Definition zstandard.hpp:41
std::uint8_t accuracy_log
Base-two logarithm of the state count.
Definition zstandard.hpp:43
std::vector< fse_entry > entries
State transitions indexed by current state.
Definition zstandard.hpp:45
A parsed Huffman tree description and its encoded byte length.
Definition zstandard.hpp:88
std::size_t consumed_bytes
Bytes occupied by the encoded tree description.
Definition zstandard.hpp:92
huffman_table table
Parsed canonical decoding tree.
Definition zstandard.hpp:90
One node in a native canonical Huffman decoding tree.
Definition zstandard.hpp:70
std::optional< std::uint16_t > one_child
Child selected by a one bit, if present.
Definition zstandard.hpp:74
std::optional< std::uint16_t > zero_child
Child selected by a zero bit, if present.
Definition zstandard.hpp:72
std::optional< std::uint8_t > symbol
Literal value for a leaf node.
Definition zstandard.hpp:76
Canonical Huffman decoding tree retained for treeless literal blocks.
Definition zstandard.hpp:81
std::vector< huffman_node > nodes
Tree nodes with the root at index zero.
Definition zstandard.hpp:83
Baseline and additional-bit count selected by a sequence length code.
std::uint8_t number_of_bits
Number of additional value bits following the code.
std::uint32_t baseline
Smallest length represented by this code.
Decoded literals and the number of compressed-block bytes they occupy.
Definition zstandard.hpp:61
std::vector< std::byte > literals
Regenerated literal bytes.
Definition zstandard.hpp:63
std::size_t consumed_bytes
Bytes occupied by the literals section in its compressed block.
Definition zstandard.hpp:65
Parsed normalized FSE probabilities and their encoded byte length.
Definition zstandard.hpp:50
std::vector< std::int16_t > probabilities
Normalized probability for every symbol through the last encoded symbol.
Definition zstandard.hpp:54
std::uint8_t accuracy_log
Accuracy log encoded by the description.
Definition zstandard.hpp:52
std::size_t consumed_bytes
Whole number of source bytes occupied by the description.
Definition zstandard.hpp:56