diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2023-06-24 16:12:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-24 19:42:08 +0530 |
commit | 4a18c761351dccb146973793cf22e6efffff18bf (patch) | |
tree | 35f23a7f6c64c0a9f28a5f0e21d6ecbd378a5c28 /ext/node/ops/zlib/brotli.rs | |
parent | 7a8df8f00cce29605b2d74cb32b255d482f29dda (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.rs | 349 |
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) + } +} |