| package zstd |
| |
| /* |
| #include "zstd.h" |
| */ |
| import "C" |
| import ( |
| "bytes" |
| "io/ioutil" |
| "runtime" |
| "unsafe" |
| ) |
| |
| type Ctx interface { |
| // Compress src into dst. If you have a buffer to use, you can pass it to |
| // prevent allocation. If it is too small, or if nil is passed, a new buffer |
| // will be allocated and returned. |
| Compress(dst, src []byte) ([]byte, error) |
| |
| // CompressLevel is the same as Compress but you can pass a compression level |
| CompressLevel(dst, src []byte, level int) ([]byte, error) |
| |
| // Decompress src into dst. If you have a buffer to use, you can pass it to |
| // prevent allocation. If it is too small, or if nil is passed, a new buffer |
| // will be allocated and returned. |
| Decompress(dst, src []byte) ([]byte, error) |
| } |
| |
| type ctx struct { |
| cctx *C.ZSTD_CCtx |
| dctx *C.ZSTD_DCtx |
| } |
| |
| // Create a new ZStd Context. |
| // When compressing/decompressing many times, it is recommended to allocate a |
| // context just once, and re-use it for each successive compression operation. |
| // This will make workload friendlier for system's memory. |
| // Note : re-using context is just a speed / resource optimization. |
| // It doesn't change the compression ratio, which remains identical. |
| // Note 2 : In multi-threaded environments, |
| // use one different context per thread for parallel execution. |
| // |
| func NewCtx() Ctx { |
| c := &ctx{ |
| cctx: C.ZSTD_createCCtx(), |
| dctx: C.ZSTD_createDCtx(), |
| } |
| |
| runtime.SetFinalizer(c, finalizeCtx) |
| return c |
| } |
| |
| func (c *ctx) Compress(dst, src []byte) ([]byte, error) { |
| return c.CompressLevel(dst, src, DefaultCompression) |
| } |
| |
| func (c *ctx) CompressLevel(dst, src []byte, level int) ([]byte, error) { |
| bound := CompressBound(len(src)) |
| if cap(dst) >= bound { |
| dst = dst[0:bound] // Reuse dst buffer |
| } else { |
| dst = make([]byte, bound) |
| } |
| |
| // We need unsafe.Pointer(&src[0]) in the Cgo call to avoid "Go pointer to Go pointer" panics. |
| // This means we need to special case empty input. See: |
| // https://github.com/golang/go/issues/14210#issuecomment-346402945 |
| var cWritten C.size_t |
| if len(src) == 0 { |
| cWritten = C.ZSTD_compressCCtx( |
| c.cctx, |
| unsafe.Pointer(&dst[0]), |
| C.size_t(len(dst)), |
| unsafe.Pointer(nil), |
| C.size_t(0), |
| C.int(level)) |
| } else { |
| cWritten = C.ZSTD_compressCCtx( |
| c.cctx, |
| unsafe.Pointer(&dst[0]), |
| C.size_t(len(dst)), |
| unsafe.Pointer(&src[0]), |
| C.size_t(len(src)), |
| C.int(level)) |
| } |
| |
| written := int(cWritten) |
| // Check if the return is an Error code |
| if err := getError(written); err != nil { |
| return nil, err |
| } |
| return dst[:written], nil |
| } |
| |
| func (c *ctx) Decompress(dst, src []byte) ([]byte, error) { |
| if len(src) == 0 { |
| return []byte{}, ErrEmptySlice |
| } |
| |
| bound := decompressSizeHint(src) |
| if cap(dst) >= bound { |
| dst = dst[0:cap(dst)] |
| } else { |
| dst = make([]byte, bound) |
| } |
| |
| written := int(C.ZSTD_decompressDCtx( |
| c.dctx, |
| unsafe.Pointer(&dst[0]), |
| C.size_t(len(dst)), |
| unsafe.Pointer(&src[0]), |
| C.size_t(len(src)))) |
| |
| err := getError(written) |
| if err == nil { |
| return dst[:written], nil |
| } |
| if !IsDstSizeTooSmallError(err) { |
| return nil, err |
| } |
| |
| // We failed getting a dst buffer of correct size, use stream API |
| r := NewReader(bytes.NewReader(src)) |
| defer r.Close() |
| return ioutil.ReadAll(r) |
| } |
| |
| func finalizeCtx(c *ctx) { |
| C.ZSTD_freeCCtx(c.cctx) |
| C.ZSTD_freeDCtx(c.dctx) |
| } |