| ... | @@ -10,7 +10,11 @@ const Decompress = @This(); | ... | @@ -10,7 +10,11 @@ const Decompress = @This(); |
| 10 | const Token = @import("Token.zig"); | 10 | const Token = @import("Token.zig"); |
| 11 | | 11 | |
| 12 | input: *Reader, | 12 | input: *Reader, |
| | 13 | next_bits: usize, |
| | 14 | remaining_bits: std.math.Log2Int(usize), |
| | 15 | |
| 13 | reader: Reader, | 16 | reader: Reader, |
| | 17 | |
| 14 | /// Hashes, produces checksum, of uncompressed data for gzip/zlib footer. | 18 | /// Hashes, produces checksum, of uncompressed data for gzip/zlib footer. |
| 15 | hasher: Container.Hasher, | 19 | hasher: Container.Hasher, |
| 16 | | 20 | |
| ... | @@ -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; |
| 229 | | 235 | |
| 230 | // lengths for code lengths | 236 | // 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); |
| 237 | | 243 | |
| 238 | // decoded code lengths | 244 | // 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 | } |
| 353 | | 359 | |
| 354 | fn takeBits(d: *Decompress, comptime T: type) !T { | 360 | fn 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 | } |
| 358 | | 386 | |
| 359 | fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T { | 387 | fn 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 | } |
| 379 | | 407 | |
| 380 | fn alignBitsToByte(d: *Decompress) void { | 408 | fn 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 | } |
| 384 | | 424 | |
| 385 | fn shiftBits(d: *Decompress, n: u6) !void { | 425 | fn 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 | } |
| 693 | | 733 | |
| 694 | test "decompress" { | 734 | test "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, nlen | 742 | try testBasicCase(&[_]u8{ |
| 703 | 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data | 743 | 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 1 | 750 | 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(); | | |
| 729 | | 754 | |
| 730 | var decompress: Decompress = .init(&fb, .raw, &.{}); | 755 | fn 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 | } |
| 736 | | 766 | |
| 737 | test "gzip decompress" { | 767 | test "gzip decompress" { |