| // 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 cgozstd wraps the C "zstd" library. |
| package cgozstd |
| |
| /* |
| #cgo pkg-config: libzstd |
| #include "zstd.h" |
| #include "zstd_errors.h" |
| |
| #include <stdint.h> |
| |
| // -------- |
| #if (ZSTD_VERSION_MAJOR < 1) || (ZSTD_VERSION_MINOR < 3) |
| |
| // For zstd version 1.2 and below, dictionaries simply aren't supported. |
| |
| int32_t cgozstd_compress_start(ZSTD_CCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len, |
| int compression_level) { |
| if (dict_len > 0) { |
| return -1; |
| } |
| return ZSTD_getErrorCode(ZSTD_initCStream(z, compression_level)); |
| } |
| |
| int32_t cgozstd_decompress_start(ZSTD_DCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len) { |
| if (dict_len > 0) { |
| return -1; |
| } |
| return ZSTD_getErrorCode(ZSTD_initDStream(z)); |
| } |
| |
| #elif (ZSTD_VERSION_MAJOR < 1) || (ZSTD_VERSION_MINOR < 4) |
| |
| // For zstd version 1.3 (but not 1.4 and above), the ZSTD_initFoo_usingDict |
| // functions are present in the dynamic library, but not part of the stable |
| // zstd API. We have to explicitly write their function prototypes (or #define |
| // ZSTD_STATIC_LINKING_ONLY) so that cgo knows about them. |
| // |
| // For example, Ubuntu "bionic" 18.04 LTS ships zstd version 1.3.3. |
| |
| ZSTDLIB_API size_t ZSTD_initCStream_usingDict( |
| ZSTD_CCtx* z, |
| const void* dict_ptr, |
| size_t dict_len, |
| int compression_level); |
| ZSTDLIB_API size_t ZSTD_initDStream_usingDict( |
| ZSTD_DCtx* z, |
| const void* dict_ptr, |
| size_t dict_len); |
| |
| int32_t cgozstd_compress_start(ZSTD_CCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len, |
| int compression_level) { |
| return ZSTD_getErrorCode(ZSTD_initCStream_usingDict( |
| z, dict_ptr, dict_len, compression_level)); |
| } |
| |
| int32_t cgozstd_decompress_start(ZSTD_DCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len) { |
| return ZSTD_getErrorCode(ZSTD_initDStream_usingDict( |
| z, dict_ptr, dict_len)); |
| } |
| |
| #else |
| |
| // For zstd version 1.4 and above, the ZSTD_initFoo_usingDict functions are |
| // deprecated, and their replacements are part of the stable zstd API. |
| |
| int32_t cgozstd_compress_start(ZSTD_CCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len, |
| int compression_level) { |
| ZSTD_ErrorCode e; |
| e = ZSTD_getErrorCode(ZSTD_CCtx_reset(z, ZSTD_reset_session_only)); |
| if (e) { |
| return e; |
| } |
| e = ZSTD_getErrorCode(ZSTD_CCtx_setParameter( |
| z, ZSTD_c_compressionLevel, compression_level)); |
| if (e) { |
| return e; |
| } |
| e = ZSTD_getErrorCode(ZSTD_CCtx_loadDictionary(z, dict_ptr, dict_len)); |
| if (e) { |
| return e; |
| } |
| return 0; |
| } |
| |
| int32_t cgozstd_decompress_start(ZSTD_DCtx* z, |
| uint8_t* dict_ptr, |
| uint32_t dict_len) { |
| ZSTD_ErrorCode e; |
| e = ZSTD_getErrorCode(ZSTD_DCtx_reset(z, ZSTD_reset_session_only)); |
| if (e) { |
| return e; |
| } |
| e = ZSTD_getErrorCode(ZSTD_DCtx_loadDictionary(z, dict_ptr, dict_len)); |
| if (e) { |
| return e; |
| } |
| return 0; |
| } |
| |
| #endif |
| // -------- |
| |
| typedef struct { |
| uint32_t ndst; |
| uint32_t nsrc; |
| uint32_t eof; |
| } advances; |
| |
| int32_t cgozstd_compress(ZSTD_CCtx* z, |
| advances* a, |
| uint8_t* dst_ptr, |
| uint32_t dst_len, |
| uint8_t* src_ptr, |
| uint32_t src_len) { |
| ZSTD_outBuffer ob; |
| ob.dst = dst_ptr; |
| ob.size = dst_len; |
| ob.pos = 0; |
| |
| ZSTD_inBuffer ib; |
| ib.src = src_ptr; |
| ib.size = src_len; |
| ib.pos = 0; |
| |
| size_t result = ZSTD_compressStream(z, &ob, &ib); |
| |
| a->ndst = ob.pos; |
| a->nsrc = ib.pos; |
| a->eof = 0; |
| |
| return ZSTD_getErrorCode(result); |
| } |
| |
| int32_t cgozstd_compress_end(ZSTD_CCtx* z, |
| advances* a, |
| uint8_t* dst_ptr, |
| uint32_t dst_len) { |
| ZSTD_outBuffer ob; |
| ob.dst = dst_ptr; |
| ob.size = dst_len; |
| ob.pos = 0; |
| |
| size_t result = ZSTD_endStream(z, &ob); |
| |
| a->ndst = ob.pos; |
| a->nsrc = 0; |
| a->eof = (result == 0) ? 1 : 0; |
| |
| return ZSTD_getErrorCode(result); |
| } |
| |
| int32_t cgozstd_decompress(ZSTD_DCtx* z, |
| advances* a, |
| uint8_t* dst_ptr, |
| uint32_t dst_len, |
| uint8_t* src_ptr, |
| uint32_t src_len) { |
| ZSTD_outBuffer ob; |
| ob.dst = dst_ptr; |
| ob.size = dst_len; |
| ob.pos = 0; |
| |
| ZSTD_inBuffer ib; |
| ib.src = src_ptr; |
| ib.size = src_len; |
| ib.pos = 0; |
| |
| size_t result = ZSTD_decompressStream(z, &ob, &ib); |
| |
| a->ndst = ob.pos; |
| a->nsrc = ib.pos; |
| a->eof = (result == 0) ? 1 : 0; |
| |
| return ZSTD_getErrorCode(result); |
| } |
| */ |
| import "C" |
| |
| import ( |
| "errors" |
| "io" |
| "unsafe" |
| |
| "github.com/google/wuffs/lib/compression" |
| ) |
| |
| const cgoEnabled = true |
| |
| // maxLen avoids overflow concerns when converting C and Go integer types. |
| const maxLen = 1 << 30 |
| |
| var ( |
| errMissingResetCall = errors.New("cgozstd: missing Reset call") |
| errNilIOReader = errors.New("cgozstd: nil io.Reader") |
| errNilIOWriter = errors.New("cgozstd: nil io.Writer") |
| errNilReceiver = errors.New("cgozstd: nil receiver") |
| errOutOfMemory = errors.New("cgozstd: out of memory") |
| errZstdVersionTooSmall = errors.New("cgozstd: zstd version too small (1.3 minimum)") |
| ) |
| |
| type errCode int32 |
| |
| func (e errCode) Error() string { |
| if s := C.GoString(C.ZSTD_getErrorString(C.ZSTD_ErrorCode(e))); s != "" { |
| return "cgozstd: " + s |
| } |
| return "cgozstd: unknown error" |
| } |
| |
| func slicePointer(s []uint8) unsafe.Pointer { |
| if len(s) == 0 { |
| return nil |
| } |
| return unsafe.Pointer(&s[0]) |
| } |
| |
| // ReaderRecycler can lessen the new memory allocated when calling Reader.Reset |
| // on a bound Reader. |
| // |
| // It is not safe to use a ReaderRecycler and a Reader concurrently. |
| type ReaderRecycler struct { |
| z *C.ZSTD_DCtx |
| closed bool |
| } |
| |
| // Bind lets r re-use the memory that is manually managed by c. Call c.Close to |
| // free that memory. |
| func (c *ReaderRecycler) Bind(r *Reader) { |
| r.recycler = c |
| } |
| |
| // Close implements io.Closer. |
| func (c *ReaderRecycler) Close() error { |
| c.closed = true |
| if c.z != nil { |
| C.ZSTD_freeDCtx(c.z) |
| c.z = nil |
| } |
| return nil |
| } |
| |
| // Reader decompresses from the zstd format. |
| // |
| // The zero value is not usable until Reset is called. |
| type Reader struct { |
| buf [65536]byte |
| i, j uint32 |
| r io.Reader |
| |
| readErr error |
| zstdErr error |
| |
| recycler *ReaderRecycler |
| |
| z *C.ZSTD_DCtx |
| a C.advances |
| } |
| |
| // Reset implements compression.Reader. |
| func (r *Reader) Reset(reader io.Reader, dictionary []byte) error { |
| if r == nil { |
| return errNilReceiver |
| } |
| if err := r.Close(); err != nil { |
| return err |
| } |
| if reader == nil { |
| return errNilIOReader |
| } |
| if len(dictionary) > maxLen { |
| dictionary = dictionary[len(dictionary)-maxLen:] |
| } |
| |
| z := (*C.ZSTD_DCtx)(nil) |
| if (r.recycler != nil) && !r.recycler.closed && (r.recycler.z != nil) { |
| z, r.recycler.z = r.recycler.z, nil |
| } else { |
| z = C.ZSTD_createDStream() |
| if z == nil { |
| return errOutOfMemory |
| } |
| } |
| |
| if e := errCode(C.cgozstd_decompress_start(z, |
| (*C.uint8_t)(slicePointer(dictionary)), |
| (C.uint32_t)(len(dictionary)), |
| )); e != 0 { |
| C.ZSTD_freeDCtx(z) |
| if e < 0 { |
| return errZstdVersionTooSmall |
| } |
| return e |
| } |
| |
| r.r = reader |
| r.z = z |
| return nil |
| } |
| |
| // Close implements compression.Reader. |
| 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.readErr = nil |
| r.zstdErr = nil |
| if r.z != nil { |
| if (r.recycler != nil) && !r.recycler.closed && (r.recycler.z == nil) { |
| r.recycler.z, r.z = r.z, nil |
| } else { |
| C.ZSTD_freeDCtx(r.z) |
| r.z = nil |
| } |
| } |
| return nil |
| } |
| |
| // Read implements compression.Reader. |
| func (r *Reader) Read(p []byte) (int, error) { |
| if r == nil { |
| return 0, errNilReceiver |
| } |
| if r.r == nil { |
| return 0, errMissingResetCall |
| } |
| |
| if len(p) > maxLen { |
| p = p[:maxLen] |
| } |
| |
| for numRead := 0; ; { |
| if r.zstdErr != nil { |
| return numRead, r.zstdErr |
| } |
| 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 := errCode(C.cgozstd_decompress(r.z, &r.a, |
| (*C.uint8_t)(unsafe.Pointer(&p[0])), |
| (C.uint32_t)(len(p)), |
| (*C.uint8_t)(unsafe.Pointer(&r.buf[r.i])), |
| (C.uint32_t)(r.j-r.i), |
| )) |
| |
| numRead += int(r.a.ndst) |
| p = p[int(r.a.ndst):] |
| |
| r.i += uint32(r.a.nsrc) |
| |
| if e == 0 { |
| if r.a.eof == 0 { |
| continue |
| } |
| r.zstdErr = io.EOF |
| } else { |
| r.zstdErr = e |
| } |
| return numRead, r.zstdErr |
| } |
| } |
| |
| // WriterRecycler can lessen the new memory allocated when calling Writer.Reset |
| // on a bound Writer. |
| // |
| // It is not safe to use a WriterRecycler and a Writer concurrently. |
| type WriterRecycler struct { |
| z *C.ZSTD_CCtx |
| closed bool |
| } |
| |
| // Bind lets w re-use the memory that is manually managed by c. Call c.Close to |
| // free that memory. |
| func (c *WriterRecycler) Bind(w *Writer) { |
| w.recycler = c |
| } |
| |
| // Close implements io.Closer. |
| func (c *WriterRecycler) Close() error { |
| c.closed = true |
| if c.z != nil { |
| C.ZSTD_freeCCtx(c.z) |
| c.z = nil |
| } |
| return nil |
| } |
| |
| // Writer compresses to the zstd format. |
| // |
| // Compressed bytes may be buffered and not sent to the underlying io.Writer |
| // until Close is called. |
| // |
| // The zero value is not usable until Reset is called. |
| type Writer struct { |
| buf [65536]byte |
| j uint32 |
| w io.Writer |
| |
| writeErr error |
| |
| recycler *WriterRecycler |
| |
| z *C.ZSTD_CCtx |
| a C.advances |
| } |
| |
| func zstdCompressionLevel(level compression.Level) int32 { |
| return level.Interpolate(1, 2, 3, 15, 22) |
| } |
| |
| // Reset implements compression.Writer. |
| func (w *Writer) Reset(writer io.Writer, dictionary []byte, level compression.Level) error { |
| if w == nil { |
| return errNilReceiver |
| } |
| w.close() |
| if writer == nil { |
| return errNilIOWriter |
| } |
| if len(dictionary) > maxLen { |
| dictionary = dictionary[len(dictionary)-maxLen:] |
| } |
| |
| z := (*C.ZSTD_CCtx)(nil) |
| if (w.recycler != nil) && !w.recycler.closed && (w.recycler.z != nil) { |
| z, w.recycler.z = w.recycler.z, nil |
| } else { |
| z = C.ZSTD_createCStream() |
| if z == nil { |
| return errOutOfMemory |
| } |
| } |
| |
| if e := errCode(C.cgozstd_compress_start(z, |
| (*C.uint8_t)(slicePointer(dictionary)), |
| (C.uint32_t)(len(dictionary)), |
| C.int(zstdCompressionLevel(level)), |
| )); e != 0 { |
| C.ZSTD_freeCCtx(z) |
| if e < 0 { |
| return errZstdVersionTooSmall |
| } |
| return e |
| } |
| |
| w.w = writer |
| w.z = z |
| return nil |
| } |
| |
| // Close implements compression.Writer. |
| func (w *Writer) Close() error { |
| if w == nil { |
| return errNilReceiver |
| } |
| err := w.flush(true) |
| w.close() |
| return err |
| } |
| |
| func (w *Writer) flush(final bool) error { |
| if w.w == nil { |
| return nil |
| } |
| |
| if final { |
| if err := w.write(nil, true); err != nil { |
| return err |
| } |
| } |
| |
| if w.j == 0 { |
| return nil |
| } |
| _, err := w.w.Write(w.buf[:w.j]) |
| w.j = 0 |
| return err |
| } |
| |
| func (w *Writer) close() { |
| if w.w == nil { |
| return |
| } |
| w.j = 0 |
| w.w = nil |
| w.writeErr = nil |
| if w.z != nil { |
| if (w.recycler != nil) && !w.recycler.closed && (w.recycler.z == nil) { |
| w.recycler.z, w.z = w.z, nil |
| } else { |
| C.ZSTD_freeCCtx(w.z) |
| w.z = nil |
| } |
| } |
| } |
| |
| // Write implements compression.Writer. |
| func (w *Writer) Write(p []byte) (int, error) { |
| if w == nil { |
| return 0, errNilReceiver |
| } |
| if w.w == nil { |
| return 0, errMissingResetCall |
| } |
| if w.writeErr != nil { |
| return 0, w.writeErr |
| } |
| |
| originalLenP := len(p) |
| for { |
| remaining := []byte(nil) |
| if len(p) > maxLen { |
| p, remaining = p[:maxLen], p[maxLen:] |
| } |
| |
| if err := w.write(p, false); err != nil { |
| return 0, err |
| } |
| |
| p, remaining = remaining, nil |
| if len(p) == 0 { |
| return originalLenP, nil |
| } |
| } |
| } |
| |
| func (w *Writer) write(p []byte, final bool) error { |
| if len(p) > maxLen { |
| panic("unreachable") |
| } |
| |
| for (len(p) > 0) || final { |
| if w.j == uint32(len(w.buf)) { |
| if err := w.flush(false); err != nil { |
| w.writeErr = err |
| return w.writeErr |
| } |
| } |
| |
| e := errCode(0) |
| if final { |
| e = errCode(C.cgozstd_compress_end(w.z, &w.a, |
| (*C.uint8_t)(unsafe.Pointer(&w.buf[w.j])), |
| (C.uint32_t)(uint32(len(w.buf))-w.j), |
| )) |
| final = w.a.eof == 0 |
| } else { |
| e = errCode(C.cgozstd_compress(w.z, &w.a, |
| (*C.uint8_t)(unsafe.Pointer(&w.buf[w.j])), |
| (C.uint32_t)(uint32(len(w.buf))-w.j), |
| (*C.uint8_t)(unsafe.Pointer(&p[0])), |
| (C.uint32_t)(len(p)), |
| )) |
| } |
| |
| w.j += uint32(w.a.ndst) |
| p = p[uint32(w.a.nsrc):] |
| |
| if e != 0 { |
| w.writeErr = e |
| return w.writeErr |
| } |
| } |
| return nil |
| } |