| ... | @@ -9,20 +9,14 @@ pub const decompress = @import("zstandard/decompress.zig"); | ... | @@ -9,20 +9,14 @@ pub const decompress = @import("zstandard/decompress.zig"); |
| 9 | | 9 | |
| 10 | pub const DecompressStreamOptions = struct { | 10 | pub const DecompressStreamOptions = struct { |
| 11 | verify_checksum: bool = true, | 11 | verify_checksum: bool = true, |
| 12 | window_size_max: usize = default_window_size_max, | | |
| 13 | | | |
| 14 | pub const default_window_size_max = 1 << 23; // 8MiB default maximum window size | | |
| 15 | }; | 12 | }; |
| 16 | | 13 | |
| 17 | pub fn DecompressStream( | 14 | pub fn DecompressStream( |
| 18 | comptime ReaderType: type, | 15 | comptime ReaderType: type, |
| 19 | comptime options: DecompressStreamOptions, | | |
| 20 | ) type { | 16 | ) type { |
| 21 | return struct { | 17 | return struct { |
| 22 | const Self = @This(); | 18 | const Self = @This(); |
| 23 | | 19 | |
| 24 | pub const window_size_max = options.window_size_max; | | |
| 25 | | | |
| 26 | const table_size_max = types.compressed_block.table_size_max; | 20 | const table_size_max = types.compressed_block.table_size_max; |
| 27 | | 21 | |
| 28 | source: std.io.CountingReader(ReaderType), | 22 | source: std.io.CountingReader(ReaderType), |
| ... | @@ -35,11 +29,12 @@ pub fn DecompressStream( | ... | @@ -35,11 +29,12 @@ pub fn DecompressStream( |
| 35 | offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse, | 29 | offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse, |
| 36 | literals_buffer: [types.block_size_max]u8, | 30 | literals_buffer: [types.block_size_max]u8, |
| 37 | sequence_buffer: [types.block_size_max]u8, | 31 | sequence_buffer: [types.block_size_max]u8, |
| 38 | checksum: if (options.verify_checksum) ?u32 else void, | 32 | verify_checksum: bool, |
| | 33 | checksum: ?u32, |
| 39 | current_frame_decompressed_size: usize, | 34 | current_frame_decompressed_size: usize, |
| 40 | | 35 | |
| 41 | const WindowBuffer = struct { | 36 | const WindowBuffer = struct { |
| 42 | data: *[options.window_size_max]u8 = undefined, | 37 | data: []u8 = undefined, |
| 43 | read_index: usize = 0, | 38 | read_index: usize = 0, |
| 44 | write_index: usize = 0, | 39 | write_index: usize = 0, |
| 45 | }; | 40 | }; |
| ... | @@ -54,7 +49,7 @@ pub fn DecompressStream( | ... | @@ -54,7 +49,7 @@ pub fn DecompressStream( |
| 54 | | 49 | |
| 55 | pub const Reader = std.io.Reader(*Self, Error, read); | 50 | pub const Reader = std.io.Reader(*Self, Error, read); |
| 56 | | 51 | |
| 57 | pub fn init(source: ReaderType, window_buffer: *[options.window_size_max]u8) Self { | 52 | pub fn init(source: ReaderType, window_buffer: []u8, options: DecompressStreamOptions) Self { |
| 58 | return Self{ | 53 | return Self{ |
| 59 | .source = std.io.countingReader(source), | 54 | .source = std.io.countingReader(source), |
| 60 | .state = .NewFrame, | 55 | .state = .NewFrame, |
| ... | @@ -66,6 +61,7 @@ pub fn DecompressStream( | ... | @@ -66,6 +61,7 @@ pub fn DecompressStream( |
| 66 | .offset_fse_buffer = undefined, | 61 | .offset_fse_buffer = undefined, |
| 67 | .literals_buffer = undefined, | 62 | .literals_buffer = undefined, |
| 68 | .sequence_buffer = undefined, | 63 | .sequence_buffer = undefined, |
| | 64 | .verify_checksum = options.verify_checksum, |
| 69 | .checksum = undefined, | 65 | .checksum = undefined, |
| 70 | .current_frame_decompressed_size = undefined, | 66 | .current_frame_decompressed_size = undefined, |
| 71 | }; | 67 | }; |
| ... | @@ -81,8 +77,8 @@ pub fn DecompressStream( | ... | @@ -81,8 +77,8 @@ pub fn DecompressStream( |
| 81 | .zstandard => |header| { | 77 | .zstandard => |header| { |
| 82 | const frame_context = try decompress.FrameContext.init( | 78 | const frame_context = try decompress.FrameContext.init( |
| 83 | header, | 79 | header, |
| 84 | options.window_size_max, | 80 | self.buffer.data.len, |
| 85 | options.verify_checksum, | 81 | self.verify_checksum, |
| 86 | ); | 82 | ); |
| 87 | | 83 | |
| 88 | const decode_state = decompress.block.DecodeState.init( | 84 | const decode_state = decompress.block.DecodeState.init( |
| ... | @@ -94,7 +90,7 @@ pub fn DecompressStream( | ... | @@ -94,7 +90,7 @@ pub fn DecompressStream( |
| 94 | self.decode_state = decode_state; | 90 | self.decode_state = decode_state; |
| 95 | self.frame_context = frame_context; | 91 | self.frame_context = frame_context; |
| 96 | | 92 | |
| 97 | self.checksum = if (options.verify_checksum) null else {}; | 93 | self.checksum = null; |
| 98 | self.current_frame_decompressed_size = 0; | 94 | self.current_frame_decompressed_size = 0; |
| 99 | | 95 | |
| 100 | self.state = .InFrame; | 96 | self.state = .InFrame; |
| ... | @@ -176,7 +172,7 @@ pub fn DecompressStream( | ... | @@ -176,7 +172,7 @@ pub fn DecompressStream( |
| 176 | if (self.frame_context.has_checksum) { | 172 | if (self.frame_context.has_checksum) { |
| 177 | const checksum = source_reader.readInt(u32, .little) catch | 173 | const checksum = source_reader.readInt(u32, .little) catch |
| 178 | return error.MalformedFrame; | 174 | return error.MalformedFrame; |
| 179 | if (comptime options.verify_checksum) { | 175 | if (self.verify_checksum) { |
| 180 | if (self.frame_context.hasher_opt) |*hasher| { | 176 | if (self.frame_context.hasher_opt) |*hasher| { |
| 181 | if (checksum != decompress.computeChecksum(hasher)) | 177 | if (checksum != decompress.computeChecksum(hasher)) |
| 182 | return error.ChecksumFailure; | 178 | return error.ChecksumFailure; |
| ... | @@ -213,15 +209,18 @@ pub fn decompressStreamOptions( | ... | @@ -213,15 +209,18 @@ pub fn decompressStreamOptions( |
| 213 | | 209 | |
| 214 | pub fn decompressStream( | 210 | pub fn decompressStream( |
| 215 | reader: anytype, | 211 | reader: anytype, |
| 216 | window_buffer: *[DecompressStreamOptions.default_window_size_max]u8, | 212 | window_buffer: []u8, |
| 217 | ) DecompressStream(@TypeOf(reader), .{}) { | 213 | options: DecompressStreamOptions, |
| 218 | return DecompressStream(@TypeOf(reader), .{}).init(reader, window_buffer); | 214 | ) DecompressStream(@TypeOf(reader)) { |
| | 215 | return DecompressStream(@TypeOf(reader)).init(reader, window_buffer, options); |
| 219 | } | 216 | } |
| 220 | | 217 | |
| 221 | fn testDecompress(data: []const u8) ![]u8 { | 218 | fn testDecompress(data: []const u8) ![]u8 { |
| 222 | var window_buffer: [DecompressStreamOptions.default_window_size_max]u8 = undefined; | 219 | const window_buffer = try std.testing.allocator.alloc(u8, 1 << 23); |
| | 220 | defer std.testing.allocator.free(window_buffer); |
| | 221 | |
| 223 | var in_stream = std.io.fixedBufferStream(data); | 222 | var in_stream = std.io.fixedBufferStream(data); |
| 224 | var zstd_stream = decompressStream(in_stream.reader(), &window_buffer); | 223 | var zstd_stream = decompressStream(in_stream.reader(), window_buffer, .{}); |
| 225 | const result = zstd_stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); | 224 | const result = zstd_stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); |
| 226 | return result; | 225 | return result; |
| 227 | } | 226 | } |
| ... | @@ -251,7 +250,7 @@ test "zstandard decompression" { | ... | @@ -251,7 +250,7 @@ test "zstandard decompression" { |
| 251 | | 250 | |
| 252 | test "zstandard streaming decompression" { | 251 | test "zstandard streaming decompression" { |
| 253 | // default stack size for wasm32 is too low for DecompressStream - slightly | 252 | // default stack size for wasm32 is too low for DecompressStream - slightly |
| 254 | // over 9MiB stack space is needed via the --stack CLI flag | 253 | // over 1MiB stack space is needed via the --stack CLI flag |
| 255 | if (@import("builtin").target.cpu.arch == .wasm32) return error.SkipZigTest; | 254 | if (@import("builtin").target.cpu.arch == .wasm32) return error.SkipZigTest; |
| 256 | | 255 | |
| 257 | const uncompressed = @embedFile("testdata/rfc8478.txt"); | 256 | const uncompressed = @embedFile("testdata/rfc8478.txt"); |
| ... | @@ -279,9 +278,11 @@ fn expectEqualDecoded(expected: []const u8, input: []const u8) !void { | ... | @@ -279,9 +278,11 @@ fn expectEqualDecoded(expected: []const u8, input: []const u8) !void { |
| 279 | } | 278 | } |
| 280 | | 279 | |
| 281 | fn expectEqualDecodedStreaming(expected: []const u8, input: []const u8) !void { | 280 | fn expectEqualDecodedStreaming(expected: []const u8, input: []const u8) !void { |
| 282 | var window_buffer: [DecompressStreamOptions.default_window_size_max]u8 = undefined; | 281 | const window_buffer = try std.testing.allocator.alloc(u8, 1 << 23); |
| | 282 | defer std.testing.allocator.free(window_buffer); |
| | 283 | |
| 283 | var in_stream = std.io.fixedBufferStream(input); | 284 | var in_stream = std.io.fixedBufferStream(input); |
| 284 | var stream = decompressStream(in_stream.reader(), &window_buffer); | 285 | var stream = decompressStream(in_stream.reader(), window_buffer, .{}); |
| 285 | | 286 | |
| 286 | const result = try stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); | 287 | const result = try stream.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); |
| 287 | defer std.testing.allocator.free(result); | 288 | defer std.testing.allocator.free(result); |
| ... | @@ -307,7 +308,7 @@ test "zero sized block" { | ... | @@ -307,7 +308,7 @@ test "zero sized block" { |
| 307 | | 308 | |
| 308 | test "zero sized block streaming" { | 309 | test "zero sized block streaming" { |
| 309 | // default stack size for wasm32 is too low for DecompressStream - slightly | 310 | // default stack size for wasm32 is too low for DecompressStream - slightly |
| 310 | // over 9MiB stack space is needed via the --stack CLI flag | 311 | // over 1MiB stack space is needed via the --stack CLI flag |
| 311 | if (@import("builtin").target.cpu.arch == .wasm32) return error.SkipZigTest; | 312 | if (@import("builtin").target.cpu.arch == .wasm32) return error.SkipZigTest; |
| 312 | | 313 | |
| 313 | const input_raw = | 314 | const input_raw = |