use std::{mem::MaybeUninit, slice};
use crate::encoding::Encoder;
use crate::iterable_enum;
use crate::utils::{
bytes::{get_array_bytes, get_start_end_indexes, ObjectBytes},
error_messages::{ERROR_MSG_ARRAY_BUFFER_DETACHED, ERROR_MSG_NOT_ARRAY_BUFFER},
primordials::Primordial,
result::ResultExt,
string::{get_coerced_string, get_string},
};
use rquickjs::{
atom::PredefinedAtom,
function::{Constructor, Opt},
prelude::{Func, Rest, This},
Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, JsLifetime, Object, Result, TypedArray,
Value,
};
#[derive(JsLifetime)]
pub struct BufferPrimordials<'js> {
constructor: Constructor<'js>,
}
impl<'js> Primordial<'js> for BufferPrimordials<'js> {
fn new(ctx: &Ctx<'js>) -> Result<Self>
where
Self: Sized,
{
let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
Ok(Self { constructor })
}
}
pub struct Buffer(pub Vec<u8>);
fn resolve_view_bytes<'js>(
ctx: &Ctx<'js>,
array_buffer: ArrayBuffer<'js>,
byte_length: usize,
byte_offset: usize,
) -> Result<&'js mut [u8]> {
let raw = array_buffer
.as_raw()
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED)
.or_throw(ctx)?;
if byte_offset > raw.len || byte_length > raw.len - byte_offset {
return Err(Exception::throw_range(
ctx,
"The value of \"byteOffset\" is out of range",
));
}
Ok(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr().add(byte_offset), byte_length) })
}
impl<'js> IntoJs<'js> for Buffer {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
let array_buffer = ArrayBuffer::new(ctx.clone(), self.0)?;
Self::from_array_buffer(ctx, array_buffer)
}
}
impl<'js> Buffer {
pub fn alloc(length: usize) -> Self {
Self(vec![0; length])
}
pub fn to_string(&self, ctx: &Ctx<'js>, encoding: &str) -> Result<String> {
Encoder::from_str(encoding)
.and_then(|enc| enc.encode_to_string(self.0.as_ref(), true))
.or_throw(ctx)
}
fn from_array_buffer(ctx: &Ctx<'js>, buffer: ArrayBuffer<'js>) -> Result<Value<'js>> {
BufferPrimordials::get(ctx)?
.constructor
.construct((buffer,))
}
fn from_array_buffer_offset_length(
ctx: &Ctx<'js>,
array_buffer: ArrayBuffer<'js>,
offset: usize,
length: usize,
) -> Result<Value<'js>> {
BufferPrimordials::get(ctx)?
.constructor
.construct((array_buffer, offset, length))
}
fn from_encoding(
ctx: &Ctx<'js>,
mut bytes: Vec<u8>,
encoding: Option<String>,
) -> Result<Value<'js>> {
if let Some(encoding) = encoding {
let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
bytes = encoder.decode(bytes).or_throw(ctx)?;
}
Buffer(bytes).into_js(ctx)
}
fn from_string_encoding(
ctx: &Ctx<'js>,
string: String,
encoding: Option<String>,
) -> Result<Value<'js>> {
let bytes = if let Some(encoding) = encoding {
let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
encoder.decode_from_string(string).or_throw(ctx)?
} else {
string.into_bytes()
};
Buffer(bytes).into_js(ctx)
}
}
fn alloc<'js>(
ctx: Ctx<'js>,
length: usize,
fill: Opt<Value<'js>>,
encoding: Opt<String>,
) -> Result<Value<'js>> {
if let Some(value) = fill.0 {
if let Some(value) = value.as_string() {
let string = value.to_string()?;
if let Some(encoding) = encoding.0 {
let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
let bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
return alloc_byte_ref(&ctx, &bytes, length);
}
let byte_ref = string.as_bytes();
return alloc_byte_ref(&ctx, byte_ref, length);
}
if let Some(value) = value.as_int() {
let bytes = vec![value as u8; length];
return Buffer(bytes).into_js(&ctx);
}
if let Some(obj) = value.as_object() {
if let Some(ob) = ObjectBytes::from_array_buffer(obj)? {
let bytes = ob.as_bytes(&ctx)?;
return alloc_byte_ref(&ctx, bytes, length);
}
}
}
Buffer(vec![0; length]).into_js(&ctx)
}
fn alloc_byte_ref<'js>(ctx: &Ctx<'js>, byte_ref: &[u8], length: usize) -> Result<Value<'js>> {
let mut bytes = vec![0; length];
let byte_ref_length = byte_ref.len();
for i in 0..length {
bytes[i] = byte_ref[i % byte_ref_length];
}
Buffer(bytes).into_js(ctx)
}
fn alloc_unsafe(ctx: Ctx<'_>, size: usize) -> Result<Value<'_>> {
let mut bytes: Vec<MaybeUninit<u8>> = Vec::with_capacity(size);
unsafe {
bytes.set_len(size);
}
Buffer(maybeuninit_to_u8(bytes)).into_js(&ctx)
}
fn maybeuninit_to_u8(vec: Vec<MaybeUninit<u8>>) -> Vec<u8> {
let len = vec.len();
let capacity = vec.capacity();
let ptr = vec.as_ptr() as *mut u8;
std::mem::forget(vec);
unsafe { Vec::from_raw_parts(ptr, len, capacity) }
}
fn alloc_unsafe_slow(ctx: Ctx<'_>, size: usize) -> Result<Value<'_>> {
let layout = std::alloc::Layout::array::<u8>(size).or_throw(&ctx)?;
let bytes = unsafe {
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
return Err(Exception::throw_internal(&ctx, "Memory allocation failed"));
}
Vec::from_raw_parts(ptr, size, size)
};
Buffer(bytes).into_js(&ctx)
}
fn byte_length<'js>(ctx: Ctx<'js>, value: Value<'js>, encoding: Opt<String>) -> Result<usize> {
if let Some(encoding) = encoding.0 {
let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
let a = ObjectBytes::from(&ctx, &value)?;
let bytes = a.as_bytes(&ctx)?;
return Ok(encoder.decode(bytes).or_throw(&ctx)?.len());
}
if let Some(val) = value.as_string() {
return Ok(val.to_string()?.len());
}
if value.is_array() {
let array = value.as_array().unwrap();
for val in array.iter::<u8>() {
val.or_throw_msg(&ctx, "array value is not u8")?;
}
return Ok(array.len());
}
if let Some(obj) = value.as_object() {
if let Some(ob) = ObjectBytes::from_array_buffer(obj)? {
return Ok(ob.as_bytes(&ctx)?.len());
}
}
Err(Exception::throw_message(
&ctx,
"value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or string",
))
}
fn concat<'js>(ctx: Ctx<'js>, list: Array<'js>, max_length: Opt<usize>) -> Result<Value<'js>> {
let mut bytes = Vec::new();
let mut total_length = 0;
let mut length;
for value in list.iter::<Object>() {
let typed_array = TypedArray::<u8>::from_object(value?)?;
let bytes_ref: &[u8] = typed_array.as_ref();
length = bytes_ref.len();
if length == 0 {
continue;
}
if let Some(max_length) = max_length.0 {
total_length += length;
if total_length > max_length {
let diff = max_length - (total_length - length);
bytes.extend_from_slice(&bytes_ref[0..diff]);
break;
}
}
bytes.extend_from_slice(bytes_ref);
}
Buffer(bytes).into_js(&ctx)
}
fn from<'js>(
ctx: Ctx<'js>,
value: Value<'js>,
offset_or_encoding: Opt<Value<'js>>,
length: Opt<usize>,
) -> Result<Value<'js>> {
let mut encoding: Option<String> = None;
let mut offset = 0;
if let Some(offset_or_encoding) = offset_or_encoding.0 {
if offset_or_encoding.is_string() {
encoding = Some(offset_or_encoding.get()?);
} else if offset_or_encoding.is_number() {
offset = offset_or_encoding.get()?;
}
}
if let Some(string) = get_string(&value)? {
return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
}
if let Some(bytes) = get_array_bytes(&value, offset, length.0)? {
return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
}
if let Some(obj) = value.as_object() {
if let Some(ab_bytes) = ObjectBytes::from_array_buffer(obj)? {
let bytes = ab_bytes.as_bytes(&ctx)?;
let (start, end) = get_start_end_indexes(bytes.len(), length.0, offset);
if obj
.get::<_, Option<String>>(PredefinedAtom::Meta)?
.as_deref()
== Some(stringify!(Buffer))
|| encoding.is_some()
{
let bytes = bytes.into();
return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
} else {
let (array_buffer, _, source_offset) = ab_bytes.get_array_buffer()?.unwrap(); return Buffer::from_array_buffer_offset_length(
&ctx,
array_buffer,
start + source_offset,
end - start,
);
}
}
}
if let Some(string) = get_coerced_string(&value) {
return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
}
Err(Exception::throw_message(
&ctx,
"value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
))
}
fn is_buffer<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<bool> {
if let Some(object) = value.as_object() {
let constructor = BufferPrimordials::get(&ctx)?;
return Ok(object.is_instance_of(&constructor.constructor));
}
Ok(false)
}
fn is_encoding(value: Value) -> Result<bool> {
if let Some(js_string) = value.as_string() {
let std_string = js_string.to_string()?;
return Ok(Encoder::from_str(std_string.as_str()).is_ok());
}
Ok(false)
}
fn copy<'js>(
this: This<Object<'js>>,
ctx: Ctx<'js>,
target: ObjectBytes<'js>,
args: Rest<usize>,
) -> Result<usize> {
let mut args_iter = args.0.into_iter();
let target_start = args_iter.next().unwrap_or_default();
let source_start = args_iter.next().unwrap_or_default();
let source_end = args_iter.next().unwrap_or_else(|| this.0.len());
let source_bytes = ObjectBytes::from(&ctx, this.0.as_inner())?;
let source_bytes = source_bytes.as_bytes(&ctx)?;
if source_start > source_bytes.len() {
return Err(Exception::throw_range(
&ctx,
"The value of \"sourceStart\" is out of range",
));
}
let source_end = source_end.min(source_bytes.len());
let mut copyable_length = 0;
if source_start >= source_end {
return Ok(copyable_length);
}
if let Some((array_buffer, target_byte_length, target_byte_offset)) =
target.get_array_buffer()?
{
let target_bytes =
resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
if target_start <= target_bytes.len() {
copyable_length = (source_end - source_start).min(target_bytes.len() - target_start);
target_bytes[target_start..target_start + copyable_length]
.copy_from_slice(&source_bytes[source_start..source_start + copyable_length]);
}
}
Ok(copyable_length)
}
fn subarray<'js>(
this: This<Object<'js>>,
ctx: Ctx<'js>,
start: Opt<isize>,
end: Opt<isize>,
) -> Result<Value<'js>> {
let view = TypedArray::<u8>::from_object(this.0.clone())?;
let array_buffer = view.arraybuffer()?;
let view_offset = this.0.get::<_, isize>("byteOffset")?;
let view_length = this.0.get::<_, isize>("byteLength")?;
let start_index = start.map_or(0, |s| {
if s < 0 {
(view_length + s).max(0)
} else {
s.min(view_length)
}
});
let end_index = end.map_or(view_length, |e| {
if e < 0 {
(view_length + e).max(0)
} else {
e.min(view_length)
}
});
let length = (end_index - start_index).max(0) as usize;
let new_offset = (view_offset + start_index).max(0) as usize;
Buffer::from_array_buffer_offset_length(&ctx, array_buffer, new_offset, length)
}
fn to_string(
this: This<Object<'_>>,
ctx: Ctx,
encoding: Opt<String>,
start: Opt<i32>,
end: Opt<i32>,
) -> Result<String> {
let typed_array = TypedArray::<u8>::from_object(this.0)?;
let bytes: &[u8] = typed_array.as_ref();
let start = start
.0
.map(|s| s.max(0) as usize)
.unwrap_or(0)
.min(bytes.len());
let end = end
.0
.map(|e| e.max(0) as usize)
.unwrap_or(bytes.len())
.min(bytes.len());
let bytes = &bytes[start..end];
let encoder = Encoder::from_optional_str(encoding.as_deref()).or_throw(&ctx)?;
encoder.encode_to_string(bytes, true).or_throw(&ctx)
}
fn write<'js>(
this: This<Object<'js>>,
ctx: Ctx<'js>,
string: String,
args: Rest<Value<'js>>,
) -> Result<usize> {
let (offset, length, encoding) = get_write_parameters(&ctx, &args, this.0.len())?;
let target = ObjectBytes::from(&ctx, this.0.as_inner())?;
let mut writable_length = 0;
if let Some((array_buffer, target_byte_length, target_byte_offset)) =
target.get_array_buffer()?
{
let target_bytes =
resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
if encoder.as_label() == "utf-8" {
let (source_slice, valid_length) = safe_byte_slice(&string, length.min(string.len()));
writable_length = valid_length;
target_bytes[offset..offset + writable_length].copy_from_slice(source_slice);
} else {
let decode_bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
writable_length = length.min(decode_bytes.len());
target_bytes[offset..offset + writable_length]
.copy_from_slice(&decode_bytes[..writable_length]);
};
}
Ok(writable_length)
}
fn get_write_parameters<'js>(
ctx: &Ctx<'js>,
args: &Rest<Value<'js>>,
len: usize,
) -> Result<(usize, usize, String)> {
let mut offset = 0;
let mut length = len;
let mut encoding = "utf8".to_owned();
if let Some(v1) = args.0.first() {
if let Some(s) = v1.as_string() {
return Ok((0, len, s.to_string()?));
}
offset = v1.as_int().unwrap_or(0) as usize;
if offset > len {
return Err(Exception::throw_range(
ctx,
"The value of \"offset\" is out of range",
));
}
length = len - offset;
}
if let Some(v2) = args.0.get(1) {
if let Some(s) = v2.as_string() {
return Ok((offset, len - offset, s.to_string()?));
}
length = v2
.as_int()
.map_or(len - offset, |l| (l as usize).min(len - offset));
}
if let Some(v3) = args.0.get(2) {
if let Some(s) = v3.as_string() {
encoding = s.to_string()?;
}
}
Ok((offset, length, encoding))
}
fn safe_byte_slice(s: &str, end: usize) -> (&[u8], usize) {
let bytes = s.as_bytes();
if bytes.len() <= end {
return (bytes, bytes.len());
}
let valid_end = s
.char_indices()
.map(|(i, _)| i)
.rfind(|&i| i <= end)
.unwrap_or(0);
(&bytes[0..valid_end], valid_end)
}
#[derive(Clone, Copy)]
pub enum Endian {
Little,
Big,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum NumberKind {
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Float32,
Float64,
BigInt,
BigUInt,
}
impl NumberKind {
pub fn bits(&self) -> u8 {
match self {
NumberKind::Int8 => 8,
NumberKind::UInt8 => 8,
NumberKind::Int16 => 16,
NumberKind::UInt16 => 16,
NumberKind::Int32 => 32,
NumberKind::UInt32 => 32,
NumberKind::Float32 => 32,
NumberKind::Float64 => 64,
NumberKind::BigInt => 64,
NumberKind::BigUInt => 64,
}
}
pub fn is_signed(&self) -> bool {
matches!(
self,
NumberKind::Int8 | NumberKind::Int16 | NumberKind::Int32
)
}
pub fn prototype(&self) -> &'static [(Endian, &'static str, Option<&'static str>)] {
match self {
NumberKind::Int8 => &[(Endian::Little, "Int8", None)],
NumberKind::UInt8 => &[(Endian::Little, "UInt8", Some("Uint8"))],
NumberKind::Int16 => &[
(Endian::Little, "Int16LE", None),
(Endian::Big, "Int16BE", None),
],
NumberKind::UInt16 => &[
(Endian::Little, "UInt16LE", Some("Uint16LE")),
(Endian::Big, "UInt16BE", Some("Uint16BE")),
],
NumberKind::Int32 => &[
(Endian::Little, "Int32LE", None),
(Endian::Big, "Int32BE", None),
],
NumberKind::UInt32 => &[
(Endian::Little, "UInt32LE", Some("Uint32LE")),
(Endian::Big, "UInt32BE", Some("Uint32BE")),
],
NumberKind::Float32 => &[
(Endian::Little, "FloatLE", None),
(Endian::Big, "FloatBE", None),
],
NumberKind::Float64 => &[
(Endian::Little, "DoubleLE", None),
(Endian::Big, "DoubleBE", None),
],
NumberKind::BigInt => &[
(Endian::Little, "BigInt64LE", None),
(Endian::Big, "BigInt64BE", None),
],
NumberKind::BigUInt => &[
(Endian::Little, "BigUInt64LE", Some("BigUint64LE")),
(Endian::Big, "BigUInt64BE", Some("BigUint64BE")),
],
}
}
}
iterable_enum!(
NumberKind, Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32, Float64, BigInt, BigUInt
);
#[allow(clippy::too_many_arguments)]
fn write_buf<'js>(
this: &This<Object<'js>>,
ctx: &Ctx<'js>,
value: &Value<'js>,
offset: &Opt<usize>,
endian: Endian,
kind: NumberKind,
) -> Result<usize> {
let offset = offset.0.unwrap_or_default();
let (byte_count, bytes) = match kind {
NumberKind::BigInt => {
let Some(bigint) = value.as_big_int() else {
return Err(Exception::throw_type(ctx, "Expected BigInt"));
};
let (byte_count, val) = (8, bigint.clone().to_i64().or_throw(ctx)? as u64);
(byte_count, endian_bytes(val, endian))
},
NumberKind::BigUInt => {
return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
},
NumberKind::Float32 => {
let Some(float_val) = value.as_float() else {
return Err(Exception::throw_type(ctx, "Expected number"));
};
match endian {
Endian::Big => (4, (float_val as f32).to_bits().to_be_bytes().to_vec()),
Endian::Little => (4, (float_val as f32).to_bits().to_le_bytes().to_vec()),
}
},
NumberKind::Float64 => {
let Some(float_val) = value.as_float() else {
return Err(Exception::throw_type(ctx, "Expected number"));
};
match endian {
Endian::Big => (8, float_val.to_bits().to_be_bytes().to_vec()),
Endian::Little => (8, float_val.to_bits().to_le_bytes().to_vec()),
}
},
NumberKind::Int8
| NumberKind::UInt8
| NumberKind::Int16
| NumberKind::UInt16
| NumberKind::Int32
| NumberKind::UInt32 => {
let Some(int_val) = value.as_number() else {
return Err(Exception::throw_type(ctx, "Expected number"));
};
let int_val = int_val as i64;
let bit_mask = (1i64 << kind.bits()) - 1;
let max_val = if kind.is_signed() {
(1i64 << (kind.bits() - 1)) - 1
} else {
bit_mask
};
let min_val = if kind.is_signed() { -max_val - 1 } else { 0 };
if int_val < min_val || int_val > max_val {
return Err(Exception::throw_range(ctx, "Value out of range"));
}
let masked = int_val & bit_mask;
(
(kind.bits() / 8) as usize,
shifted_bytes(masked as u64, kind.bits(), endian),
)
},
};
if offset >= this.0.len() || offset + byte_count > this.0.len() {
return Err(Exception::throw_range(
ctx,
"The specified offset is out of range",
));
}
let target = ObjectBytes::from(ctx, this.0.as_inner())?;
let mut writable_length = 0;
if let Some((array_buffer, target_byte_length, target_byte_offset)) =
target.get_array_buffer()?
{
let target_bytes =
resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
writable_length = offset + bytes.len();
target_bytes[offset..writable_length].copy_from_slice(&bytes);
}
Ok(writable_length)
}
fn read_buf<'js>(
this: &This<Object<'js>>,
ctx: &Ctx<'js>,
offset: &Opt<usize>,
endian: Endian,
kind: NumberKind,
) -> Result<Value<'js>> {
let target = ObjectBytes::from(ctx, this.0.as_inner())?;
let Some((array_buffer, target_byte_length, target_byte_offset)) = target.get_array_buffer()?
else {
return Err(Exception::throw_message(ctx, ERROR_MSG_NOT_ARRAY_BUFFER));
};
let target_bytes =
resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
let start = offset.0.unwrap_or_default();
let end = start + (kind.bits() / 8) as usize;
if end > target_bytes.len() {
return Err(Exception::throw_range(
ctx,
"The value of \"offset\" is out of range",
));
}
let bytes = &target_bytes[start..end];
let value = match kind {
NumberKind::BigInt => {
let value = match endian {
Endian::Big => i64::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => i64::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_big_int(ctx.clone(), value)?
},
NumberKind::BigUInt => {
return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
},
NumberKind::Float32 => {
let value = match endian {
Endian::Big => f32::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => f32::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_float(ctx.clone(), value as f64)
},
NumberKind::Float64 => {
let value = match endian {
Endian::Big => f64::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => f64::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_float(ctx.clone(), value)
},
NumberKind::Int8 => {
let value = match endian {
Endian::Big => i8::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => i8::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_int(ctx.clone(), value as i32)
},
NumberKind::UInt8 => {
let value = match endian {
Endian::Big => u8::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => u8::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_int(ctx.clone(), value as i32)
},
NumberKind::Int16 => {
let value = match endian {
Endian::Big => i16::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => i16::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_int(ctx.clone(), value as i32)
},
NumberKind::UInt16 => {
let value = match endian {
Endian::Big => u16::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => u16::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_int(ctx.clone(), value as i32)
},
NumberKind::Int32 => {
let value = match endian {
Endian::Big => i32::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => i32::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_int(ctx.clone(), value)
},
NumberKind::UInt32 => {
let value = match endian {
Endian::Big => u32::from_be_bytes(bytes.try_into().unwrap()),
Endian::Little => u32::from_le_bytes(bytes.try_into().unwrap()),
};
Value::new_float(ctx.clone(), value as f64)
},
};
Ok(value)
}
fn endian_bytes(mut val: u64, endian: Endian) -> Vec<u8> {
let mut bytes = vec![0u8; 8];
#[allow(clippy::needless_range_loop)]
for i in 0..8 {
bytes[i] = match endian {
Endian::Big => (val >> (56 - i * 8)) as u8,
Endian::Little => (val >> (i * 8)) as u8,
};
match endian {
Endian::Big => val &= !(0xFF << ((7 - i) * 8)),
Endian::Little => val &= !(0xFF << (i * 8)),
}
}
bytes
}
fn shifted_bytes(mut val: u64, bits: u8, endian: Endian) -> Vec<u8> {
let byte_count = (bits / 8) as usize;
let mut bytes = vec![0u8; byte_count];
#[allow(clippy::needless_range_loop)]
for i in 0..byte_count {
let shift = match endian {
Endian::Big => (byte_count - 1 - i) * 8,
Endian::Little => i * 8,
};
bytes[i] = (val >> shift) as u8;
val &= !(0xFF << shift); }
bytes
}
pub(crate) fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> {
let _ = &constructor.set("alloc", Func::from(alloc))?;
let _ = &constructor.set("allocUnsafe", Func::from(alloc_unsafe))?;
let _ = &constructor.set("allocUnsafeSlow", Func::from(alloc_unsafe_slow))?;
let _ = &constructor.set("byteLength", Func::from(byte_length))?;
let _ = &constructor.set("concat", Func::from(concat))?;
let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?;
let _ = &constructor.set("isBuffer", Func::from(is_buffer))?;
let _ = &constructor.set("isEncoding", Func::from(is_encoding))?;
let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?;
prototype.set("copy", Func::from(copy))?;
prototype.set("subarray", Func::from(subarray))?;
prototype.set(PredefinedAtom::ToString, Func::from(to_string))?;
prototype.set("write", Func::from(write))?;
for kind in NumberKind::iter() {
for (endian, name, alias) in kind.prototype() {
let write_func = Function::new(ctx.clone(), |t, c, v, o| {
write_buf(&t, &c, &v, &o, *endian, *kind)
})?;
let read_func =
Function::new(ctx.clone(), |t, c, o| read_buf(&t, &c, &o, *endian, *kind))?;
if let Some(alias) = alias {
prototype.set(["write", alias].concat(), write_func.clone())?;
prototype.set(["read", alias].concat(), read_func.clone())?;
}
prototype.set(["write", name].concat(), write_func)?;
prototype.set(["read", name].concat(), read_func)?;
}
}
prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?;
ctx.globals().set(stringify!(Buffer), constructor)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::test::{call_test, test_async_with, ModuleEvaluator};
use crate::buffer::BufferModule;
#[tokio::test]
async fn test_subarray() {
test_async_with(|ctx| {
Box::pin(async move {
crate::buffer::init(&ctx).unwrap();
ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
.await
.unwrap();
let data = "hello world".to_string().into_bytes();
let module = ModuleEvaluator::eval_js(
ctx.clone(),
"test",
r#"
import { Buffer } from 'buffer';
export async function test(data) {
let buffer = Buffer.from(data);
let sub = buffer.subarray(6, 11); // "world" part
return sub.toString();
}
"#,
)
.await
.unwrap();
let result = call_test::<String, _>(&ctx, &module, (data,)).await;
assert_eq!(result, "world");
})
})
.await;
}
#[tokio::test]
async fn test_subarray_partial() {
test_async_with(|ctx| {
Box::pin(async move {
crate::buffer::init(&ctx).unwrap();
ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
.await
.unwrap();
let data = "hello world".to_string().into_bytes();
let module = ModuleEvaluator::eval_js(
ctx.clone(),
"test",
r#"
import { Buffer } from 'buffer';
export async function test(data) {
let buffer = Buffer.from(data);
let sub = buffer.subarray(0, 5); // "hello" part
return sub.toString();
}
"#,
)
.await
.unwrap();
let result = call_test::<String, _>(&ctx, &module, (data,)).await;
assert_eq!(result, "hello");
})
})
.await;
}
#[tokio::test]
async fn test_subarray_out_of_bounds() {
test_async_with(|ctx| {
Box::pin(async move {
crate::buffer::init(&ctx).unwrap();
ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
.await
.unwrap();
let data = "hello world".to_string().into_bytes();
let module = ModuleEvaluator::eval_js(
ctx.clone(),
"test",
r#"
import { Buffer } from 'buffer';
export async function test(data) {
let buffer = Buffer.from(data);
let sub = buffer.subarray(6, 20); // "world" part but goes out of bounds
return sub.toString();
}
"#,
)
.await
.unwrap();
let result = call_test::<String, _>(&ctx, &module, (data,)).await;
assert_eq!(result, "world");
})
})
.await;
}
#[tokio::test]
async fn test_read_int_32_be() {
test_async_with(|ctx| {
Box::pin(async move {
crate::buffer::init(&ctx).unwrap();
ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
.await
.unwrap();
let data = "hello world".to_string().into_bytes();
let module = ModuleEvaluator::eval_js(
ctx.clone(),
"test",
r#"
import { Buffer } from 'buffer';
export async function test(data) {
const buf = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
return buf.readInt32BE();
}
"#,
)
.await
.unwrap();
let result = call_test::<i32, _>(&ctx, &module, (data,)).await;
assert_eq!(result, 0x01020304);
})
})
.await;
}
}