diff options
author | Eugene Kliuchnikov <eustas@google.com> | 2017-03-22 12:41:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-22 12:41:19 +0100 |
commit | a657d9969dfb9655f4b483a658ad5da9dda3e52e (patch) | |
tree | 4c422b8d194ab563edf0c50dfc2045b7487dc91a /go | |
parent | 8a06e02935abadcfff46099c43c879a677d56280 (diff) | |
download | brotli-a657d9969dfb9655f4b483a658ad5da9dda3e52e.zip brotli-a657d9969dfb9655f4b483a658ad5da9dda3e52e.tar.gz brotli-a657d9969dfb9655f4b483a658ad5da9dda3e52e.tar.bz2 |
Add go wrapper, streamline java decoder: (#524)
* add (c)brotli golang wrapper
* remove (language-specific) enums in java decoder
Diffstat (limited to 'go')
-rwxr-xr-x | go/cbrotli/BUILD | 23 | ||||
-rwxr-xr-x | go/cbrotli/cbrotli.go | 254 | ||||
-rwxr-xr-x | go/cbrotli/cbrotli_test.go | 271 | ||||
-rwxr-xr-x | go/cbrotli/internal/BUILD | 19 | ||||
-rwxr-xr-x | go/cbrotli/internal/decoder.go | 110 | ||||
-rwxr-xr-x | go/cbrotli/internal/encoder.go | 135 |
6 files changed, 812 insertions, 0 deletions
diff --git a/go/cbrotli/BUILD b/go/cbrotli/BUILD new file mode 100755 index 0000000..4bea8a0 --- /dev/null +++ b/go/cbrotli/BUILD @@ -0,0 +1,23 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # MIT + +load("@io_bazel_rules_go//go:def.bzl", "go_prefix", "go_library", "go_test") + +go_prefix("github.com/google/brotli") + +go_library( + name = "cbrotli", + srcs = ["cbrotli.go"], + deps = [ + "//go/cbrotli/internal:decoder", + "//go/cbrotli/internal:encoder", + ], +) + +go_test( + name = "cbrotli_test", + size = "small", + srcs = ["cbrotli_test.go"], + library = ":cbrotli", +) 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 +} diff --git a/go/cbrotli/cbrotli_test.go b/go/cbrotli/cbrotli_test.go new file mode 100755 index 0000000..4fe0960 --- /dev/null +++ b/go/cbrotli/cbrotli_test.go @@ -0,0 +1,271 @@ +// 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 + +import ( + "bytes" + "fmt" + "io" + "math" + "math/rand" + "testing" +) + +func checkCompressedData(compressedData, wantOriginalData []byte) error { + uncompressed, err := Decode(compressedData) + if err != nil { + return fmt.Errorf("brotli decompress failed: %v", err) + } + if !bytes.Equal(uncompressed, wantOriginalData) { + if len(wantOriginalData) != len(uncompressed) { + return fmt.Errorf(""+ + "Data doesn't uncompress to the original value.\n"+ + "Length of original: %v\n"+ + "Length of uncompressed: %v", + len(wantOriginalData), len(uncompressed)) + } + for i := range wantOriginalData { + if wantOriginalData[i] != uncompressed[i] { + return fmt.Errorf(""+ + "Data doesn't uncompress to the original value.\n"+ + "Original at %v is %v\n"+ + "Uncompressed at %v is %v", + i, wantOriginalData[i], i, uncompressed[i]) + } + } + } + return nil +} + +func TestEncoderNoWrite(t *testing.T) { + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 5}) + if err := e.Close(); err != nil { + t.Errorf("Close()=%v, want nil", err) + } + // Check Write after close. + if _, err := e.Write([]byte("hi")); err == nil { + t.Errorf("No error after Close() + Write()") + } +} + +func TestEncoderEmptyWrite(t *testing.T) { + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 5}) + n, err := e.Write([]byte("")) + if n != 0 || err != nil { + t.Errorf("Write()=%v,%v, want 0, nil", n, err) + } + if err := e.Close(); err != nil { + t.Errorf("Close()=%v, want nil", err) + } +} + +func TestWriter(t *testing.T) { + // Test basic encoder usage. + input := []byte("<html><body><H1>Hello world</H1></body></html>") + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 1}) + in := bytes.NewReader([]byte(input)) + n, err := io.Copy(e, in) + if err != nil { + t.Errorf("Copy Error: %v", err) + } + if int(n) != len(input) { + t.Errorf("Copy() n=%v, want %v", n, len(input)) + } + if err := e.Close(); err != nil { + t.Errorf("Close Error after copied %d bytes: %v", n, err) + } + if err := checkCompressedData(out.Bytes(), input); err != nil { + t.Error(err) + } +} + +func TestEncoderStreams(t *testing.T) { + // Test that output is streamed. + // Adjust window size to ensure the encoder outputs at least enough bytes + // to fill the window. + const lgWin = 16 + windowSize := int(math.Pow(2, lgWin)) + input := make([]byte, 2*windowSize) + rand.Read(input) + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 11, LGWin: lgWin}) + halfInput := input[:len(input)/2] + in := bytes.NewReader(halfInput) + + n, err := io.Copy(e, in) + if err != nil { + t.Errorf("Copy Error: %v", err) + } + + // We've fed more data than the sliding window size. Check that some + // compressed data has been output. + if out.Len() == 0 { + t.Errorf("Output length is after %d bytes written", n) + } + if err := e.Close(); err != nil { + t.Errorf("Close Error after copied %d bytes: %v", n, err) + } + if err := checkCompressedData(out.Bytes(), halfInput); err != nil { + t.Error(err) + } +} + +func TestEncoderLargeInput(t *testing.T) { + input := make([]byte, 1000000) + rand.Read(input) + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 5}) + in := bytes.NewReader(input) + + n, err := io.Copy(e, in) + if err != nil { + t.Errorf("Copy Error: %v", err) + } + if int(n) != len(input) { + t.Errorf("Copy() n=%v, want %v", n, len(input)) + } + if err := e.Close(); err != nil { + t.Errorf("Close Error after copied %d bytes: %v", n, err) + } + if err := checkCompressedData(out.Bytes(), input); err != nil { + t.Error(err) + } +} + +func TestEncoderFlush(t *testing.T) { + input := make([]byte, 1000) + rand.Read(input) + out := bytes.Buffer{} + e := NewWriter(&out, WriterOptions{Quality: 5}) + in := bytes.NewReader(input) + _, err := io.Copy(e, in) + if err != nil { + t.Fatalf("Copy Error: %v", err) + } + if err := e.Flush(); err != nil { + t.Fatalf("Flush(): %v", err) + } + decompressed := make([]byte, 1000) + reader := NewReader(bytes.NewReader(out.Bytes())) + n, err := reader.Read(decompressed) + if n != len(decompressed) || err != nil { + t.Errorf("Expected <%v, nil>, but <%v, %v>", len(decompressed), n, err) + } + reader.Close() + if !bytes.Equal(decompressed, input) { + t.Errorf(""+ + "Decompress after flush: %v\n"+ + "%q\n"+ + "want:\n%q", + err, decompressed, input) + } + if err := e.Close(); err != nil { + t.Errorf("Close(): %v", err) + } +} + +func TestReader(t *testing.T) { + content := bytes.Repeat([]byte("hello world!"), 10000) + encoded, _ := Encode(content, WriterOptions{Quality: 5}) + r := NewReader(bytes.NewReader(encoded)) + var decodedOutput bytes.Buffer + n, err := io.Copy(&decodedOutput, r) + if err != nil { + t.Fatalf("Copy(): n=%v, err=%v", n, err) + } + if err := r.Close(); err != nil { + t.Errorf("Close(): %v", err) + } + if got := decodedOutput.Bytes(); !bytes.Equal(got, content) { + t.Errorf(""+ + "Reader output:\n"+ + "%q\n"+ + "want:\n"+ + "%q", + got, content) + } +} + +func TestDecode(t *testing.T) { + content := bytes.Repeat([]byte("hello world!"), 10000) + encoded, _ := Encode(content, WriterOptions{Quality: 5}) + decoded, err := Decode(encoded) + if err != nil { + t.Errorf("Decode: %v", err) + } + if !bytes.Equal(decoded, content) { + t.Errorf(""+ + "Decode content:\n"+ + "%q\n"+ + "want:\n"+ + "%q", + decoded, content) + } +} + +func TestDecodeFuzz(t *testing.T) { + // Test that the decoder terminates with corrupted input. + content := bytes.Repeat([]byte("hello world!"), 100) + src := rand.NewSource(0) + encoded, _ := Encode(content, WriterOptions{Quality: 5}) + for i := 0; i < 100; i++ { + enc := append([]byte{}, encoded...) + for j := 0; j < 5; j++ { + enc[int(src.Int63())%len(enc)] = byte(src.Int63() % 256) + } + Decode(enc) + } +} + +func TestDecodeTrailingData(t *testing.T) { + content := bytes.Repeat([]byte("hello world!"), 100) + encoded, _ := Encode(content, WriterOptions{Quality: 5}) + _, err := Decode(append(encoded, 0)) + if err == nil { + t.Errorf("Expected 'excessive input' error") + } +} + +func TestEncodeDecode(t *testing.T) { + for _, test := range []struct { + data []byte + repeats int + }{ + {nil, 0}, + {[]byte("A"), 1}, + {[]byte("<html><body><H1>Hello world</H1></body></html>"), 10}, + {[]byte("<html><body><H1>Hello world</H1></body></html>"), 1000}, + } { + t.Logf("case %q x %d", test.data, test.repeats) + input := bytes.Repeat(test.data, test.repeats) + encoded, err := Encode(input, WriterOptions{Quality: 5}) + if err != nil { + t.Errorf("Encode: %v", err) + } + // Inputs are compressible, but may be too small to compress. + if maxSize := len(input)/2 + 20; len(encoded) >= maxSize { + t.Errorf(""+ + "Encode returned %d bytes, want <%d\n"+ + "Encoded=%q", + len(encoded), maxSize, encoded) + } + decoded, err := Decode(encoded) + if err != nil { + t.Errorf("Decode: %v", err) + } + if !bytes.Equal(decoded, input) { + t.Errorf(""+ + "Decode content:\n"+ + "%q\n"+ + "want:\n"+ + "%q", + decoded, input) + } + } +} diff --git a/go/cbrotli/internal/BUILD b/go/cbrotli/internal/BUILD new file mode 100755 index 0000000..a3e3b87 --- /dev/null +++ b/go/cbrotli/internal/BUILD @@ -0,0 +1,19 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # MIT + +load("@io_bazel_rules_go//go:def.bzl", "cgo_library") + +cgo_library( + name = "decoder", + srcs = ["decoder.go"], + visibility = ["//go/cbrotli:__subpackages__"], + cdeps = ["//:brotlidec"], +) + +cgo_library( + name = "encoder", + srcs = ["encoder.go"], + visibility = ["//go/cbrotli:__subpackages__"], + cdeps = ["//:brotlienc"], +) diff --git a/go/cbrotli/internal/decoder.go b/go/cbrotli/internal/decoder.go new file mode 100755 index 0000000..74d4c21 --- /dev/null +++ b/go/cbrotli/internal/decoder.go @@ -0,0 +1,110 @@ +// 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 decoder wraps the brotli decoder C API used by package brotli. +package decoder + +/* +#include <brotli/decode.h> + +// Wrap BrotliDecoderDecompressStream so that it doesn't take variable (in-out) +// pointers. Instead of updated pointer, deltas are saved in auxiliary struct. + +struct DecompressStreamResult { + size_t bytes_consumed; + const uint8_t* output_data; + size_t output_data_size; + BrotliDecoderResult status; +}; + +struct DecompressStreamResult DecompressStream(BrotliDecoderState* s, + const uint8_t* encoded_data, size_t encoded_data_size) { + struct DecompressStreamResult result; + size_t available_in = encoded_data_size; + const uint8_t* next_in = encoded_data; + size_t available_out = 0; + result.status = BrotliDecoderDecompressStream(s, + &available_in, &next_in, &available_out, 0, 0); + result.bytes_consumed = encoded_data_size - available_in; + result.output_data = 0; + result.output_data_size = 0; + if (result.status != BROTLI_DECODER_RESULT_ERROR) { + result.output_data = BrotliDecoderTakeOutput(s, &result.output_data_size); + if (BrotliDecoderIsFinished(s)) { + result.status = BROTLI_DECODER_RESULT_SUCCESS; + } + } + return result; +} + +*/ +import "C" +import ( + "unsafe" +) + +// Status represents internal state after DecompressStream invokation +type Status int + +const ( + // Error happened + Error Status = iota + // Done means that no more output will be produced + Done + // Ok means that more output might be produced with no additional input + Ok +) + +// Decoder is the Brotli c-decoder handle. +type Decoder struct { + state *C.BrotliDecoderState +} + +// New returns a new Brotli c-decoder handle. +// Close MUST be called to free resources. +func New() Decoder { + return Decoder{state: C.BrotliDecoderCreateInstance(nil, nil, nil)} +} + +// Close frees resources used by decoder. +func (z *Decoder) Close() { + C.BrotliDecoderDestroyInstance(z.state) + z.state = nil +} + +func goStatus(cStatus C.BrotliDecoderResult) (status Status) { + switch cStatus { + case C.BROTLI_DECODER_RESULT_SUCCESS: + return Done + case C.BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: + return Ok + case C.BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: + return Ok + } + return Error +} + +// cBytes casts a Go []byte into a C uint8_t*. We pass &buf[0] directly to C, +// which is legal because C doesn't save the pointer longer than the call and +// the byte array itself doesn't contain any pointers. +func cBytes(buf []byte) (*C.uint8_t, C.size_t) { + if len(buf) == 0 { + return (*C.uint8_t)(nil), 0 + } + return (*C.uint8_t)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)) +} + +// DecompressStream reads Brotli-encoded bytes from in, and returns produced +// bytes. Output contents should not be modified. Liveness of output is +// hard-limited by Decoder liveness; slice becomes invalid when any Decoder +// method is invoked. +func (z *Decoder) DecompressStream(in []byte) ( + bytesConsumed int, output []byte, status Status) { + cin, cinSize := cBytes(in) + result := C.DecompressStream(z.state, cin, cinSize) + output = C.GoBytes( + unsafe.Pointer(result.output_data), C.int(result.output_data_size)) + return int(result.bytes_consumed), output, goStatus(result.status) +} diff --git a/go/cbrotli/internal/encoder.go b/go/cbrotli/internal/encoder.go new file mode 100755 index 0000000..b8dd21c --- /dev/null +++ b/go/cbrotli/internal/encoder.go @@ -0,0 +1,135 @@ +// 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 encoder wraps the brotli encoder C API used by package brotli. +package encoder + +/* +#include <brotli/encode.h> + +// Wrap BrotliEncoderCompressStream so that it doesn't take variable (in-out) +// pointers. Instead of updated pointer, deltas are saved in auxiliary struct. +struct CompressStreamResult { + size_t bytes_consumed; + const uint8_t* output_data; + size_t output_data_size; + int success; + int has_more; +}; + +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 ( + "unsafe" +) + +// Operation represents type of request to CompressStream +type Operation int + +const ( + // Process input + Process Operation = iota + // Flush input processed so far + Flush + // Finish stream + Finish +) + +// Status represents internal state after CompressStream invocation +type Status int + +const ( + // Error happened + Error Status = iota + // Done means that no more output will be produced + Done + // Ok means that more output might be produced with no additional input + Ok +) + +// Encoder is the Brotli c-encoder handle. +type Encoder struct { + state *C.BrotliEncoderState +} + +// New returns a new Brotli c-encoder handle. +// quality and lgWin are described in third_party/Brotli/enc/encode.h. +// Close MUST be called to free resources. +func New(quality, lgWin int) Encoder { + state := C.BrotliEncoderCreateInstance(nil, nil, nil) + // TODO(b/18187008): Check if LGBLOCK or MODE are useful to Flywheel. + C.BrotliEncoderSetParameter( + state, C.BROTLI_PARAM_QUALITY, (C.uint32_t)(quality)) + C.BrotliEncoderSetParameter( + state, C.BROTLI_PARAM_LGWIN, (C.uint32_t)(lgWin)) + return Encoder{state} +} + +// Close frees resources used by encoder. +func (z *Encoder) Close() { + C.BrotliEncoderDestroyInstance(z.state) + z.state = nil +} + +// cBytes casts a Go []byte into a C uint8_t*. We pass &buf[0] directly to C, +// which is legal because C doesn't save the pointer longer than the call and +// the byte array itself doesn't contain any pointers. +func cBytes(buf []byte) (*C.uint8_t, C.size_t) { + if len(buf) == 0 { + return (*C.uint8_t)(nil), 0 + } + return (*C.uint8_t)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)) +} + +func cOperation(op Operation) (cOp C.BrotliEncoderOperation) { + switch op { + case Flush: + return C.BROTLI_OPERATION_FLUSH + case Finish: + return C.BROTLI_OPERATION_FINISH + } + return C.BROTLI_OPERATION_PROCESS +} + +// CompressStream processes data and produces Brotli-encoded bytes. Encoder may +// consume considerable amount of input before the first output bytes come out. +// Flush and Finish operations force Encoder to produce output that corresponds +// to input consumed so far. Output contents should not be modified. Liveness of +// output is hard-limited by Encoder liveness; slice becomes invalid when any +// Encoder method is invoked. +func (z *Encoder) CompressStream(in []byte, op Operation) ( + bytesConsumed int, output []byte, status Status) { + cin, cinSize := cBytes(in) + result := C.CompressStream(z.state, cOperation(op), cin, cinSize) + output = C.GoBytes( + unsafe.Pointer(result.output_data), C.int(result.output_data_size)) + var outcome Status + if result.success == 0 { + outcome = Error + } else if result.has_more != 0 { + outcome = Ok + } else { + outcome = Done + } + return int(result.bytes_consumed), output, outcome +} |