| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | // | 1 | // |
| 2 | // Decompressor for ZLIB data streams (RFC1950) | 2 | // Compressor/Decompressor for ZLIB data streams (RFC1950) |
| 3 | | 3 | |
| 4 | const std = @import("std"); | 4 | const std = @import("std"); |
| 5 | const io = std.io; | 5 | const io = std.io; |
| ... | @@ -8,7 +8,7 @@ const testing = std.testing; | ... | @@ -8,7 +8,7 @@ const testing = std.testing; |
| 8 | const mem = std.mem; | 8 | const mem = std.mem; |
| 9 | const deflate = std.compress.deflate; | 9 | const deflate = std.compress.deflate; |
| 10 | | 10 | |
| 11 | pub fn ZlibStream(comptime ReaderType: type) type { | 11 | pub fn ZlibStreamReader(comptime ReaderType: type) type { |
| 12 | return struct { | 12 | return struct { |
| 13 | const Self = @This(); | 13 | const Self = @This(); |
| 14 | | 14 | |
| ... | @@ -84,14 +84,99 @@ pub fn ZlibStream(comptime ReaderType: type) type { | ... | @@ -84,14 +84,99 @@ pub fn ZlibStream(comptime ReaderType: type) type { |
| 84 | }; | 84 | }; |
| 85 | } | 85 | } |
| 86 | | 86 | |
| 87 | pub fn zlibStream(allocator: mem.Allocator, reader: anytype) !ZlibStream(@TypeOf(reader)) { | 87 | pub fn zlibStreamReader(allocator: mem.Allocator, reader: anytype) !ZlibStreamReader(@TypeOf(reader)) { |
| 88 | return ZlibStream(@TypeOf(reader)).init(allocator, reader); | 88 | return ZlibStreamReader(@TypeOf(reader)).init(allocator, reader); |
| 89 | } | 89 | } |
| 90 | | 90 | |
| | 91 | pub const CompressionLevel = enum(u2) { |
| | 92 | no_compression = 0, |
| | 93 | fastest = 1, |
| | 94 | default = 2, |
| | 95 | maximum = 3, |
| | 96 | }; |
| | 97 | |
| | 98 | pub const CompressionOptions = struct { |
| | 99 | level: CompressionLevel = .default, |
| | 100 | }; |
| | 101 | |
| | 102 | pub fn ZlibStreamWriter(comptime WriterType: type) type { |
| | 103 | return struct { |
| | 104 | const Self = @This(); |
| | 105 | |
| | 106 | const Error = WriterType.Error || |
| | 107 | deflate.Compressor(WriterType).Error; |
| | 108 | pub const Writer = io.Writer(*Self, Error, write); |
| | 109 | |
| | 110 | allocator: mem.Allocator, |
| | 111 | deflator: deflate.Compressor(WriterType), |
| | 112 | in_writer: WriterType, |
| | 113 | hasher: std.hash.Adler32, |
| | 114 | |
| | 115 | fn init(allocator: mem.Allocator, dest: WriterType, options: CompressionOptions) !Self { |
| | 116 | // Zlib header format is specified in RFC1950 |
| | 117 | const CM: u4 = 8; // DEFLATE |
| | 118 | const CINFO: u4 = 7; // 32K window |
| | 119 | const CMF: u8 = (@as(u8, CINFO) << 4) | CM; |
| | 120 | |
| | 121 | const FLEVEL: u2 = @enumToInt(options.level); |
| | 122 | const FDICT: u1 = 0; // No preset dictionary support |
| | 123 | const FLG_temp = (@as(u8, FLEVEL) << 6) | (@as(u8, FDICT) << 5); |
| | 124 | const FCHECK: u5 = 31 - ((@as(u16, CMF) * 256 + FLG_temp) % 31); |
| | 125 | const FLG = FLG_temp | FCHECK; |
| | 126 | |
| | 127 | const compression_level: deflate.Compression = switch (options.level) { |
| | 128 | .no_compression => .no_compression, |
| | 129 | .fastest => .best_speed, |
| | 130 | .default => .default_compression, |
| | 131 | .maximum => .best_compression, |
| | 132 | }; |
| | 133 | |
| | 134 | try dest.writeAll(&.{ CMF, FLG }); |
| | 135 | |
| | 136 | return Self{ |
| | 137 | .allocator = allocator, |
| | 138 | .deflator = try deflate.compressor(allocator, dest, .{ .level = compression_level }), |
| | 139 | .in_writer = dest, |
| | 140 | .hasher = std.hash.Adler32.init(), |
| | 141 | }; |
| | 142 | } |
| | 143 | |
| | 144 | pub fn write(self: *Self, bytes: []const u8) Error!usize { |
| | 145 | if (bytes.len == 0) { |
| | 146 | return 0; |
| | 147 | } |
| | 148 | |
| | 149 | const w = try self.deflator.write(bytes); |
| | 150 | |
| | 151 | self.hasher.update(bytes[0..w]); |
| | 152 | return w; |
| | 153 | } |
| | 154 | |
| | 155 | pub fn writer(self: *Self) Writer { |
| | 156 | return .{ .context = self }; |
| | 157 | } |
| | 158 | |
| | 159 | pub fn deinit(self: *Self) void { |
| | 160 | self.deflator.deinit(); |
| | 161 | } |
| | 162 | |
| | 163 | pub fn close(self: *Self) !void { |
| | 164 | const hash = self.hasher.final(); |
| | 165 | try self.deflator.close(); |
| | 166 | try self.in_writer.writeIntBig(u32, hash); |
| | 167 | } |
| | 168 | }; |
| | 169 | } |
| | 170 | |
| | 171 | pub fn zlibStreamWriter(allocator: mem.Allocator, writer: anytype, options: CompressionOptions) !ZlibStreamWriter(@TypeOf(writer)) { |
| | 172 | return ZlibStreamWriter(@TypeOf(writer)).init(allocator, writer, options); |
| | 173 | } |
| | 174 | |
| | 175 | |
| 91 | fn testReader(data: []const u8, expected: []const u8) !void { | 176 | fn testReader(data: []const u8, expected: []const u8) !void { |
| 92 | var in_stream = io.fixedBufferStream(data); | 177 | var in_stream = io.fixedBufferStream(data); |
| 93 | | 178 | |
| 94 | var zlib_stream = try zlibStream(testing.allocator, in_stream.reader()); | 179 | var zlib_stream = try zlibStreamReader(testing.allocator, in_stream.reader()); |
| 95 | defer zlib_stream.deinit(); | 180 | defer zlib_stream.deinit(); |
| 96 | | 181 | |
| 97 | // Read and decompress the whole file | 182 | // Read and decompress the whole file |
| ... | @@ -170,3 +255,19 @@ test "sanity checks" { | ... | @@ -170,3 +255,19 @@ test "sanity checks" { |
| 170 | testReader(&[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""), | 255 | testReader(&[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""), |
| 171 | ); | 256 | ); |
| 172 | } | 257 | } |
| | 258 | |
| | 259 | test "compress data" { |
| | 260 | const allocator = testing.allocator; |
| | 261 | const rfc1951_txt = @embedFile("testdata/rfc1951.txt"); |
| | 262 | |
| | 263 | var compressed_data = std.ArrayList(u8).init(allocator); |
| | 264 | defer compressed_data.deinit(); |
| | 265 | |
| | 266 | var compressor = try zlibStreamWriter(allocator, compressed_data.writer(), .{}); |
| | 267 | defer compressor.deinit(); |
| | 268 | |
| | 269 | try compressor.writer().writeAll(rfc1951_txt); |
| | 270 | try compressor.close(); |
| | 271 | |
| | 272 | try testReader(compressed_data.items, rfc1951_txt); |
| | 273 | } |