| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | // |
| 2 | | // Decompressor for ZLIB data streams (RFC1950) |
| 2 | // Compressor/Decompressor for ZLIB data streams (RFC1950) |
| 3 | 3 | |
| 4 | 4 | const std = @import("std"); |
| 5 | 5 | const io = std.io; |
| ... | ... | @@ -8,7 +8,7 @@ const testing = std.testing; |
| 8 | 8 | const mem = std.mem; |
| 9 | 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 | 12 | return struct { |
| 13 | 13 | const Self = @This(); |
| 14 | 14 | |
| ... | ... | @@ -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)) { |
| 88 | | return ZlibStream(@TypeOf(reader)).init(allocator, reader); |
| 87 | pub fn zlibStreamReader(allocator: mem.Allocator, reader: anytype) !ZlibStreamReader(@TypeOf(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 | 176 | fn testReader(data: []const u8, expected: []const u8) !void { |
| 92 | 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 | 180 | defer zlib_stream.deinit(); |
| 96 | 181 | |
| 97 | 182 | // Read and decompress the whole file |
| ... | ... | @@ -170,3 +255,19 @@ test "sanity checks" { |
| 170 | 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 | } |