Skip to content

Commit 0c921e0

Browse files
authored
Merge pull request #11 from myl7/stream
Streaming support
2 parents 762b1ed + 1fd1c6a commit 0c921e0

File tree

3 files changed

+432
-1
lines changed

3 files changed

+432
-1
lines changed

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
pub mod stream;
2+
13
use brotli;
24
use wasm_bindgen::prelude::*;
35
use serde::{Serialize, Deserialize};
@@ -6,7 +8,7 @@ use serde::{Serialize, Deserialize};
68
#[global_allocator]
79
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
810

9-
fn set_panic_hook() {
11+
pub fn set_panic_hook() {
1012
#[cfg(feature="console_error_panic_hook")]
1113
console_error_panic_hook::set_once();
1214
}

src/stream.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use crate::set_panic_hook;
2+
use brotli::enc::encode::{
3+
BrotliEncoderCompressStream, BrotliEncoderCreateInstance, BrotliEncoderDestroyInstance,
4+
BrotliEncoderIsFinished, BrotliEncoderOperation, BrotliEncoderParameter,
5+
BrotliEncoderSetParameter, BrotliEncoderStateStruct,
6+
};
7+
use brotli::enc::StandardAlloc; // Re-exported from alloc_stdlib::StandardAlloc
8+
use brotli::{self, BrotliDecompressStream, BrotliResult, BrotliState};
9+
use wasm_bindgen::prelude::*;
10+
11+
#[wasm_bindgen]
12+
#[repr(i32)]
13+
pub enum BrotliStreamResult {
14+
/// The stream is just initialized and no input is provided currently.
15+
/// `BrotliResult` uses `ResultFailure = 0`, but as we will convert `ResultFailure` to a negative actual error code,
16+
/// 0 is reused as no input currently.
17+
/// As for Brotli compressing, since offical API does not provide a way to retrieve a detailed error code, -1 is used.
18+
Init = 0,
19+
ResultSuccess = 1,
20+
NeedsMoreInput = 2,
21+
NeedsMoreOutput = 3,
22+
}
23+
24+
#[wasm_bindgen]
25+
pub struct CompressStream {
26+
state: BrotliEncoderStateStruct<StandardAlloc>,
27+
result: i32,
28+
total_out: usize,
29+
last_input_offset: usize,
30+
}
31+
32+
impl Drop for CompressStream {
33+
fn drop(&mut self) {
34+
BrotliEncoderDestroyInstance(&mut self.state);
35+
}
36+
}
37+
38+
#[wasm_bindgen]
39+
impl CompressStream {
40+
#[wasm_bindgen(constructor)]
41+
pub fn new(quality: Option<u32>) -> CompressStream {
42+
set_panic_hook();
43+
let alloc = StandardAlloc::default();
44+
let mut state = BrotliEncoderCreateInstance(alloc);
45+
match quality {
46+
None => (),
47+
Some(quality) => {
48+
BrotliEncoderSetParameter(
49+
&mut state,
50+
BrotliEncoderParameter::BROTLI_PARAM_QUALITY,
51+
quality,
52+
);
53+
}
54+
}
55+
Self {
56+
state,
57+
result: BrotliStreamResult::Init as i32,
58+
total_out: 0,
59+
last_input_offset: 0,
60+
}
61+
}
62+
63+
pub fn compress(
64+
&mut self,
65+
input_opt: Option<Box<[u8]>>,
66+
output_size: usize,
67+
) -> Result<Box<[u8]>, JsValue> {
68+
let mut nop_callback = |_data: &mut brotli::interface::PredictionModeContextMap<
69+
brotli::interface::InputReferenceMut,
70+
>,
71+
_cmds: &mut [brotli::interface::StaticCommand],
72+
_mb: brotli::interface::InputPair,
73+
_mfv: &mut StandardAlloc| ();
74+
let mut output = vec![0; output_size];
75+
let mut input_offset = 0;
76+
let mut available_out = output_size;
77+
let mut output_offset = 0;
78+
match input_opt {
79+
Some(input) => {
80+
let op = BrotliEncoderOperation::BROTLI_OPERATION_PROCESS;
81+
let mut available_in = input.len();
82+
// `BrotliEncoderCompressStream` does not return a `BrotliResult` but returns a boolean,
83+
// which is different from `BrotliDecompressStream`.
84+
// But the requirement for input/output buf is common so we reused `BrotliStreamResult` to report it.
85+
let ret = BrotliEncoderCompressStream(
86+
&mut self.state,
87+
op,
88+
&mut available_in,
89+
&input,
90+
&mut input_offset,
91+
&mut available_out,
92+
&mut output,
93+
&mut output_offset,
94+
&mut Some(self.total_out),
95+
&mut nop_callback,
96+
);
97+
if ret != 0 {
98+
if available_in == 0 {
99+
output.truncate(output_offset);
100+
self.result = BrotliStreamResult::NeedsMoreInput as i32;
101+
self.last_input_offset = input.len();
102+
Ok(output.into_boxed_slice())
103+
} else if available_out == 0 {
104+
self.result = BrotliStreamResult::NeedsMoreOutput as i32;
105+
self.last_input_offset = input_offset;
106+
Ok(output.into_boxed_slice())
107+
} else {
108+
self.result = -1;
109+
self.last_input_offset = 0;
110+
Err(JsValue::from_str("Unexpected Brotli streaming compress: both available_in & available_out are not 0 after a successful processing"))
111+
}
112+
} else {
113+
self.result = -1;
114+
self.last_input_offset = 0;
115+
Err(JsValue::from_str(
116+
"Brotli streaming compress failed: When processing",
117+
))
118+
}
119+
}
120+
None => {
121+
let op = BrotliEncoderOperation::BROTLI_OPERATION_FINISH;
122+
let input = Vec::new().into_boxed_slice();
123+
let mut available_in = 0;
124+
while BrotliEncoderIsFinished(&mut self.state) == 0 && available_out > 0 {
125+
let ret = BrotliEncoderCompressStream(
126+
&mut self.state,
127+
op,
128+
&mut available_in,
129+
&input,
130+
&mut input_offset,
131+
&mut available_out,
132+
&mut output,
133+
&mut output_offset,
134+
&mut Some(self.total_out),
135+
&mut nop_callback,
136+
);
137+
if ret == 0 {
138+
self.result = -1;
139+
return Err(JsValue::from_str(
140+
"Brotli streaming compress failed: When finishing",
141+
));
142+
}
143+
}
144+
self.result = if available_out == 0 {
145+
BrotliStreamResult::NeedsMoreOutput as i32
146+
} else {
147+
output.truncate(output_offset);
148+
BrotliStreamResult::ResultSuccess as i32
149+
};
150+
self.last_input_offset = 0;
151+
Ok(output.into_boxed_slice())
152+
}
153+
}
154+
}
155+
156+
pub fn total_out(&self) -> usize {
157+
self.total_out
158+
}
159+
160+
pub fn result(&self) -> i32 {
161+
self.result
162+
}
163+
164+
pub fn last_input_offset(&self) -> usize {
165+
self.last_input_offset
166+
}
167+
}
168+
169+
#[wasm_bindgen]
170+
pub struct DecompressStream {
171+
state: BrotliState<StandardAlloc, StandardAlloc, StandardAlloc>,
172+
result: i32,
173+
total_out: usize,
174+
last_input_offset: usize,
175+
}
176+
177+
#[wasm_bindgen]
178+
impl DecompressStream {
179+
#[allow(clippy::new_without_default)]
180+
#[wasm_bindgen(constructor)]
181+
pub fn new() -> Self {
182+
set_panic_hook();
183+
let alloc = StandardAlloc::default();
184+
Self {
185+
state: BrotliState::new(alloc, alloc, alloc),
186+
result: BrotliStreamResult::Init as i32,
187+
total_out: 0,
188+
last_input_offset: 0,
189+
}
190+
}
191+
192+
pub fn decompress(
193+
&mut self,
194+
input: Box<[u8]>,
195+
output_size: usize,
196+
) -> Result<Box<[u8]>, JsValue> {
197+
let mut output = vec![0; output_size];
198+
let mut available_in = input.len();
199+
let mut input_offset = 0;
200+
let mut available_out = output_size;
201+
let mut output_offset = 0;
202+
match BrotliDecompressStream(
203+
&mut available_in,
204+
&mut input_offset,
205+
&input,
206+
&mut available_out,
207+
&mut output_offset,
208+
&mut output,
209+
&mut self.total_out,
210+
&mut self.state,
211+
) {
212+
BrotliResult::ResultFailure => {
213+
// It should be a negative error code
214+
self.result = self.state.error_code as i32;
215+
self.last_input_offset = 0;
216+
Err(JsValue::from_str(&format!(
217+
"Brotli streaming decompress failed: Error code {}",
218+
self.result
219+
)))
220+
}
221+
BrotliResult::NeedsMoreOutput => {
222+
self.result = BrotliStreamResult::NeedsMoreOutput as i32;
223+
self.last_input_offset = input_offset;
224+
Ok(output.into_boxed_slice())
225+
}
226+
BrotliResult::ResultSuccess => {
227+
output.truncate(output_offset);
228+
self.result = BrotliStreamResult::ResultSuccess as i32;
229+
self.last_input_offset = input.len();
230+
Ok(output.into_boxed_slice())
231+
}
232+
BrotliResult::NeedsMoreInput => {
233+
output.truncate(output_offset);
234+
self.result = BrotliStreamResult::NeedsMoreInput as i32;
235+
self.last_input_offset = input.len();
236+
Ok(output.into_boxed_slice())
237+
}
238+
}
239+
}
240+
241+
pub fn total_out(&self) -> usize {
242+
self.total_out
243+
}
244+
245+
pub fn result(&self) -> i32 {
246+
self.result
247+
}
248+
249+
pub fn last_input_offset(&self) -> usize {
250+
self.last_input_offset
251+
}
252+
}

0 commit comments

Comments
 (0)