authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-28 11:42:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-31 22:10:11-07:00
log6509fa1cf3694ae16333739fa0f3fae1b63f1eaf
treed51cf407b897c3e7ccdb6f2af4d8ed892574cdef
parent88ca75020909f922a5d3edfd1ca0be3ce148040f

std.compress.flate.Decompress: passing basic test case


1 files changed, 78 insertions(+), 48 deletions(-)

lib/std/compress/flate/Decompress.zig+78-48
...@@ -10,7 +10,11 @@ const Decompress = @This();...@@ -10,7 +10,11 @@ const Decompress = @This();
10const Token = @import("Token.zig");10const Token = @import("Token.zig");
1111
12input: *Reader,12input: *Reader,
13next_bits: usize,
14remaining_bits: std.math.Log2Int(usize),
15
13reader: Reader,16reader: Reader,
17
14/// Hashes, produces checksum, of uncompressed data for gzip/zlib footer.18/// Hashes, produces checksum, of uncompressed data for gzip/zlib footer.
15hasher: Container.Hasher,19hasher: Container.Hasher,
1620
...@@ -65,6 +69,8 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {...@@ -65,6 +69,8 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
65 .end = 0,69 .end = 0,
66 },70 },
67 .input = input,71 .input = input,
72 .next_bits = 0,
73 .remaining_bits = 0,
68 .hasher = .init(container),74 .hasher = .init(container),
69 .lit_dec = .{},75 .lit_dec = .{},
70 .dst_dec = .{},76 .dst_dec = .{},
...@@ -228,15 +234,15 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S...@@ -228,15 +234,15 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S
228 return error.InvalidDynamicBlockHeader;234 return error.InvalidDynamicBlockHeader;
229235
230 // lengths for code lengths236 // lengths for code lengths
231 var cl_lens = [_]u4{0} ** 19;237 var cl_lens: [19]u4 = @splat(0);
232 for (0..hclen) |i| {238 for (flate.HuffmanEncoder.codegen_order[0..hclen]) |i| {
233 cl_lens[flate.HuffmanEncoder.codegen_order[i]] = try d.takeBits(u3);239 cl_lens[i] = try d.takeBits(u3);
234 }240 }
235 var cl_dec: CodegenDecoder = .{};241 var cl_dec: CodegenDecoder = .{};
236 try cl_dec.generate(&cl_lens);242 try cl_dec.generate(&cl_lens);
237243
238 // decoded code lengths244 // decoded code lengths
239 var dec_lens = [_]u4{0} ** (286 + 30);245 var dec_lens: [286 + 30]u4 = @splat(0);
240 var pos: usize = 0;246 var pos: usize = 0;
241 while (pos < hlit + hdist) {247 while (pos < hlit + hdist) {
242 const sym = try cl_dec.find(try d.peekBitsReverse(u7));248 const sym = try cl_dec.find(try d.peekBitsReverse(u7));
...@@ -352,8 +358,30 @@ fn writeMatch(w: *Writer, length: u16, distance: u16, remaining: usize) !usize {...@@ -352,8 +358,30 @@ fn writeMatch(w: *Writer, length: u16, distance: u16, remaining: usize) !usize {
352}358}
353359
354fn takeBits(d: *Decompress, comptime T: type) !T {360fn takeBits(d: *Decompress, comptime T: type) !T {
355 _ = d;361 const U = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } });
356 @panic("TODO");362 const remaining_bits = d.remaining_bits;
363 const next_bits = d.next_bits;
364 if (remaining_bits >= @bitSizeOf(T)) {
365 const u: U = @truncate(next_bits);
366 d.next_bits = next_bits >> @bitSizeOf(T);
367 d.remaining_bits = remaining_bits - @bitSizeOf(T);
368 return switch (@typeInfo(T)) {
369 .int => u,
370 .@"enum" => @enumFromInt(u),
371 else => @bitCast(u),
372 };
373 }
374 const in = d.input;
375 const next_int = try in.takeInt(usize, .little);
376 const needed_bits = @bitSizeOf(T) - remaining_bits;
377 const u: U = @intCast((next_bits << needed_bits) | (next_int & ((@as(usize, 1) << needed_bits) - 1)));
378 d.next_bits = next_int >> needed_bits;
379 d.remaining_bits = @intCast(@bitSizeOf(usize) - @as(usize, needed_bits));
380 return switch (@typeInfo(T)) {
381 .int => u,
382 .@"enum" => @enumFromInt(u),
383 else => @bitCast(u),
384 };
357}385}
358386
359fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T {387fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
...@@ -378,8 +406,20 @@ fn peekBitsReverseBuffered(d: *Decompress, comptime T: type) !T {...@@ -378,8 +406,20 @@ fn peekBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
378}406}
379407
380fn alignBitsToByte(d: *Decompress) void {408fn alignBitsToByte(d: *Decompress) void {
381 _ = d;409 const remaining_bits = d.remaining_bits;
382 @panic("TODO");410 const next_bits = d.next_bits;
411 if (remaining_bits == 0) return;
412 const discard_bits = remaining_bits % 8;
413 const n_bytes = remaining_bits / 8;
414 var put_back_bits = next_bits >> discard_bits;
415 const in = d.input;
416 in.seek -= n_bytes;
417 for (in.buffer[in.seek..][0..n_bytes]) |*b| {
418 b.* = @truncate(put_back_bits);
419 put_back_bits >>= 8;
420 }
421 d.remaining_bits = 0;
422 d.next_bits = 0;
383}423}
384424
385fn shiftBits(d: *Decompress, n: u6) !void {425fn shiftBits(d: *Decompress, n: u6) !void {
...@@ -691,47 +731,37 @@ test "encode/decode literals" {...@@ -691,47 +731,37 @@ test "encode/decode literals" {
691 }731 }
692}732}
693733
694test "decompress" {734test "basic" {
695 const cases = [_]struct {735 // non compressed block (type 0)
696 in: []const u8,736 try testBasicCase(&[_]u8{
697 out: []const u8,737 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
698 }{738 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
699 // non compressed block (type 0)739 }, "Hello world\n");
700 .{740
701 .in = &[_]u8{741 // fixed code block (type 1)
702 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen742 try testBasicCase(&[_]u8{
703 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data743 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
704 },744 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
705 .out = "Hello world\n",745 }, "Hello world\n");
706 },746
707 // fixed code block (type 1)747 // dynamic block (type 2)
708 .{748 try testBasicCase(&[_]u8{
709 .in = &[_]u8{749 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
710 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1750 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
711 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,751 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
712 },752 }, "ABCDEABCD ABCDEABCD");
713 .out = "Hello world\n",753}
714 },
715 // dynamic block (type 2)
716 .{
717 .in = &[_]u8{
718 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
719 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
720 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
721 },
722 .out = "ABCDEABCD ABCDEABCD",
723 },
724 };
725 for (cases) |c| {
726 var fb: Reader = .fixed(c.in);
727 var aw: Writer.Allocating = .init(testing.allocator);
728 defer aw.deinit();
729754
730 var decompress: Decompress = .init(&fb, .raw, &.{});755fn testBasicCase(in: []const u8, out: []const u8) !void {
731 const r = &decompress.reader;756 var reader: Reader = .fixed(in);
732 _ = try r.streamRemaining(&aw.writer);757 var aw: Writer.Allocating = .init(testing.allocator);
733 try testing.expectEqualStrings(c.out, aw.getWritten());758 try aw.ensureUnusedCapacity(flate.history_len + 1);
734 }759 defer aw.deinit();
760
761 var decompress: Decompress = .init(&reader, .raw, &.{});
762 const r = &decompress.reader;
763 _ = try r.streamRemaining(&aw.writer);
764 try testing.expectEqualStrings(out, aw.getWritten());
735}765}
736766
737test "gzip decompress" {767test "gzip decompress" {