| // 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(eustas): 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 | 
 | } |