summaryrefslogtreecommitdiff
path: root/ext/node/ops/zlib/brotli.rs
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-06-24 16:12:08 +0200
committerGitHub <noreply@github.com>2023-06-24 19:42:08 +0530
commit4a18c761351dccb146973793cf22e6efffff18bf (patch)
tree35f23a7f6c64c0a9f28a5f0e21d6ecbd378a5c28 /ext/node/ops/zlib/brotli.rs
parent7a8df8f00cce29605b2d74cb32b255d482f29dda (diff)
fix(ext/node): support brotli APIs (#19223)
Co-authored-by: Bartek IwaƄczuk <biwanczuk@gmail.com>
Diffstat (limited to 'ext/node/ops/zlib/brotli.rs')
-rw-r--r--ext/node/ops/zlib/brotli.rs349
1 files changed, 349 insertions, 0 deletions
diff --git a/ext/node/ops/zlib/brotli.rs b/ext/node/ops/zlib/brotli.rs
new file mode 100644
index 000000000..f3b5001aa
--- /dev/null
+++ b/ext/node/ops/zlib/brotli.rs
@@ -0,0 +1,349 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use brotli::enc::encode::BrotliEncoderParameter;
+use brotli::ffi::compressor::*;
+use brotli::ffi::decompressor::ffi::interface::BrotliDecoderResult;
+use brotli::ffi::decompressor::ffi::BrotliDecoderState;
+use brotli::ffi::decompressor::*;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::JsBuffer;
+use deno_core::OpState;
+use deno_core::Resource;
+use deno_core::ToJsBuffer;
+
+fn encoder_mode(mode: u32) -> Result<BrotliEncoderMode, AnyError> {
+ if mode > 6 {
+ return Err(type_error("Invalid encoder mode"));
+ }
+ // SAFETY: mode is a valid discriminant for BrotliEncoderMode
+ unsafe { Ok(std::mem::transmute::<u32, BrotliEncoderMode>(mode)) }
+}
+
+#[op]
+pub fn op_brotli_compress(
+ buffer: &[u8],
+ out: &mut [u8],
+ quality: i32,
+ lgwin: i32,
+ mode: u32,
+) -> Result<usize, AnyError> {
+ let in_buffer = buffer.as_ptr();
+ let in_size = buffer.len();
+ let out_buffer = out.as_mut_ptr();
+ let mut out_size = out.len();
+
+ // SAFETY: in_size and in_buffer, out_size and out_buffer are valid for this call.
+ if unsafe {
+ BrotliEncoderCompress(
+ quality,
+ lgwin,
+ encoder_mode(mode)?,
+ in_size,
+ in_buffer,
+ &mut out_size as *mut usize,
+ out_buffer,
+ )
+ } != 1
+ {
+ return Err(type_error("Failed to compress"));
+ }
+
+ Ok(out_size)
+}
+
+fn max_compressed_size(input_size: usize) -> usize {
+ if input_size == 0 {
+ return 2;
+ }
+
+ // [window bits / empty metadata] + N * [uncompressed] + [last empty]
+ let num_large_blocks = input_size >> 14;
+ let overhead = 2 + (4 * num_large_blocks) + 3 + 1;
+ let result = input_size + overhead;
+
+ if result < input_size {
+ 0
+ } else {
+ result
+ }
+}
+
+#[op]
+pub async fn op_brotli_compress_async(
+ input: JsBuffer,
+ quality: i32,
+ lgwin: i32,
+ mode: u32,
+) -> Result<ToJsBuffer, AnyError> {
+ tokio::task::spawn_blocking(move || {
+ let in_buffer = input.as_ptr();
+ let in_size = input.len();
+
+ let mut out = vec![0u8; max_compressed_size(in_size)];
+ let out_buffer = out.as_mut_ptr();
+ let mut out_size = out.len();
+
+ // SAFETY: in_size and in_buffer, out_size and out_buffer
+ // are valid for this call.
+ if unsafe {
+ BrotliEncoderCompress(
+ quality,
+ lgwin,
+ encoder_mode(mode)?,
+ in_size,
+ in_buffer,
+ &mut out_size as *mut usize,
+ out_buffer,
+ )
+ } != 1
+ {
+ return Err(type_error("Failed to compress"));
+ }
+
+ out.truncate(out_size);
+ Ok(out.into())
+ })
+ .await?
+}
+
+struct BrotliCompressCtx {
+ inst: *mut BrotliEncoderState,
+}
+
+impl Resource for BrotliCompressCtx {}
+
+impl Drop for BrotliCompressCtx {
+ fn drop(&mut self) {
+ // SAFETY: `self.inst` is the current brotli encoder instance.
+ // It is not used after the following call.
+ unsafe { BrotliEncoderDestroyInstance(self.inst) };
+ }
+}
+
+#[op]
+pub fn op_create_brotli_compress(
+ state: &mut OpState,
+ params: Vec<(u8, i32)>,
+) -> u32 {
+ let inst =
+ // SAFETY: Creates a brotli encoder instance for default allocators.
+ unsafe { BrotliEncoderCreateInstance(None, None, std::ptr::null_mut()) };
+
+ for (key, value) in params {
+ // SAFETY: `key` can range from 0-255.
+ // Any valid u32 can be used for the `value`.
+ unsafe {
+ BrotliEncoderSetParameter(inst, encoder_param(key), value as u32);
+ }
+ }
+
+ state.resource_table.add(BrotliCompressCtx { inst })
+}
+
+fn encoder_param(param: u8) -> BrotliEncoderParameter {
+ // SAFETY: BrotliEncoderParam is valid for 0-255
+ unsafe { std::mem::transmute(param as u32) }
+}
+
+#[op]
+pub fn op_brotli_compress_stream(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> Result<usize, AnyError> {
+ let ctx = state.resource_table.get::<BrotliCompressCtx>(rid)?;
+
+ // SAFETY: TODO(littledivy)
+ unsafe {
+ let mut available_in = input.len();
+ let mut next_in = input.as_ptr();
+ let mut available_out = output.len();
+ let mut next_out = output.as_mut_ptr();
+ let mut total_out = 0;
+
+ if BrotliEncoderCompressStream(
+ ctx.inst,
+ BrotliEncoderOperation::BROTLI_OPERATION_PROCESS,
+ &mut available_in,
+ &mut next_in,
+ &mut available_out,
+ &mut next_out,
+ &mut total_out,
+ ) != 1
+ {
+ return Err(type_error("Failed to compress"));
+ }
+
+ // On progress, next_out is advanced and available_out is reduced.
+ Ok(output.len() - available_out)
+ }
+}
+
+#[op]
+pub fn op_brotli_compress_stream_end(
+ state: &mut OpState,
+ rid: u32,
+ output: &mut [u8],
+) -> Result<usize, AnyError> {
+ let ctx = state.resource_table.take::<BrotliCompressCtx>(rid)?;
+
+ // SAFETY: TODO(littledivy)
+ unsafe {
+ let mut available_out = output.len();
+ let mut next_out = output.as_mut_ptr();
+ let mut total_out = 0;
+
+ if BrotliEncoderCompressStream(
+ ctx.inst,
+ BrotliEncoderOperation::BROTLI_OPERATION_FINISH,
+ &mut 0,
+ std::ptr::null_mut(),
+ &mut available_out,
+ &mut next_out,
+ &mut total_out,
+ ) != 1
+ {
+ return Err(type_error("Failed to compress"));
+ }
+
+ // On finish, next_out is advanced and available_out is reduced.
+ Ok(output.len() - available_out)
+ }
+}
+
+fn brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> {
+ let in_buffer = buffer.as_ptr();
+ let in_size = buffer.len();
+
+ let mut out = vec![0u8; 4096];
+ loop {
+ let out_buffer = out.as_mut_ptr();
+ let mut out_size = out.len();
+ // SAFETY: TODO(littledivy)
+ match unsafe {
+ CBrotliDecoderDecompress(
+ in_size,
+ in_buffer,
+ &mut out_size as *mut usize,
+ out_buffer,
+ )
+ } {
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS => {
+ out.truncate(out_size);
+ return Ok(out.into());
+ }
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT => {
+ let new_size = out.len() * 2;
+ if new_size < out.len() {
+ return Err(type_error("Failed to decompress"));
+ }
+ out.resize(new_size, 0);
+ }
+ _ => return Err(type_error("Failed to decompress")),
+ }
+ }
+}
+
+#[op]
+pub fn op_brotli_decompress(buffer: &[u8]) -> Result<ToJsBuffer, AnyError> {
+ brotli_decompress(buffer)
+}
+
+#[op]
+pub async fn op_brotli_decompress_async(
+ buffer: JsBuffer,
+) -> Result<ToJsBuffer, AnyError> {
+ tokio::task::spawn_blocking(move || brotli_decompress(&buffer)).await?
+}
+
+struct BrotliDecompressCtx {
+ inst: *mut BrotliDecoderState,
+}
+
+impl Resource for BrotliDecompressCtx {}
+
+impl Drop for BrotliDecompressCtx {
+ fn drop(&mut self) {
+ // SAFETY: TODO(littledivy)
+ unsafe { CBrotliDecoderDestroyInstance(self.inst) };
+ }
+}
+
+#[op]
+pub fn op_create_brotli_decompress(state: &mut OpState) -> u32 {
+ let inst =
+ // SAFETY: TODO(littledivy)
+ unsafe { CBrotliDecoderCreateInstance(None, None, std::ptr::null_mut()) };
+ state.resource_table.add(BrotliDecompressCtx { inst })
+}
+
+#[op]
+pub fn op_brotli_decompress_stream(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> Result<usize, AnyError> {
+ let ctx = state.resource_table.get::<BrotliDecompressCtx>(rid)?;
+
+ // SAFETY: TODO(littledivy)
+ unsafe {
+ let mut available_in = input.len();
+ let mut next_in = input.as_ptr();
+ let mut available_out = output.len();
+ let mut next_out = output.as_mut_ptr();
+ let mut total_out = 0;
+
+ if matches!(
+ CBrotliDecoderDecompressStream(
+ ctx.inst,
+ &mut available_in,
+ &mut next_in,
+ &mut available_out,
+ &mut next_out,
+ &mut total_out,
+ ),
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_ERROR
+ ) {
+ return Err(type_error("Failed to decompress"));
+ }
+
+ // On progress, next_out is advanced and available_out is reduced.
+ Ok(output.len() - available_out)
+ }
+}
+
+#[op]
+pub fn op_brotli_decompress_stream_end(
+ state: &mut OpState,
+ rid: u32,
+ output: &mut [u8],
+) -> Result<usize, AnyError> {
+ let ctx = state.resource_table.get::<BrotliDecompressCtx>(rid)?;
+
+ // SAFETY: TODO(littledivy)
+ unsafe {
+ let mut available_out = output.len();
+ let mut next_out = output.as_mut_ptr();
+ let mut total_out = 0;
+
+ if matches!(
+ CBrotliDecoderDecompressStream(
+ ctx.inst,
+ &mut 0,
+ std::ptr::null_mut(),
+ &mut available_out,
+ &mut next_out,
+ &mut total_out,
+ ),
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_ERROR
+ ) {
+ return Err(type_error("Failed to decompress"));
+ }
+
+ // On finish, next_out is advanced and available_out is reduced.
+ Ok(output.len() - available_out)
+ }
+}