authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-01 18:23:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
log0d29c78af5d3f9c38c64399eec5509e11e10047a
treefe1ad3ec4326f67468b4a5f42ed943dcf46410a5
parent990031444dde4da223adcadfd2f2824f5baa0812

std.compress.zstd: tests passing


2 files changed, 43 insertions(+), 86 deletions(-)

lib/std/compress/zstd.zig+6-5
...@@ -81,12 +81,11 @@ pub const table_size_max = struct {...@@ -81,12 +81,11 @@ pub const table_size_max = struct {
81fn testDecompress(gpa: std.mem.Allocator, compressed: []const u8) ![]u8 {81fn testDecompress(gpa: std.mem.Allocator, compressed: []const u8) ![]u8 {
82 var out: std.ArrayListUnmanaged(u8) = .empty;82 var out: std.ArrayListUnmanaged(u8) = .empty;
83 defer out.deinit(gpa);83 defer out.deinit(gpa);
84 try out.ensureUnusedCapacity(gpa, default_window_len);
8584
86 var in: std.io.BufferedReader = undefined;85 var in: std.io.BufferedReader = undefined;
87 in.initFixed(@constCast(compressed));86 in.initFixed(@constCast(compressed));
88 var zstd_stream: Decompress = .init(&in, .{});87 var zstd_stream: Decompress = .init(&in, .{});
89 try zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited);88 try zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited, default_window_len);
9089
91 return out.toOwnedSlice(gpa);90 return out.toOwnedSlice(gpa);
92}91}
...@@ -103,16 +102,18 @@ fn testExpectDecompressError(err: anyerror, compressed: []const u8) !void {...@@ -103,16 +102,18 @@ fn testExpectDecompressError(err: anyerror, compressed: []const u8) !void {
103102
104 var out: std.ArrayListUnmanaged(u8) = .empty;103 var out: std.ArrayListUnmanaged(u8) = .empty;
105 defer out.deinit(gpa);104 defer out.deinit(gpa);
106 try out.ensureUnusedCapacity(gpa, default_window_len);
107105
108 var in: std.io.BufferedReader = undefined;106 var in: std.io.BufferedReader = undefined;
109 in.initFixed(@constCast(compressed));107 in.initFixed(@constCast(compressed));
110 var zstd_stream: Decompress = .init(&in, .{});108 var zstd_stream: Decompress = .init(&in, .{});
111 try std.testing.expectError(error.ReadFailed, zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited));109 try std.testing.expectError(
110 error.ReadFailed,
111 zstd_stream.reader().readRemainingArrayList(gpa, null, &out, .unlimited, default_window_len),
112 );
112 try std.testing.expectError(err, zstd_stream.err orelse {});113 try std.testing.expectError(err, zstd_stream.err orelse {});
113}114}
114115
115test "decompression" {116test Decompress {
116 const uncompressed = @embedFile("testdata/rfc8478.txt");117 const uncompressed = @embedFile("testdata/rfc8478.txt");
117 const compressed3 = @embedFile("testdata/rfc8478.txt.zst.3");118 const compressed3 = @embedFile("testdata/rfc8478.txt.zst.3");
118 const compressed19 = @embedFile("testdata/rfc8478.txt.zst.19");119 const compressed19 = @embedFile("testdata/rfc8478.txt.zst.19");
lib/std/compress/zstd/Decompress.zig+37-81
...@@ -149,7 +149,8 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:...@@ -149,7 +149,8 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:
149 const header_bytes = try in.takeArray(3);149 const header_bytes = try in.takeArray(3);
150 const block_header: Frame.Zstandard.Block.Header = @bitCast(header_bytes.*);150 const block_header: Frame.Zstandard.Block.Header = @bitCast(header_bytes.*);
151 const block_size = block_header.size;151 const block_size = block_header.size;
152 if (state.frame.block_size_max < block_size) return error.BlockOversize;152 const frame_block_size_max = state.frame.block_size_max;
153 if (frame_block_size_max < block_size) return error.BlockOversize;
153 if (@intFromEnum(limit) < block_size) return error.OutputBufferUndersize;154 if (@intFromEnum(limit) < block_size) return error.OutputBufferUndersize;
154 var bytes_written: usize = 0;155 var bytes_written: usize = 0;
155 switch (block_header.type) {156 switch (block_header.type) {
...@@ -185,17 +186,18 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:...@@ -185,17 +186,18 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:
185 if (sequences_header.sequence_count > 0) {186 if (sequences_header.sequence_count > 0) {
186 try decode.readInitialFseState(&bit_stream);187 try decode.readInitialFseState(&bit_stream);
187188
188 var sequence_size_limit = state.frame.block_size_max;189 // Ensures the following calls to `decodeSequence` will not flush.
189 for (0..sequences_header.sequence_count) |i| {190 if (frame_block_size_max > bw.buffer.len) return error.OutputBufferUndersize;
190 const decompressed_size = try decode.decodeSequence(191 const dest = (try bw.writableSliceGreedy(frame_block_size_max))[0..frame_block_size_max];
191 bw,192 for (0..sequences_header.sequence_count - 1) |_| {
192 &bit_stream,193 bytes_written += try decode.decodeSequence(dest, bytes_written, &bit_stream);
193 sequence_size_limit,194 try decode.updateState(.literal, &bit_stream);
194 i == sequences_header.sequence_count - 1,195 try decode.updateState(.match, &bit_stream);
195 );196 try decode.updateState(.offset, &bit_stream);
196 sequence_size_limit -= decompressed_size;
197 bytes_written += decompressed_size;
198 }197 }
198 bytes_written += try decode.decodeSequence(dest, bytes_written, &bit_stream);
199 if (bytes_written > dest.len) return error.MalformedSequence;
200 bw.advance(bytes_written);
199 }201 }
200202
201 if (!bit_stream.isEmpty()) {203 if (!bit_stream.isEmpty()) {
...@@ -206,6 +208,7 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:...@@ -206,6 +208,7 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:
206 if (decode.literal_written_count < literals.header.regenerated_size) {208 if (decode.literal_written_count < literals.header.regenerated_size) {
207 const len = literals.header.regenerated_size - decode.literal_written_count;209 const len = literals.header.regenerated_size - decode.literal_written_count;
208 try decode.decodeLiterals(bw, len);210 try decode.decodeLiterals(bw, len);
211 decode.literal_written_count += len;
209 bytes_written += len;212 bytes_written += len;
210 }213 }
211214
...@@ -216,8 +219,7 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:...@@ -216,8 +219,7 @@ fn readInFrame(d: *Decompress, bw: *BufferedWriter, limit: Reader.Limit, state:
216 .raw, .rle => {},219 .raw, .rle => {},
217 }220 }
218221
219 if (bytes_written > state.frame.block_size_max) return error.BlockOversize;222 if (bytes_written > frame_block_size_max) return error.BlockOversize;
220 if (remaining.nonzero()) return error.MalformedCompressedBlock;
221223
222 state.decompressed_size += bytes_written;224 state.decompressed_size += bytes_written;
223 if (state.frame.content_size) |size| {225 if (state.frame.content_size) |size| {
...@@ -649,63 +651,35 @@ pub const Frame = struct {...@@ -649,63 +651,35 @@ pub const Frame = struct {
649 };651 };
650 }652 }
651653
652 const DecodeSequenceError = error{
653 /// The decompressed sequence would be longer than
654 /// `sequence_size_limit` or the sequence's offset is too large
655 MalformedSequence,
656 /// The decoder state's literal streams do not contain enough
657 /// literals for the sequence (this may mean the literal stream or the
658 /// sequence is malformed).
659 UnexpectedEndOfLiteralStream,
660 /// The FSE sequence bitstream is malformed
661 InvalidBitStream,
662 /// `bit_reader` does not contain enough bits
663 EndOfStream,
664 /// The `BufferedWriter` storage capacity is not large enough to
665 /// accept this stream.
666 OutputBufferUndersize,
667 WriteFailed,
668 MalformedLiteralsLength,
669 MalformedFseBits,
670 MissingStartBit,
671 HuffmanTreeIncomplete,
672 };
673
674 /// Decode one sequence from `bit_reader` into `dest`. Updates FSE states654 /// Decode one sequence from `bit_reader` into `dest`. Updates FSE states
675 /// if `last_sequence` is `false`. Assumes `prepare` called for the block655 /// if `last_sequence` is `false`. Assumes `prepare` called for the block
676 /// before attempting to decode sequences.656 /// before attempting to decode sequences.
677 fn decodeSequence(657 fn decodeSequence(
678 self: *Decode,658 decode: *Decode,
679 dest: *BufferedWriter,659 dest: []u8,
660 write_pos: usize,
680 bit_reader: *ReverseBitReader,661 bit_reader: *ReverseBitReader,
681 sequence_size_limit: usize,662 ) !usize {
682 last_sequence: bool,663 const sequence = try decode.nextSequence(bit_reader);
683 ) DecodeSequenceError!usize {
684 const sequence = try self.nextSequence(bit_reader);
685 const literal_length: usize = sequence.literal_length;664 const literal_length: usize = sequence.literal_length;
686 const match_length: usize = sequence.match_length;665 const match_length: usize = sequence.match_length;
687 const sequence_length = literal_length + match_length;666 const sequence_length = literal_length + match_length;
688 if (sequence_length > sequence_size_limit) return error.MalformedSequence;667
689 if (sequence_length > dest.buffer.len) return error.OutputBufferUndersize;668 const copy_start = std.math.sub(usize, write_pos + sequence.literal_length, sequence.offset) catch
690669 return error.MalformedSequence;
691 if (sequence.offset > literal_length) return error.MalformedSequence;670
692 // Ensures the following call to `decodeLiterals` will not cause a671 if (decode.literal_written_count + literal_length > decode.literal_header.regenerated_size)
693 // flush and therefore be at the beginning of `out`.672 return error.MalformedLiteralsLength;
694 const out = try dest.writableSlice(sequence_length);673 var sub_bw: BufferedWriter = undefined;
695 const expected_end = dest.end + out.len;674 sub_bw.initFixed(dest[write_pos..]);
696 try decodeLiterals(self, dest, literal_length);675 try decodeLiterals(decode, &sub_bw, literal_length);
697 @memmove(676 decode.literal_written_count += literal_length;
698 out[literal_length..][0..match_length],677 // This is not a @memmove; it intentionally repeats patterns
699 out[literal_length - sequence.offset ..][0..match_length],678 // caused by iterating one byte at a time.
700 );679 for (
701 dest.advance(match_length);680 dest[write_pos + literal_length ..][0..match_length],
702 assert(dest.end == expected_end);681 dest[copy_start..][0..match_length],
703682 ) |*d, s| d.* = s;
704 if (!last_sequence) {
705 try self.updateState(.literal, bit_reader);
706 try self.updateState(.match, bit_reader);
707 try self.updateState(.offset, bit_reader);
708 }
709 return sequence_length;683 return sequence_length;
710 }684 }
711685
...@@ -744,31 +718,14 @@ pub const Frame = struct {...@@ -744,31 +718,14 @@ pub const Frame = struct {
744 };718 };
745 }719 }
746720
747 const DecodeLiteralsError = error{
748 /// The number of literal bytes decoded by `self` plus `len` is greater
749 /// than the regenerated size of `literals`
750 MalformedLiteralsLength,
751 /// Problems decoding Huffman compressed literals
752 UnexpectedEndOfLiteralStream,
753 OutputBufferUndersize,
754 WriteFailed,
755 MissingStartBit,
756 HuffmanTreeIncomplete,
757 };
758
759 /// Decode `len` bytes of literals into `dest`.721 /// Decode `len` bytes of literals into `dest`.
760 pub fn decodeLiterals(self: *Decode, dest: *BufferedWriter, len: usize) DecodeLiteralsError!void {722 fn decodeLiterals(self: *Decode, dest: *BufferedWriter, len: usize) !void {
761 if (self.literal_written_count + len > self.literal_header.regenerated_size)
762 return error.MalformedLiteralsLength;
763
764 switch (self.literal_header.block_type) {723 switch (self.literal_header.block_type) {
765 .raw => {724 .raw => {
766 try dest.writeAll(self.literal_streams.one[self.literal_written_count..][0..len]);725 try dest.writeAll(self.literal_streams.one[self.literal_written_count..][0..len]);
767 self.literal_written_count += len;
768 },726 },
769 .rle => {727 .rle => {
770 try dest.splatByteAll(self.literal_streams.one[0], len);728 try dest.splatByteAll(self.literal_streams.one[0], len);
771 self.literal_written_count += len;
772 },729 },
773 .compressed, .treeless => {730 .compressed, .treeless => {
774 if (len > dest.buffer.len) return error.OutputBufferUndersize;731 if (len > dest.buffer.len) return error.OutputBufferUndersize;
...@@ -810,7 +767,6 @@ pub const Frame = struct {...@@ -810,7 +767,6 @@ pub const Frame = struct {
810 }767 }
811 }768 }
812 }769 }
813 self.literal_written_count += len;
814 },770 },
815 }771 }
816 }772 }