• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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