| ... | ... | @@ -10,7 +10,11 @@ const Decompress = @This(); |
| 10 | 10 | const Token = @import("Token.zig"); |
| 11 | 11 | |
| 12 | 12 | input: *Reader, |
| 13 | next_bits: usize, |
| 14 | remaining_bits: std.math.Log2Int(usize), |
| 15 | |
| 13 | 16 | reader: Reader, |
| 17 | |
| 14 | 18 | /// Hashes, produces checksum, of uncompressed data for gzip/zlib footer. |
| 15 | 19 | hasher: Container.Hasher, |
| 16 | 20 | |
| ... | ... | @@ -65,6 +69,8 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress { |
| 65 | 69 | .end = 0, |
| 66 | 70 | }, |
| 67 | 71 | .input = input, |
| 72 | .next_bits = 0, |
| 73 | .remaining_bits = 0, |
| 68 | 74 | .hasher = .init(container), |
| 69 | 75 | .lit_dec = .{}, |
| 70 | 76 | .dst_dec = .{}, |
| ... | ... | @@ -228,15 +234,15 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S |
| 228 | 234 | return error.InvalidDynamicBlockHeader; |
| 229 | 235 | |
| 230 | 236 | // lengths for code lengths |
| 231 | | var cl_lens = [_]u4{0} ** 19; |
| 232 | | for (0..hclen) |i| { |
| 233 | | cl_lens[flate.HuffmanEncoder.codegen_order[i]] = try d.takeBits(u3); |
| 237 | var cl_lens: [19]u4 = @splat(0); |
| 238 | for (flate.HuffmanEncoder.codegen_order[0..hclen]) |i| { |
| 239 | cl_lens[i] = try d.takeBits(u3); |
| 234 | 240 | } |
| 235 | 241 | var cl_dec: CodegenDecoder = .{}; |
| 236 | 242 | try cl_dec.generate(&cl_lens); |
| 237 | 243 | |
| 238 | 244 | // decoded code lengths |
| 239 | | var dec_lens = [_]u4{0} ** (286 + 30); |
| 245 | var dec_lens: [286 + 30]u4 = @splat(0); |
| 240 | 246 | var pos: usize = 0; |
| 241 | 247 | while (pos < hlit + hdist) { |
| 242 | 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 | 358 | } |
| 353 | 359 | |
| 354 | 360 | fn takeBits(d: *Decompress, comptime T: type) !T { |
| 355 | | _ = d; |
| 356 | | @panic("TODO"); |
| 361 | const U = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } }); |
| 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 | 387 | fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T { |
| ... | ... | @@ -378,8 +406,20 @@ fn peekBitsReverseBuffered(d: *Decompress, comptime T: type) !T { |
| 378 | 406 | } |
| 379 | 407 | |
| 380 | 408 | fn alignBitsToByte(d: *Decompress) void { |
| 381 | | _ = d; |
| 382 | | @panic("TODO"); |
| 409 | const remaining_bits = d.remaining_bits; |
| 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 | 425 | fn shiftBits(d: *Decompress, n: u6) !void { |
| ... | ... | @@ -691,47 +731,37 @@ test "encode/decode literals" { |
| 691 | 731 | } |
| 692 | 732 | } |
| 693 | 733 | |
| 694 | | test "decompress" { |
| 695 | | const cases = [_]struct { |
| 696 | | in: []const u8, |
| 697 | | out: []const u8, |
| 698 | | }{ |
| 699 | | // non compressed block (type 0) |
| 700 | | .{ |
| 701 | | .in = &[_]u8{ |
| 702 | | 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen |
| 703 | | 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data |
| 704 | | }, |
| 705 | | .out = "Hello world\n", |
| 706 | | }, |
| 707 | | // fixed code block (type 1) |
| 708 | | .{ |
| 709 | | .in = &[_]u8{ |
| 710 | | 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1 |
| 711 | | 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00, |
| 712 | | }, |
| 713 | | .out = "Hello world\n", |
| 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(); |
| 734 | test "basic" { |
| 735 | // non compressed block (type 0) |
| 736 | try testBasicCase(&[_]u8{ |
| 737 | 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen |
| 738 | 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data |
| 739 | }, "Hello world\n"); |
| 740 | |
| 741 | // fixed code block (type 1) |
| 742 | try testBasicCase(&[_]u8{ |
| 743 | 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1 |
| 744 | 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00, |
| 745 | }, "Hello world\n"); |
| 746 | |
| 747 | // dynamic block (type 2) |
| 748 | try testBasicCase(&[_]u8{ |
| 749 | 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2 |
| 750 | 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38, |
| 751 | 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01, |
| 752 | }, "ABCDEABCD ABCDEABCD"); |
| 753 | } |
| 729 | 754 | |
| 730 | | var decompress: Decompress = .init(&fb, .raw, &.{}); |
| 731 | | const r = &decompress.reader; |
| 732 | | _ = try r.streamRemaining(&aw.writer); |
| 733 | | try testing.expectEqualStrings(c.out, aw.getWritten()); |
| 734 | | } |
| 755 | fn testBasicCase(in: []const u8, out: []const u8) !void { |
| 756 | var reader: Reader = .fixed(in); |
| 757 | var aw: Writer.Allocating = .init(testing.allocator); |
| 758 | try aw.ensureUnusedCapacity(flate.history_len + 1); |
| 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 | 767 | test "gzip decompress" { |