Commit 8b92354396

dantecatalfamo <dante.catalfamo@gmail.com>
2022-12-16 19:14:12
std: add zlib stream writer
1 parent 9370fb8
Changed files (1)
lib
std
compress
lib/std/compress/zlib.zig
@@ -1,5 +1,5 @@
 //
-// Decompressor for ZLIB data streams (RFC1950)
+// Compressor/Decompressor for ZLIB data streams (RFC1950)
 
 const std = @import("std");
 const io = std.io;
@@ -8,7 +8,7 @@ const testing = std.testing;
 const mem = std.mem;
 const deflate = std.compress.deflate;
 
-pub fn ZlibStream(comptime ReaderType: type) type {
+pub fn ZlibStreamReader(comptime ReaderType: type) type {
     return struct {
         const Self = @This();
 
@@ -84,14 +84,99 @@ pub fn ZlibStream(comptime ReaderType: type) type {
     };
 }
 
-pub fn zlibStream(allocator: mem.Allocator, reader: anytype) !ZlibStream(@TypeOf(reader)) {
-    return ZlibStream(@TypeOf(reader)).init(allocator, reader);
+pub fn zlibStreamReader(allocator: mem.Allocator, reader: anytype) !ZlibStreamReader(@TypeOf(reader)) {
+    return ZlibStreamReader(@TypeOf(reader)).init(allocator, reader);
 }
 
+pub const CompressionLevel = enum(u2) {
+    no_compression = 0,
+    fastest = 1,
+    default = 2,
+    maximum = 3,
+};
+
+pub const CompressionOptions = struct {
+    level: CompressionLevel = .default,
+};
+
+pub fn ZlibStreamWriter(comptime WriterType: type) type {
+    return struct {
+        const Self = @This();
+
+        const Error = WriterType.Error ||
+            deflate.Compressor(WriterType).Error;
+        pub const Writer = io.Writer(*Self, Error, write);
+
+        allocator: mem.Allocator,
+        deflator: deflate.Compressor(WriterType),
+        in_writer: WriterType,
+        hasher: std.hash.Adler32,
+
+        fn init(allocator: mem.Allocator, dest: WriterType, options: CompressionOptions) !Self {
+            // Zlib header format is specified in RFC1950
+            const CM: u4 = 8; // DEFLATE
+            const CINFO: u4 = 7; // 32K window
+            const CMF: u8 = (@as(u8, CINFO) << 4) | CM;
+
+            const FLEVEL: u2 = @enumToInt(options.level);
+            const FDICT: u1 = 0; // No preset dictionary support
+            const FLG_temp = (@as(u8, FLEVEL) << 6) | (@as(u8, FDICT) << 5);
+            const FCHECK: u5 = 31 - ((@as(u16, CMF) * 256 + FLG_temp) % 31);
+            const FLG = FLG_temp | FCHECK;
+
+            const compression_level: deflate.Compression = switch (options.level) {
+                .no_compression => .no_compression,
+                .fastest => .best_speed,
+                .default => .default_compression,
+                .maximum => .best_compression,
+            };
+
+            try dest.writeAll(&.{ CMF, FLG });
+
+            return Self{
+                .allocator = allocator,
+                .deflator = try deflate.compressor(allocator, dest, .{ .level = compression_level }),
+                .in_writer = dest,
+                .hasher = std.hash.Adler32.init(),
+            };
+        }
+
+        pub fn write(self: *Self, bytes: []const u8) Error!usize {
+            if (bytes.len == 0) {
+                return 0;
+            }
+
+            const w = try self.deflator.write(bytes);
+
+            self.hasher.update(bytes[0..w]);
+            return w;
+        }
+
+        pub fn writer(self: *Self) Writer {
+            return .{ .context = self };
+        }
+
+        pub fn deinit(self: *Self) void {
+            self.deflator.deinit();
+        }
+
+        pub fn close(self: *Self) !void {
+            const hash = self.hasher.final();
+            try self.deflator.close();
+            try self.in_writer.writeIntBig(u32, hash);
+        }
+    };
+}
+
+pub fn zlibStreamWriter(allocator: mem.Allocator, writer: anytype, options: CompressionOptions) !ZlibStreamWriter(@TypeOf(writer)) {
+    return ZlibStreamWriter(@TypeOf(writer)).init(allocator, writer, options);
+}
+
+
 fn testReader(data: []const u8, expected: []const u8) !void {
     var in_stream = io.fixedBufferStream(data);
 
-    var zlib_stream = try zlibStream(testing.allocator, in_stream.reader());
+    var zlib_stream = try zlibStreamReader(testing.allocator, in_stream.reader());
     defer zlib_stream.deinit();
 
     // Read and decompress the whole file
@@ -170,3 +255,19 @@ test "sanity checks" {
         testReader(&[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""),
     );
 }
+
+test "compress data" {
+    const allocator = testing.allocator;
+    const rfc1951_txt = @embedFile("testdata/rfc1951.txt");
+
+    var compressed_data = std.ArrayList(u8).init(allocator);
+    defer compressed_data.deinit();
+
+    var compressor = try zlibStreamWriter(allocator, compressed_data.writer(), .{});
+    defer compressor.deinit();
+
+    try compressor.writer().writeAll(rfc1951_txt);
+    try compressor.close();
+
+    try testReader(compressed_data.items, rfc1951_txt);
+}