// 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 { if mode > 6 { return Err(type_error("Invalid encoder mode")); } // SAFETY: mode is a valid discriminant for BrotliEncoderMode unsafe { Ok(std::mem::transmute::(mode)) } } #[op] pub fn op_brotli_compress( buffer: &[u8], out: &mut [u8], quality: i32, lgwin: i32, mode: u32, ) -> Result { 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 { 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 { let ctx = state.resource_table.get::(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 { let ctx = state.resource_table.take::(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 { 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 { brotli_decompress(buffer) } #[op] pub async fn op_brotli_decompress_async( buffer: JsBuffer, ) -> Result { 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 { let ctx = state.resource_table.get::(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 { let ctx = state.resource_table.get::(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) } }