| author | |
| committer | |
| log | 824c157e0c25a9337ffd036f7ad5cb811b1f18cd |
| tree | c58f974383513f7ff22e4e47a99f52793b1abff5 |
| parent | 73c98ca0e6aa52b942b92135ecf0305362030733 |
7 files changed, 271 insertions(+), 803 deletions(-)
lib/std/compress/flate.zig+21-484| ... | ... | @@ -1,7 +1,23 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | 1 | const std = @import("../std.zig"); |
| 3 | const testing = std.testing; | |
| 4 | const Writer = std.Io.Writer; | |
| 2 | ||
| 3 | /// When decompressing, the output buffer is used as the history window, so | |
| 4 | /// less than this may result in failure to decompress streams that were | |
| 5 | /// compressed with a larger window. | |
| 6 | pub const max_window_len = history_len * 2; | |
| 7 | ||
| 8 | pub const history_len = 32768; | |
| 9 | ||
| 10 | /// Deflate is a lossless data compression file format that uses a combination | |
| 11 | /// of LZ77 and Huffman coding. | |
| 12 | pub const Compress = @import("flate/Compress.zig"); | |
| 13 | ||
| 14 | /// Inflate is the decoding process that takes a Deflate bitstream for | |
| 15 | /// decompression and correctly produces the original full-size data or file. | |
| 16 | pub const Decompress = @import("flate/Decompress.zig"); | |
| 17 | ||
| 18 | /// Compression without Lempel-Ziv match searching. Faster compression, less | |
| 19 | /// memory requirements but bigger compressed sizes. | |
| 20 | pub const HuffmanEncoder = @import("flate/HuffmanEncoder.zig"); | |
| 5 | 21 | |
| 6 | 22 | /// Container of the deflate bit stream body. Container adds header before |
| 7 | 23 | /// deflate bit stream and footer after. It can bi gzip, zlib or raw (no header, |
| ... | ... | @@ -13,7 +29,6 @@ const Writer = std.Io.Writer; |
| 13 | 29 | /// Gzip format is defined in rfc 1952. Header has 10+ bytes and footer 4 bytes |
| 14 | 30 | /// crc32 checksum and 4 bytes of uncompressed data length. |
| 15 | 31 | /// |
| 16 | /// | |
| 17 | 32 | /// rfc 1950: https://datatracker.ietf.org/doc/html/rfc1950#page-4 |
| 18 | 33 | /// rfc 1952: https://datatracker.ietf.org/doc/html/rfc1952#page-5 |
| 19 | 34 | pub const Container = enum { |
| ... | ... | @@ -84,7 +99,7 @@ pub const Container = enum { |
| 84 | 99 | pub fn init(containter: Container) Hasher { |
| 85 | 100 | return switch (containter) { |
| 86 | 101 | .gzip => .{ .gzip = .{} }, |
| 87 | .zlib => .{ .zlib = .init() }, | |
| 102 | .zlib => .{ .zlib = .{} }, | |
| 88 | 103 | .raw => .raw, |
| 89 | 104 | }; |
| 90 | 105 | } |
| ... | ... | @@ -107,7 +122,7 @@ pub const Container = enum { |
| 107 | 122 | } |
| 108 | 123 | } |
| 109 | 124 | |
| 110 | pub fn writeFooter(hasher: *Hasher, writer: *Writer) Writer.Error!void { | |
| 125 | pub fn writeFooter(hasher: *Hasher, writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 111 | 126 | var bits: [4]u8 = undefined; |
| 112 | 127 | switch (hasher.*) { |
| 113 | 128 | .gzip => |*gzip| { |
| ... | ... | @@ -135,484 +150,6 @@ pub const Container = enum { |
| 135 | 150 | }; |
| 136 | 151 | }; |
| 137 | 152 | |
| 138 | /// When decompressing, the output buffer is used as the history window, so | |
| 139 | /// less than this may result in failure to decompress streams that were | |
| 140 | /// compressed with a larger window. | |
| 141 | pub const max_window_len = 1 << 16; | |
| 142 | ||
| 143 | /// Deflate is a lossless data compression file format that uses a combination | |
| 144 | /// of LZ77 and Huffman coding. | |
| 145 | pub const Compress = @import("flate/Compress.zig"); | |
| 146 | ||
| 147 | /// Inflate is the decoding process that takes a Deflate bitstream for | |
| 148 | /// decompression and correctly produces the original full-size data or file. | |
| 149 | pub const Decompress = @import("flate/Decompress.zig"); | |
| 150 | ||
| 151 | /// Compression without Lempel-Ziv match searching. Faster compression, less | |
| 152 | /// memory requirements but bigger compressed sizes. | |
| 153 | pub const HuffmanEncoder = @import("flate/HuffmanEncoder.zig"); | |
| 154 | ||
| 155 | test "compress/decompress" { | |
| 156 | const print = std.debug.print; | |
| 157 | var cmp_buf: [64 * 1024]u8 = undefined; // compressed data buffer | |
| 158 | var dcm_buf: [64 * 1024]u8 = undefined; // decompressed data buffer | |
| 159 | ||
| 160 | const levels = [_]Compress.Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 }; | |
| 161 | const cases = [_]struct { | |
| 162 | data: []const u8, // uncompressed content | |
| 163 | // compressed data sizes per level 4-9 | |
| 164 | gzip_sizes: [levels.len]usize = [_]usize{0} ** levels.len, | |
| 165 | huffman_only_size: usize = 0, | |
| 166 | store_size: usize = 0, | |
| 167 | }{ | |
| 168 | .{ | |
| 169 | .data = @embedFile("flate/testdata/rfc1951.txt"), | |
| 170 | .gzip_sizes = [_]usize{ 11513, 11217, 11139, 11126, 11122, 11119 }, | |
| 171 | .huffman_only_size = 20287, | |
| 172 | .store_size = 36967, | |
| 173 | }, | |
| 174 | .{ | |
| 175 | .data = @embedFile("flate/testdata/fuzz/roundtrip1.input"), | |
| 176 | .gzip_sizes = [_]usize{ 373, 370, 370, 370, 370, 370 }, | |
| 177 | .huffman_only_size = 393, | |
| 178 | .store_size = 393, | |
| 179 | }, | |
| 180 | .{ | |
| 181 | .data = @embedFile("flate/testdata/fuzz/roundtrip2.input"), | |
| 182 | .gzip_sizes = [_]usize{ 373, 373, 373, 373, 373, 373 }, | |
| 183 | .huffman_only_size = 394, | |
| 184 | .store_size = 394, | |
| 185 | }, | |
| 186 | .{ | |
| 187 | .data = @embedFile("flate/testdata/fuzz/deflate-stream.expect"), | |
| 188 | .gzip_sizes = [_]usize{ 351, 347, 347, 347, 347, 347 }, | |
| 189 | .huffman_only_size = 498, | |
| 190 | .store_size = 747, | |
| 191 | }, | |
| 192 | }; | |
| 193 | ||
| 194 | for (cases, 0..) |case, case_no| { | |
| 195 | const data = case.data; | |
| 196 | ||
| 197 | for (levels, 0..) |level, i| { | |
| 198 | for (Container.list) |container| { | |
| 199 | var compressed_size: usize = if (case.gzip_sizes[i] > 0) | |
| 200 | case.gzip_sizes[i] - Container.gzip.size() + container.size() | |
| 201 | else | |
| 202 | 0; | |
| 203 | ||
| 204 | // compress original stream to compressed stream | |
| 205 | { | |
| 206 | var compressed: Writer = .fixed(&cmp_buf); | |
| 207 | var compress: Compress = .init(&compressed, &.{}, .{ .container = .raw, .level = level }); | |
| 208 | try compress.writer.writeAll(data); | |
| 209 | try compress.end(); | |
| 210 | ||
| 211 | if (compressed_size == 0) { | |
| 212 | if (container == .gzip) | |
| 213 | print("case {d} gzip level {} compressed size: {d}\n", .{ case_no, level, compressed.pos }); | |
| 214 | compressed_size = compressed.end; | |
| 215 | } | |
| 216 | try testing.expectEqual(compressed_size, compressed.end); | |
| 217 | } | |
| 218 | // decompress compressed stream to decompressed stream | |
| 219 | { | |
| 220 | var compressed: std.Io.Reader = .fixed(cmp_buf[0..compressed_size]); | |
| 221 | var decompressed: Writer = .fixed(&dcm_buf); | |
| 222 | var decompress: Decompress = .init(&compressed, container, &.{}); | |
| 223 | _ = try decompress.reader.streamRemaining(&decompressed); | |
| 224 | try testing.expectEqualSlices(u8, data, decompressed.buffered()); | |
| 225 | } | |
| 226 | ||
| 227 | // compressor writer interface | |
| 228 | { | |
| 229 | var compressed: Writer = .fixed(&cmp_buf); | |
| 230 | var cmp = try Compress.init(&compressed, &.{}, .{ | |
| 231 | .level = level, | |
| 232 | .container = container, | |
| 233 | }); | |
| 234 | var cmp_wrt = cmp.writer(); | |
| 235 | try cmp_wrt.writeAll(data); | |
| 236 | try cmp.finish(); | |
| 237 | ||
| 238 | try testing.expectEqual(compressed_size, compressed.pos); | |
| 239 | } | |
| 240 | // decompressor reader interface | |
| 241 | { | |
| 242 | var compressed: std.Io.Reader = .fixed(cmp_buf[0..compressed_size]); | |
| 243 | var decompress: Decompress = .init(&compressed, container, &.{}); | |
| 244 | const n = try decompress.reader.readSliceShort(&dcm_buf); | |
| 245 | try testing.expectEqual(data.len, n); | |
| 246 | try testing.expectEqualSlices(u8, data, dcm_buf[0..n]); | |
| 247 | } | |
| 248 | } | |
| 249 | } | |
| 250 | // huffman only compression | |
| 251 | { | |
| 252 | for (Container.list) |container| { | |
| 253 | var compressed_size: usize = if (case.huffman_only_size > 0) | |
| 254 | case.huffman_only_size - Container.gzip.size() + container.size() | |
| 255 | else | |
| 256 | 0; | |
| 257 | ||
| 258 | // compress original stream to compressed stream | |
| 259 | { | |
| 260 | var original: std.Io.Reader = .fixed(data); | |
| 261 | var compressed: Writer = .fixed(&cmp_buf); | |
| 262 | var cmp = try Compress.Huffman.init(container, &compressed); | |
| 263 | try cmp.compress(original.reader()); | |
| 264 | try cmp.finish(); | |
| 265 | if (compressed_size == 0) { | |
| 266 | if (container == .gzip) | |
| 267 | print("case {d} huffman only compressed size: {d}\n", .{ case_no, compressed.pos }); | |
| 268 | compressed_size = compressed.pos; | |
| 269 | } | |
| 270 | try testing.expectEqual(compressed_size, compressed.pos); | |
| 271 | } | |
| 272 | // decompress compressed stream to decompressed stream | |
| 273 | { | |
| 274 | var compressed: std.Io.Reader = .fixed(cmp_buf[0..compressed_size]); | |
| 275 | var decompress: Decompress = .init(&compressed, container, &.{}); | |
| 276 | var decompressed: Writer = .fixed(&dcm_buf); | |
| 277 | _ = try decompress.reader.streamRemaining(&decompressed); | |
| 278 | try testing.expectEqualSlices(u8, data, decompressed.buffered()); | |
| 279 | } | |
| 280 | } | |
| 281 | } | |
| 282 | ||
| 283 | // store only | |
| 284 | { | |
| 285 | for (Container.list) |container| { | |
| 286 | var compressed_size: usize = if (case.store_size > 0) | |
| 287 | case.store_size - Container.gzip.size() + container.size() | |
| 288 | else | |
| 289 | 0; | |
| 290 | ||
| 291 | // compress original stream to compressed stream | |
| 292 | { | |
| 293 | var original: std.Io.Reader = .fixed(data); | |
| 294 | var compressed: Writer = .fixed(&cmp_buf); | |
| 295 | var cmp = try Compress.SimpleCompressor(.store, container).init(&compressed); | |
| 296 | try cmp.compress(original.reader()); | |
| 297 | try cmp.finish(); | |
| 298 | if (compressed_size == 0) { | |
| 299 | if (container == .gzip) | |
| 300 | print("case {d} store only compressed size: {d}\n", .{ case_no, compressed.pos }); | |
| 301 | compressed_size = compressed.pos; | |
| 302 | } | |
| 303 | ||
| 304 | try testing.expectEqual(compressed_size, compressed.pos); | |
| 305 | } | |
| 306 | // decompress compressed stream to decompressed stream | |
| 307 | { | |
| 308 | var compressed: std.Io.Reader = .fixed(cmp_buf[0..compressed_size]); | |
| 309 | var decompress: Decompress = .init(&compressed, container, &.{}); | |
| 310 | var decompressed: Writer = .fixed(&dcm_buf); | |
| 311 | _ = try decompress.reader.streamRemaining(&decompressed); | |
| 312 | try testing.expectEqualSlices(u8, data, decompressed.buffered()); | |
| 313 | } | |
| 314 | } | |
| 315 | } | |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void { | |
| 320 | var in: std.Io.Reader = .fixed(compressed); | |
| 321 | var aw: std.Io.Writer.Allocating = .init(testing.allocator); | |
| 322 | defer aw.deinit(); | |
| 323 | ||
| 324 | var decompress: Decompress = .init(&in, container, &.{}); | |
| 325 | _ = try decompress.reader.streamRemaining(&aw.writer); | |
| 326 | try testing.expectEqualSlices(u8, expected_plain, aw.getWritten()); | |
| 327 | } | |
| 328 | ||
| 329 | test "don't read past deflate stream's end" { | |
| 330 | try testDecompress(.zlib, &[_]u8{ | |
| 331 | 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0xc0, 0x00, 0xc1, 0xff, | |
| 332 | 0xff, 0x43, 0x30, 0x03, 0x03, 0xc3, 0xff, 0xff, 0xff, 0x01, | |
| 333 | 0x83, 0x95, 0x0b, 0xf5, | |
| 334 | }, &[_]u8{ | |
| 335 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, | |
| 336 | 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, | |
| 337 | 0x00, 0x00, 0xff, 0xff, 0xff, | |
| 338 | }); | |
| 339 | } | |
| 340 | ||
| 341 | test "zlib header" { | |
| 342 | // Truncated header | |
| 343 | try testing.expectError( | |
| 344 | error.EndOfStream, | |
| 345 | testDecompress(.zlib, &[_]u8{0x78}, ""), | |
| 346 | ); | |
| 347 | // Wrong CM | |
| 348 | try testing.expectError( | |
| 349 | error.BadZlibHeader, | |
| 350 | testDecompress(.zlib, &[_]u8{ 0x79, 0x94 }, ""), | |
| 351 | ); | |
| 352 | // Wrong CINFO | |
| 353 | try testing.expectError( | |
| 354 | error.BadZlibHeader, | |
| 355 | testDecompress(.zlib, &[_]u8{ 0x88, 0x98 }, ""), | |
| 356 | ); | |
| 357 | // Wrong checksum | |
| 358 | try testing.expectError( | |
| 359 | error.WrongZlibChecksum, | |
| 360 | testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, ""), | |
| 361 | ); | |
| 362 | // Truncated checksum | |
| 363 | try testing.expectError( | |
| 364 | error.EndOfStream, | |
| 365 | testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""), | |
| 366 | ); | |
| 367 | } | |
| 368 | ||
| 369 | test "gzip header" { | |
| 370 | // Truncated header | |
| 371 | try testing.expectError( | |
| 372 | error.EndOfStream, | |
| 373 | testDecompress(.gzip, &[_]u8{ 0x1f, 0x8B }, undefined), | |
| 374 | ); | |
| 375 | // Wrong CM | |
| 376 | try testing.expectError( | |
| 377 | error.BadGzipHeader, | |
| 378 | testDecompress(.gzip, &[_]u8{ | |
| 379 | 0x1f, 0x8b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 380 | 0x00, 0x03, | |
| 381 | }, undefined), | |
| 382 | ); | |
| 383 | ||
| 384 | // Wrong checksum | |
| 385 | try testing.expectError( | |
| 386 | error.WrongGzipChecksum, | |
| 387 | testDecompress(.gzip, &[_]u8{ | |
| 388 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 389 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, | |
| 390 | 0x00, 0x00, 0x00, 0x00, | |
| 391 | }, undefined), | |
| 392 | ); | |
| 393 | // Truncated checksum | |
| 394 | try testing.expectError( | |
| 395 | error.EndOfStream, | |
| 396 | testDecompress(.gzip, &[_]u8{ | |
| 397 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 398 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, | |
| 399 | }, undefined), | |
| 400 | ); | |
| 401 | // Wrong initial size | |
| 402 | try testing.expectError( | |
| 403 | error.WrongGzipSize, | |
| 404 | testDecompress(.gzip, &[_]u8{ | |
| 405 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 406 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 407 | 0x00, 0x00, 0x00, 0x01, | |
| 408 | }, undefined), | |
| 409 | ); | |
| 410 | // Truncated initial size field | |
| 411 | try testing.expectError( | |
| 412 | error.EndOfStream, | |
| 413 | testDecompress(.gzip, &[_]u8{ | |
| 414 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 415 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 416 | 0x00, 0x00, 0x00, | |
| 417 | }, undefined), | |
| 418 | ); | |
| 419 | ||
| 420 | try testDecompress(.gzip, &[_]u8{ | |
| 421 | // GZIP header | |
| 422 | 0x1f, 0x8b, 0x08, 0x12, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, | |
| 423 | // header.FHCRC (should cover entire header) | |
| 424 | 0x99, 0xd6, | |
| 425 | // GZIP data | |
| 426 | 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 427 | }, ""); | |
| 428 | } | |
| 429 | ||
| 430 | test "public interface" { | |
| 431 | const plain_data_buf = [_]u8{ 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a }; | |
| 432 | ||
| 433 | // deflate final stored block, header + plain (stored) data | |
| 434 | const deflate_block = [_]u8{ | |
| 435 | 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen | |
| 436 | } ++ plain_data_buf; | |
| 437 | ||
| 438 | const plain_data: []const u8 = &plain_data_buf; | |
| 439 | const gzip_data: []const u8 = &deflate_block; | |
| 440 | ||
| 441 | //// gzip header/footer + deflate block | |
| 442 | //const gzip_data = | |
| 443 | // [_]u8{ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } ++ // gzip header (10 bytes) | |
| 444 | // deflate_block ++ | |
| 445 | // [_]u8{ 0xd5, 0xe0, 0x39, 0xb7, 0x0c, 0x00, 0x00, 0x00 }; // gzip footer checksum (4 byte), size (4 bytes) | |
| 446 | ||
| 447 | //// zlib header/footer + deflate block | |
| 448 | //const zlib_data = [_]u8{ 0x78, 0b10_0_11100 } ++ // zlib header (2 bytes)} | |
| 449 | // deflate_block ++ | |
| 450 | // [_]u8{ 0x1c, 0xf2, 0x04, 0x47 }; // zlib footer: checksum | |
| 451 | ||
| 452 | // TODO | |
| 453 | //const gzip = @import("gzip.zig"); | |
| 454 | //const zlib = @import("zlib.zig"); | |
| 455 | ||
| 456 | var buffer1: [64]u8 = undefined; | |
| 457 | var buffer2: [64]u8 = undefined; | |
| 458 | ||
| 459 | // decompress | |
| 460 | { | |
| 461 | var plain: Writer = .fixed(&buffer2); | |
| 462 | var in: std.Io.Reader = .fixed(gzip_data); | |
| 463 | var d: Decompress = .init(&in, .raw, &.{}); | |
| 464 | _ = try d.reader.streamRemaining(&plain); | |
| 465 | try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 466 | } | |
| 467 | ||
| 468 | // compress/decompress | |
| 469 | { | |
| 470 | var plain: Writer = .fixed(&buffer2); | |
| 471 | var compressed: Writer = .fixed(&buffer1); | |
| 472 | ||
| 473 | var cmp: Compress = .init(&compressed, &.{}, .{}); | |
| 474 | try cmp.writer.writeAll(plain_data); | |
| 475 | try cmp.end(); | |
| 476 | ||
| 477 | var r: std.Io.Reader = .fixed(&buffer1); | |
| 478 | var d: Decompress = .init(&r, .raw, &.{}); | |
| 479 | _ = try d.reader.streamRemaining(&plain); | |
| 480 | try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 481 | } | |
| 482 | ||
| 483 | // compressor/decompressor | |
| 484 | { | |
| 485 | var plain: Writer = .fixed(&buffer2); | |
| 486 | var compressed: Writer = .fixed(&buffer1); | |
| 487 | ||
| 488 | var cmp: Compress = .init(&compressed, &.{}, .{}); | |
| 489 | try cmp.writer.writeAll(plain_data); | |
| 490 | try cmp.end(); | |
| 491 | ||
| 492 | var r: std.Io.Reader = .fixed(&buffer1); | |
| 493 | var dcp = Decompress(&r); | |
| 494 | try dcp.decompress(&plain); | |
| 495 | try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 496 | } | |
| 497 | ||
| 498 | // huffman | |
| 499 | { | |
| 500 | // huffman compress/decompress | |
| 501 | { | |
| 502 | var plain: Writer = .fixed(&buffer2); | |
| 503 | var compressed: Writer = .fixed(&buffer1); | |
| 504 | ||
| 505 | var in: std.Io.Reader = .fixed(plain_data); | |
| 506 | try HuffmanEncoder.compress(&in, &compressed); | |
| 507 | ||
| 508 | var r: std.Io.Reader = .fixed(&buffer1); | |
| 509 | var d: Decompress = .init(&r, .raw, &.{}); | |
| 510 | _ = try d.reader.streamRemaining(&plain); | |
| 511 | try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 512 | } | |
| 513 | ||
| 514 | // huffman compressor/decompressor | |
| 515 | { | |
| 516 | var plain: Writer = .fixed(&buffer2); | |
| 517 | var compressed: Writer = .fixed(&buffer1); | |
| 518 | ||
| 519 | var in: std.Io.Reader = .fixed(plain_data); | |
| 520 | var cmp = try HuffmanEncoder.Compressor(&compressed); | |
| 521 | try cmp.compress(&in); | |
| 522 | try cmp.finish(); | |
| 523 | ||
| 524 | var r: std.Io.Reader = .fixed(&buffer1); | |
| 525 | var d: Decompress = .init(&r, .raw, &.{}); | |
| 526 | _ = try d.reader.streamRemaining(&plain); | |
| 527 | try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 528 | } | |
| 529 | } | |
| 530 | ||
| 531 | // TODO | |
| 532 | //{ | |
| 533 | // // store compress/decompress | |
| 534 | // { | |
| 535 | // var plain: Writer = .fixed(&buffer2); | |
| 536 | // var compressed: Writer = .fixed(&buffer1); | |
| 537 | ||
| 538 | // var in: std.Io.Reader = .fixed(plain_data); | |
| 539 | // try store.compress(&in, &compressed); | |
| 540 | ||
| 541 | // var r: std.Io.Reader = .fixed(&buffer1); | |
| 542 | // var d: Decompress = .init(&r, .raw, &.{}); | |
| 543 | // _ = try d.reader.streamRemaining(&plain); | |
| 544 | // try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 545 | // } | |
| 546 | ||
| 547 | // // store compressor/decompressor | |
| 548 | // { | |
| 549 | // var plain: Writer = .fixed(&buffer2); | |
| 550 | // var compressed: Writer = .fixed(&buffer1); | |
| 551 | ||
| 552 | // var in: std.Io.Reader = .fixed(plain_data); | |
| 553 | // var cmp = try store.compressor(&compressed); | |
| 554 | // try cmp.compress(&in); | |
| 555 | // try cmp.finish(); | |
| 556 | ||
| 557 | // var r: std.Io.Reader = .fixed(&buffer1); | |
| 558 | // var d: Decompress = .init(&r, .raw, &.{}); | |
| 559 | // _ = try d.reader.streamRemaining(&plain); | |
| 560 | // try testing.expectEqualSlices(u8, plain_data, plain.buffered()); | |
| 561 | // } | |
| 562 | //} | |
| 563 | } | |
| 564 | ||
| 565 | pub const match = struct { | |
| 566 | pub const base_length = 3; // smallest match length per the RFC section 3.2.5 | |
| 567 | pub const min_length = 4; // min length used in this algorithm | |
| 568 | pub const max_length = 258; | |
| 569 | ||
| 570 | pub const min_distance = 1; | |
| 571 | pub const max_distance = 32768; | |
| 572 | }; | |
| 573 | ||
| 574 | pub const history_len = match.max_distance; | |
| 575 | ||
| 576 | pub const lookup = struct { | |
| 577 | pub const bits = 15; | |
| 578 | pub const len = 1 << bits; | |
| 579 | pub const shift = 32 - bits; | |
| 580 | }; | |
| 581 | ||
| 582 | test "zlib should not overshoot" { | |
| 583 | // Compressed zlib data with extra 4 bytes at the end. | |
| 584 | const data = [_]u8{ | |
| 585 | 0x78, 0x9c, 0x73, 0xce, 0x2f, 0xa8, 0x2c, 0xca, 0x4c, 0xcf, 0x28, 0x51, 0x08, 0xcf, 0xcc, 0xc9, | |
| 586 | 0x49, 0xcd, 0x55, 0x28, 0x4b, 0xcc, 0x53, 0x08, 0x4e, 0xce, 0x48, 0xcc, 0xcc, 0xd6, 0x51, 0x08, | |
| 587 | 0xce, 0xcc, 0x4b, 0x4f, 0x2c, 0xc8, 0x2f, 0x4a, 0x55, 0x30, 0xb4, 0xb4, 0x34, 0xd5, 0xb5, 0x34, | |
| 588 | 0x03, 0x00, 0x8b, 0x61, 0x0f, 0xa4, 0x52, 0x5a, 0x94, 0x12, | |
| 589 | }; | |
| 590 | ||
| 591 | var reader: std.Io.Reader = .fixed(&data); | |
| 592 | ||
| 593 | var decompress: Decompress = .init(&reader, .zlib, &.{}); | |
| 594 | var out: [128]u8 = undefined; | |
| 595 | ||
| 596 | { | |
| 597 | const n = try decompress.reader.readSliceShort(out[0..]); | |
| 598 | ||
| 599 | // Expected decompressed data | |
| 600 | try std.testing.expectEqual(46, n); | |
| 601 | try std.testing.expectEqualStrings("Copyright Willem van Schaik, Singapore 1995-96", out[0..n]); | |
| 602 | ||
| 603 | // Decompressor don't overshoot underlying reader. | |
| 604 | // It is leaving it at the end of compressed data chunk. | |
| 605 | try std.testing.expectEqual(data.len - 4, reader.seek); | |
| 606 | // TODO what was this testing, exactly? | |
| 607 | //try std.testing.expectEqual(0, decompress.unreadBytes()); | |
| 608 | } | |
| 609 | ||
| 610 | // 4 bytes after compressed chunk are available in reader. | |
| 611 | const n = try reader.readSliceShort(out[0..]); | |
| 612 | try std.testing.expectEqual(n, 4); | |
| 613 | try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]); | |
| 614 | } | |
| 615 | ||
| 616 | 153 | test { |
| 617 | 154 | _ = HuffmanEncoder; |
| 618 | 155 | _ = Compress; |
lib/std/compress/flate/BlockWriter.zig+20-1| ... | ... | @@ -31,7 +31,26 @@ fixed_literal_codes: [HuffmanEncoder.max_num_frequencies]HuffmanEncoder.Code, |
| 31 | 31 | fixed_distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code, |
| 32 | 32 | distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code, |
| 33 | 33 | |
| 34 | pub fn init(bw: *BlockWriter) void { | |
| 34 | pub fn init(output: *Writer) BlockWriter { | |
| 35 | return .{ | |
| 36 | .output = output, | |
| 37 | .codegen_freq = undefined, | |
| 38 | .literal_freq = undefined, | |
| 39 | .distance_freq = undefined, | |
| 40 | .codegen = undefined, | |
| 41 | .literal_encoding = undefined, | |
| 42 | .distance_encoding = undefined, | |
| 43 | .codegen_encoding = undefined, | |
| 44 | .fixed_literal_encoding = undefined, | |
| 45 | .fixed_distance_encoding = undefined, | |
| 46 | .huff_distance = undefined, | |
| 47 | .fixed_literal_codes = undefined, | |
| 48 | .fixed_distance_codes = undefined, | |
| 49 | .distance_codes = undefined, | |
| 50 | }; | |
| 51 | } | |
| 52 | ||
| 53 | pub fn initBuffers(bw: *BlockWriter) void { | |
| 35 | 54 | bw.fixed_literal_encoding = .fixedLiteralEncoder(&bw.fixed_literal_codes); |
| 36 | 55 | bw.fixed_distance_encoding = .fixedDistanceEncoder(&bw.fixed_distance_codes); |
| 37 | 56 | bw.huff_distance = .huffmanDistanceEncoder(&bw.distance_codes); |
lib/std/compress/flate/Compress.zig+20-271| ... | ... | @@ -122,22 +122,7 @@ pub const Options = struct { |
| 122 | 122 | |
| 123 | 123 | pub fn init(output: *Writer, buffer: []u8, options: Options) Compress { |
| 124 | 124 | return .{ |
| 125 | .block_writer = .{ | |
| 126 | .output = output, | |
| 127 | .codegen_freq = undefined, | |
| 128 | .literal_freq = undefined, | |
| 129 | .distance_freq = undefined, | |
| 130 | .codegen = undefined, | |
| 131 | .literal_encoding = undefined, | |
| 132 | .distance_encoding = undefined, | |
| 133 | .codegen_encoding = undefined, | |
| 134 | .fixed_literal_encoding = undefined, | |
| 135 | .fixed_distance_encoding = undefined, | |
| 136 | .huff_distance = undefined, | |
| 137 | .fixed_literal_codes = undefined, | |
| 138 | .fixed_distance_codes = undefined, | |
| 139 | .distance_codes = undefined, | |
| 140 | }, | |
| 125 | .block_writer = .init(output), | |
| 141 | 126 | .level = .get(options.level), |
| 142 | 127 | .hasher = .init(options.container), |
| 143 | 128 | .state = .header, |
| ... | ... | @@ -188,20 +173,21 @@ fn drain(me: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize |
| 188 | 173 | } |
| 189 | 174 | |
| 190 | 175 | const buffered = me.buffered(); |
| 191 | const min_lookahead = flate.match.min_length + flate.match.max_length; | |
| 176 | const min_lookahead = Token.min_length + Token.max_length; | |
| 192 | 177 | const history_plus_lookahead_len = flate.history_len + min_lookahead; |
| 193 | 178 | if (buffered.len < history_plus_lookahead_len) return 0; |
| 194 | 179 | const lookahead = buffered[flate.history_len..]; |
| 195 | 180 | |
| 196 | _ = lookahead; | |
| 197 | 181 | // TODO tokenize |
| 182 | _ = lookahead; | |
| 198 | 183 | //c.hasher.update(lookahead[0..n]); |
| 199 | 184 | @panic("TODO"); |
| 200 | 185 | } |
| 201 | 186 | |
| 202 | 187 | pub fn end(c: *Compress) !void { |
| 203 | 188 | try endUnflushed(c); |
| 204 | try c.output.flush(); | |
| 189 | const out = c.block_writer.output; | |
| 190 | try out.flush(); | |
| 205 | 191 | } |
| 206 | 192 | |
| 207 | 193 | pub fn endUnflushed(c: *Compress) !void { |
| ... | ... | @@ -227,7 +213,7 @@ pub fn endUnflushed(c: *Compress) !void { |
| 227 | 213 | // Checksum value of the uncompressed data (excluding any |
| 228 | 214 | // dictionary data) computed according to Adler-32 |
| 229 | 215 | // algorithm. |
| 230 | std.mem.writeInt(u32, try out.writableArray(4), zlib.final, .big); | |
| 216 | std.mem.writeInt(u32, try out.writableArray(4), zlib.adler, .big); | |
| 231 | 217 | }, |
| 232 | 218 | .raw => {}, |
| 233 | 219 | } |
| ... | ... | @@ -243,15 +229,16 @@ pub const Simple = struct { |
| 243 | 229 | |
| 244 | 230 | pub const Strategy = enum { huffman, store }; |
| 245 | 231 | |
| 246 | pub fn init(out: *Writer, buffer: []u8, container: Container) !Simple { | |
| 247 | const self: Simple = .{ | |
| 232 | pub fn init(output: *Writer, buffer: []u8, container: Container, strategy: Strategy) !Simple { | |
| 233 | const header = container.header(); | |
| 234 | try output.writeAll(header); | |
| 235 | return .{ | |
| 248 | 236 | .buffer = buffer, |
| 249 | 237 | .wp = 0, |
| 250 | .block_writer = .init(out), | |
| 238 | .block_writer = .init(output), | |
| 251 | 239 | .hasher = .init(container), |
| 240 | .strategy = strategy, | |
| 252 | 241 | }; |
| 253 | try container.writeHeader(self.out); | |
| 254 | return self; | |
| 255 | 242 | } |
| 256 | 243 | |
| 257 | 244 | pub fn flush(self: *Simple) !void { |
| ... | ... | @@ -263,7 +250,7 @@ pub const Simple = struct { |
| 263 | 250 | pub fn finish(self: *Simple) !void { |
| 264 | 251 | try self.flushBuffer(true); |
| 265 | 252 | try self.block_writer.flush(); |
| 266 | try self.hasher.container().writeFooter(&self.hasher, self.out); | |
| 253 | try self.hasher.container().writeFooter(&self.hasher, self.block_writer.output); | |
| 267 | 254 | } |
| 268 | 255 | |
| 269 | 256 | fn flushBuffer(self: *Simple, final: bool) !void { |
| ... | ... | @@ -300,7 +287,13 @@ test "generate a Huffman code from an array of frequencies" { |
| 300 | 287 | }; |
| 301 | 288 | |
| 302 | 289 | var codes: [19]HuffmanEncoder.Code = undefined; |
| 303 | var enc: HuffmanEncoder = .{ .codes = &codes }; | |
| 290 | var enc: HuffmanEncoder = .{ | |
| 291 | .codes = &codes, | |
| 292 | .freq_cache = undefined, | |
| 293 | .bit_count = undefined, | |
| 294 | .lns = undefined, | |
| 295 | .lfs = undefined, | |
| 296 | }; | |
| 304 | 297 | enc.generate(freqs[0..], 7); |
| 305 | 298 | |
| 306 | 299 | try testing.expectEqual(@as(u32, 141), enc.bitLength(freqs[0..])); |
| ... | ... | @@ -337,247 +330,3 @@ test "generate a Huffman code from an array of frequencies" { |
| 337 | 330 | try testing.expectEqual(@as(u16, 0x1f), enc.codes[7].code); |
| 338 | 331 | try testing.expectEqual(@as(u16, 0x3f), enc.codes[16].code); |
| 339 | 332 | } |
| 340 | ||
| 341 | test "tokenization" { | |
| 342 | const L = Token.initLiteral; | |
| 343 | const M = Token.initMatch; | |
| 344 | ||
| 345 | const cases = [_]struct { | |
| 346 | data: []const u8, | |
| 347 | tokens: []const Token, | |
| 348 | }{ | |
| 349 | .{ | |
| 350 | .data = "Blah blah blah blah blah!", | |
| 351 | .tokens = &[_]Token{ L('B'), L('l'), L('a'), L('h'), L(' '), L('b'), M(5, 18), L('!') }, | |
| 352 | }, | |
| 353 | .{ | |
| 354 | .data = "ABCDEABCD ABCDEABCD", | |
| 355 | .tokens = &[_]Token{ | |
| 356 | L('A'), L('B'), L('C'), L('D'), L('E'), L('A'), L('B'), L('C'), L('D'), L(' '), | |
| 357 | L('A'), M(10, 8), | |
| 358 | }, | |
| 359 | }, | |
| 360 | }; | |
| 361 | ||
| 362 | for (cases) |c| { | |
| 363 | inline for (Container.list) |container| { // for each wrapping | |
| 364 | ||
| 365 | var cw = std.Io.countingWriter(std.Io.null_writer); | |
| 366 | const cww = cw.writer(); | |
| 367 | var df = try Compress(container, @TypeOf(cww), TestTokenWriter).init(cww, .{}); | |
| 368 | ||
| 369 | _ = try df.write(c.data); | |
| 370 | try df.flush(); | |
| 371 | ||
| 372 | // df.token_writer.show(); | |
| 373 | try expect(df.block_writer.pos == c.tokens.len); // number of tokens written | |
| 374 | try testing.expectEqualSlices(Token, df.block_writer.get(), c.tokens); // tokens match | |
| 375 | ||
| 376 | try testing.expectEqual(container.headerSize(), cw.bytes_written); | |
| 377 | try df.finish(); | |
| 378 | try testing.expectEqual(container.size(), cw.bytes_written); | |
| 379 | } | |
| 380 | } | |
| 381 | } | |
| 382 | ||
| 383 | // Tests that tokens written are equal to expected token list. | |
| 384 | const TestTokenWriter = struct { | |
| 385 | const Self = @This(); | |
| 386 | ||
| 387 | pos: usize = 0, | |
| 388 | actual: [128]Token = undefined, | |
| 389 | ||
| 390 | pub fn init(_: anytype) Self { | |
| 391 | return .{}; | |
| 392 | } | |
| 393 | pub fn write(self: *Self, tokens: []const Token, _: bool, _: ?[]const u8) !void { | |
| 394 | for (tokens) |t| { | |
| 395 | self.actual[self.pos] = t; | |
| 396 | self.pos += 1; | |
| 397 | } | |
| 398 | } | |
| 399 | ||
| 400 | pub fn storedBlock(_: *Self, _: []const u8, _: bool) !void {} | |
| 401 | ||
| 402 | pub fn get(self: *Self) []Token { | |
| 403 | return self.actual[0..self.pos]; | |
| 404 | } | |
| 405 | ||
| 406 | pub fn show(self: *Self) void { | |
| 407 | std.debug.print("\n", .{}); | |
| 408 | for (self.get()) |t| { | |
| 409 | t.show(); | |
| 410 | } | |
| 411 | } | |
| 412 | ||
| 413 | pub fn flush(_: *Self) !void {} | |
| 414 | }; | |
| 415 | ||
| 416 | test "file tokenization" { | |
| 417 | const levels = [_]Level{ .level_4, .level_5, .level_6, .level_7, .level_8, .level_9 }; | |
| 418 | const cases = [_]struct { | |
| 419 | data: []const u8, // uncompressed content | |
| 420 | // expected number of tokens producet in deflate tokenization | |
| 421 | tokens_count: [levels.len]usize = .{0} ** levels.len, | |
| 422 | }{ | |
| 423 | .{ | |
| 424 | .data = @embedFile("testdata/rfc1951.txt"), | |
| 425 | .tokens_count = .{ 7675, 7672, 7599, 7594, 7598, 7599 }, | |
| 426 | }, | |
| 427 | ||
| 428 | .{ | |
| 429 | .data = @embedFile("testdata/block_writer/huffman-null-max.input"), | |
| 430 | .tokens_count = .{ 257, 257, 257, 257, 257, 257 }, | |
| 431 | }, | |
| 432 | .{ | |
| 433 | .data = @embedFile("testdata/block_writer/huffman-pi.input"), | |
| 434 | .tokens_count = .{ 2570, 2564, 2564, 2564, 2564, 2564 }, | |
| 435 | }, | |
| 436 | .{ | |
| 437 | .data = @embedFile("testdata/block_writer/huffman-text.input"), | |
| 438 | .tokens_count = .{ 235, 234, 234, 234, 234, 234 }, | |
| 439 | }, | |
| 440 | .{ | |
| 441 | .data = @embedFile("testdata/fuzz/roundtrip1.input"), | |
| 442 | .tokens_count = .{ 333, 331, 331, 331, 331, 331 }, | |
| 443 | }, | |
| 444 | .{ | |
| 445 | .data = @embedFile("testdata/fuzz/roundtrip2.input"), | |
| 446 | .tokens_count = .{ 334, 334, 334, 334, 334, 334 }, | |
| 447 | }, | |
| 448 | }; | |
| 449 | ||
| 450 | for (cases) |case| { // for each case | |
| 451 | const data = case.data; | |
| 452 | ||
| 453 | for (levels, 0..) |level, i| { // for each compression level | |
| 454 | var original: std.Io.Reader = .fixed(data); | |
| 455 | ||
| 456 | // buffer for decompressed data | |
| 457 | var al = std.ArrayList(u8).init(testing.allocator); | |
| 458 | defer al.deinit(); | |
| 459 | const writer = al.writer(); | |
| 460 | ||
| 461 | // create compressor | |
| 462 | const WriterType = @TypeOf(writer); | |
| 463 | const TokenWriter = TokenDecoder(@TypeOf(writer)); | |
| 464 | var cmp = try Compress(.raw, WriterType, TokenWriter).init(writer, .{ .level = level }); | |
| 465 | ||
| 466 | // Stream uncompressed `original` data to the compressor. It will | |
| 467 | // produce tokens list and pass that list to the TokenDecoder. This | |
| 468 | // TokenDecoder uses CircularBuffer from inflate to convert list of | |
| 469 | // tokens back to the uncompressed stream. | |
| 470 | try cmp.compress(original.reader()); | |
| 471 | try cmp.flush(); | |
| 472 | const expected_count = case.tokens_count[i]; | |
| 473 | const actual = cmp.block_writer.tokens_count; | |
| 474 | if (expected_count == 0) { | |
| 475 | std.debug.print("actual token count {d}\n", .{actual}); | |
| 476 | } else { | |
| 477 | try testing.expectEqual(expected_count, actual); | |
| 478 | } | |
| 479 | ||
| 480 | try testing.expectEqual(data.len, al.items.len); | |
| 481 | try testing.expectEqualSlices(u8, data, al.items); | |
| 482 | } | |
| 483 | } | |
| 484 | } | |
| 485 | ||
| 486 | const TokenDecoder = struct { | |
| 487 | output: *Writer, | |
| 488 | tokens_count: usize, | |
| 489 | ||
| 490 | pub fn init(output: *Writer) TokenDecoder { | |
| 491 | return .{ | |
| 492 | .output = output, | |
| 493 | .tokens_count = 0, | |
| 494 | }; | |
| 495 | } | |
| 496 | ||
| 497 | pub fn write(self: *TokenDecoder, tokens: []const Token, _: bool, _: ?[]const u8) !void { | |
| 498 | self.tokens_count += tokens.len; | |
| 499 | for (tokens) |t| { | |
| 500 | switch (t.kind) { | |
| 501 | .literal => self.hist.write(t.literal()), | |
| 502 | .match => try self.hist.writeMatch(t.length(), t.distance()), | |
| 503 | } | |
| 504 | if (self.hist.free() < 285) try self.flushWin(); | |
| 505 | } | |
| 506 | try self.flushWin(); | |
| 507 | } | |
| 508 | ||
| 509 | fn flushWin(self: *TokenDecoder) !void { | |
| 510 | while (true) { | |
| 511 | const buf = self.hist.read(); | |
| 512 | if (buf.len == 0) break; | |
| 513 | try self.output.writeAll(buf); | |
| 514 | } | |
| 515 | } | |
| 516 | }; | |
| 517 | ||
| 518 | test "store simple compressor" { | |
| 519 | if (true) return error.SkipZigTest; | |
| 520 | //const data = "Hello world!"; | |
| 521 | //const expected = [_]u8{ | |
| 522 | // 0x1, // block type 0, final bit set | |
| 523 | // 0xc, 0x0, // len = 12 | |
| 524 | // 0xf3, 0xff, // ~len | |
| 525 | // 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', // | |
| 526 | // //0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, | |
| 527 | //}; | |
| 528 | ||
| 529 | //var fbs: std.Io.Reader = .fixed(data); | |
| 530 | //var al = std.ArrayList(u8).init(testing.allocator); | |
| 531 | //defer al.deinit(); | |
| 532 | ||
| 533 | //var cmp = try store.compressor(.raw, al.writer()); | |
| 534 | //try cmp.compress(&fbs); | |
| 535 | //try cmp.finish(); | |
| 536 | //try testing.expectEqualSlices(u8, &expected, al.items); | |
| 537 | ||
| 538 | //fbs = .fixed(data); | |
| 539 | //try al.resize(0); | |
| 540 | ||
| 541 | //// huffman only compresoor will also emit store block for this small sample | |
| 542 | //var hc = try huffman.compressor(.raw, al.writer()); | |
| 543 | //try hc.compress(&fbs); | |
| 544 | //try hc.finish(); | |
| 545 | //try testing.expectEqualSlices(u8, &expected, al.items); | |
| 546 | } | |
| 547 | ||
| 548 | test "sliding window match" { | |
| 549 | const data = "Blah blah blah blah blah!"; | |
| 550 | var win: Writer = .{}; | |
| 551 | try expect(win.write(data) == data.len); | |
| 552 | try expect(win.wp == data.len); | |
| 553 | try expect(win.rp == 0); | |
| 554 | ||
| 555 | // length between l symbols | |
| 556 | try expect(win.match(1, 6, 0) == 18); | |
| 557 | try expect(win.match(1, 11, 0) == 13); | |
| 558 | try expect(win.match(1, 16, 0) == 8); | |
| 559 | try expect(win.match(1, 21, 0) == 0); | |
| 560 | ||
| 561 | // position 15 = "blah blah!" | |
| 562 | // position 20 = "blah!" | |
| 563 | try expect(win.match(15, 20, 0) == 4); | |
| 564 | try expect(win.match(15, 20, 3) == 4); | |
| 565 | try expect(win.match(15, 20, 4) == 0); | |
| 566 | } | |
| 567 | ||
| 568 | test "sliding window slide" { | |
| 569 | var win: Writer = .{}; | |
| 570 | win.wp = Writer.buffer_len - 11; | |
| 571 | win.rp = Writer.buffer_len - 111; | |
| 572 | win.buffer[win.rp] = 0xab; | |
| 573 | try expect(win.lookahead().len == 100); | |
| 574 | try expect(win.tokensBuffer().?.len == win.rp); | |
| 575 | ||
| 576 | const n = win.slide(); | |
| 577 | try expect(n == 32757); | |
| 578 | try expect(win.buffer[win.rp] == 0xab); | |
| 579 | try expect(win.rp == Writer.hist_len - 111); | |
| 580 | try expect(win.wp == Writer.hist_len - 11); | |
| 581 | try expect(win.lookahead().len == 100); | |
| 582 | try expect(win.tokensBuffer() == null); | |
| 583 | } |
lib/std/compress/flate/Decompress.zig+184-33| ... | ... | @@ -4,8 +4,8 @@ const Container = flate.Container; |
| 4 | 4 | const Token = @import("Token.zig"); |
| 5 | 5 | const testing = std.testing; |
| 6 | 6 | const Decompress = @This(); |
| 7 | const Writer = std.io.Writer; | |
| 8 | const Reader = std.io.Reader; | |
| 7 | const Writer = std.Io.Writer; | |
| 8 | const Reader = std.Io.Reader; | |
| 9 | 9 | |
| 10 | 10 | input: *Reader, |
| 11 | 11 | reader: Reader, |
| ... | ... | @@ -129,7 +129,7 @@ fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol { |
| 129 | 129 | return sym; |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | pub fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize { | |
| 132 | pub fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { | |
| 133 | 133 | const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); |
| 134 | 134 | return readInner(d, w, limit) catch |err| switch (err) { |
| 135 | 135 | error.EndOfStream => return error.EndOfStream, |
| ... | ... | @@ -143,7 +143,8 @@ pub fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!us |
| 143 | 143 | }; |
| 144 | 144 | } |
| 145 | 145 | |
| 146 | fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.StreamError)!usize { | |
| 146 | fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.StreamError)!usize { | |
| 147 | var remaining = @intFromEnum(limit); | |
| 147 | 148 | const in = d.input; |
| 148 | 149 | sw: switch (d.state) { |
| 149 | 150 | .protocol_header => switch (d.hasher.container()) { |
| ... | ... | @@ -182,15 +183,9 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.S |
| 182 | 183 | continue :sw .block_header; |
| 183 | 184 | }, |
| 184 | 185 | .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; | |
| 186 | const header = try in.takeArray(2); | |
| 187 | const cmf: packed struct(u8) { cm: u4, cinfo: u4 } = @bitCast(header[0]); | |
| 188 | if (cmf.cm != 8 or cmf.cinfo > 7) return error.BadZlibHeader; | |
| 194 | 189 | continue :sw .block_header; |
| 195 | 190 | }, |
| 196 | 191 | .raw => continue :sw .block_header, |
| ... | ... | @@ -219,7 +214,7 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.S |
| 219 | 214 | // lengths for code lengths |
| 220 | 215 | var cl_lens = [_]u4{0} ** 19; |
| 221 | 216 | for (0..hclen) |i| { |
| 222 | cl_lens[flate.huffman.codegen_order[i]] = try d.takeBits(u3); | |
| 217 | cl_lens[flate.HuffmanEncoder.codegen_order[i]] = try d.takeBits(u3); | |
| 223 | 218 | } |
| 224 | 219 | var cl_dec: CodegenDecoder = .{}; |
| 225 | 220 | try cl_dec.generate(&cl_lens); |
| ... | ... | @@ -259,52 +254,56 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.S |
| 259 | 254 | return n; |
| 260 | 255 | }, |
| 261 | 256 | .fixed_block => { |
| 262 | const start = w.count; | |
| 263 | while (@intFromEnum(limit) > w.count - start) { | |
| 257 | while (remaining > 0) { | |
| 264 | 258 | const code = try d.readFixedCode(); |
| 265 | 259 | switch (code) { |
| 266 | 0...255 => try w.writeBytePreserve(flate.history_len, @intCast(code)), | |
| 260 | 0...255 => { | |
| 261 | try w.writeBytePreserve(flate.history_len, @intCast(code)); | |
| 262 | remaining -= 1; | |
| 263 | }, | |
| 267 | 264 | 256 => { |
| 268 | 265 | d.state = if (d.final_block) .protocol_footer else .block_header; |
| 269 | return w.count - start; | |
| 266 | return @intFromEnum(limit) - remaining; | |
| 270 | 267 | }, |
| 271 | 268 | 257...285 => { |
| 272 | 269 | // Handles fixed block non literal (length) code. |
| 273 | 270 | // Length code is followed by 5 bits of distance code. |
| 274 | 271 | const length = try d.decodeLength(@intCast(code - 257)); |
| 275 | 272 | const distance = try d.decodeDistance(try d.takeBitsReverseBuffered(u5)); |
| 276 | try writeMatch(w, length, distance); | |
| 273 | remaining = try writeMatch(w, length, distance, remaining); | |
| 277 | 274 | }, |
| 278 | 275 | else => return error.InvalidCode, |
| 279 | 276 | } |
| 280 | 277 | } |
| 281 | 278 | d.state = .fixed_block; |
| 282 | return w.count - start; | |
| 279 | return @intFromEnum(limit) - remaining; | |
| 283 | 280 | }, |
| 284 | 281 | .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) { | |
| 282 | // In larger archives most blocks are usually dynamic, so | |
| 283 | // decompression performance depends on this logic. | |
| 284 | while (remaining > 0) { | |
| 289 | 285 | const sym = try d.decodeSymbol(&d.lit_dec); |
| 290 | 286 | |
| 291 | 287 | switch (sym.kind) { |
| 292 | .literal => try w.writeBytePreserve(flate.history_len, sym.symbol), | |
| 288 | .literal => { | |
| 289 | try w.writeBytePreserve(flate.history_len, sym.symbol); | |
| 290 | remaining -= 1; | |
| 291 | }, | |
| 293 | 292 | .match => { |
| 294 | 293 | // Decode match backreference <length, distance> |
| 295 | 294 | const length = try d.decodeLength(sym.symbol); |
| 296 | 295 | const dsm = try d.decodeSymbol(&d.dst_dec); |
| 297 | 296 | const distance = try d.decodeDistance(dsm.symbol); |
| 298 | try writeMatch(w, length, distance); | |
| 297 | remaining = try writeMatch(w, length, distance, remaining); | |
| 299 | 298 | }, |
| 300 | 299 | .end_of_block => { |
| 301 | 300 | d.state = if (d.final_block) .protocol_footer else .block_header; |
| 302 | return w.count - start; | |
| 301 | return @intFromEnum(limit) - remaining; | |
| 303 | 302 | }, |
| 304 | 303 | } |
| 305 | 304 | } |
| 306 | 305 | d.state = .dynamic_block; |
| 307 | return w.count - start; | |
| 306 | return @intFromEnum(limit) - remaining; | |
| 308 | 307 | }, |
| 309 | 308 | .protocol_footer => { |
| 310 | 309 | d.alignBitsToByte(); |
| ... | ... | @@ -314,7 +313,7 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.S |
| 314 | 313 | if (try in.takeInt(u32, .little) != gzip.count) return error.WrongGzipSize; |
| 315 | 314 | }, |
| 316 | 315 | .zlib => |*zlib| { |
| 317 | const chksum: u32 = @byteSwap(zlib.final()); | |
| 316 | const chksum: u32 = @byteSwap(zlib.adler); | |
| 318 | 317 | if (try in.takeInt(u32, .big) != chksum) return error.WrongZlibChecksum; |
| 319 | 318 | }, |
| 320 | 319 | .raw => {}, |
| ... | ... | @@ -328,10 +327,11 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.io.Limit) (Error || Reader.S |
| 328 | 327 | |
| 329 | 328 | /// Write match (back-reference to the same data slice) starting at `distance` |
| 330 | 329 | /// back from current write position, and `length` of bytes. |
| 331 | fn writeMatch(bw: *Writer, length: u16, distance: u16) !void { | |
| 332 | _ = bw; | |
| 330 | fn writeMatch(w: *Writer, length: u16, distance: u16, remaining: usize) !usize { | |
| 331 | _ = w; | |
| 333 | 332 | _ = length; |
| 334 | 333 | _ = distance; |
| 334 | _ = remaining; | |
| 335 | 335 | @panic("TODO"); |
| 336 | 336 | } |
| 337 | 337 | |
| ... | ... | @@ -622,7 +622,13 @@ test "init/find" { |
| 622 | 622 | test "encode/decode literals" { |
| 623 | 623 | var codes: [flate.HuffmanEncoder.max_num_frequencies]flate.HuffmanEncoder.Code = undefined; |
| 624 | 624 | for (1..286) |j| { // for all different number of codes |
| 625 | var enc: flate.HuffmanEncoder = .{ .codes = &codes }; | |
| 625 | var enc: flate.HuffmanEncoder = .{ | |
| 626 | .codes = &codes, | |
| 627 | .freq_cache = undefined, | |
| 628 | .bit_count = undefined, | |
| 629 | .lns = undefined, | |
| 630 | .lfs = undefined, | |
| 631 | }; | |
| 626 | 632 | // create frequencies |
| 627 | 633 | var freq = [_]u16{0} ** 286; |
| 628 | 634 | freq[256] = 1; // ensure we have end of block code |
| ... | ... | @@ -857,7 +863,7 @@ test "fuzzing tests" { |
| 857 | 863 | const r = &decompress.reader; |
| 858 | 864 | if (c.err) |expected_err| { |
| 859 | 865 | try testing.expectError(error.ReadFailed, r.streamRemaining(&aw.writer)); |
| 860 | try testing.expectError(expected_err, decompress.read_err.?); | |
| 866 | try testing.expectEqual(expected_err, decompress.read_err orelse return error.TestFailed); | |
| 861 | 867 | } else { |
| 862 | 868 | _ = try r.streamRemaining(&aw.writer); |
| 863 | 869 | try testing.expectEqualStrings(c.out, aw.getWritten()); |
| ... | ... | @@ -891,3 +897,148 @@ test "reading into empty buffer" { |
| 891 | 897 | var buf: [0]u8 = undefined; |
| 892 | 898 | try testing.expectEqual(0, try r.readVec(&.{&buf})); |
| 893 | 899 | } |
| 900 | ||
| 901 | test "don't read past deflate stream's end" { | |
| 902 | try testDecompress(.zlib, &[_]u8{ | |
| 903 | 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0xc0, 0x00, 0xc1, 0xff, | |
| 904 | 0xff, 0x43, 0x30, 0x03, 0x03, 0xc3, 0xff, 0xff, 0xff, 0x01, | |
| 905 | 0x83, 0x95, 0x0b, 0xf5, | |
| 906 | }, &[_]u8{ | |
| 907 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, | |
| 908 | 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, | |
| 909 | 0x00, 0x00, 0xff, 0xff, 0xff, | |
| 910 | }); | |
| 911 | } | |
| 912 | ||
| 913 | test "zlib header" { | |
| 914 | // Truncated header | |
| 915 | try testing.expectError( | |
| 916 | error.EndOfStream, | |
| 917 | testDecompress(.zlib, &[_]u8{0x78}, ""), | |
| 918 | ); | |
| 919 | // Wrong CM | |
| 920 | try testing.expectError( | |
| 921 | error.BadZlibHeader, | |
| 922 | testDecompress(.zlib, &[_]u8{ 0x79, 0x94 }, ""), | |
| 923 | ); | |
| 924 | // Wrong CINFO | |
| 925 | try testing.expectError( | |
| 926 | error.BadZlibHeader, | |
| 927 | testDecompress(.zlib, &[_]u8{ 0x88, 0x98 }, ""), | |
| 928 | ); | |
| 929 | // Wrong checksum | |
| 930 | try testing.expectError( | |
| 931 | error.WrongZlibChecksum, | |
| 932 | testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, ""), | |
| 933 | ); | |
| 934 | // Truncated checksum | |
| 935 | try testing.expectError( | |
| 936 | error.EndOfStream, | |
| 937 | testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""), | |
| 938 | ); | |
| 939 | } | |
| 940 | ||
| 941 | test "gzip header" { | |
| 942 | // Truncated header | |
| 943 | try testing.expectError( | |
| 944 | error.EndOfStream, | |
| 945 | testDecompress(.gzip, &[_]u8{ 0x1f, 0x8B }, undefined), | |
| 946 | ); | |
| 947 | // Wrong CM | |
| 948 | try testing.expectError( | |
| 949 | error.BadGzipHeader, | |
| 950 | testDecompress(.gzip, &[_]u8{ | |
| 951 | 0x1f, 0x8b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 952 | 0x00, 0x03, | |
| 953 | }, undefined), | |
| 954 | ); | |
| 955 | ||
| 956 | // Wrong checksum | |
| 957 | try testing.expectError( | |
| 958 | error.WrongGzipChecksum, | |
| 959 | testDecompress(.gzip, &[_]u8{ | |
| 960 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 961 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, | |
| 962 | 0x00, 0x00, 0x00, 0x00, | |
| 963 | }, undefined), | |
| 964 | ); | |
| 965 | // Truncated checksum | |
| 966 | try testing.expectError( | |
| 967 | error.EndOfStream, | |
| 968 | testDecompress(.gzip, &[_]u8{ | |
| 969 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 970 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, | |
| 971 | }, undefined), | |
| 972 | ); | |
| 973 | // Wrong initial size | |
| 974 | try testing.expectError( | |
| 975 | error.WrongGzipSize, | |
| 976 | testDecompress(.gzip, &[_]u8{ | |
| 977 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 978 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 979 | 0x00, 0x00, 0x00, 0x01, | |
| 980 | }, undefined), | |
| 981 | ); | |
| 982 | // Truncated initial size field | |
| 983 | try testing.expectError( | |
| 984 | error.EndOfStream, | |
| 985 | testDecompress(.gzip, &[_]u8{ | |
| 986 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 987 | 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 988 | 0x00, 0x00, 0x00, | |
| 989 | }, undefined), | |
| 990 | ); | |
| 991 | ||
| 992 | try testDecompress(.gzip, &[_]u8{ | |
| 993 | // GZIP header | |
| 994 | 0x1f, 0x8b, 0x08, 0x12, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, | |
| 995 | // header.FHCRC (should cover entire header) | |
| 996 | 0x99, 0xd6, | |
| 997 | // GZIP data | |
| 998 | 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 999 | }, ""); | |
| 1000 | } | |
| 1001 | ||
| 1002 | fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void { | |
| 1003 | var in: std.Io.Reader = .fixed(compressed); | |
| 1004 | var aw: std.Io.Writer.Allocating = .init(testing.allocator); | |
| 1005 | defer aw.deinit(); | |
| 1006 | ||
| 1007 | var decompress: Decompress = .init(&in, container, &.{}); | |
| 1008 | _ = try decompress.reader.streamRemaining(&aw.writer); | |
| 1009 | try testing.expectEqualSlices(u8, expected_plain, aw.getWritten()); | |
| 1010 | } | |
| 1011 | ||
| 1012 | test "zlib should not overshoot" { | |
| 1013 | // Compressed zlib data with extra 4 bytes at the end. | |
| 1014 | const data = [_]u8{ | |
| 1015 | 0x78, 0x9c, 0x73, 0xce, 0x2f, 0xa8, 0x2c, 0xca, 0x4c, 0xcf, 0x28, 0x51, 0x08, 0xcf, 0xcc, 0xc9, | |
| 1016 | 0x49, 0xcd, 0x55, 0x28, 0x4b, 0xcc, 0x53, 0x08, 0x4e, 0xce, 0x48, 0xcc, 0xcc, 0xd6, 0x51, 0x08, | |
| 1017 | 0xce, 0xcc, 0x4b, 0x4f, 0x2c, 0xc8, 0x2f, 0x4a, 0x55, 0x30, 0xb4, 0xb4, 0x34, 0xd5, 0xb5, 0x34, | |
| 1018 | 0x03, 0x00, 0x8b, 0x61, 0x0f, 0xa4, 0x52, 0x5a, 0x94, 0x12, | |
| 1019 | }; | |
| 1020 | ||
| 1021 | var reader: std.Io.Reader = .fixed(&data); | |
| 1022 | ||
| 1023 | var decompress: Decompress = .init(&reader, .zlib, &.{}); | |
| 1024 | var out: [128]u8 = undefined; | |
| 1025 | ||
| 1026 | { | |
| 1027 | const n = try decompress.reader.readSliceShort(out[0..]); | |
| 1028 | ||
| 1029 | // Expected decompressed data | |
| 1030 | try std.testing.expectEqual(46, n); | |
| 1031 | try std.testing.expectEqualStrings("Copyright Willem van Schaik, Singapore 1995-96", out[0..n]); | |
| 1032 | ||
| 1033 | // Decompressor don't overshoot underlying reader. | |
| 1034 | // It is leaving it at the end of compressed data chunk. | |
| 1035 | try std.testing.expectEqual(data.len - 4, reader.seek); | |
| 1036 | // TODO what was this testing, exactly? | |
| 1037 | //try std.testing.expectEqual(0, decompress.unreadBytes()); | |
| 1038 | } | |
| 1039 | ||
| 1040 | // 4 bytes after compressed chunk are available in reader. | |
| 1041 | const n = try reader.readSliceShort(out[0..]); | |
| 1042 | try std.testing.expectEqual(n, 4); | |
| 1043 | try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]); | |
| 1044 | } |
lib/std/compress/flate/HuffmanEncoder.zig+3-2| ... | ... | @@ -135,7 +135,7 @@ fn bitCounts(self: *HuffmanEncoder, list: []LiteralNode, max_bits_to_use: usize) |
| 135 | 135 | // of ancestors of the rightmost node at level i. |
| 136 | 136 | // leaf_counts[i][j] is the number of literals at the left |
| 137 | 137 | // of the level j ancestor. |
| 138 | var leaf_counts: [max_bits_limit][max_bits_limit]u32 = @splat(0); | |
| 138 | var leaf_counts: [max_bits_limit][max_bits_limit]u32 = @splat(@splat(0)); | |
| 139 | 139 | |
| 140 | 140 | { |
| 141 | 141 | var level = @as(u32, 1); |
| ... | ... | @@ -389,7 +389,8 @@ pub fn huffmanDistanceEncoder(codes: *[distance_code_count]Code) HuffmanEncoder |
| 389 | 389 | } |
| 390 | 390 | |
| 391 | 391 | test "generate a Huffman code for the fixed literal table specific to Deflate" { |
| 392 | const enc = fixedLiteralEncoder(); | |
| 392 | var codes: [max_num_frequencies]Code = undefined; | |
| 393 | const enc: HuffmanEncoder = .fixedLiteralEncoder(&codes); | |
| 393 | 394 | for (enc.codes) |c| { |
| 394 | 395 | switch (c.len) { |
| 395 | 396 | 7 => { |
lib/std/compress/flate/Lookup.zig+10-5| ... | ... | @@ -6,14 +6,19 @@ const std = @import("std"); |
| 6 | 6 | const testing = std.testing; |
| 7 | 7 | const expect = testing.expect; |
| 8 | 8 | const flate = @import("../flate.zig"); |
| 9 | const Token = @import("Token.zig"); | |
| 9 | 10 | |
| 10 | 11 | const Lookup = @This(); |
| 11 | 12 | |
| 12 | 13 | const prime4 = 0x9E3779B1; // 4 bytes prime number 2654435761 |
| 13 | 14 | const chain_len = 2 * flate.history_len; |
| 14 | 15 | |
| 16 | pub const bits = 15; | |
| 17 | pub const len = 1 << bits; | |
| 18 | pub const shift = 32 - bits; | |
| 19 | ||
| 15 | 20 | // Maps hash => first position |
| 16 | head: [flate.lookup.len]u16 = [_]u16{0} ** flate.lookup.len, | |
| 21 | head: [len]u16 = [_]u16{0} ** len, | |
| 17 | 22 | // Maps position => previous positions for the same hash value |
| 18 | 23 | chain: [chain_len]u16 = [_]u16{0} ** (chain_len), |
| 19 | 24 | |
| ... | ... | @@ -52,8 +57,8 @@ pub fn slide(self: *Lookup, n: u16) void { |
| 52 | 57 | |
| 53 | 58 | // Add `len` 4 bytes hashes from `data` into lookup. |
| 54 | 59 | // Position of the first byte is `pos`. |
| 55 | pub fn bulkAdd(self: *Lookup, data: []const u8, len: u16, pos: u16) void { | |
| 56 | if (len == 0 or data.len < flate.match.min_length) { | |
| 60 | pub fn bulkAdd(self: *Lookup, data: []const u8, length: u16, pos: u16) void { | |
| 61 | if (length == 0 or data.len < Token.min_length) { | |
| 57 | 62 | return; |
| 58 | 63 | } |
| 59 | 64 | var hb = |
| ... | ... | @@ -64,7 +69,7 @@ pub fn bulkAdd(self: *Lookup, data: []const u8, len: u16, pos: u16) void { |
| 64 | 69 | _ = self.set(hashu(hb), pos); |
| 65 | 70 | |
| 66 | 71 | var i = pos; |
| 67 | for (4..@min(len + 3, data.len)) |j| { | |
| 72 | for (4..@min(length + 3, data.len)) |j| { | |
| 68 | 73 | hb = (hb << 8) | @as(u32, data[j]); |
| 69 | 74 | i += 1; |
| 70 | 75 | _ = self.set(hashu(hb), i); |
| ... | ... | @@ -80,7 +85,7 @@ fn hash(b: *const [4]u8) u32 { |
| 80 | 85 | } |
| 81 | 86 | |
| 82 | 87 | fn hashu(v: u32) u32 { |
| 83 | return @intCast((v *% prime4) >> flate.lookup.shift); | |
| 88 | return @intCast((v *% prime4) >> shift); | |
| 84 | 89 | } |
| 85 | 90 | |
| 86 | 91 | test add { |
lib/std/compress/flate/Token.zig+13-7| ... | ... | @@ -6,7 +6,6 @@ const std = @import("std"); |
| 6 | 6 | const assert = std.debug.assert; |
| 7 | 7 | const print = std.debug.print; |
| 8 | 8 | const expect = std.testing.expect; |
| 9 | const match = std.compress.flate.match; | |
| 10 | 9 | |
| 11 | 10 | const Token = @This(); |
| 12 | 11 | |
| ... | ... | @@ -21,16 +20,23 @@ dist: u15 = 0, |
| 21 | 20 | len_lit: u8 = 0, |
| 22 | 21 | kind: Kind = .literal, |
| 23 | 22 | |
| 23 | pub const base_length = 3; // smallest match length per the RFC section 3.2.5 | |
| 24 | pub const min_length = 4; // min length used in this algorithm | |
| 25 | pub const max_length = 258; | |
| 26 | ||
| 27 | pub const min_distance = 1; | |
| 28 | pub const max_distance = std.compress.flate.history_len; | |
| 29 | ||
| 24 | 30 | pub fn literal(t: Token) u8 { |
| 25 | 31 | return t.len_lit; |
| 26 | 32 | } |
| 27 | 33 | |
| 28 | 34 | pub fn distance(t: Token) u16 { |
| 29 | return @as(u16, t.dist) + match.min_distance; | |
| 35 | return @as(u16, t.dist) + min_distance; | |
| 30 | 36 | } |
| 31 | 37 | |
| 32 | 38 | pub fn length(t: Token) u16 { |
| 33 | return @as(u16, t.len_lit) + match.base_length; | |
| 39 | return @as(u16, t.len_lit) + base_length; | |
| 34 | 40 | } |
| 35 | 41 | |
| 36 | 42 | pub fn initLiteral(lit: u8) Token { |
| ... | ... | @@ -40,12 +46,12 @@ pub fn initLiteral(lit: u8) Token { |
| 40 | 46 | // distance range 1 - 32768, stored in dist as 0 - 32767 (u15) |
| 41 | 47 | // length range 3 - 258, stored in len_lit as 0 - 255 (u8) |
| 42 | 48 | pub fn initMatch(dist: u16, len: u16) Token { |
| 43 | assert(len >= match.min_length and len <= match.max_length); | |
| 44 | assert(dist >= match.min_distance and dist <= match.max_distance); | |
| 49 | assert(len >= min_length and len <= max_length); | |
| 50 | assert(dist >= min_distance and dist <= max_distance); | |
| 45 | 51 | return .{ |
| 46 | 52 | .kind = .match, |
| 47 | .dist = @intCast(dist - match.min_distance), | |
| 48 | .len_lit = @intCast(len - match.base_length), | |
| 53 | .dist = @intCast(dist - min_distance), | |
| 54 | .len_lit = @intCast(len - base_length), | |
| 49 | 55 | }; |
| 50 | 56 | } |
| 51 | 57 |