// Copyright 2016 Google Inc. All Rights Reserved. // // Distributed under MIT license. // See file LICENSE for detail or copy at https://opensource.org/licenses/MIT package cbrotli /* #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <brotli/encode.h> struct CompressStreamResult { size_t bytes_consumed; const uint8_t* output_data; size_t output_data_size; int success; int has_more; }; static struct CompressStreamResult CompressStream( BrotliEncoderState* s, BrotliEncoderOperation op, const uint8_t* data, size_t data_size) { struct CompressStreamResult result; size_t available_in = data_size; const uint8_t* next_in = data; size_t available_out = 0; result.success = BrotliEncoderCompressStream(s, op, &available_in, &next_in, &available_out, 0, 0) ? 1 : 0; result.bytes_consumed = data_size - available_in; result.output_data = 0; result.output_data_size = 0; if (result.success) { result.output_data = BrotliEncoderTakeOutput(s, &result.output_data_size); } result.has_more = BrotliEncoderHasMoreOutput(s) ? 1 : 0; return result; } */ import "C" import ( "bytes" "errors" "io" "unsafe" ) // WriterOptions configures Writer. type WriterOptions struct { // Quality controls the compression-speed vs compression-density trade-offs. // The higher the quality, the slower the compression. Range is 0 to 11. Quality int // LGWin is the base 2 logarithm of the sliding window size. // Range is 10 to 24. 0 indicates automatic configuration based on Quality. LGWin int } // Writer implements io.WriteCloser by writing Brotli-encoded data to an // underlying Writer. type Writer struct { dst io.Writer state *C.BrotliEncoderState buf, encoded []byte } var ( errEncode = errors.New("cbrotli: encode error") errWriterClosed = errors.New("cbrotli: Writer is closed") ) // NewWriter initializes new Writer instance. // Close MUST be called to free resources. func NewWriter(dst io.Writer, options WriterOptions) *Writer { state := C.BrotliEncoderCreateInstance(nil, nil, nil) C.BrotliEncoderSetParameter( state, C.BROTLI_PARAM_QUALITY, (C.uint32_t)(options.Quality)) if options.LGWin > 0 { C.BrotliEncoderSetParameter( state, C.BROTLI_PARAM_LGWIN, (C.uint32_t)(options.LGWin)) } return &Writer{ dst: dst, state: state, } } func (w *Writer) writeChunk(p []byte, op C.BrotliEncoderOperation) (n int, err error) { if w.state == nil { return 0, errWriterClosed } for { var data *C.uint8_t if len(p) != 0 { data = (*C.uint8_t)(&p[0]) } result := C.CompressStream(w.state, op, data, C.size_t(len(p))) if result.success == 0 { return n, errEncode } p = p[int(result.bytes_consumed):] n += int(result.bytes_consumed) length := int(result.output_data_size) if length != 0 { // It is a workaround for non-copying-wrapping of native memory. // C-encoder never pushes output block longer than ((2 << 25) + 502). // TODO: use natural wrapper, when it becomes available, see // https://golang.org/issue/13656. output := (*[1 << 30]byte)(unsafe.Pointer(result.output_data))[:length:length] _, err = w.dst.Write(output) if err != nil { return n, err } } if len(p) == 0 && result.has_more == 0 { return n, nil } } } // Flush outputs encoded data for all input provided to Write. The resulting // output can be decoded to match all input before Flush, but the stream is // not yet complete until after Close. // Flush has a negative impact on compression. func (w *Writer) Flush() error { _, err := w.writeChunk(nil, C.BROTLI_OPERATION_FLUSH) return err } // Close flushes remaining data to the decorated writer and frees C resources. func (w *Writer) Close() error { // If stream is already closed, it is reported by `writeChunk`. _, err := w.writeChunk(nil, C.BROTLI_OPERATION_FINISH) // C-Brotli tolerates `nil` pointer here. C.BrotliEncoderDestroyInstance(w.state) w.state = nil return err } // Write implements io.Writer. Flush or Close must be called to ensure that the // encoded bytes are actually flushed to the underlying Writer. func (w *Writer) Write(p []byte) (n int, err error) { return w.writeChunk(p, C.BROTLI_OPERATION_PROCESS) } // Encode returns content encoded with Brotli. func Encode(content []byte, options WriterOptions) ([]byte, error) { var buf bytes.Buffer writer := NewWriter(&buf, options) _, err := writer.Write(content) if closeErr := writer.Close(); err == nil { err = closeErr } return buf.Bytes(), err }