RPNX::Compress
Self-contained C++20 compression and ZIP library
 
Loading...
Searching...
No Matches
lz4.hpp
Go to the documentation of this file.
1#ifndef RPNX_COMPRESSION_IMPLEMENTATION_LZ4_HPP
2#define RPNX_COMPRESSION_IMPLEMENTATION_LZ4_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 <type_traits>
13#include <vector>
14
16
17/**
18 * @file
19 * @brief Native LZ4 block and frame coding implementation.
20 */
21
22/** @brief Internal implementation of LZ4 frame compression and decompression. */
24{
25
26 /**
27 * @brief Rotates a 32-bit value left without compiler-specific intrinsics.
28 * @param value Value to rotate.
29 * @param count Rotation distance in the range 1--31.
30 * @return Rotated value.
31 */
32 [[nodiscard]] inline std::uint32_t rotate_left(std::uint32_t value, std::uint8_t count) noexcept
33 {
34 return (value << count) | (value >> (32U - count));
35 }
36
37 /**
38 * @brief Reads an unchecked little-endian word from a known-valid range.
39 * @param input Byte range containing at least four bytes at @p offset.
40 * @param offset Position of the first byte.
41 * @return Decoded 32-bit word.
42 */
43 [[nodiscard]] inline std::uint32_t read_word(std::span< std::byte const > input, std::size_t offset) noexcept
44 {
45 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);
46 }
47
48 /**
49 * @brief Computes XXH32 as required by the LZ4 frame format.
50 * @param input Bytes to hash.
51 * @param seed XXH32 seed.
52 * @return Finalized 32-bit hash.
53 */
54 [[nodiscard]] inline std::uint32_t xxhash32(std::span< std::byte const > input, std::uint32_t seed = 0U) noexcept
55 {
56 constexpr std::uint32_t prime1 = 0x9e3779b1U;
57 constexpr std::uint32_t prime2 = 0x85ebca77U;
58 constexpr std::uint32_t prime3 = 0xc2b2ae3dU;
59 constexpr std::uint32_t prime4 = 0x27d4eb2fU;
60 constexpr std::uint32_t prime5 = 0x165667b1U;
61 std::size_t position = 0U;
62 std::uint32_t hash = 0U;
63 if (input.size() >= 16U)
64 {
65 std::uint32_t lane1 = seed + prime1 + prime2;
66 std::uint32_t lane2 = seed + prime2;
67 std::uint32_t lane3 = seed;
68 std::uint32_t lane4 = seed - prime1;
69 while (position + 16U <= input.size())
70 {
71 lane1 = rotate_left(lane1 + read_word(input, position) * prime2, 13U) * prime1;
72 lane2 = rotate_left(lane2 + read_word(input, position + 4U) * prime2, 13U) * prime1;
73 lane3 = rotate_left(lane3 + read_word(input, position + 8U) * prime2, 13U) * prime1;
74 lane4 = rotate_left(lane4 + read_word(input, position + 12U) * prime2, 13U) * prime1;
75 position += 16U;
76 }
77 hash = rotate_left(lane1, 1U) + rotate_left(lane2, 7U) + rotate_left(lane3, 12U) + rotate_left(lane4, 18U);
78 }
79 else
80 {
81 hash = seed + prime5;
82 }
83 hash += static_cast< std::uint32_t >(input.size());
84 while (position + 4U <= input.size())
85 {
86 hash = rotate_left(hash + read_word(input, position) * prime3, 17U) * prime4;
87 position += 4U;
88 }
89 while (position < input.size())
90 {
91 hash = rotate_left(hash + std::to_integer< std::uint8_t >(input[position]) * prime5, 11U) * prime1;
92 ++position;
93 }
94 hash ^= hash >> 15U;
95 hash *= prime2;
96 hash ^= hash >> 13U;
97 hash *= prime3;
98 hash ^= hash >> 16U;
99 return hash;
100 }
101
102 /** Incremental XXH32 accumulator for streamed LZ4 content checksums. */
104 {
105 public:
106 /**
107 * @brief Includes one byte in the hash.
108 * @param value Next byte in stream order.
109 */
110 void update(std::byte value) noexcept
111 {
112 m_buffer[m_buffer_size++] = value;
113 ++m_total_size;
114 if (m_buffer_size == m_buffer.size())
115 {
116 m_lane1 = round(m_lane1, read_word(m_buffer, 0U));
117 m_lane2 = round(m_lane2, read_word(m_buffer, 4U));
118 m_lane3 = round(m_lane3, read_word(m_buffer, 8U));
119 m_lane4 = round(m_lane4, read_word(m_buffer, 12U));
120 m_buffer_size = 0U;
121 }
122 }
123
124 /**
125 * @brief Returns the hash for all supplied bytes.
126 * @return Finalized XXH32 value without changing accumulator state.
127 */
128 [[nodiscard]] std::uint32_t value() const noexcept
129 {
130 constexpr std::uint32_t prime1 = 0x9e3779b1U;
131 constexpr std::uint32_t prime2 = 0x85ebca77U;
132 constexpr std::uint32_t prime3 = 0xc2b2ae3dU;
133 constexpr std::uint32_t prime4 = 0x27d4eb2fU;
134 constexpr std::uint32_t prime5 = 0x165667b1U;
135 std::uint32_t hash = m_total_size >= 16U ? rotate_left(m_lane1, 1U) + rotate_left(m_lane2, 7U) + rotate_left(m_lane3, 12U) + rotate_left(m_lane4, 18U) : prime5;
136 hash += static_cast< std::uint32_t >(m_total_size);
137 std::size_t position = 0U;
138 std::span< std::byte const > const remaining(m_buffer.data(), m_buffer_size);
139 while (position + 4U <= remaining.size())
140 {
141 hash = rotate_left(hash + read_word(remaining, position) * prime3, 17U) * prime4;
142 position += 4U;
143 }
144 while (position < remaining.size())
145 {
146 hash = rotate_left(hash + std::to_integer< std::uint8_t >(remaining[position]) * prime5, 11U) * prime1;
147 ++position;
148 }
149 hash ^= hash >> 15U;
150 hash *= prime2;
151 hash ^= hash >> 13U;
152 hash *= prime3;
153 hash ^= hash >> 16U;
154 return hash;
155 }
156
157 private:
158 /**
159 * @brief Applies one XXH32 lane round.
160 * @param lane Current lane accumulator.
161 * @param value Next little-endian input word.
162 * @return Updated lane accumulator.
163 */
164 [[nodiscard]] static std::uint32_t round(std::uint32_t lane, std::uint32_t value) noexcept
165 {
166 constexpr std::uint32_t prime1 = 0x9e3779b1U;
167 constexpr std::uint32_t prime2 = 0x85ebca77U;
168 return rotate_left(lane + value * prime2, 13U) * prime1;
169 }
170
171 std::array< std::byte, 16U > m_buffer{};
172 std::size_t m_buffer_size = 0U;
173 std::size_t m_total_size = 0U;
174 std::uint32_t m_lane1 = 0x9e3779b1U + 0x85ebca77U;
175 std::uint32_t m_lane2 = 0x85ebca77U;
176 std::uint32_t m_lane3 = 0U;
177 std::uint32_t m_lane4 = 0U - 0x9e3779b1U;
178 };
179
180 /**
181 * @brief Encodes an extended LZ4 literal or match length.
182 * @param output Block buffer receiving extension bytes.
183 * @param length Amount beyond the token's base length.
184 */
185 inline void append_extended_length(std::vector< std::byte >& output, std::size_t length)
186 {
187 while (length >= 255U)
188 {
189 output.push_back(std::byte{0xff});
190 length -= 255U;
191 }
192 output.push_back(static_cast< std::byte >(length));
193 }
194
195 /**
196 * @brief Compresses one independent LZ4 block.
197 * @param input Uncompressed block containing at most 64 KiB.
198 * @param level Search level from 0 through 12.
199 * @return Encoded block without an LZ4 frame header.
200 */
201 [[nodiscard]] inline std::vector< std::byte > compress_block(std::span< std::byte const > input, std::int32_t level)
202 {
203 constexpr std::size_t no_position = std::numeric_limits< std::size_t >::max();
204 std::array< std::size_t, 65536U > heads{};
205 heads.fill(no_position);
206 std::vector< std::size_t > previous(input.size(), no_position);
207 std::vector< std::byte > output;
208 std::size_t anchor = 0U;
209 std::size_t position = 0U;
210 std::size_t const search_depth = level == 0 ? 1U : 4U + static_cast< std::size_t >(level) * 16U;
211 auto hash_at = [&](std::size_t offset)
212 {
213 return static_cast< std::uint16_t >((read_word(input, offset) * 2654435761U) >> 16U);
214 };
215
216 while (position + 12U <= input.size())
217 {
218 std::uint16_t const hash = hash_at(position);
219 std::size_t candidate = heads[hash];
220 previous[position] = candidate;
221 heads[hash] = position;
222 std::size_t best_length = 0U;
223 std::size_t best_distance = 0U;
224 std::size_t depth = 0U;
225 while (candidate != no_position && position - candidate <= 65535U && depth < search_depth)
226 {
227 if (read_word(input, candidate) == read_word(input, position))
228 {
229 std::size_t length = 4U;
230 std::size_t const maximum_match_length = input.size() - position - 5U;
231 while (length < maximum_match_length && input[candidate + length] == input[position + length])
232 {
233 ++length;
234 }
235 if (length > best_length)
236 {
237 best_length = length;
238 best_distance = position - candidate;
239 }
240 }
241 candidate = previous[candidate];
242 ++depth;
243 }
244 if (best_length < 4U)
245 {
246 ++position;
247 continue;
248 }
249
250 std::size_t const literal_length = position - anchor;
251 std::size_t const match_length = best_length - 4U;
252 std::size_t const token_position = output.size();
253 output.push_back(std::byte{0x00});
254 std::uint8_t token = static_cast< std::uint8_t >(std::min< std::size_t >(literal_length, 15U) << 4U);
255 token = static_cast< std::uint8_t >(token | std::min< std::size_t >(match_length, 15U));
256 output[token_position] = static_cast< std::byte >(token);
257 if (literal_length >= 15U)
258 {
259 append_extended_length(output, literal_length - 15U);
260 }
261 output.insert(output.end(), input.begin() + static_cast< std::ptrdiff_t >(anchor), input.begin() + static_cast< std::ptrdiff_t >(position));
262 output.push_back(static_cast< std::byte >(best_distance & 0xffU));
263 output.push_back(static_cast< std::byte >((best_distance >> 8U) & 0xffU));
264 if (match_length >= 15U)
265 {
266 append_extended_length(output, match_length - 15U);
267 }
268
269 std::size_t const match_end = position + best_length;
270 for (std::size_t inserted = position + 1U; inserted < match_end && inserted + 4U <= input.size(); ++inserted)
271 {
272 std::uint16_t const inserted_hash = hash_at(inserted);
273 previous[inserted] = heads[inserted_hash];
274 heads[inserted_hash] = inserted;
275 }
276 position = match_end;
277 anchor = position;
278 }
279
280 std::size_t const literal_length = input.size() - anchor;
281 output.push_back(static_cast< std::byte >(std::min< std::size_t >(literal_length, 15U) << 4U));
282 if (literal_length >= 15U)
283 {
284 append_extended_length(output, literal_length - 15U);
285 }
286 output.insert(output.end(), input.begin() + static_cast< std::ptrdiff_t >(anchor), input.end());
287 return output;
288 }
289
290 /**
291 * @brief Reads an LZ4 extended length while checking block bounds.
292 * @param input Encoded block.
293 * @param position Current byte position, advanced past extension bytes.
294 * @param initial Length nibble from the token.
295 * @return Complete literal or match length.
296 * @throws compression_error If the extension is truncated or overflows size_t.
297 */
298 [[nodiscard]] inline std::size_t read_extended_length(std::span< std::byte const > input, std::size_t& position, std::size_t initial)
299 {
300 std::size_t length = initial;
301 if (initial != 15U)
302 {
303 return length;
304 }
305 while (true)
306 {
307 if (position == input.size())
308 {
309 throw compression_error(error_code::invalid_data, format::lz4_frame, "truncated LZ4 extended length");
310 }
311 std::uint8_t const extension = std::to_integer< std::uint8_t >(input[position++]);
312 if (length > std::numeric_limits< std::size_t >::max() - extension)
313 {
314 throw compression_error(error_code::invalid_data, format::lz4_frame, "LZ4 length overflows the platform size");
315 }
316 length += extension;
317 if (extension != 255U)
318 {
319 return length;
320 }
321 }
322 }
323
324 /**
325 * @brief Compresses an iterator range as an LZ4 frame.
326 * @tparam input_iterator Single-pass byte iterator.
327 * @tparam sentinel Sentinel for @p first.
328 * @tparam output_iterator Destination byte iterator.
329 * @param first First source byte.
330 * @param last Sentinel past the source.
331 * @param output Destination iterator.
332 * @param options Compression level from 0 through 12.
333 * @return Destination advanced past the frame end mark.
334 */
335 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
336 output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const& options)
337 {
338 std::int32_t const level = options.level.value_or(0);
339 if (level < 0 || level > 12)
340 {
341 throw compression_error(error_code::invalid_option, format::lz4_frame, "LZ4 compression level must be between 0 and 12");
342 }
343 auto write_u32 = [&](std::uint32_t value)
344 {
345 for (std::uint8_t index = 0U; index < 4U; ++index)
346 {
347 implementation::write_byte(output, static_cast< std::byte >(value >> (index * 8U)));
348 }
349 };
350 write_u32(0x184d2204U);
351 std::array< std::byte, 2U > const descriptor{std::byte{0x60}, std::byte{0x40}};
352 implementation::write_byte(output, descriptor[0U]);
353 implementation::write_byte(output, descriptor[1U]);
354 implementation::write_byte(output, static_cast< std::byte >((xxhash32(descriptor) >> 8U) & 0xffU));
355
356 constexpr std::size_t block_limit = 64U * 1024U;
357 std::vector< std::byte > block;
358 block.reserve(block_limit);
359 while (first != last)
360 {
361 block.clear();
362 while (first != last && block.size() < block_limit)
363 {
364 block.push_back(implementation::to_byte(*first));
365 ++first;
366 }
367 std::vector< std::byte > encoded = compress_block(block, level);
368 if (encoded.size() < block.size())
369 {
370 write_u32(static_cast< std::uint32_t >(encoded.size()));
371 for (std::byte value : encoded)
372 {
373 implementation::write_byte(output, value);
374 }
375 }
376 else
377 {
378 write_u32(static_cast< std::uint32_t >(block.size()) | 0x80000000U);
379 for (std::byte value : block)
380 {
381 implementation::write_byte(output, value);
382 }
383 }
384 }
385 write_u32(0U);
386 return output;
387 }
388
389 /**
390 * @brief Decompresses one or more LZ4 frames.
391 * @tparam input_iterator Single-pass byte iterator.
392 * @tparam sentinel Sentinel for @p first.
393 * @tparam output_iterator Destination byte iterator.
394 * @param first First compressed byte.
395 * @param last Sentinel past the compressed input.
396 * @param output Destination iterator.
397 * @param options Output limit and concatenated-frame policy.
398 * @return Destination advanced past the uncompressed data.
399 */
400 template < std::input_iterator input_iterator, std::sentinel_for< input_iterator > sentinel, typename output_iterator >
401 output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const& options)
402 {
403 implementation::byte_reader< input_iterator, sentinel > reader(std::move(first), std::move(last));
404 auto read_byte = [&]() -> std::byte
405 {
406 std::byte value{};
407 if (!reader.read(value))
408 {
409 throw compression_error(error_code::invalid_data, format::lz4_frame, "truncated LZ4 frame");
410 }
411 return value;
412 };
413 auto read_u32 = [&]() -> std::uint32_t
414 {
415 std::uint32_t value = 0U;
416 for (std::uint8_t index = 0U; index < 4U; ++index)
417 {
418 value |= static_cast< std::uint32_t >(std::to_integer< std::uint8_t >(read_byte())) << (index * 8U);
419 }
420 return value;
421 };
422 std::array< std::byte, 65536U > history{};
423 std::size_t total_output = 0U;
424 bool decoded_frame = false;
425 do
426 {
427 if (decoded_frame && !options.allow_concatenated_streams)
428 {
429 throw compression_error(error_code::trailing_data, format::lz4_frame, "LZ4 frame contains trailing data");
430 }
431 if (read_u32() != 0x184d2204U)
432 {
433 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 frame magic");
434 }
435 std::array< std::byte, 10U > descriptor{};
436 std::size_t descriptor_size = 0U;
437 std::byte const flags_byte = read_byte();
438 std::byte const block_descriptor_byte = read_byte();
439 descriptor[descriptor_size++] = flags_byte;
440 descriptor[descriptor_size++] = block_descriptor_byte;
441 std::uint8_t const flags = std::to_integer< std::uint8_t >(flags_byte);
442 std::uint8_t const block_descriptor = std::to_integer< std::uint8_t >(block_descriptor_byte);
443 if ((flags & 0xc0U) != 0x40U || (flags & 0x02U) != 0U || (block_descriptor & 0x8fU) != 0U)
444 {
445 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 frame flags");
446 }
447 std::uint8_t const block_size_code = static_cast< std::uint8_t >((block_descriptor >> 4U) & 0x07U);
448 if (block_size_code < 4U || block_size_code > 7U)
449 {
450 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 maximum block size");
451 }
452 constexpr std::array< std::size_t, 4U > maximum_block_sizes{64U * 1024U, 256U * 1024U, 1024U * 1024U, 4U * 1024U * 1024U};
453 std::size_t const maximum_block_size = maximum_block_sizes[block_size_code - 4U];
454 std::optional< std::uint64_t > content_size;
455 if ((flags & 0x08U) != 0U)
456 {
457 std::uint64_t size = 0U;
458 for (std::uint8_t index = 0U; index < 8U; ++index)
459 {
460 std::byte const value = read_byte();
461 descriptor[descriptor_size++] = value;
462 size |= static_cast< std::uint64_t >(std::to_integer< std::uint8_t >(value)) << (index * 8U);
463 }
464 content_size = size;
465 }
466 if ((flags & 0x01U) != 0U)
467 {
468 throw compression_error(error_code::unsupported_feature, format::lz4_frame, "dictionary-based LZ4 frames are not supported");
469 }
470 if (std::to_integer< std::uint8_t >(read_byte()) != static_cast< std::uint8_t >((xxhash32(std::span< std::byte const >(descriptor).first(descriptor_size)) >> 8U) & 0xffU))
471 {
472 throw compression_error(error_code::invalid_data, format::lz4_frame, "LZ4 header checksum mismatch");
473 }
474
475 std::size_t const frame_begin = total_output;
476 std::size_t history_begin = frame_begin;
477 xxhash32_accumulator frame_checksum;
478 auto emit = [&](std::byte value)
479 {
480 if (total_output == options.maximum_output_size)
481 {
482 throw compression_error(error_code::output_limit_exceeded, format::lz4_frame, "decompressed LZ4 output exceeds its limit");
483 }
484 history[total_output % history.size()] = value;
485 ++total_output;
486 if ((flags & 0x04U) != 0U)
487 {
488 frame_checksum.update(value);
489 }
490 implementation::write_byte(output, value);
491 };
492
493 while (true)
494 {
495 std::uint32_t const block_header = read_u32();
496 if (block_header == 0U)
497 {
498 break;
499 }
500 bool const uncompressed = (block_header & 0x80000000U) != 0U;
501 std::size_t const block_size = block_header & 0x7fffffffU;
502 if (block_size > maximum_block_size)
503 {
504 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 block size");
505 }
506 std::vector< std::byte > block;
507 block.reserve(block_size);
508 for (std::size_t index = 0U; index < block_size; ++index)
509 {
510 block.push_back(read_byte());
511 }
512 if ((flags & 0x10U) != 0U && read_u32() != xxhash32(block))
513 {
514 throw compression_error(error_code::invalid_data, format::lz4_frame, "LZ4 block checksum mismatch");
515 }
516 if ((flags & 0x20U) != 0U)
517 {
518 history_begin = total_output;
519 }
520 if (uncompressed)
521 {
522 for (std::byte value : block)
523 {
524 emit(value);
525 }
526 continue;
527 }
528
529 std::size_t position = 0U;
530 while (position < block.size())
531 {
532 std::uint8_t const token = std::to_integer< std::uint8_t >(block[position++]);
533 std::size_t const literal_length = read_extended_length(block, position, token >> 4U);
534 if (literal_length > block.size() - std::min(position, block.size()))
535 {
536 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 literal length");
537 }
538 for (std::size_t index = 0U; index < literal_length; ++index)
539 {
540 emit(block[position++]);
541 }
542 if (position == block.size())
543 {
544 break;
545 }
546 if (block.size() - position < 2U)
547 {
548 throw compression_error(error_code::invalid_data, format::lz4_frame, "truncated LZ4 match offset");
549 }
550 std::size_t const distance = std::to_integer< std::uint8_t >(block[position]) | (static_cast< std::size_t >(std::to_integer< std::uint8_t >(block[position + 1U])) << 8U);
551 position += 2U;
552 std::size_t const match_length = read_extended_length(block, position, token & 0x0fU) + 4U;
553 if (distance == 0U || distance > total_output - history_begin || distance > history.size())
554 {
555 throw compression_error(error_code::invalid_data, format::lz4_frame, "invalid LZ4 match distance or length");
556 }
557 for (std::size_t index = 0U; index < match_length; ++index)
558 {
559 emit(history[(total_output - distance) % history.size()]);
560 }
561 }
562 }
563 if ((flags & 0x04U) != 0U && read_u32() != frame_checksum.value())
564 {
565 throw compression_error(error_code::invalid_data, format::lz4_frame, "LZ4 content checksum mismatch");
566 }
567 if (content_size.has_value() && total_output - frame_begin != *content_size)
568 {
569 throw compression_error(error_code::invalid_data, format::lz4_frame, "LZ4 content size mismatch");
570 }
571 decoded_frame = true;
572 } while (!reader.empty());
573 return output;
574 }
575
576} // namespace rpnx::compression::lz4_codec
577
578#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
Incremental XXH32 accumulator for streamed LZ4 content checksums.
Definition lz4.hpp:104
std::uint32_t value() const noexcept
Returns the hash for all supplied bytes.
Definition lz4.hpp:128
void update(std::byte value) noexcept
Includes one byte in the hash.
Definition lz4.hpp:110
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 LZ4 frame compression and decompression.
Definition lz4.hpp:24
std::uint32_t read_word(std::span< std::byte const > input, std::size_t offset) noexcept
Reads an unchecked little-endian word from a known-valid range.
Definition lz4.hpp:43
std::size_t read_extended_length(std::span< std::byte const > input, std::size_t &position, std::size_t initial)
Reads an LZ4 extended length while checking block bounds.
Definition lz4.hpp:298
output_iterator decompress(input_iterator first, sentinel last, output_iterator output, decompression_options const &options)
Decompresses one or more LZ4 frames.
Definition lz4.hpp:401
std::vector< std::byte > compress_block(std::span< std::byte const > input, std::int32_t level)
Compresses one independent LZ4 block.
Definition lz4.hpp:201
std::uint32_t xxhash32(std::span< std::byte const > input, std::uint32_t seed=0U) noexcept
Computes XXH32 as required by the LZ4 frame format.
Definition lz4.hpp:54
void append_extended_length(std::vector< std::byte > &output, std::size_t length)
Encodes an extended LZ4 literal or match length.
Definition lz4.hpp:185
std::uint32_t rotate_left(std::uint32_t value, std::uint8_t count) noexcept
Rotates a 32-bit value left without compiler-specific intrinsics.
Definition lz4.hpp:32
output_iterator compress(input_iterator first, sentinel last, output_iterator output, compression_options const &options)
Compresses an iterator range as an LZ4 frame.
Definition lz4.hpp:336
@ 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.