| ... | @@ -37,10 +37,16 @@ const State = union(enum) { | ... | @@ -37,10 +37,16 @@ const State = union(enum) { |
| 37 | stored_block: u16, | 37 | stored_block: u16, |
| 38 | fixed_block, | 38 | fixed_block, |
| 39 | fixed_block_literal: u8, | 39 | fixed_block_literal: u8, |
| 40 | fixed_block_match: u16, | 40 | fixed_block_match: struct { |
| | 41 | distance: u16, |
| | 42 | length: u16, |
| | 43 | }, |
| 41 | dynamic_block, | 44 | dynamic_block, |
| 42 | dynamic_block_literal: u8, | 45 | dynamic_block_literal: u8, |
| 43 | dynamic_block_match: u16, | 46 | dynamic_block_match: struct { |
| | 47 | distance: u16, |
| | 48 | length: u16, |
| | 49 | }, |
| 44 | protocol_footer, | 50 | protocol_footer, |
| 45 | end, | 51 | end, |
| 46 | }; | 52 | }; |
| ... | @@ -398,7 +404,8 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -398,7 +404,8 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 398 | | 404 | |
| 399 | // Match | 405 | // Match |
| 400 | const length = try d.decodeLength(@intCast(sym - 257)); | 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 | const byte: u8 = @intCast(sym); | 411 | const byte: u8 = @intCast(sym); |
| ... | @@ -417,16 +424,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -417,16 +424,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 417 | try w.writeBytePreserve(flate.history_len, symbol); | 424 | try w.writeBytePreserve(flate.history_len, symbol); |
| 418 | continue :sw .fixed_block; | 425 | continue :sw .fixed_block; |
| 419 | }, | 426 | }, |
| 420 | .fixed_block_match => |length| { | 427 | .fixed_block_match => |match| { |
| 421 | if (remaining >= length) { | 428 | if (remaining >= match.length) { |
| 422 | @branchHint(.likely); | 429 | @branchHint(.likely); |
| 423 | const distance = try d.decodeDistance(@bitReverse(try d.takeIntBits(u5))); | 430 | try writeMatch(w, match.length, match.distance); |
| 424 | try writeMatch(w, length, distance); | 431 | remaining -= match.length; |
| 425 | remaining -= length; | | |
| 426 | continue :sw .fixed_block; | 432 | continue :sw .fixed_block; |
| 427 | } else { | 433 | } else { |
| 428 | d.state = .{ .fixed_block_match = length }; | 434 | if (remaining > 0) { |
| 429 | return @backingInt(limit) - remaining; | 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 | // In larger archives most blocks are usually dynamic, so | 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,7 +459,9 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 447 | | 459 | |
| 448 | // Match | 460 | // Match |
| 449 | const length = try d.decodeLength(@intCast(sym - 257)); | 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 | const byte: u8 = @intCast(sym); | 467 | const byte: u8 = @intCast(sym); |
| ... | @@ -466,17 +480,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -466,17 +480,21 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 466 | try w.writeBytePreserve(flate.history_len, symbol); | 480 | try w.writeBytePreserve(flate.history_len, symbol); |
| 467 | continue :sw .dynamic_block; | 481 | continue :sw .dynamic_block; |
| 468 | }, | 482 | }, |
| 469 | .dynamic_block_match => |length| { | 483 | .dynamic_block_match => |match| { |
| 470 | if (remaining >= length) { | 484 | if (remaining >= match.length) { |
| 471 | @branchHint(.likely); | 485 | @branchHint(.likely); |
| 472 | remaining -= length; | 486 | remaining -= match.length; |
| 473 | const dsm = try d.decodeSymbol(&d.dst_dec); | 487 | try writeMatch(w, match.length, match.distance); |
| 474 | const distance = try d.decodeDistance(@intCast(dsm)); | | |
| 475 | try writeMatch(w, length, distance); | | |
| 476 | continue :sw .dynamic_block; | 488 | continue :sw .dynamic_block; |
| 477 | } else { | 489 | } else { |
| 478 | d.state = .{ .dynamic_block_match = length }; | 490 | if (remaining > 0) { |
| 479 | return @backingInt(limit) - remaining; | 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 | .protocol_footer => { | 500 | .protocol_footer => { |
| ... | @@ -500,9 +518,11 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader | ... | @@ -500,9 +518,11 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader |
| 500 | | 518 | |
| 501 | /// Write match (back-reference to the same data slice) starting at `distance` | 519 | /// Write match (back-reference to the same data slice) starting at `distance` |
| 502 | /// back from current write position, and `length` of bytes. | 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 | fn writeMatch(w: *Writer, length: u16, distance: u16) !void { | 523 | fn writeMatch(w: *Writer, length: u16, distance: u16) !void { |
| 504 | if (w.end < distance) return error.InvalidMatch; | 524 | if (w.end < distance) return error.InvalidMatch; |
| 505 | assert(length >= token.min_length); | 525 | assert(length > 0); |
| 506 | assert(length <= token.max_length); | 526 | assert(length <= token.max_length); |
| 507 | assert(distance >= token.min_distance); | 527 | assert(distance >= token.min_distance); |
| 508 | assert(distance <= token.max_distance); | 528 | assert(distance <= token.max_distance); |
| ... | @@ -1171,12 +1191,32 @@ fn testFailure(container: Container, in: []const u8, expected_err: anyerror) !vo | ... | @@ -1171,12 +1191,32 @@ fn testFailure(container: Container, in: []const u8, expected_err: anyerror) !vo |
| 1171 | } | 1191 | } |
| 1172 | | 1192 | |
| 1173 | fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void { | 1193 | fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void { |
| 1174 | var in: std.Io.Reader = .fixed(compressed); | | |
| 1175 | var aw: std.Io.Writer.Allocating = .init(testing.allocator); | 1194 | var aw: std.Io.Writer.Allocating = .init(testing.allocator); |
| 1176 | defer aw.deinit(); | 1195 | defer aw.deinit(); |
| 1177 | | 1196 | |
| 1178 | var decompress: Decompress = .init(&in, container, &.{}); | 1197 | // Decompress once using the normal methods. |
| 1179 | const decompressed_len = try decompress.reader.streamRemaining(&aw.writer); | 1198 | { |
| 1180 | try testing.expectEqual(expected_plain.len, decompressed_len); | 1199 | var in: std.Io.Reader = .fixed(compressed); |
| 1181 | try testing.expectEqualSlices(u8, expected_plain, aw.written()); | 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 | } |