commit 107ca216d414e61c79e00db80585e146c1f80310
parent 5c0d392f2678b91416aaf3f330dc769e0d5d3086
Author: Eyal Sawady <ecs@d2evs.net>
Date: Tue, 30 Mar 2021 05:34:58 -0400
compress::zlib: new module
Diffstat:
4 files changed, 182 insertions(+), 0 deletions(-)
diff --git a/compress/zlib/data+test.ha b/compress/zlib/data+test.ha
@@ -0,0 +1 @@
+const vectors: [](*[]u8, *[]u8) = [];
diff --git a/compress/zlib/reader.ha b/compress/zlib/reader.ha
@@ -0,0 +1,139 @@
+use bufio;
+use bytes;
+use compress::flate;
+use endian;
+use errors;
+use fmt;
+use hash::adler32;
+use hash;
+use io;
+
+def CM: u8 = 0b00001111;
+def CINFO: u8 = 0b11110000;
+
+def FCHECK: u8 = 0b00011111;
+def FDICT: u8 = 0b00100000;
+def FLEVEL: u8 = 0b11000000;
+
+type reader = struct {
+ stream: io::stream,
+ source: *io::stream,
+ flate: *io::stream,
+ hash: *hash::hash,
+};
+
+type decompress_err = enum {
+ HEADER,
+ CHECKSUM,
+ EOF,
+ DICT,
+};
+
+fn opaque_strerror(
+ data: *errors::opaque_data
+) const str = switch (*(data: *decompress_err)) {
+ decompress_err::HEADER => "Invalid zlib header",
+ decompress_err::CHECKSUM => "Invalid zlib checksum",
+ decompress_err::EOF => "Unexpected EOF",
+ decompress_err::DICT => "Invalid dictionary",
+ * => abort(),
+};
+
+fn wraperror(err: decompress_err) errors::opaque = {
+ static assert(size(decompress_err) <= size(error::opaque_data));
+ let wrapped = errors::opaque { strerror = &opaque_strerror, ... };
+ let myptr = &wrapped.data: *decompress_err;
+ *myptr = err;
+ return wrapped;
+};
+
+fn verifysum(s: *reader) (io::EOF | io::error) = {
+ let hash: [4]u8 = [0...];
+ for (let n = 0z; n < len(hash)) {
+ match (io::read(s.source, hash[n..])?) {
+ io::EOF => return wraperror(decompress_err::EOF),
+ z: size => n += z,
+ };
+ };
+ let checksum = hash::sum(s.hash);
+ defer free(checksum);
+ return if (bytes::equal(checksum, hash)) io::EOF
+ else wraperror(decompress_err::CHECKSUM);
+};
+
+fn read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ let s = s: *reader;
+ match (io::read(s.flate, buf)?) {
+ io::EOF => return verifysum(s),
+ z: size => buf = buf[..z],
+ };
+ hash::write(s.hash, buf);
+};
+
+fn close(s: *io::stream) void = {
+ const s = s: *reader;
+ io::close(s.flate);
+ hash::close(s.hash);
+};
+
+// Creates a stream which decompresses zlib (RFC 1950) data.
+export fn decompress(s: *io::stream) (*io::stream | io::error) = {
+ let buf: [2]u8 = [0...];
+ for (let n = 0z; n < len(buf)) {
+ match (io::read(s, buf[n..])?) {
+ io::EOF => return wraperror(decompress_err::EOF),
+ z: size => n += z,
+ };
+ };
+ if (buf[0] & CM != 8) {
+ return wraperror(decompress_err::HEADER);
+ };
+ // XXX: Should we tell compress::flate to allocate a smaller buffer on
+ // CINFO < 7?
+ if ((buf[0] & CINFO) >> 4 > 7) {
+ return wraperror(decompress_err::HEADER);
+ };
+ if ((buf[0] << 8 | buf[1]) % 31 != 0) {
+ // Invalid FCHECK
+ return wraperror(decompress_err::HEADER);
+ };
+ if ((buf[1] & FDICT == 1)) {
+ // TODO: Allow the caller to pass in a list of dictionaries to
+ // be tried
+ return wraperror(decompress_err::DICT);
+ };
+ return alloc(reader {
+ stream = io::stream {
+ reader = &read,
+ closer = &close,
+ ...
+ },
+ source = s,
+ flate = flate::inflate(s),
+ hash = adler32::adler32(),
+ ...
+ }): *io::stream;
+};
+
+@test fn decompress() void = {
+ for (let i = 1z; i < len(vectors); i += 1) {
+ let in = bufio::fixed(*vectors[i].1, io::mode::READ);
+ let out = bufio::dynamic(io::mode::WRITE);
+ let d = match (decompress(in)) {
+ s: *io::stream => s,
+ e: io::error => {
+ fmt::errorln(io::strerror(e));
+ abort();
+ },
+ };
+ match (io::copy(out, d)) {
+ size => void,
+ e: io::error => {
+ fmt::errorfln("vector {}: {}", i, io::strerror(e));
+ abort();
+ },
+ };
+ let s = bufio::finish(out);
+ assert(bytes::equal(s, *vectors[i].0));
+ };
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -131,6 +131,20 @@ compress_flate() {
gen_ssa compress::flate bufio bytes endian errors io fmt
}
+compress_zlib() {
+ if [ $testing -eq 0 ]
+ then
+ gen_srcs compress::zlib \
+ reader.ha
+ else
+ gen_srcs compress::zlib \
+ data+test.ha \
+ reader.ha
+ fi
+ gen_ssa compress::zlib bufio bytes compress::flate endian errors hash \
+ hash::adler32 io
+}
+
crypto_math() {
gen_srcs crypto::math \
bits.ha
@@ -614,6 +628,7 @@ modules="ascii
bufio
bytes
compress_flate
+compress_zlib
crypto_math
crypto_random
crypto_md5
diff --git a/stdlib.mk b/stdlib.mk
@@ -81,6 +81,9 @@ hare_stdlib_deps+=$(stdlib_bytes)
stdlib_compress_flate=$(HARECACHE)/compress/flate/compress_flate.o
hare_stdlib_deps+=$(stdlib_compress_flate)
+stdlib_compress_zlib=$(HARECACHE)/compress/zlib/compress_zlib.o
+hare_stdlib_deps+=$(stdlib_compress_zlib)
+
stdlib_crypto_math=$(HARECACHE)/crypto/math/crypto_math.o
hare_stdlib_deps+=$(stdlib_crypto_math)
@@ -268,6 +271,16 @@ $(HARECACHE)/compress/flate/compress_flate.ssa: $(stdlib_compress_flate_srcs) $(
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncompress::flate \
-t$(HARECACHE)/compress/flate/compress_flate.td $(stdlib_compress_flate_srcs)
+# compress::zlib
+stdlib_compress_zlib_srcs= \
+ $(STDLIB)/compress/zlib/reader.ha
+
+$(HARECACHE)/compress/zlib/compress_zlib.ssa: $(stdlib_compress_zlib_srcs) $(stdlib_rt) $(stdlib_bufio) $(stdlib_bytes) $(stdlib_compress_flate) $(stdlib_endian) $(stdlib_errors) $(stdlib_hash) $(stdlib_hash_adler32) $(stdlib_io)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/compress/zlib
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncompress::zlib \
+ -t$(HARECACHE)/compress/zlib/compress_zlib.td $(stdlib_compress_zlib_srcs)
+
# crypto::math
stdlib_crypto_math_srcs= \
$(STDLIB)/crypto/math/bits.ha
@@ -907,6 +920,9 @@ hare_testlib_deps+=$(testlib_bytes)
testlib_compress_flate=$(TESTCACHE)/compress/flate/compress_flate.o
hare_testlib_deps+=$(testlib_compress_flate)
+testlib_compress_zlib=$(TESTCACHE)/compress/zlib/compress_zlib.o
+hare_testlib_deps+=$(testlib_compress_zlib)
+
testlib_crypto_math=$(TESTCACHE)/crypto/math/crypto_math.o
hare_testlib_deps+=$(testlib_crypto_math)
@@ -1094,6 +1110,17 @@ $(TESTCACHE)/compress/flate/compress_flate.ssa: $(testlib_compress_flate_srcs) $
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncompress::flate \
-t$(TESTCACHE)/compress/flate/compress_flate.td $(testlib_compress_flate_srcs)
+# compress::zlib
+testlib_compress_zlib_srcs= \
+ $(STDLIB)/compress/zlib/data+test.ha \
+ $(STDLIB)/compress/zlib/reader.ha
+
+$(TESTCACHE)/compress/zlib/compress_zlib.ssa: $(testlib_compress_zlib_srcs) $(testlib_rt) $(testlib_bufio) $(testlib_bytes) $(testlib_compress_flate) $(testlib_endian) $(testlib_errors) $(testlib_hash) $(testlib_hash_adler32) $(testlib_io)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/compress/zlib
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncompress::zlib \
+ -t$(TESTCACHE)/compress/zlib/compress_zlib.td $(testlib_compress_zlib_srcs)
+
# crypto::math
testlib_crypto_math_srcs= \
$(STDLIB)/crypto/math/bits.ha