aboutsummaryrefslogtreecommitdiff
path: root/go/cbrotli/internal/encoder.go
blob: 13526f3abc9c36ea0dee90ad0d4e10bd53f89a01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 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)
	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
}