authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-25 22:10:29-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-31 22:10:11-07:00
log83513ade3591de673e9ac4824fe974cd8f90c847
tree5a581e31b4b004061673ac1e44e669e1808be3bd
parenta2d21d63270ebb5eec0d437f7726c261455da66b

std.compress: rework flate to new I/O API


24 files changed, 3613 insertions(+), 4956 deletions(-)

lib/std/compress.zig+1-4
...@@ -1,8 +1,7 @@...@@ -1,8 +1,7 @@
1//! Compression algorithms.1//! Compression algorithms.
22
3/// gzip and zlib are here.
3pub const flate = @import("compress/flate.zig");4pub const flate = @import("compress/flate.zig");
4pub const gzip = @import("compress/gzip.zig");
5pub const zlib = @import("compress/zlib.zig");
6pub const lzma = @import("compress/lzma.zig");5pub const lzma = @import("compress/lzma.zig");
7pub const lzma2 = @import("compress/lzma2.zig");6pub const lzma2 = @import("compress/lzma2.zig");
8pub const xz = @import("compress/xz.zig");7pub const xz = @import("compress/xz.zig");
...@@ -14,6 +13,4 @@ test {...@@ -14,6 +13,4 @@ test {
14 _ = lzma2;13 _ = lzma2;
15 _ = xz;14 _ = xz;
16 _ = zstd;15 _ = zstd;
17 _ = gzip;
18 _ = zlib;
19}16}
lib/std/compress/flate.zig+341-178
...@@ -1,94 +1,189 @@...@@ -1,94 +1,189 @@
1/// Deflate is a lossless data compression file format that uses a combination1const builtin = @import("builtin");
2/// of LZ77 and Huffman coding.2const std = @import("../std.zig");
3pub const deflate = @import("flate/deflate.zig");3const testing = std.testing;
44const Writer = std.io.Writer;
5/// Inflate is the decoding process that takes a Deflate bitstream for5
6/// decompression and correctly produces the original full-size data or file.6/// Container of the deflate bit stream body. Container adds header before
7pub const inflate = @import("flate/inflate.zig");7/// deflate bit stream and footer after. It can bi gzip, zlib or raw (no header,
88/// no footer, raw bit stream).
9/// Decompress compressed data from reader and write plain data to the writer.9///
10pub fn decompress(reader: anytype, writer: anytype) !void {10/// Zlib format is defined in rfc 1950. Header has 2 bytes and footer 4 bytes
11 try inflate.decompress(.raw, reader, writer);11/// addler 32 checksum.
12}12///
13/// Gzip format is defined in rfc 1952. Header has 10+ bytes and footer 4 bytes
14/// crc32 checksum and 4 bytes of uncompressed data length.
15///
16///
17/// rfc 1950: https://datatracker.ietf.org/doc/html/rfc1950#page-4
18/// rfc 1952: https://datatracker.ietf.org/doc/html/rfc1952#page-5
19pub const Container = enum {
20 raw, // no header or footer
21 gzip, // gzip header and footer
22 zlib, // zlib header and footer
23
24 pub fn size(w: Container) usize {
25 return headerSize(w) + footerSize(w);
26 }
1327
14/// Decompressor type28 pub fn headerSize(w: Container) usize {
15pub fn Decompressor(comptime ReaderType: type) type {29 return header(w).len;
16 return inflate.Decompressor(.raw, ReaderType);30 }
17}
1831
19/// Create Decompressor which will read compressed data from reader.32 pub fn footerSize(w: Container) usize {
20pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) {33 return switch (w) {
21 return inflate.decompressor(.raw, reader);34 .gzip => 8,
22}35 .zlib => 4,
36 .raw => 0,
37 };
38 }
2339
24/// Compression level, trades between speed and compression size.40 pub const list = [_]Container{ .raw, .gzip, .zlib };
25pub const Options = deflate.Options;
2641
27/// Compress plain data from reader and write compressed data to the writer.42 pub const Error = error{
28pub fn compress(reader: anytype, writer: anytype, options: Options) !void {43 BadGzipHeader,
29 try deflate.compress(.raw, reader, writer, options);44 BadZlibHeader,
30}45 WrongGzipChecksum,
46 WrongGzipSize,
47 WrongZlibChecksum,
48 };
3149
32/// Compressor type50 pub fn header(container: Container) []const u8 {
33pub fn Compressor(comptime WriterType: type) type {51 return switch (container) {
34 return deflate.Compressor(.raw, WriterType);52 // GZIP 10 byte header (https://datatracker.ietf.org/doc/html/rfc1952#page-5):
35}53 // - ID1 (IDentification 1), always 0x1f
54 // - ID2 (IDentification 2), always 0x8b
55 // - CM (Compression Method), always 8 = deflate
56 // - FLG (Flags), all set to 0
57 // - 4 bytes, MTIME (Modification time), not used, all set to zero
58 // - XFL (eXtra FLags), all set to zero
59 // - OS (Operating System), 03 = Unix
60 .gzip => &[_]u8{ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 },
61 // ZLIB has a two-byte header (https://datatracker.ietf.org/doc/html/rfc1950#page-4):
62 // 1st byte:
63 // - First four bits is the CINFO (compression info), which is 7 for the default deflate window size.
64 // - The next four bits is the CM (compression method), which is 8 for deflate.
65 // 2nd byte:
66 // - Two bits is the FLEVEL (compression level). Values are: 0=fastest, 1=fast, 2=default, 3=best.
67 // - The next bit, FDICT, is set if a dictionary is given.
68 // - The final five FCHECK bits form a mod-31 checksum.
69 //
70 // CINFO = 7, CM = 8, FLEVEL = 0b10, FDICT = 0, FCHECK = 0b11100
71 .zlib => &[_]u8{ 0x78, 0b10_0_11100 },
72 .raw => &.{},
73 };
74 }
3675
37/// Create Compressor which outputs compressed data to the writer.76 pub const Hasher = union(Container) {
38pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) {77 raw: void,
39 return try deflate.compressor(.raw, writer, options);78 gzip: struct {
40}79 crc: std.hash.Crc32 = .init(),
80 count: usize = 0,
81 },
82 zlib: std.hash.Adler32,
83
84 pub fn init(containter: Container) Hasher {
85 return switch (containter) {
86 .gzip => .{ .gzip = .{} },
87 .zlib => .{ .zlib = .init() },
88 .raw => .raw,
89 };
90 }
4191
42/// Huffman only compression. Without Lempel-Ziv match searching. Faster92 pub fn container(h: Hasher) Container {
43/// compression, less memory requirements but bigger compressed sizes.93 return h;
44pub const huffman = struct {94 }
45 pub fn compress(reader: anytype, writer: anytype) !void {
46 try deflate.huffman.compress(.raw, reader, writer);
47 }
4895
49 pub fn Compressor(comptime WriterType: type) type {96 pub fn update(h: *Hasher, buf: []const u8) void {
50 return deflate.huffman.Compressor(.raw, WriterType);97 switch (h.*) {
51 }98 .raw => {},
99 .gzip => |*gzip| {
100 gzip.update(buf);
101 gzip.count += buf.len;
102 },
103 .zlib => |*zlib| {
104 zlib.update(buf);
105 },
106 inline .gzip, .zlib => |*x| x.update(buf),
107 }
108 }
52109
53 pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) {110 pub fn writeFooter(hasher: *Hasher, writer: *Writer) Writer.Error!void {
54 return deflate.huffman.compressor(.raw, writer);111 var bits: [4]u8 = undefined;
55 }112 switch (hasher.*) {
113 .gzip => |*gzip| {
114 // GZIP 8 bytes footer
115 // - 4 bytes, CRC32 (CRC-32)
116 // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32
117 std.mem.writeInt(u32, &bits, gzip.final(), .little);
118 try writer.writeAll(&bits);
119
120 std.mem.writeInt(u32, &bits, gzip.bytes_read, .little);
121 try writer.writeAll(&bits);
122 },
123 .zlib => |*zlib| {
124 // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
125 // 4 bytes of ADLER32 (Adler-32 checksum)
126 // Checksum value of the uncompressed data (excluding any
127 // dictionary data) computed according to Adler-32
128 // algorithm.
129 std.mem.writeInt(u32, &bits, zlib.final, .big);
130 try writer.writeAll(&bits);
131 },
132 .raw => {},
133 }
134 }
135 };
56};136};
57137
58// No compression store only. Compressed size is slightly bigger than plain.138/// When decompressing, the output buffer is used as the history window, so
59pub const store = struct {139/// less than this may result in failure to decompress streams that were
60 pub fn compress(reader: anytype, writer: anytype) !void {140/// compressed with a larger window.
61 try deflate.store.compress(.raw, reader, writer);141pub const max_window_len = 1 << 16;
62 }
63142
64 pub fn Compressor(comptime WriterType: type) type {143/// Deflate is a lossless data compression file format that uses a combination
65 return deflate.store.Compressor(.raw, WriterType);144/// of LZ77 and Huffman coding.
66 }145pub const Compress = @import("flate/Compress.zig");
67146
68 pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) {147/// Inflate is the decoding process that takes a Deflate bitstream for
69 return deflate.store.compressor(.raw, writer);148/// decompression and correctly produces the original full-size data or file.
70 }149pub const Decompress = @import("flate/Decompress.zig");
71};
72150
73/// Container defines header/footer around deflate bit stream. Gzip and zlib151/// Huffman only compression. Without Lempel-Ziv match searching. Faster
74/// compression algorithms are containers around deflate bit stream body.152/// compression, less memory requirements but bigger compressed sizes.
75const Container = @import("flate/container.zig").Container;153pub const huffman = struct {
76const std = @import("std");154 // The odd order in which the codegen code sizes are written.
77const testing = std.testing;155 pub const codegen_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
78const fixedBufferStream = std.io.fixedBufferStream;156 // The number of codegen codes.
79const print = std.debug.print;157 pub const codegen_code_count = 19;
80const builtin = @import("builtin");158
159 // The largest distance code.
160 pub const distance_code_count = 30;
161
162 // Maximum number of literals.
163 pub const max_num_lit = 286;
164
165 // Max number of frequencies used for a Huffman Code
166 // Possible lengths are codegen_code_count (19), distance_code_count (30) and max_num_lit (286).
167 // The largest of these is max_num_lit.
168 pub const max_num_frequencies = max_num_lit;
169
170 // Biggest block size for uncompressed block.
171 pub const max_store_block_size = 65535;
172 // The special code used to mark the end of a block.
173 pub const end_block_marker = 256;
174};
81175
82test {176test {
83 _ = deflate;177 _ = Compress;
84 _ = inflate;178 _ = Decompress;
85}179}
86180
87test "compress/decompress" {181test "compress/decompress" {
182 const print = std.debug.print;
88 var cmp_buf: [64 * 1024]u8 = undefined; // compressed data buffer183 var cmp_buf: [64 * 1024]u8 = undefined; // compressed data buffer
89 var dcm_buf: [64 * 1024]u8 = undefined; // decompressed data buffer184 var dcm_buf: [64 * 1024]u8 = undefined; // decompressed data buffer
90185
91 const levels = [_]deflate.Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 };186 const levels = [_]Compress.Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 };
92 const cases = [_]struct {187 const cases = [_]struct {
93 data: []const u8, // uncompressed content188 data: []const u8, // uncompressed content
94 // compressed data sizes per level 4-9189 // compressed data sizes per level 4-9
...@@ -135,28 +230,34 @@ test "compress/decompress" {...@@ -135,28 +230,34 @@ test "compress/decompress" {
135230
136 // compress original stream to compressed stream231 // compress original stream to compressed stream
137 {232 {
138 var original = fixedBufferStream(data);233 var original: std.io.Reader = .fixed(data);
139 var compressed = fixedBufferStream(&cmp_buf);234 var compressed: Writer = .fixed(&cmp_buf);
140 try deflate.compress(container, original.reader(), compressed.writer(), .{ .level = level });235 var compress: Compress = .init(&original, &.{}, .{ .container = .raw, .level = level });
236 const n = try compress.reader.streamRemaining(&compressed);
141 if (compressed_size == 0) {237 if (compressed_size == 0) {
142 if (container == .gzip)238 if (container == .gzip)
143 print("case {d} gzip level {} compressed size: {d}\n", .{ case_no, level, compressed.pos });239 print("case {d} gzip level {} compressed size: {d}\n", .{ case_no, level, compressed.pos });
144 compressed_size = compressed.pos;240 compressed_size = compressed.end;
145 }241 }
146 try testing.expectEqual(compressed_size, compressed.pos);242 try testing.expectEqual(compressed_size, n);
243 try testing.expectEqual(compressed_size, compressed.end);
147 }244 }
148 // decompress compressed stream to decompressed stream245 // decompress compressed stream to decompressed stream
149 {246 {
150 var compressed = fixedBufferStream(cmp_buf[0..compressed_size]);247 var compressed: std.io.Reader = .fixed(cmp_buf[0..compressed_size]);
151 var decompressed = fixedBufferStream(&dcm_buf);248 var decompressed: Writer = .fixed(&dcm_buf);
152 try inflate.decompress(container, compressed.reader(), decompressed.writer());249 var decompress: Decompress = .init(&compressed, container, &.{});
153 try testing.expectEqualSlices(u8, data, decompressed.getWritten());250 _ = try decompress.reader.streamRemaining(&decompressed);
251 try testing.expectEqualSlices(u8, data, decompressed.buffered());
154 }252 }
155253
156 // compressor writer interface254 // compressor writer interface
157 {255 {
158 var compressed = fixedBufferStream(&cmp_buf);256 var compressed: Writer = .fixed(&cmp_buf);
159 var cmp = try deflate.compressor(container, compressed.writer(), .{ .level = level });257 var cmp = try Compress.init(&compressed, &.{}, .{
258 .level = level,
259 .container = container,
260 });
160 var cmp_wrt = cmp.writer();261 var cmp_wrt = cmp.writer();
161 try cmp_wrt.writeAll(data);262 try cmp_wrt.writeAll(data);
162 try cmp.finish();263 try cmp.finish();
...@@ -165,10 +266,9 @@ test "compress/decompress" {...@@ -165,10 +266,9 @@ test "compress/decompress" {
165 }266 }
166 // decompressor reader interface267 // decompressor reader interface
167 {268 {
168 var compressed = fixedBufferStream(cmp_buf[0..compressed_size]);269 var compressed: std.io.Reader = .fixed(cmp_buf[0..compressed_size]);
169 var dcm = inflate.decompressor(container, compressed.reader());270 var decompress: Decompress = .init(&compressed, container, &.{});
170 var dcm_rdr = dcm.reader();271 const n = try decompress.reader.readSliceShort(&dcm_buf);
171 const n = try dcm_rdr.readAll(&dcm_buf);
172 try testing.expectEqual(data.len, n);272 try testing.expectEqual(data.len, n);
173 try testing.expectEqualSlices(u8, data, dcm_buf[0..n]);273 try testing.expectEqualSlices(u8, data, dcm_buf[0..n]);
174 }274 }
...@@ -184,9 +284,9 @@ test "compress/decompress" {...@@ -184,9 +284,9 @@ test "compress/decompress" {
184284
185 // compress original stream to compressed stream285 // compress original stream to compressed stream
186 {286 {
187 var original = fixedBufferStream(data);287 var original: std.io.Reader = .fixed(data);
188 var compressed = fixedBufferStream(&cmp_buf);288 var compressed: Writer = .fixed(&cmp_buf);
189 var cmp = try deflate.huffman.compressor(container, compressed.writer());289 var cmp = try Compress.Huffman.init(container, &compressed);
190 try cmp.compress(original.reader());290 try cmp.compress(original.reader());
191 try cmp.finish();291 try cmp.finish();
192 if (compressed_size == 0) {292 if (compressed_size == 0) {
...@@ -198,10 +298,11 @@ test "compress/decompress" {...@@ -198,10 +298,11 @@ test "compress/decompress" {
198 }298 }
199 // decompress compressed stream to decompressed stream299 // decompress compressed stream to decompressed stream
200 {300 {
201 var compressed = fixedBufferStream(cmp_buf[0..compressed_size]);301 var compressed: std.io.Reader = .fixed(cmp_buf[0..compressed_size]);
202 var decompressed = fixedBufferStream(&dcm_buf);302 var decompress: Decompress = .init(&compressed, container, &.{});
203 try inflate.decompress(container, compressed.reader(), decompressed.writer());303 var decompressed: Writer = .fixed(&dcm_buf);
204 try testing.expectEqualSlices(u8, data, decompressed.getWritten());304 _ = try decompress.reader.streamRemaining(&decompressed);
305 try testing.expectEqualSlices(u8, data, decompressed.buffered());
205 }306 }
206 }307 }
207 }308 }
...@@ -216,9 +317,9 @@ test "compress/decompress" {...@@ -216,9 +317,9 @@ test "compress/decompress" {
216317
217 // compress original stream to compressed stream318 // compress original stream to compressed stream
218 {319 {
219 var original = fixedBufferStream(data);320 var original: std.io.Reader = .fixed(data);
220 var compressed = fixedBufferStream(&cmp_buf);321 var compressed: Writer = .fixed(&cmp_buf);
221 var cmp = try deflate.store.compressor(container, compressed.writer());322 var cmp = try Compress.SimpleCompressor(.store, container).init(&compressed);
222 try cmp.compress(original.reader());323 try cmp.compress(original.reader());
223 try cmp.finish();324 try cmp.finish();
224 if (compressed_size == 0) {325 if (compressed_size == 0) {
...@@ -231,23 +332,25 @@ test "compress/decompress" {...@@ -231,23 +332,25 @@ test "compress/decompress" {
231 }332 }
232 // decompress compressed stream to decompressed stream333 // decompress compressed stream to decompressed stream
233 {334 {
234 var compressed = fixedBufferStream(cmp_buf[0..compressed_size]);335 var compressed: std.io.Reader = .fixed(cmp_buf[0..compressed_size]);
235 var decompressed = fixedBufferStream(&dcm_buf);336 var decompress: Decompress = .init(&compressed, container, &.{});
236 try inflate.decompress(container, compressed.reader(), decompressed.writer());337 var decompressed: Writer = .fixed(&dcm_buf);
237 try testing.expectEqualSlices(u8, data, decompressed.getWritten());338 _ = try decompress.reader.streamRemaining(&decompressed);
339 try testing.expectEqualSlices(u8, data, decompressed.buffered());
238 }340 }
239 }341 }
240 }342 }
241 }343 }
242}344}
243345
244fn testDecompress(comptime container: Container, compressed: []const u8, expected_plain: []const u8) !void {346fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void {
245 var in = fixedBufferStream(compressed);347 var in: std.io.Reader = .fixed(compressed);
246 var out = std.ArrayList(u8).init(testing.allocator);348 var aw: std.io.Writer.Allocating = .init(testing.allocator);
247 defer out.deinit();349 defer aw.deinit();
248350
249 try inflate.decompress(container, in.reader(), out.writer());351 var decompress: Decompress = .init(&in, container, &.{});
250 try testing.expectEqualSlices(u8, expected_plain, out.items);352 _ = try decompress.reader.streamRemaining(&aw.writer);
353 try testing.expectEqualSlices(u8, expected_plain, aw.items);
251}354}
252355
253test "don't read past deflate stream's end" {356test "don't read past deflate stream's end" {
...@@ -352,126 +455,186 @@ test "gzip header" {...@@ -352,126 +455,186 @@ test "gzip header" {
352}455}
353456
354test "public interface" {457test "public interface" {
355 const plain_data = [_]u8{ 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a };458 const plain_data_buf = [_]u8{ 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a };
356459
357 // deflate final stored block, header + plain (stored) data460 // deflate final stored block, header + plain (stored) data
358 const deflate_block = [_]u8{461 const deflate_block = [_]u8{
359 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen462 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
360 } ++ plain_data;463 } ++ plain_data_buf;
361464
362 // gzip header/footer + deflate block465 const plain_data: []const u8 = &plain_data_buf;
363 const gzip_data =466 const gzip_data: []const u8 = &deflate_block;
364 [_]u8{ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } ++ // gzip header (10 bytes)467
365 deflate_block ++468 //// gzip header/footer + deflate block
366 [_]u8{ 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00 }; // gzip footer checksum (4 byte), size (4 bytes)469 //const gzip_data =
367470 // [_]u8{ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } ++ // gzip header (10 bytes)
368 // zlib header/footer + deflate block471 // deflate_block ++
369 const zlib_data = [_]u8{ 0x78, 0b10_0_11100 } ++ // zlib header (2 bytes)}472 // [_]u8{ 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00 }; // gzip footer checksum (4 byte), size (4 bytes)
370 deflate_block ++473
371 [_]u8{ 0x1c, 0xf2, 0x04, 0x47 }; // zlib footer: checksum474 //// zlib header/footer + deflate block
372475 //const zlib_data = [_]u8{ 0x78, 0b10_0_11100 } ++ // zlib header (2 bytes)}
373 const gzip = @import("gzip.zig");476 // deflate_block ++
374 const zlib = @import("zlib.zig");477 // [_]u8{ 0x1c, 0xf2, 0x04, 0x47 }; // zlib footer: checksum
375 const flate = @This();478
376479 // TODO
377 try testInterface(gzip, &gzip_data, &plain_data);480 //const gzip = @import("gzip.zig");
378 try testInterface(zlib, &zlib_data, &plain_data);481 //const zlib = @import("zlib.zig");
379 try testInterface(flate, &deflate_block, &plain_data);
380}
381482
382fn testInterface(comptime pkg: type, gzip_data: []const u8, plain_data: []const u8) !void {
383 var buffer1: [64]u8 = undefined;483 var buffer1: [64]u8 = undefined;
384 var buffer2: [64]u8 = undefined;484 var buffer2: [64]u8 = undefined;
385485
386 var compressed = fixedBufferStream(&buffer1);486 // TODO These used to be functions, need to migrate the tests
387 var plain = fixedBufferStream(&buffer2);487 const decompress = void;
488 const compress = void;
489 const store = void;
388490
389 // decompress491 // decompress
390 {492 {
391 var in = fixedBufferStream(gzip_data);493 var plain: Writer = .fixed(&buffer2);
392 try pkg.decompress(in.reader(), plain.writer());494
393 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());495 var in: std.io.Reader = .fixed(gzip_data);
496 try decompress(&in, &plain);
497 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
394 }498 }
395 plain.reset();
396 compressed.reset();
397499
398 // compress/decompress500 // compress/decompress
399 {501 {
400 var in = fixedBufferStream(plain_data);502 var plain: Writer = .fixed(&buffer2);
401 try pkg.compress(in.reader(), compressed.writer(), .{});503 var compressed: Writer = .fixed(&buffer1);
402 compressed.reset();504
403 try pkg.decompress(compressed.reader(), plain.writer());505 var in: std.io.Reader = .fixed(plain_data);
404 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());506 try compress(&in, &compressed, .{});
507
508 var r: std.io.Reader = .fixed(&buffer1);
509 try decompress(&r, &plain);
510 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
405 }511 }
406 plain.reset();
407 compressed.reset();
408512
409 // compressor/decompressor513 // compressor/decompressor
410 {514 {
411 var in = fixedBufferStream(plain_data);515 var plain: Writer = .fixed(&buffer2);
412 var cmp = try pkg.compressor(compressed.writer(), .{});516 var compressed: Writer = .fixed(&buffer1);
413 try cmp.compress(in.reader());517
518 var in: std.io.Reader = .fixed(plain_data);
519 var cmp = try Compress(&compressed, .{});
520 try cmp.compress(&in);
414 try cmp.finish();521 try cmp.finish();
415522
416 compressed.reset();523 var r: std.io.Reader = .fixed(&buffer1);
417 var dcp = pkg.decompressor(compressed.reader());524 var dcp = Decompress(&r);
418 try dcp.decompress(plain.writer());525 try dcp.decompress(&plain);
419 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());526 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
420 }527 }
421 plain.reset();
422 compressed.reset();
423528
424 // huffman529 // huffman
425 {530 {
426 // huffman compress/decompress531 // huffman compress/decompress
427 {532 {
428 var in = fixedBufferStream(plain_data);533 var plain: Writer = .fixed(&buffer2);
429 try pkg.huffman.compress(in.reader(), compressed.writer());534 var compressed: Writer = .fixed(&buffer1);
430 compressed.reset();535
431 try pkg.decompress(compressed.reader(), plain.writer());536 var in: std.io.Reader = .fixed(plain_data);
432 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());537 try huffman.compress(&in, &compressed);
538
539 var r: std.io.Reader = .fixed(&buffer1);
540 try decompress(&r, &plain);
541 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
433 }542 }
434 plain.reset();
435 compressed.reset();
436543
437 // huffman compressor/decompressor544 // huffman compressor/decompressor
438 {545 {
439 var in = fixedBufferStream(plain_data);546 var plain: Writer = .fixed(&buffer2);
440 var cmp = try pkg.huffman.compressor(compressed.writer());547 var compressed: Writer = .fixed(&buffer1);
441 try cmp.compress(in.reader());548
549 var in: std.io.Reader = .fixed(plain_data);
550 var cmp = try huffman.Compressor(&compressed);
551 try cmp.compress(&in);
442 try cmp.finish();552 try cmp.finish();
443553
444 compressed.reset();554 var r: std.io.Reader = .fixed(&buffer1);
445 try pkg.decompress(compressed.reader(), plain.writer());555 try decompress(&r, &plain);
446 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());556 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
447 }557 }
448 }558 }
449 plain.reset();
450 compressed.reset();
451559
452 // store560 // store
453 {561 {
454 // store compress/decompress562 // store compress/decompress
455 {563 {
456 var in = fixedBufferStream(plain_data);564 var plain: Writer = .fixed(&buffer2);
457 try pkg.store.compress(in.reader(), compressed.writer());565 var compressed: Writer = .fixed(&buffer1);
458 compressed.reset();566
459 try pkg.decompress(compressed.reader(), plain.writer());567 var in: std.io.Reader = .fixed(plain_data);
460 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());568 try store.compress(&in, &compressed);
569
570 var r: std.io.Reader = .fixed(&buffer1);
571 try decompress(&r, &plain);
572 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
461 }573 }
462 plain.reset();
463 compressed.reset();
464574
465 // store compressor/decompressor575 // store compressor/decompressor
466 {576 {
467 var in = fixedBufferStream(plain_data);577 var plain: Writer = .fixed(&buffer2);
468 var cmp = try pkg.store.compressor(compressed.writer());578 var compressed: Writer = .fixed(&buffer1);
469 try cmp.compress(in.reader());579
580 var in: std.io.Reader = .fixed(plain_data);
581 var cmp = try store.compressor(&compressed);
582 try cmp.compress(&in);
470 try cmp.finish();583 try cmp.finish();
471584
472 compressed.reset();585 var r: std.io.Reader = .fixed(&buffer1);
473 try pkg.decompress(compressed.reader(), plain.writer());586 try decompress(&r, &plain);
474 try testing.expectEqualSlices(u8, plain_data, plain.getWritten());587 try testing.expectEqualSlices(u8, plain_data, plain.buffered());
475 }588 }
476 }589 }
477}590}
591
592pub const match = struct {
593 pub const base_length = 3; // smallest match length per the RFC section 3.2.5
594 pub const min_length = 4; // min length used in this algorithm
595 pub const max_length = 258;
596
597 pub const min_distance = 1;
598 pub const max_distance = 32768;
599};
600
601pub const history_len = match.max_distance;
602
603pub const lookup = struct {
604 pub const bits = 15;
605 pub const len = 1 << bits;
606 pub const shift = 32 - bits;
607};
608
609test "zlib should not overshoot" {
610 // Compressed zlib data with extra 4 bytes at the end.
611 const data = [_]u8{
612 0x78, 0x9c, 0x73, 0xce, 0x2f, 0xa8, 0x2c, 0xca, 0x4c, 0xcf, 0x28, 0x51, 0x08, 0xcf, 0xcc, 0xc9,
613 0x49, 0xcd, 0x55, 0x28, 0x4b, 0xcc, 0x53, 0x08, 0x4e, 0xce, 0x48, 0xcc, 0xcc, 0xd6, 0x51, 0x08,
614 0xce, 0xcc, 0x4b, 0x4f, 0x2c, 0xc8, 0x2f, 0x4a, 0x55, 0x30, 0xb4, 0xb4, 0x34, 0xd5, 0xb5, 0x34,
615 0x03, 0x00, 0x8b, 0x61, 0x0f, 0xa4, 0x52, 0x5a, 0x94, 0x12,
616 };
617
618 var stream: std.io.Reader = .fixed(&data);
619 const reader = stream.reader();
620
621 var dcp = Decompress.init(reader);
622 var out: [128]u8 = undefined;
623
624 // Decompress
625 var n = try dcp.reader().readAll(out[0..]);
626
627 // Expected decompressed data
628 try std.testing.expectEqual(46, n);
629 try std.testing.expectEqualStrings("Copyright Willem van Schaik, Singapore 1995-96", out[0..n]);
630
631 // Decompressor don't overshoot underlying reader.
632 // It is leaving it at the end of compressed data chunk.
633 try std.testing.expectEqual(data.len - 4, stream.getPos());
634 try std.testing.expectEqual(0, dcp.unreadBytes());
635
636 // 4 bytes after compressed chunk are available in reader.
637 n = try reader.readAll(out[0..]);
638 try std.testing.expectEqual(n, 4);
639 try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]);
640}
lib/std/compress/flate/BlockWriter.zig created+696
...@@ -0,0 +1,696 @@
1//! Accepts list of tokens, decides what is best block type to write. What block
2//! type will provide best compression. Writes header and body of the block.
3const std = @import("std");
4const io = std.io;
5const assert = std.debug.assert;
6const Writer = std.io.Writer;
7
8const BlockWriter = @This();
9const flate = @import("../flate.zig");
10const Compress = flate.Compress;
11const huffman = flate.huffman;
12const Token = @import("Token.zig");
13
14const codegen_order = huffman.codegen_order;
15const end_code_mark = 255;
16
17output: *Writer,
18
19codegen_freq: [huffman.codegen_code_count]u16 = undefined,
20literal_freq: [huffman.max_num_lit]u16 = undefined,
21distance_freq: [huffman.distance_code_count]u16 = undefined,
22codegen: [huffman.max_num_lit + huffman.distance_code_count + 1]u8 = undefined,
23literal_encoding: Compress.LiteralEncoder = .{},
24distance_encoding: Compress.DistanceEncoder = .{},
25codegen_encoding: Compress.CodegenEncoder = .{},
26fixed_literal_encoding: Compress.LiteralEncoder,
27fixed_distance_encoding: Compress.DistanceEncoder,
28huff_distance: Compress.DistanceEncoder,
29
30pub fn init(output: *Writer) BlockWriter {
31 return .{
32 .output = output,
33 .fixed_literal_encoding = Compress.fixedLiteralEncoder(),
34 .fixed_distance_encoding = Compress.fixedDistanceEncoder(),
35 .huff_distance = Compress.huffmanDistanceEncoder(),
36 };
37}
38
39/// Flush intrenal bit buffer to the writer.
40/// Should be called only when bit stream is at byte boundary.
41///
42/// That is after final block; when last byte could be incomplete or
43/// after stored block; which is aligned to the byte boundary (it has x
44/// padding bits after first 3 bits).
45pub fn flush(self: *BlockWriter) Writer.Error!void {
46 try self.bit_writer.flush();
47}
48
49pub fn setWriter(self: *BlockWriter, new_writer: *Writer) void {
50 self.bit_writer.setWriter(new_writer);
51}
52
53fn writeCode(self: *BlockWriter, c: Compress.HuffCode) Writer.Error!void {
54 try self.bit_writer.writeBits(c.code, c.len);
55}
56
57// RFC 1951 3.2.7 specifies a special run-length encoding for specifying
58// the literal and distance lengths arrays (which are concatenated into a single
59// array). This method generates that run-length encoding.
60//
61// The result is written into the codegen array, and the frequencies
62// of each code is written into the codegen_freq array.
63// Codes 0-15 are single byte codes. Codes 16-18 are followed by additional
64// information. Code bad_code is an end marker
65//
66// num_literals: The number of literals in literal_encoding
67// num_distances: The number of distances in distance_encoding
68// lit_enc: The literal encoder to use
69// dist_enc: The distance encoder to use
70fn generateCodegen(
71 self: *BlockWriter,
72 num_literals: u32,
73 num_distances: u32,
74 lit_enc: *Compress.LiteralEncoder,
75 dist_enc: *Compress.DistanceEncoder,
76) void {
77 for (self.codegen_freq, 0..) |_, i| {
78 self.codegen_freq[i] = 0;
79 }
80
81 // Note that we are using codegen both as a temporary variable for holding
82 // a copy of the frequencies, and as the place where we put the result.
83 // This is fine because the output is always shorter than the input used
84 // so far.
85 var codegen = &self.codegen; // cache
86 // Copy the concatenated code sizes to codegen. Put a marker at the end.
87 var cgnl = codegen[0..num_literals];
88 for (cgnl, 0..) |_, i| {
89 cgnl[i] = @as(u8, @intCast(lit_enc.codes[i].len));
90 }
91
92 cgnl = codegen[num_literals .. num_literals + num_distances];
93 for (cgnl, 0..) |_, i| {
94 cgnl[i] = @as(u8, @intCast(dist_enc.codes[i].len));
95 }
96 codegen[num_literals + num_distances] = end_code_mark;
97
98 var size = codegen[0];
99 var count: i32 = 1;
100 var out_index: u32 = 0;
101 var in_index: u32 = 1;
102 while (size != end_code_mark) : (in_index += 1) {
103 // INVARIANT: We have seen "count" copies of size that have not yet
104 // had output generated for them.
105 const next_size = codegen[in_index];
106 if (next_size == size) {
107 count += 1;
108 continue;
109 }
110 // We need to generate codegen indicating "count" of size.
111 if (size != 0) {
112 codegen[out_index] = size;
113 out_index += 1;
114 self.codegen_freq[size] += 1;
115 count -= 1;
116 while (count >= 3) {
117 var n: i32 = 6;
118 if (n > count) {
119 n = count;
120 }
121 codegen[out_index] = 16;
122 out_index += 1;
123 codegen[out_index] = @as(u8, @intCast(n - 3));
124 out_index += 1;
125 self.codegen_freq[16] += 1;
126 count -= n;
127 }
128 } else {
129 while (count >= 11) {
130 var n: i32 = 138;
131 if (n > count) {
132 n = count;
133 }
134 codegen[out_index] = 18;
135 out_index += 1;
136 codegen[out_index] = @as(u8, @intCast(n - 11));
137 out_index += 1;
138 self.codegen_freq[18] += 1;
139 count -= n;
140 }
141 if (count >= 3) {
142 // 3 <= count <= 10
143 codegen[out_index] = 17;
144 out_index += 1;
145 codegen[out_index] = @as(u8, @intCast(count - 3));
146 out_index += 1;
147 self.codegen_freq[17] += 1;
148 count = 0;
149 }
150 }
151 count -= 1;
152 while (count >= 0) : (count -= 1) {
153 codegen[out_index] = size;
154 out_index += 1;
155 self.codegen_freq[size] += 1;
156 }
157 // Set up invariant for next time through the loop.
158 size = next_size;
159 count = 1;
160 }
161 // Marker indicating the end of the codegen.
162 codegen[out_index] = end_code_mark;
163}
164
165const DynamicSize = struct {
166 size: u32,
167 num_codegens: u32,
168};
169
170// dynamicSize returns the size of dynamically encoded data in bits.
171fn dynamicSize(
172 self: *BlockWriter,
173 lit_enc: *Compress.LiteralEncoder, // literal encoder
174 dist_enc: *Compress.DistanceEncoder, // distance encoder
175 extra_bits: u32,
176) DynamicSize {
177 var num_codegens = self.codegen_freq.len;
178 while (num_codegens > 4 and self.codegen_freq[codegen_order[num_codegens - 1]] == 0) {
179 num_codegens -= 1;
180 }
181 const header = 3 + 5 + 5 + 4 + (3 * num_codegens) +
182 self.codegen_encoding.bitLength(self.codegen_freq[0..]) +
183 self.codegen_freq[16] * 2 +
184 self.codegen_freq[17] * 3 +
185 self.codegen_freq[18] * 7;
186 const size = header +
187 lit_enc.bitLength(&self.literal_freq) +
188 dist_enc.bitLength(&self.distance_freq) +
189 extra_bits;
190
191 return DynamicSize{
192 .size = @as(u32, @intCast(size)),
193 .num_codegens = @as(u32, @intCast(num_codegens)),
194 };
195}
196
197// fixedSize returns the size of dynamically encoded data in bits.
198fn fixedSize(self: *BlockWriter, extra_bits: u32) u32 {
199 return 3 +
200 self.fixed_literal_encoding.bitLength(&self.literal_freq) +
201 self.fixed_distance_encoding.bitLength(&self.distance_freq) +
202 extra_bits;
203}
204
205const StoredSize = struct {
206 size: u32,
207 storable: bool,
208};
209
210// storedSizeFits calculates the stored size, including header.
211// The function returns the size in bits and whether the block
212// fits inside a single block.
213fn storedSizeFits(in: ?[]const u8) StoredSize {
214 if (in == null) {
215 return .{ .size = 0, .storable = false };
216 }
217 if (in.?.len <= huffman.max_store_block_size) {
218 return .{ .size = @as(u32, @intCast((in.?.len + 5) * 8)), .storable = true };
219 }
220 return .{ .size = 0, .storable = false };
221}
222
223// Write the header of a dynamic Huffman block to the output stream.
224//
225// num_literals: The number of literals specified in codegen
226// num_distances: The number of distances specified in codegen
227// num_codegens: The number of codegens used in codegen
228// eof: Is it the end-of-file? (end of stream)
229fn dynamicHeader(
230 self: *BlockWriter,
231 num_literals: u32,
232 num_distances: u32,
233 num_codegens: u32,
234 eof: bool,
235) Writer.Error!void {
236 const first_bits: u32 = if (eof) 5 else 4;
237 try self.bit_writer.writeBits(first_bits, 3);
238 try self.bit_writer.writeBits(num_literals - 257, 5);
239 try self.bit_writer.writeBits(num_distances - 1, 5);
240 try self.bit_writer.writeBits(num_codegens - 4, 4);
241
242 var i: u32 = 0;
243 while (i < num_codegens) : (i += 1) {
244 const value = self.codegen_encoding.codes[codegen_order[i]].len;
245 try self.bit_writer.writeBits(value, 3);
246 }
247
248 i = 0;
249 while (true) {
250 const code_word: u32 = @as(u32, @intCast(self.codegen[i]));
251 i += 1;
252 if (code_word == end_code_mark) {
253 break;
254 }
255 try self.writeCode(self.codegen_encoding.codes[@as(u32, @intCast(code_word))]);
256
257 switch (code_word) {
258 16 => {
259 try self.bit_writer.writeBits(self.codegen[i], 2);
260 i += 1;
261 },
262 17 => {
263 try self.bit_writer.writeBits(self.codegen[i], 3);
264 i += 1;
265 },
266 18 => {
267 try self.bit_writer.writeBits(self.codegen[i], 7);
268 i += 1;
269 },
270 else => {},
271 }
272 }
273}
274
275fn storedHeader(self: *BlockWriter, length: usize, eof: bool) Writer.Error!void {
276 assert(length <= 65535);
277 const flag: u32 = if (eof) 1 else 0;
278 try self.bit_writer.writeBits(flag, 3);
279 try self.flush();
280 const l: u16 = @intCast(length);
281 try self.bit_writer.writeBits(l, 16);
282 try self.bit_writer.writeBits(~l, 16);
283}
284
285fn fixedHeader(self: *BlockWriter, eof: bool) Writer.Error!void {
286 // Indicate that we are a fixed Huffman block
287 var value: u32 = 2;
288 if (eof) {
289 value = 3;
290 }
291 try self.bit_writer.writeBits(value, 3);
292}
293
294// Write a block of tokens with the smallest encoding. Will choose block type.
295// The original input can be supplied, and if the huffman encoded data
296// is larger than the original bytes, the data will be written as a
297// stored block.
298// If the input is null, the tokens will always be Huffman encoded.
299pub fn write(self: *BlockWriter, tokens: []const Token, eof: bool, input: ?[]const u8) Writer.Error!void {
300 const lit_and_dist = self.indexTokens(tokens);
301 const num_literals = lit_and_dist.num_literals;
302 const num_distances = lit_and_dist.num_distances;
303
304 var extra_bits: u32 = 0;
305 const ret = storedSizeFits(input);
306 const stored_size = ret.size;
307 const storable = ret.storable;
308
309 if (storable) {
310 // We only bother calculating the costs of the extra bits required by
311 // the length of distance fields (which will be the same for both fixed
312 // and dynamic encoding), if we need to compare those two encodings
313 // against stored encoding.
314 var length_code: u16 = Token.length_codes_start + 8;
315 while (length_code < num_literals) : (length_code += 1) {
316 // First eight length codes have extra size = 0.
317 extra_bits += @as(u32, @intCast(self.literal_freq[length_code])) *
318 @as(u32, @intCast(Token.lengthExtraBits(length_code)));
319 }
320 var distance_code: u16 = 4;
321 while (distance_code < num_distances) : (distance_code += 1) {
322 // First four distance codes have extra size = 0.
323 extra_bits += @as(u32, @intCast(self.distance_freq[distance_code])) *
324 @as(u32, @intCast(Token.distanceExtraBits(distance_code)));
325 }
326 }
327
328 // Figure out smallest code.
329 // Fixed Huffman baseline.
330 var literal_encoding = &self.fixed_literal_encoding;
331 var distance_encoding = &self.fixed_distance_encoding;
332 var size = self.fixedSize(extra_bits);
333
334 // Dynamic Huffman?
335 var num_codegens: u32 = 0;
336
337 // Generate codegen and codegenFrequencies, which indicates how to encode
338 // the literal_encoding and the distance_encoding.
339 self.generateCodegen(
340 num_literals,
341 num_distances,
342 &self.literal_encoding,
343 &self.distance_encoding,
344 );
345 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
346 const dynamic_size = self.dynamicSize(
347 &self.literal_encoding,
348 &self.distance_encoding,
349 extra_bits,
350 );
351 const dyn_size = dynamic_size.size;
352 num_codegens = dynamic_size.num_codegens;
353
354 if (dyn_size < size) {
355 size = dyn_size;
356 literal_encoding = &self.literal_encoding;
357 distance_encoding = &self.distance_encoding;
358 }
359
360 // Stored bytes?
361 if (storable and stored_size < size) {
362 try self.storedBlock(input.?, eof);
363 return;
364 }
365
366 // Huffman.
367 if (@intFromPtr(literal_encoding) == @intFromPtr(&self.fixed_literal_encoding)) {
368 try self.fixedHeader(eof);
369 } else {
370 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
371 }
372
373 // Write the tokens.
374 try self.writeTokens(tokens, &literal_encoding.codes, &distance_encoding.codes);
375}
376
377pub fn storedBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void {
378 try self.storedHeader(input.len, eof);
379 try self.bit_writer.writeBytes(input);
380}
381
382// writeBlockDynamic encodes a block using a dynamic Huffman table.
383// This should be used if the symbols used have a disproportionate
384// histogram distribution.
385// If input is supplied and the compression savings are below 1/16th of the
386// input size the block is stored.
387fn dynamicBlock(
388 self: *BlockWriter,
389 tokens: []const Token,
390 eof: bool,
391 input: ?[]const u8,
392) Writer.Error!void {
393 const total_tokens = self.indexTokens(tokens);
394 const num_literals = total_tokens.num_literals;
395 const num_distances = total_tokens.num_distances;
396
397 // Generate codegen and codegenFrequencies, which indicates how to encode
398 // the literal_encoding and the distance_encoding.
399 self.generateCodegen(
400 num_literals,
401 num_distances,
402 &self.literal_encoding,
403 &self.distance_encoding,
404 );
405 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
406 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.distance_encoding, 0);
407 const size = dynamic_size.size;
408 const num_codegens = dynamic_size.num_codegens;
409
410 // Store bytes, if we don't get a reasonable improvement.
411
412 const stored_size = storedSizeFits(input);
413 const ssize = stored_size.size;
414 const storable = stored_size.storable;
415 if (storable and ssize < (size + (size >> 4))) {
416 try self.storedBlock(input.?, eof);
417 return;
418 }
419
420 // Write Huffman table.
421 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
422
423 // Write the tokens.
424 try self.writeTokens(tokens, &self.literal_encoding.codes, &self.distance_encoding.codes);
425}
426
427const TotalIndexedTokens = struct {
428 num_literals: u32,
429 num_distances: u32,
430};
431
432// Indexes a slice of tokens followed by an end_block_marker, and updates
433// literal_freq and distance_freq, and generates literal_encoding
434// and distance_encoding.
435// The number of literal and distance tokens is returned.
436fn indexTokens(self: *BlockWriter, tokens: []const Token) TotalIndexedTokens {
437 var num_literals: u32 = 0;
438 var num_distances: u32 = 0;
439
440 for (self.literal_freq, 0..) |_, i| {
441 self.literal_freq[i] = 0;
442 }
443 for (self.distance_freq, 0..) |_, i| {
444 self.distance_freq[i] = 0;
445 }
446
447 for (tokens) |t| {
448 if (t.kind == Token.Kind.literal) {
449 self.literal_freq[t.literal()] += 1;
450 continue;
451 }
452 self.literal_freq[t.lengthCode()] += 1;
453 self.distance_freq[t.distanceCode()] += 1;
454 }
455 // add end_block_marker token at the end
456 self.literal_freq[huffman.end_block_marker] += 1;
457
458 // get the number of literals
459 num_literals = @as(u32, @intCast(self.literal_freq.len));
460 while (self.literal_freq[num_literals - 1] == 0) {
461 num_literals -= 1;
462 }
463 // get the number of distances
464 num_distances = @as(u32, @intCast(self.distance_freq.len));
465 while (num_distances > 0 and self.distance_freq[num_distances - 1] == 0) {
466 num_distances -= 1;
467 }
468 if (num_distances == 0) {
469 // We haven't found a single match. If we want to go with the dynamic encoding,
470 // we should count at least one distance to be sure that the distance huffman tree could be encoded.
471 self.distance_freq[0] = 1;
472 num_distances = 1;
473 }
474 self.literal_encoding.generate(&self.literal_freq, 15);
475 self.distance_encoding.generate(&self.distance_freq, 15);
476 return TotalIndexedTokens{
477 .num_literals = num_literals,
478 .num_distances = num_distances,
479 };
480}
481
482// Writes a slice of tokens to the output followed by and end_block_marker.
483// codes for literal and distance encoding must be supplied.
484fn writeTokens(
485 self: *BlockWriter,
486 tokens: []const Token,
487 le_codes: []Compress.HuffCode,
488 oe_codes: []Compress.HuffCode,
489) Writer.Error!void {
490 for (tokens) |t| {
491 if (t.kind == Token.Kind.literal) {
492 try self.writeCode(le_codes[t.literal()]);
493 continue;
494 }
495
496 // Write the length
497 const le = t.lengthEncoding();
498 try self.writeCode(le_codes[le.code]);
499 if (le.extra_bits > 0) {
500 try self.bit_writer.writeBits(le.extra_length, le.extra_bits);
501 }
502
503 // Write the distance
504 const oe = t.distanceEncoding();
505 try self.writeCode(oe_codes[oe.code]);
506 if (oe.extra_bits > 0) {
507 try self.bit_writer.writeBits(oe.extra_distance, oe.extra_bits);
508 }
509 }
510 // add end_block_marker at the end
511 try self.writeCode(le_codes[huffman.end_block_marker]);
512}
513
514// Encodes a block of bytes as either Huffman encoded literals or uncompressed bytes
515// if the results only gains very little from compression.
516pub fn huffmanBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void {
517 // Add everything as literals
518 histogram(input, &self.literal_freq);
519
520 self.literal_freq[huffman.end_block_marker] = 1;
521
522 const num_literals = huffman.end_block_marker + 1;
523 self.distance_freq[0] = 1;
524 const num_distances = 1;
525
526 self.literal_encoding.generate(&self.literal_freq, 15);
527
528 // Figure out smallest code.
529 // Always use dynamic Huffman or Store
530 var num_codegens: u32 = 0;
531
532 // Generate codegen and codegenFrequencies, which indicates how to encode
533 // the literal_encoding and the distance_encoding.
534 self.generateCodegen(
535 num_literals,
536 num_distances,
537 &self.literal_encoding,
538 &self.huff_distance,
539 );
540 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
541 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.huff_distance, 0);
542 const size = dynamic_size.size;
543 num_codegens = dynamic_size.num_codegens;
544
545 // Store bytes, if we don't get a reasonable improvement.
546 const stored_size_ret = storedSizeFits(input);
547 const ssize = stored_size_ret.size;
548 const storable = stored_size_ret.storable;
549
550 if (storable and ssize < (size + (size >> 4))) {
551 try self.storedBlock(input, eof);
552 return;
553 }
554
555 // Huffman.
556 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
557 const encoding = self.literal_encoding.codes[0..257];
558
559 for (input) |t| {
560 const c = encoding[t];
561 try self.bit_writer.writeBits(c.code, c.len);
562 }
563 try self.writeCode(encoding[huffman.end_block_marker]);
564}
565
566// histogram accumulates a histogram of b in h.
567fn histogram(b: []const u8, h: *[286]u16) void {
568 // Clear histogram
569 for (h, 0..) |_, i| {
570 h[i] = 0;
571 }
572
573 var lh = h.*[0..256];
574 for (b) |t| {
575 lh[t] += 1;
576 }
577}
578
579// tests
580const expect = std.testing.expect;
581const fmt = std.fmt;
582const testing = std.testing;
583const ArrayList = std.ArrayList;
584
585const TestCase = @import("testdata/block_writer.zig").TestCase;
586const testCases = @import("testdata/block_writer.zig").testCases;
587
588// tests if the writeBlock encoding has changed.
589test "write" {
590 inline for (0..testCases.len) |i| {
591 try testBlock(testCases[i], .write_block);
592 }
593}
594
595// tests if the writeBlockDynamic encoding has changed.
596test "dynamicBlock" {
597 inline for (0..testCases.len) |i| {
598 try testBlock(testCases[i], .write_dyn_block);
599 }
600}
601
602test "huffmanBlock" {
603 inline for (0..testCases.len) |i| {
604 try testBlock(testCases[i], .write_huffman_block);
605 }
606 try testBlock(.{
607 .tokens = &[_]Token{},
608 .input = "huffman-rand-max.input",
609 .want = "huffman-rand-max.{s}.expect",
610 }, .write_huffman_block);
611}
612
613const TestFn = enum {
614 write_block,
615 write_dyn_block, // write dynamic block
616 write_huffman_block,
617
618 fn to_s(self: TestFn) []const u8 {
619 return switch (self) {
620 .write_block => "wb",
621 .write_dyn_block => "dyn",
622 .write_huffman_block => "huff",
623 };
624 }
625
626 fn write(
627 comptime self: TestFn,
628 bw: anytype,
629 tok: []const Token,
630 input: ?[]const u8,
631 final: bool,
632 ) !void {
633 switch (self) {
634 .write_block => try bw.write(tok, final, input),
635 .write_dyn_block => try bw.dynamicBlock(tok, final, input),
636 .write_huffman_block => try bw.huffmanBlock(input.?, final),
637 }
638 try bw.flush();
639 }
640};
641
642// testBlock tests a block against its references
643//
644// size
645// 64K [file-name].input - input non compressed file
646// 8.1K [file-name].golden -
647// 78 [file-name].dyn.expect - output with writeBlockDynamic
648// 78 [file-name].wb.expect - output with writeBlock
649// 8.1K [file-name].huff.expect - output with writeBlockHuff
650// 78 [file-name].dyn.expect-noinput - output with writeBlockDynamic when input is null
651// 78 [file-name].wb.expect-noinput - output with writeBlock when input is null
652//
653// wb - writeBlock
654// dyn - writeBlockDynamic
655// huff - writeBlockHuff
656//
657fn testBlock(comptime tc: TestCase, comptime tfn: TestFn) !void {
658 if (tc.input.len != 0 and tc.want.len != 0) {
659 const want_name = comptime fmt.comptimePrint(tc.want, .{tfn.to_s()});
660 const input = @embedFile("testdata/block_writer/" ++ tc.input);
661 const want = @embedFile("testdata/block_writer/" ++ want_name);
662 try testWriteBlock(tfn, input, want, tc.tokens);
663 }
664
665 if (tfn == .write_huffman_block) {
666 return;
667 }
668
669 const want_name_no_input = comptime fmt.comptimePrint(tc.want_no_input, .{tfn.to_s()});
670 const want = @embedFile("testdata/block_writer/" ++ want_name_no_input);
671 try testWriteBlock(tfn, null, want, tc.tokens);
672}
673
674// Uses writer function `tfn` to write `tokens`, tests that we got `want` as output.
675fn testWriteBlock(comptime tfn: TestFn, input: ?[]const u8, want: []const u8, tokens: []const Token) !void {
676 var buf = ArrayList(u8).init(testing.allocator);
677 var bw: BlockWriter = .init(buf.writer());
678 try tfn.write(&bw, tokens, input, false);
679 var got = buf.items;
680 try testing.expectEqualSlices(u8, want, got); // expect writeBlock to yield expected result
681 try expect(got[0] & 0b0000_0001 == 0); // bfinal is not set
682 //
683 // Test if the writer produces the same output after reset.
684 buf.deinit();
685 buf = ArrayList(u8).init(testing.allocator);
686 defer buf.deinit();
687 bw.setWriter(buf.writer());
688
689 try tfn.write(&bw, tokens, input, true);
690 try bw.flush();
691 got = buf.items;
692
693 try expect(got[0] & 1 == 1); // bfinal is set
694 buf.items[0] &= 0b1111_1110; // remove bfinal bit, so we can run test slices
695 try testing.expectEqualSlices(u8, want, got); // expect writeBlock to yield expected result
696}
lib/std/compress/flate/CircularBuffer.zig deleted-240
...@@ -1,240 +0,0 @@
1//! 64K buffer of uncompressed data created in inflate (decompression). Has enough
2//! history to support writing match<length, distance>; copying length of bytes
3//! from the position distance backward from current.
4//!
5//! Reads can return less than available bytes if they are spread across
6//! different circles. So reads should repeat until get required number of bytes
7//! or until returned slice is zero length.
8//!
9//! Note on deflate limits:
10//! * non-compressible block is limited to 65,535 bytes.
11//! * backward pointer is limited in distance to 32K bytes and in length to 258 bytes.
12//!
13//! Whole non-compressed block can be written without overlap. We always have
14//! history of up to 64K, more then 32K needed.
15//!
16const std = @import("std");
17const assert = std.debug.assert;
18const testing = std.testing;
19
20const consts = @import("consts.zig").match;
21
22const mask = 0xffff; // 64K - 1
23const buffer_len = mask + 1; // 64K buffer
24
25const Self = @This();
26
27buffer: [buffer_len]u8 = undefined,
28wp: usize = 0, // write position
29rp: usize = 0, // read position
30
31fn writeAll(self: *Self, buf: []const u8) void {
32 for (buf) |c| self.write(c);
33}
34
35/// Write literal.
36pub fn write(self: *Self, b: u8) void {
37 assert(self.wp - self.rp < mask);
38 self.buffer[self.wp & mask] = b;
39 self.wp += 1;
40}
41
42/// Write match (back-reference to the same data slice) starting at `distance`
43/// back from current write position, and `length` of bytes.
44pub fn writeMatch(self: *Self, length: u16, distance: u16) !void {
45 if (self.wp < distance or
46 length < consts.base_length or length > consts.max_length or
47 distance < consts.min_distance or distance > consts.max_distance)
48 {
49 return error.InvalidMatch;
50 }
51 assert(self.wp - self.rp < mask);
52
53 var from: usize = self.wp - distance & mask;
54 const from_end: usize = from + length;
55 var to: usize = self.wp & mask;
56 const to_end: usize = to + length;
57
58 self.wp += length;
59
60 // Fast path using memcpy
61 if (from_end < buffer_len and to_end < buffer_len) // start and end at the same circle
62 {
63 var cur_len = distance;
64 var remaining_len = length;
65 while (cur_len < remaining_len) {
66 @memcpy(self.buffer[to..][0..cur_len], self.buffer[from..][0..cur_len]);
67 to += cur_len;
68 remaining_len -= cur_len;
69 cur_len = cur_len * 2;
70 }
71 @memcpy(self.buffer[to..][0..remaining_len], self.buffer[from..][0..remaining_len]);
72 return;
73 }
74
75 // Slow byte by byte
76 while (to < to_end) {
77 self.buffer[to & mask] = self.buffer[from & mask];
78 to += 1;
79 from += 1;
80 }
81}
82
83/// Returns writable part of the internal buffer of size `n` at most. Advances
84/// write pointer, assumes that returned buffer will be filled with data.
85pub fn getWritable(self: *Self, n: usize) []u8 {
86 const wp = self.wp & mask;
87 const len = @min(n, buffer_len - wp);
88 self.wp += len;
89 return self.buffer[wp .. wp + len];
90}
91
92/// Read available data. Can return part of the available data if it is
93/// spread across two circles. So read until this returns zero length.
94pub fn read(self: *Self) []const u8 {
95 return self.readAtMost(buffer_len);
96}
97
98/// Read part of available data. Can return less than max even if there are
99/// more than max decoded data.
100pub fn readAtMost(self: *Self, limit: usize) []const u8 {
101 const rb = self.readBlock(if (limit == 0) buffer_len else limit);
102 defer self.rp += rb.len;
103 return self.buffer[rb.head..rb.tail];
104}
105
106const ReadBlock = struct {
107 head: usize,
108 tail: usize,
109 len: usize,
110};
111
112/// Returns position of continuous read block data.
113fn readBlock(self: *Self, max: usize) ReadBlock {
114 const r = self.rp & mask;
115 const w = self.wp & mask;
116 const n = @min(
117 max,
118 if (w >= r) w - r else buffer_len - r,
119 );
120 return .{
121 .head = r,
122 .tail = r + n,
123 .len = n,
124 };
125}
126
127/// Number of free bytes for write.
128pub fn free(self: *Self) usize {
129 return buffer_len - (self.wp - self.rp);
130}
131
132/// Full if largest match can't fit. 258 is largest match length. That much
133/// bytes can be produced in single decode step.
134pub fn full(self: *Self) bool {
135 return self.free() < 258 + 1;
136}
137
138// example from: https://youtu.be/SJPvNi4HrWQ?t=3558
139test writeMatch {
140 var cb: Self = .{};
141
142 cb.writeAll("a salad; ");
143 try cb.writeMatch(5, 9);
144 try cb.writeMatch(3, 3);
145
146 try testing.expectEqualStrings("a salad; a salsal", cb.read());
147}
148
149test "writeMatch overlap" {
150 var cb: Self = .{};
151
152 cb.writeAll("a b c ");
153 try cb.writeMatch(8, 4);
154 cb.write('d');
155
156 try testing.expectEqualStrings("a b c b c b c d", cb.read());
157}
158
159test readAtMost {
160 var cb: Self = .{};
161
162 cb.writeAll("0123456789");
163 try cb.writeMatch(50, 10);
164
165 try testing.expectEqualStrings("0123456789" ** 6, cb.buffer[cb.rp..cb.wp]);
166 for (0..6) |i| {
167 try testing.expectEqual(i * 10, cb.rp);
168 try testing.expectEqualStrings("0123456789", cb.readAtMost(10));
169 }
170 try testing.expectEqualStrings("", cb.readAtMost(10));
171 try testing.expectEqualStrings("", cb.read());
172}
173
174test Self {
175 var cb: Self = .{};
176
177 const data = "0123456789abcdef" ** (1024 / 16);
178 cb.writeAll(data);
179 try testing.expectEqual(@as(usize, 0), cb.rp);
180 try testing.expectEqual(@as(usize, 1024), cb.wp);
181 try testing.expectEqual(@as(usize, 1024 * 63), cb.free());
182
183 for (0..62 * 4) |_|
184 try cb.writeMatch(256, 1024); // write 62K
185
186 try testing.expectEqual(@as(usize, 0), cb.rp);
187 try testing.expectEqual(@as(usize, 63 * 1024), cb.wp);
188 try testing.expectEqual(@as(usize, 1024), cb.free());
189
190 cb.writeAll(data[0..200]);
191 _ = cb.readAtMost(1024); // make some space
192 cb.writeAll(data); // overflows write position
193 try testing.expectEqual(@as(usize, 200 + 65536), cb.wp);
194 try testing.expectEqual(@as(usize, 1024), cb.rp);
195 try testing.expectEqual(@as(usize, 1024 - 200), cb.free());
196
197 const rb = cb.readBlock(Self.buffer_len);
198 try testing.expectEqual(@as(usize, 65536 - 1024), rb.len);
199 try testing.expectEqual(@as(usize, 1024), rb.head);
200 try testing.expectEqual(@as(usize, 65536), rb.tail);
201
202 try testing.expectEqual(@as(usize, 65536 - 1024), cb.read().len); // read to the end of the buffer
203 try testing.expectEqual(@as(usize, 200 + 65536), cb.wp);
204 try testing.expectEqual(@as(usize, 65536), cb.rp);
205 try testing.expectEqual(@as(usize, 65536 - 200), cb.free());
206
207 try testing.expectEqual(@as(usize, 200), cb.read().len); // read the rest
208}
209
210test "write overlap" {
211 var cb: Self = .{};
212 cb.wp = cb.buffer.len - 15;
213 cb.rp = cb.wp;
214
215 cb.writeAll("0123456789");
216 cb.writeAll("abcdefghij");
217
218 try testing.expectEqual(cb.buffer.len + 5, cb.wp);
219 try testing.expectEqual(cb.buffer.len - 15, cb.rp);
220
221 try testing.expectEqualStrings("0123456789abcde", cb.read());
222 try testing.expectEqualStrings("fghij", cb.read());
223
224 try testing.expect(cb.wp == cb.rp);
225}
226
227test "writeMatch/read overlap" {
228 var cb: Self = .{};
229 cb.wp = cb.buffer.len - 15;
230 cb.rp = cb.wp;
231
232 cb.writeAll("0123456789");
233 try cb.writeMatch(15, 5);
234
235 try testing.expectEqualStrings("012345678956789", cb.read());
236 try testing.expectEqualStrings("5678956789", cb.read());
237
238 try cb.writeMatch(20, 25);
239 try testing.expectEqualStrings("01234567895678956789", cb.read());
240}
lib/std/compress/flate/Compress.zig created+1264
...@@ -0,0 +1,1264 @@
1//! Default compression algorithm. Has two steps: tokenization and token
2//! encoding.
3//!
4//! Tokenization takes uncompressed input stream and produces list of tokens.
5//! Each token can be literal (byte of data) or match (backrefernce to previous
6//! data with length and distance). Tokenization accumulators 32K tokens, when
7//! full or `flush` is called tokens are passed to the `block_writer`. Level
8//! defines how hard (how slow) it tries to find match.
9//!
10//! Block writer will decide which type of deflate block to write (stored, fixed,
11//! dynamic) and encode tokens to the output byte stream. Client has to call
12//! `finish` to write block with the final bit set.
13//!
14//! Container defines type of header and footer which can be gzip, zlib or raw.
15//! They all share same deflate body. Raw has no header or footer just deflate
16//! body.
17//!
18//! Compression algorithm explained in rfc-1951 (slightly edited for this case):
19//!
20//! The compressor uses a chained hash table `lookup` to find duplicated
21//! strings, using a hash function that operates on 4-byte sequences. At any
22//! given point during compression, let XYZW be the next 4 input bytes
23//! (lookahead) to be examined (not necessarily all different, of course).
24//! First, the compressor examines the hash chain for XYZW. If the chain is
25//! empty, the compressor simply writes out X as a literal byte and advances
26//! one byte in the input. If the hash chain is not empty, indicating that the
27//! sequence XYZW (or, if we are unlucky, some other 4 bytes with the same
28//! hash function value) has occurred recently, the compressor compares all
29//! strings on the XYZW hash chain with the actual input data sequence
30//! starting at the current point, and selects the longest match.
31//!
32//! To improve overall compression, the compressor defers the selection of
33//! matches ("lazy matching"): after a match of length N has been found, the
34//! compressor searches for a longer match starting at the next input byte. If
35//! it finds a longer match, it truncates the previous match to a length of
36//! one (thus producing a single literal byte) and then emits the longer
37//! match. Otherwise, it emits the original match, and, as described above,
38//! advances N bytes before continuing.
39//!
40//!
41//! Allocates statically ~400K (192K lookup, 128K tokens, 64K window).
42const builtin = @import("builtin");
43const std = @import("std");
44const assert = std.debug.assert;
45const testing = std.testing;
46const expect = testing.expect;
47const mem = std.mem;
48const math = std.math;
49const Writer = std.Io.Writer;
50const Reader = std.Io.Reader;
51
52const Compress = @This();
53const Token = @import("Token.zig");
54const BlockWriter = @import("BlockWriter.zig");
55const flate = @import("../flate.zig");
56const Container = flate.Container;
57const Lookup = @import("Lookup.zig");
58const huffman = flate.huffman;
59
60lookup: Lookup = .{},
61tokens: Tokens = .{},
62/// Asserted to have a buffer capacity of at least `flate.max_window_len`.
63input: *Reader,
64block_writer: BlockWriter,
65level: LevelArgs,
66hasher: Container.Hasher,
67reader: Reader,
68
69// Match and literal at the previous position.
70// Used for lazy match finding in processWindow.
71prev_match: ?Token = null,
72prev_literal: ?u8 = null,
73
74/// Trades between speed and compression size.
75/// Starts with level 4: in [zlib](https://github.com/madler/zlib/blob/abd3d1a28930f89375d4b41408b39f6c1be157b2/deflate.c#L115C1-L117C43)
76/// levels 1-3 are using different algorithm to perform faster but with less
77/// compression. That is not implemented here.
78pub const Level = enum(u4) {
79 level_4 = 4,
80 level_5 = 5,
81 level_6 = 6,
82 level_7 = 7,
83 level_8 = 8,
84 level_9 = 9,
85
86 fast = 0xb,
87 default = 0xc,
88 best = 0xd,
89};
90
91/// Number of tokens to accumulate in deflate before starting block encoding.
92///
93/// In zlib this depends on memlevel: 6 + memlevel, where default memlevel is
94/// 8 and max 9 that gives 14 or 15 bits.
95pub const n_tokens = 1 << 15;
96
97/// Algorithm knobs for each level.
98const LevelArgs = struct {
99 good: u16, // Do less lookups if we already have match of this length.
100 nice: u16, // Stop looking for better match if we found match with at least this length.
101 lazy: u16, // Don't do lazy match find if got match with at least this length.
102 chain: u16, // How many lookups for previous match to perform.
103
104 pub fn get(level: Level) LevelArgs {
105 return switch (level) {
106 .fast, .level_4 => .{ .good = 4, .lazy = 4, .nice = 16, .chain = 16 },
107 .level_5 => .{ .good = 8, .lazy = 16, .nice = 32, .chain = 32 },
108 .default, .level_6 => .{ .good = 8, .lazy = 16, .nice = 128, .chain = 128 },
109 .level_7 => .{ .good = 8, .lazy = 32, .nice = 128, .chain = 256 },
110 .level_8 => .{ .good = 32, .lazy = 128, .nice = 258, .chain = 1024 },
111 .best, .level_9 => .{ .good = 32, .lazy = 258, .nice = 258, .chain = 4096 },
112 };
113 }
114};
115
116pub const Options = struct {
117 level: Level = .default,
118 container: Container = .raw,
119};
120
121pub fn init(input: *Reader, buffer: []u8, options: Options) Compress {
122 return .{
123 .input = input,
124 .block_writer = undefined,
125 .level = .get(options.level),
126 .hasher = .init(options.container),
127 .state = .header,
128 .reader = .{
129 .buffer = buffer,
130 .stream = stream,
131 },
132 };
133}
134
135const FlushOption = enum { none, flush, final };
136
137/// Process data in window and create tokens. If token buffer is full
138/// flush tokens to the token writer.
139///
140/// Returns number of bytes consumed from `lh`.
141fn tokenizeSlice(c: *Compress, bw: *Writer, limit: std.Io.Limit, lh: []const u8) !usize {
142 _ = bw;
143 _ = limit;
144 if (true) @panic("TODO");
145 var step: u16 = 1; // 1 in the case of literal, match length otherwise
146 const pos: u16 = c.win.pos();
147 const literal = lh[0]; // literal at current position
148 const min_len: u16 = if (c.prev_match) |m| m.length() else 0;
149
150 // Try to find match at least min_len long.
151 if (c.findMatch(pos, lh, min_len)) |match| {
152 // Found better match than previous.
153 try c.addPrevLiteral();
154
155 // Is found match length good enough?
156 if (match.length() >= c.level.lazy) {
157 // Don't try to lazy find better match, use this.
158 step = try c.addMatch(match);
159 } else {
160 // Store this match.
161 c.prev_literal = literal;
162 c.prev_match = match;
163 }
164 } else {
165 // There is no better match at current pos then it was previous.
166 // Write previous match or literal.
167 if (c.prev_match) |m| {
168 // Write match from previous position.
169 step = try c.addMatch(m) - 1; // we already advanced 1 from previous position
170 } else {
171 // No match at previous position.
172 // Write previous literal if any, and remember this literal.
173 try c.addPrevLiteral();
174 c.prev_literal = literal;
175 }
176 }
177 // Advance window and add hashes.
178 c.windowAdvance(step, lh, pos);
179}
180
181fn windowAdvance(self: *Compress, step: u16, lh: []const u8, pos: u16) void {
182 // current position is already added in findMatch
183 self.lookup.bulkAdd(lh[1..], step - 1, pos + 1);
184 self.win.advance(step);
185}
186
187// Add previous literal (if any) to the tokens list.
188fn addPrevLiteral(self: *Compress) !void {
189 if (self.prev_literal) |l| try self.addToken(Token.initLiteral(l));
190}
191
192// Add match to the tokens list, reset prev pointers.
193// Returns length of the added match.
194fn addMatch(self: *Compress, m: Token) !u16 {
195 try self.addToken(m);
196 self.prev_literal = null;
197 self.prev_match = null;
198 return m.length();
199}
200
201fn addToken(self: *Compress, token: Token) !void {
202 self.tokens.add(token);
203 if (self.tokens.full()) try self.flushTokens(.none);
204}
205
206// Finds largest match in the history window with the data at current pos.
207fn findMatch(self: *Compress, pos: u16, lh: []const u8, min_len: u16) ?Token {
208 var len: u16 = min_len;
209 // Previous location with the same hash (same 4 bytes).
210 var prev_pos = self.lookup.add(lh, pos);
211 // Last found match.
212 var match: ?Token = null;
213
214 // How much back-references to try, performance knob.
215 var chain: usize = self.level.chain;
216 if (len >= self.level.good) {
217 // If we've got a match that's good enough, only look in 1/4 the chain.
218 chain >>= 2;
219 }
220
221 // Hot path loop!
222 while (prev_pos > 0 and chain > 0) : (chain -= 1) {
223 const distance = pos - prev_pos;
224 if (distance > flate.match.max_distance)
225 break;
226
227 const new_len = self.win.match(prev_pos, pos, len);
228 if (new_len > len) {
229 match = Token.initMatch(@intCast(distance), new_len);
230 if (new_len >= self.level.nice) {
231 // The match is good enough that we don't try to find a better one.
232 return match;
233 }
234 len = new_len;
235 }
236 prev_pos = self.lookup.prev(prev_pos);
237 }
238
239 return match;
240}
241
242fn flushTokens(self: *Compress, flush_opt: FlushOption) !void {
243 // Pass tokens to the token writer
244 try self.block_writer.write(self.tokens.tokens(), flush_opt == .final, self.win.tokensBuffer());
245 // Stored block ensures byte alignment.
246 // It has 3 bits (final, block_type) and then padding until byte boundary.
247 // After that everything is aligned to the boundary in the stored block.
248 // Empty stored block is Ob000 + (0-7) bits of padding + 0x00 0x00 0xFF 0xFF.
249 // Last 4 bytes are byte aligned.
250 if (flush_opt == .flush) {
251 try self.block_writer.storedBlock("", false);
252 }
253 if (flush_opt != .none) {
254 // Safe to call only when byte aligned or it is OK to add
255 // padding bits (on last byte of the final block).
256 try self.block_writer.flush();
257 }
258 // Reset internal tokens store.
259 self.tokens.reset();
260 // Notify win that tokens are flushed.
261 self.win.flush();
262}
263
264// Slide win and if needed lookup tables.
265fn slide(self: *Compress) void {
266 const n = self.win.slide();
267 self.lookup.slide(n);
268}
269
270/// Flushes internal buffers to the output writer. Outputs empty stored
271/// block to sync bit stream to the byte boundary, so that the
272/// decompressor can get all input data available so far.
273///
274/// It is useful mainly in compressed network protocols, to ensure that
275/// deflate bit stream can be used as byte stream. May degrade
276/// compression so it should be used only when necessary.
277///
278/// Completes the current deflate block and follows it with an empty
279/// stored block that is three zero bits plus filler bits to the next
280/// byte, followed by four bytes (00 00 ff ff).
281///
282pub fn flush(c: *Compress) !void {
283 try c.tokenize(.flush);
284}
285
286/// Completes deflate bit stream by writing any pending data as deflate
287/// final deflate block. HAS to be called once all data are written to
288/// the compressor as a signal that next block has to have final bit
289/// set.
290///
291pub fn finish(c: *Compress) !void {
292 _ = c;
293 @panic("TODO");
294}
295
296/// Use another writer while preserving history. Most probably flush
297/// should be called on old writer before setting new.
298pub fn setWriter(self: *Compress, new_writer: *Writer) void {
299 self.block_writer.setWriter(new_writer);
300 self.output = new_writer;
301}
302
303// Tokens store
304const Tokens = struct {
305 list: [n_tokens]Token = undefined,
306 pos: usize = 0,
307
308 fn add(self: *Tokens, t: Token) void {
309 self.list[self.pos] = t;
310 self.pos += 1;
311 }
312
313 fn full(self: *Tokens) bool {
314 return self.pos == self.list.len;
315 }
316
317 fn reset(self: *Tokens) void {
318 self.pos = 0;
319 }
320
321 fn tokens(self: *Tokens) []const Token {
322 return self.list[0..self.pos];
323 }
324};
325
326/// Creates huffman only deflate blocks. Disables Lempel-Ziv match searching and
327/// only performs Huffman entropy encoding. Results in faster compression, much
328/// less memory requirements during compression but bigger compressed sizes.
329pub const Huffman = SimpleCompressor(.huffman, .raw);
330
331/// Creates store blocks only. Data are not compressed only packed into deflate
332/// store blocks. That adds 9 bytes of header for each block. Max stored block
333/// size is 64K. Block is emitted when flush is called on on finish.
334pub const store = struct {
335 pub fn Compressor(comptime container: Container, comptime WriterType: type) type {
336 return SimpleCompressor(.store, container, WriterType);
337 }
338
339 pub fn compressor(comptime container: Container, writer: anytype) !store.Compressor(container, @TypeOf(writer)) {
340 return try store.Compressor(container, @TypeOf(writer)).init(writer);
341 }
342};
343
344const SimpleCompressorKind = enum {
345 huffman,
346 store,
347};
348
349fn simpleCompressor(
350 comptime kind: SimpleCompressorKind,
351 comptime container: Container,
352 writer: anytype,
353) !SimpleCompressor(kind, container, @TypeOf(writer)) {
354 return try SimpleCompressor(kind, container, @TypeOf(writer)).init(writer);
355}
356
357fn SimpleCompressor(
358 comptime kind: SimpleCompressorKind,
359 comptime container: Container,
360 comptime WriterType: type,
361) type {
362 const BlockWriterType = BlockWriter(WriterType);
363 return struct {
364 buffer: [65535]u8 = undefined, // because store blocks are limited to 65535 bytes
365 wp: usize = 0,
366
367 output: WriterType,
368 block_writer: BlockWriterType,
369 hasher: container.Hasher() = .{},
370
371 const Self = @This();
372
373 pub fn init(output: WriterType) !Self {
374 const self = Self{
375 .output = output,
376 .block_writer = BlockWriterType.init(output),
377 };
378 try container.writeHeader(self.output);
379 return self;
380 }
381
382 pub fn flush(self: *Self) !void {
383 try self.flushBuffer(false);
384 try self.block_writer.storedBlock("", false);
385 try self.block_writer.flush();
386 }
387
388 pub fn finish(self: *Self) !void {
389 try self.flushBuffer(true);
390 try self.block_writer.flush();
391 try container.writeFooter(&self.hasher, self.output);
392 }
393
394 fn flushBuffer(self: *Self, final: bool) !void {
395 const buf = self.buffer[0..self.wp];
396 switch (kind) {
397 .huffman => try self.block_writer.huffmanBlock(buf, final),
398 .store => try self.block_writer.storedBlock(buf, final),
399 }
400 self.wp = 0;
401 }
402 };
403}
404
405const LiteralNode = struct {
406 literal: u16,
407 freq: u16,
408};
409
410// Describes the state of the constructed tree for a given depth.
411const LevelInfo = struct {
412 // Our level. for better printing
413 level: u32,
414
415 // The frequency of the last node at this level
416 last_freq: u32,
417
418 // The frequency of the next character to add to this level
419 next_char_freq: u32,
420
421 // The frequency of the next pair (from level below) to add to this level.
422 // Only valid if the "needed" value of the next lower level is 0.
423 next_pair_freq: u32,
424
425 // The number of chains remaining to generate for this level before moving
426 // up to the next level
427 needed: u32,
428};
429
430// hcode is a huffman code with a bit code and bit length.
431pub const HuffCode = struct {
432 code: u16 = 0,
433 len: u16 = 0,
434
435 // set sets the code and length of an hcode.
436 fn set(self: *HuffCode, code: u16, length: u16) void {
437 self.len = length;
438 self.code = code;
439 }
440};
441
442pub fn HuffmanEncoder(comptime size: usize) type {
443 return struct {
444 codes: [size]HuffCode = undefined,
445 // Reusable buffer with the longest possible frequency table.
446 freq_cache: [huffman.max_num_frequencies + 1]LiteralNode = undefined,
447 bit_count: [17]u32 = undefined,
448 lns: []LiteralNode = undefined, // sorted by literal, stored to avoid repeated allocation in generate
449 lfs: []LiteralNode = undefined, // sorted by frequency, stored to avoid repeated allocation in generate
450
451 const Self = @This();
452
453 // Update this Huffman Code object to be the minimum code for the specified frequency count.
454 //
455 // freq An array of frequencies, in which frequency[i] gives the frequency of literal i.
456 // max_bits The maximum number of bits to use for any literal.
457 pub fn generate(self: *Self, freq: []u16, max_bits: u32) void {
458 var list = self.freq_cache[0 .. freq.len + 1];
459 // Number of non-zero literals
460 var count: u32 = 0;
461 // Set list to be the set of all non-zero literals and their frequencies
462 for (freq, 0..) |f, i| {
463 if (f != 0) {
464 list[count] = LiteralNode{ .literal = @as(u16, @intCast(i)), .freq = f };
465 count += 1;
466 } else {
467 list[count] = LiteralNode{ .literal = 0x00, .freq = 0 };
468 self.codes[i].len = 0;
469 }
470 }
471 list[freq.len] = LiteralNode{ .literal = 0x00, .freq = 0 };
472
473 list = list[0..count];
474 if (count <= 2) {
475 // Handle the small cases here, because they are awkward for the general case code. With
476 // two or fewer literals, everything has bit length 1.
477 for (list, 0..) |node, i| {
478 // "list" is in order of increasing literal value.
479 self.codes[node.literal].set(@as(u16, @intCast(i)), 1);
480 }
481 return;
482 }
483 self.lfs = list;
484 mem.sort(LiteralNode, self.lfs, {}, byFreq);
485
486 // Get the number of literals for each bit count
487 const bit_count = self.bitCounts(list, max_bits);
488 // And do the assignment
489 self.assignEncodingAndSize(bit_count, list);
490 }
491
492 pub fn bitLength(self: *Self, freq: []u16) u32 {
493 var total: u32 = 0;
494 for (freq, 0..) |f, i| {
495 if (f != 0) {
496 total += @as(u32, @intCast(f)) * @as(u32, @intCast(self.codes[i].len));
497 }
498 }
499 return total;
500 }
501
502 // Return the number of literals assigned to each bit size in the Huffman encoding
503 //
504 // This method is only called when list.len >= 3
505 // The cases of 0, 1, and 2 literals are handled by special case code.
506 //
507 // list: An array of the literals with non-zero frequencies
508 // and their associated frequencies. The array is in order of increasing
509 // frequency, and has as its last element a special element with frequency
510 // `math.maxInt(i32)`
511 //
512 // max_bits: The maximum number of bits that should be used to encode any literal.
513 // Must be less than 16.
514 //
515 // Returns an integer array in which array[i] indicates the number of literals
516 // that should be encoded in i bits.
517 fn bitCounts(self: *Self, list: []LiteralNode, max_bits_to_use: usize) []u32 {
518 var max_bits = max_bits_to_use;
519 const n = list.len;
520 const max_bits_limit = 16;
521
522 assert(max_bits < max_bits_limit);
523
524 // The tree can't have greater depth than n - 1, no matter what. This
525 // saves a little bit of work in some small cases
526 max_bits = @min(max_bits, n - 1);
527
528 // Create information about each of the levels.
529 // A bogus "Level 0" whose sole purpose is so that
530 // level1.prev.needed == 0. This makes level1.next_pair_freq
531 // be a legitimate value that never gets chosen.
532 var levels: [max_bits_limit]LevelInfo = mem.zeroes([max_bits_limit]LevelInfo);
533 // leaf_counts[i] counts the number of literals at the left
534 // of ancestors of the rightmost node at level i.
535 // leaf_counts[i][j] is the number of literals at the left
536 // of the level j ancestor.
537 var leaf_counts: [max_bits_limit][max_bits_limit]u32 = mem.zeroes([max_bits_limit][max_bits_limit]u32);
538
539 {
540 var level = @as(u32, 1);
541 while (level <= max_bits) : (level += 1) {
542 // For every level, the first two items are the first two characters.
543 // We initialize the levels as if we had already figured this out.
544 levels[level] = LevelInfo{
545 .level = level,
546 .last_freq = list[1].freq,
547 .next_char_freq = list[2].freq,
548 .next_pair_freq = list[0].freq + list[1].freq,
549 .needed = 0,
550 };
551 leaf_counts[level][level] = 2;
552 if (level == 1) {
553 levels[level].next_pair_freq = math.maxInt(i32);
554 }
555 }
556 }
557
558 // We need a total of 2*n - 2 items at top level and have already generated 2.
559 levels[max_bits].needed = 2 * @as(u32, @intCast(n)) - 4;
560
561 {
562 var level = max_bits;
563 while (true) {
564 var l = &levels[level];
565 if (l.next_pair_freq == math.maxInt(i32) and l.next_char_freq == math.maxInt(i32)) {
566 // We've run out of both leaves and pairs.
567 // End all calculations for this level.
568 // To make sure we never come back to this level or any lower level,
569 // set next_pair_freq impossibly large.
570 l.needed = 0;
571 levels[level + 1].next_pair_freq = math.maxInt(i32);
572 level += 1;
573 continue;
574 }
575
576 const prev_freq = l.last_freq;
577 if (l.next_char_freq < l.next_pair_freq) {
578 // The next item on this row is a leaf node.
579 const next = leaf_counts[level][level] + 1;
580 l.last_freq = l.next_char_freq;
581 // Lower leaf_counts are the same of the previous node.
582 leaf_counts[level][level] = next;
583 if (next >= list.len) {
584 l.next_char_freq = maxNode().freq;
585 } else {
586 l.next_char_freq = list[next].freq;
587 }
588 } else {
589 // The next item on this row is a pair from the previous row.
590 // next_pair_freq isn't valid until we generate two
591 // more values in the level below
592 l.last_freq = l.next_pair_freq;
593 // Take leaf counts from the lower level, except counts[level] remains the same.
594 @memcpy(leaf_counts[level][0..level], leaf_counts[level - 1][0..level]);
595 levels[l.level - 1].needed = 2;
596 }
597
598 l.needed -= 1;
599 if (l.needed == 0) {
600 // We've done everything we need to do for this level.
601 // Continue calculating one level up. Fill in next_pair_freq
602 // of that level with the sum of the two nodes we've just calculated on
603 // this level.
604 if (l.level == max_bits) {
605 // All done!
606 break;
607 }
608 levels[l.level + 1].next_pair_freq = prev_freq + l.last_freq;
609 level += 1;
610 } else {
611 // If we stole from below, move down temporarily to replenish it.
612 while (levels[level - 1].needed > 0) {
613 level -= 1;
614 if (level == 0) {
615 break;
616 }
617 }
618 }
619 }
620 }
621
622 // Somethings is wrong if at the end, the top level is null or hasn't used
623 // all of the leaves.
624 assert(leaf_counts[max_bits][max_bits] == n);
625
626 var bit_count = self.bit_count[0 .. max_bits + 1];
627 var bits: u32 = 1;
628 const counts = &leaf_counts[max_bits];
629 {
630 var level = max_bits;
631 while (level > 0) : (level -= 1) {
632 // counts[level] gives the number of literals requiring at least "bits"
633 // bits to encode.
634 bit_count[bits] = counts[level] - counts[level - 1];
635 bits += 1;
636 if (level == 0) {
637 break;
638 }
639 }
640 }
641 return bit_count;
642 }
643
644 // Look at the leaves and assign them a bit count and an encoding as specified
645 // in RFC 1951 3.2.2
646 fn assignEncodingAndSize(self: *Self, bit_count: []u32, list_arg: []LiteralNode) void {
647 var code = @as(u16, 0);
648 var list = list_arg;
649
650 for (bit_count, 0..) |bits, n| {
651 code <<= 1;
652 if (n == 0 or bits == 0) {
653 continue;
654 }
655 // The literals list[list.len-bits] .. list[list.len-bits]
656 // are encoded using "bits" bits, and get the values
657 // code, code + 1, .... The code values are
658 // assigned in literal order (not frequency order).
659 const chunk = list[list.len - @as(u32, @intCast(bits)) ..];
660
661 self.lns = chunk;
662 mem.sort(LiteralNode, self.lns, {}, byLiteral);
663
664 for (chunk) |node| {
665 self.codes[node.literal] = HuffCode{
666 .code = bitReverse(u16, code, @as(u5, @intCast(n))),
667 .len = @as(u16, @intCast(n)),
668 };
669 code += 1;
670 }
671 list = list[0 .. list.len - @as(u32, @intCast(bits))];
672 }
673 }
674 };
675}
676
677fn maxNode() LiteralNode {
678 return LiteralNode{
679 .literal = math.maxInt(u16),
680 .freq = math.maxInt(u16),
681 };
682}
683
684pub fn huffmanEncoder(comptime size: u32) HuffmanEncoder(size) {
685 return .{};
686}
687
688pub const LiteralEncoder = HuffmanEncoder(huffman.max_num_frequencies);
689pub const DistanceEncoder = HuffmanEncoder(huffman.distance_code_count);
690pub const CodegenEncoder = HuffmanEncoder(19);
691
692// Generates a HuffmanCode corresponding to the fixed literal table
693pub fn fixedLiteralEncoder() LiteralEncoder {
694 var h: LiteralEncoder = undefined;
695 var ch: u16 = 0;
696
697 while (ch < huffman.max_num_frequencies) : (ch += 1) {
698 var bits: u16 = undefined;
699 var size: u16 = undefined;
700 switch (ch) {
701 0...143 => {
702 // size 8, 000110000 .. 10111111
703 bits = ch + 48;
704 size = 8;
705 },
706 144...255 => {
707 // size 9, 110010000 .. 111111111
708 bits = ch + 400 - 144;
709 size = 9;
710 },
711 256...279 => {
712 // size 7, 0000000 .. 0010111
713 bits = ch - 256;
714 size = 7;
715 },
716 else => {
717 // size 8, 11000000 .. 11000111
718 bits = ch + 192 - 280;
719 size = 8;
720 },
721 }
722 h.codes[ch] = HuffCode{ .code = bitReverse(u16, bits, @as(u5, @intCast(size))), .len = size };
723 }
724 return h;
725}
726
727pub fn fixedDistanceEncoder() DistanceEncoder {
728 var h: DistanceEncoder = undefined;
729 for (h.codes, 0..) |_, ch| {
730 h.codes[ch] = HuffCode{ .code = bitReverse(u16, @as(u16, @intCast(ch)), 5), .len = 5 };
731 }
732 return h;
733}
734
735pub fn huffmanDistanceEncoder() DistanceEncoder {
736 var distance_freq = [1]u16{0} ** huffman.distance_code_count;
737 distance_freq[0] = 1;
738 // huff_distance is a static distance encoder used for huffman only encoding.
739 // It can be reused since we will not be encoding distance values.
740 var h: DistanceEncoder = .{};
741 h.generate(distance_freq[0..], 15);
742 return h;
743}
744
745fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) bool {
746 _ = context;
747 return a.literal < b.literal;
748}
749
750fn byFreq(context: void, a: LiteralNode, b: LiteralNode) bool {
751 _ = context;
752 if (a.freq == b.freq) {
753 return a.literal < b.literal;
754 }
755 return a.freq < b.freq;
756}
757
758fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
759 const c: *Compress = @fieldParentPtr("reader", r);
760 switch (c.state) {
761 .header => |i| {
762 const header = c.hasher.container().header();
763 const n = try w.write(header[i..]);
764 if (header.len - i - n == 0) {
765 c.state = .middle;
766 } else {
767 c.state.header += n;
768 }
769 return n;
770 },
771 .middle => {
772 c.input.fillMore() catch |err| switch (err) {
773 error.EndOfStream => {
774 c.state = .final;
775 return 0;
776 },
777 else => |e| return e,
778 };
779 const buffer_contents = c.input.buffered();
780 const min_lookahead = flate.match.min_length + flate.match.max_length;
781 const history_plus_lookahead_len = flate.history_len + min_lookahead;
782 if (buffer_contents.len < history_plus_lookahead_len) return 0;
783 const lookahead = buffer_contents[flate.history_len..];
784 const start = w.count;
785 const n = try c.tokenizeSlice(w, limit, lookahead) catch |err| switch (err) {
786 error.WriteFailed => return error.WriteFailed,
787 };
788 c.hasher.update(lookahead[0..n]);
789 c.input.toss(n);
790 return w.count - start;
791 },
792 .final => {
793 const buffer_contents = c.input.buffered();
794 const start = w.count;
795 const n = c.tokenizeSlice(w, limit, buffer_contents) catch |err| switch (err) {
796 error.WriteFailed => return error.WriteFailed,
797 };
798 if (buffer_contents.len - n == 0) {
799 c.hasher.update(buffer_contents);
800 c.input.tossAll();
801 {
802 // In the case of flushing, last few lookahead buffers were
803 // smaller than min match len, so only last literal can be
804 // unwritten.
805 assert(c.prev_match == null);
806 try c.addPrevLiteral();
807 c.prev_literal = null;
808
809 try c.flushTokens(.final);
810 }
811 switch (c.hasher) {
812 .gzip => |*gzip| {
813 // GZIP 8 bytes footer
814 // - 4 bytes, CRC32 (CRC-32)
815 // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32
816 comptime assert(c.footer_buffer.len == 8);
817 std.mem.writeInt(u32, c.footer_buffer[0..4], gzip.final(), .little);
818 std.mem.writeInt(u32, c.footer_buffer[4..8], gzip.bytes_read, .little);
819 c.state = .{ .footer = 0 };
820 },
821 .zlib => |*zlib| {
822 // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
823 // 4 bytes of ADLER32 (Adler-32 checksum)
824 // Checksum value of the uncompressed data (excluding any
825 // dictionary data) computed according to Adler-32
826 // algorithm.
827 comptime assert(c.footer_buffer.len == 8);
828 std.mem.writeInt(u32, c.footer_buffer[4..8], zlib.final, .big);
829 c.state = .{ .footer = 4 };
830 },
831 .raw => {
832 c.state = .ended;
833 },
834 }
835 }
836 return w.count - start;
837 },
838 .ended => return error.EndOfStream,
839 .footer => |i| {
840 const remaining = c.footer_buffer[i..];
841 const n = try w.write(limit.slice(remaining));
842 c.state = if (n == remaining) .ended else .{ .footer = i - n };
843 return n;
844 },
845 }
846}
847
848test "generate a Huffman code from an array of frequencies" {
849 var freqs: [19]u16 = [_]u16{
850 8, // 0
851 1, // 1
852 1, // 2
853 2, // 3
854 5, // 4
855 10, // 5
856 9, // 6
857 1, // 7
858 0, // 8
859 0, // 9
860 0, // 10
861 0, // 11
862 0, // 12
863 0, // 13
864 0, // 14
865 0, // 15
866 1, // 16
867 3, // 17
868 5, // 18
869 };
870
871 var enc = huffmanEncoder(19);
872 enc.generate(freqs[0..], 7);
873
874 try testing.expectEqual(@as(u32, 141), enc.bitLength(freqs[0..]));
875
876 try testing.expectEqual(@as(usize, 3), enc.codes[0].len);
877 try testing.expectEqual(@as(usize, 6), enc.codes[1].len);
878 try testing.expectEqual(@as(usize, 6), enc.codes[2].len);
879 try testing.expectEqual(@as(usize, 5), enc.codes[3].len);
880 try testing.expectEqual(@as(usize, 3), enc.codes[4].len);
881 try testing.expectEqual(@as(usize, 2), enc.codes[5].len);
882 try testing.expectEqual(@as(usize, 2), enc.codes[6].len);
883 try testing.expectEqual(@as(usize, 6), enc.codes[7].len);
884 try testing.expectEqual(@as(usize, 0), enc.codes[8].len);
885 try testing.expectEqual(@as(usize, 0), enc.codes[9].len);
886 try testing.expectEqual(@as(usize, 0), enc.codes[10].len);
887 try testing.expectEqual(@as(usize, 0), enc.codes[11].len);
888 try testing.expectEqual(@as(usize, 0), enc.codes[12].len);
889 try testing.expectEqual(@as(usize, 0), enc.codes[13].len);
890 try testing.expectEqual(@as(usize, 0), enc.codes[14].len);
891 try testing.expectEqual(@as(usize, 0), enc.codes[15].len);
892 try testing.expectEqual(@as(usize, 6), enc.codes[16].len);
893 try testing.expectEqual(@as(usize, 5), enc.codes[17].len);
894 try testing.expectEqual(@as(usize, 3), enc.codes[18].len);
895
896 try testing.expectEqual(@as(u16, 0x0), enc.codes[5].code);
897 try testing.expectEqual(@as(u16, 0x2), enc.codes[6].code);
898 try testing.expectEqual(@as(u16, 0x1), enc.codes[0].code);
899 try testing.expectEqual(@as(u16, 0x5), enc.codes[4].code);
900 try testing.expectEqual(@as(u16, 0x3), enc.codes[18].code);
901 try testing.expectEqual(@as(u16, 0x7), enc.codes[3].code);
902 try testing.expectEqual(@as(u16, 0x17), enc.codes[17].code);
903 try testing.expectEqual(@as(u16, 0x0f), enc.codes[1].code);
904 try testing.expectEqual(@as(u16, 0x2f), enc.codes[2].code);
905 try testing.expectEqual(@as(u16, 0x1f), enc.codes[7].code);
906 try testing.expectEqual(@as(u16, 0x3f), enc.codes[16].code);
907}
908
909test "generate a Huffman code for the fixed literal table specific to Deflate" {
910 const enc = fixedLiteralEncoder();
911 for (enc.codes) |c| {
912 switch (c.len) {
913 7 => {
914 const v = @bitReverse(@as(u7, @intCast(c.code)));
915 try testing.expect(v <= 0b0010111);
916 },
917 8 => {
918 const v = @bitReverse(@as(u8, @intCast(c.code)));
919 try testing.expect((v >= 0b000110000 and v <= 0b10111111) or
920 (v >= 0b11000000 and v <= 11000111));
921 },
922 9 => {
923 const v = @bitReverse(@as(u9, @intCast(c.code)));
924 try testing.expect(v >= 0b110010000 and v <= 0b111111111);
925 },
926 else => unreachable,
927 }
928 }
929}
930
931test "generate a Huffman code for the 30 possible relative distances (LZ77 distances) of Deflate" {
932 const enc = fixedDistanceEncoder();
933 for (enc.codes) |c| {
934 const v = @bitReverse(@as(u5, @intCast(c.code)));
935 try testing.expect(v <= 29);
936 try testing.expect(c.len == 5);
937 }
938}
939
940// Reverse bit-by-bit a N-bit code.
941fn bitReverse(comptime T: type, value: T, n: usize) T {
942 const r = @bitReverse(value);
943 return r >> @as(math.Log2Int(T), @intCast(@typeInfo(T).int.bits - n));
944}
945
946test bitReverse {
947 const ReverseBitsTest = struct {
948 in: u16,
949 bit_count: u5,
950 out: u16,
951 };
952
953 const reverse_bits_tests = [_]ReverseBitsTest{
954 .{ .in = 1, .bit_count = 1, .out = 1 },
955 .{ .in = 1, .bit_count = 2, .out = 2 },
956 .{ .in = 1, .bit_count = 3, .out = 4 },
957 .{ .in = 1, .bit_count = 4, .out = 8 },
958 .{ .in = 1, .bit_count = 5, .out = 16 },
959 .{ .in = 17, .bit_count = 5, .out = 17 },
960 .{ .in = 257, .bit_count = 9, .out = 257 },
961 .{ .in = 29, .bit_count = 5, .out = 23 },
962 };
963
964 for (reverse_bits_tests) |h| {
965 const v = bitReverse(u16, h.in, h.bit_count);
966 try std.testing.expectEqual(h.out, v);
967 }
968}
969
970test "fixedLiteralEncoder codes" {
971 var al = std.ArrayList(u8).init(testing.allocator);
972 defer al.deinit();
973 var bw = std.Io.bitWriter(.little, al.writer());
974
975 const f = fixedLiteralEncoder();
976 for (f.codes) |c| {
977 try bw.writeBits(c.code, c.len);
978 }
979 try testing.expectEqualSlices(u8, &fixed_codes, al.items);
980}
981
982pub const fixed_codes = [_]u8{
983 0b00001100, 0b10001100, 0b01001100, 0b11001100, 0b00101100, 0b10101100, 0b01101100, 0b11101100,
984 0b00011100, 0b10011100, 0b01011100, 0b11011100, 0b00111100, 0b10111100, 0b01111100, 0b11111100,
985 0b00000010, 0b10000010, 0b01000010, 0b11000010, 0b00100010, 0b10100010, 0b01100010, 0b11100010,
986 0b00010010, 0b10010010, 0b01010010, 0b11010010, 0b00110010, 0b10110010, 0b01110010, 0b11110010,
987 0b00001010, 0b10001010, 0b01001010, 0b11001010, 0b00101010, 0b10101010, 0b01101010, 0b11101010,
988 0b00011010, 0b10011010, 0b01011010, 0b11011010, 0b00111010, 0b10111010, 0b01111010, 0b11111010,
989 0b00000110, 0b10000110, 0b01000110, 0b11000110, 0b00100110, 0b10100110, 0b01100110, 0b11100110,
990 0b00010110, 0b10010110, 0b01010110, 0b11010110, 0b00110110, 0b10110110, 0b01110110, 0b11110110,
991 0b00001110, 0b10001110, 0b01001110, 0b11001110, 0b00101110, 0b10101110, 0b01101110, 0b11101110,
992 0b00011110, 0b10011110, 0b01011110, 0b11011110, 0b00111110, 0b10111110, 0b01111110, 0b11111110,
993 0b00000001, 0b10000001, 0b01000001, 0b11000001, 0b00100001, 0b10100001, 0b01100001, 0b11100001,
994 0b00010001, 0b10010001, 0b01010001, 0b11010001, 0b00110001, 0b10110001, 0b01110001, 0b11110001,
995 0b00001001, 0b10001001, 0b01001001, 0b11001001, 0b00101001, 0b10101001, 0b01101001, 0b11101001,
996 0b00011001, 0b10011001, 0b01011001, 0b11011001, 0b00111001, 0b10111001, 0b01111001, 0b11111001,
997 0b00000101, 0b10000101, 0b01000101, 0b11000101, 0b00100101, 0b10100101, 0b01100101, 0b11100101,
998 0b00010101, 0b10010101, 0b01010101, 0b11010101, 0b00110101, 0b10110101, 0b01110101, 0b11110101,
999 0b00001101, 0b10001101, 0b01001101, 0b11001101, 0b00101101, 0b10101101, 0b01101101, 0b11101101,
1000 0b00011101, 0b10011101, 0b01011101, 0b11011101, 0b00111101, 0b10111101, 0b01111101, 0b11111101,
1001 0b00010011, 0b00100110, 0b01001110, 0b10011010, 0b00111100, 0b01100101, 0b11101010, 0b10110100,
1002 0b11101001, 0b00110011, 0b01100110, 0b11001110, 0b10011010, 0b00111101, 0b01100111, 0b11101110,
1003 0b10111100, 0b11111001, 0b00001011, 0b00010110, 0b00101110, 0b01011010, 0b10111100, 0b01100100,
1004 0b11101001, 0b10110010, 0b11100101, 0b00101011, 0b01010110, 0b10101110, 0b01011010, 0b10111101,
1005 0b01100110, 0b11101101, 0b10111010, 0b11110101, 0b00011011, 0b00110110, 0b01101110, 0b11011010,
1006 0b10111100, 0b01100101, 0b11101011, 0b10110110, 0b11101101, 0b00111011, 0b01110110, 0b11101110,
1007 0b11011010, 0b10111101, 0b01100111, 0b11101111, 0b10111110, 0b11111101, 0b00000111, 0b00001110,
1008 0b00011110, 0b00111010, 0b01111100, 0b11100100, 0b11101000, 0b10110001, 0b11100011, 0b00100111,
1009 0b01001110, 0b10011110, 0b00111010, 0b01111101, 0b11100110, 0b11101100, 0b10111001, 0b11110011,
1010 0b00010111, 0b00101110, 0b01011110, 0b10111010, 0b01111100, 0b11100101, 0b11101010, 0b10110101,
1011 0b11101011, 0b00110111, 0b01101110, 0b11011110, 0b10111010, 0b01111101, 0b11100111, 0b11101110,
1012 0b10111101, 0b11111011, 0b00001111, 0b00011110, 0b00111110, 0b01111010, 0b11111100, 0b11100100,
1013 0b11101001, 0b10110011, 0b11100111, 0b00101111, 0b01011110, 0b10111110, 0b01111010, 0b11111101,
1014 0b11100110, 0b11101101, 0b10111011, 0b11110111, 0b00011111, 0b00111110, 0b01111110, 0b11111010,
1015 0b11111100, 0b11100101, 0b11101011, 0b10110111, 0b11101111, 0b00111111, 0b01111110, 0b11111110,
1016 0b11111010, 0b11111101, 0b11100111, 0b11101111, 0b10111111, 0b11111111, 0b00000000, 0b00100000,
1017 0b00001000, 0b00001100, 0b10000001, 0b11000010, 0b11100000, 0b00001000, 0b00100100, 0b00001010,
1018 0b10001101, 0b11000001, 0b11100010, 0b11110000, 0b00000100, 0b00100010, 0b10001001, 0b01001100,
1019 0b10100001, 0b11010010, 0b11101000, 0b00000011, 0b10000011, 0b01000011, 0b11000011, 0b00100011,
1020 0b10100011,
1021};
1022
1023test "tokenization" {
1024 const L = Token.initLiteral;
1025 const M = Token.initMatch;
1026
1027 const cases = [_]struct {
1028 data: []const u8,
1029 tokens: []const Token,
1030 }{
1031 .{
1032 .data = "Blah blah blah blah blah!",
1033 .tokens = &[_]Token{ L('B'), L('l'), L('a'), L('h'), L(' '), L('b'), M(5, 18), L('!') },
1034 },
1035 .{
1036 .data = "ABCDEABCD ABCDEABCD",
1037 .tokens = &[_]Token{
1038 L('A'), L('B'), L('C'), L('D'), L('E'), L('A'), L('B'), L('C'), L('D'), L(' '),
1039 L('A'), M(10, 8),
1040 },
1041 },
1042 };
1043
1044 for (cases) |c| {
1045 inline for (Container.list) |container| { // for each wrapping
1046
1047 var cw = std.Io.countingWriter(std.Io.null_writer);
1048 const cww = cw.writer();
1049 var df = try Compress(container, @TypeOf(cww), TestTokenWriter).init(cww, .{});
1050
1051 _ = try df.write(c.data);
1052 try df.flush();
1053
1054 // df.token_writer.show();
1055 try expect(df.block_writer.pos == c.tokens.len); // number of tokens written
1056 try testing.expectEqualSlices(Token, df.block_writer.get(), c.tokens); // tokens match
1057
1058 try testing.expectEqual(container.headerSize(), cw.bytes_written);
1059 try df.finish();
1060 try testing.expectEqual(container.size(), cw.bytes_written);
1061 }
1062 }
1063}
1064
1065// Tests that tokens written are equal to expected token list.
1066const TestTokenWriter = struct {
1067 const Self = @This();
1068
1069 pos: usize = 0,
1070 actual: [128]Token = undefined,
1071
1072 pub fn init(_: anytype) Self {
1073 return .{};
1074 }
1075 pub fn write(self: *Self, tokens: []const Token, _: bool, _: ?[]const u8) !void {
1076 for (tokens) |t| {
1077 self.actual[self.pos] = t;
1078 self.pos += 1;
1079 }
1080 }
1081
1082 pub fn storedBlock(_: *Self, _: []const u8, _: bool) !void {}
1083
1084 pub fn get(self: *Self) []Token {
1085 return self.actual[0..self.pos];
1086 }
1087
1088 pub fn show(self: *Self) void {
1089 std.debug.print("\n", .{});
1090 for (self.get()) |t| {
1091 t.show();
1092 }
1093 }
1094
1095 pub fn flush(_: *Self) !void {}
1096};
1097
1098test "file tokenization" {
1099 const levels = [_]Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 };
1100 const cases = [_]struct {
1101 data: []const u8, // uncompressed content
1102 // expected number of tokens producet in deflate tokenization
1103 tokens_count: [levels.len]usize = .{0} ** levels.len,
1104 }{
1105 .{
1106 .data = @embedFile("testdata/rfc1951.txt"),
1107 .tokens_count = .{ 7675, 7672, 7599, 7594, 7598, 7599 },
1108 },
1109
1110 .{
1111 .data = @embedFile("testdata/block_writer/huffman-null-max.input"),
1112 .tokens_count = .{ 257, 257, 257, 257, 257, 257 },
1113 },
1114 .{
1115 .data = @embedFile("testdata/block_writer/huffman-pi.input"),
1116 .tokens_count = .{ 2570, 2564, 2564, 2564, 2564, 2564 },
1117 },
1118 .{
1119 .data = @embedFile("testdata/block_writer/huffman-text.input"),
1120 .tokens_count = .{ 235, 234, 234, 234, 234, 234 },
1121 },
1122 .{
1123 .data = @embedFile("testdata/fuzz/roundtrip1.input"),
1124 .tokens_count = .{ 333, 331, 331, 331, 331, 331 },
1125 },
1126 .{
1127 .data = @embedFile("testdata/fuzz/roundtrip2.input"),
1128 .tokens_count = .{ 334, 334, 334, 334, 334, 334 },
1129 },
1130 };
1131
1132 for (cases) |case| { // for each case
1133 const data = case.data;
1134
1135 for (levels, 0..) |level, i| { // for each compression level
1136 var original: Reader = .fixed(data);
1137
1138 // buffer for decompressed data
1139 var al = std.ArrayList(u8).init(testing.allocator);
1140 defer al.deinit();
1141 const writer = al.writer();
1142
1143 // create compressor
1144 const WriterType = @TypeOf(writer);
1145 const TokenWriter = TokenDecoder(@TypeOf(writer));
1146 var cmp = try Compress(.raw, WriterType, TokenWriter).init(writer, .{ .level = level });
1147
1148 // Stream uncompressed `original` data to the compressor. It will
1149 // produce tokens list and pass that list to the TokenDecoder. This
1150 // TokenDecoder uses CircularBuffer from inflate to convert list of
1151 // tokens back to the uncompressed stream.
1152 try cmp.compress(original.reader());
1153 try cmp.flush();
1154 const expected_count = case.tokens_count[i];
1155 const actual = cmp.block_writer.tokens_count;
1156 if (expected_count == 0) {
1157 std.debug.print("actual token count {d}\n", .{actual});
1158 } else {
1159 try testing.expectEqual(expected_count, actual);
1160 }
1161
1162 try testing.expectEqual(data.len, al.items.len);
1163 try testing.expectEqualSlices(u8, data, al.items);
1164 }
1165 }
1166}
1167
1168const TokenDecoder = struct {
1169 output: *Writer,
1170 tokens_count: usize,
1171
1172 pub fn init(output: *Writer) TokenDecoder {
1173 return .{
1174 .output = output,
1175 .tokens_count = 0,
1176 };
1177 }
1178
1179 pub fn write(self: *TokenDecoder, tokens: []const Token, _: bool, _: ?[]const u8) !void {
1180 self.tokens_count += tokens.len;
1181 for (tokens) |t| {
1182 switch (t.kind) {
1183 .literal => self.hist.write(t.literal()),
1184 .match => try self.hist.writeMatch(t.length(), t.distance()),
1185 }
1186 if (self.hist.free() < 285) try self.flushWin();
1187 }
1188 try self.flushWin();
1189 }
1190
1191 fn flushWin(self: *TokenDecoder) !void {
1192 while (true) {
1193 const buf = self.hist.read();
1194 if (buf.len == 0) break;
1195 try self.output.writeAll(buf);
1196 }
1197 }
1198};
1199
1200test "store simple compressor" {
1201 const data = "Hello world!";
1202 const expected = [_]u8{
1203 0x1, // block type 0, final bit set
1204 0xc, 0x0, // len = 12
1205 0xf3, 0xff, // ~len
1206 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', //
1207 //0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21,
1208 };
1209
1210 var fbs: Reader = .fixed(data);
1211 var al = std.ArrayList(u8).init(testing.allocator);
1212 defer al.deinit();
1213
1214 var cmp = try store.compressor(.raw, al.writer());
1215 try cmp.compress(&fbs);
1216 try cmp.finish();
1217 try testing.expectEqualSlices(u8, &expected, al.items);
1218
1219 fbs = .fixed(data);
1220 try al.resize(0);
1221
1222 // huffman only compresoor will also emit store block for this small sample
1223 var hc = try huffman.compressor(.raw, al.writer());
1224 try hc.compress(&fbs);
1225 try hc.finish();
1226 try testing.expectEqualSlices(u8, &expected, al.items);
1227}
1228
1229test "sliding window match" {
1230 const data = "Blah blah blah blah blah!";
1231 var win: Writer = .{};
1232 try expect(win.write(data) == data.len);
1233 try expect(win.wp == data.len);
1234 try expect(win.rp == 0);
1235
1236 // length between l symbols
1237 try expect(win.match(1, 6, 0) == 18);
1238 try expect(win.match(1, 11, 0) == 13);
1239 try expect(win.match(1, 16, 0) == 8);
1240 try expect(win.match(1, 21, 0) == 0);
1241
1242 // position 15 = "blah blah!"
1243 // position 20 = "blah!"
1244 try expect(win.match(15, 20, 0) == 4);
1245 try expect(win.match(15, 20, 3) == 4);
1246 try expect(win.match(15, 20, 4) == 0);
1247}
1248
1249test "sliding window slide" {
1250 var win: Writer = .{};
1251 win.wp = Writer.buffer_len - 11;
1252 win.rp = Writer.buffer_len - 111;
1253 win.buffer[win.rp] = 0xab;
1254 try expect(win.lookahead().len == 100);
1255 try expect(win.tokensBuffer().?.len == win.rp);
1256
1257 const n = win.slide();
1258 try expect(n == 32757);
1259 try expect(win.buffer[win.rp] == 0xab);
1260 try expect(win.rp == Writer.hist_len - 111);
1261 try expect(win.wp == Writer.hist_len - 11);
1262 try expect(win.lookahead().len == 100);
1263 try expect(win.tokensBuffer() == null);
1264}
lib/std/compress/flate/Decompress.zig created+894
...@@ -0,0 +1,894 @@
1const std = @import("../../std.zig");
2const flate = std.compress.flate;
3const Container = flate.Container;
4const Token = @import("Token.zig");
5const testing = std.testing;
6const Decompress = @This();
7const Writer = std.io.Writer;
8const Reader = std.io.Reader;
9
10input: *Reader,
11reader: Reader,
12/// Hashes, produces checksum, of uncompressed data for gzip/zlib footer.
13hasher: Container.Hasher,
14
15lit_dec: LiteralDecoder,
16dst_dec: DistanceDecoder,
17
18final_block: bool,
19state: State,
20
21read_err: ?Error,
22
23const BlockType = enum(u2) {
24 stored = 0,
25 fixed = 1,
26 dynamic = 2,
27};
28
29const State = union(enum) {
30 protocol_header,
31 block_header,
32 stored_block: u16,
33 fixed_block,
34 dynamic_block,
35 protocol_footer,
36 end,
37};
38
39pub const Error = Container.Error || error{
40 InvalidCode,
41 InvalidMatch,
42 InvalidBlockType,
43 WrongStoredBlockNlen,
44 InvalidDynamicBlockHeader,
45 EndOfStream,
46 ReadFailed,
47 OversubscribedHuffmanTree,
48 IncompleteHuffmanTree,
49 MissingEndOfBlockCode,
50};
51
52pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
53 return .{
54 .reader = .{
55 // TODO populate discard so that when an amount is discarded that
56 // includes an entire frame, skip decoding that frame.
57 .vtable = &.{ .stream = stream },
58 .buffer = buffer,
59 .seek = 0,
60 .end = 0,
61 },
62 .input = input,
63 .hasher = .init(container),
64 .lit_dec = .{},
65 .dst_dec = .{},
66 .final_block = false,
67 .state = .protocol_header,
68 .read_err = null,
69 };
70}
71
72fn decodeLength(self: *Decompress, code: u8) !u16 {
73 if (code > 28) return error.InvalidCode;
74 const ml = Token.matchLength(code);
75 return if (ml.extra_bits == 0) // 0 - 5 extra bits
76 ml.base
77 else
78 ml.base + try self.takeNBitsBuffered(ml.extra_bits);
79}
80
81fn decodeDistance(self: *Decompress, code: u8) !u16 {
82 if (code > 29) return error.InvalidCode;
83 const md = Token.matchDistance(code);
84 return if (md.extra_bits == 0) // 0 - 13 extra bits
85 md.base
86 else
87 md.base + try self.takeNBitsBuffered(md.extra_bits);
88}
89
90// Decode code length symbol to code length. Writes decoded length into
91// lens slice starting at position pos. Returns number of positions
92// advanced.
93fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usize {
94 if (pos >= lens.len)
95 return error.InvalidDynamicBlockHeader;
96
97 switch (code) {
98 0...15 => {
99 // Represent code lengths of 0 - 15
100 lens[pos] = @intCast(code);
101 return 1;
102 },
103 16 => {
104 // Copy the previous code length 3 - 6 times.
105 // The next 2 bits indicate repeat length
106 const n: u8 = @as(u8, try self.takeBits(u2)) + 3;
107 if (pos == 0 or pos + n > lens.len)
108 return error.InvalidDynamicBlockHeader;
109 for (0..n) |i| {
110 lens[pos + i] = lens[pos + i - 1];
111 }
112 return n;
113 },
114 // Repeat a code length of 0 for 3 - 10 times. (3 bits of length)
115 17 => return @as(u8, try self.takeBits(u3)) + 3,
116 // Repeat a code length of 0 for 11 - 138 times (7 bits of length)
117 18 => return @as(u8, try self.takeBits(u7)) + 11,
118 else => return error.InvalidDynamicBlockHeader,
119 }
120}
121
122// Peek 15 bits from bits reader (maximum code len is 15 bits). Use
123// decoder to find symbol for that code. We then know how many bits is
124// used. Shift bit reader for that much bits, those bits are used. And
125// return symbol.
126fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol {
127 const sym = try decoder.find(try self.peekBitsReverseBuffered(u15));
128 try self.shiftBits(sym.code_bits);
129 return sym;
130}
131
132pub fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
133 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
134 return readInner(d, w, limit) catch |err| switch (err) {
135 error.EndOfStream => return error.EndOfStream,
136 error.WriteFailed => return error.WriteFailed,
137 else => |e| {
138 // In the event of an error, state is unmodified so that it can be
139 // better used to diagnose the failure.
140 d.read_err = e;
141 return error.ReadFailed;
142 },
143 };
144}
145
146fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.StreamError)!usize {
147 const in = d.input;
148 sw: switch (d.state) {
149 .protocol_header => switch (d.hasher.container()) {
150 .gzip => {
151 const Header = extern struct {
152 magic: u16 align(1),
153 method: u8,
154 flags: packed struct(u8) {
155 text: bool,
156 hcrc: bool,
157 extra: bool,
158 name: bool,
159 comment: bool,
160 reserved: u3,
161 },
162 mtime: u32 align(1),
163 xfl: u8,
164 os: u8,
165 };
166 const header = try in.takeStruct(Header, .little);
167 if (header.magic != 0x8b1f or header.method != 0x08)
168 return error.BadGzipHeader;
169 if (header.flags.extra) {
170 const extra_len = try in.takeInt(u16, .little);
171 try in.discardAll(extra_len);
172 }
173 if (header.flags.name) {
174 _ = try in.discardDelimiterInclusive(0);
175 }
176 if (header.flags.comment) {
177 _ = try in.discardDelimiterInclusive(0);
178 }
179 if (header.flags.hcrc) {
180 try in.discardAll(2);
181 }
182 continue :sw .block_header;
183 },
184 .zlib => {
185 const Header = extern struct {
186 cmf: packed struct(u8) {
187 cm: u4,
188 cinfo: u4,
189 },
190 flg: u8,
191 };
192 const header = try in.takeStruct(Header);
193 if (header.cmf.cm != 8 or header.cmf.cinfo > 7) return error.BadZlibHeader;
194 continue :sw .block_header;
195 },
196 .raw => continue :sw .block_header,
197 },
198 .block_header => {
199 d.final_block = (try d.takeBits(u1)) != 0;
200 const block_type = try d.takeBits(BlockType);
201 switch (block_type) {
202 .stored => {
203 d.alignBitsToByte(); // skip padding until byte boundary
204 // everything after this is byte aligned in stored block
205 const len = try in.takeInt(u16, .little);
206 const nlen = try in.takeInt(u16, .little);
207 if (len != ~nlen) return error.WrongStoredBlockNlen;
208 continue :sw .{ .stored_block = len };
209 },
210 .fixed => continue :sw .fixed_block,
211 .dynamic => {
212 const hlit: u16 = @as(u16, try d.takeBits(u5)) + 257; // number of ll code entries present - 257
213 const hdist: u16 = @as(u16, try d.takeBits(u5)) + 1; // number of distance code entries - 1
214 const hclen: u8 = @as(u8, try d.takeBits(u4)) + 4; // hclen + 4 code lengths are encoded
215
216 if (hlit > 286 or hdist > 30)
217 return error.InvalidDynamicBlockHeader;
218
219 // lengths for code lengths
220 var cl_lens = [_]u4{0} ** 19;
221 for (0..hclen) |i| {
222 cl_lens[flate.huffman.codegen_order[i]] = try d.takeBits(u3);
223 }
224 var cl_dec: CodegenDecoder = .{};
225 try cl_dec.generate(&cl_lens);
226
227 // decoded code lengths
228 var dec_lens = [_]u4{0} ** (286 + 30);
229 var pos: usize = 0;
230 while (pos < hlit + hdist) {
231 const sym = try cl_dec.find(try d.peekBitsReverse(u7));
232 try d.shiftBits(sym.code_bits);
233 pos += try d.dynamicCodeLength(sym.symbol, &dec_lens, pos);
234 }
235 if (pos > hlit + hdist) {
236 return error.InvalidDynamicBlockHeader;
237 }
238
239 // literal code lengths to literal decoder
240 try d.lit_dec.generate(dec_lens[0..hlit]);
241
242 // distance code lengths to distance decoder
243 try d.dst_dec.generate(dec_lens[hlit .. hlit + hdist]);
244
245 continue :sw .dynamic_block;
246 },
247 }
248 },
249 .stored_block => |remaining_len| {
250 const out = try w.writableSliceGreedyPreserve(flate.history_len, 1);
251 const limited_out = limit.min(.limited(remaining_len)).slice(out);
252 const n = try d.input.readVec(&.{limited_out});
253 if (remaining_len - n == 0) {
254 d.state = if (d.final_block) .protocol_footer else .block_header;
255 } else {
256 d.state = .{ .stored_block = @intCast(remaining_len - n) };
257 }
258 w.advance(n);
259 return n;
260 },
261 .fixed_block => {
262 const start = w.count;
263 while (@intFromEnum(limit) > w.count - start) {
264 const code = try d.readFixedCode();
265 switch (code) {
266 0...255 => try w.writeBytePreserve(flate.history_len, @intCast(code)),
267 256 => {
268 d.state = if (d.final_block) .protocol_footer else .block_header;
269 return w.count - start;
270 },
271 257...285 => {
272 // Handles fixed block non literal (length) code.
273 // Length code is followed by 5 bits of distance code.
274 const length = try d.decodeLength(@intCast(code - 257));
275 const distance = try d.decodeDistance(try d.takeBitsReverseBuffered(u5));
276 try writeMatch(w, length, distance);
277 },
278 else => return error.InvalidCode,
279 }
280 }
281 d.state = .fixed_block;
282 return w.count - start;
283 },
284 .dynamic_block => {
285 // In larger archives most blocks are usually dynamic, so decompression
286 // performance depends on this logic.
287 const start = w.count;
288 while (@intFromEnum(limit) > w.count - start) {
289 const sym = try d.decodeSymbol(&d.lit_dec);
290
291 switch (sym.kind) {
292 .literal => try w.writeBytePreserve(flate.history_len, sym.symbol),
293 .match => {
294 // Decode match backreference <length, distance>
295 const length = try d.decodeLength(sym.symbol);
296 const dsm = try d.decodeSymbol(&d.dst_dec);
297 const distance = try d.decodeDistance(dsm.symbol);
298 try writeMatch(w, length, distance);
299 },
300 .end_of_block => {
301 d.state = if (d.final_block) .protocol_footer else .block_header;
302 return w.count - start;
303 },
304 }
305 }
306 d.state = .dynamic_block;
307 return w.count - start;
308 },
309 .protocol_footer => {
310 d.alignBitsToByte();
311 switch (d.hasher) {
312 .gzip => |*gzip| {
313 if (try in.takeInt(u32, .little) != gzip.crc.final()) return error.WrongGzipChecksum;
314 if (try in.takeInt(u32, .little) != gzip.count) return error.WrongGzipSize;
315 },
316 .zlib => |*zlib| {
317 const chksum: u32 = @byteSwap(zlib.final());
318 if (try in.takeInt(u32, .big) != chksum) return error.WrongZlibChecksum;
319 },
320 .raw => {},
321 }
322 d.state = .end;
323 return 0;
324 },
325 .end => return error.EndOfStream,
326 }
327}
328
329/// Write match (back-reference to the same data slice) starting at `distance`
330/// back from current write position, and `length` of bytes.
331fn writeMatch(bw: *Writer, length: u16, distance: u16) !void {
332 _ = bw;
333 _ = length;
334 _ = distance;
335 @panic("TODO");
336}
337
338fn takeBits(d: *Decompress, comptime T: type) !T {
339 _ = d;
340 @panic("TODO");
341}
342
343fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
344 _ = d;
345 @panic("TODO");
346}
347
348fn takeNBitsBuffered(d: *Decompress, n: u4) !u16 {
349 _ = d;
350 _ = n;
351 @panic("TODO");
352}
353
354fn peekBitsReverse(d: *Decompress, comptime T: type) !T {
355 _ = d;
356 @panic("TODO");
357}
358
359fn peekBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
360 _ = d;
361 @panic("TODO");
362}
363
364fn alignBitsToByte(d: *Decompress) void {
365 _ = d;
366 @panic("TODO");
367}
368
369fn shiftBits(d: *Decompress, n: u6) !void {
370 _ = d;
371 _ = n;
372 @panic("TODO");
373}
374
375fn readFixedCode(d: *Decompress) !u16 {
376 _ = d;
377 @panic("TODO");
378}
379
380pub const Symbol = packed struct {
381 pub const Kind = enum(u2) {
382 literal,
383 end_of_block,
384 match,
385 };
386
387 symbol: u8 = 0, // symbol from alphabet
388 code_bits: u4 = 0, // number of bits in code 0-15
389 kind: Kind = .literal,
390
391 code: u16 = 0, // huffman code of the symbol
392 next: u16 = 0, // pointer to the next symbol in linked list
393 // it is safe to use 0 as null pointer, when sorted 0 has shortest code and fits into lookup
394
395 // Sorting less than function.
396 pub fn asc(_: void, a: Symbol, b: Symbol) bool {
397 if (a.code_bits == b.code_bits) {
398 if (a.kind == b.kind) {
399 return a.symbol < b.symbol;
400 }
401 return @intFromEnum(a.kind) < @intFromEnum(b.kind);
402 }
403 return a.code_bits < b.code_bits;
404 }
405};
406
407pub const LiteralDecoder = HuffmanDecoder(286, 15, 9);
408pub const DistanceDecoder = HuffmanDecoder(30, 15, 9);
409pub const CodegenDecoder = HuffmanDecoder(19, 7, 7);
410
411/// Creates huffman tree codes from list of code lengths (in `build`).
412///
413/// `find` then finds symbol for code bits. Code can be any length between 1 and
414/// 15 bits. When calling `find` we don't know how many bits will be used to
415/// find symbol. When symbol is returned it has code_bits field which defines
416/// how much we should advance in bit stream.
417///
418/// Lookup table is used to map 15 bit int to symbol. Same symbol is written
419/// many times in this table; 32K places for 286 (at most) symbols.
420/// Small lookup table is optimization for faster search.
421/// It is variation of the algorithm explained in [zlib](https://github.com/madler/zlib/blob/643e17b7498d12ab8d15565662880579692f769d/doc/algorithm.txt#L92)
422/// with difference that we here use statically allocated arrays.
423///
424fn HuffmanDecoder(
425 comptime alphabet_size: u16,
426 comptime max_code_bits: u4,
427 comptime lookup_bits: u4,
428) type {
429 const lookup_shift = max_code_bits - lookup_bits;
430
431 return struct {
432 // all symbols in alaphabet, sorted by code_len, symbol
433 symbols: [alphabet_size]Symbol = undefined,
434 // lookup table code -> symbol
435 lookup: [1 << lookup_bits]Symbol = undefined,
436
437 const Self = @This();
438
439 /// Generates symbols and lookup tables from list of code lens for each symbol.
440 pub fn generate(self: *Self, lens: []const u4) !void {
441 try checkCompleteness(lens);
442
443 // init alphabet with code_bits
444 for (self.symbols, 0..) |_, i| {
445 const cb: u4 = if (i < lens.len) lens[i] else 0;
446 self.symbols[i] = if (i < 256)
447 .{ .kind = .literal, .symbol = @intCast(i), .code_bits = cb }
448 else if (i == 256)
449 .{ .kind = .end_of_block, .symbol = 0xff, .code_bits = cb }
450 else
451 .{ .kind = .match, .symbol = @intCast(i - 257), .code_bits = cb };
452 }
453 std.sort.heap(Symbol, &self.symbols, {}, Symbol.asc);
454
455 // reset lookup table
456 for (0..self.lookup.len) |i| {
457 self.lookup[i] = .{};
458 }
459
460 // assign code to symbols
461 // reference: https://youtu.be/9_YEGLe33NA?list=PLU4IQLU9e_OrY8oASHx0u3IXAL9TOdidm&t=2639
462 var code: u16 = 0;
463 var idx: u16 = 0;
464 for (&self.symbols, 0..) |*sym, pos| {
465 if (sym.code_bits == 0) continue; // skip unused
466 sym.code = code;
467
468 const next_code = code + (@as(u16, 1) << (max_code_bits - sym.code_bits));
469 const next_idx = next_code >> lookup_shift;
470
471 if (next_idx > self.lookup.len or idx >= self.lookup.len) break;
472 if (sym.code_bits <= lookup_bits) {
473 // fill small lookup table
474 for (idx..next_idx) |j|
475 self.lookup[j] = sym.*;
476 } else {
477 // insert into linked table starting at root
478 const root = &self.lookup[idx];
479 const root_next = root.next;
480 root.next = @intCast(pos);
481 sym.next = root_next;
482 }
483
484 idx = next_idx;
485 code = next_code;
486 }
487 }
488
489 /// Given the list of code lengths check that it represents a canonical
490 /// Huffman code for n symbols.
491 ///
492 /// Reference: https://github.com/madler/zlib/blob/5c42a230b7b468dff011f444161c0145b5efae59/contrib/puff/puff.c#L340
493 fn checkCompleteness(lens: []const u4) !void {
494 if (alphabet_size == 286)
495 if (lens[256] == 0) return error.MissingEndOfBlockCode;
496
497 var count = [_]u16{0} ** (@as(usize, max_code_bits) + 1);
498 var max: usize = 0;
499 for (lens) |n| {
500 if (n == 0) continue;
501 if (n > max) max = n;
502 count[n] += 1;
503 }
504 if (max == 0) // empty tree
505 return;
506
507 // check for an over-subscribed or incomplete set of lengths
508 var left: usize = 1; // one possible code of zero length
509 for (1..count.len) |len| {
510 left <<= 1; // one more bit, double codes left
511 if (count[len] > left)
512 return error.OversubscribedHuffmanTree;
513 left -= count[len]; // deduct count from possible codes
514 }
515 if (left > 0) { // left > 0 means incomplete
516 // incomplete code ok only for single length 1 code
517 if (max_code_bits > 7 and max == count[0] + count[1]) return;
518 return error.IncompleteHuffmanTree;
519 }
520 }
521
522 /// Finds symbol for lookup table code.
523 pub fn find(self: *Self, code: u16) !Symbol {
524 // try to find in lookup table
525 const idx = code >> lookup_shift;
526 const sym = self.lookup[idx];
527 if (sym.code_bits != 0) return sym;
528 // if not use linked list of symbols with same prefix
529 return self.findLinked(code, sym.next);
530 }
531
532 inline fn findLinked(self: *Self, code: u16, start: u16) !Symbol {
533 var pos = start;
534 while (pos > 0) {
535 const sym = self.symbols[pos];
536 const shift = max_code_bits - sym.code_bits;
537 // compare code_bits number of upper bits
538 if ((code ^ sym.code) >> shift == 0) return sym;
539 pos = sym.next;
540 }
541 return error.InvalidCode;
542 }
543 };
544}
545
546test "init/find" {
547 // example data from: https://youtu.be/SJPvNi4HrWQ?t=8423
548 const code_lens = [_]u4{ 4, 3, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 2 };
549 var h: CodegenDecoder = .{};
550 try h.generate(&code_lens);
551
552 const expected = [_]struct {
553 sym: Symbol,
554 code: u16,
555 }{
556 .{
557 .code = 0b00_00000,
558 .sym = .{ .symbol = 3, .code_bits = 2 },
559 },
560 .{
561 .code = 0b01_00000,
562 .sym = .{ .symbol = 18, .code_bits = 2 },
563 },
564 .{
565 .code = 0b100_0000,
566 .sym = .{ .symbol = 1, .code_bits = 3 },
567 },
568 .{
569 .code = 0b101_0000,
570 .sym = .{ .symbol = 4, .code_bits = 3 },
571 },
572 .{
573 .code = 0b110_0000,
574 .sym = .{ .symbol = 17, .code_bits = 3 },
575 },
576 .{
577 .code = 0b1110_000,
578 .sym = .{ .symbol = 0, .code_bits = 4 },
579 },
580 .{
581 .code = 0b1111_000,
582 .sym = .{ .symbol = 16, .code_bits = 4 },
583 },
584 };
585
586 // unused symbols
587 for (0..12) |i| {
588 try testing.expectEqual(0, h.symbols[i].code_bits);
589 }
590 // used, from index 12
591 for (expected, 12..) |e, i| {
592 try testing.expectEqual(e.sym.symbol, h.symbols[i].symbol);
593 try testing.expectEqual(e.sym.code_bits, h.symbols[i].code_bits);
594 const sym_from_code = try h.find(e.code);
595 try testing.expectEqual(e.sym.symbol, sym_from_code.symbol);
596 }
597
598 // All possible codes for each symbol.
599 // Lookup table has 126 elements, to cover all possible 7 bit codes.
600 for (0b0000_000..0b0100_000) |c| // 0..32 (32)
601 try testing.expectEqual(3, (try h.find(@intCast(c))).symbol);
602
603 for (0b0100_000..0b1000_000) |c| // 32..64 (32)
604 try testing.expectEqual(18, (try h.find(@intCast(c))).symbol);
605
606 for (0b1000_000..0b1010_000) |c| // 64..80 (16)
607 try testing.expectEqual(1, (try h.find(@intCast(c))).symbol);
608
609 for (0b1010_000..0b1100_000) |c| // 80..96 (16)
610 try testing.expectEqual(4, (try h.find(@intCast(c))).symbol);
611
612 for (0b1100_000..0b1110_000) |c| // 96..112 (16)
613 try testing.expectEqual(17, (try h.find(@intCast(c))).symbol);
614
615 for (0b1110_000..0b1111_000) |c| // 112..120 (8)
616 try testing.expectEqual(0, (try h.find(@intCast(c))).symbol);
617
618 for (0b1111_000..0b1_0000_000) |c| // 120...128 (8)
619 try testing.expectEqual(16, (try h.find(@intCast(c))).symbol);
620}
621
622test "encode/decode literals" {
623 const LiteralEncoder = std.compress.flate.Compress.LiteralEncoder;
624
625 for (1..286) |j| { // for all different number of codes
626 var enc: LiteralEncoder = .{};
627 // create frequencies
628 var freq = [_]u16{0} ** 286;
629 freq[256] = 1; // ensure we have end of block code
630 for (&freq, 1..) |*f, i| {
631 if (i % j == 0)
632 f.* = @intCast(i);
633 }
634
635 // encoder from frequencies
636 enc.generate(&freq, 15);
637
638 // get code_lens from encoder
639 var code_lens = [_]u4{0} ** 286;
640 for (code_lens, 0..) |_, i| {
641 code_lens[i] = @intCast(enc.codes[i].len);
642 }
643 // generate decoder from code lens
644 var dec: LiteralDecoder = .{};
645 try dec.generate(&code_lens);
646
647 // expect decoder code to match original encoder code
648 for (dec.symbols) |s| {
649 if (s.code_bits == 0) continue;
650 const c_code: u16 = @bitReverse(@as(u15, @intCast(s.code)));
651 const symbol: u16 = switch (s.kind) {
652 .literal => s.symbol,
653 .end_of_block => 256,
654 .match => @as(u16, s.symbol) + 257,
655 };
656
657 const c = enc.codes[symbol];
658 try testing.expect(c.code == c_code);
659 }
660
661 // find each symbol by code
662 for (enc.codes) |c| {
663 if (c.len == 0) continue;
664
665 const s_code: u15 = @bitReverse(@as(u15, @intCast(c.code)));
666 const s = try dec.find(s_code);
667 try testing.expect(s.code == s_code);
668 try testing.expect(s.code_bits == c.len);
669 }
670 }
671}
672
673test "decompress" {
674 const cases = [_]struct {
675 in: []const u8,
676 out: []const u8,
677 }{
678 // non compressed block (type 0)
679 .{
680 .in = &[_]u8{
681 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
682 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
683 },
684 .out = "Hello world\n",
685 },
686 // fixed code block (type 1)
687 .{
688 .in = &[_]u8{
689 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
690 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
691 },
692 .out = "Hello world\n",
693 },
694 // dynamic block (type 2)
695 .{
696 .in = &[_]u8{
697 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
698 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
699 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
700 },
701 .out = "ABCDEABCD ABCDEABCD",
702 },
703 };
704 for (cases) |c| {
705 var fb: Reader = .fixed(c.in);
706 var aw: Writer.Allocating = .init(testing.allocator);
707 defer aw.deinit();
708
709 var decompress: Decompress = .init(&fb, .raw, &.{});
710 const r = &decompress.reader;
711 _ = try r.streamRemaining(&aw.writer);
712 try testing.expectEqualStrings(c.out, aw.getWritten());
713 }
714}
715
716test "gzip decompress" {
717 const cases = [_]struct {
718 in: []const u8,
719 out: []const u8,
720 }{
721 // non compressed block (type 0)
722 .{
723 .in = &[_]u8{
724 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // gzip header (10 bytes)
725 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
726 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
727 0xd5, 0xe0, 0x39, 0xb7, // gzip footer: checksum
728 0x0c, 0x00, 0x00, 0x00, // gzip footer: size
729 },
730 .out = "Hello world\n",
731 },
732 // fixed code block (type 1)
733 .{
734 .in = &[_]u8{
735 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, // gzip header (10 bytes)
736 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
737 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
738 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00, // gzip footer (chksum, len)
739 },
740 .out = "Hello world\n",
741 },
742 // dynamic block (type 2)
743 .{
744 .in = &[_]u8{
745 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // gzip header (10 bytes)
746 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
747 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
748 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
749 0x17, 0x1c, 0x39, 0xb4, 0x13, 0x00, 0x00, 0x00, // gzip footer (chksum, len)
750 },
751 .out = "ABCDEABCD ABCDEABCD",
752 },
753 // gzip header with name
754 .{
755 .in = &[_]u8{
756 0x1f, 0x8b, 0x08, 0x08, 0xe5, 0x70, 0xb1, 0x65, 0x00, 0x03, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e,
757 0x74, 0x78, 0x74, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1,
758 0x02, 0x00, 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00,
759 },
760 .out = "Hello world\n",
761 },
762 };
763 for (cases) |c| {
764 var fb: Reader = .fixed(c.in);
765 var aw: Writer.Allocating = .init(testing.allocator);
766 defer aw.deinit();
767
768 var decompress: Decompress = .init(&fb, .gzip, &.{});
769 const r = &decompress.reader;
770 _ = try r.streamRemaining(&aw.writer);
771 try testing.expectEqualStrings(c.out, aw.getWritten());
772 }
773}
774
775test "zlib decompress" {
776 const cases = [_]struct {
777 in: []const u8,
778 out: []const u8,
779 }{
780 // non compressed block (type 0)
781 .{
782 .in = &[_]u8{
783 0x78, 0b10_0_11100, // zlib header (2 bytes)
784 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
785 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
786 0x1c, 0xf2, 0x04, 0x47, // zlib footer: checksum
787 },
788 .out = "Hello world\n",
789 },
790 };
791 for (cases) |c| {
792 var fb: Reader = .fixed(c.in);
793 var aw: Writer.Allocating = .init(testing.allocator);
794 defer aw.deinit();
795
796 var decompress: Decompress = .init(&fb, .zlib, &.{});
797 const r = &decompress.reader;
798 _ = try r.streamRemaining(&aw.writer);
799 try testing.expectEqualStrings(c.out, aw.getWritten());
800 }
801}
802
803test "fuzzing tests" {
804 const cases = [_]struct {
805 input: []const u8,
806 out: []const u8 = "",
807 err: ?anyerror = null,
808 }{
809 .{ .input = "deflate-stream", .out = @embedFile("testdata/fuzz/deflate-stream.expect") }, // 0
810 .{ .input = "empty-distance-alphabet01" },
811 .{ .input = "empty-distance-alphabet02" },
812 .{ .input = "end-of-stream", .err = error.EndOfStream },
813 .{ .input = "invalid-distance", .err = error.InvalidMatch },
814 .{ .input = "invalid-tree01", .err = error.IncompleteHuffmanTree }, // 5
815 .{ .input = "invalid-tree02", .err = error.IncompleteHuffmanTree },
816 .{ .input = "invalid-tree03", .err = error.IncompleteHuffmanTree },
817 .{ .input = "lengths-overflow", .err = error.InvalidDynamicBlockHeader },
818 .{ .input = "out-of-codes", .err = error.InvalidCode },
819 .{ .input = "puff01", .err = error.WrongStoredBlockNlen }, // 10
820 .{ .input = "puff02", .err = error.EndOfStream },
821 .{ .input = "puff03", .out = &[_]u8{0xa} },
822 .{ .input = "puff04", .err = error.InvalidCode },
823 .{ .input = "puff05", .err = error.EndOfStream },
824 .{ .input = "puff06", .err = error.EndOfStream },
825 .{ .input = "puff08", .err = error.InvalidCode },
826 .{ .input = "puff09", .out = "P" },
827 .{ .input = "puff10", .err = error.InvalidCode },
828 .{ .input = "puff11", .err = error.InvalidMatch },
829 .{ .input = "puff12", .err = error.InvalidDynamicBlockHeader }, // 20
830 .{ .input = "puff13", .err = error.IncompleteHuffmanTree },
831 .{ .input = "puff14", .err = error.EndOfStream },
832 .{ .input = "puff15", .err = error.IncompleteHuffmanTree },
833 .{ .input = "puff16", .err = error.InvalidDynamicBlockHeader },
834 .{ .input = "puff17", .err = error.MissingEndOfBlockCode }, // 25
835 .{ .input = "fuzz1", .err = error.InvalidDynamicBlockHeader },
836 .{ .input = "fuzz2", .err = error.InvalidDynamicBlockHeader },
837 .{ .input = "fuzz3", .err = error.InvalidMatch },
838 .{ .input = "fuzz4", .err = error.OversubscribedHuffmanTree },
839 .{ .input = "puff18", .err = error.OversubscribedHuffmanTree }, // 30
840 .{ .input = "puff19", .err = error.OversubscribedHuffmanTree },
841 .{ .input = "puff20", .err = error.OversubscribedHuffmanTree },
842 .{ .input = "puff21", .err = error.OversubscribedHuffmanTree },
843 .{ .input = "puff22", .err = error.OversubscribedHuffmanTree },
844 .{ .input = "puff23", .err = error.OversubscribedHuffmanTree }, // 35
845 .{ .input = "puff24", .err = error.IncompleteHuffmanTree },
846 .{ .input = "puff25", .err = error.OversubscribedHuffmanTree },
847 .{ .input = "puff26", .err = error.InvalidDynamicBlockHeader },
848 .{ .input = "puff27", .err = error.InvalidDynamicBlockHeader },
849 };
850
851 inline for (cases, 0..) |c, case_no| {
852 var in: Reader = .fixed(@embedFile("testdata/fuzz/" ++ c.input ++ ".input"));
853 var aw: Writer.Allocating = .init(testing.allocator);
854 defer aw.deinit();
855 errdefer std.debug.print("test case failed {}\n", .{case_no});
856
857 var decompress: Decompress = .init(&in, .raw, &.{});
858 const r = &decompress.reader;
859 if (c.err) |expected_err| {
860 try testing.expectError(error.ReadFailed, r.streamRemaining(&aw.writer));
861 try testing.expectError(expected_err, decompress.read_err.?);
862 } else {
863 _ = try r.streamRemaining(&aw.writer);
864 try testing.expectEqualStrings(c.out, aw.getWritten());
865 }
866 }
867}
868
869test "bug 18966" {
870 const input = @embedFile("testdata/fuzz/bug_18966.input");
871 const expect = @embedFile("testdata/fuzz/bug_18966.expect");
872
873 var in: Reader = .fixed(input);
874 var aw: Writer.Allocating = .init(testing.allocator);
875 defer aw.deinit();
876
877 var decompress: Decompress = .init(&in, .gzip, &.{});
878 const r = &decompress.reader;
879 _ = try r.streamRemaining(&aw.writer);
880 try testing.expectEqualStrings(expect, aw.getWritten());
881}
882
883test "reading into empty buffer" {
884 // Inspired by https://github.com/ziglang/zig/issues/19895
885 const input = &[_]u8{
886 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
887 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
888 };
889 var in: Reader = .fixed(input);
890 var decomp: Decompress = .init(&in, .raw, &.{});
891 const r = &decomp.reader;
892 var buf: [0]u8 = undefined;
893 try testing.expectEqual(0, try r.readVec(&.{&buf}));
894}
lib/std/compress/flate/Lookup.zig+15-15
...@@ -5,22 +5,22 @@...@@ -5,22 +5,22 @@
5const std = @import("std");5const std = @import("std");
6const testing = std.testing;6const testing = std.testing;
7const expect = testing.expect;7const expect = testing.expect;
8const consts = @import("consts.zig");8const flate = @import("../flate.zig");
99
10const Self = @This();10const Lookup = @This();
1111
12const prime4 = 0x9E3779B1; // 4 bytes prime number 265443576112const prime4 = 0x9E3779B1; // 4 bytes prime number 2654435761
13const chain_len = 2 * consts.history.len;13const chain_len = 2 * flate.history_len;
1414
15// Maps hash => first position15// Maps hash => first position
16head: [consts.lookup.len]u16 = [_]u16{0} ** consts.lookup.len,16head: [flate.lookup.len]u16 = [_]u16{0} ** flate.lookup.len,
17// Maps position => previous positions for the same hash value17// Maps position => previous positions for the same hash value
18chain: [chain_len]u16 = [_]u16{0} ** (chain_len),18chain: [chain_len]u16 = [_]u16{0} ** (chain_len),
1919
20// Calculates hash of the 4 bytes from data.20// Calculates hash of the 4 bytes from data.
21// Inserts `pos` position of that hash in the lookup tables.21// Inserts `pos` position of that hash in the lookup tables.
22// Returns previous location with the same hash value.22// Returns previous location with the same hash value.
23pub fn add(self: *Self, data: []const u8, pos: u16) u16 {23pub fn add(self: *Lookup, data: []const u8, pos: u16) u16 {
24 if (data.len < 4) return 0;24 if (data.len < 4) return 0;
25 const h = hash(data[0..4]);25 const h = hash(data[0..4]);
26 return self.set(h, pos);26 return self.set(h, pos);
...@@ -28,11 +28,11 @@ pub fn add(self: *Self, data: []const u8, pos: u16) u16 {...@@ -28,11 +28,11 @@ pub fn add(self: *Self, data: []const u8, pos: u16) u16 {
2828
29// Returns previous location with the same hash value given the current29// Returns previous location with the same hash value given the current
30// position.30// position.
31pub fn prev(self: *Self, pos: u16) u16 {31pub fn prev(self: *Lookup, pos: u16) u16 {
32 return self.chain[pos];32 return self.chain[pos];
33}33}
3434
35fn set(self: *Self, h: u32, pos: u16) u16 {35fn set(self: *Lookup, h: u32, pos: u16) u16 {
36 const p = self.head[h];36 const p = self.head[h];
37 self.head[h] = pos;37 self.head[h] = pos;
38 self.chain[pos] = p;38 self.chain[pos] = p;
...@@ -40,7 +40,7 @@ fn set(self: *Self, h: u32, pos: u16) u16 {...@@ -40,7 +40,7 @@ fn set(self: *Self, h: u32, pos: u16) u16 {
40}40}
4141
42// Slide all positions in head and chain for `n`42// Slide all positions in head and chain for `n`
43pub fn slide(self: *Self, n: u16) void {43pub fn slide(self: *Lookup, n: u16) void {
44 for (&self.head) |*v| {44 for (&self.head) |*v| {
45 v.* -|= n;45 v.* -|= n;
46 }46 }
...@@ -52,8 +52,8 @@ pub fn slide(self: *Self, n: u16) void {...@@ -52,8 +52,8 @@ pub fn slide(self: *Self, n: u16) void {
5252
53// Add `len` 4 bytes hashes from `data` into lookup.53// Add `len` 4 bytes hashes from `data` into lookup.
54// Position of the first byte is `pos`.54// Position of the first byte is `pos`.
55pub fn bulkAdd(self: *Self, data: []const u8, len: u16, pos: u16) void {55pub fn bulkAdd(self: *Lookup, data: []const u8, len: u16, pos: u16) void {
56 if (len == 0 or data.len < consts.match.min_length) {56 if (len == 0 or data.len < flate.match.min_length) {
57 return;57 return;
58 }58 }
59 var hb =59 var hb =
...@@ -80,7 +80,7 @@ fn hash(b: *const [4]u8) u32 {...@@ -80,7 +80,7 @@ fn hash(b: *const [4]u8) u32 {
80}80}
8181
82fn hashu(v: u32) u32 {82fn hashu(v: u32) u32 {
83 return @intCast((v *% prime4) >> consts.lookup.shift);83 return @intCast((v *% prime4) >> flate.lookup.shift);
84}84}
8585
86test add {86test add {
...@@ -91,7 +91,7 @@ test add {...@@ -91,7 +91,7 @@ test add {
91 0x01, 0x02, 0x03,91 0x01, 0x02, 0x03,
92 };92 };
9393
94 var h: Self = .{};94 var h: Lookup = .{};
95 for (data, 0..) |_, i| {95 for (data, 0..) |_, i| {
96 const p = h.add(data[i..], @intCast(i));96 const p = h.add(data[i..], @intCast(i));
97 if (i >= 8 and i < 24) {97 if (i >= 8 and i < 24) {
...@@ -101,7 +101,7 @@ test add {...@@ -101,7 +101,7 @@ test add {
101 }101 }
102 }102 }
103103
104 const v = Self.hash(data[2 .. 2 + 4]);104 const v = Lookup.hash(data[2 .. 2 + 4]);
105 try expect(h.head[v] == 2 + 16);105 try expect(h.head[v] == 2 + 16);
106 try expect(h.chain[2 + 16] == 2 + 8);106 try expect(h.chain[2 + 16] == 2 + 8);
107 try expect(h.chain[2 + 8] == 2);107 try expect(h.chain[2 + 8] == 2);
...@@ -111,13 +111,13 @@ test bulkAdd {...@@ -111,13 +111,13 @@ test bulkAdd {
111 const data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";111 const data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
112112
113 // one by one113 // one by one
114 var h: Self = .{};114 var h: Lookup = .{};
115 for (data, 0..) |_, i| {115 for (data, 0..) |_, i| {
116 _ = h.add(data[i..], @intCast(i));116 _ = h.add(data[i..], @intCast(i));
117 }117 }
118118
119 // in bulk119 // in bulk
120 var bh: Self = .{};120 var bh: Lookup = .{};
121 bh.bulkAdd(data, data.len, 0);121 bh.bulkAdd(data, data.len, 0);
122122
123 try testing.expectEqualSlices(u16, &h.head, &bh.head);123 try testing.expectEqualSlices(u16, &h.head, &bh.head);
lib/std/compress/flate/SlidingWindow.zig deleted-160
...@@ -1,160 +0,0 @@
1//! Used in deflate (compression), holds uncompressed data form which Tokens are
2//! produces. In combination with Lookup it is used to find matches in history data.
3//!
4const std = @import("std");
5const consts = @import("consts.zig");
6
7const expect = testing.expect;
8const assert = std.debug.assert;
9const testing = std.testing;
10
11const hist_len = consts.history.len;
12const buffer_len = 2 * hist_len;
13const min_lookahead = consts.match.min_length + consts.match.max_length;
14const max_rp = buffer_len - min_lookahead;
15
16const Self = @This();
17
18buffer: [buffer_len]u8 = undefined,
19wp: usize = 0, // write position
20rp: usize = 0, // read position
21fp: isize = 0, // last flush position, tokens are build from fp..rp
22
23/// Returns number of bytes written, or 0 if buffer is full and need to slide.
24pub fn write(self: *Self, buf: []const u8) usize {
25 if (self.rp >= max_rp) return 0; // need to slide
26
27 const n = @min(buf.len, buffer_len - self.wp);
28 @memcpy(self.buffer[self.wp .. self.wp + n], buf[0..n]);
29 self.wp += n;
30 return n;
31}
32
33/// Slide buffer for hist_len.
34/// Drops old history, preserves between hist_len and hist_len - min_lookahead.
35/// Returns number of bytes removed.
36pub fn slide(self: *Self) u16 {
37 assert(self.rp >= max_rp and self.wp >= self.rp);
38 const n = self.wp - hist_len;
39 @memcpy(self.buffer[0..n], self.buffer[hist_len..self.wp]);
40 self.rp -= hist_len;
41 self.wp -= hist_len;
42 self.fp -= hist_len;
43 return @intCast(n);
44}
45
46/// Data from the current position (read position). Those part of the buffer is
47/// not converted to tokens yet.
48fn lookahead(self: *Self) []const u8 {
49 assert(self.wp >= self.rp);
50 return self.buffer[self.rp..self.wp];
51}
52
53/// Returns part of the lookahead buffer. If should_flush is set no lookahead is
54/// preserved otherwise preserves enough data for the longest match. Returns
55/// null if there is not enough data.
56pub fn activeLookahead(self: *Self, should_flush: bool) ?[]const u8 {
57 const min: usize = if (should_flush) 0 else min_lookahead;
58 const lh = self.lookahead();
59 return if (lh.len > min) lh else null;
60}
61
62/// Advances read position, shrinks lookahead.
63pub fn advance(self: *Self, n: u16) void {
64 assert(self.wp >= self.rp + n);
65 self.rp += n;
66}
67
68/// Returns writable part of the buffer, where new uncompressed data can be
69/// written.
70pub fn writable(self: *Self) []u8 {
71 return self.buffer[self.wp..];
72}
73
74/// Notification of what part of writable buffer is filled with data.
75pub fn written(self: *Self, n: usize) void {
76 self.wp += n;
77}
78
79/// Finds match length between previous and current position.
80/// Used in hot path!
81pub fn match(self: *Self, prev_pos: u16, curr_pos: u16, min_len: u16) u16 {
82 const max_len: usize = @min(self.wp - curr_pos, consts.match.max_length);
83 // lookahead buffers from previous and current positions
84 const prev_lh = self.buffer[prev_pos..][0..max_len];
85 const curr_lh = self.buffer[curr_pos..][0..max_len];
86
87 // If we already have match (min_len > 0),
88 // test the first byte above previous len a[min_len] != b[min_len]
89 // and then all the bytes from that position to zero.
90 // That is likely positions to find difference than looping from first bytes.
91 var i: usize = min_len;
92 if (i > 0) {
93 if (max_len <= i) return 0;
94 while (true) {
95 if (prev_lh[i] != curr_lh[i]) return 0;
96 if (i == 0) break;
97 i -= 1;
98 }
99 i = min_len;
100 }
101 while (i < max_len) : (i += 1)
102 if (prev_lh[i] != curr_lh[i]) break;
103 return if (i >= consts.match.min_length) @intCast(i) else 0;
104}
105
106/// Current position of non-compressed data. Data before rp are already converted
107/// to tokens.
108pub fn pos(self: *Self) u16 {
109 return @intCast(self.rp);
110}
111
112/// Notification that token list is cleared.
113pub fn flush(self: *Self) void {
114 self.fp = @intCast(self.rp);
115}
116
117/// Part of the buffer since last flush or null if there was slide in between (so
118/// fp becomes negative).
119pub fn tokensBuffer(self: *Self) ?[]const u8 {
120 assert(self.fp <= self.rp);
121 if (self.fp < 0) return null;
122 return self.buffer[@intCast(self.fp)..self.rp];
123}
124
125test match {
126 const data = "Blah blah blah blah blah!";
127 var win: Self = .{};
128 try expect(win.write(data) == data.len);
129 try expect(win.wp == data.len);
130 try expect(win.rp == 0);
131
132 // length between l symbols
133 try expect(win.match(1, 6, 0) == 18);
134 try expect(win.match(1, 11, 0) == 13);
135 try expect(win.match(1, 16, 0) == 8);
136 try expect(win.match(1, 21, 0) == 0);
137
138 // position 15 = "blah blah!"
139 // position 20 = "blah!"
140 try expect(win.match(15, 20, 0) == 4);
141 try expect(win.match(15, 20, 3) == 4);
142 try expect(win.match(15, 20, 4) == 0);
143}
144
145test slide {
146 var win: Self = .{};
147 win.wp = Self.buffer_len - 11;
148 win.rp = Self.buffer_len - 111;
149 win.buffer[win.rp] = 0xab;
150 try expect(win.lookahead().len == 100);
151 try expect(win.tokensBuffer().?.len == win.rp);
152
153 const n = win.slide();
154 try expect(n == 32757);
155 try expect(win.buffer[win.rp] == 0xab);
156 try expect(win.rp == Self.hist_len - 111);
157 try expect(win.wp == Self.hist_len - 11);
158 try expect(win.lookahead().len == 100);
159 try expect(win.tokensBuffer() == null);
160}
lib/std/compress/flate/Token.zig+7-7
...@@ -6,7 +6,7 @@ const std = @import("std");...@@ -6,7 +6,7 @@ const std = @import("std");
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const print = std.debug.print;7const print = std.debug.print;
8const expect = std.testing.expect;8const expect = std.testing.expect;
9const consts = @import("consts.zig").match;9const match = std.compress.flate.match;
1010
11const Token = @This();11const Token = @This();
1212
...@@ -26,11 +26,11 @@ pub fn literal(t: Token) u8 {...@@ -26,11 +26,11 @@ pub fn literal(t: Token) u8 {
26}26}
2727
28pub fn distance(t: Token) u16 {28pub fn distance(t: Token) u16 {
29 return @as(u16, t.dist) + consts.min_distance;29 return @as(u16, t.dist) + match.min_distance;
30}30}
3131
32pub fn length(t: Token) u16 {32pub fn length(t: Token) u16 {
33 return @as(u16, t.len_lit) + consts.base_length;33 return @as(u16, t.len_lit) + match.base_length;
34}34}
3535
36pub fn initLiteral(lit: u8) Token {36pub fn initLiteral(lit: u8) Token {
...@@ -40,12 +40,12 @@ pub fn initLiteral(lit: u8) Token {...@@ -40,12 +40,12 @@ pub fn initLiteral(lit: u8) Token {
40// distance range 1 - 32768, stored in dist as 0 - 32767 (u15)40// distance range 1 - 32768, stored in dist as 0 - 32767 (u15)
41// length range 3 - 258, stored in len_lit as 0 - 255 (u8)41// length range 3 - 258, stored in len_lit as 0 - 255 (u8)
42pub fn initMatch(dist: u16, len: u16) Token {42pub fn initMatch(dist: u16, len: u16) Token {
43 assert(len >= consts.min_length and len <= consts.max_length);43 assert(len >= match.min_length and len <= match.max_length);
44 assert(dist >= consts.min_distance and dist <= consts.max_distance);44 assert(dist >= match.min_distance and dist <= match.max_distance);
45 return .{45 return .{
46 .kind = .match,46 .kind = .match,
47 .dist = @intCast(dist - consts.min_distance),47 .dist = @intCast(dist - match.min_distance),
48 .len_lit = @intCast(len - consts.base_length),48 .len_lit = @intCast(len - match.base_length),
49 };49 };
50}50}
5151
lib/std/compress/flate/bit_reader.zig deleted-422
...@@ -1,422 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4
5pub fn bitReader(comptime T: type, reader: anytype) BitReader(T, @TypeOf(reader)) {
6 return BitReader(T, @TypeOf(reader)).init(reader);
7}
8
9pub fn BitReader64(comptime ReaderType: type) type {
10 return BitReader(u64, ReaderType);
11}
12
13pub fn BitReader32(comptime ReaderType: type) type {
14 return BitReader(u32, ReaderType);
15}
16
17/// Bit reader used during inflate (decompression). Has internal buffer of 64
18/// bits which shifts right after bits are consumed. Uses forward_reader to fill
19/// that internal buffer when needed.
20///
21/// readF is the core function. Supports few different ways of getting bits
22/// controlled by flags. In hot path we try to avoid checking whether we need to
23/// fill buffer from forward_reader by calling fill in advance and readF with
24/// buffered flag set.
25///
26pub fn BitReader(comptime T: type, comptime ReaderType: type) type {
27 assert(T == u32 or T == u64);
28 const t_bytes: usize = @sizeOf(T);
29 const Tshift = if (T == u64) u6 else u5;
30
31 return struct {
32 // Underlying reader used for filling internal bits buffer
33 forward_reader: ReaderType = undefined,
34 // Internal buffer of 64 bits
35 bits: T = 0,
36 // Number of bits in the buffer
37 nbits: u32 = 0,
38
39 const Self = @This();
40
41 pub const Error = ReaderType.Error || error{EndOfStream};
42
43 pub fn init(rdr: ReaderType) Self {
44 var self = Self{ .forward_reader = rdr };
45 self.fill(1) catch {};
46 return self;
47 }
48
49 /// Try to have `nice` bits are available in buffer. Reads from
50 /// forward reader if there is no `nice` bits in buffer. Returns error
51 /// if end of forward stream is reached and internal buffer is empty.
52 /// It will not error if less than `nice` bits are in buffer, only when
53 /// all bits are exhausted. During inflate we usually know what is the
54 /// maximum bits for the next step but usually that step will need less
55 /// bits to decode. So `nice` is not hard limit, it will just try to have
56 /// that number of bits available. If end of forward stream is reached
57 /// it may be some extra zero bits in buffer.
58 pub inline fn fill(self: *Self, nice: u6) !void {
59 if (self.nbits >= nice and nice != 0) {
60 return; // We have enough bits
61 }
62 // Read more bits from forward reader
63
64 // Number of empty bytes in bits, round nbits to whole bytes.
65 const empty_bytes =
66 @as(u8, if (self.nbits & 0x7 == 0) t_bytes else t_bytes - 1) - // 8 for 8, 16, 24..., 7 otherwise
67 (self.nbits >> 3); // 0 for 0-7, 1 for 8-16, ... same as / 8
68
69 var buf: [t_bytes]u8 = [_]u8{0} ** t_bytes;
70 const bytes_read = self.forward_reader.readAll(buf[0..empty_bytes]) catch 0;
71 if (bytes_read > 0) {
72 const u: T = std.mem.readInt(T, buf[0..t_bytes], .little);
73 self.bits |= u << @as(Tshift, @intCast(self.nbits));
74 self.nbits += 8 * @as(u8, @intCast(bytes_read));
75 return;
76 }
77
78 if (self.nbits == 0)
79 return error.EndOfStream;
80 }
81
82 /// Read exactly buf.len bytes into buf.
83 pub fn readAll(self: *Self, buf: []u8) !void {
84 assert(self.alignBits() == 0); // internal bits must be at byte boundary
85
86 // First read from internal bits buffer.
87 var n: usize = 0;
88 while (self.nbits > 0 and n < buf.len) {
89 buf[n] = try self.readF(u8, flag.buffered);
90 n += 1;
91 }
92 // Then use forward reader for all other bytes.
93 try self.forward_reader.readNoEof(buf[n..]);
94 }
95
96 pub const flag = struct {
97 pub const peek: u3 = 0b001; // dont advance internal buffer, just get bits, leave them in buffer
98 pub const buffered: u3 = 0b010; // assume that there is no need to fill, fill should be called before
99 pub const reverse: u3 = 0b100; // bit reverse read bits
100 };
101
102 /// Alias for readF(U, 0).
103 pub fn read(self: *Self, comptime U: type) !U {
104 return self.readF(U, 0);
105 }
106
107 /// Alias for readF with flag.peak set.
108 pub inline fn peekF(self: *Self, comptime U: type, comptime how: u3) !U {
109 return self.readF(U, how | flag.peek);
110 }
111
112 /// Read with flags provided.
113 pub fn readF(self: *Self, comptime U: type, comptime how: u3) !U {
114 if (U == T) {
115 assert(how == 0);
116 assert(self.alignBits() == 0);
117 try self.fill(@bitSizeOf(T));
118 if (self.nbits != @bitSizeOf(T)) return error.EndOfStream;
119 const v = self.bits;
120 self.nbits = 0;
121 self.bits = 0;
122 return v;
123 }
124 const n: Tshift = @bitSizeOf(U);
125 switch (how) {
126 0 => { // `normal` read
127 try self.fill(n); // ensure that there are n bits in the buffer
128 const u: U = @truncate(self.bits); // get n bits
129 try self.shift(n); // advance buffer for n
130 return u;
131 },
132 (flag.peek) => { // no shift, leave bits in the buffer
133 try self.fill(n);
134 return @truncate(self.bits);
135 },
136 flag.buffered => { // no fill, assume that buffer has enough bits
137 const u: U = @truncate(self.bits);
138 try self.shift(n);
139 return u;
140 },
141 (flag.reverse) => { // same as 0 with bit reverse
142 try self.fill(n);
143 const u: U = @truncate(self.bits);
144 try self.shift(n);
145 return @bitReverse(u);
146 },
147 (flag.peek | flag.reverse) => {
148 try self.fill(n);
149 return @bitReverse(@as(U, @truncate(self.bits)));
150 },
151 (flag.buffered | flag.reverse) => {
152 const u: U = @truncate(self.bits);
153 try self.shift(n);
154 return @bitReverse(u);
155 },
156 (flag.peek | flag.buffered) => {
157 return @truncate(self.bits);
158 },
159 (flag.peek | flag.buffered | flag.reverse) => {
160 return @bitReverse(@as(U, @truncate(self.bits)));
161 },
162 }
163 }
164
165 /// Read n number of bits.
166 /// Only buffered flag can be used in how.
167 pub fn readN(self: *Self, n: u4, comptime how: u3) !u16 {
168 switch (how) {
169 0 => {
170 try self.fill(n);
171 },
172 flag.buffered => {},
173 else => unreachable,
174 }
175 const mask: u16 = (@as(u16, 1) << n) - 1;
176 const u: u16 = @as(u16, @truncate(self.bits)) & mask;
177 try self.shift(n);
178 return u;
179 }
180
181 /// Advance buffer for n bits.
182 pub fn shift(self: *Self, n: Tshift) !void {
183 if (n > self.nbits) return error.EndOfStream;
184 self.bits >>= n;
185 self.nbits -= n;
186 }
187
188 /// Skip n bytes.
189 pub fn skipBytes(self: *Self, n: u16) !void {
190 for (0..n) |_| {
191 try self.fill(8);
192 try self.shift(8);
193 }
194 }
195
196 // Number of bits to align stream to the byte boundary.
197 fn alignBits(self: *Self) u3 {
198 return @intCast(self.nbits & 0x7);
199 }
200
201 /// Align stream to the byte boundary.
202 pub fn alignToByte(self: *Self) void {
203 const ab = self.alignBits();
204 if (ab > 0) self.shift(ab) catch unreachable;
205 }
206
207 /// Skip zero terminated string.
208 pub fn skipStringZ(self: *Self) !void {
209 while (true) {
210 if (try self.readF(u8, 0) == 0) break;
211 }
212 }
213
214 /// Read deflate fixed fixed code.
215 /// Reads first 7 bits, and then maybe 1 or 2 more to get full 7,8 or 9 bit code.
216 /// ref: https://datatracker.ietf.org/doc/html/rfc1951#page-12
217 /// Lit Value Bits Codes
218 /// --------- ---- -----
219 /// 0 - 143 8 00110000 through
220 /// 10111111
221 /// 144 - 255 9 110010000 through
222 /// 111111111
223 /// 256 - 279 7 0000000 through
224 /// 0010111
225 /// 280 - 287 8 11000000 through
226 /// 11000111
227 pub fn readFixedCode(self: *Self) !u16 {
228 try self.fill(7 + 2);
229 const code7 = try self.readF(u7, flag.buffered | flag.reverse);
230 if (code7 <= 0b0010_111) { // 7 bits, 256-279, codes 0000_000 - 0010_111
231 return @as(u16, code7) + 256;
232 } else if (code7 <= 0b1011_111) { // 8 bits, 0-143, codes 0011_0000 through 1011_1111
233 return (@as(u16, code7) << 1) + @as(u16, try self.readF(u1, flag.buffered)) - 0b0011_0000;
234 } else if (code7 <= 0b1100_011) { // 8 bit, 280-287, codes 1100_0000 - 1100_0111
235 return (@as(u16, code7 - 0b1100000) << 1) + try self.readF(u1, flag.buffered) + 280;
236 } else { // 9 bit, 144-255, codes 1_1001_0000 - 1_1111_1111
237 return (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, try self.readF(u2, flag.buffered | flag.reverse)) + 144;
238 }
239 }
240 };
241}
242
243test "readF" {
244 var fbs = std.io.fixedBufferStream(&[_]u8{ 0xf3, 0x48, 0xcd, 0xc9, 0x00, 0x00 });
245 var br = bitReader(u64, fbs.reader());
246 const F = BitReader64(@TypeOf(fbs.reader())).flag;
247
248 try testing.expectEqual(@as(u8, 48), br.nbits);
249 try testing.expectEqual(@as(u64, 0xc9cd48f3), br.bits);
250
251 try testing.expect(try br.readF(u1, 0) == 0b0000_0001);
252 try testing.expect(try br.readF(u2, 0) == 0b0000_0001);
253 try testing.expectEqual(@as(u8, 48 - 3), br.nbits);
254 try testing.expectEqual(@as(u3, 5), br.alignBits());
255
256 try testing.expect(try br.readF(u8, F.peek) == 0b0001_1110);
257 try testing.expect(try br.readF(u9, F.peek) == 0b1_0001_1110);
258 try br.shift(9);
259 try testing.expectEqual(@as(u8, 36), br.nbits);
260 try testing.expectEqual(@as(u3, 4), br.alignBits());
261
262 try testing.expect(try br.readF(u4, 0) == 0b0100);
263 try testing.expectEqual(@as(u8, 32), br.nbits);
264 try testing.expectEqual(@as(u3, 0), br.alignBits());
265
266 try br.shift(1);
267 try testing.expectEqual(@as(u3, 7), br.alignBits());
268 try br.shift(1);
269 try testing.expectEqual(@as(u3, 6), br.alignBits());
270 br.alignToByte();
271 try testing.expectEqual(@as(u3, 0), br.alignBits());
272
273 try testing.expectEqual(@as(u64, 0xc9), br.bits);
274 try testing.expectEqual(@as(u16, 0x9), try br.readN(4, 0));
275 try testing.expectEqual(@as(u16, 0xc), try br.readN(4, 0));
276}
277
278test "read block type 1 data" {
279 inline for ([_]type{ u64, u32 }) |T| {
280 const data = [_]u8{
281 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
282 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
283 0x0c, 0x01, 0x02, 0x03, //
284 0xaa, 0xbb, 0xcc, 0xdd,
285 };
286 var fbs = std.io.fixedBufferStream(&data);
287 var br = bitReader(T, fbs.reader());
288 const F = BitReader(T, @TypeOf(fbs.reader())).flag;
289
290 try testing.expectEqual(@as(u1, 1), try br.readF(u1, 0)); // bfinal
291 try testing.expectEqual(@as(u2, 1), try br.readF(u2, 0)); // block_type
292
293 for ("Hello world\n") |c| {
294 try testing.expectEqual(@as(u8, c), try br.readF(u8, F.reverse) - 0x30);
295 }
296 try testing.expectEqual(@as(u7, 0), try br.readF(u7, 0)); // end of block
297 br.alignToByte();
298 try testing.expectEqual(@as(u32, 0x0302010c), try br.readF(u32, 0));
299 try testing.expectEqual(@as(u16, 0xbbaa), try br.readF(u16, 0));
300 try testing.expectEqual(@as(u16, 0xddcc), try br.readF(u16, 0));
301 }
302}
303
304test "shift/fill" {
305 const data = [_]u8{
306 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
307 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
308 };
309 var fbs = std.io.fixedBufferStream(&data);
310 var br = bitReader(u64, fbs.reader());
311
312 try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits);
313 try br.shift(8);
314 try testing.expectEqual(@as(u64, 0x00_08_07_06_05_04_03_02), br.bits);
315 try br.fill(60); // fill with 1 byte
316 try testing.expectEqual(@as(u64, 0x01_08_07_06_05_04_03_02), br.bits);
317 try br.shift(8 * 4 + 4);
318 try testing.expectEqual(@as(u64, 0x00_00_00_00_00_10_80_70), br.bits);
319
320 try br.fill(60); // fill with 4 bytes (shift by 4)
321 try testing.expectEqual(@as(u64, 0x00_50_40_30_20_10_80_70), br.bits);
322 try testing.expectEqual(@as(u8, 8 * 7 + 4), br.nbits);
323
324 try br.shift(@intCast(br.nbits)); // clear buffer
325 try br.fill(8); // refill with the rest of the bytes
326 try testing.expectEqual(@as(u64, 0x00_00_00_00_00_08_07_06), br.bits);
327}
328
329test "readAll" {
330 inline for ([_]type{ u64, u32 }) |T| {
331 const data = [_]u8{
332 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
333 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
334 };
335 var fbs = std.io.fixedBufferStream(&data);
336 var br = bitReader(T, fbs.reader());
337
338 switch (T) {
339 u64 => try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits),
340 u32 => try testing.expectEqual(@as(u32, 0x04_03_02_01), br.bits),
341 else => unreachable,
342 }
343
344 var out: [16]u8 = undefined;
345 try br.readAll(out[0..]);
346 try testing.expect(br.nbits == 0);
347 try testing.expect(br.bits == 0);
348
349 try testing.expectEqualSlices(u8, data[0..16], &out);
350 }
351}
352
353test "readFixedCode" {
354 inline for ([_]type{ u64, u32 }) |T| {
355 const fixed_codes = @import("huffman_encoder.zig").fixed_codes;
356
357 var fbs = std.io.fixedBufferStream(&fixed_codes);
358 var rdr = bitReader(T, fbs.reader());
359
360 for (0..286) |c| {
361 try testing.expectEqual(c, try rdr.readFixedCode());
362 }
363 try testing.expect(rdr.nbits == 0);
364 }
365}
366
367test "u32 leaves no bits on u32 reads" {
368 const data = [_]u8{
369 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
370 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
371 };
372 var fbs = std.io.fixedBufferStream(&data);
373 var br = bitReader(u32, fbs.reader());
374
375 _ = try br.read(u3);
376 try testing.expectEqual(29, br.nbits);
377 br.alignToByte();
378 try testing.expectEqual(24, br.nbits);
379 try testing.expectEqual(0x04_03_02_01, try br.read(u32));
380 try testing.expectEqual(0, br.nbits);
381 try testing.expectEqual(0x08_07_06_05, try br.read(u32));
382 try testing.expectEqual(0, br.nbits);
383
384 _ = try br.read(u9);
385 try testing.expectEqual(23, br.nbits);
386 br.alignToByte();
387 try testing.expectEqual(16, br.nbits);
388 try testing.expectEqual(0x0e_0d_0c_0b, try br.read(u32));
389 try testing.expectEqual(0, br.nbits);
390}
391
392test "u64 need fill after alignToByte" {
393 const data = [_]u8{
394 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
395 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
396 };
397
398 // without fill
399 var fbs = std.io.fixedBufferStream(&data);
400 var br = bitReader(u64, fbs.reader());
401 _ = try br.read(u23);
402 try testing.expectEqual(41, br.nbits);
403 br.alignToByte();
404 try testing.expectEqual(40, br.nbits);
405 try testing.expectEqual(0x06_05_04_03, try br.read(u32));
406 try testing.expectEqual(8, br.nbits);
407 try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
408 try testing.expectEqual(32, br.nbits);
409
410 // fill after align ensures all bits filled
411 fbs.reset();
412 br = bitReader(u64, fbs.reader());
413 _ = try br.read(u23);
414 try testing.expectEqual(41, br.nbits);
415 br.alignToByte();
416 try br.fill(0);
417 try testing.expectEqual(64, br.nbits);
418 try testing.expectEqual(0x06_05_04_03, try br.read(u32));
419 try testing.expectEqual(32, br.nbits);
420 try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
421 try testing.expectEqual(0, br.nbits);
422}
lib/std/compress/flate/bit_writer.zig deleted-99
...@@ -1,99 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3
4/// Bit writer for use in deflate (compression).
5///
6/// Has internal bits buffer of 64 bits and internal bytes buffer of 248 bytes.
7/// When we accumulate 48 bits 6 bytes are moved to the bytes buffer. When we
8/// accumulate 240 bytes they are flushed to the underlying inner_writer.
9///
10pub fn BitWriter(comptime WriterType: type) type {
11 // buffer_flush_size indicates the buffer size
12 // after which bytes are flushed to the writer.
13 // Should preferably be a multiple of 6, since
14 // we accumulate 6 bytes between writes to the buffer.
15 const buffer_flush_size = 240;
16
17 // buffer_size is the actual output byte buffer size.
18 // It must have additional headroom for a flush
19 // which can contain up to 8 bytes.
20 const buffer_size = buffer_flush_size + 8;
21
22 return struct {
23 inner_writer: WriterType,
24
25 // Data waiting to be written is bytes[0 .. nbytes]
26 // and then the low nbits of bits. Data is always written
27 // sequentially into the bytes array.
28 bits: u64 = 0,
29 nbits: u32 = 0, // number of bits
30 bytes: [buffer_size]u8 = undefined,
31 nbytes: u32 = 0, // number of bytes
32
33 const Self = @This();
34
35 pub const Error = WriterType.Error || error{UnfinishedBits};
36
37 pub fn init(writer: WriterType) Self {
38 return .{ .inner_writer = writer };
39 }
40
41 pub fn setWriter(self: *Self, new_writer: WriterType) void {
42 //assert(self.bits == 0 and self.nbits == 0 and self.nbytes == 0);
43 self.inner_writer = new_writer;
44 }
45
46 pub fn flush(self: *Self) Error!void {
47 var n = self.nbytes;
48 while (self.nbits != 0) {
49 self.bytes[n] = @as(u8, @truncate(self.bits));
50 self.bits >>= 8;
51 if (self.nbits > 8) { // Avoid underflow
52 self.nbits -= 8;
53 } else {
54 self.nbits = 0;
55 }
56 n += 1;
57 }
58 self.bits = 0;
59 _ = try self.inner_writer.write(self.bytes[0..n]);
60 self.nbytes = 0;
61 }
62
63 pub fn writeBits(self: *Self, b: u32, nb: u32) Error!void {
64 self.bits |= @as(u64, @intCast(b)) << @as(u6, @intCast(self.nbits));
65 self.nbits += nb;
66 if (self.nbits < 48)
67 return;
68
69 var n = self.nbytes;
70 std.mem.writeInt(u64, self.bytes[n..][0..8], self.bits, .little);
71 n += 6;
72 if (n >= buffer_flush_size) {
73 _ = try self.inner_writer.write(self.bytes[0..n]);
74 n = 0;
75 }
76 self.nbytes = n;
77 self.bits >>= 48;
78 self.nbits -= 48;
79 }
80
81 pub fn writeBytes(self: *Self, bytes: []const u8) Error!void {
82 var n = self.nbytes;
83 if (self.nbits & 7 != 0) {
84 return error.UnfinishedBits;
85 }
86 while (self.nbits != 0) {
87 self.bytes[n] = @as(u8, @truncate(self.bits));
88 self.bits >>= 8;
89 self.nbits -= 8;
90 n += 1;
91 }
92 if (n != 0) {
93 _ = try self.inner_writer.write(self.bytes[0..n]);
94 }
95 self.nbytes = 0;
96 _ = try self.inner_writer.write(bytes);
97 }
98 };
99}
lib/std/compress/flate/block_writer.zig deleted-706
...@@ -1,706 +0,0 @@
1const std = @import("std");
2const io = std.io;
3const assert = std.debug.assert;
4
5const hc = @import("huffman_encoder.zig");
6const consts = @import("consts.zig").huffman;
7const Token = @import("Token.zig");
8const BitWriter = @import("bit_writer.zig").BitWriter;
9
10pub fn blockWriter(writer: anytype) BlockWriter(@TypeOf(writer)) {
11 return BlockWriter(@TypeOf(writer)).init(writer);
12}
13
14/// Accepts list of tokens, decides what is best block type to write. What block
15/// type will provide best compression. Writes header and body of the block.
16///
17pub fn BlockWriter(comptime WriterType: type) type {
18 const BitWriterType = BitWriter(WriterType);
19 return struct {
20 const codegen_order = consts.codegen_order;
21 const end_code_mark = 255;
22 const Self = @This();
23
24 pub const Error = BitWriterType.Error;
25 bit_writer: BitWriterType,
26
27 codegen_freq: [consts.codegen_code_count]u16 = undefined,
28 literal_freq: [consts.max_num_lit]u16 = undefined,
29 distance_freq: [consts.distance_code_count]u16 = undefined,
30 codegen: [consts.max_num_lit + consts.distance_code_count + 1]u8 = undefined,
31 literal_encoding: hc.LiteralEncoder = .{},
32 distance_encoding: hc.DistanceEncoder = .{},
33 codegen_encoding: hc.CodegenEncoder = .{},
34 fixed_literal_encoding: hc.LiteralEncoder,
35 fixed_distance_encoding: hc.DistanceEncoder,
36 huff_distance: hc.DistanceEncoder,
37
38 pub fn init(writer: WriterType) Self {
39 return .{
40 .bit_writer = BitWriterType.init(writer),
41 .fixed_literal_encoding = hc.fixedLiteralEncoder(),
42 .fixed_distance_encoding = hc.fixedDistanceEncoder(),
43 .huff_distance = hc.huffmanDistanceEncoder(),
44 };
45 }
46
47 /// Flush intrenal bit buffer to the writer.
48 /// Should be called only when bit stream is at byte boundary.
49 ///
50 /// That is after final block; when last byte could be incomplete or
51 /// after stored block; which is aligned to the byte boundary (it has x
52 /// padding bits after first 3 bits).
53 pub fn flush(self: *Self) Error!void {
54 try self.bit_writer.flush();
55 }
56
57 pub fn setWriter(self: *Self, new_writer: WriterType) void {
58 self.bit_writer.setWriter(new_writer);
59 }
60
61 fn writeCode(self: *Self, c: hc.HuffCode) Error!void {
62 try self.bit_writer.writeBits(c.code, c.len);
63 }
64
65 // RFC 1951 3.2.7 specifies a special run-length encoding for specifying
66 // the literal and distance lengths arrays (which are concatenated into a single
67 // array). This method generates that run-length encoding.
68 //
69 // The result is written into the codegen array, and the frequencies
70 // of each code is written into the codegen_freq array.
71 // Codes 0-15 are single byte codes. Codes 16-18 are followed by additional
72 // information. Code bad_code is an end marker
73 //
74 // num_literals: The number of literals in literal_encoding
75 // num_distances: The number of distances in distance_encoding
76 // lit_enc: The literal encoder to use
77 // dist_enc: The distance encoder to use
78 fn generateCodegen(
79 self: *Self,
80 num_literals: u32,
81 num_distances: u32,
82 lit_enc: *hc.LiteralEncoder,
83 dist_enc: *hc.DistanceEncoder,
84 ) void {
85 for (self.codegen_freq, 0..) |_, i| {
86 self.codegen_freq[i] = 0;
87 }
88
89 // Note that we are using codegen both as a temporary variable for holding
90 // a copy of the frequencies, and as the place where we put the result.
91 // This is fine because the output is always shorter than the input used
92 // so far.
93 var codegen = &self.codegen; // cache
94 // Copy the concatenated code sizes to codegen. Put a marker at the end.
95 var cgnl = codegen[0..num_literals];
96 for (cgnl, 0..) |_, i| {
97 cgnl[i] = @as(u8, @intCast(lit_enc.codes[i].len));
98 }
99
100 cgnl = codegen[num_literals .. num_literals + num_distances];
101 for (cgnl, 0..) |_, i| {
102 cgnl[i] = @as(u8, @intCast(dist_enc.codes[i].len));
103 }
104 codegen[num_literals + num_distances] = end_code_mark;
105
106 var size = codegen[0];
107 var count: i32 = 1;
108 var out_index: u32 = 0;
109 var in_index: u32 = 1;
110 while (size != end_code_mark) : (in_index += 1) {
111 // INVARIANT: We have seen "count" copies of size that have not yet
112 // had output generated for them.
113 const next_size = codegen[in_index];
114 if (next_size == size) {
115 count += 1;
116 continue;
117 }
118 // We need to generate codegen indicating "count" of size.
119 if (size != 0) {
120 codegen[out_index] = size;
121 out_index += 1;
122 self.codegen_freq[size] += 1;
123 count -= 1;
124 while (count >= 3) {
125 var n: i32 = 6;
126 if (n > count) {
127 n = count;
128 }
129 codegen[out_index] = 16;
130 out_index += 1;
131 codegen[out_index] = @as(u8, @intCast(n - 3));
132 out_index += 1;
133 self.codegen_freq[16] += 1;
134 count -= n;
135 }
136 } else {
137 while (count >= 11) {
138 var n: i32 = 138;
139 if (n > count) {
140 n = count;
141 }
142 codegen[out_index] = 18;
143 out_index += 1;
144 codegen[out_index] = @as(u8, @intCast(n - 11));
145 out_index += 1;
146 self.codegen_freq[18] += 1;
147 count -= n;
148 }
149 if (count >= 3) {
150 // 3 <= count <= 10
151 codegen[out_index] = 17;
152 out_index += 1;
153 codegen[out_index] = @as(u8, @intCast(count - 3));
154 out_index += 1;
155 self.codegen_freq[17] += 1;
156 count = 0;
157 }
158 }
159 count -= 1;
160 while (count >= 0) : (count -= 1) {
161 codegen[out_index] = size;
162 out_index += 1;
163 self.codegen_freq[size] += 1;
164 }
165 // Set up invariant for next time through the loop.
166 size = next_size;
167 count = 1;
168 }
169 // Marker indicating the end of the codegen.
170 codegen[out_index] = end_code_mark;
171 }
172
173 const DynamicSize = struct {
174 size: u32,
175 num_codegens: u32,
176 };
177
178 // dynamicSize returns the size of dynamically encoded data in bits.
179 fn dynamicSize(
180 self: *Self,
181 lit_enc: *hc.LiteralEncoder, // literal encoder
182 dist_enc: *hc.DistanceEncoder, // distance encoder
183 extra_bits: u32,
184 ) DynamicSize {
185 var num_codegens = self.codegen_freq.len;
186 while (num_codegens > 4 and self.codegen_freq[codegen_order[num_codegens - 1]] == 0) {
187 num_codegens -= 1;
188 }
189 const header = 3 + 5 + 5 + 4 + (3 * num_codegens) +
190 self.codegen_encoding.bitLength(self.codegen_freq[0..]) +
191 self.codegen_freq[16] * 2 +
192 self.codegen_freq[17] * 3 +
193 self.codegen_freq[18] * 7;
194 const size = header +
195 lit_enc.bitLength(&self.literal_freq) +
196 dist_enc.bitLength(&self.distance_freq) +
197 extra_bits;
198
199 return DynamicSize{
200 .size = @as(u32, @intCast(size)),
201 .num_codegens = @as(u32, @intCast(num_codegens)),
202 };
203 }
204
205 // fixedSize returns the size of dynamically encoded data in bits.
206 fn fixedSize(self: *Self, extra_bits: u32) u32 {
207 return 3 +
208 self.fixed_literal_encoding.bitLength(&self.literal_freq) +
209 self.fixed_distance_encoding.bitLength(&self.distance_freq) +
210 extra_bits;
211 }
212
213 const StoredSize = struct {
214 size: u32,
215 storable: bool,
216 };
217
218 // storedSizeFits calculates the stored size, including header.
219 // The function returns the size in bits and whether the block
220 // fits inside a single block.
221 fn storedSizeFits(in: ?[]const u8) StoredSize {
222 if (in == null) {
223 return .{ .size = 0, .storable = false };
224 }
225 if (in.?.len <= consts.max_store_block_size) {
226 return .{ .size = @as(u32, @intCast((in.?.len + 5) * 8)), .storable = true };
227 }
228 return .{ .size = 0, .storable = false };
229 }
230
231 // Write the header of a dynamic Huffman block to the output stream.
232 //
233 // num_literals: The number of literals specified in codegen
234 // num_distances: The number of distances specified in codegen
235 // num_codegens: The number of codegens used in codegen
236 // eof: Is it the end-of-file? (end of stream)
237 fn dynamicHeader(
238 self: *Self,
239 num_literals: u32,
240 num_distances: u32,
241 num_codegens: u32,
242 eof: bool,
243 ) Error!void {
244 const first_bits: u32 = if (eof) 5 else 4;
245 try self.bit_writer.writeBits(first_bits, 3);
246 try self.bit_writer.writeBits(num_literals - 257, 5);
247 try self.bit_writer.writeBits(num_distances - 1, 5);
248 try self.bit_writer.writeBits(num_codegens - 4, 4);
249
250 var i: u32 = 0;
251 while (i < num_codegens) : (i += 1) {
252 const value = self.codegen_encoding.codes[codegen_order[i]].len;
253 try self.bit_writer.writeBits(value, 3);
254 }
255
256 i = 0;
257 while (true) {
258 const code_word: u32 = @as(u32, @intCast(self.codegen[i]));
259 i += 1;
260 if (code_word == end_code_mark) {
261 break;
262 }
263 try self.writeCode(self.codegen_encoding.codes[@as(u32, @intCast(code_word))]);
264
265 switch (code_word) {
266 16 => {
267 try self.bit_writer.writeBits(self.codegen[i], 2);
268 i += 1;
269 },
270 17 => {
271 try self.bit_writer.writeBits(self.codegen[i], 3);
272 i += 1;
273 },
274 18 => {
275 try self.bit_writer.writeBits(self.codegen[i], 7);
276 i += 1;
277 },
278 else => {},
279 }
280 }
281 }
282
283 fn storedHeader(self: *Self, length: usize, eof: bool) Error!void {
284 assert(length <= 65535);
285 const flag: u32 = if (eof) 1 else 0;
286 try self.bit_writer.writeBits(flag, 3);
287 try self.flush();
288 const l: u16 = @intCast(length);
289 try self.bit_writer.writeBits(l, 16);
290 try self.bit_writer.writeBits(~l, 16);
291 }
292
293 fn fixedHeader(self: *Self, eof: bool) Error!void {
294 // Indicate that we are a fixed Huffman block
295 var value: u32 = 2;
296 if (eof) {
297 value = 3;
298 }
299 try self.bit_writer.writeBits(value, 3);
300 }
301
302 // Write a block of tokens with the smallest encoding. Will choose block type.
303 // The original input can be supplied, and if the huffman encoded data
304 // is larger than the original bytes, the data will be written as a
305 // stored block.
306 // If the input is null, the tokens will always be Huffman encoded.
307 pub fn write(self: *Self, tokens: []const Token, eof: bool, input: ?[]const u8) Error!void {
308 const lit_and_dist = self.indexTokens(tokens);
309 const num_literals = lit_and_dist.num_literals;
310 const num_distances = lit_and_dist.num_distances;
311
312 var extra_bits: u32 = 0;
313 const ret = storedSizeFits(input);
314 const stored_size = ret.size;
315 const storable = ret.storable;
316
317 if (storable) {
318 // We only bother calculating the costs of the extra bits required by
319 // the length of distance fields (which will be the same for both fixed
320 // and dynamic encoding), if we need to compare those two encodings
321 // against stored encoding.
322 var length_code: u16 = Token.length_codes_start + 8;
323 while (length_code < num_literals) : (length_code += 1) {
324 // First eight length codes have extra size = 0.
325 extra_bits += @as(u32, @intCast(self.literal_freq[length_code])) *
326 @as(u32, @intCast(Token.lengthExtraBits(length_code)));
327 }
328 var distance_code: u16 = 4;
329 while (distance_code < num_distances) : (distance_code += 1) {
330 // First four distance codes have extra size = 0.
331 extra_bits += @as(u32, @intCast(self.distance_freq[distance_code])) *
332 @as(u32, @intCast(Token.distanceExtraBits(distance_code)));
333 }
334 }
335
336 // Figure out smallest code.
337 // Fixed Huffman baseline.
338 var literal_encoding = &self.fixed_literal_encoding;
339 var distance_encoding = &self.fixed_distance_encoding;
340 var size = self.fixedSize(extra_bits);
341
342 // Dynamic Huffman?
343 var num_codegens: u32 = 0;
344
345 // Generate codegen and codegenFrequencies, which indicates how to encode
346 // the literal_encoding and the distance_encoding.
347 self.generateCodegen(
348 num_literals,
349 num_distances,
350 &self.literal_encoding,
351 &self.distance_encoding,
352 );
353 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
354 const dynamic_size = self.dynamicSize(
355 &self.literal_encoding,
356 &self.distance_encoding,
357 extra_bits,
358 );
359 const dyn_size = dynamic_size.size;
360 num_codegens = dynamic_size.num_codegens;
361
362 if (dyn_size < size) {
363 size = dyn_size;
364 literal_encoding = &self.literal_encoding;
365 distance_encoding = &self.distance_encoding;
366 }
367
368 // Stored bytes?
369 if (storable and stored_size < size) {
370 try self.storedBlock(input.?, eof);
371 return;
372 }
373
374 // Huffman.
375 if (@intFromPtr(literal_encoding) == @intFromPtr(&self.fixed_literal_encoding)) {
376 try self.fixedHeader(eof);
377 } else {
378 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
379 }
380
381 // Write the tokens.
382 try self.writeTokens(tokens, &literal_encoding.codes, &distance_encoding.codes);
383 }
384
385 pub fn storedBlock(self: *Self, input: []const u8, eof: bool) Error!void {
386 try self.storedHeader(input.len, eof);
387 try self.bit_writer.writeBytes(input);
388 }
389
390 // writeBlockDynamic encodes a block using a dynamic Huffman table.
391 // This should be used if the symbols used have a disproportionate
392 // histogram distribution.
393 // If input is supplied and the compression savings are below 1/16th of the
394 // input size the block is stored.
395 fn dynamicBlock(
396 self: *Self,
397 tokens: []const Token,
398 eof: bool,
399 input: ?[]const u8,
400 ) Error!void {
401 const total_tokens = self.indexTokens(tokens);
402 const num_literals = total_tokens.num_literals;
403 const num_distances = total_tokens.num_distances;
404
405 // Generate codegen and codegenFrequencies, which indicates how to encode
406 // the literal_encoding and the distance_encoding.
407 self.generateCodegen(
408 num_literals,
409 num_distances,
410 &self.literal_encoding,
411 &self.distance_encoding,
412 );
413 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
414 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.distance_encoding, 0);
415 const size = dynamic_size.size;
416 const num_codegens = dynamic_size.num_codegens;
417
418 // Store bytes, if we don't get a reasonable improvement.
419
420 const stored_size = storedSizeFits(input);
421 const ssize = stored_size.size;
422 const storable = stored_size.storable;
423 if (storable and ssize < (size + (size >> 4))) {
424 try self.storedBlock(input.?, eof);
425 return;
426 }
427
428 // Write Huffman table.
429 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
430
431 // Write the tokens.
432 try self.writeTokens(tokens, &self.literal_encoding.codes, &self.distance_encoding.codes);
433 }
434
435 const TotalIndexedTokens = struct {
436 num_literals: u32,
437 num_distances: u32,
438 };
439
440 // Indexes a slice of tokens followed by an end_block_marker, and updates
441 // literal_freq and distance_freq, and generates literal_encoding
442 // and distance_encoding.
443 // The number of literal and distance tokens is returned.
444 fn indexTokens(self: *Self, tokens: []const Token) TotalIndexedTokens {
445 var num_literals: u32 = 0;
446 var num_distances: u32 = 0;
447
448 for (self.literal_freq, 0..) |_, i| {
449 self.literal_freq[i] = 0;
450 }
451 for (self.distance_freq, 0..) |_, i| {
452 self.distance_freq[i] = 0;
453 }
454
455 for (tokens) |t| {
456 if (t.kind == Token.Kind.literal) {
457 self.literal_freq[t.literal()] += 1;
458 continue;
459 }
460 self.literal_freq[t.lengthCode()] += 1;
461 self.distance_freq[t.distanceCode()] += 1;
462 }
463 // add end_block_marker token at the end
464 self.literal_freq[consts.end_block_marker] += 1;
465
466 // get the number of literals
467 num_literals = @as(u32, @intCast(self.literal_freq.len));
468 while (self.literal_freq[num_literals - 1] == 0) {
469 num_literals -= 1;
470 }
471 // get the number of distances
472 num_distances = @as(u32, @intCast(self.distance_freq.len));
473 while (num_distances > 0 and self.distance_freq[num_distances - 1] == 0) {
474 num_distances -= 1;
475 }
476 if (num_distances == 0) {
477 // We haven't found a single match. If we want to go with the dynamic encoding,
478 // we should count at least one distance to be sure that the distance huffman tree could be encoded.
479 self.distance_freq[0] = 1;
480 num_distances = 1;
481 }
482 self.literal_encoding.generate(&self.literal_freq, 15);
483 self.distance_encoding.generate(&self.distance_freq, 15);
484 return TotalIndexedTokens{
485 .num_literals = num_literals,
486 .num_distances = num_distances,
487 };
488 }
489
490 // Writes a slice of tokens to the output followed by and end_block_marker.
491 // codes for literal and distance encoding must be supplied.
492 fn writeTokens(
493 self: *Self,
494 tokens: []const Token,
495 le_codes: []hc.HuffCode,
496 oe_codes: []hc.HuffCode,
497 ) Error!void {
498 for (tokens) |t| {
499 if (t.kind == Token.Kind.literal) {
500 try self.writeCode(le_codes[t.literal()]);
501 continue;
502 }
503
504 // Write the length
505 const le = t.lengthEncoding();
506 try self.writeCode(le_codes[le.code]);
507 if (le.extra_bits > 0) {
508 try self.bit_writer.writeBits(le.extra_length, le.extra_bits);
509 }
510
511 // Write the distance
512 const oe = t.distanceEncoding();
513 try self.writeCode(oe_codes[oe.code]);
514 if (oe.extra_bits > 0) {
515 try self.bit_writer.writeBits(oe.extra_distance, oe.extra_bits);
516 }
517 }
518 // add end_block_marker at the end
519 try self.writeCode(le_codes[consts.end_block_marker]);
520 }
521
522 // Encodes a block of bytes as either Huffman encoded literals or uncompressed bytes
523 // if the results only gains very little from compression.
524 pub fn huffmanBlock(self: *Self, input: []const u8, eof: bool) Error!void {
525 // Add everything as literals
526 histogram(input, &self.literal_freq);
527
528 self.literal_freq[consts.end_block_marker] = 1;
529
530 const num_literals = consts.end_block_marker + 1;
531 self.distance_freq[0] = 1;
532 const num_distances = 1;
533
534 self.literal_encoding.generate(&self.literal_freq, 15);
535
536 // Figure out smallest code.
537 // Always use dynamic Huffman or Store
538 var num_codegens: u32 = 0;
539
540 // Generate codegen and codegenFrequencies, which indicates how to encode
541 // the literal_encoding and the distance_encoding.
542 self.generateCodegen(
543 num_literals,
544 num_distances,
545 &self.literal_encoding,
546 &self.huff_distance,
547 );
548 self.codegen_encoding.generate(self.codegen_freq[0..], 7);
549 const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.huff_distance, 0);
550 const size = dynamic_size.size;
551 num_codegens = dynamic_size.num_codegens;
552
553 // Store bytes, if we don't get a reasonable improvement.
554 const stored_size_ret = storedSizeFits(input);
555 const ssize = stored_size_ret.size;
556 const storable = stored_size_ret.storable;
557
558 if (storable and ssize < (size + (size >> 4))) {
559 try self.storedBlock(input, eof);
560 return;
561 }
562
563 // Huffman.
564 try self.dynamicHeader(num_literals, num_distances, num_codegens, eof);
565 const encoding = self.literal_encoding.codes[0..257];
566
567 for (input) |t| {
568 const c = encoding[t];
569 try self.bit_writer.writeBits(c.code, c.len);
570 }
571 try self.writeCode(encoding[consts.end_block_marker]);
572 }
573
574 // histogram accumulates a histogram of b in h.
575 fn histogram(b: []const u8, h: *[286]u16) void {
576 // Clear histogram
577 for (h, 0..) |_, i| {
578 h[i] = 0;
579 }
580
581 var lh = h.*[0..256];
582 for (b) |t| {
583 lh[t] += 1;
584 }
585 }
586 };
587}
588
589// tests
590const expect = std.testing.expect;
591const fmt = std.fmt;
592const testing = std.testing;
593const ArrayList = std.ArrayList;
594
595const TestCase = @import("testdata/block_writer.zig").TestCase;
596const testCases = @import("testdata/block_writer.zig").testCases;
597
598// tests if the writeBlock encoding has changed.
599test "write" {
600 inline for (0..testCases.len) |i| {
601 try testBlock(testCases[i], .write_block);
602 }
603}
604
605// tests if the writeBlockDynamic encoding has changed.
606test "dynamicBlock" {
607 inline for (0..testCases.len) |i| {
608 try testBlock(testCases[i], .write_dyn_block);
609 }
610}
611
612test "huffmanBlock" {
613 inline for (0..testCases.len) |i| {
614 try testBlock(testCases[i], .write_huffman_block);
615 }
616 try testBlock(.{
617 .tokens = &[_]Token{},
618 .input = "huffman-rand-max.input",
619 .want = "huffman-rand-max.{s}.expect",
620 }, .write_huffman_block);
621}
622
623const TestFn = enum {
624 write_block,
625 write_dyn_block, // write dynamic block
626 write_huffman_block,
627
628 fn to_s(self: TestFn) []const u8 {
629 return switch (self) {
630 .write_block => "wb",
631 .write_dyn_block => "dyn",
632 .write_huffman_block => "huff",
633 };
634 }
635
636 fn write(
637 comptime self: TestFn,
638 bw: anytype,
639 tok: []const Token,
640 input: ?[]const u8,
641 final: bool,
642 ) !void {
643 switch (self) {
644 .write_block => try bw.write(tok, final, input),
645 .write_dyn_block => try bw.dynamicBlock(tok, final, input),
646 .write_huffman_block => try bw.huffmanBlock(input.?, final),
647 }
648 try bw.flush();
649 }
650};
651
652// testBlock tests a block against its references
653//
654// size
655// 64K [file-name].input - input non compressed file
656// 8.1K [file-name].golden -
657// 78 [file-name].dyn.expect - output with writeBlockDynamic
658// 78 [file-name].wb.expect - output with writeBlock
659// 8.1K [file-name].huff.expect - output with writeBlockHuff
660// 78 [file-name].dyn.expect-noinput - output with writeBlockDynamic when input is null
661// 78 [file-name].wb.expect-noinput - output with writeBlock when input is null
662//
663// wb - writeBlock
664// dyn - writeBlockDynamic
665// huff - writeBlockHuff
666//
667fn testBlock(comptime tc: TestCase, comptime tfn: TestFn) !void {
668 if (tc.input.len != 0 and tc.want.len != 0) {
669 const want_name = comptime fmt.comptimePrint(tc.want, .{tfn.to_s()});
670 const input = @embedFile("testdata/block_writer/" ++ tc.input);
671 const want = @embedFile("testdata/block_writer/" ++ want_name);
672 try testWriteBlock(tfn, input, want, tc.tokens);
673 }
674
675 if (tfn == .write_huffman_block) {
676 return;
677 }
678
679 const want_name_no_input = comptime fmt.comptimePrint(tc.want_no_input, .{tfn.to_s()});
680 const want = @embedFile("testdata/block_writer/" ++ want_name_no_input);
681 try testWriteBlock(tfn, null, want, tc.tokens);
682}
683
684// Uses writer function `tfn` to write `tokens`, tests that we got `want` as output.
685fn testWriteBlock(comptime tfn: TestFn, input: ?[]const u8, want: []const u8, tokens: []const Token) !void {
686 var buf = ArrayList(u8).init(testing.allocator);
687 var bw = blockWriter(buf.writer());
688 try tfn.write(&bw, tokens, input, false);
689 var got = buf.items;
690 try testing.expectEqualSlices(u8, want, got); // expect writeBlock to yield expected result
691 try expect(got[0] & 0b0000_0001 == 0); // bfinal is not set
692 //
693 // Test if the writer produces the same output after reset.
694 buf.deinit();
695 buf = ArrayList(u8).init(testing.allocator);
696 defer buf.deinit();
697 bw.setWriter(buf.writer());
698
699 try tfn.write(&bw, tokens, input, true);
700 try bw.flush();
701 got = buf.items;
702
703 try expect(got[0] & 1 == 1); // bfinal is set
704 buf.items[0] &= 0b1111_1110; // remove bfinal bit, so we can run test slices
705 try testing.expectEqualSlices(u8, want, got); // expect writeBlock to yield expected result
706}
lib/std/compress/flate/consts.zig deleted-49
...@@ -1,49 +0,0 @@
1pub const deflate = struct {
2 // Number of tokens to accumulate in deflate before starting block encoding.
3 //
4 // In zlib this depends on memlevel: 6 + memlevel, where default memlevel is
5 // 8 and max 9 that gives 14 or 15 bits.
6 pub const tokens = 1 << 15;
7};
8
9pub const match = struct {
10 pub const base_length = 3; // smallest match length per the RFC section 3.2.5
11 pub const min_length = 4; // min length used in this algorithm
12 pub const max_length = 258;
13
14 pub const min_distance = 1;
15 pub const max_distance = 32768;
16};
17
18pub const history = struct {
19 pub const len = match.max_distance;
20};
21
22pub const lookup = struct {
23 pub const bits = 15;
24 pub const len = 1 << bits;
25 pub const shift = 32 - bits;
26};
27
28pub const huffman = struct {
29 // The odd order in which the codegen code sizes are written.
30 pub const codegen_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
31 // The number of codegen codes.
32 pub const codegen_code_count = 19;
33
34 // The largest distance code.
35 pub const distance_code_count = 30;
36
37 // Maximum number of literals.
38 pub const max_num_lit = 286;
39
40 // Max number of frequencies used for a Huffman Code
41 // Possible lengths are codegen_code_count (19), distance_code_count (30) and max_num_lit (286).
42 // The largest of these is max_num_lit.
43 pub const max_num_frequencies = max_num_lit;
44
45 // Biggest block size for uncompressed block.
46 pub const max_store_block_size = 65535;
47 // The special code used to mark the end of a block.
48 pub const end_block_marker = 256;
49};
lib/std/compress/flate/container.zig deleted-208
...@@ -1,208 +0,0 @@
1//! Container of the deflate bit stream body. Container adds header before
2//! deflate bit stream and footer after. It can bi gzip, zlib or raw (no header,
3//! no footer, raw bit stream).
4//!
5//! Zlib format is defined in rfc 1950. Header has 2 bytes and footer 4 bytes
6//! addler 32 checksum.
7//!
8//! Gzip format is defined in rfc 1952. Header has 10+ bytes and footer 4 bytes
9//! crc32 checksum and 4 bytes of uncompressed data length.
10//!
11//!
12//! rfc 1950: https://datatracker.ietf.org/doc/html/rfc1950#page-4
13//! rfc 1952: https://datatracker.ietf.org/doc/html/rfc1952#page-5
14//!
15
16const std = @import("std");
17
18pub const Container = enum {
19 raw, // no header or footer
20 gzip, // gzip header and footer
21 zlib, // zlib header and footer
22
23 pub fn size(w: Container) usize {
24 return headerSize(w) + footerSize(w);
25 }
26
27 pub fn headerSize(w: Container) usize {
28 return switch (w) {
29 .gzip => 10,
30 .zlib => 2,
31 .raw => 0,
32 };
33 }
34
35 pub fn footerSize(w: Container) usize {
36 return switch (w) {
37 .gzip => 8,
38 .zlib => 4,
39 .raw => 0,
40 };
41 }
42
43 pub const list = [_]Container{ .raw, .gzip, .zlib };
44
45 pub const Error = error{
46 BadGzipHeader,
47 BadZlibHeader,
48 WrongGzipChecksum,
49 WrongGzipSize,
50 WrongZlibChecksum,
51 };
52
53 pub fn writeHeader(comptime wrap: Container, writer: anytype) !void {
54 switch (wrap) {
55 .gzip => {
56 // GZIP 10 byte header (https://datatracker.ietf.org/doc/html/rfc1952#page-5):
57 // - ID1 (IDentification 1), always 0x1f
58 // - ID2 (IDentification 2), always 0x8b
59 // - CM (Compression Method), always 8 = deflate
60 // - FLG (Flags), all set to 0
61 // - 4 bytes, MTIME (Modification time), not used, all set to zero
62 // - XFL (eXtra FLags), all set to zero
63 // - OS (Operating System), 03 = Unix
64 const gzipHeader = [_]u8{ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 };
65 try writer.writeAll(&gzipHeader);
66 },
67 .zlib => {
68 // ZLIB has a two-byte header (https://datatracker.ietf.org/doc/html/rfc1950#page-4):
69 // 1st byte:
70 // - First four bits is the CINFO (compression info), which is 7 for the default deflate window size.
71 // - The next four bits is the CM (compression method), which is 8 for deflate.
72 // 2nd byte:
73 // - Two bits is the FLEVEL (compression level). Values are: 0=fastest, 1=fast, 2=default, 3=best.
74 // - The next bit, FDICT, is set if a dictionary is given.
75 // - The final five FCHECK bits form a mod-31 checksum.
76 //
77 // CINFO = 7, CM = 8, FLEVEL = 0b10, FDICT = 0, FCHECK = 0b11100
78 const zlibHeader = [_]u8{ 0x78, 0b10_0_11100 };
79 try writer.writeAll(&zlibHeader);
80 },
81 .raw => {},
82 }
83 }
84
85 pub fn writeFooter(comptime wrap: Container, hasher: *Hasher(wrap), writer: anytype) !void {
86 var bits: [4]u8 = undefined;
87 switch (wrap) {
88 .gzip => {
89 // GZIP 8 bytes footer
90 // - 4 bytes, CRC32 (CRC-32)
91 // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32
92 std.mem.writeInt(u32, &bits, hasher.chksum(), .little);
93 try writer.writeAll(&bits);
94
95 std.mem.writeInt(u32, &bits, hasher.bytesRead(), .little);
96 try writer.writeAll(&bits);
97 },
98 .zlib => {
99 // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
100 // 4 bytes of ADLER32 (Adler-32 checksum)
101 // Checksum value of the uncompressed data (excluding any
102 // dictionary data) computed according to Adler-32
103 // algorithm.
104 std.mem.writeInt(u32, &bits, hasher.chksum(), .big);
105 try writer.writeAll(&bits);
106 },
107 .raw => {},
108 }
109 }
110
111 pub fn parseHeader(comptime wrap: Container, reader: anytype) !void {
112 switch (wrap) {
113 .gzip => try parseGzipHeader(reader),
114 .zlib => try parseZlibHeader(reader),
115 .raw => {},
116 }
117 }
118
119 fn parseGzipHeader(reader: anytype) !void {
120 const magic1 = try reader.read(u8);
121 const magic2 = try reader.read(u8);
122 const method = try reader.read(u8);
123 const flags = try reader.read(u8);
124 try reader.skipBytes(6); // mtime(4), xflags, os
125 if (magic1 != 0x1f or magic2 != 0x8b or method != 0x08)
126 return error.BadGzipHeader;
127 // Flags description: https://www.rfc-editor.org/rfc/rfc1952.html#page-5
128 if (flags != 0) {
129 if (flags & 0b0000_0100 != 0) { // FEXTRA
130 const extra_len = try reader.read(u16);
131 try reader.skipBytes(extra_len);
132 }
133 if (flags & 0b0000_1000 != 0) { // FNAME
134 try reader.skipStringZ();
135 }
136 if (flags & 0b0001_0000 != 0) { // FCOMMENT
137 try reader.skipStringZ();
138 }
139 if (flags & 0b0000_0010 != 0) { // FHCRC
140 try reader.skipBytes(2);
141 }
142 }
143 }
144
145 fn parseZlibHeader(reader: anytype) !void {
146 const cm = try reader.read(u4);
147 const cinfo = try reader.read(u4);
148 _ = try reader.read(u8);
149 if (cm != 8 or cinfo > 7) {
150 return error.BadZlibHeader;
151 }
152 }
153
154 pub fn parseFooter(comptime wrap: Container, hasher: *Hasher(wrap), reader: anytype) !void {
155 switch (wrap) {
156 .gzip => {
157 try reader.fill(0);
158 if (try reader.read(u32) != hasher.chksum()) return error.WrongGzipChecksum;
159 if (try reader.read(u32) != hasher.bytesRead()) return error.WrongGzipSize;
160 },
161 .zlib => {
162 const chksum: u32 = @byteSwap(hasher.chksum());
163 if (try reader.read(u32) != chksum) return error.WrongZlibChecksum;
164 },
165 .raw => {},
166 }
167 }
168
169 pub fn Hasher(comptime wrap: Container) type {
170 const HasherType = switch (wrap) {
171 .gzip => std.hash.Crc32,
172 .zlib => std.hash.Adler32,
173 .raw => struct {
174 pub fn init() @This() {
175 return .{};
176 }
177 },
178 };
179
180 return struct {
181 hasher: HasherType = HasherType.init(),
182 bytes: usize = 0,
183
184 const Self = @This();
185
186 pub fn update(self: *Self, buf: []const u8) void {
187 switch (wrap) {
188 .raw => {},
189 else => {
190 self.hasher.update(buf);
191 self.bytes += buf.len;
192 },
193 }
194 }
195
196 pub fn chksum(self: *Self) u32 {
197 switch (wrap) {
198 .raw => return 0,
199 else => return self.hasher.final(),
200 }
201 }
202
203 pub fn bytesRead(self: *Self) u32 {
204 return @truncate(self.bytes);
205 }
206 };
207 }
208};
lib/std/compress/flate/deflate.zig deleted-744
...@@ -1,744 +0,0 @@
1const std = @import("std");
2const io = std.io;
3const assert = std.debug.assert;
4const testing = std.testing;
5const expect = testing.expect;
6const print = std.debug.print;
7
8const Token = @import("Token.zig");
9const consts = @import("consts.zig");
10const BlockWriter = @import("block_writer.zig").BlockWriter;
11const Container = @import("container.zig").Container;
12const SlidingWindow = @import("SlidingWindow.zig");
13const Lookup = @import("Lookup.zig");
14
15pub const Options = struct {
16 level: Level = .default,
17};
18
19/// Trades between speed and compression size.
20/// Starts with level 4: in [zlib](https://github.com/madler/zlib/blob/abd3d1a28930f89375d4b41408b39f6c1be157b2/deflate.c#L115C1-L117C43)
21/// levels 1-3 are using different algorithm to perform faster but with less
22/// compression. That is not implemented here.
23pub const Level = enum(u4) {
24 // zig fmt: off
25 fast = 0xb, level_4 = 4,
26 level_5 = 5,
27 default = 0xc, level_6 = 6,
28 level_7 = 7,
29 level_8 = 8,
30 best = 0xd, level_9 = 9,
31 // zig fmt: on
32};
33
34/// Algorithm knobs for each level.
35const LevelArgs = struct {
36 good: u16, // Do less lookups if we already have match of this length.
37 nice: u16, // Stop looking for better match if we found match with at least this length.
38 lazy: u16, // Don't do lazy match find if got match with at least this length.
39 chain: u16, // How many lookups for previous match to perform.
40
41 pub fn get(level: Level) LevelArgs {
42 // zig fmt: off
43 return switch (level) {
44 .fast, .level_4 => .{ .good = 4, .lazy = 4, .nice = 16, .chain = 16 },
45 .level_5 => .{ .good = 8, .lazy = 16, .nice = 32, .chain = 32 },
46 .default, .level_6 => .{ .good = 8, .lazy = 16, .nice = 128, .chain = 128 },
47 .level_7 => .{ .good = 8, .lazy = 32, .nice = 128, .chain = 256 },
48 .level_8 => .{ .good = 32, .lazy = 128, .nice = 258, .chain = 1024 },
49 .best, .level_9 => .{ .good = 32, .lazy = 258, .nice = 258, .chain = 4096 },
50 };
51 // zig fmt: on
52 }
53};
54
55/// Compress plain data from reader into compressed stream written to writer.
56pub fn compress(comptime container: Container, reader: anytype, writer: anytype, options: Options) !void {
57 var c = try compressor(container, writer, options);
58 try c.compress(reader);
59 try c.finish();
60}
61
62/// Create compressor for writer type.
63pub fn compressor(comptime container: Container, writer: anytype, options: Options) !Compressor(
64 container,
65 @TypeOf(writer),
66) {
67 return try Compressor(container, @TypeOf(writer)).init(writer, options);
68}
69
70/// Compressor type.
71pub fn Compressor(comptime container: Container, comptime WriterType: type) type {
72 const TokenWriterType = BlockWriter(WriterType);
73 return Deflate(container, WriterType, TokenWriterType);
74}
75
76/// Default compression algorithm. Has two steps: tokenization and token
77/// encoding.
78///
79/// Tokenization takes uncompressed input stream and produces list of tokens.
80/// Each token can be literal (byte of data) or match (backrefernce to previous
81/// data with length and distance). Tokenization accumulators 32K tokens, when
82/// full or `flush` is called tokens are passed to the `block_writer`. Level
83/// defines how hard (how slow) it tries to find match.
84///
85/// Block writer will decide which type of deflate block to write (stored, fixed,
86/// dynamic) and encode tokens to the output byte stream. Client has to call
87/// `finish` to write block with the final bit set.
88///
89/// Container defines type of header and footer which can be gzip, zlib or raw.
90/// They all share same deflate body. Raw has no header or footer just deflate
91/// body.
92///
93/// Compression algorithm explained in rfc-1951 (slightly edited for this case):
94///
95/// The compressor uses a chained hash table `lookup` to find duplicated
96/// strings, using a hash function that operates on 4-byte sequences. At any
97/// given point during compression, let XYZW be the next 4 input bytes
98/// (lookahead) to be examined (not necessarily all different, of course).
99/// First, the compressor examines the hash chain for XYZW. If the chain is
100/// empty, the compressor simply writes out X as a literal byte and advances
101/// one byte in the input. If the hash chain is not empty, indicating that the
102/// sequence XYZW (or, if we are unlucky, some other 4 bytes with the same
103/// hash function value) has occurred recently, the compressor compares all
104/// strings on the XYZW hash chain with the actual input data sequence
105/// starting at the current point, and selects the longest match.
106///
107/// To improve overall compression, the compressor defers the selection of
108/// matches ("lazy matching"): after a match of length N has been found, the
109/// compressor searches for a longer match starting at the next input byte. If
110/// it finds a longer match, it truncates the previous match to a length of
111/// one (thus producing a single literal byte) and then emits the longer
112/// match. Otherwise, it emits the original match, and, as described above,
113/// advances N bytes before continuing.
114///
115///
116/// Allocates statically ~400K (192K lookup, 128K tokens, 64K window).
117///
118/// Deflate function accepts BlockWriterType so we can change that in test to test
119/// just tokenization part.
120///
121fn Deflate(comptime container: Container, comptime WriterType: type, comptime BlockWriterType: type) type {
122 return struct {
123 lookup: Lookup = .{},
124 win: SlidingWindow = .{},
125 tokens: Tokens = .{},
126 wrt: WriterType,
127 block_writer: BlockWriterType,
128 level: LevelArgs,
129 hasher: container.Hasher() = .{},
130
131 // Match and literal at the previous position.
132 // Used for lazy match finding in processWindow.
133 prev_match: ?Token = null,
134 prev_literal: ?u8 = null,
135
136 const Self = @This();
137
138 pub fn init(wrt: WriterType, options: Options) !Self {
139 const self = Self{
140 .wrt = wrt,
141 .block_writer = BlockWriterType.init(wrt),
142 .level = LevelArgs.get(options.level),
143 };
144 try container.writeHeader(self.wrt);
145 return self;
146 }
147
148 const FlushOption = enum { none, flush, final };
149
150 // Process data in window and create tokens. If token buffer is full
151 // flush tokens to the token writer. In the case of `flush` or `final`
152 // option it will process all data from the window. In the `none` case
153 // it will preserve some data for the next match.
154 fn tokenize(self: *Self, flush_opt: FlushOption) !void {
155 // flush - process all data from window
156 const should_flush = (flush_opt != .none);
157
158 // While there is data in active lookahead buffer.
159 while (self.win.activeLookahead(should_flush)) |lh| {
160 var step: u16 = 1; // 1 in the case of literal, match length otherwise
161 const pos: u16 = self.win.pos();
162 const literal = lh[0]; // literal at current position
163 const min_len: u16 = if (self.prev_match) |m| m.length() else 0;
164
165 // Try to find match at least min_len long.
166 if (self.findMatch(pos, lh, min_len)) |match| {
167 // Found better match than previous.
168 try self.addPrevLiteral();
169
170 // Is found match length good enough?
171 if (match.length() >= self.level.lazy) {
172 // Don't try to lazy find better match, use this.
173 step = try self.addMatch(match);
174 } else {
175 // Store this match.
176 self.prev_literal = literal;
177 self.prev_match = match;
178 }
179 } else {
180 // There is no better match at current pos then it was previous.
181 // Write previous match or literal.
182 if (self.prev_match) |m| {
183 // Write match from previous position.
184 step = try self.addMatch(m) - 1; // we already advanced 1 from previous position
185 } else {
186 // No match at previous position.
187 // Write previous literal if any, and remember this literal.
188 try self.addPrevLiteral();
189 self.prev_literal = literal;
190 }
191 }
192 // Advance window and add hashes.
193 self.windowAdvance(step, lh, pos);
194 }
195
196 if (should_flush) {
197 // In the case of flushing, last few lookahead buffers were smaller then min match len.
198 // So only last literal can be unwritten.
199 assert(self.prev_match == null);
200 try self.addPrevLiteral();
201 self.prev_literal = null;
202
203 try self.flushTokens(flush_opt);
204 }
205 }
206
207 fn windowAdvance(self: *Self, step: u16, lh: []const u8, pos: u16) void {
208 // current position is already added in findMatch
209 self.lookup.bulkAdd(lh[1..], step - 1, pos + 1);
210 self.win.advance(step);
211 }
212
213 // Add previous literal (if any) to the tokens list.
214 fn addPrevLiteral(self: *Self) !void {
215 if (self.prev_literal) |l| try self.addToken(Token.initLiteral(l));
216 }
217
218 // Add match to the tokens list, reset prev pointers.
219 // Returns length of the added match.
220 fn addMatch(self: *Self, m: Token) !u16 {
221 try self.addToken(m);
222 self.prev_literal = null;
223 self.prev_match = null;
224 return m.length();
225 }
226
227 fn addToken(self: *Self, token: Token) !void {
228 self.tokens.add(token);
229 if (self.tokens.full()) try self.flushTokens(.none);
230 }
231
232 // Finds largest match in the history window with the data at current pos.
233 fn findMatch(self: *Self, pos: u16, lh: []const u8, min_len: u16) ?Token {
234 var len: u16 = min_len;
235 // Previous location with the same hash (same 4 bytes).
236 var prev_pos = self.lookup.add(lh, pos);
237 // Last found match.
238 var match: ?Token = null;
239
240 // How much back-references to try, performance knob.
241 var chain: usize = self.level.chain;
242 if (len >= self.level.good) {
243 // If we've got a match that's good enough, only look in 1/4 the chain.
244 chain >>= 2;
245 }
246
247 // Hot path loop!
248 while (prev_pos > 0 and chain > 0) : (chain -= 1) {
249 const distance = pos - prev_pos;
250 if (distance > consts.match.max_distance)
251 break;
252
253 const new_len = self.win.match(prev_pos, pos, len);
254 if (new_len > len) {
255 match = Token.initMatch(@intCast(distance), new_len);
256 if (new_len >= self.level.nice) {
257 // The match is good enough that we don't try to find a better one.
258 return match;
259 }
260 len = new_len;
261 }
262 prev_pos = self.lookup.prev(prev_pos);
263 }
264
265 return match;
266 }
267
268 fn flushTokens(self: *Self, flush_opt: FlushOption) !void {
269 // Pass tokens to the token writer
270 try self.block_writer.write(self.tokens.tokens(), flush_opt == .final, self.win.tokensBuffer());
271 // Stored block ensures byte alignment.
272 // It has 3 bits (final, block_type) and then padding until byte boundary.
273 // After that everything is aligned to the boundary in the stored block.
274 // Empty stored block is Ob000 + (0-7) bits of padding + 0x00 0x00 0xFF 0xFF.
275 // Last 4 bytes are byte aligned.
276 if (flush_opt == .flush) {
277 try self.block_writer.storedBlock("", false);
278 }
279 if (flush_opt != .none) {
280 // Safe to call only when byte aligned or it is OK to add
281 // padding bits (on last byte of the final block).
282 try self.block_writer.flush();
283 }
284 // Reset internal tokens store.
285 self.tokens.reset();
286 // Notify win that tokens are flushed.
287 self.win.flush();
288 }
289
290 // Slide win and if needed lookup tables.
291 fn slide(self: *Self) void {
292 const n = self.win.slide();
293 self.lookup.slide(n);
294 }
295
296 /// Compresses as much data as possible, stops when the reader becomes
297 /// empty. It will introduce some output latency (reading input without
298 /// producing all output) because some data are still in internal
299 /// buffers.
300 ///
301 /// It is up to the caller to call flush (if needed) or finish (required)
302 /// when is need to output any pending data or complete stream.
303 ///
304 pub fn compress(self: *Self, reader: anytype) !void {
305 while (true) {
306 // Fill window from reader
307 const buf = self.win.writable();
308 if (buf.len == 0) {
309 try self.tokenize(.none);
310 self.slide();
311 continue;
312 }
313 const n = try reader.readAll(buf);
314 self.hasher.update(buf[0..n]);
315 self.win.written(n);
316 // Process window
317 try self.tokenize(.none);
318 // Exit when no more data in reader
319 if (n < buf.len) break;
320 }
321 }
322
323 /// Flushes internal buffers to the output writer. Outputs empty stored
324 /// block to sync bit stream to the byte boundary, so that the
325 /// decompressor can get all input data available so far.
326 ///
327 /// It is useful mainly in compressed network protocols, to ensure that
328 /// deflate bit stream can be used as byte stream. May degrade
329 /// compression so it should be used only when necessary.
330 ///
331 /// Completes the current deflate block and follows it with an empty
332 /// stored block that is three zero bits plus filler bits to the next
333 /// byte, followed by four bytes (00 00 ff ff).
334 ///
335 pub fn flush(self: *Self) !void {
336 try self.tokenize(.flush);
337 }
338
339 /// Completes deflate bit stream by writing any pending data as deflate
340 /// final deflate block. HAS to be called once all data are written to
341 /// the compressor as a signal that next block has to have final bit
342 /// set.
343 ///
344 pub fn finish(self: *Self) !void {
345 try self.tokenize(.final);
346 try container.writeFooter(&self.hasher, self.wrt);
347 }
348
349 /// Use another writer while preserving history. Most probably flush
350 /// should be called on old writer before setting new.
351 pub fn setWriter(self: *Self, new_writer: WriterType) void {
352 self.block_writer.setWriter(new_writer);
353 self.wrt = new_writer;
354 }
355
356 // Writer interface
357
358 pub const Writer = io.GenericWriter(*Self, Error, write);
359 pub const Error = BlockWriterType.Error;
360
361 /// Write `input` of uncompressed data.
362 /// See compress.
363 pub fn write(self: *Self, input: []const u8) !usize {
364 var fbs = io.fixedBufferStream(input);
365 try self.compress(fbs.reader());
366 return input.len;
367 }
368
369 pub fn writer(self: *Self) Writer {
370 return .{ .context = self };
371 }
372 };
373}
374
375// Tokens store
376const Tokens = struct {
377 list: [consts.deflate.tokens]Token = undefined,
378 pos: usize = 0,
379
380 fn add(self: *Tokens, t: Token) void {
381 self.list[self.pos] = t;
382 self.pos += 1;
383 }
384
385 fn full(self: *Tokens) bool {
386 return self.pos == self.list.len;
387 }
388
389 fn reset(self: *Tokens) void {
390 self.pos = 0;
391 }
392
393 fn tokens(self: *Tokens) []const Token {
394 return self.list[0..self.pos];
395 }
396};
397
398/// Creates huffman only deflate blocks. Disables Lempel-Ziv match searching and
399/// only performs Huffman entropy encoding. Results in faster compression, much
400/// less memory requirements during compression but bigger compressed sizes.
401pub const huffman = struct {
402 pub fn compress(comptime container: Container, reader: anytype, writer: anytype) !void {
403 var c = try huffman.compressor(container, writer);
404 try c.compress(reader);
405 try c.finish();
406 }
407
408 pub fn Compressor(comptime container: Container, comptime WriterType: type) type {
409 return SimpleCompressor(.huffman, container, WriterType);
410 }
411
412 pub fn compressor(comptime container: Container, writer: anytype) !huffman.Compressor(container, @TypeOf(writer)) {
413 return try huffman.Compressor(container, @TypeOf(writer)).init(writer);
414 }
415};
416
417/// Creates store blocks only. Data are not compressed only packed into deflate
418/// store blocks. That adds 9 bytes of header for each block. Max stored block
419/// size is 64K. Block is emitted when flush is called on on finish.
420pub const store = struct {
421 pub fn compress(comptime container: Container, reader: anytype, writer: anytype) !void {
422 var c = try store.compressor(container, writer);
423 try c.compress(reader);
424 try c.finish();
425 }
426
427 pub fn Compressor(comptime container: Container, comptime WriterType: type) type {
428 return SimpleCompressor(.store, container, WriterType);
429 }
430
431 pub fn compressor(comptime container: Container, writer: anytype) !store.Compressor(container, @TypeOf(writer)) {
432 return try store.Compressor(container, @TypeOf(writer)).init(writer);
433 }
434};
435
436const SimpleCompressorKind = enum {
437 huffman,
438 store,
439};
440
441fn simpleCompressor(
442 comptime kind: SimpleCompressorKind,
443 comptime container: Container,
444 writer: anytype,
445) !SimpleCompressor(kind, container, @TypeOf(writer)) {
446 return try SimpleCompressor(kind, container, @TypeOf(writer)).init(writer);
447}
448
449fn SimpleCompressor(
450 comptime kind: SimpleCompressorKind,
451 comptime container: Container,
452 comptime WriterType: type,
453) type {
454 const BlockWriterType = BlockWriter(WriterType);
455 return struct {
456 buffer: [65535]u8 = undefined, // because store blocks are limited to 65535 bytes
457 wp: usize = 0,
458
459 wrt: WriterType,
460 block_writer: BlockWriterType,
461 hasher: container.Hasher() = .{},
462
463 const Self = @This();
464
465 pub fn init(wrt: WriterType) !Self {
466 const self = Self{
467 .wrt = wrt,
468 .block_writer = BlockWriterType.init(wrt),
469 };
470 try container.writeHeader(self.wrt);
471 return self;
472 }
473
474 pub fn flush(self: *Self) !void {
475 try self.flushBuffer(false);
476 try self.block_writer.storedBlock("", false);
477 try self.block_writer.flush();
478 }
479
480 pub fn finish(self: *Self) !void {
481 try self.flushBuffer(true);
482 try self.block_writer.flush();
483 try container.writeFooter(&self.hasher, self.wrt);
484 }
485
486 fn flushBuffer(self: *Self, final: bool) !void {
487 const buf = self.buffer[0..self.wp];
488 switch (kind) {
489 .huffman => try self.block_writer.huffmanBlock(buf, final),
490 .store => try self.block_writer.storedBlock(buf, final),
491 }
492 self.wp = 0;
493 }
494
495 // Writes all data from the input reader of uncompressed data.
496 // It is up to the caller to call flush or finish if there is need to
497 // output compressed blocks.
498 pub fn compress(self: *Self, reader: anytype) !void {
499 while (true) {
500 // read from rdr into buffer
501 const buf = self.buffer[self.wp..];
502 if (buf.len == 0) {
503 try self.flushBuffer(false);
504 continue;
505 }
506 const n = try reader.readAll(buf);
507 self.hasher.update(buf[0..n]);
508 self.wp += n;
509 if (n < buf.len) break; // no more data in reader
510 }
511 }
512
513 // Writer interface
514
515 pub const Writer = io.GenericWriter(*Self, Error, write);
516 pub const Error = BlockWriterType.Error;
517
518 // Write `input` of uncompressed data.
519 pub fn write(self: *Self, input: []const u8) !usize {
520 var fbs = io.fixedBufferStream(input);
521 try self.compress(fbs.reader());
522 return input.len;
523 }
524
525 pub fn writer(self: *Self) Writer {
526 return .{ .context = self };
527 }
528 };
529}
530
531const builtin = @import("builtin");
532
533test "tokenization" {
534 const L = Token.initLiteral;
535 const M = Token.initMatch;
536
537 const cases = [_]struct {
538 data: []const u8,
539 tokens: []const Token,
540 }{
541 .{
542 .data = "Blah blah blah blah blah!",
543 .tokens = &[_]Token{ L('B'), L('l'), L('a'), L('h'), L(' '), L('b'), M(5, 18), L('!') },
544 },
545 .{
546 .data = "ABCDEABCD ABCDEABCD",
547 .tokens = &[_]Token{
548 L('A'), L('B'), L('C'), L('D'), L('E'), L('A'), L('B'), L('C'), L('D'), L(' '),
549 L('A'), M(10, 8),
550 },
551 },
552 };
553
554 for (cases) |c| {
555 inline for (Container.list) |container| { // for each wrapping
556
557 var cw = io.countingWriter(io.null_writer);
558 const cww = cw.writer();
559 var df = try Deflate(container, @TypeOf(cww), TestTokenWriter).init(cww, .{});
560
561 _ = try df.write(c.data);
562 try df.flush();
563
564 // df.token_writer.show();
565 try expect(df.block_writer.pos == c.tokens.len); // number of tokens written
566 try testing.expectEqualSlices(Token, df.block_writer.get(), c.tokens); // tokens match
567
568 try testing.expectEqual(container.headerSize(), cw.bytes_written);
569 try df.finish();
570 try testing.expectEqual(container.size(), cw.bytes_written);
571 }
572 }
573}
574
575// Tests that tokens written are equal to expected token list.
576const TestTokenWriter = struct {
577 const Self = @This();
578
579 pos: usize = 0,
580 actual: [128]Token = undefined,
581
582 pub fn init(_: anytype) Self {
583 return .{};
584 }
585 pub fn write(self: *Self, tokens: []const Token, _: bool, _: ?[]const u8) !void {
586 for (tokens) |t| {
587 self.actual[self.pos] = t;
588 self.pos += 1;
589 }
590 }
591
592 pub fn storedBlock(_: *Self, _: []const u8, _: bool) !void {}
593
594 pub fn get(self: *Self) []Token {
595 return self.actual[0..self.pos];
596 }
597
598 pub fn show(self: *Self) void {
599 print("\n", .{});
600 for (self.get()) |t| {
601 t.show();
602 }
603 }
604
605 pub fn flush(_: *Self) !void {}
606};
607
608test "file tokenization" {
609 const levels = [_]Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 };
610 const cases = [_]struct {
611 data: []const u8, // uncompressed content
612 // expected number of tokens producet in deflate tokenization
613 tokens_count: [levels.len]usize = .{0} ** levels.len,
614 }{
615 .{
616 .data = @embedFile("testdata/rfc1951.txt"),
617 .tokens_count = .{ 7675, 7672, 7599, 7594, 7598, 7599 },
618 },
619
620 .{
621 .data = @embedFile("testdata/block_writer/huffman-null-max.input"),
622 .tokens_count = .{ 257, 257, 257, 257, 257, 257 },
623 },
624 .{
625 .data = @embedFile("testdata/block_writer/huffman-pi.input"),
626 .tokens_count = .{ 2570, 2564, 2564, 2564, 2564, 2564 },
627 },
628 .{
629 .data = @embedFile("testdata/block_writer/huffman-text.input"),
630 .tokens_count = .{ 235, 234, 234, 234, 234, 234 },
631 },
632 .{
633 .data = @embedFile("testdata/fuzz/roundtrip1.input"),
634 .tokens_count = .{ 333, 331, 331, 331, 331, 331 },
635 },
636 .{
637 .data = @embedFile("testdata/fuzz/roundtrip2.input"),
638 .tokens_count = .{ 334, 334, 334, 334, 334, 334 },
639 },
640 };
641
642 for (cases) |case| { // for each case
643 const data = case.data;
644
645 for (levels, 0..) |level, i| { // for each compression level
646 var original = io.fixedBufferStream(data);
647
648 // buffer for decompressed data
649 var al = std.ArrayList(u8).init(testing.allocator);
650 defer al.deinit();
651 const writer = al.writer();
652
653 // create compressor
654 const WriterType = @TypeOf(writer);
655 const TokenWriter = TokenDecoder(@TypeOf(writer));
656 var cmp = try Deflate(.raw, WriterType, TokenWriter).init(writer, .{ .level = level });
657
658 // Stream uncompressed `original` data to the compressor. It will
659 // produce tokens list and pass that list to the TokenDecoder. This
660 // TokenDecoder uses CircularBuffer from inflate to convert list of
661 // tokens back to the uncompressed stream.
662 try cmp.compress(original.reader());
663 try cmp.flush();
664 const expected_count = case.tokens_count[i];
665 const actual = cmp.block_writer.tokens_count;
666 if (expected_count == 0) {
667 print("actual token count {d}\n", .{actual});
668 } else {
669 try testing.expectEqual(expected_count, actual);
670 }
671
672 try testing.expectEqual(data.len, al.items.len);
673 try testing.expectEqualSlices(u8, data, al.items);
674 }
675 }
676}
677
678fn TokenDecoder(comptime WriterType: type) type {
679 return struct {
680 const CircularBuffer = @import("CircularBuffer.zig");
681 hist: CircularBuffer = .{},
682 wrt: WriterType,
683 tokens_count: usize = 0,
684
685 const Self = @This();
686
687 pub fn init(wrt: WriterType) Self {
688 return .{ .wrt = wrt };
689 }
690
691 pub fn write(self: *Self, tokens: []const Token, _: bool, _: ?[]const u8) !void {
692 self.tokens_count += tokens.len;
693 for (tokens) |t| {
694 switch (t.kind) {
695 .literal => self.hist.write(t.literal()),
696 .match => try self.hist.writeMatch(t.length(), t.distance()),
697 }
698 if (self.hist.free() < 285) try self.flushWin();
699 }
700 try self.flushWin();
701 }
702
703 pub fn storedBlock(_: *Self, _: []const u8, _: bool) !void {}
704
705 fn flushWin(self: *Self) !void {
706 while (true) {
707 const buf = self.hist.read();
708 if (buf.len == 0) break;
709 try self.wrt.writeAll(buf);
710 }
711 }
712
713 pub fn flush(_: *Self) !void {}
714 };
715}
716
717test "store simple compressor" {
718 const data = "Hello world!";
719 const expected = [_]u8{
720 0x1, // block type 0, final bit set
721 0xc, 0x0, // len = 12
722 0xf3, 0xff, // ~len
723 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', //
724 //0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21,
725 };
726
727 var fbs = std.io.fixedBufferStream(data);
728 var al = std.ArrayList(u8).init(testing.allocator);
729 defer al.deinit();
730
731 var cmp = try store.compressor(.raw, al.writer());
732 try cmp.compress(fbs.reader());
733 try cmp.finish();
734 try testing.expectEqualSlices(u8, &expected, al.items);
735
736 fbs.reset();
737 try al.resize(0);
738
739 // huffman only compresoor will also emit store block for this small sample
740 var hc = try huffman.compressor(.raw, al.writer());
741 try hc.compress(fbs.reader());
742 try hc.finish();
743 try testing.expectEqualSlices(u8, &expected, al.items);
744}
lib/std/compress/flate/huffman_decoder.zig deleted-302
...@@ -1,302 +0,0 @@
1const std = @import("std");
2const testing = std.testing;
3
4pub const Symbol = packed struct {
5 pub const Kind = enum(u2) {
6 literal,
7 end_of_block,
8 match,
9 };
10
11 symbol: u8 = 0, // symbol from alphabet
12 code_bits: u4 = 0, // number of bits in code 0-15
13 kind: Kind = .literal,
14
15 code: u16 = 0, // huffman code of the symbol
16 next: u16 = 0, // pointer to the next symbol in linked list
17 // it is safe to use 0 as null pointer, when sorted 0 has shortest code and fits into lookup
18
19 // Sorting less than function.
20 pub fn asc(_: void, a: Symbol, b: Symbol) bool {
21 if (a.code_bits == b.code_bits) {
22 if (a.kind == b.kind) {
23 return a.symbol < b.symbol;
24 }
25 return @intFromEnum(a.kind) < @intFromEnum(b.kind);
26 }
27 return a.code_bits < b.code_bits;
28 }
29};
30
31pub const LiteralDecoder = HuffmanDecoder(286, 15, 9);
32pub const DistanceDecoder = HuffmanDecoder(30, 15, 9);
33pub const CodegenDecoder = HuffmanDecoder(19, 7, 7);
34
35pub const Error = error{
36 InvalidCode,
37 OversubscribedHuffmanTree,
38 IncompleteHuffmanTree,
39 MissingEndOfBlockCode,
40};
41
42/// Creates huffman tree codes from list of code lengths (in `build`).
43///
44/// `find` then finds symbol for code bits. Code can be any length between 1 and
45/// 15 bits. When calling `find` we don't know how many bits will be used to
46/// find symbol. When symbol is returned it has code_bits field which defines
47/// how much we should advance in bit stream.
48///
49/// Lookup table is used to map 15 bit int to symbol. Same symbol is written
50/// many times in this table; 32K places for 286 (at most) symbols.
51/// Small lookup table is optimization for faster search.
52/// It is variation of the algorithm explained in [zlib](https://github.com/madler/zlib/blob/643e17b7498d12ab8d15565662880579692f769d/doc/algorithm.txt#L92)
53/// with difference that we here use statically allocated arrays.
54///
55fn HuffmanDecoder(
56 comptime alphabet_size: u16,
57 comptime max_code_bits: u4,
58 comptime lookup_bits: u4,
59) type {
60 const lookup_shift = max_code_bits - lookup_bits;
61
62 return struct {
63 // all symbols in alaphabet, sorted by code_len, symbol
64 symbols: [alphabet_size]Symbol = undefined,
65 // lookup table code -> symbol
66 lookup: [1 << lookup_bits]Symbol = undefined,
67
68 const Self = @This();
69
70 /// Generates symbols and lookup tables from list of code lens for each symbol.
71 pub fn generate(self: *Self, lens: []const u4) !void {
72 try checkCompleteness(lens);
73
74 // init alphabet with code_bits
75 for (self.symbols, 0..) |_, i| {
76 const cb: u4 = if (i < lens.len) lens[i] else 0;
77 self.symbols[i] = if (i < 256)
78 .{ .kind = .literal, .symbol = @intCast(i), .code_bits = cb }
79 else if (i == 256)
80 .{ .kind = .end_of_block, .symbol = 0xff, .code_bits = cb }
81 else
82 .{ .kind = .match, .symbol = @intCast(i - 257), .code_bits = cb };
83 }
84 std.sort.heap(Symbol, &self.symbols, {}, Symbol.asc);
85
86 // reset lookup table
87 for (0..self.lookup.len) |i| {
88 self.lookup[i] = .{};
89 }
90
91 // assign code to symbols
92 // reference: https://youtu.be/9_YEGLe33NA?list=PLU4IQLU9e_OrY8oASHx0u3IXAL9TOdidm&t=2639
93 var code: u16 = 0;
94 var idx: u16 = 0;
95 for (&self.symbols, 0..) |*sym, pos| {
96 if (sym.code_bits == 0) continue; // skip unused
97 sym.code = code;
98
99 const next_code = code + (@as(u16, 1) << (max_code_bits - sym.code_bits));
100 const next_idx = next_code >> lookup_shift;
101
102 if (next_idx > self.lookup.len or idx >= self.lookup.len) break;
103 if (sym.code_bits <= lookup_bits) {
104 // fill small lookup table
105 for (idx..next_idx) |j|
106 self.lookup[j] = sym.*;
107 } else {
108 // insert into linked table starting at root
109 const root = &self.lookup[idx];
110 const root_next = root.next;
111 root.next = @intCast(pos);
112 sym.next = root_next;
113 }
114
115 idx = next_idx;
116 code = next_code;
117 }
118 }
119
120 /// Given the list of code lengths check that it represents a canonical
121 /// Huffman code for n symbols.
122 ///
123 /// Reference: https://github.com/madler/zlib/blob/5c42a230b7b468dff011f444161c0145b5efae59/contrib/puff/puff.c#L340
124 fn checkCompleteness(lens: []const u4) !void {
125 if (alphabet_size == 286)
126 if (lens[256] == 0) return error.MissingEndOfBlockCode;
127
128 var count = [_]u16{0} ** (@as(usize, max_code_bits) + 1);
129 var max: usize = 0;
130 for (lens) |n| {
131 if (n == 0) continue;
132 if (n > max) max = n;
133 count[n] += 1;
134 }
135 if (max == 0) // empty tree
136 return;
137
138 // check for an over-subscribed or incomplete set of lengths
139 var left: usize = 1; // one possible code of zero length
140 for (1..count.len) |len| {
141 left <<= 1; // one more bit, double codes left
142 if (count[len] > left)
143 return error.OversubscribedHuffmanTree;
144 left -= count[len]; // deduct count from possible codes
145 }
146 if (left > 0) { // left > 0 means incomplete
147 // incomplete code ok only for single length 1 code
148 if (max_code_bits > 7 and max == count[0] + count[1]) return;
149 return error.IncompleteHuffmanTree;
150 }
151 }
152
153 /// Finds symbol for lookup table code.
154 pub fn find(self: *Self, code: u16) !Symbol {
155 // try to find in lookup table
156 const idx = code >> lookup_shift;
157 const sym = self.lookup[idx];
158 if (sym.code_bits != 0) return sym;
159 // if not use linked list of symbols with same prefix
160 return self.findLinked(code, sym.next);
161 }
162
163 inline fn findLinked(self: *Self, code: u16, start: u16) !Symbol {
164 var pos = start;
165 while (pos > 0) {
166 const sym = self.symbols[pos];
167 const shift = max_code_bits - sym.code_bits;
168 // compare code_bits number of upper bits
169 if ((code ^ sym.code) >> shift == 0) return sym;
170 pos = sym.next;
171 }
172 return error.InvalidCode;
173 }
174 };
175}
176
177test "init/find" {
178 // example data from: https://youtu.be/SJPvNi4HrWQ?t=8423
179 const code_lens = [_]u4{ 4, 3, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 2 };
180 var h: CodegenDecoder = .{};
181 try h.generate(&code_lens);
182
183 const expected = [_]struct {
184 sym: Symbol,
185 code: u16,
186 }{
187 .{
188 .code = 0b00_00000,
189 .sym = .{ .symbol = 3, .code_bits = 2 },
190 },
191 .{
192 .code = 0b01_00000,
193 .sym = .{ .symbol = 18, .code_bits = 2 },
194 },
195 .{
196 .code = 0b100_0000,
197 .sym = .{ .symbol = 1, .code_bits = 3 },
198 },
199 .{
200 .code = 0b101_0000,
201 .sym = .{ .symbol = 4, .code_bits = 3 },
202 },
203 .{
204 .code = 0b110_0000,
205 .sym = .{ .symbol = 17, .code_bits = 3 },
206 },
207 .{
208 .code = 0b1110_000,
209 .sym = .{ .symbol = 0, .code_bits = 4 },
210 },
211 .{
212 .code = 0b1111_000,
213 .sym = .{ .symbol = 16, .code_bits = 4 },
214 },
215 };
216
217 // unused symbols
218 for (0..12) |i| {
219 try testing.expectEqual(0, h.symbols[i].code_bits);
220 }
221 // used, from index 12
222 for (expected, 12..) |e, i| {
223 try testing.expectEqual(e.sym.symbol, h.symbols[i].symbol);
224 try testing.expectEqual(e.sym.code_bits, h.symbols[i].code_bits);
225 const sym_from_code = try h.find(e.code);
226 try testing.expectEqual(e.sym.symbol, sym_from_code.symbol);
227 }
228
229 // All possible codes for each symbol.
230 // Lookup table has 126 elements, to cover all possible 7 bit codes.
231 for (0b0000_000..0b0100_000) |c| // 0..32 (32)
232 try testing.expectEqual(3, (try h.find(@intCast(c))).symbol);
233
234 for (0b0100_000..0b1000_000) |c| // 32..64 (32)
235 try testing.expectEqual(18, (try h.find(@intCast(c))).symbol);
236
237 for (0b1000_000..0b1010_000) |c| // 64..80 (16)
238 try testing.expectEqual(1, (try h.find(@intCast(c))).symbol);
239
240 for (0b1010_000..0b1100_000) |c| // 80..96 (16)
241 try testing.expectEqual(4, (try h.find(@intCast(c))).symbol);
242
243 for (0b1100_000..0b1110_000) |c| // 96..112 (16)
244 try testing.expectEqual(17, (try h.find(@intCast(c))).symbol);
245
246 for (0b1110_000..0b1111_000) |c| // 112..120 (8)
247 try testing.expectEqual(0, (try h.find(@intCast(c))).symbol);
248
249 for (0b1111_000..0b1_0000_000) |c| // 120...128 (8)
250 try testing.expectEqual(16, (try h.find(@intCast(c))).symbol);
251}
252
253test "encode/decode literals" {
254 const LiteralEncoder = @import("huffman_encoder.zig").LiteralEncoder;
255
256 for (1..286) |j| { // for all different number of codes
257 var enc: LiteralEncoder = .{};
258 // create frequencies
259 var freq = [_]u16{0} ** 286;
260 freq[256] = 1; // ensure we have end of block code
261 for (&freq, 1..) |*f, i| {
262 if (i % j == 0)
263 f.* = @intCast(i);
264 }
265
266 // encoder from frequencies
267 enc.generate(&freq, 15);
268
269 // get code_lens from encoder
270 var code_lens = [_]u4{0} ** 286;
271 for (code_lens, 0..) |_, i| {
272 code_lens[i] = @intCast(enc.codes[i].len);
273 }
274 // generate decoder from code lens
275 var dec: LiteralDecoder = .{};
276 try dec.generate(&code_lens);
277
278 // expect decoder code to match original encoder code
279 for (dec.symbols) |s| {
280 if (s.code_bits == 0) continue;
281 const c_code: u16 = @bitReverse(@as(u15, @intCast(s.code)));
282 const symbol: u16 = switch (s.kind) {
283 .literal => s.symbol,
284 .end_of_block => 256,
285 .match => @as(u16, s.symbol) + 257,
286 };
287
288 const c = enc.codes[symbol];
289 try testing.expect(c.code == c_code);
290 }
291
292 // find each symbol by code
293 for (enc.codes) |c| {
294 if (c.len == 0) continue;
295
296 const s_code: u15 = @bitReverse(@as(u15, @intCast(c.code)));
297 const s = try dec.find(s_code);
298 try testing.expect(s.code == s_code);
299 try testing.expect(s.code_bits == c.len);
300 }
301 }
302}
lib/std/compress/flate/huffman_encoder.zig deleted-536
...@@ -1,536 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const math = std.math;
4const mem = std.mem;
5const sort = std.sort;
6const testing = std.testing;
7
8const consts = @import("consts.zig").huffman;
9
10const LiteralNode = struct {
11 literal: u16,
12 freq: u16,
13};
14
15// Describes the state of the constructed tree for a given depth.
16const LevelInfo = struct {
17 // Our level. for better printing
18 level: u32,
19
20 // The frequency of the last node at this level
21 last_freq: u32,
22
23 // The frequency of the next character to add to this level
24 next_char_freq: u32,
25
26 // The frequency of the next pair (from level below) to add to this level.
27 // Only valid if the "needed" value of the next lower level is 0.
28 next_pair_freq: u32,
29
30 // The number of chains remaining to generate for this level before moving
31 // up to the next level
32 needed: u32,
33};
34
35// hcode is a huffman code with a bit code and bit length.
36pub const HuffCode = struct {
37 code: u16 = 0,
38 len: u16 = 0,
39
40 // set sets the code and length of an hcode.
41 fn set(self: *HuffCode, code: u16, length: u16) void {
42 self.len = length;
43 self.code = code;
44 }
45};
46
47pub fn HuffmanEncoder(comptime size: usize) type {
48 return struct {
49 codes: [size]HuffCode = undefined,
50 // Reusable buffer with the longest possible frequency table.
51 freq_cache: [consts.max_num_frequencies + 1]LiteralNode = undefined,
52 bit_count: [17]u32 = undefined,
53 lns: []LiteralNode = undefined, // sorted by literal, stored to avoid repeated allocation in generate
54 lfs: []LiteralNode = undefined, // sorted by frequency, stored to avoid repeated allocation in generate
55
56 const Self = @This();
57
58 // Update this Huffman Code object to be the minimum code for the specified frequency count.
59 //
60 // freq An array of frequencies, in which frequency[i] gives the frequency of literal i.
61 // max_bits The maximum number of bits to use for any literal.
62 pub fn generate(self: *Self, freq: []u16, max_bits: u32) void {
63 var list = self.freq_cache[0 .. freq.len + 1];
64 // Number of non-zero literals
65 var count: u32 = 0;
66 // Set list to be the set of all non-zero literals and their frequencies
67 for (freq, 0..) |f, i| {
68 if (f != 0) {
69 list[count] = LiteralNode{ .literal = @as(u16, @intCast(i)), .freq = f };
70 count += 1;
71 } else {
72 list[count] = LiteralNode{ .literal = 0x00, .freq = 0 };
73 self.codes[i].len = 0;
74 }
75 }
76 list[freq.len] = LiteralNode{ .literal = 0x00, .freq = 0 };
77
78 list = list[0..count];
79 if (count <= 2) {
80 // Handle the small cases here, because they are awkward for the general case code. With
81 // two or fewer literals, everything has bit length 1.
82 for (list, 0..) |node, i| {
83 // "list" is in order of increasing literal value.
84 self.codes[node.literal].set(@as(u16, @intCast(i)), 1);
85 }
86 return;
87 }
88 self.lfs = list;
89 mem.sort(LiteralNode, self.lfs, {}, byFreq);
90
91 // Get the number of literals for each bit count
92 const bit_count = self.bitCounts(list, max_bits);
93 // And do the assignment
94 self.assignEncodingAndSize(bit_count, list);
95 }
96
97 pub fn bitLength(self: *Self, freq: []u16) u32 {
98 var total: u32 = 0;
99 for (freq, 0..) |f, i| {
100 if (f != 0) {
101 total += @as(u32, @intCast(f)) * @as(u32, @intCast(self.codes[i].len));
102 }
103 }
104 return total;
105 }
106
107 // Return the number of literals assigned to each bit size in the Huffman encoding
108 //
109 // This method is only called when list.len >= 3
110 // The cases of 0, 1, and 2 literals are handled by special case code.
111 //
112 // list: An array of the literals with non-zero frequencies
113 // and their associated frequencies. The array is in order of increasing
114 // frequency, and has as its last element a special element with frequency
115 // std.math.maxInt(i32)
116 //
117 // max_bits: The maximum number of bits that should be used to encode any literal.
118 // Must be less than 16.
119 //
120 // Returns an integer array in which array[i] indicates the number of literals
121 // that should be encoded in i bits.
122 fn bitCounts(self: *Self, list: []LiteralNode, max_bits_to_use: usize) []u32 {
123 var max_bits = max_bits_to_use;
124 const n = list.len;
125 const max_bits_limit = 16;
126
127 assert(max_bits < max_bits_limit);
128
129 // The tree can't have greater depth than n - 1, no matter what. This
130 // saves a little bit of work in some small cases
131 max_bits = @min(max_bits, n - 1);
132
133 // Create information about each of the levels.
134 // A bogus "Level 0" whose sole purpose is so that
135 // level1.prev.needed == 0. This makes level1.next_pair_freq
136 // be a legitimate value that never gets chosen.
137 var levels: [max_bits_limit]LevelInfo = mem.zeroes([max_bits_limit]LevelInfo);
138 // leaf_counts[i] counts the number of literals at the left
139 // of ancestors of the rightmost node at level i.
140 // leaf_counts[i][j] is the number of literals at the left
141 // of the level j ancestor.
142 var leaf_counts: [max_bits_limit][max_bits_limit]u32 = mem.zeroes([max_bits_limit][max_bits_limit]u32);
143
144 {
145 var level = @as(u32, 1);
146 while (level <= max_bits) : (level += 1) {
147 // For every level, the first two items are the first two characters.
148 // We initialize the levels as if we had already figured this out.
149 levels[level] = LevelInfo{
150 .level = level,
151 .last_freq = list[1].freq,
152 .next_char_freq = list[2].freq,
153 .next_pair_freq = list[0].freq + list[1].freq,
154 .needed = 0,
155 };
156 leaf_counts[level][level] = 2;
157 if (level == 1) {
158 levels[level].next_pair_freq = math.maxInt(i32);
159 }
160 }
161 }
162
163 // We need a total of 2*n - 2 items at top level and have already generated 2.
164 levels[max_bits].needed = 2 * @as(u32, @intCast(n)) - 4;
165
166 {
167 var level = max_bits;
168 while (true) {
169 var l = &levels[level];
170 if (l.next_pair_freq == math.maxInt(i32) and l.next_char_freq == math.maxInt(i32)) {
171 // We've run out of both leaves and pairs.
172 // End all calculations for this level.
173 // To make sure we never come back to this level or any lower level,
174 // set next_pair_freq impossibly large.
175 l.needed = 0;
176 levels[level + 1].next_pair_freq = math.maxInt(i32);
177 level += 1;
178 continue;
179 }
180
181 const prev_freq = l.last_freq;
182 if (l.next_char_freq < l.next_pair_freq) {
183 // The next item on this row is a leaf node.
184 const next = leaf_counts[level][level] + 1;
185 l.last_freq = l.next_char_freq;
186 // Lower leaf_counts are the same of the previous node.
187 leaf_counts[level][level] = next;
188 if (next >= list.len) {
189 l.next_char_freq = maxNode().freq;
190 } else {
191 l.next_char_freq = list[next].freq;
192 }
193 } else {
194 // The next item on this row is a pair from the previous row.
195 // next_pair_freq isn't valid until we generate two
196 // more values in the level below
197 l.last_freq = l.next_pair_freq;
198 // Take leaf counts from the lower level, except counts[level] remains the same.
199 @memcpy(leaf_counts[level][0..level], leaf_counts[level - 1][0..level]);
200 levels[l.level - 1].needed = 2;
201 }
202
203 l.needed -= 1;
204 if (l.needed == 0) {
205 // We've done everything we need to do for this level.
206 // Continue calculating one level up. Fill in next_pair_freq
207 // of that level with the sum of the two nodes we've just calculated on
208 // this level.
209 if (l.level == max_bits) {
210 // All done!
211 break;
212 }
213 levels[l.level + 1].next_pair_freq = prev_freq + l.last_freq;
214 level += 1;
215 } else {
216 // If we stole from below, move down temporarily to replenish it.
217 while (levels[level - 1].needed > 0) {
218 level -= 1;
219 if (level == 0) {
220 break;
221 }
222 }
223 }
224 }
225 }
226
227 // Somethings is wrong if at the end, the top level is null or hasn't used
228 // all of the leaves.
229 assert(leaf_counts[max_bits][max_bits] == n);
230
231 var bit_count = self.bit_count[0 .. max_bits + 1];
232 var bits: u32 = 1;
233 const counts = &leaf_counts[max_bits];
234 {
235 var level = max_bits;
236 while (level > 0) : (level -= 1) {
237 // counts[level] gives the number of literals requiring at least "bits"
238 // bits to encode.
239 bit_count[bits] = counts[level] - counts[level - 1];
240 bits += 1;
241 if (level == 0) {
242 break;
243 }
244 }
245 }
246 return bit_count;
247 }
248
249 // Look at the leaves and assign them a bit count and an encoding as specified
250 // in RFC 1951 3.2.2
251 fn assignEncodingAndSize(self: *Self, bit_count: []u32, list_arg: []LiteralNode) void {
252 var code = @as(u16, 0);
253 var list = list_arg;
254
255 for (bit_count, 0..) |bits, n| {
256 code <<= 1;
257 if (n == 0 or bits == 0) {
258 continue;
259 }
260 // The literals list[list.len-bits] .. list[list.len-bits]
261 // are encoded using "bits" bits, and get the values
262 // code, code + 1, .... The code values are
263 // assigned in literal order (not frequency order).
264 const chunk = list[list.len - @as(u32, @intCast(bits)) ..];
265
266 self.lns = chunk;
267 mem.sort(LiteralNode, self.lns, {}, byLiteral);
268
269 for (chunk) |node| {
270 self.codes[node.literal] = HuffCode{
271 .code = bitReverse(u16, code, @as(u5, @intCast(n))),
272 .len = @as(u16, @intCast(n)),
273 };
274 code += 1;
275 }
276 list = list[0 .. list.len - @as(u32, @intCast(bits))];
277 }
278 }
279 };
280}
281
282fn maxNode() LiteralNode {
283 return LiteralNode{
284 .literal = math.maxInt(u16),
285 .freq = math.maxInt(u16),
286 };
287}
288
289pub fn huffmanEncoder(comptime size: u32) HuffmanEncoder(size) {
290 return .{};
291}
292
293pub const LiteralEncoder = HuffmanEncoder(consts.max_num_frequencies);
294pub const DistanceEncoder = HuffmanEncoder(consts.distance_code_count);
295pub const CodegenEncoder = HuffmanEncoder(19);
296
297// Generates a HuffmanCode corresponding to the fixed literal table
298pub fn fixedLiteralEncoder() LiteralEncoder {
299 var h: LiteralEncoder = undefined;
300 var ch: u16 = 0;
301
302 while (ch < consts.max_num_frequencies) : (ch += 1) {
303 var bits: u16 = undefined;
304 var size: u16 = undefined;
305 switch (ch) {
306 0...143 => {
307 // size 8, 000110000 .. 10111111
308 bits = ch + 48;
309 size = 8;
310 },
311 144...255 => {
312 // size 9, 110010000 .. 111111111
313 bits = ch + 400 - 144;
314 size = 9;
315 },
316 256...279 => {
317 // size 7, 0000000 .. 0010111
318 bits = ch - 256;
319 size = 7;
320 },
321 else => {
322 // size 8, 11000000 .. 11000111
323 bits = ch + 192 - 280;
324 size = 8;
325 },
326 }
327 h.codes[ch] = HuffCode{ .code = bitReverse(u16, bits, @as(u5, @intCast(size))), .len = size };
328 }
329 return h;
330}
331
332pub fn fixedDistanceEncoder() DistanceEncoder {
333 var h: DistanceEncoder = undefined;
334 for (h.codes, 0..) |_, ch| {
335 h.codes[ch] = HuffCode{ .code = bitReverse(u16, @as(u16, @intCast(ch)), 5), .len = 5 };
336 }
337 return h;
338}
339
340pub fn huffmanDistanceEncoder() DistanceEncoder {
341 var distance_freq = [1]u16{0} ** consts.distance_code_count;
342 distance_freq[0] = 1;
343 // huff_distance is a static distance encoder used for huffman only encoding.
344 // It can be reused since we will not be encoding distance values.
345 var h: DistanceEncoder = .{};
346 h.generate(distance_freq[0..], 15);
347 return h;
348}
349
350fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) bool {
351 _ = context;
352 return a.literal < b.literal;
353}
354
355fn byFreq(context: void, a: LiteralNode, b: LiteralNode) bool {
356 _ = context;
357 if (a.freq == b.freq) {
358 return a.literal < b.literal;
359 }
360 return a.freq < b.freq;
361}
362
363test "generate a Huffman code from an array of frequencies" {
364 var freqs: [19]u16 = [_]u16{
365 8, // 0
366 1, // 1
367 1, // 2
368 2, // 3
369 5, // 4
370 10, // 5
371 9, // 6
372 1, // 7
373 0, // 8
374 0, // 9
375 0, // 10
376 0, // 11
377 0, // 12
378 0, // 13
379 0, // 14
380 0, // 15
381 1, // 16
382 3, // 17
383 5, // 18
384 };
385
386 var enc = huffmanEncoder(19);
387 enc.generate(freqs[0..], 7);
388
389 try testing.expectEqual(@as(u32, 141), enc.bitLength(freqs[0..]));
390
391 try testing.expectEqual(@as(usize, 3), enc.codes[0].len);
392 try testing.expectEqual(@as(usize, 6), enc.codes[1].len);
393 try testing.expectEqual(@as(usize, 6), enc.codes[2].len);
394 try testing.expectEqual(@as(usize, 5), enc.codes[3].len);
395 try testing.expectEqual(@as(usize, 3), enc.codes[4].len);
396 try testing.expectEqual(@as(usize, 2), enc.codes[5].len);
397 try testing.expectEqual(@as(usize, 2), enc.codes[6].len);
398 try testing.expectEqual(@as(usize, 6), enc.codes[7].len);
399 try testing.expectEqual(@as(usize, 0), enc.codes[8].len);
400 try testing.expectEqual(@as(usize, 0), enc.codes[9].len);
401 try testing.expectEqual(@as(usize, 0), enc.codes[10].len);
402 try testing.expectEqual(@as(usize, 0), enc.codes[11].len);
403 try testing.expectEqual(@as(usize, 0), enc.codes[12].len);
404 try testing.expectEqual(@as(usize, 0), enc.codes[13].len);
405 try testing.expectEqual(@as(usize, 0), enc.codes[14].len);
406 try testing.expectEqual(@as(usize, 0), enc.codes[15].len);
407 try testing.expectEqual(@as(usize, 6), enc.codes[16].len);
408 try testing.expectEqual(@as(usize, 5), enc.codes[17].len);
409 try testing.expectEqual(@as(usize, 3), enc.codes[18].len);
410
411 try testing.expectEqual(@as(u16, 0x0), enc.codes[5].code);
412 try testing.expectEqual(@as(u16, 0x2), enc.codes[6].code);
413 try testing.expectEqual(@as(u16, 0x1), enc.codes[0].code);
414 try testing.expectEqual(@as(u16, 0x5), enc.codes[4].code);
415 try testing.expectEqual(@as(u16, 0x3), enc.codes[18].code);
416 try testing.expectEqual(@as(u16, 0x7), enc.codes[3].code);
417 try testing.expectEqual(@as(u16, 0x17), enc.codes[17].code);
418 try testing.expectEqual(@as(u16, 0x0f), enc.codes[1].code);
419 try testing.expectEqual(@as(u16, 0x2f), enc.codes[2].code);
420 try testing.expectEqual(@as(u16, 0x1f), enc.codes[7].code);
421 try testing.expectEqual(@as(u16, 0x3f), enc.codes[16].code);
422}
423
424test "generate a Huffman code for the fixed literal table specific to Deflate" {
425 const enc = fixedLiteralEncoder();
426 for (enc.codes) |c| {
427 switch (c.len) {
428 7 => {
429 const v = @bitReverse(@as(u7, @intCast(c.code)));
430 try testing.expect(v <= 0b0010111);
431 },
432 8 => {
433 const v = @bitReverse(@as(u8, @intCast(c.code)));
434 try testing.expect((v >= 0b000110000 and v <= 0b10111111) or
435 (v >= 0b11000000 and v <= 11000111));
436 },
437 9 => {
438 const v = @bitReverse(@as(u9, @intCast(c.code)));
439 try testing.expect(v >= 0b110010000 and v <= 0b111111111);
440 },
441 else => unreachable,
442 }
443 }
444}
445
446test "generate a Huffman code for the 30 possible relative distances (LZ77 distances) of Deflate" {
447 const enc = fixedDistanceEncoder();
448 for (enc.codes) |c| {
449 const v = @bitReverse(@as(u5, @intCast(c.code)));
450 try testing.expect(v <= 29);
451 try testing.expect(c.len == 5);
452 }
453}
454
455// Reverse bit-by-bit a N-bit code.
456fn bitReverse(comptime T: type, value: T, n: usize) T {
457 const r = @bitReverse(value);
458 return r >> @as(math.Log2Int(T), @intCast(@typeInfo(T).int.bits - n));
459}
460
461test bitReverse {
462 const ReverseBitsTest = struct {
463 in: u16,
464 bit_count: u5,
465 out: u16,
466 };
467
468 const reverse_bits_tests = [_]ReverseBitsTest{
469 .{ .in = 1, .bit_count = 1, .out = 1 },
470 .{ .in = 1, .bit_count = 2, .out = 2 },
471 .{ .in = 1, .bit_count = 3, .out = 4 },
472 .{ .in = 1, .bit_count = 4, .out = 8 },
473 .{ .in = 1, .bit_count = 5, .out = 16 },
474 .{ .in = 17, .bit_count = 5, .out = 17 },
475 .{ .in = 257, .bit_count = 9, .out = 257 },
476 .{ .in = 29, .bit_count = 5, .out = 23 },
477 };
478
479 for (reverse_bits_tests) |h| {
480 const v = bitReverse(u16, h.in, h.bit_count);
481 try std.testing.expectEqual(h.out, v);
482 }
483}
484
485test "fixedLiteralEncoder codes" {
486 var al = std.ArrayList(u8).init(testing.allocator);
487 defer al.deinit();
488 var bw = std.io.bitWriter(.little, al.writer());
489
490 const f = fixedLiteralEncoder();
491 for (f.codes) |c| {
492 try bw.writeBits(c.code, c.len);
493 }
494 try testing.expectEqualSlices(u8, &fixed_codes, al.items);
495}
496
497pub const fixed_codes = [_]u8{
498 0b00001100, 0b10001100, 0b01001100, 0b11001100, 0b00101100, 0b10101100, 0b01101100, 0b11101100,
499 0b00011100, 0b10011100, 0b01011100, 0b11011100, 0b00111100, 0b10111100, 0b01111100, 0b11111100,
500 0b00000010, 0b10000010, 0b01000010, 0b11000010, 0b00100010, 0b10100010, 0b01100010, 0b11100010,
501 0b00010010, 0b10010010, 0b01010010, 0b11010010, 0b00110010, 0b10110010, 0b01110010, 0b11110010,
502 0b00001010, 0b10001010, 0b01001010, 0b11001010, 0b00101010, 0b10101010, 0b01101010, 0b11101010,
503 0b00011010, 0b10011010, 0b01011010, 0b11011010, 0b00111010, 0b10111010, 0b01111010, 0b11111010,
504 0b00000110, 0b10000110, 0b01000110, 0b11000110, 0b00100110, 0b10100110, 0b01100110, 0b11100110,
505 0b00010110, 0b10010110, 0b01010110, 0b11010110, 0b00110110, 0b10110110, 0b01110110, 0b11110110,
506 0b00001110, 0b10001110, 0b01001110, 0b11001110, 0b00101110, 0b10101110, 0b01101110, 0b11101110,
507 0b00011110, 0b10011110, 0b01011110, 0b11011110, 0b00111110, 0b10111110, 0b01111110, 0b11111110,
508 0b00000001, 0b10000001, 0b01000001, 0b11000001, 0b00100001, 0b10100001, 0b01100001, 0b11100001,
509 0b00010001, 0b10010001, 0b01010001, 0b11010001, 0b00110001, 0b10110001, 0b01110001, 0b11110001,
510 0b00001001, 0b10001001, 0b01001001, 0b11001001, 0b00101001, 0b10101001, 0b01101001, 0b11101001,
511 0b00011001, 0b10011001, 0b01011001, 0b11011001, 0b00111001, 0b10111001, 0b01111001, 0b11111001,
512 0b00000101, 0b10000101, 0b01000101, 0b11000101, 0b00100101, 0b10100101, 0b01100101, 0b11100101,
513 0b00010101, 0b10010101, 0b01010101, 0b11010101, 0b00110101, 0b10110101, 0b01110101, 0b11110101,
514 0b00001101, 0b10001101, 0b01001101, 0b11001101, 0b00101101, 0b10101101, 0b01101101, 0b11101101,
515 0b00011101, 0b10011101, 0b01011101, 0b11011101, 0b00111101, 0b10111101, 0b01111101, 0b11111101,
516 0b00010011, 0b00100110, 0b01001110, 0b10011010, 0b00111100, 0b01100101, 0b11101010, 0b10110100,
517 0b11101001, 0b00110011, 0b01100110, 0b11001110, 0b10011010, 0b00111101, 0b01100111, 0b11101110,
518 0b10111100, 0b11111001, 0b00001011, 0b00010110, 0b00101110, 0b01011010, 0b10111100, 0b01100100,
519 0b11101001, 0b10110010, 0b11100101, 0b00101011, 0b01010110, 0b10101110, 0b01011010, 0b10111101,
520 0b01100110, 0b11101101, 0b10111010, 0b11110101, 0b00011011, 0b00110110, 0b01101110, 0b11011010,
521 0b10111100, 0b01100101, 0b11101011, 0b10110110, 0b11101101, 0b00111011, 0b01110110, 0b11101110,
522 0b11011010, 0b10111101, 0b01100111, 0b11101111, 0b10111110, 0b11111101, 0b00000111, 0b00001110,
523 0b00011110, 0b00111010, 0b01111100, 0b11100100, 0b11101000, 0b10110001, 0b11100011, 0b00100111,
524 0b01001110, 0b10011110, 0b00111010, 0b01111101, 0b11100110, 0b11101100, 0b10111001, 0b11110011,
525 0b00010111, 0b00101110, 0b01011110, 0b10111010, 0b01111100, 0b11100101, 0b11101010, 0b10110101,
526 0b11101011, 0b00110111, 0b01101110, 0b11011110, 0b10111010, 0b01111101, 0b11100111, 0b11101110,
527 0b10111101, 0b11111011, 0b00001111, 0b00011110, 0b00111110, 0b01111010, 0b11111100, 0b11100100,
528 0b11101001, 0b10110011, 0b11100111, 0b00101111, 0b01011110, 0b10111110, 0b01111010, 0b11111101,
529 0b11100110, 0b11101101, 0b10111011, 0b11110111, 0b00011111, 0b00111110, 0b01111110, 0b11111010,
530 0b11111100, 0b11100101, 0b11101011, 0b10110111, 0b11101111, 0b00111111, 0b01111110, 0b11111110,
531 0b11111010, 0b11111101, 0b11100111, 0b11101111, 0b10111111, 0b11111111, 0b00000000, 0b00100000,
532 0b00001000, 0b00001100, 0b10000001, 0b11000010, 0b11100000, 0b00001000, 0b00100100, 0b00001010,
533 0b10001101, 0b11000001, 0b11100010, 0b11110000, 0b00000100, 0b00100010, 0b10001001, 0b01001100,
534 0b10100001, 0b11010010, 0b11101000, 0b00000011, 0b10000011, 0b01000011, 0b11000011, 0b00100011,
535 0b10100011,
536};
lib/std/compress/flate/inflate.zig deleted-570
...@@ -1,570 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4
5const hfd = @import("huffman_decoder.zig");
6const BitReader = @import("bit_reader.zig").BitReader;
7const CircularBuffer = @import("CircularBuffer.zig");
8const Container = @import("container.zig").Container;
9const Token = @import("Token.zig");
10const codegen_order = @import("consts.zig").huffman.codegen_order;
11
12/// Decompresses deflate bit stream `reader` and writes uncompressed data to the
13/// `writer` stream.
14pub fn decompress(comptime container: Container, reader: anytype, writer: anytype) !void {
15 var d = decompressor(container, reader);
16 try d.decompress(writer);
17}
18
19/// Inflate decompressor for the reader type.
20pub fn decompressor(comptime container: Container, reader: anytype) Decompressor(container, @TypeOf(reader)) {
21 return Decompressor(container, @TypeOf(reader)).init(reader);
22}
23
24pub fn Decompressor(comptime container: Container, comptime ReaderType: type) type {
25 // zlib has 4 bytes footer, lookahead of 4 bytes ensures that we will not overshoot.
26 // gzip has 8 bytes footer so we will not overshoot even with 8 bytes of lookahead.
27 // For raw deflate there is always possibility of overshot so we use 8 bytes lookahead.
28 const lookahead: type = if (container == .zlib) u32 else u64;
29 return Inflate(container, lookahead, ReaderType);
30}
31
32/// Inflate decompresses deflate bit stream. Reads compressed data from reader
33/// provided in init. Decompressed data are stored in internal hist buffer and
34/// can be accesses iterable `next` or reader interface.
35///
36/// Container defines header/footer wrapper around deflate bit stream. Can be
37/// gzip or zlib.
38///
39/// Deflate bit stream consists of multiple blocks. Block can be one of three types:
40/// * stored, non compressed, max 64k in size
41/// * fixed, huffman codes are predefined
42/// * dynamic, huffman code tables are encoded at the block start
43///
44/// `step` function runs decoder until internal `hist` buffer is full. Client
45/// than needs to read that data in order to proceed with decoding.
46///
47/// Allocates 74.5K of internal buffers, most important are:
48/// * 64K for history (CircularBuffer)
49/// * ~10K huffman decoders (Literal and DistanceDecoder)
50///
51pub fn Inflate(comptime container: Container, comptime LookaheadType: type, comptime ReaderType: type) type {
52 assert(LookaheadType == u32 or LookaheadType == u64);
53 const BitReaderType = BitReader(LookaheadType, ReaderType);
54
55 return struct {
56 //const BitReaderType = BitReader(ReaderType);
57 const F = BitReaderType.flag;
58
59 bits: BitReaderType = .{},
60 hist: CircularBuffer = .{},
61 // Hashes, produces checkusm, of uncompressed data for gzip/zlib footer.
62 hasher: container.Hasher() = .{},
63
64 // dynamic block huffman code decoders
65 lit_dec: hfd.LiteralDecoder = .{}, // literals
66 dst_dec: hfd.DistanceDecoder = .{}, // distances
67
68 // current read state
69 bfinal: u1 = 0,
70 block_type: u2 = 0b11,
71 state: ReadState = .protocol_header,
72
73 const ReadState = enum {
74 protocol_header,
75 block_header,
76 block,
77 protocol_footer,
78 end,
79 };
80
81 const Self = @This();
82
83 pub const Error = BitReaderType.Error || Container.Error || hfd.Error || error{
84 InvalidCode,
85 InvalidMatch,
86 InvalidBlockType,
87 WrongStoredBlockNlen,
88 InvalidDynamicBlockHeader,
89 };
90
91 pub fn init(rt: ReaderType) Self {
92 return .{ .bits = BitReaderType.init(rt) };
93 }
94
95 fn blockHeader(self: *Self) !void {
96 self.bfinal = try self.bits.read(u1);
97 self.block_type = try self.bits.read(u2);
98 }
99
100 fn storedBlock(self: *Self) !bool {
101 self.bits.alignToByte(); // skip padding until byte boundary
102 // everything after this is byte aligned in stored block
103 var len = try self.bits.read(u16);
104 const nlen = try self.bits.read(u16);
105 if (len != ~nlen) return error.WrongStoredBlockNlen;
106
107 while (len > 0) {
108 const buf = self.hist.getWritable(len);
109 try self.bits.readAll(buf);
110 len -= @intCast(buf.len);
111 }
112 return true;
113 }
114
115 fn fixedBlock(self: *Self) !bool {
116 while (!self.hist.full()) {
117 const code = try self.bits.readFixedCode();
118 switch (code) {
119 0...255 => self.hist.write(@intCast(code)),
120 256 => return true, // end of block
121 257...285 => try self.fixedDistanceCode(@intCast(code - 257)),
122 else => return error.InvalidCode,
123 }
124 }
125 return false;
126 }
127
128 // Handles fixed block non literal (length) code.
129 // Length code is followed by 5 bits of distance code.
130 fn fixedDistanceCode(self: *Self, code: u8) !void {
131 try self.bits.fill(5 + 5 + 13);
132 const length = try self.decodeLength(code);
133 const distance = try self.decodeDistance(try self.bits.readF(u5, F.buffered | F.reverse));
134 try self.hist.writeMatch(length, distance);
135 }
136
137 inline fn decodeLength(self: *Self, code: u8) !u16 {
138 if (code > 28) return error.InvalidCode;
139 const ml = Token.matchLength(code);
140 return if (ml.extra_bits == 0) // 0 - 5 extra bits
141 ml.base
142 else
143 ml.base + try self.bits.readN(ml.extra_bits, F.buffered);
144 }
145
146 fn decodeDistance(self: *Self, code: u8) !u16 {
147 if (code > 29) return error.InvalidCode;
148 const md = Token.matchDistance(code);
149 return if (md.extra_bits == 0) // 0 - 13 extra bits
150 md.base
151 else
152 md.base + try self.bits.readN(md.extra_bits, F.buffered);
153 }
154
155 fn dynamicBlockHeader(self: *Self) !void {
156 const hlit: u16 = @as(u16, try self.bits.read(u5)) + 257; // number of ll code entries present - 257
157 const hdist: u16 = @as(u16, try self.bits.read(u5)) + 1; // number of distance code entries - 1
158 const hclen: u8 = @as(u8, try self.bits.read(u4)) + 4; // hclen + 4 code lengths are encoded
159
160 if (hlit > 286 or hdist > 30)
161 return error.InvalidDynamicBlockHeader;
162
163 // lengths for code lengths
164 var cl_lens = [_]u4{0} ** 19;
165 for (0..hclen) |i| {
166 cl_lens[codegen_order[i]] = try self.bits.read(u3);
167 }
168 var cl_dec: hfd.CodegenDecoder = .{};
169 try cl_dec.generate(&cl_lens);
170
171 // decoded code lengths
172 var dec_lens = [_]u4{0} ** (286 + 30);
173 var pos: usize = 0;
174 while (pos < hlit + hdist) {
175 const sym = try cl_dec.find(try self.bits.peekF(u7, F.reverse));
176 try self.bits.shift(sym.code_bits);
177 pos += try self.dynamicCodeLength(sym.symbol, &dec_lens, pos);
178 }
179 if (pos > hlit + hdist) {
180 return error.InvalidDynamicBlockHeader;
181 }
182
183 // literal code lengths to literal decoder
184 try self.lit_dec.generate(dec_lens[0..hlit]);
185
186 // distance code lengths to distance decoder
187 try self.dst_dec.generate(dec_lens[hlit .. hlit + hdist]);
188 }
189
190 // Decode code length symbol to code length. Writes decoded length into
191 // lens slice starting at position pos. Returns number of positions
192 // advanced.
193 fn dynamicCodeLength(self: *Self, code: u16, lens: []u4, pos: usize) !usize {
194 if (pos >= lens.len)
195 return error.InvalidDynamicBlockHeader;
196
197 switch (code) {
198 0...15 => {
199 // Represent code lengths of 0 - 15
200 lens[pos] = @intCast(code);
201 return 1;
202 },
203 16 => {
204 // Copy the previous code length 3 - 6 times.
205 // The next 2 bits indicate repeat length
206 const n: u8 = @as(u8, try self.bits.read(u2)) + 3;
207 if (pos == 0 or pos + n > lens.len)
208 return error.InvalidDynamicBlockHeader;
209 for (0..n) |i| {
210 lens[pos + i] = lens[pos + i - 1];
211 }
212 return n;
213 },
214 // Repeat a code length of 0 for 3 - 10 times. (3 bits of length)
215 17 => return @as(u8, try self.bits.read(u3)) + 3,
216 // Repeat a code length of 0 for 11 - 138 times (7 bits of length)
217 18 => return @as(u8, try self.bits.read(u7)) + 11,
218 else => return error.InvalidDynamicBlockHeader,
219 }
220 }
221
222 // In larger archives most blocks are usually dynamic, so decompression
223 // performance depends on this function.
224 fn dynamicBlock(self: *Self) !bool {
225 // Hot path loop!
226 while (!self.hist.full()) {
227 try self.bits.fill(15); // optimization so other bit reads can be buffered (avoiding one `if` in hot path)
228 const sym = try self.decodeSymbol(&self.lit_dec);
229
230 switch (sym.kind) {
231 .literal => self.hist.write(sym.symbol),
232 .match => { // Decode match backreference <length, distance>
233 // fill so we can use buffered reads
234 if (LookaheadType == u32)
235 try self.bits.fill(5 + 15)
236 else
237 try self.bits.fill(5 + 15 + 13);
238 const length = try self.decodeLength(sym.symbol);
239 const dsm = try self.decodeSymbol(&self.dst_dec);
240 if (LookaheadType == u32) try self.bits.fill(13);
241 const distance = try self.decodeDistance(dsm.symbol);
242 try self.hist.writeMatch(length, distance);
243 },
244 .end_of_block => return true,
245 }
246 }
247 return false;
248 }
249
250 // Peek 15 bits from bits reader (maximum code len is 15 bits). Use
251 // decoder to find symbol for that code. We then know how many bits is
252 // used. Shift bit reader for that much bits, those bits are used. And
253 // return symbol.
254 fn decodeSymbol(self: *Self, decoder: anytype) !hfd.Symbol {
255 const sym = try decoder.find(try self.bits.peekF(u15, F.buffered | F.reverse));
256 try self.bits.shift(sym.code_bits);
257 return sym;
258 }
259
260 fn step(self: *Self) !void {
261 switch (self.state) {
262 .protocol_header => {
263 try container.parseHeader(&self.bits);
264 self.state = .block_header;
265 },
266 .block_header => {
267 try self.blockHeader();
268 self.state = .block;
269 if (self.block_type == 2) try self.dynamicBlockHeader();
270 },
271 .block => {
272 const done = switch (self.block_type) {
273 0 => try self.storedBlock(),
274 1 => try self.fixedBlock(),
275 2 => try self.dynamicBlock(),
276 else => return error.InvalidBlockType,
277 };
278 if (done) {
279 self.state = if (self.bfinal == 1) .protocol_footer else .block_header;
280 }
281 },
282 .protocol_footer => {
283 self.bits.alignToByte();
284 try container.parseFooter(&self.hasher, &self.bits);
285 self.state = .end;
286 },
287 .end => {},
288 }
289 }
290
291 /// Replaces the inner reader with new reader.
292 pub fn setReader(self: *Self, new_reader: ReaderType) void {
293 self.bits.forward_reader = new_reader;
294 if (self.state == .end or self.state == .protocol_footer) {
295 self.state = .protocol_header;
296 }
297 }
298
299 // Reads all compressed data from the internal reader and outputs plain
300 // (uncompressed) data to the provided writer.
301 pub fn decompress(self: *Self, writer: anytype) !void {
302 while (try self.next()) |buf| {
303 try writer.writeAll(buf);
304 }
305 }
306
307 /// Returns the number of bytes that have been read from the internal
308 /// reader but not yet consumed by the decompressor.
309 pub fn unreadBytes(self: Self) usize {
310 // There can be no error here: the denominator is not zero, and
311 // overflow is not possible since the type is unsigned.
312 return std.math.divCeil(usize, self.bits.nbits, 8) catch unreachable;
313 }
314
315 // Iterator interface
316
317 /// Can be used in iterator like loop without memcpy to another buffer:
318 /// while (try inflate.next()) |buf| { ... }
319 pub fn next(self: *Self) Error!?[]const u8 {
320 const out = try self.get(0);
321 if (out.len == 0) return null;
322 return out;
323 }
324
325 /// Returns decompressed data from internal sliding window buffer.
326 /// Returned buffer can be any length between 0 and `limit` bytes. 0
327 /// returned bytes means end of stream reached. With limit=0 returns as
328 /// much data it can. It newer will be more than 65536 bytes, which is
329 /// size of internal buffer.
330 pub fn get(self: *Self, limit: usize) Error![]const u8 {
331 while (true) {
332 const out = self.hist.readAtMost(limit);
333 if (out.len > 0) {
334 self.hasher.update(out);
335 return out;
336 }
337 if (self.state == .end) return out;
338 try self.step();
339 }
340 }
341
342 // Reader interface
343
344 pub const Reader = std.io.GenericReader(*Self, Error, read);
345
346 /// Returns the number of bytes read. It may be less than buffer.len.
347 /// If the number of bytes read is 0, it means end of stream.
348 /// End of stream is not an error condition.
349 pub fn read(self: *Self, buffer: []u8) Error!usize {
350 if (buffer.len == 0) return 0;
351 const out = try self.get(buffer.len);
352 @memcpy(buffer[0..out.len], out);
353 return out.len;
354 }
355
356 pub fn reader(self: *Self) Reader {
357 return .{ .context = self };
358 }
359 };
360}
361
362test "decompress" {
363 const cases = [_]struct {
364 in: []const u8,
365 out: []const u8,
366 }{
367 // non compressed block (type 0)
368 .{
369 .in = &[_]u8{
370 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
371 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
372 },
373 .out = "Hello world\n",
374 },
375 // fixed code block (type 1)
376 .{
377 .in = &[_]u8{
378 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
379 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
380 },
381 .out = "Hello world\n",
382 },
383 // dynamic block (type 2)
384 .{
385 .in = &[_]u8{
386 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
387 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
388 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
389 },
390 .out = "ABCDEABCD ABCDEABCD",
391 },
392 };
393 for (cases) |c| {
394 var fb = std.io.fixedBufferStream(c.in);
395 var al = std.ArrayList(u8).init(testing.allocator);
396 defer al.deinit();
397
398 try decompress(.raw, fb.reader(), al.writer());
399 try testing.expectEqualStrings(c.out, al.items);
400 }
401}
402
403test "gzip decompress" {
404 const cases = [_]struct {
405 in: []const u8,
406 out: []const u8,
407 }{
408 // non compressed block (type 0)
409 .{
410 .in = &[_]u8{
411 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // gzip header (10 bytes)
412 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
413 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
414 0xd5, 0xe0, 0x39, 0xb7, // gzip footer: checksum
415 0x0c, 0x00, 0x00, 0x00, // gzip footer: size
416 },
417 .out = "Hello world\n",
418 },
419 // fixed code block (type 1)
420 .{
421 .in = &[_]u8{
422 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, // gzip header (10 bytes)
423 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
424 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
425 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00, // gzip footer (chksum, len)
426 },
427 .out = "Hello world\n",
428 },
429 // dynamic block (type 2)
430 .{
431 .in = &[_]u8{
432 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // gzip header (10 bytes)
433 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
434 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
435 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
436 0x17, 0x1c, 0x39, 0xb4, 0x13, 0x00, 0x00, 0x00, // gzip footer (chksum, len)
437 },
438 .out = "ABCDEABCD ABCDEABCD",
439 },
440 // gzip header with name
441 .{
442 .in = &[_]u8{
443 0x1f, 0x8b, 0x08, 0x08, 0xe5, 0x70, 0xb1, 0x65, 0x00, 0x03, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e,
444 0x74, 0x78, 0x74, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1,
445 0x02, 0x00, 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00,
446 },
447 .out = "Hello world\n",
448 },
449 };
450 for (cases) |c| {
451 var fb = std.io.fixedBufferStream(c.in);
452 var al = std.ArrayList(u8).init(testing.allocator);
453 defer al.deinit();
454
455 try decompress(.gzip, fb.reader(), al.writer());
456 try testing.expectEqualStrings(c.out, al.items);
457 }
458}
459
460test "zlib decompress" {
461 const cases = [_]struct {
462 in: []const u8,
463 out: []const u8,
464 }{
465 // non compressed block (type 0)
466 .{
467 .in = &[_]u8{
468 0x78, 0b10_0_11100, // zlib header (2 bytes)
469 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
470 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
471 0x1c, 0xf2, 0x04, 0x47, // zlib footer: checksum
472 },
473 .out = "Hello world\n",
474 },
475 };
476 for (cases) |c| {
477 var fb = std.io.fixedBufferStream(c.in);
478 var al = std.ArrayList(u8).init(testing.allocator);
479 defer al.deinit();
480
481 try decompress(.zlib, fb.reader(), al.writer());
482 try testing.expectEqualStrings(c.out, al.items);
483 }
484}
485
486test "fuzzing tests" {
487 const cases = [_]struct {
488 input: []const u8,
489 out: []const u8 = "",
490 err: ?anyerror = null,
491 }{
492 .{ .input = "deflate-stream", .out = @embedFile("testdata/fuzz/deflate-stream.expect") }, // 0
493 .{ .input = "empty-distance-alphabet01" },
494 .{ .input = "empty-distance-alphabet02" },
495 .{ .input = "end-of-stream", .err = error.EndOfStream },
496 .{ .input = "invalid-distance", .err = error.InvalidMatch },
497 .{ .input = "invalid-tree01", .err = error.IncompleteHuffmanTree }, // 5
498 .{ .input = "invalid-tree02", .err = error.IncompleteHuffmanTree },
499 .{ .input = "invalid-tree03", .err = error.IncompleteHuffmanTree },
500 .{ .input = "lengths-overflow", .err = error.InvalidDynamicBlockHeader },
501 .{ .input = "out-of-codes", .err = error.InvalidCode },
502 .{ .input = "puff01", .err = error.WrongStoredBlockNlen }, // 10
503 .{ .input = "puff02", .err = error.EndOfStream },
504 .{ .input = "puff03", .out = &[_]u8{0xa} },
505 .{ .input = "puff04", .err = error.InvalidCode },
506 .{ .input = "puff05", .err = error.EndOfStream },
507 .{ .input = "puff06", .err = error.EndOfStream },
508 .{ .input = "puff08", .err = error.InvalidCode },
509 .{ .input = "puff09", .out = "P" },
510 .{ .input = "puff10", .err = error.InvalidCode },
511 .{ .input = "puff11", .err = error.InvalidMatch },
512 .{ .input = "puff12", .err = error.InvalidDynamicBlockHeader }, // 20
513 .{ .input = "puff13", .err = error.IncompleteHuffmanTree },
514 .{ .input = "puff14", .err = error.EndOfStream },
515 .{ .input = "puff15", .err = error.IncompleteHuffmanTree },
516 .{ .input = "puff16", .err = error.InvalidDynamicBlockHeader },
517 .{ .input = "puff17", .err = error.MissingEndOfBlockCode }, // 25
518 .{ .input = "fuzz1", .err = error.InvalidDynamicBlockHeader },
519 .{ .input = "fuzz2", .err = error.InvalidDynamicBlockHeader },
520 .{ .input = "fuzz3", .err = error.InvalidMatch },
521 .{ .input = "fuzz4", .err = error.OversubscribedHuffmanTree },
522 .{ .input = "puff18", .err = error.OversubscribedHuffmanTree }, // 30
523 .{ .input = "puff19", .err = error.OversubscribedHuffmanTree },
524 .{ .input = "puff20", .err = error.OversubscribedHuffmanTree },
525 .{ .input = "puff21", .err = error.OversubscribedHuffmanTree },
526 .{ .input = "puff22", .err = error.OversubscribedHuffmanTree },
527 .{ .input = "puff23", .err = error.OversubscribedHuffmanTree }, // 35
528 .{ .input = "puff24", .err = error.IncompleteHuffmanTree },
529 .{ .input = "puff25", .err = error.OversubscribedHuffmanTree },
530 .{ .input = "puff26", .err = error.InvalidDynamicBlockHeader },
531 .{ .input = "puff27", .err = error.InvalidDynamicBlockHeader },
532 };
533
534 inline for (cases, 0..) |c, case_no| {
535 var in = std.io.fixedBufferStream(@embedFile("testdata/fuzz/" ++ c.input ++ ".input"));
536 var out = std.ArrayList(u8).init(testing.allocator);
537 defer out.deinit();
538 errdefer std.debug.print("test case failed {}\n", .{case_no});
539
540 if (c.err) |expected_err| {
541 try testing.expectError(expected_err, decompress(.raw, in.reader(), out.writer()));
542 } else {
543 try decompress(.raw, in.reader(), out.writer());
544 try testing.expectEqualStrings(c.out, out.items);
545 }
546 }
547}
548
549test "bug 18966" {
550 const input = @embedFile("testdata/fuzz/bug_18966.input");
551 const expect = @embedFile("testdata/fuzz/bug_18966.expect");
552
553 var in = std.io.fixedBufferStream(input);
554 var out = std.ArrayList(u8).init(testing.allocator);
555 defer out.deinit();
556
557 try decompress(.gzip, in.reader(), out.writer());
558 try testing.expectEqualStrings(expect, out.items);
559}
560
561test "bug 19895" {
562 const input = &[_]u8{
563 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
564 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
565 };
566 var in = std.io.fixedBufferStream(input);
567 var decomp = decompressor(.raw, in.reader());
568 var buf: [0]u8 = undefined;
569 try testing.expectEqual(0, try decomp.read(&buf));
570}
lib/std/compress/gzip.zig deleted-66
...@@ -1,66 +0,0 @@
1const deflate = @import("flate/deflate.zig");
2const inflate = @import("flate/inflate.zig");
3
4/// Decompress compressed data from reader and write plain data to the writer.
5pub fn decompress(reader: anytype, writer: anytype) !void {
6 try inflate.decompress(.gzip, reader, writer);
7}
8
9/// Decompressor type
10pub fn Decompressor(comptime ReaderType: type) type {
11 return inflate.Decompressor(.gzip, ReaderType);
12}
13
14/// Create Decompressor which will read compressed data from reader.
15pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) {
16 return inflate.decompressor(.gzip, reader);
17}
18
19/// Compression level, trades between speed and compression size.
20pub const Options = deflate.Options;
21
22/// Compress plain data from reader and write compressed data to the writer.
23pub fn compress(reader: anytype, writer: anytype, options: Options) !void {
24 try deflate.compress(.gzip, reader, writer, options);
25}
26
27/// Compressor type
28pub fn Compressor(comptime WriterType: type) type {
29 return deflate.Compressor(.gzip, WriterType);
30}
31
32/// Create Compressor which outputs compressed data to the writer.
33pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) {
34 return try deflate.compressor(.gzip, writer, options);
35}
36
37/// Huffman only compression. Without Lempel-Ziv match searching. Faster
38/// compression, less memory requirements but bigger compressed sizes.
39pub const huffman = struct {
40 pub fn compress(reader: anytype, writer: anytype) !void {
41 try deflate.huffman.compress(.gzip, reader, writer);
42 }
43
44 pub fn Compressor(comptime WriterType: type) type {
45 return deflate.huffman.Compressor(.gzip, WriterType);
46 }
47
48 pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) {
49 return deflate.huffman.compressor(.gzip, writer);
50 }
51};
52
53// No compression store only. Compressed size is slightly bigger than plain.
54pub const store = struct {
55 pub fn compress(reader: anytype, writer: anytype) !void {
56 try deflate.store.compress(.gzip, reader, writer);
57 }
58
59 pub fn Compressor(comptime WriterType: type) type {
60 return deflate.store.Compressor(.gzip, WriterType);
61 }
62
63 pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) {
64 return deflate.store.compressor(.gzip, writer);
65 }
66};
lib/std/compress/zlib.zig deleted-101
...@@ -1,101 +0,0 @@
1const deflate = @import("flate/deflate.zig");
2const inflate = @import("flate/inflate.zig");
3
4/// Decompress compressed data from reader and write plain data to the writer.
5pub fn decompress(reader: anytype, writer: anytype) !void {
6 try inflate.decompress(.zlib, reader, writer);
7}
8
9/// Decompressor type
10pub fn Decompressor(comptime ReaderType: type) type {
11 return inflate.Decompressor(.zlib, ReaderType);
12}
13
14/// Create Decompressor which will read compressed data from reader.
15pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) {
16 return inflate.decompressor(.zlib, reader);
17}
18
19/// Compression level, trades between speed and compression size.
20pub const Options = deflate.Options;
21
22/// Compress plain data from reader and write compressed data to the writer.
23pub fn compress(reader: anytype, writer: anytype, options: Options) !void {
24 try deflate.compress(.zlib, reader, writer, options);
25}
26
27/// Compressor type
28pub fn Compressor(comptime WriterType: type) type {
29 return deflate.Compressor(.zlib, WriterType);
30}
31
32/// Create Compressor which outputs compressed data to the writer.
33pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) {
34 return try deflate.compressor(.zlib, writer, options);
35}
36
37/// Huffman only compression. Without Lempel-Ziv match searching. Faster
38/// compression, less memory requirements but bigger compressed sizes.
39pub const huffman = struct {
40 pub fn compress(reader: anytype, writer: anytype) !void {
41 try deflate.huffman.compress(.zlib, reader, writer);
42 }
43
44 pub fn Compressor(comptime WriterType: type) type {
45 return deflate.huffman.Compressor(.zlib, WriterType);
46 }
47
48 pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) {
49 return deflate.huffman.compressor(.zlib, writer);
50 }
51};
52
53// No compression store only. Compressed size is slightly bigger than plain.
54pub const store = struct {
55 pub fn compress(reader: anytype, writer: anytype) !void {
56 try deflate.store.compress(.zlib, reader, writer);
57 }
58
59 pub fn Compressor(comptime WriterType: type) type {
60 return deflate.store.Compressor(.zlib, WriterType);
61 }
62
63 pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) {
64 return deflate.store.compressor(.zlib, writer);
65 }
66};
67
68test "should not overshoot" {
69 const std = @import("std");
70
71 // Compressed zlib data with extra 4 bytes at the end.
72 const data = [_]u8{
73 0x78, 0x9c, 0x73, 0xce, 0x2f, 0xa8, 0x2c, 0xca, 0x4c, 0xcf, 0x28, 0x51, 0x08, 0xcf, 0xcc, 0xc9,
74 0x49, 0xcd, 0x55, 0x28, 0x4b, 0xcc, 0x53, 0x08, 0x4e, 0xce, 0x48, 0xcc, 0xcc, 0xd6, 0x51, 0x08,
75 0xce, 0xcc, 0x4b, 0x4f, 0x2c, 0xc8, 0x2f, 0x4a, 0x55, 0x30, 0xb4, 0xb4, 0x34, 0xd5, 0xb5, 0x34,
76 0x03, 0x00, 0x8b, 0x61, 0x0f, 0xa4, 0x52, 0x5a, 0x94, 0x12,
77 };
78
79 var stream = std.io.fixedBufferStream(data[0..]);
80 const reader = stream.reader();
81
82 var dcp = decompressor(reader);
83 var out: [128]u8 = undefined;
84
85 // Decompress
86 var n = try dcp.reader().readAll(out[0..]);
87
88 // Expected decompressed data
89 try std.testing.expectEqual(46, n);
90 try std.testing.expectEqualStrings("Copyright Willem van Schaik, Singapore 1995-96", out[0..n]);
91
92 // Decompressor don't overshoot underlying reader.
93 // It is leaving it at the end of compressed data chunk.
94 try std.testing.expectEqual(data.len - 4, stream.getPos());
95 try std.testing.expectEqual(0, dcp.unreadBytes());
96
97 // 4 bytes after compressed chunk are available in reader.
98 n = try reader.readAll(out[0..]);
99 try std.testing.expectEqual(n, 4);
100 try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]);
101}
lib/std/debug/Dwarf.zig+5-9
...@@ -2235,18 +2235,14 @@ pub const ElfModule = struct {...@@ -2235,18 +2235,14 @@ pub const ElfModule = struct {
22352235
2236 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);2236 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
2237 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {2237 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
2238 var section_stream = std.io.fixedBufferStream(section_bytes);2238 var section_reader: std.Io.Reader = .fixed(section_bytes);
2239 const section_reader = section_stream.reader();2239 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
2240 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
2241 if (chdr.ch_type != .ZLIB) continue;2240 if (chdr.ch_type != .ZLIB) continue;
22422241
2243 var zlib_stream = std.compress.zlib.decompressor(section_reader);2242 var zlib_stream: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{});
22442243 const decompressed_section = zlib_stream.reader.allocRemaining(gpa, .unlimited) catch continue;
2245 const decompressed_section = try gpa.alloc(u8, chdr.ch_size);
2246 errdefer gpa.free(decompressed_section);2244 errdefer gpa.free(decompressed_section);
22472245 assert(chdr.ch_size == decompressed_section.len);
2248 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
2249 assert(read == decompressed_section.len);
22502246
2251 break :blk .{2247 break :blk .{
2252 .data = decompressed_section,2248 .data = decompressed_section,
lib/std/http/Client.zig+2-7
...@@ -405,13 +405,8 @@ pub const RequestTransfer = union(enum) {...@@ -405,13 +405,8 @@ pub const RequestTransfer = union(enum) {
405405
406/// The decompressor for response messages.406/// The decompressor for response messages.
407pub const Compression = union(enum) {407pub const Compression = union(enum) {
408 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Request.TransferReader);408 deflate: std.compress.flate.Decompress,
409 pub const GzipDecompressor = std.compress.gzip.Decompressor(Request.TransferReader);409 gzip: std.compress.flate.Decompress,
410 // https://github.com/ziglang/zig/issues/18937
411 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Request.TransferReader, .{});
412
413 deflate: DeflateDecompressor,
414 gzip: GzipDecompressor,
415 // https://github.com/ziglang/zig/issues/18937410 // https://github.com/ziglang/zig/issues/18937
416 //zstd: ZstdDecompressor,411 //zstd: ZstdDecompressor,
417 none: void,412 none: void,
lib/std/http/Server.zig+2-2
...@@ -130,8 +130,8 @@ pub const Request = struct {...@@ -130,8 +130,8 @@ pub const Request = struct {
130 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);130 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
131 pub const GzipDecompressor = std.compress.gzip.Decompressor(std.io.AnyReader);131 pub const GzipDecompressor = std.compress.gzip.Decompressor(std.io.AnyReader);
132132
133 deflate: DeflateDecompressor,133 deflate: std.compress.flate.Decompress,
134 gzip: GzipDecompressor,134 gzip: std.compress.flate.Decompress,
135 zstd: std.compress.zstd.Decompress,135 zstd: std.compress.zstd.Decompress,
136 none: void,136 none: void,
137 };137 };
lib/std/zip.zig+386-531
...@@ -5,11 +5,10 @@...@@ -5,11 +5,10 @@
55
6const builtin = @import("builtin");6const builtin = @import("builtin");
7const std = @import("std");7const std = @import("std");
8const testing = std.testing;8const File = std.fs.File;
99const is_le = builtin.target.cpu.arch.endian() == .little;
10pub const testutil = @import("zip/test.zig");10const Writer = std.io.Writer;
11const File = testutil.File;11const Reader = std.io.Reader;
12const FileStore = testutil.FileStore;
1312
14pub const CompressionMethod = enum(u16) {13pub const CompressionMethod = enum(u16) {
15 store = 0,14 store = 0,
...@@ -95,102 +94,116 @@ pub const EndRecord = extern struct {...@@ -95,102 +94,116 @@ pub const EndRecord = extern struct {
95 central_directory_size: u32 align(1),94 central_directory_size: u32 align(1),
96 central_directory_offset: u32 align(1),95 central_directory_offset: u32 align(1),
97 comment_len: u16 align(1),96 comment_len: u16 align(1),
97
98 pub fn need_zip64(self: EndRecord) bool {98 pub fn need_zip64(self: EndRecord) bool {
99 return isMaxInt(self.record_count_disk) or99 return isMaxInt(self.record_count_disk) or
100 isMaxInt(self.record_count_total) or100 isMaxInt(self.record_count_total) or
101 isMaxInt(self.central_directory_size) or101 isMaxInt(self.central_directory_size) or
102 isMaxInt(self.central_directory_offset);102 isMaxInt(self.central_directory_offset);
103 }103 }
104};
105104
106/// Find and return the end record for the given seekable zip stream.105 pub const FindBufferError = error{ ZipNoEndRecord, ZipTruncated };
107/// Note that `seekable_stream` must be an instance of `std.io.SeekableStream` and
108/// its context must also have a `.reader()` method that returns an instance of
109/// `std.io.GenericReader`.
110pub fn findEndRecord(seekable_stream: anytype, stream_len: u64) !EndRecord {
111 var buf: [@sizeOf(EndRecord) + std.math.maxInt(u16)]u8 = undefined;
112 const record_len_max = @min(stream_len, buf.len);
113 var loaded_len: u32 = 0;
114
115 var comment_len: u16 = 0;
116 while (true) {
117 const record_len: u32 = @as(u32, comment_len) + @sizeOf(EndRecord);
118 if (record_len > record_len_max)
119 return error.ZipNoEndRecord;
120
121 if (record_len > loaded_len) {
122 const new_loaded_len = @min(loaded_len + 300, record_len_max);
123 const read_len = new_loaded_len - loaded_len;
124
125 try seekable_stream.seekTo(stream_len - @as(u64, new_loaded_len));
126 const read_buf: []u8 = buf[buf.len - new_loaded_len ..][0..read_len];
127 const len = try (if (@TypeOf(seekable_stream.context) == std.fs.File) seekable_stream.context.deprecatedReader() else seekable_stream.context.reader()).readAll(read_buf);
128 if (len != read_len)
129 return error.ZipTruncated;
130 loaded_len = new_loaded_len;
131 }
132106
133 const record_bytes = buf[buf.len - record_len ..][0..@sizeOf(EndRecord)];107 /// TODO audit this logic
134 if (std.mem.eql(u8, record_bytes[0..4], &end_record_sig) and108 pub fn findBuffer(buffer: []const u8) FindBufferError!EndRecord {
135 std.mem.readInt(u16, record_bytes[20..22], .little) == comment_len)109 const pos = std.mem.lastIndexOf(u8, buffer, &end_record_sig) orelse return error.ZipNoEndRecord;
136 {110 if (pos + @sizeOf(EndRecord) > buffer.len) return error.EndOfStream;
137 const record: *align(1) EndRecord = @ptrCast(record_bytes.ptr);111 const record_ptr: *EndRecord = @ptrCast(buffer[pos..][0..@sizeOf(EndRecord)]);
138 if (builtin.target.cpu.arch.endian() != .little) {112 var record = record_ptr.*;
139 std.mem.byteSwapAllFields(@TypeOf(record.*), record);113 if (!is_le) std.mem.byteSwapAllFields(EndRecord, &record);
114 return record;
115 }
116
117 pub const FindFileError = File.GetEndPosError || File.SeekError || File.ReadError || error{
118 ZipNoEndRecord,
119 EndOfStream,
120 };
121
122 pub fn findFile(fr: *File.Reader) FindFileError!EndRecord {
123 const end_pos = try fr.getSize();
124
125 var buf: [@sizeOf(EndRecord) + std.math.maxInt(u16)]u8 = undefined;
126 const record_len_max = @min(end_pos, buf.len);
127 var loaded_len: u32 = 0;
128 var comment_len: u16 = 0;
129 while (true) {
130 const record_len: u32 = @as(u32, comment_len) + @sizeOf(EndRecord);
131 if (record_len > record_len_max)
132 return error.ZipNoEndRecord;
133
134 if (record_len > loaded_len) {
135 const new_loaded_len = @min(loaded_len + 300, record_len_max);
136 const read_len = new_loaded_len - loaded_len;
137
138 try fr.seekTo(end_pos - @as(u64, new_loaded_len));
139 const read_buf: []u8 = buf[buf.len - new_loaded_len ..][0..read_len];
140 var br = fr.interface().unbuffered();
141 br.readSlice(read_buf) catch |err| switch (err) {
142 error.ReadFailed => return fr.err.?,
143 error.EndOfStream => return error.EndOfStream,
144 };
145 loaded_len = new_loaded_len;
146 }
147
148 const record_bytes = buf[buf.len - record_len ..][0..@sizeOf(EndRecord)];
149 if (std.mem.eql(u8, record_bytes[0..4], &end_record_sig) and
150 std.mem.readInt(u16, record_bytes[20..22], .little) == comment_len)
151 {
152 const record: *align(1) EndRecord = @ptrCast(record_bytes.ptr);
153 if (!is_le) std.mem.byteSwapAllFields(EndRecord, record);
154 return record.*;
140 }155 }
141 return record.*;156
157 if (comment_len == std.math.maxInt(u16))
158 return error.ZipNoEndRecord;
159 comment_len += 1;
142 }160 }
161 }
162};
143163
144 if (comment_len == std.math.maxInt(u16))164pub const Decompress = struct {
145 return error.ZipNoEndRecord;165 interface: Reader,
146 comment_len += 1;166 state: union {
167 inflate: std.compress.flate.Decompress,
168 store: *Reader,
169 },
170
171 pub fn init(reader: *Reader, method: CompressionMethod, buffer: []u8) Reader {
172 return switch (method) {
173 .store => .{
174 .state = .{ .store = reader },
175 .interface = .{
176 .context = undefined,
177 .vtable = &.{ .stream = streamStore },
178 .buffer = buffer,
179 .end = 0,
180 .seek = 0,
181 },
182 },
183 .deflate => .{
184 .state = .{ .inflate = .init(reader, .raw) },
185 .interface = .{
186 .context = undefined,
187 .vtable = &.{ .stream = streamDeflate },
188 .buffer = buffer,
189 .end = 0,
190 .seek = 0,
191 },
192 },
193 else => unreachable,
194 };
147 }195 }
148}
149196
150/// Decompresses the given data from `reader` into `writer`. Stops early if more197 fn streamStore(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
151/// than `uncompressed_size` bytes are processed and verifies that exactly that198 const d: *Decompress = @fieldParentPtr("interface", r);
152/// number of bytes are decompressed. Returns the CRC-32 of the uncompressed data.199 return d.store.read(w, limit);
153/// `writer` can be anything with a `writeAll(self: *Self, chunk: []const u8) anyerror!void` method.
154pub fn decompress(
155 method: CompressionMethod,
156 uncompressed_size: u64,
157 reader: anytype,
158 writer: anytype,
159) !u32 {
160 var hash = std.hash.Crc32.init();
161
162 var total_uncompressed: u64 = 0;
163 switch (method) {
164 .store => {
165 var buf: [4096]u8 = undefined;
166 while (true) {
167 const len = try reader.read(&buf);
168 if (len == 0) break;
169 try writer.writeAll(buf[0..len]);
170 hash.update(buf[0..len]);
171 total_uncompressed += @intCast(len);
172 }
173 },
174 .deflate => {
175 var br = std.io.bufferedReader(reader);
176 var decompressor = std.compress.flate.decompressor(br.reader());
177 while (try decompressor.next()) |chunk| {
178 try writer.writeAll(chunk);
179 hash.update(chunk);
180 total_uncompressed += @intCast(chunk.len);
181 if (total_uncompressed > uncompressed_size)
182 return error.ZipUncompressSizeTooSmall;
183 }
184 if (br.end != br.start)
185 return error.ZipDeflateTruncated;
186 },
187 _ => return error.UnsupportedCompressionMethod,
188 }200 }
189 if (total_uncompressed != uncompressed_size)
190 return error.ZipUncompressSizeMismatch;
191201
192 return hash.final();202 fn streamDeflate(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
193}203 const d: *Decompress = @fieldParentPtr("interface", r);
204 return std.compress.flate.Decompress.read(&d.inflate, w, limit);
205 }
206};
194207
195fn isBadFilename(filename: []const u8) bool {208fn isBadFilename(filename: []const u8) bool {
196 if (filename.len == 0 or filename[0] == '/')209 if (filename.len == 0 or filename[0] == '/')
...@@ -253,319 +266,332 @@ fn readZip64FileExtents(comptime T: type, header: T, extents: *FileExtents, data...@@ -253,319 +266,332 @@ fn readZip64FileExtents(comptime T: type, header: T, extents: *FileExtents, data
253 }266 }
254}267}
255268
256pub fn Iterator(comptime SeekableStream: type) type {269pub const Iterator = struct {
257 return struct {270 input: *File.Reader,
258 stream: SeekableStream,
259271
260 cd_record_count: u64,272 cd_record_count: u64,
261 cd_zip_offset: u64,273 cd_zip_offset: u64,
262 cd_size: u64,274 cd_size: u64,
263275
264 cd_record_index: u64 = 0,276 cd_record_index: u64 = 0,
265 cd_record_offset: u64 = 0,277 cd_record_offset: u64 = 0,
266278
267 const Self = @This();279 pub fn init(input: *File.Reader) !Iterator {
280 const end_record = try EndRecord.findFile(input);
268281
269 pub fn init(stream: SeekableStream) !Self {282 if (!isMaxInt(end_record.record_count_disk) and end_record.record_count_disk > end_record.record_count_total)
270 const stream_len = try stream.getEndPos();283 return error.ZipDiskRecordCountTooLarge;
271284
272 const end_record = try findEndRecord(stream, stream_len);285 if (end_record.disk_number != 0 or end_record.central_directory_disk_number != 0)
273286 return error.ZipMultiDiskUnsupported;
274 if (!isMaxInt(end_record.record_count_disk) and end_record.record_count_disk > end_record.record_count_total)
275 return error.ZipDiskRecordCountTooLarge;
276
277 if (end_record.disk_number != 0 or end_record.central_directory_disk_number != 0)
278 return error.ZipMultiDiskUnsupported;
279287
280 {288 {
281 const counts_valid = !isMaxInt(end_record.record_count_disk) and !isMaxInt(end_record.record_count_total);289 const counts_valid = !isMaxInt(end_record.record_count_disk) and !isMaxInt(end_record.record_count_total);
282 if (counts_valid and end_record.record_count_disk != end_record.record_count_total)290 if (counts_valid and end_record.record_count_disk != end_record.record_count_total)
283 return error.ZipMultiDiskUnsupported;
284 }
285
286 var result = Self{
287 .stream = stream,
288 .cd_record_count = end_record.record_count_total,
289 .cd_zip_offset = end_record.central_directory_offset,
290 .cd_size = end_record.central_directory_size,
291 };
292 if (!end_record.need_zip64()) return result;
293
294 const locator_end_offset: u64 = @as(u64, end_record.comment_len) + @sizeOf(EndRecord) + @sizeOf(EndLocator64);
295 if (locator_end_offset > stream_len)
296 return error.ZipTruncated;
297 try stream.seekTo(stream_len - locator_end_offset);
298 const locator = try (if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()).readStructEndian(EndLocator64, .little);
299 if (!std.mem.eql(u8, &locator.signature, &end_locator64_sig))
300 return error.ZipBadLocatorSig;
301 if (locator.zip64_disk_count != 0)
302 return error.ZipUnsupportedZip64DiskCount;
303 if (locator.total_disk_count != 1)
304 return error.ZipMultiDiskUnsupported;291 return error.ZipMultiDiskUnsupported;
292 }
305293
306 try stream.seekTo(locator.record_file_offset);294 var result: Iterator = .{
307295 .input = input,
308 const record64 = try (if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()).readStructEndian(EndRecord64, .little);296 .cd_record_count = end_record.record_count_total,
309297 .cd_zip_offset = end_record.central_directory_offset,
310 if (!std.mem.eql(u8, &record64.signature, &end_record64_sig))298 .cd_size = end_record.central_directory_size,
311 return error.ZipBadEndRecord64Sig;299 };
312300 if (!end_record.need_zip64()) return result;
313 if (record64.end_record_size < @sizeOf(EndRecord64) - 12)
314 return error.ZipEndRecord64SizeTooSmall;
315 if (record64.end_record_size > @sizeOf(EndRecord64) - 12)
316 return error.ZipEndRecord64UnhandledExtraData;
317301
318 if (record64.version_needed_to_extract > 45)302 const locator_end_offset: u64 = @as(u64, end_record.comment_len) + @sizeOf(EndRecord) + @sizeOf(EndLocator64);
319 return error.ZipUnsupportedVersion;303 const stream_len = try input.getSize();
320304
321 {305 if (locator_end_offset > stream_len)
322 const is_multidisk = record64.disk_number != 0 or306 return error.ZipTruncated;
323 record64.central_directory_disk_number != 0 or307 try input.seekTo(stream_len - locator_end_offset);
324 record64.record_count_disk != record64.record_count_total;308 const locator = input.interface.takeStructEndian(EndLocator64, .little) catch |err| switch (err) {
325 if (is_multidisk)309 error.ReadFailed => return input.err.?,
326 return error.ZipMultiDiskUnsupported;310 error.EndOfStream => return error.EndOfStream,
327 }311 };
312 if (!std.mem.eql(u8, &locator.signature, &end_locator64_sig))
313 return error.ZipBadLocatorSig;
314 if (locator.zip64_disk_count != 0)
315 return error.ZipUnsupportedZip64DiskCount;
316 if (locator.total_disk_count != 1)
317 return error.ZipMultiDiskUnsupported;
318
319 try input.seekTo(locator.record_file_offset);
320
321 const record64 = input.interface.takeStructEndian(EndRecord64, .little) catch |err| switch (err) {
322 error.ReadFailed => return input.err.?,
323 error.EndOfStream => return error.EndOfStream,
324 };
328325
329 if (isMaxInt(end_record.record_count_total)) {326 if (!std.mem.eql(u8, &record64.signature, &end_record64_sig))
330 result.cd_record_count = record64.record_count_total;327 return error.ZipBadEndRecord64Sig;
331 } else if (end_record.record_count_total != record64.record_count_total)
332 return error.Zip64RecordCountTotalMismatch;
333328
334 if (isMaxInt(end_record.central_directory_offset)) {329 if (record64.end_record_size < @sizeOf(EndRecord64) - 12)
335 result.cd_zip_offset = record64.central_directory_offset;330 return error.ZipEndRecord64SizeTooSmall;
336 } else if (end_record.central_directory_offset != record64.central_directory_offset)331 if (record64.end_record_size > @sizeOf(EndRecord64) - 12)
337 return error.Zip64CentralDirectoryOffsetMismatch;332 return error.ZipEndRecord64UnhandledExtraData;
338333
339 if (isMaxInt(end_record.central_directory_size)) {334 if (record64.version_needed_to_extract > 45)
340 result.cd_size = record64.central_directory_size;335 return error.ZipUnsupportedVersion;
341 } else if (end_record.central_directory_size != record64.central_directory_size)
342 return error.Zip64CentralDirectorySizeMismatch;
343336
344 return result;337 {
338 const is_multidisk = record64.disk_number != 0 or
339 record64.central_directory_disk_number != 0 or
340 record64.record_count_disk != record64.record_count_total;
341 if (is_multidisk)
342 return error.ZipMultiDiskUnsupported;
345 }343 }
346344
347 pub fn next(self: *Self) !?Entry {345 if (isMaxInt(end_record.record_count_total)) {
348 if (self.cd_record_index == self.cd_record_count) {346 result.cd_record_count = record64.record_count_total;
349 if (self.cd_record_offset != self.cd_size)347 } else if (end_record.record_count_total != record64.record_count_total)
350 return if (self.cd_size > self.cd_record_offset)348 return error.Zip64RecordCountTotalMismatch;
351 error.ZipCdOversized
352 else
353 error.ZipCdUndersized;
354349
355 return null;350 if (isMaxInt(end_record.central_directory_offset)) {
356 }351 result.cd_zip_offset = record64.central_directory_offset;
352 } else if (end_record.central_directory_offset != record64.central_directory_offset)
353 return error.Zip64CentralDirectoryOffsetMismatch;
357354
358 const header_zip_offset = self.cd_zip_offset + self.cd_record_offset;355 if (isMaxInt(end_record.central_directory_size)) {
359 try self.stream.seekTo(header_zip_offset);356 result.cd_size = record64.central_directory_size;
360 const header = try (if (@TypeOf(self.stream.context) == std.fs.File) self.stream.context.deprecatedReader() else self.stream.context.reader()).readStructEndian(CentralDirectoryFileHeader, .little);357 } else if (end_record.central_directory_size != record64.central_directory_size)
361 if (!std.mem.eql(u8, &header.signature, &central_file_header_sig))358 return error.Zip64CentralDirectorySizeMismatch;
362 return error.ZipBadCdOffset;
363359
364 self.cd_record_index += 1;360 return result;
365 self.cd_record_offset += @sizeOf(CentralDirectoryFileHeader) + header.filename_len + header.extra_len + header.comment_len;361 }
366362
367 // Note: checking the version_needed_to_extract doesn't seem to be helpful, i.e. the zip file363 pub fn next(self: *Iterator) !?Entry {
368 // at https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux.zip364 if (self.cd_record_index == self.cd_record_count) {
369 // has an undocumented version 788 but extracts just fine.365 if (self.cd_record_offset != self.cd_size)
366 return if (self.cd_size > self.cd_record_offset)
367 error.ZipCdOversized
368 else
369 error.ZipCdUndersized;
370370
371 if (header.flags.encrypted)371 return null;
372 return error.ZipEncryptionUnsupported;372 }
373 // TODO: check/verify more flags
374 if (header.disk_number != 0)
375 return error.ZipMultiDiskUnsupported;
376373
377 var extents: FileExtents = .{374 const header_zip_offset = self.cd_zip_offset + self.cd_record_offset;
378 .uncompressed_size = header.uncompressed_size,375 const input = self.input;
379 .compressed_size = header.compressed_size,376 try input.seekTo(header_zip_offset);
380 .local_file_header_offset = header.local_file_header_offset,377 const header = input.interface.takeStructEndian(CentralDirectoryFileHeader, .little) catch |err| switch (err) {
381 };378 error.ReadFailed => return input.err.?,
379 error.EndOfStream => return error.EndOfStream,
380 };
381 if (!std.mem.eql(u8, &header.signature, &central_file_header_sig))
382 return error.ZipBadCdOffset;
383
384 self.cd_record_index += 1;
385 self.cd_record_offset += @sizeOf(CentralDirectoryFileHeader) + header.filename_len + header.extra_len + header.comment_len;
386
387 // Note: checking the version_needed_to_extract doesn't seem to be helpful, i.e. the zip file
388 // at https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux.zip
389 // has an undocumented version 788 but extracts just fine.
390
391 if (header.flags.encrypted)
392 return error.ZipEncryptionUnsupported;
393 // TODO: check/verify more flags
394 if (header.disk_number != 0)
395 return error.ZipMultiDiskUnsupported;
396
397 var extents: FileExtents = .{
398 .uncompressed_size = header.uncompressed_size,
399 .compressed_size = header.compressed_size,
400 .local_file_header_offset = header.local_file_header_offset,
401 };
382402
383 if (header.extra_len > 0) {403 if (header.extra_len > 0) {
384 var extra_buf: [std.math.maxInt(u16)]u8 = undefined;404 var extra_buf: [std.math.maxInt(u16)]u8 = undefined;
385 const extra = extra_buf[0..header.extra_len];405 const extra = extra_buf[0..header.extra_len];
386406
387 {407 try input.seekTo(header_zip_offset + @sizeOf(CentralDirectoryFileHeader) + header.filename_len);
388 try self.stream.seekTo(header_zip_offset + @sizeOf(CentralDirectoryFileHeader) + header.filename_len);408 input.interface.readSlice(extra) catch |err| switch (err) {
389 const len = try (if (@TypeOf(self.stream.context) == std.fs.File) self.stream.context.deprecatedReader() else self.stream.context.reader()).readAll(extra);409 error.ReadFailed => return input.err.?,
390 if (len != extra.len)410 error.EndOfStream => return error.EndOfStream,
391 return error.ZipTruncated;411 };
392 }
393412
394 var extra_offset: usize = 0;413 var extra_offset: usize = 0;
395 while (extra_offset + 4 <= extra.len) {414 while (extra_offset + 4 <= extra.len) {
396 const header_id = std.mem.readInt(u16, extra[extra_offset..][0..2], .little);415 const header_id = std.mem.readInt(u16, extra[extra_offset..][0..2], .little);
397 const data_size = std.mem.readInt(u16, extra[extra_offset..][2..4], .little);416 const data_size = std.mem.readInt(u16, extra[extra_offset..][2..4], .little);
398 const end = extra_offset + 4 + data_size;417 const end = extra_offset + 4 + data_size;
399 if (end > extra.len)418 if (end > extra.len)
400 return error.ZipBadExtraFieldSize;419 return error.ZipBadExtraFieldSize;
401 const data = extra[extra_offset + 4 .. end];420 const data = extra[extra_offset + 4 .. end];
402 switch (@as(ExtraHeader, @enumFromInt(header_id))) {421 switch (@as(ExtraHeader, @enumFromInt(header_id))) {
403 .zip64_info => try readZip64FileExtents(CentralDirectoryFileHeader, header, &extents, data),422 .zip64_info => try readZip64FileExtents(CentralDirectoryFileHeader, header, &extents, data),
404 else => {}, // ignore423 else => {}, // ignore
405 }
406 extra_offset = end;
407 }424 }
425 extra_offset = end;
408 }426 }
409
410 return .{
411 .version_needed_to_extract = header.version_needed_to_extract,
412 .flags = header.flags,
413 .compression_method = header.compression_method,
414 .last_modification_time = header.last_modification_time,
415 .last_modification_date = header.last_modification_date,
416 .header_zip_offset = header_zip_offset,
417 .crc32 = header.crc32,
418 .filename_len = header.filename_len,
419 .compressed_size = extents.compressed_size,
420 .uncompressed_size = extents.uncompressed_size,
421 .file_offset = extents.local_file_header_offset,
422 };
423 }427 }
424428
425 pub const Entry = struct {429 return .{
426 version_needed_to_extract: u16,430 .version_needed_to_extract = header.version_needed_to_extract,
427 flags: GeneralPurposeFlags,431 .flags = header.flags,
428 compression_method: CompressionMethod,432 .compression_method = header.compression_method,
429 last_modification_time: u16,433 .last_modification_time = header.last_modification_time,
430 last_modification_date: u16,434 .last_modification_date = header.last_modification_date,
431 header_zip_offset: u64,435 .header_zip_offset = header_zip_offset,
432 crc32: u32,436 .crc32 = header.crc32,
433 filename_len: u32,437 .filename_len = header.filename_len,
434 compressed_size: u64,438 .compressed_size = extents.compressed_size,
435 uncompressed_size: u64,439 .uncompressed_size = extents.uncompressed_size,
436 file_offset: u64,440 .file_offset = extents.local_file_header_offset,
437441 };
438 pub fn extract(442 }
439 self: Entry,
440 stream: SeekableStream,
441 options: ExtractOptions,
442 filename_buf: []u8,
443 dest: std.fs.Dir,
444 ) !u32 {
445 if (filename_buf.len < self.filename_len)
446 return error.ZipInsufficientBuffer;
447 const filename = filename_buf[0..self.filename_len];
448443
444 pub const Entry = struct {
445 version_needed_to_extract: u16,
446 flags: GeneralPurposeFlags,
447 compression_method: CompressionMethod,
448 last_modification_time: u16,
449 last_modification_date: u16,
450 header_zip_offset: u64,
451 crc32: u32,
452 filename_len: u32,
453 compressed_size: u64,
454 uncompressed_size: u64,
455 file_offset: u64,
456
457 pub fn extract(
458 self: Entry,
459 stream: *File.Reader,
460 options: ExtractOptions,
461 filename_buf: []u8,
462 dest: std.fs.Dir,
463 ) !u32 {
464 if (filename_buf.len < self.filename_len)
465 return error.ZipInsufficientBuffer;
466 switch (self.compression_method) {
467 .store, .deflate => {},
468 else => return error.UnsupportedCompressionMethod,
469 }
470 const filename = filename_buf[0..self.filename_len];
471 {
449 try stream.seekTo(self.header_zip_offset + @sizeOf(CentralDirectoryFileHeader));472 try stream.seekTo(self.header_zip_offset + @sizeOf(CentralDirectoryFileHeader));
473 try stream.interface.readSlice(filename);
474 }
450475
451 {476 const local_data_header_offset: u64 = local_data_header_offset: {
452 const len = try (if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()).readAll(filename);477 const local_header = blk: {
453 if (len != filename.len)478 try stream.seekTo(self.file_offset);
454 return error.ZipBadFileOffset;479 break :blk try stream.interface.takeStructEndian(LocalFileHeader, .little);
455 }480 };
481 if (!std.mem.eql(u8, &local_header.signature, &local_file_header_sig))
482 return error.ZipBadFileOffset;
483 if (local_header.version_needed_to_extract != self.version_needed_to_extract)
484 return error.ZipMismatchVersionNeeded;
485 if (local_header.last_modification_time != self.last_modification_time)
486 return error.ZipMismatchModTime;
487 if (local_header.last_modification_date != self.last_modification_date)
488 return error.ZipMismatchModDate;
489
490 if (@as(u16, @bitCast(local_header.flags)) != @as(u16, @bitCast(self.flags)))
491 return error.ZipMismatchFlags;
492 if (local_header.crc32 != 0 and local_header.crc32 != self.crc32)
493 return error.ZipMismatchCrc32;
494 var extents: FileExtents = .{
495 .uncompressed_size = local_header.uncompressed_size,
496 .compressed_size = local_header.compressed_size,
497 .local_file_header_offset = 0,
498 };
499 if (local_header.extra_len > 0) {
500 var extra_buf: [std.math.maxInt(u16)]u8 = undefined;
501 const extra = extra_buf[0..local_header.extra_len];
456502
457 const local_data_header_offset: u64 = local_data_header_offset: {503 {
458 const local_header = blk: {504 try stream.seekTo(self.file_offset + @sizeOf(LocalFileHeader) + local_header.filename_len);
459 try stream.seekTo(self.file_offset);505 try stream.interface.readSlice(extra);
460 break :blk try (if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()).readStructEndian(LocalFileHeader, .little);506 }
461 };
462 if (!std.mem.eql(u8, &local_header.signature, &local_file_header_sig))
463 return error.ZipBadFileOffset;
464 if (local_header.version_needed_to_extract != self.version_needed_to_extract)
465 return error.ZipMismatchVersionNeeded;
466 if (local_header.last_modification_time != self.last_modification_time)
467 return error.ZipMismatchModTime;
468 if (local_header.last_modification_date != self.last_modification_date)
469 return error.ZipMismatchModDate;
470
471 if (@as(u16, @bitCast(local_header.flags)) != @as(u16, @bitCast(self.flags)))
472 return error.ZipMismatchFlags;
473 if (local_header.crc32 != 0 and local_header.crc32 != self.crc32)
474 return error.ZipMismatchCrc32;
475 var extents: FileExtents = .{
476 .uncompressed_size = local_header.uncompressed_size,
477 .compressed_size = local_header.compressed_size,
478 .local_file_header_offset = 0,
479 };
480 if (local_header.extra_len > 0) {
481 var extra_buf: [std.math.maxInt(u16)]u8 = undefined;
482 const extra = extra_buf[0..local_header.extra_len];
483
484 {
485 try stream.seekTo(self.file_offset + @sizeOf(LocalFileHeader) + local_header.filename_len);
486 const len = try (if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()).readAll(extra);
487 if (len != extra.len)
488 return error.ZipTruncated;
489 }
490507
491 var extra_offset: usize = 0;508 var extra_offset: usize = 0;
492 while (extra_offset + 4 <= local_header.extra_len) {509 while (extra_offset + 4 <= local_header.extra_len) {
493 const header_id = std.mem.readInt(u16, extra[extra_offset..][0..2], .little);510 const header_id = std.mem.readInt(u16, extra[extra_offset..][0..2], .little);
494 const data_size = std.mem.readInt(u16, extra[extra_offset..][2..4], .little);511 const data_size = std.mem.readInt(u16, extra[extra_offset..][2..4], .little);
495 const end = extra_offset + 4 + data_size;512 const end = extra_offset + 4 + data_size;
496 if (end > local_header.extra_len)513 if (end > local_header.extra_len)
497 return error.ZipBadExtraFieldSize;514 return error.ZipBadExtraFieldSize;
498 const data = extra[extra_offset + 4 .. end];515 const data = extra[extra_offset + 4 .. end];
499 switch (@as(ExtraHeader, @enumFromInt(header_id))) {516 switch (@as(ExtraHeader, @enumFromInt(header_id))) {
500 .zip64_info => try readZip64FileExtents(LocalFileHeader, local_header, &extents, data),517 .zip64_info => try readZip64FileExtents(LocalFileHeader, local_header, &extents, data),
501 else => {}, // ignore518 else => {}, // ignore
502 }
503 extra_offset = end;
504 }519 }
520 extra_offset = end;
505 }521 }
522 }
506523
507 if (extents.compressed_size != 0 and524 if (extents.compressed_size != 0 and
508 extents.compressed_size != self.compressed_size)525 extents.compressed_size != self.compressed_size)
509 return error.ZipMismatchCompLen;526 return error.ZipMismatchCompLen;
510 if (extents.uncompressed_size != 0 and527 if (extents.uncompressed_size != 0 and
511 extents.uncompressed_size != self.uncompressed_size)528 extents.uncompressed_size != self.uncompressed_size)
512 return error.ZipMismatchUncompLen;529 return error.ZipMismatchUncompLen;
513530
514 if (local_header.filename_len != self.filename_len)531 if (local_header.filename_len != self.filename_len)
515 return error.ZipMismatchFilenameLen;532 return error.ZipMismatchFilenameLen;
516533
517 break :local_data_header_offset @as(u64, local_header.filename_len) +534 break :local_data_header_offset @as(u64, local_header.filename_len) +
518 @as(u64, local_header.extra_len);535 @as(u64, local_header.extra_len);
519 };536 };
520537
521 if (isBadFilename(filename))538 if (isBadFilename(filename))
522 return error.ZipBadFilename;539 return error.ZipBadFilename;
523540
524 if (options.allow_backslashes) {541 if (options.allow_backslashes) {
525 std.mem.replaceScalar(u8, filename, '\\', '/');542 std.mem.replaceScalar(u8, filename, '\\', '/');
526 } else {543 } else {
527 if (std.mem.indexOfScalar(u8, filename, '\\')) |_|544 if (std.mem.indexOfScalar(u8, filename, '\\')) |_|
528 return error.ZipFilenameHasBackslash;545 return error.ZipFilenameHasBackslash;
529 }546 }
530547
531 // All entries that end in '/' are directories548 // All entries that end in '/' are directories
532 if (filename[filename.len - 1] == '/') {549 if (filename[filename.len - 1] == '/') {
533 if (self.uncompressed_size != 0)550 if (self.uncompressed_size != 0)
534 return error.ZipBadDirectorySize;551 return error.ZipBadDirectorySize;
535 try dest.makePath(filename[0 .. filename.len - 1]);552 try dest.makePath(filename[0 .. filename.len - 1]);
536 return std.hash.Crc32.hash(&.{});553 return std.hash.Crc32.hash(&.{});
537 }554 }
538555
539 const out_file = blk: {556 const out_file = blk: {
540 if (std.fs.path.dirname(filename)) |dirname| {557 if (std.fs.path.dirname(filename)) |dirname| {
541 var parent_dir = try dest.makeOpenPath(dirname, .{});558 var parent_dir = try dest.makeOpenPath(dirname, .{});
542 defer parent_dir.close();559 defer parent_dir.close();
543560
544 const basename = std.fs.path.basename(filename);561 const basename = std.fs.path.basename(filename);
545 break :blk try parent_dir.createFile(basename, .{ .exclusive = true });562 break :blk try parent_dir.createFile(basename, .{ .exclusive = true });
546 }563 }
547 break :blk try dest.createFile(filename, .{ .exclusive = true });564 break :blk try dest.createFile(filename, .{ .exclusive = true });
548 };565 };
549 defer out_file.close();566 defer out_file.close();
550 const local_data_file_offset: u64 =567 var file_writer = out_file.writer();
551 @as(u64, self.file_offset) +568 var file_bw = file_writer.writer(&.{});
552 @as(u64, @sizeOf(LocalFileHeader)) +569 const local_data_file_offset: u64 =
553 local_data_header_offset;570 @as(u64, self.file_offset) +
554 try stream.seekTo(local_data_file_offset);571 @as(u64, @sizeOf(LocalFileHeader)) +
555 var limited_reader = std.io.limitedReader((if (@TypeOf(stream.context) == std.fs.File) stream.context.deprecatedReader() else stream.context.reader()), self.compressed_size);572 local_data_header_offset;
556 const crc = try decompress(573 try stream.seekTo(local_data_file_offset);
557 self.compression_method,574 var limited_file_reader = stream.interface.limited(.limited(self.compressed_size));
558 self.uncompressed_size,575 var file_read_buffer: [1000]u8 = undefined;
559 limited_reader.reader(),576 var decompress_read_buffer: [1000]u8 = undefined;
560 out_file.deprecatedWriter(),577 var limited_br = limited_file_reader.reader().buffered(&file_read_buffer);
561 );578 var decompress: Decompress = undefined;
562 if (limited_reader.bytes_left != 0)579 var decompress_br = decompress.readable(&limited_br, self.compression_method, &decompress_read_buffer);
563 return error.ZipDecompressTruncated;580 const start_out = file_bw.count;
564 return crc;581 var hash_writer = file_bw.hashed(std.hash.Crc32.init());
565 }582 var hash_bw = hash_writer.writer(&.{});
566 };583 decompress_br.readAll(&hash_bw, .limited(self.uncompressed_size)) catch |err| switch (err) {
584 error.ReadFailed => return stream.err.?,
585 error.WriteFailed => return file_writer.err.?,
586 error.EndOfStream => return error.ZipDecompressTruncated,
587 };
588 if (limited_file_reader.remaining.nonzero()) return error.ZipDecompressTruncated;
589 const written = file_bw.count - start_out;
590 if (written != self.uncompressed_size) return error.ZipUncompressSizeMismatch;
591 return hash_writer.hasher.final();
592 }
567 };593 };
568}594};
569595
570// returns true if `filename` starts with `root` followed by a forward slash596// returns true if `filename` starts with `root` followed by a forward slash
571fn filenameInRoot(filename: []const u8, root: []const u8) bool {597fn filenameInRoot(filename: []const u8, root: []const u8) bool {
...@@ -614,17 +640,13 @@ pub const ExtractOptions = struct {...@@ -614,17 +640,13 @@ pub const ExtractOptions = struct {
614 diagnostics: ?*Diagnostics = null,640 diagnostics: ?*Diagnostics = null,
615};641};
616642
617/// Extract the zipped files inside `seekable_stream` to the given `dest` directory.643/// Extract the zipped files to the given `dest` directory.
618/// Note that `seekable_stream` must be an instance of `std.io.SeekableStream` and644pub fn extract(dest: std.fs.Dir, fr: *File.Reader, options: ExtractOptions) !void {
619/// its context must also have a `.reader()` method that returns an instance of645 var iter = try Iterator.init(fr);
620/// `std.io.GenericReader`.
621pub fn extract(dest: std.fs.Dir, seekable_stream: anytype, options: ExtractOptions) !void {
622 const SeekableStream = @TypeOf(seekable_stream);
623 var iter = try Iterator(SeekableStream).init(seekable_stream);
624646
625 var filename_buf: [std.fs.max_path_bytes]u8 = undefined;647 var filename_buf: [std.fs.max_path_bytes]u8 = undefined;
626 while (try iter.next()) |entry| {648 while (try iter.next()) |entry| {
627 const crc32 = try entry.extract(seekable_stream, options, &filename_buf, dest);649 const crc32 = try entry.extract(fr, options, &filename_buf, dest);
628 if (crc32 != entry.crc32)650 if (crc32 != entry.crc32)
629 return error.ZipCrcMismatch;651 return error.ZipCrcMismatch;
630 if (options.diagnostics) |d| {652 if (options.diagnostics) |d| {
...@@ -633,173 +655,6 @@ pub fn extract(dest: std.fs.Dir, seekable_stream: anytype, options: ExtractOptio...@@ -633,173 +655,6 @@ pub fn extract(dest: std.fs.Dir, seekable_stream: anytype, options: ExtractOptio
633 }655 }
634}656}
635657
636fn testZip(options: ExtractOptions, comptime files: []const File, write_opt: testutil.WriteZipOptions) !void {658test {
637 var store: [files.len]FileStore = undefined;659 _ = @import("zip/test.zig");
638 try testZipWithStore(options, files, write_opt, &store);
639}
640fn testZipWithStore(
641 options: ExtractOptions,
642 test_files: []const File,
643 write_opt: testutil.WriteZipOptions,
644 store: []FileStore,
645) !void {
646 var zip_buf: [4096]u8 = undefined;
647 var fbs = try testutil.makeZipWithStore(&zip_buf, test_files, write_opt, store);
648
649 var tmp = testing.tmpDir(.{ .no_follow = true });
650 defer tmp.cleanup();
651 try extract(tmp.dir, fbs.seekableStream(), options);
652 try testutil.expectFiles(test_files, tmp.dir, .{});
653}
654fn testZipError(expected_error: anyerror, file: File, options: ExtractOptions) !void {
655 var zip_buf: [4096]u8 = undefined;
656 var store: [1]FileStore = undefined;
657 var fbs = try testutil.makeZipWithStore(&zip_buf, &[_]File{file}, .{}, &store);
658 var tmp = testing.tmpDir(.{ .no_follow = true });
659 defer tmp.cleanup();
660 try testing.expectError(expected_error, extract(tmp.dir, fbs.seekableStream(), options));
661}
662
663test "zip one file" {
664 try testZip(.{}, &[_]File{
665 .{ .name = "onefile.txt", .content = "Just a single file\n", .compression = .store },
666 }, .{});
667}
668test "zip multiple files" {
669 try testZip(.{ .allow_backslashes = true }, &[_]File{
670 .{ .name = "foo", .content = "a foo file\n", .compression = .store },
671 .{ .name = "subdir/bar", .content = "bar is this right?\nanother newline\n", .compression = .store },
672 .{ .name = "subdir\\whoa", .content = "you can do backslashes", .compression = .store },
673 .{ .name = "subdir/another/baz", .content = "bazzy mc bazzerson", .compression = .store },
674 }, .{});
675}
676test "zip deflated" {
677 try testZip(.{}, &[_]File{
678 .{ .name = "deflateme", .content = "This is a deflated file.\nIt should be smaller in the Zip file1\n", .compression = .deflate },
679 // TODO: re-enable this if/when we add support for deflate64
680 //.{ .name = "deflateme64", .content = "The 64k version of deflate!\n", .compression = .deflate64 },
681 .{ .name = "raw", .content = "Not all files need to be deflated in the same Zip.\n", .compression = .store },
682 }, .{});
683}
684test "zip verify filenames" {
685 // no empty filenames
686 try testZipError(error.ZipBadFilename, .{ .name = "", .content = "", .compression = .store }, .{});
687 // no absolute paths
688 try testZipError(error.ZipBadFilename, .{ .name = "/", .content = "", .compression = .store }, .{});
689 try testZipError(error.ZipBadFilename, .{ .name = "/foo", .content = "", .compression = .store }, .{});
690 try testZipError(error.ZipBadFilename, .{ .name = "/foo/bar", .content = "", .compression = .store }, .{});
691 // no '..' components
692 try testZipError(error.ZipBadFilename, .{ .name = "..", .content = "", .compression = .store }, .{});
693 try testZipError(error.ZipBadFilename, .{ .name = "foo/..", .content = "", .compression = .store }, .{});
694 try testZipError(error.ZipBadFilename, .{ .name = "foo/bar/..", .content = "", .compression = .store }, .{});
695 try testZipError(error.ZipBadFilename, .{ .name = "foo/bar/../", .content = "", .compression = .store }, .{});
696 // no backslashes
697 try testZipError(error.ZipFilenameHasBackslash, .{ .name = "foo\\bar", .content = "", .compression = .store }, .{});
698}
699
700test "zip64" {
701 const test_files = [_]File{
702 .{ .name = "fram", .content = "fram foo fro fraba", .compression = .store },
703 .{ .name = "subdir/barro", .content = "aljdk;jal;jfd;lajkf", .compression = .store },
704 };
705
706 try testZip(.{}, &test_files, .{
707 .end = .{
708 .zip64 = .{},
709 .record_count_disk = std.math.maxInt(u16), // trigger zip64
710 },
711 });
712 try testZip(.{}, &test_files, .{
713 .end = .{
714 .zip64 = .{},
715 .record_count_total = std.math.maxInt(u16), // trigger zip64
716 },
717 });
718 try testZip(.{}, &test_files, .{
719 .end = .{
720 .zip64 = .{},
721 .record_count_disk = std.math.maxInt(u16), // trigger zip64
722 .record_count_total = std.math.maxInt(u16), // trigger zip64
723 },
724 });
725 try testZip(.{}, &test_files, .{
726 .end = .{
727 .zip64 = .{},
728 .central_directory_size = std.math.maxInt(u32), // trigger zip64
729 },
730 });
731 try testZip(.{}, &test_files, .{
732 .end = .{
733 .zip64 = .{},
734 .central_directory_offset = std.math.maxInt(u32), // trigger zip64
735 },
736 });
737 try testZip(.{}, &test_files, .{
738 .end = .{
739 .zip64 = .{},
740 .central_directory_offset = std.math.maxInt(u32), // trigger zip64
741 },
742 .local_header = .{
743 .zip64 = .{ // trigger local header zip64
744 .data_size = 16,
745 },
746 .compressed_size = std.math.maxInt(u32),
747 .uncompressed_size = std.math.maxInt(u32),
748 .extra_len = 20,
749 },
750 });
751}
752
753test "bad zip files" {
754 var tmp = testing.tmpDir(.{ .no_follow = true });
755 defer tmp.cleanup();
756 var zip_buf: [4096]u8 = undefined;
757
758 const file_a = [_]File{.{ .name = "a", .content = "", .compression = .store }};
759
760 {
761 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .sig = [_]u8{ 1, 2, 3, 4 } } });
762 try testing.expectError(error.ZipNoEndRecord, extract(tmp.dir, fbs.seekableStream(), .{}));
763 }
764 {
765 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .comment_len = 1 } });
766 try testing.expectError(error.ZipNoEndRecord, extract(tmp.dir, fbs.seekableStream(), .{}));
767 }
768 {
769 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .comment = "a", .comment_len = 0 } });
770 try testing.expectError(error.ZipNoEndRecord, extract(tmp.dir, fbs.seekableStream(), .{}));
771 }
772 {
773 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .disk_number = 1 } });
774 try testing.expectError(error.ZipMultiDiskUnsupported, extract(tmp.dir, fbs.seekableStream(), .{}));
775 }
776 {
777 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .central_directory_disk_number = 1 } });
778 try testing.expectError(error.ZipMultiDiskUnsupported, extract(tmp.dir, fbs.seekableStream(), .{}));
779 }
780 {
781 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .record_count_disk = 1 } });
782 try testing.expectError(error.ZipDiskRecordCountTooLarge, extract(tmp.dir, fbs.seekableStream(), .{}));
783 }
784 {
785 var fbs = try testutil.makeZip(&zip_buf, &.{}, .{ .end = .{ .central_directory_size = 1 } });
786 try testing.expectError(error.ZipCdOversized, extract(tmp.dir, fbs.seekableStream(), .{}));
787 }
788 {
789 var fbs = try testutil.makeZip(&zip_buf, &file_a, .{ .end = .{ .central_directory_size = 0 } });
790 try testing.expectError(error.ZipCdUndersized, extract(tmp.dir, fbs.seekableStream(), .{}));
791 }
792 {
793 var fbs = try testutil.makeZip(&zip_buf, &file_a, .{ .end = .{ .central_directory_offset = 0 } });
794 try testing.expectError(error.ZipBadCdOffset, extract(tmp.dir, fbs.seekableStream(), .{}));
795 }
796 {
797 var fbs = try testutil.makeZip(&zip_buf, &file_a, .{
798 .end = .{
799 .zip64 = .{ .locator_sig = [_]u8{ 1, 2, 3, 4 } },
800 .central_directory_size = std.math.maxInt(u32), // trigger 64
801 },
802 });
803 try testing.expectError(error.ZipBadLocatorSig, extract(tmp.dir, fbs.seekableStream(), .{}));
804 }
805}660}