Add a lib/cgozlib package
diff --git a/lib/cgozlib/cgozlib.go b/lib/cgozlib/cgozlib.go
new file mode 100644
index 0000000..2379128
--- /dev/null
+++ b/lib/cgozlib/cgozlib.go
@@ -0,0 +1,239 @@
+// Copyright 2019 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ----------------
+
+// Package cgozlib wraps the C "zlib" library.
+//
+// Unlike some other wrappers, this one supports dictionaries.
+package cgozlib
+
+/*
+#cgo pkg-config: zlib
+#include "zlib.h"
+
+typedef struct {
+ uInt ndst;
+ uInt nsrc;
+} advances;
+
+int cgozlib_inflateInit(z_stream* z) {
+ return inflateInit(z);
+}
+
+int cgozlib_inflateSetDictionary(z_stream* z,
+ Bytef* dict_ptr,
+ uInt dict_len) {
+ return inflateSetDictionary(z, dict_ptr, dict_len);
+}
+
+int cgozlib_inflate(z_stream* z,
+ advances *a,
+ Bytef* next_out,
+ uInt avail_out,
+ Bytef* next_in,
+ uInt avail_in) {
+ z->next_out = next_out;
+ z->avail_out = avail_out;
+ z->next_in = next_in;
+ z->avail_in = avail_in;
+
+ int ret = inflate(z, Z_NO_FLUSH);
+
+ a->ndst = avail_out - z->avail_out;
+ a->nsrc = avail_in - z->avail_in;
+
+ z->next_out = NULL;
+ z->avail_out = 0;
+ z->next_in = NULL;
+ z->avail_in = 0;
+
+ return ret;
+}
+
+int cgozlib_inflateEnd(z_stream* z) {
+ return inflateEnd(z);
+}
+*/
+import "C"
+
+import (
+ "errors"
+ "io"
+ "unsafe"
+)
+
+var (
+ errMissingResetCall = errors.New("cgozlib: missing Reset call")
+ errNilIOReader = errors.New("cgozlib: nil io.Reader")
+ errNilReceiver = errors.New("cgozlib: nil receiver")
+)
+
+const (
+ errCodeStreamEnd = 1
+ errCodeNeedDict = 2
+)
+
+type errCode int32
+
+func (e errCode) Error() string {
+ switch e {
+ case +1:
+ return "cgozlib: Z_STREAM_END"
+ case +2:
+ return "cgozlib: Z_NEED_DICT"
+ case -1:
+ return "cgozlib: Z_ERRNO"
+ case -2:
+ return "cgozlib: Z_STREAM_ERROR"
+ case -3:
+ return "cgozlib: Z_DATA_ERROR"
+ case -4:
+ return "cgozlib: Z_MEM_ERROR"
+ case -5:
+ return "cgozlib: Z_BUF_ERROR"
+ case -6:
+ return "cgozlib: Z_VERSION_ERROR"
+ }
+ return "cgozlib: unknown zlib error"
+}
+
+// Reader is both a zlib.Resetter and an io.ReadCloser. Call Reset before
+// calling Read.
+//
+// It is analogous to the value returned by zlib.NewReader in the Go standard
+// library.
+type Reader struct {
+ buf [4096]byte
+ i, j uint32
+ r io.Reader
+ dict []byte
+
+ readErr error
+ zlibErr error
+
+ z C.z_stream
+ a C.advances
+}
+
+// Reset implements zlib.Resetter.
+func (r *Reader) Reset(reader io.Reader, dictionary []byte) error {
+ if r == nil {
+ return errNilReceiver
+ }
+ if reader == nil {
+ return errNilIOReader
+ }
+ if err := r.Close(); err != nil {
+ return err
+ }
+
+ if e := C.cgozlib_inflateInit(&r.z); e != 0 {
+ return errCode(e)
+ }
+
+ r.r = reader
+ r.dict = dictionary
+ if n := len(r.dict); n > 32768 {
+ r.dict = r.dict[n-32768:]
+ }
+ return nil
+}
+
+// Close implements io.Closer.
+func (r *Reader) Close() error {
+ if r == nil {
+ return errNilReceiver
+ }
+ if r.r == nil {
+ return nil
+ }
+ r.i = 0
+ r.j = 0
+ r.r = nil
+ r.dict = nil
+ r.readErr = nil
+ r.zlibErr = nil
+ if e := C.cgozlib_inflateEnd(&r.z); e != 0 {
+ return errCode(e)
+ }
+ return nil
+}
+
+// Read implements io.Reader.
+func (r *Reader) Read(p []byte) (int, error) {
+ if r == nil {
+ return 0, errNilReceiver
+ }
+ if r.r == nil {
+ return 0, errMissingResetCall
+ }
+
+ const maxLen = 1 << 30
+ if len(p) > maxLen {
+ p = p[:maxLen]
+ }
+
+ for numRead := 0; ; {
+ if r.zlibErr != nil {
+ return numRead, r.zlibErr
+ }
+ if len(p) == 0 {
+ return numRead, nil
+ }
+
+ if r.i >= r.j {
+ if r.readErr != nil {
+ return numRead, r.readErr
+ }
+
+ n, err := r.r.Read(r.buf[:])
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ r.i, r.j, r.readErr = 0, uint32(n), err
+ continue
+ }
+
+ e := C.cgozlib_inflate(&r.z, &r.a,
+ (*C.Bytef)(unsafe.Pointer(&p[0])),
+ (C.uInt)(len(p)),
+ (*C.Bytef)(unsafe.Pointer(&r.buf[r.i])),
+ (C.uInt)(r.j-r.i),
+ )
+
+ numRead += int(r.a.ndst)
+ p = p[int(r.a.ndst):]
+
+ r.i += uint32(r.a.nsrc)
+
+ if e == 0 {
+ continue
+ } else if e == errCodeStreamEnd {
+ r.zlibErr = io.EOF
+ } else if (e == errCodeNeedDict) && (len(r.dict) > 0) {
+ e = C.cgozlib_inflateSetDictionary(&r.z,
+ (*C.Bytef)(unsafe.Pointer(&r.dict[0])),
+ (C.uInt)(len(r.dict)),
+ )
+ if e == 0 {
+ continue
+ }
+ r.zlibErr = errCode(e)
+ } else {
+ r.zlibErr = errCode(e)
+ }
+ return numRead, r.zlibErr
+ }
+}
diff --git a/lib/cgozlib/cgozlib_test.go b/lib/cgozlib/cgozlib_test.go
new file mode 100644
index 0000000..cceb6e2
--- /dev/null
+++ b/lib/cgozlib/cgozlib_test.go
@@ -0,0 +1,109 @@
+// Copyright 2019 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cgozlib
+
+import (
+ "bytes"
+ "compress/zlib"
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+// These examples come from the RAC spec.
+var (
+ compressedMore = "\x78\x9c\x01\x06\x00\xf9\xff\x4d\x6f\x72\x65\x21" +
+ "\x0a\x07\x42\x01\xbf"
+
+ compressedSheep = "\x78\xf9\x0b\xe0\x02\x6e\x0a\x29\xcf\x87\x31\x01\x01" +
+ "\x00\x00\xff\xff\x18\x0c\x03\xa8"
+
+ dictSheep = []byte("\x20sheep.\n")
+)
+
+type resetReadCloser interface {
+ zlib.Resetter
+ io.ReadCloser
+}
+
+func makePure() resetReadCloser {
+ r, err := zlib.NewReader(strings.NewReader(compressedMore))
+ if err != nil {
+ panic(err.Error())
+ }
+ return r.(resetReadCloser)
+}
+
+func testReadAll(t *testing.T, r resetReadCloser, src io.Reader, dict []byte, want string) {
+ t.Helper()
+
+ if err := r.Reset(src, dict); err != nil {
+ t.Fatalf("Reset: %v", err)
+ }
+
+ got, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Fatalf("ReadAll: %v", err)
+ }
+
+ if err := r.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+
+ if string(got) != want {
+ t.Fatalf("got %q, want %q", got, want)
+ }
+}
+
+func testReader(t *testing.T, r resetReadCloser) {
+ testReadAll(t, r, strings.NewReader(compressedMore), nil, "More!\n")
+ testReadAll(t, r, strings.NewReader(compressedSheep), dictSheep, "Two sheep.\n")
+ testReadAll(t, r, strings.NewReader(compressedMore), nil, "More!\n")
+ testReadAll(t, r, strings.NewReader(compressedSheep), dictSheep, "Two sheep.\n")
+}
+
+func TestCgo(t *testing.T) { testReader(t, &Reader{}) }
+func TestPure(t *testing.T) { testReader(t, makePure()) }
+
+func benchmarkReader(b *testing.B, r resetReadCloser) {
+ src := bytes.NewReader(nil)
+ srcBytes, err := ioutil.ReadFile("../../test/data/pi.txt.zlib")
+ if err != nil {
+ b.Fatalf("ReadFile: %v", err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ src.Reset(srcBytes)
+
+ if err := r.Reset(src, nil); err != nil {
+ b.Fatalf("Reset: %v", err)
+ }
+
+ if n, err := io.Copy(ioutil.Discard, r); err != nil {
+ b.Fatalf("Copy: %v", err)
+ } else if n != 100003 {
+ b.Fatalf("Copy: got %d, want %d", n, 100003)
+ }
+
+ if err := r.Close(); err != nil {
+ b.Fatalf("Close: %v", err)
+ }
+ }
+}
+
+func BenchmarkCgo(b *testing.B) { benchmarkReader(b, &Reader{}) }
+func BenchmarkPure(b *testing.B) { benchmarkReader(b, makePure()) }