| ... | ... | @@ -37,10 +37,16 @@ const State = union(enum) { |
| 37 | 37 | stored_block: u16, |
| 38 | 38 | fixed_block, |
| 39 | 39 | fixed_block_literal: u8, |
| 40 | | fixed_block_match: u16, |
| 40 | fixed_block_match: struct { |
| 41 | distance: u16, |
| 42 | length: u16, |
| 43 | }, |
| 41 | 44 | dynamic_block, |
| 42 | 45 | dynamic_block_literal: u8, |
| 43 | | dynamic_block_match: u16, |
| 46 | dynamic_block_match: struct { |
| 47 | distance: u16, |
| 48 | length: u16, |
| 49 | }, |
| 44 | 50 | protocol_footer, |
| 45 | 51 | end, |
| 46 | 52 | }; |
| ... | ... | @@ -398,7 +404,8 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 398 | 404 | |
| 399 | 405 | // Match |
| 400 | 406 | const length = try d.decodeLength(@intCast(sym - 257)); |
| 401 | | continue :sw .{ .fixed_block_match = length }; |
| 407 | const distance = try d.decodeDistance(@bitReverse(try d.takeIntBits(u5))); |
| 408 | continue :sw .{ .fixed_block_match = .{ .length = length, .distance = distance } }; |
| 402 | 409 | } |
| 403 | 410 | |
| 404 | 411 | const byte: u8 = @intCast(sym); |
| ... | ... | @@ -417,16 +424,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 417 | 424 | try w.writeBytePreserve(flate.history_len, symbol); |
| 418 | 425 | continue :sw .fixed_block; |
| 419 | 426 | }, |
| 420 | | .fixed_block_match => |length| { |
| 421 | | if (remaining >= length) { |
| 427 | .fixed_block_match => |match| { |
| 428 | if (remaining >= match.length) { |
| 422 | 429 | @branchHint(.likely); |
| 423 | | const distance = try d.decodeDistance(@bitReverse(try d.takeIntBits(u5))); |
| 424 | | try writeMatch(w, length, distance); |
| 425 | | remaining -= length; |
| 430 | try writeMatch(w, match.length, match.distance); |
| 431 | remaining -= match.length; |
| 426 | 432 | continue :sw .fixed_block; |
| 427 | 433 | } else { |
| 428 | | d.state = .{ .fixed_block_match = length }; |
| 429 | | return @backingInt(limit) - remaining; |
| 434 | if (remaining > 0) { |
| 435 | try writeMatch(w, @intCast(remaining), match.distance); |
| 436 | } |
| 437 | d.state = .{ .fixed_block_match = .{ |
| 438 | .distance = match.distance, |
| 439 | .length = match.length - @as(u16, @intCast(remaining)), |
| 440 | } }; |
| 441 | return @backingInt(limit); |
| 430 | 442 | } |
| 431 | 443 | }, |
| 432 | 444 | // In larger archives most blocks are usually dynamic, so |
| ... | ... | @@ -447,7 +459,9 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 447 | 459 | |
| 448 | 460 | // Match |
| 449 | 461 | const length = try d.decodeLength(@intCast(sym - 257)); |
| 450 | | continue :sw .{ .dynamic_block_match = length }; |
| 462 | const dsm = try d.decodeSymbol(&d.dst_dec); |
| 463 | const distance = try d.decodeDistance(@intCast(dsm)); |
| 464 | continue :sw .{ .dynamic_block_match = .{ .length = length, .distance = distance } }; |
| 451 | 465 | } |
| 452 | 466 | |
| 453 | 467 | const byte: u8 = @intCast(sym); |
| ... | ... | @@ -466,17 +480,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 466 | 480 | try w.writeBytePreserve(flate.history_len, symbol); |
| 467 | 481 | continue :sw .dynamic_block; |
| 468 | 482 | }, |
| 469 | | .dynamic_block_match => |length| { |
| 470 | | if (remaining >= length) { |
| 483 | .dynamic_block_match => |match| { |
| 484 | if (remaining >= match.length) { |
| 471 | 485 | @branchHint(.likely); |
| 472 | | remaining -= length; |
| 473 | | const dsm = try d.decodeSymbol(&d.dst_dec); |
| 474 | | const distance = try d.decodeDistance(@intCast(dsm)); |
| 475 | | try writeMatch(w, length, distance); |
| 486 | remaining -= match.length; |
| 487 | try writeMatch(w, match.length, match.distance); |
| 476 | 488 | continue :sw .dynamic_block; |
| 477 | 489 | } else { |
| 478 | | d.state = .{ .dynamic_block_match = length }; |
| 479 | | return @backingInt(limit) - remaining; |
| 490 | if (remaining > 0) { |
| 491 | try writeMatch(w, @intCast(remaining), match.distance); |
| 492 | } |
| 493 | d.state = .{ .dynamic_block_match = .{ |
| 494 | .distance = match.distance, |
| 495 | .length = match.length - @as(u16, @intCast(remaining)), |
| 496 | } }; |
| 497 | return @backingInt(limit); |
| 480 | 498 | } |
| 481 | 499 | }, |
| 482 | 500 | .protocol_footer => { |
| ... | ... | @@ -500,9 +518,11 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 500 | 518 | |
| 501 | 519 | /// Write match (back-reference to the same data slice) starting at `distance` |
| 502 | 520 | /// back from current write position, and `length` of bytes. |
| 521 | /// `length` may be less than the minimum match length to allow for writing |
| 522 | /// partial matches, but must be greater than zero. |
| 503 | 523 | fn writeMatch(w: *Writer, length: u16, distance: u16) !void { |
| 504 | 524 | if (w.end < distance) return error.InvalidMatch; |
| 505 | | assert(length >= token.min_length); |
| 525 | assert(length > 0); |
| 506 | 526 | assert(length <= token.max_length); |
| 507 | 527 | assert(distance >= token.min_distance); |
| 508 | 528 | assert(distance <= token.max_distance); |
| ... | ... | @@ -1171,12 +1191,32 @@ fn testFailure(container: Container, in: []const u8, expected_err: anyerror) !vo |
| 1171 | 1191 | } |
| 1172 | 1192 | |
| 1173 | 1193 | fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void { |
| 1174 | | var in: std.Io.Reader = .fixed(compressed); |
| 1175 | 1194 | var aw: std.Io.Writer.Allocating = .init(testing.allocator); |
| 1176 | 1195 | defer aw.deinit(); |
| 1177 | 1196 | |
| 1178 | | var decompress: Decompress = .init(&in, container, &.{}); |
| 1179 | | const decompressed_len = try decompress.reader.streamRemaining(&aw.writer); |
| 1180 | | try testing.expectEqual(expected_plain.len, decompressed_len); |
| 1181 | | try testing.expectEqualSlices(u8, expected_plain, aw.written()); |
| 1197 | // Decompress once using the normal methods. |
| 1198 | { |
| 1199 | var in: std.Io.Reader = .fixed(compressed); |
| 1200 | var decompress: Decompress = .init(&in, container, &.{}); |
| 1201 | const decompressed_len = try decompress.reader.streamRemaining(&aw.writer); |
| 1202 | try testing.expectEqual(expected_plain.len, decompressed_len); |
| 1203 | try testing.expectEqualSlices(u8, expected_plain, aw.written()); |
| 1204 | } |
| 1205 | |
| 1206 | // Decompress again by streaming one byte at a time to check that there aren't |
| 1207 | // any problems with things like writing partial matches, etc. |
| 1208 | aw.clearRetainingCapacity(); |
| 1209 | { |
| 1210 | var in: std.Io.Reader = .fixed(compressed); |
| 1211 | var decompress: Decompress = .init(&in, container, &.{}); |
| 1212 | var decompressed_len: usize = 0; |
| 1213 | while (true) { |
| 1214 | decompressed_len += decompress.reader.stream(&aw.writer, .limited(1)) catch |err| switch (err) { |
| 1215 | error.EndOfStream => break, |
| 1216 | else => |e| return e, |
| 1217 | }; |
| 1218 | } |
| 1219 | try testing.expectEqual(expected_plain.len, decompressed_len); |
| 1220 | try testing.expectEqualSlices(u8, expected_plain, aw.written()); |
| 1221 | } |
| 1182 | 1222 | } |