diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go')
7 files changed, 749 insertions, 0 deletions
diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/BUILD b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/BUILD new file mode 100644 index 000000000..72a5317f1 --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/BUILD @@ -0,0 +1,2 @@ +# Description: +# cbrotli is a CGo wrapper for Brotli, a generic-purpose lossless compression algorithm. diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/WORKSPACE b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/WORKSPACE new file mode 100644 index 000000000..e9652697c --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/WORKSPACE @@ -0,0 +1,21 @@ +workspace(name = "org_brotli_go") + +local_repository( + name = "org_brotli", + path = "..", +) + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "io_bazel_rules_go", + urls = [ + "https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/rules_go/releases/download/0.19.0/rules_go-0.19.0.tar.gz", + "https://github.com/bazelbuild/rules_go/releases/download/0.19.0/rules_go-0.19.0.tar.gz", + ], + sha256 = "9fb16af4d4836c8222142e54c9efa0bb5fc562ffc893ce2abeac3e25daead144", +) + +load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains") +go_rules_dependencies() +go_register_toolchains() diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/BUILD b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/BUILD new file mode 100644 index 000000000..ce594ad19 --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # MIT + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "cbrotli", + srcs = [ + "reader.go", + "writer.go", + ], + cdeps = [ + "@org_brotli//:brotlidec", + "@org_brotli//:brotlienc", + ], + cgo = True, + importpath = "github.com/google/brotli/go/cbrotli", +) + +go_test( + name = "cbrotli_test", + size = "small", + srcs = ["cbrotli_test.go"], + embed = [":cbrotli"], +) diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cbrotli_test.go b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cbrotli_test.go new file mode 100644 index 000000000..f59ec582d --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cbrotli_test.go @@ -0,0 +1,367 @@ +// 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" + "io/ioutil" + "math" + "math/rand" + "testing" + "time" +) + +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, 8*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 0 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) + } + if out.Len() == 0 { + t.Fatalf("0 bytes written after Flush()") + } + 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) + } +} + +type readerWithTimeout struct { + io.ReadCloser +} + +func (r readerWithTimeout) Read(p []byte) (int, error) { + type result struct { + n int + err error + } + ch := make(chan result) + go func() { + n, err := r.ReadCloser.Read(p) + ch <- result{n, err} + }() + select { + case result := <-ch: + return result.n, result.err + case <-time.After(5 * time.Second): + return 0, fmt.Errorf("read timed out") + } +} + +func TestDecoderStreaming(t *testing.T) { + pr, pw := io.Pipe() + writer := NewWriter(pw, WriterOptions{Quality: 5, LGWin: 20}) + reader := readerWithTimeout{NewReader(pr)} + defer func() { + if err := reader.Close(); err != nil { + t.Errorf("reader.Close: %v", err) + } + go ioutil.ReadAll(pr) // swallow the "EOF" token from writer.Close + if err := writer.Close(); err != nil { + t.Errorf("writer.Close: %v", err) + } + }() + + ch := make(chan []byte) + errch := make(chan error) + go func() { + for { + segment, ok := <-ch + if !ok { + return + } + if n, err := writer.Write(segment); err != nil || n != len(segment) { + errch <- fmt.Errorf("write=%v,%v, want %v,%v", n, err, len(segment), nil) + return + } + if err := writer.Flush(); err != nil { + errch <- fmt.Errorf("flush: %v", err) + return + } + } + }() + defer close(ch) + + segments := [...][]byte{ + []byte("first"), + []byte("second"), + []byte("third"), + } + for k, segment := range segments { + t.Run(fmt.Sprintf("Segment%d", k), func(t *testing.T) { + select { + case ch <- segment: + case err := <-errch: + t.Fatalf("write: %v", err) + case <-time.After(5 * time.Second): + t.Fatalf("timed out") + } + wantLen := len(segment) + got := make([]byte, wantLen) + if n, err := reader.Read(got); err != nil || n != wantLen || !bytes.Equal(got, segment) { + t.Fatalf("read[%d]=%q,%v,%v, want %q,%v,%v", k, got, n, err, segment, wantLen, nil) + } + }) + } +} + +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"+ + "<%d bytes>", + got, len(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"+ + "<%d bytes>", + decoded, len(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, err := Encode(content, WriterOptions{Quality: 5}) + if err != nil { + t.Fatalf("Encode(<%d bytes>, _) = _, %s", len(content), err) + } + if len(encoded) == 0 { + t.Fatalf("Encode(<%d bytes>, _) produced empty output", len(content)) + } + 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) { + var want string + if len(input) > 320 { + want = fmt.Sprintf("<%d bytes>", len(input)) + } else { + want = fmt.Sprintf("%q", input) + } + t.Errorf(""+ + "Decode content:\n"+ + "%q\n"+ + "want:\n"+ + "%s", + decoded, want) + } + } +} diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cgo.go b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cgo.go new file mode 100644 index 000000000..f953f72cd --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/cgo.go @@ -0,0 +1,13 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Distributed under MIT license. +// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT + +package cbrotli + +// Inform golang build system that it should link brotli libraries. + +// #cgo LDFLAGS: -lbrotlicommon +// #cgo LDFLAGS: -lbrotlidec +// #cgo LDFLAGS: -lbrotlienc +import "C" diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/reader.go b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/reader.go new file mode 100644 index 000000000..3d8d42450 --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/reader.go @@ -0,0 +1,161 @@ +// 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 + +/* +#include <stddef.h> +#include <stdint.h> + +#include <brotli/decode.h> + +static BrotliDecoderResult DecompressStream(BrotliDecoderState* s, + uint8_t* out, size_t out_len, + const uint8_t* in, size_t in_len, + size_t* bytes_written, + size_t* bytes_consumed) { + size_t in_remaining = in_len; + size_t out_remaining = out_len; + BrotliDecoderResult result = BrotliDecoderDecompressStream( + s, &in_remaining, &in, &out_remaining, &out, NULL); + *bytes_written = out_len - out_remaining; + *bytes_consumed = in_len - in_remaining; + return result; +} +*/ +import "C" + +import ( + "bytes" + "errors" + "io" + "io/ioutil" +) + +type decodeError C.BrotliDecoderErrorCode + +func (err decodeError) Error() string { + return "cbrotli: " + + C.GoString(C.BrotliDecoderErrorString(C.BrotliDecoderErrorCode(err))) +} + +var errExcessiveInput = errors.New("cbrotli: excessive input") +var errInvalidState = errors.New("cbrotli: invalid state") +var errReaderClosed = errors.New("cbrotli: Reader is closed") + +// Reader implements io.ReadCloser by reading Brotli-encoded data from an +// underlying Reader. +type Reader struct { + src io.Reader + state *C.BrotliDecoderState + buf []byte // scratch space for reading from src + in []byte // current chunk to decode; usually aliases buf +} + +// readBufSize is a "good" buffer size that avoids excessive round-trips +// between C and Go but doesn't waste too much memory on buffering. +// It is arbitrarily chosen to be equal to the constant used in io.Copy. +const readBufSize = 32 * 1024 + +// NewReader initializes new Reader instance. +// Close MUST be called to free resources. +func NewReader(src io.Reader) *Reader { + return &Reader{ + src: src, + state: C.BrotliDecoderCreateInstance(nil, nil, nil), + buf: make([]byte, readBufSize), + } +} + +// Close implements io.Closer. Close MUST be invoked to free native resources. +func (r *Reader) Close() error { + if r.state == nil { + return errReaderClosed + } + // Close despite the state; i.e. there might be some unread decoded data. + C.BrotliDecoderDestroyInstance(r.state) + r.state = nil + return nil +} + +func (r *Reader) Read(p []byte) (n int, err error) { + if int(C.BrotliDecoderHasMoreOutput(r.state)) == 0 && len(r.in) == 0 { + m, readErr := r.src.Read(r.buf) + if m == 0 { + // If readErr is `nil`, we just proxy underlying stream behavior. + return 0, readErr + } + r.in = r.buf[:m] + } + + if len(p) == 0 { + return 0, nil + } + + for { + var written, consumed C.size_t + var data *C.uint8_t + if len(r.in) != 0 { + data = (*C.uint8_t)(&r.in[0]) + } + result := C.DecompressStream(r.state, + (*C.uint8_t)(&p[0]), C.size_t(len(p)), + data, C.size_t(len(r.in)), + &written, &consumed) + r.in = r.in[int(consumed):] + n = int(written) + + switch result { + case C.BROTLI_DECODER_RESULT_SUCCESS: + if len(r.in) > 0 { + return n, errExcessiveInput + } + return n, nil + case C.BROTLI_DECODER_RESULT_ERROR: + return n, decodeError(C.BrotliDecoderGetErrorCode(r.state)) + case C.BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: + if n == 0 { + return 0, io.ErrShortBuffer + } + return n, nil + case C.BROTLI_DECODER_NEEDS_MORE_INPUT: + } + + if len(r.in) != 0 { + return 0, errInvalidState + } + + // Calling r.src.Read may block. Don't block if we have data to return. + if n > 0 { + return n, nil + } + + // Top off the buffer. + encN, err := r.src.Read(r.buf) + if encN == 0 { + // Not enough data to complete decoding. + if err == io.EOF { + return 0, io.ErrUnexpectedEOF + } + return 0, err + } + r.in = r.buf[:encN] + } + + return n, nil +} + +// Decode decodes Brotli encoded data. +func Decode(encodedData []byte) ([]byte, error) { + r := &Reader{ + src: bytes.NewReader(nil), + state: C.BrotliDecoderCreateInstance(nil, nil, nil), + buf: make([]byte, 4), // arbitrarily small but nonzero so that r.src.Read returns io.EOF + in: encodedData, + } + defer r.Close() + return ioutil.ReadAll(r) +} diff --git a/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/writer.go b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/writer.go new file mode 100644 index 000000000..9fa75ab06 --- /dev/null +++ b/roms/edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/brotli/go/cbrotli/writer.go @@ -0,0 +1,159 @@ +// 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 +} |