diff options
Diffstat (limited to 'go/cbrotli/cbrotli.go')
-rwxr-xr-x | go/cbrotli/cbrotli.go | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/go/cbrotli/cbrotli.go b/go/cbrotli/cbrotli.go new file mode 100755 index 0000000..a7009d0 --- /dev/null +++ b/go/cbrotli/cbrotli.go @@ -0,0 +1,254 @@ +// 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 compresses and decompresses data with C-Brotli library. +package cbrotli + +import ( + "bytes" + "io" + + "github.com/google/brotli/go/cbrotli/internal/decoder" + "github.com/google/brotli/go/cbrotli/internal/encoder" +) + +// An internalError reports an error in the (c-)brotli code itself. +type internalError string + +func (e internalError) Error() string { + return "cbrotli: internal error: " + string(e) +} + +//------------------------------------------------------------------------------ +// Encoder +//------------------------------------------------------------------------------ + +// 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, an io.Writer decorator that produces +// Brotli-encoded data. +type Writer struct { + dst io.Writer + encoder encoder.Encoder + closed bool +} + +// NewWriter initializes new Writer instance. +// Close MUST be called to free resources. +func NewWriter(dst io.Writer, options WriterOptions) *Writer { + return &Writer{ + dst: dst, + encoder: encoder.New(options.Quality, options.LGWin), + } +} + +// Close implements io.Closer. Close MUST be invoked to free native resources. +// Also Close implicitly flushes remaining data to the decorated writer. +func (z *Writer) Close() error { + if z.closed { + return nil + } + defer z.encoder.Close() + _, err := z.writeChunk(nil, encoder.Finish) + z.closed = true + return err +} + +func (z *Writer) writeChunk(p []byte, op encoder.Operation) (int, error) { + if z.closed { + return 0, internalError("write after close") + } + var totalBytesConsumed int + var err error + for { + bytesConsumed, output, status := z.encoder.CompressStream(p, op) + if status == encoder.Error { + err = internalError("encoder failure") + break + } + p = p[bytesConsumed:] + totalBytesConsumed += bytesConsumed + _, err = z.dst.Write(output) + if err != nil { + break + } + if len(p) == 0 && status == encoder.Done { + break + } + } + return totalBytesConsumed, err +} + +// Write implements io.Writer. +func (z *Writer) Write(p []byte) (int, error) { + return z.writeChunk(p, encoder.Process) +} + +// 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 (z *Writer) Flush() error { + _, err := z.writeChunk(nil, encoder.Finish) + return err +} + +// Encode returns content encoded with Brotli. +func Encode(content []byte, options WriterOptions) ([]byte, error) { + var buf bytes.Buffer + writer := NewWriter(&buf, options) + defer writer.Close() + _, err := writer.Write(content) + if err != nil { + return nil, err + } + if err := writer.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +//------------------------------------------------------------------------------ +// Decoder +//------------------------------------------------------------------------------ + +// Reader implements io.ReadCloser, an io.Reader decorator that decodes +// Brotli-encoded data. +type Reader struct { + src io.Reader + decoder decoder.Decoder + buf []byte // intermediate read buffer pointed to by next + eof bool // true if all compressed stream is decoded + next []byte // buffered data to be passed to decoder + output []byte // data produced by decoder, but not yet consumed + srcErr error // last source reader error + err error // reader state; nil if it is OK to read further + closed bool // true is stream is already closed +} + +// NewReader initializes new Reader instance. +// Close MUST be called to free resources. +func NewReader(src io.Reader) *Reader { + return &Reader{ + src: src, + decoder: decoder.New(), + buf: make([]byte, 32*1024), + eof: false, + } +} + +// Close implements io.Closer. Close MUST be invoked to free native resources. +func (z *Reader) Close() error { + if z.closed { + return nil + } + z.decoder.Close() + z.err = internalError("read after close") + z.closed = true + return nil +} + +func isEOF(src io.Reader) bool { + n, err := src.Read(make([]byte, 1)) + return n == 0 && err == io.EOF +} + +// Read implements io.Reader. +func (z *Reader) Read(p []byte) (int, error) { + // Any error state is unrecoverable. + if z.err != nil { + return 0, z.err + } + // See io.Reader documentation. + if len(p) == 0 { + return 0, nil + } + + var totalOutBytes int + + // There is no practical limit for amount of bytes being consumed by decoder + // before producing any output. Continue feeding decoder until some data is + // produced + for { + // Push already produced output first. + if outBytes := len(z.output); outBytes != 0 { + outBytes = copy(p, z.output) + p = p[outBytes:] + z.output = z.output[outBytes:] + totalOutBytes += outBytes + // Output buffer is full. + if len(p) == 0 { + break + } + continue + } + // No more produced output left. + // If no more output is expected, then we are finished. + if z.eof { + z.err = io.EOF + break + } + // Replenish buffer (might cause blocking read), only if necessary. + if len(z.next) == 0 && totalOutBytes == 0 && z.srcErr != io.EOF { + var n int + n, z.srcErr = z.src.Read(z.buf) + z.next = z.buf[:n] + if z.srcErr != nil && z.srcErr != io.EOF { + z.err = z.srcErr + break + } + } + // Do decoding. + consumed, output, status := z.decoder.DecompressStream(z.next) + z.output = output + z.next = z.next[consumed:] + if status == decoder.Error { + // When error happens, the remaining output does not matter. + z.err = internalError("decoder failure") + break + } else if status == decoder.Done { + // Decoder stream is closed; no further input is expected. + if len(z.next) != 0 || (z.srcErr != io.EOF && !isEOF(z.src)) { + z.err = internalError("excessive input") + break + } + // No more output is expected; keep pushing output. + z.eof = true + continue + } else { + // If can not move any further... + if consumed == 0 && len(z.output) == 0 { + // Unexpected end of input. + if z.srcErr == io.EOF || totalOutBytes == 0 { + z.err = io.ErrUnexpectedEOF + } + // Postpone blocking reads for the next invocation. + break + } + // Continue pushing output. + } + } + return totalOutBytes, z.err +} + +// Decode decodes Brotli encoded data. +func Decode(encodedData []byte) ([]byte, error) { + var buf bytes.Buffer + reader := NewReader(bytes.NewReader(encodedData)) + defer reader.Close() + _, err := io.Copy(&buf, reader) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} |