zig/lib/std/compress/zlib.zig
LemonBoy 4b851c7255 Re-enable a compression test
It somehow got commented out...
2020-09-11 21:04:08 +02:00

179 lines
5.9 KiB
Zig

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
//
// Decompressor for ZLIB data streams (RFC1950)
const std = @import("std");
const io = std.io;
const fs = std.fs;
const testing = std.testing;
const mem = std.mem;
const deflate = std.compress.deflate;
pub fn ZlibStream(comptime ReaderType: type) type {
return struct {
const Self = @This();
pub const Error = ReaderType.Error ||
deflate.InflateStream(ReaderType).Error ||
error{ WrongChecksum, Unsupported };
pub const Reader = io.Reader(*Self, Error, read);
allocator: *mem.Allocator,
inflater: deflate.InflateStream(ReaderType),
in_reader: ReaderType,
hasher: std.hash.Adler32,
window_slice: []u8,
fn init(allocator: *mem.Allocator, source: ReaderType) !Self {
// Zlib header format is specified in RFC1950
const header = try source.readBytesNoEof(2);
const CM = @truncate(u4, header[0]);
const CINFO = @truncate(u4, header[0] >> 4);
const FCHECK = @truncate(u5, header[1]);
const FDICT = @truncate(u1, header[1] >> 5);
if ((@as(u16, header[0]) << 8 | header[1]) % 31 != 0)
return error.BadHeader;
// The CM field must be 8 to indicate the use of DEFLATE
if (CM != 8) return error.InvalidCompression;
// CINFO is the base-2 logarithm of the window size, minus 8.
// Values above 7 are unspecified and therefore rejected.
if (CINFO > 7) return error.InvalidWindowSize;
const window_size: u16 = @as(u16, 1) << (CINFO + 8);
// TODO: Support this case
if (FDICT != 0)
return error.Unsupported;
var window_slice = try allocator.alloc(u8, window_size);
return Self{
.allocator = allocator,
.inflater = deflate.inflateStream(source, window_slice),
.in_reader = source,
.hasher = std.hash.Adler32.init(),
.window_slice = window_slice,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.window_slice);
}
// Implements the io.Reader interface
pub fn read(self: *Self, buffer: []u8) Error!usize {
if (buffer.len == 0)
return 0;
// Read from the compressed stream and update the computed checksum
const r = try self.inflater.read(buffer);
if (r != 0) {
self.hasher.update(buffer[0..r]);
return r;
}
// We've reached the end of stream, check if the checksum matches
const hash = try self.in_reader.readIntBig(u32);
if (hash != self.hasher.final())
return error.WrongChecksum;
return 0;
}
pub fn reader(self: *Self) Reader {
return .{ .context = self };
}
};
}
pub fn zlibStream(allocator: *mem.Allocator, reader: anytype) !ZlibStream(@TypeOf(reader)) {
return ZlibStream(@TypeOf(reader)).init(allocator, reader);
}
fn testReader(data: []const u8, comptime expected: []const u8) !void {
var in_stream = io.fixedBufferStream(data);
var zlib_stream = try zlibStream(testing.allocator, in_stream.reader());
defer zlib_stream.deinit();
// Read and decompress the whole file
const buf = try zlib_stream.reader().readAllAlloc(testing.allocator, std.math.maxInt(usize));
defer testing.allocator.free(buf);
// Calculate its SHA256 hash and check it against the reference
var hash: [32]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash(buf, hash[0..], .{});
assertEqual(expected, &hash);
}
// Assert `expected` == `input` where `input` is a bytestring.
pub fn assertEqual(comptime expected: []const u8, input: []const u8) void {
var expected_bytes: [expected.len / 2]u8 = undefined;
for (expected_bytes) |*r, i| {
r.* = std.fmt.parseInt(u8, expected[2 * i .. 2 * i + 2], 16) catch unreachable;
}
testing.expectEqualSlices(u8, &expected_bytes, input);
}
// All the test cases are obtained by compressing the RFC1950 text
//
// https://tools.ietf.org/rfc/rfc1950.txt length=36944 bytes
// SHA256=5ebf4b5b7fe1c3a0c0ab9aa3ac8c0f3853a7dc484905e76e03b0b0f301350009
test "compressed data" {
// Compressed with compression level = 0
try testReader(
@embedFile("rfc1951.txt.z.0"),
"5ebf4b5b7fe1c3a0c0ab9aa3ac8c0f3853a7dc484905e76e03b0b0f301350009",
);
// Compressed with compression level = 9
try testReader(
@embedFile("rfc1951.txt.z.9"),
"5ebf4b5b7fe1c3a0c0ab9aa3ac8c0f3853a7dc484905e76e03b0b0f301350009",
);
// Compressed with compression level = 9 and fixed Huffman codes
try testReader(
@embedFile("rfc1951.txt.fixed.z.9"),
"5ebf4b5b7fe1c3a0c0ab9aa3ac8c0f3853a7dc484905e76e03b0b0f301350009",
);
}
test "sanity checks" {
// Truncated header
testing.expectError(
error.EndOfStream,
testReader(&[_]u8{0x78}, ""),
);
// Failed FCHECK check
testing.expectError(
error.BadHeader,
testReader(&[_]u8{ 0x78, 0x9D }, ""),
);
// Wrong CM
testing.expectError(
error.InvalidCompression,
testReader(&[_]u8{ 0x79, 0x94 }, ""),
);
// Wrong CINFO
testing.expectError(
error.InvalidWindowSize,
testReader(&[_]u8{ 0x88, 0x98 }, ""),
);
// Wrong checksum
testing.expectError(
error.WrongChecksum,
testReader(&[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, ""),
);
// Truncated checksum
testing.expectError(
error.EndOfStream,
testReader(&[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""),
);
}