use anyhow::Result;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use rusty_v8 as v8;
pub fn setup_buffer_api(
scope: &mut v8::ContextScope<v8::HandleScope>,
context: &v8::Local<v8::Context>,
) -> Result<()> {
let buffer_constructor: _ = v8::FunctionTemplate::new(scope, buffer_constructor_callback);
let from_func: _ = v8::FunctionTemplate::new(scope, buffer_from_callback);
let from_instance: _ = from_func.get_function(scope).unwrap();
buffer_constructor.set(
v8::String::new(scope, "from").unwrap().into(),
from_instance.into(),
);
let alloc_func: _ = v8::FunctionTemplate::new(scope, buffer_alloc_callback);
let alloc_instance: _ = alloc_func.get_function(scope).unwrap();
buffer_constructor.set(
v8::String::new(scope, "alloc").unwrap().into(),
alloc_instance.into(),
);
let concat_func: _ = v8::FunctionTemplate::new(scope, buffer_concat_callback);
let concat_instance: _ = concat_func.get_function(scope).unwrap();
buffer_constructor.set(
v8::String::new(scope, "concat").unwrap().into(),
concat_instance.into(),
);
let byte_length_func: _ = v8::FunctionTemplate::new(scope, buffer_byte_length_callback);
let byte_length_instance: _ = byte_length_func.get_function(scope).unwrap();
buffer_constructor.set(
v8::String::new(scope, "byteLength").unwrap().into(),
byte_length_instance.into(),
);
let is_buffer_func: _ = v8::FunctionTemplate::new(scope, buffer_is_buffer_callback);
let is_buffer_instance: _ = is_buffer_func.get_function(scope).unwrap();
buffer_constructor.set(
v8::String::new(scope, "isBuffer").unwrap().into(),
is_buffer_instance.into(),
);
let buffer_func: _ = buffer_constructor.get_function(scope).unwrap();
let global: _ = context.global(scope);
let buffer_key: _ = v8::String::new(scope, "Buffer").unwrap();
global.set(scope, buffer_key.into(), buffer_func.into());
Ok(())
}
fn buffer_constructor_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let size: _ = args
.get(0)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as usize;
let buffer: _ = v8::ArrayBuffer::new(scope, size);
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let length_key_val: _ = v8::Integer::new(scope, size as i32).into();
buffer.set(scope, length_key.into(), length_key_val);
retval.set(buffer.into());
}
fn buffer_from_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let arg: _ = args.get(0);
if arg.is_string() {
let string: _ = arg.to_string(scope).unwrap().to_rust_string_lossy(scope);
let encoding: _ = args
.get(1)
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_else(|| "utf8".to_string());
let bytes: _ = match encoding.as_str() {
"utf8" | "utf-8" => string.as_bytes().to_vec(),
"hex" => hex::decode(&string).unwrap_or_default(),
"base64" => BASE64_STANDARD.decode(&string).unwrap_or_default(),
"latin1" => string.chars().map(|c| c as u8).collect(),
_ => string.as_bytes().to_vec(),
};
let buffer: _ = v8::ArrayBuffer::new(scope, bytes.len());
if !bytes.is_empty() {
let store = buffer.get_backing_store();
let slice = unsafe {
std::slice::from_raw_parts_mut(store.as_ref().as_ptr() as *mut u8, bytes.len())
};
slice.copy_from_slice(&bytes);
}
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let len_val: _ = v8::Integer::new(scope, bytes.len() as i32).into();
buffer.set(scope, length_key.into(), len_val);
retval.set(buffer.into());
} else if arg.is_array() {
let arr: _ = v8::Local::<v8::Array>::try_from(arg).unwrap();
let length: _ = arr.length() as usize;
let mut bytes = vec![0u8; length];
for i in 0..length {
if let Some(val) = arr.get_index(scope, i as u32) {
if let Some(int) = val.to_integer(scope) {
bytes[i] = int.value() as u8;
}
}
}
let buffer: _ = v8::ArrayBuffer::new(scope, length);
if !bytes.is_empty() {
let store = buffer.get_backing_store();
let slice = unsafe {
std::slice::from_raw_parts_mut(store.as_ref().as_ptr() as *mut u8, bytes.len())
};
slice.copy_from_slice(&bytes);
}
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let length_key_val: _ = v8::Integer::new(scope, length as i32).into();
buffer.set(scope, length_key.into(), length_key_val);
retval.set(buffer.into());
} else {
let buffer: _ = v8::ArrayBuffer::new(scope, 0);
retval.set(buffer.into());
}
}
fn buffer_alloc_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let size: _ = args
.get(0)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as usize;
let fill_value: _ = args
.get(1)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as u8;
let buffer: _ = v8::ArrayBuffer::new(scope, size);
if size > 0 && fill_value != 0 {
let store = buffer.get_backing_store();
let ptr = store
.data()
.map(|p| p.as_ptr() as *mut u8)
.unwrap_or(std::ptr::null_mut());
if !ptr.is_null() {
unsafe {
std::ptr::write_bytes(ptr, fill_value, size);
}
}
}
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let length_key_val: _ = v8::Integer::new(scope, size as i32).into();
buffer.set(scope, length_key.into(), length_key_val);
retval.set(buffer.into());
}
fn buffer_concat_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let list: _ = args.get(0);
let total_length: _ = args
.get(1)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as usize;
if list.is_array() {
if let Ok(arr) = v8::Local::<v8::Array>::try_from(list) {
let _combined_data: Vec<u8> = Vec::new();
let mut calculated_length: usize = 0;
for i in 0..arr.length() {
if let Ok(buf) = v8::Local::<v8::Array>::try_from(arr.get_index(scope, i).unwrap())
{
let length_key: _ = v8::String::new(scope, "_length").unwrap();
if let Some(len_val) = buf
.get(scope, length_key.into())
.and_then(|v| v.to_integer(scope))
{
let len: _ = len_val.value() as usize;
calculated_length += len;
}
}
}
let target_length: _ = if total_length > 0 {
total_length
} else {
calculated_length
};
let buffer: _ = v8::ArrayBuffer::new(scope, target_length);
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let length_key_val: _ = v8::Integer::new(scope, target_length as i32).into();
buffer.set(scope, length_key.into(), length_key_val);
retval.set(buffer.into());
}
} else {
retval.set(v8::null(scope).into());
}
}
fn buffer_byte_length_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let string: _ = args
.get(0)
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_default();
let encoding: _ = args
.get(1)
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_else(|| "utf8".to_string());
let byte_length: _ = match encoding.as_str() {
"utf8" | "utf-8" => string.as_bytes().len(),
"hex" => string.len() / 2,
"base64" => (string.len() * 3) / 4,
"latin1" => string.len(),
_ => string.as_bytes().len(),
};
retval.set(v8::Integer::new(scope, byte_length as i32).into());
}
fn buffer_is_buffer_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let value: _ = args.get(0);
let is_buffer: _ = value.is_array_buffer();
retval.set(v8::Boolean::new(scope, is_buffer).into());
}
#[allow(dead_code)]
fn buffer_to_string_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let this: _ = args.this();
let _encoding: _ = args
.get(0)
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_else(|| "utf8".to_string());
let start: _ = args
.get(1)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as isize;
let end: _ = args
.get(2)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, -1))
.value();
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let buffer_length: _ = this
.get(scope, length_key.into())
.and_then(|v| v.to_integer(scope).map(|i| i.value()))
.unwrap_or(0);
let actual_end: usize = if end == -1 {
buffer_length as usize
} else {
(end.min(buffer_length)) as usize
};
let actual_start: _ = (start as i64).min(buffer_length) as usize;
if actual_start >= actual_end {
retval.set(v8::String::new(scope, "").unwrap().into());
return;
}
let result: _ = String::new();
retval.set(v8::String::new(scope, &result).unwrap().into());
}
#[allow(dead_code)]
fn buffer_to_json_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let this: _ = args.this();
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let _buffer_length: _ = this
.get(scope, length_key.into())
.and_then(|v| v.to_integer(scope).map(|i| i.value()))
.unwrap_or(0);
let json_array: _ = v8::Array::new(scope, 0);
retval.set(json_array.into());
}
#[allow(dead_code)]
fn buffer_fill_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let this: _ = args.this();
let value: _ = args.get(0);
let start: _ = args
.get(1)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value();
let end: _ = args
.get(2)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, -1))
.value();
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let buffer_length: _ = this
.get(scope, length_key.into())
.and_then(|v| v.to_integer(scope).map(|i| i.value()))
.unwrap_or(0);
let _actual_end: usize = if end == -1 {
buffer_length as usize
} else {
(end.min(buffer_length)) as usize
};
let _actual_start: _ = start.min(buffer_length) as usize;
let _fill_value: _ = if value.is_number() {
value.to_integer(scope).unwrap().value() as u8
} else if value.is_string() {
let string: _ = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
string.chars().next().unwrap_or('\0') as u8
} else {
0
};
retval.set(this.into());
}
#[allow(dead_code)]
fn buffer_slice_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let this: _ = args.this();
let start: isize = args
.get(0)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, 0))
.value() as isize;
let end: isize = args
.get(1)
.to_integer(scope)
.unwrap_or(v8::Integer::new(scope, -1))
.value() as isize;
let length_key: _ = v8::String::new(scope, "_length").unwrap();
let buffer_length: isize = this
.get(scope, length_key.into())
.and_then(|v| v.to_integer(scope).map(|i| i.value() as isize))
.unwrap_or(0);
let actual_start: usize = if start < 0 {
((buffer_length + start).max(0)) as usize
} else {
start.min(buffer_length) as usize
};
let actual_end: usize = if end == -1 {
buffer_length as usize
} else if end < 0 {
((buffer_length + end).max(0)) as usize
} else {
end.min(buffer_length) as usize
};
let slice_length: usize = if actual_end > actual_start {
actual_end - actual_start
} else {
0
};
let source_buffer: Option<v8::Local<v8::ArrayBuffer>> = this.try_into().ok();
if let Some(_source) = source_buffer {
if slice_length > 0 {
let new_buffer: _ = v8::ArrayBuffer::new(scope, slice_length);
let new_length_key: _ = v8::String::new(scope, "_length").unwrap();
let new_length_val: _ = v8::Integer::new(scope, slice_length as i32).into();
new_buffer.set(scope, new_length_key.into(), new_length_val);
retval.set(new_buffer.into());
} else {
let empty_buffer: _ = v8::ArrayBuffer::new(scope, 0);
let empty_length_key: _ = v8::String::new(scope, "_length").unwrap();
let empty_length_val: _ = v8::Integer::new(scope, 0).into();
empty_buffer.set(scope, empty_length_key.into(), empty_length_val);
retval.set(empty_buffer.into());
}
} else {
let new_buffer: _ = v8::ArrayBuffer::new(scope, slice_length);
let new_length_key: _ = v8::String::new(scope, "_length").unwrap();
let new_length_val: _ = v8::Integer::new(scope, slice_length as i32).into();
new_buffer.set(scope, new_length_key.into(), new_length_val);
retval.set(new_buffer.into());
}
}