1// Copyright 2016 Google Inc. All Rights Reserved. 2// 3// Distributed under MIT license. 4// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 6// Package cbrotli compresses and decompresses data with C-Brotli library. 7package cbrotli 8 9/* 10#include <stddef.h> 11#include <stdint.h> 12 13#include <brotli/decode.h> 14 15static BrotliDecoderResult DecompressStream(BrotliDecoderState* s, 16 uint8_t* out, size_t out_len, 17 const uint8_t* in, size_t in_len, 18 size_t* bytes_written, 19 size_t* bytes_consumed) { 20 size_t in_remaining = in_len; 21 size_t out_remaining = out_len; 22 BrotliDecoderResult result = BrotliDecoderDecompressStream( 23 s, &in_remaining, &in, &out_remaining, &out, NULL); 24 *bytes_written = out_len - out_remaining; 25 *bytes_consumed = in_len - in_remaining; 26 return result; 27} 28*/ 29import "C" 30 31import ( 32 "bytes" 33 "errors" 34 "io" 35 "io/ioutil" 36) 37 38type decodeError C.BrotliDecoderErrorCode 39 40func (err decodeError) Error() string { 41 return "cbrotli: " + 42 C.GoString(C.BrotliDecoderErrorString(C.BrotliDecoderErrorCode(err))) 43} 44 45var errExcessiveInput = errors.New("cbrotli: excessive input") 46var errInvalidState = errors.New("cbrotli: invalid state") 47var errReaderClosed = errors.New("cbrotli: Reader is closed") 48 49// Reader implements io.ReadCloser by reading Brotli-encoded data from an 50// underlying Reader. 51type Reader struct { 52 src io.Reader 53 state *C.BrotliDecoderState 54 buf []byte // scratch space for reading from src 55 in []byte // current chunk to decode; usually aliases buf 56} 57 58// readBufSize is a "good" buffer size that avoids excessive round-trips 59// between C and Go but doesn't waste too much memory on buffering. 60// It is arbitrarily chosen to be equal to the constant used in io.Copy. 61const readBufSize = 32 * 1024 62 63// NewReader initializes new Reader instance. 64// Close MUST be called to free resources. 65func NewReader(src io.Reader) *Reader { 66 return &Reader{ 67 src: src, 68 state: C.BrotliDecoderCreateInstance(nil, nil, nil), 69 buf: make([]byte, readBufSize), 70 } 71} 72 73// Close implements io.Closer. Close MUST be invoked to free native resources. 74func (r *Reader) Close() error { 75 if r.state == nil { 76 return errReaderClosed 77 } 78 // Close despite the state; i.e. there might be some unread decoded data. 79 C.BrotliDecoderDestroyInstance(r.state) 80 r.state = nil 81 return nil 82} 83 84func (r *Reader) Read(p []byte) (n int, err error) { 85 if r.state == nil { 86 return 0, errReaderClosed 87 } 88 if int(C.BrotliDecoderHasMoreOutput(r.state)) == 0 && len(r.in) == 0 { 89 m, readErr := r.src.Read(r.buf) 90 if m == 0 { 91 // If readErr is `nil`, we just proxy underlying stream behavior. 92 return 0, readErr 93 } 94 r.in = r.buf[:m] 95 } 96 97 if len(p) == 0 { 98 return 0, nil 99 } 100 101 for { 102 var written, consumed C.size_t 103 var data *C.uint8_t 104 if len(r.in) != 0 { 105 data = (*C.uint8_t)(&r.in[0]) 106 } 107 result := C.DecompressStream(r.state, 108 (*C.uint8_t)(&p[0]), C.size_t(len(p)), 109 data, C.size_t(len(r.in)), 110 &written, &consumed) 111 r.in = r.in[int(consumed):] 112 n = int(written) 113 114 switch result { 115 case C.BROTLI_DECODER_RESULT_SUCCESS: 116 if len(r.in) > 0 { 117 return n, errExcessiveInput 118 } 119 return n, nil 120 case C.BROTLI_DECODER_RESULT_ERROR: 121 return n, decodeError(C.BrotliDecoderGetErrorCode(r.state)) 122 case C.BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 123 if n == 0 { 124 return 0, io.ErrShortBuffer 125 } 126 return n, nil 127 case C.BROTLI_DECODER_NEEDS_MORE_INPUT: 128 } 129 130 if len(r.in) != 0 { 131 return 0, errInvalidState 132 } 133 134 // Calling r.src.Read may block. Don't block if we have data to return. 135 if n > 0 { 136 return n, nil 137 } 138 139 // Top off the buffer. 140 encN, err := r.src.Read(r.buf) 141 if encN == 0 { 142 // Not enough data to complete decoding. 143 if err == io.EOF { 144 return 0, io.ErrUnexpectedEOF 145 } 146 return 0, err 147 } 148 r.in = r.buf[:encN] 149 } 150 151 return n, nil 152} 153 154// Decode decodes Brotli encoded data. 155func Decode(encodedData []byte) ([]byte, error) { 156 r := &Reader{ 157 src: bytes.NewReader(nil), 158 state: C.BrotliDecoderCreateInstance(nil, nil, nil), 159 buf: make([]byte, 4), // arbitrarily small but nonzero so that r.src.Read returns io.EOF 160 in: encodedData, 161 } 162 defer r.Close() 163 return ioutil.ReadAll(r) 164} 165