// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::rc::Rc; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, function::Constructor, ArrayBuffer, Coerced, Ctx, Exception, FromJs, IntoJs, JsLifetime, Object, Result, TypedArray, Value, }; use crate::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt}; #[derive(Clone, PartialEq)] pub enum ObjectBytes<'js> { U8Array(TypedArray<'js, u8>), I8Array(TypedArray<'js, i8>), U16Array(TypedArray<'js, u16>), I16Array(TypedArray<'js, i16>), U32Array(TypedArray<'js, u32>), I32Array(TypedArray<'js, i32>), U64Array(TypedArray<'js, u64>), I64Array(TypedArray<'js, i64>), F32Array(TypedArray<'js, f32>), F64Array(TypedArray<'js, f64>), DataView(ArrayBuffer<'js>), Vec(Vec), } // Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64 unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> { type Changed<'to> = ObjectBytes<'to>; } impl<'js> Trace<'js> for ObjectBytes<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { match self { ObjectBytes::U8Array(a) => a.trace(tracer), ObjectBytes::I8Array(a) => a.trace(tracer), ObjectBytes::U16Array(a) => a.trace(tracer), ObjectBytes::I16Array(a) => a.trace(tracer), ObjectBytes::U32Array(a) => a.trace(tracer), ObjectBytes::I32Array(a) => a.trace(tracer), ObjectBytes::U64Array(a) => a.trace(tracer), ObjectBytes::I64Array(a) => a.trace(tracer), ObjectBytes::F32Array(a) => a.trace(tracer), ObjectBytes::F64Array(a) => a.trace(tracer), ObjectBytes::DataView(d) => d.trace(tracer), ObjectBytes::Vec(v) => v.trace(tracer), } } } impl<'js> IntoJs<'js> for ObjectBytes<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { ObjectBytes::U8Array(a) => a.into_js(ctx), ObjectBytes::I8Array(a) => a.into_js(ctx), ObjectBytes::U16Array(a) => a.into_js(ctx), ObjectBytes::I16Array(a) => a.into_js(ctx), ObjectBytes::U32Array(a) => a.into_js(ctx), ObjectBytes::I32Array(a) => a.into_js(ctx), ObjectBytes::U64Array(a) => a.into_js(ctx), ObjectBytes::I64Array(a) => a.into_js(ctx), ObjectBytes::F32Array(a) => a.into_js(ctx), ObjectBytes::F64Array(a) => a.into_js(ctx), ObjectBytes::DataView(d) => { let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?; ctor.construct((d,)) }, ObjectBytes::Vec(v) => v.into_js(ctx), } } } impl<'js> TryFrom> for Vec { type Error = Rc; fn try_from(value: ObjectBytes<'js>) -> std::result::Result { value.into_bytes_inner() } } impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] { type Error = Rc; fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result { value.as_bytes_inner() } } impl<'js> FromJs<'js> for ObjectBytes<'js> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { Self::from_offset(ctx, &value, 0, None) } } impl<'js> ObjectBytes<'js> { pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result { Self::from_offset(ctx, value, 0, None) } pub fn from_offset( ctx: &Ctx<'js>, value: &Value<'js>, offset: usize, length: Option, ) -> Result { if value.is_undefined() { return Ok(ObjectBytes::Vec(vec![])); } if let Some(bytes) = get_string_bytes(value, offset, length)? { return Ok(ObjectBytes::Vec(bytes)); } if let Some(bytes) = get_array_bytes(value, offset, length)? { return Ok(ObjectBytes::Vec(bytes)); } if let Some(obj) = value.as_object() { if let Some(bytes) = Self::from_array_buffer(obj)? { return Ok(bytes); } } if let Some(bytes) = get_coerced_string_bytes(value, offset, length) { return Ok(ObjectBytes::Vec(bytes)); } Err(Exception::throw_message( ctx, "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string", )) } pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> { self.as_bytes_inner().or_throw(ctx) } fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc> { match self { ObjectBytes::U8Array(array) => array.as_bytes(), ObjectBytes::I8Array(array) => array.as_bytes(), ObjectBytes::U16Array(array) => array.as_bytes(), ObjectBytes::I16Array(array) => array.as_bytes(), ObjectBytes::U32Array(array) => array.as_bytes(), ObjectBytes::I32Array(array) => array.as_bytes(), ObjectBytes::U64Array(array) => array.as_bytes(), ObjectBytes::I64Array(array) => array.as_bytes(), ObjectBytes::F32Array(array) => array.as_bytes(), ObjectBytes::F64Array(array) => array.as_bytes(), ObjectBytes::DataView(array_buffer) => array_buffer.as_bytes(), ObjectBytes::Vec(bytes) => Some(bytes.as_ref()), } .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into()) } pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result> { self.into_bytes_inner().or_throw(ctx) } fn into_bytes_inner(self) -> std::result::Result, Rc> { if let ObjectBytes::Vec(bytes) = self { return Ok(bytes); } Ok(self.as_bytes_inner()?.to_vec()) } pub fn from_array_buffer(obj: &Object<'js>) -> Result>> { //most common if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U8Array(typed_array))); } //second most common if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) { return Ok(Some(ObjectBytes::DataView(array_buffer))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I8Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U16Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I16Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U64Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I64Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::F32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::F64Array(typed_array))); } if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") { return Ok(Some(ObjectBytes::DataView(array_buffer))); } Ok(None) } pub fn get_array_buffer(&self) -> Result, usize, usize)>> { let buffer = match self { ObjectBytes::DataView(array_buffer) => (array_buffer.clone(), array_buffer.len(), 0), ObjectBytes::U8Array(typed_array) => { let byte_length = typed_array.len(); ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I8Array(typed_array) => { let byte_length = typed_array.len(); ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U16Array(typed_array) => { let byte_length = typed_array.len() * 2; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I16Array(typed_array) => { let byte_length = typed_array.len() * 2; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::F32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::F64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, _ => return Ok(None), }; Ok(Some(buffer)) } } pub fn get_start_end_indexes( source_len: usize, target_len: Option, offset: usize, ) -> (usize, usize) { if offset > source_len { return (0, 0); } let target_len = target_len.unwrap_or(source_len - offset); if offset + target_len > source_len { return (offset, source_len); } (offset, target_len + offset) } pub fn get_array_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Result>> { if value.is_array() { let array = value.as_array().unwrap(); let (start, end) = get_start_end_indexes(array.len(), length, offset); let size = end - start; let mut bytes: Vec = Vec::with_capacity(size); for val in array.iter::().skip(start).take(size) { let val: u8 = val?; bytes.push(val); } return Ok(Some(bytes)); } Ok(None) } pub fn get_coerced_string_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Option> { if let Ok(val) = value.get::>() { return Some(bytes_from_js_string(val.0, offset, length)); }; None } fn bytes_from_js_string(string: String, offset: usize, length: Option) -> Vec { let (start, end) = get_start_end_indexes(string.len(), length, offset); string.as_bytes()[start..end].to_vec() } #[inline] pub fn get_string_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Result>> { if let Some(val) = value.as_string() { let string = val.to_string()?; return Ok(Some(bytes_from_js_string(string, offset, length))); } Ok(None) } pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result> { TypedArray::::new(ctx.clone(), bytes).into_js(&ctx) }