Skip to main content

ferrijs_std/utils/
bytes.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::{rc::Rc, slice};
4
5use half::f16;
6use rquickjs::{
7    atom::PredefinedAtom,
8    class::{Trace, Tracer},
9    function::Constructor,
10    ArrayBuffer, Coerced, Ctx, Error, Exception, FromJs, IntoJs, JsLifetime, Object, Result,
11    TypedArray, U8Clamped, Value,
12};
13
14/// Convert a JS string to a `String`, replacing lone UTF-16 surrogates
15/// with U+FFFD per WHATWG USVString. Use when ill-formed strings must
16/// not fail.
17//
18// SAFETY (module-wide): QuickJS only emits valid WTF-8, so any run
19// without 0xED is valid strict UTF-8.
20pub fn get_lossy_string(string_value: Value) -> Result<String> {
21    let js_str = string_value.into_string().ok_or_else(|| Error::FromJs {
22        from: "Value",
23        to: "JSString",
24        message: Some("Value is not a string".into()),
25    })?;
26    let cstr = js_str.to_cstring()?;
27    let bytes = unsafe { slice::from_raw_parts(cstr.as_ptr() as *const u8, cstr.len()) };
28
29    let first = match memchr::memchr(0xED, bytes) {
30        None => return Ok(unsafe { String::from_utf8_unchecked(bytes.to_vec()) }),
31        Some(idx) => idx,
32    };
33    let mut result = String::with_capacity(bytes.len());
34    result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[..first]) });
35    qjs_substitute_into(&bytes[first..], &mut result);
36    Ok(result)
37}
38
39fn qjs_substitute_into(bytes: &[u8], result: &mut String) {
40    let mut start = 0;
41    while start < bytes.len() {
42        let next_ed = match memchr::memchr(0xED, &bytes[start..]) {
43            None => {
44                result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..]) });
45                return;
46            },
47            Some(rel) => start + rel,
48        };
49        if next_ed > start {
50            result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..next_ed]) });
51        }
52        if next_ed + 3 > bytes.len() {
53            replace_invalid_utf8_and_utf16_into(&bytes[next_ed..], result);
54            return;
55        }
56        let b1 = bytes[next_ed + 1];
57        let b2 = bytes[next_ed + 2];
58        if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 {
59            replace_invalid_utf8_and_utf16_into(&bytes[next_ed..], result);
60            return;
61        }
62        if (b1 & 0xE0) == 0xA0 {
63            result.push('\u{FFFD}');
64        } else {
65            result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[next_ed..next_ed + 3]) });
66        }
67        start = next_ed + 3;
68    }
69}
70
71#[doc(hidden)]
72pub fn replace_invalid_utf8_and_utf16(bytes: &[u8]) -> String {
73    let err = match simdutf8::compat::from_utf8(bytes) {
74        Ok(s) => return s.to_owned(),
75        Err(e) => e,
76    };
77    let valid_up_to = err.valid_up_to();
78    let mut result = String::with_capacity(bytes.len());
79    result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[..valid_up_to]) });
80    replace_invalid_utf8_and_utf16_into(&bytes[valid_up_to..], &mut result);
81    result
82}
83
84fn replace_invalid_utf8_and_utf16_into(bytes: &[u8], result: &mut String) {
85    let mut i = 0;
86
87    while i < bytes.len() {
88        let current = bytes[i];
89        match current {
90            0x00..=0x7F => {
91                result.push(current as char);
92                i += 1;
93            },
94            0xC0..=0xDF if i + 1 < bytes.len() => {
95                let next = bytes[i + 1];
96                if (next & 0xC0) == 0x80 {
97                    let code_point = ((current as u32 & 0x1F) << 6) | (next as u32 & 0x3F);
98                    result.push(char::from_u32(code_point).unwrap_or('\u{FFFD}'));
99                    i += 2;
100                } else {
101                    result.push('\u{FFFD}');
102                    i += 1;
103                }
104            },
105            0xE0..=0xEF if i + 2 < bytes.len() => {
106                let next1 = bytes[i + 1];
107                let next2 = bytes[i + 2];
108                if (next1 & 0xC0) == 0x80 && (next2 & 0xC0) == 0x80 {
109                    let code_point = ((current as u32 & 0x0F) << 12)
110                        | ((next1 as u32 & 0x3F) << 6)
111                        | (next2 as u32 & 0x3F);
112                    result.push(char::from_u32(code_point).unwrap_or('\u{FFFD}'));
113                    i += 3;
114                } else {
115                    result.push('\u{FFFD}');
116                    i += 1;
117                }
118            },
119            0xF0..=0xF7 if i + 3 < bytes.len() => {
120                let next1 = bytes[i + 1];
121                let next2 = bytes[i + 2];
122                let next3 = bytes[i + 3];
123                if (next1 & 0xC0) == 0x80 && (next2 & 0xC0) == 0x80 && (next3 & 0xC0) == 0x80 {
124                    let code_point = ((current as u32 & 0x07) << 18)
125                        | ((next1 as u32 & 0x3F) << 12)
126                        | ((next2 as u32 & 0x3F) << 6)
127                        | (next3 as u32 & 0x3F);
128                    result.push(char::from_u32(code_point).unwrap_or('\u{FFFD}'));
129                    i += 4;
130                } else {
131                    result.push('\u{FFFD}');
132                    i += 1;
133                }
134            },
135            _ => {
136                result.push('\u{FFFD}');
137                i += 1;
138            },
139        }
140    }
141}
142
143#[cfg(test)]
144mod replace_invalid_utf8_tests {
145    use super::replace_invalid_utf8_and_utf16;
146
147    fn cases() -> Vec<(&'static str, Vec<u8>, &'static str)> {
148        vec![
149            ("empty", vec![], ""),
150            ("ascii", b"hello world".to_vec(), "hello world"),
151            (
152                "ascii_with_control",
153                vec![b'a', 0x00, b'b', 0x7f, b'c'],
154                "a\u{0}b\u{7f}c",
155            ),
156            ("two_byte_latin1", vec![0xC3, 0xA9], "\u{00E9}"),
157            ("three_byte_cjk", vec![0xE4, 0xB8, 0x96], "\u{4e16}"),
158            ("four_byte_emoji", vec![0xF0, 0x9F, 0xA6, 0x80], "\u{1f980}"),
159            ("lone_high_surrogate", vec![0xED, 0xA0, 0xBD], "\u{FFFD}"),
160            ("lone_low_surrogate", vec![0xED, 0xB0, 0x80], "\u{FFFD}"),
161            (
162                "surrogate_pair_in_wtf8",
163                vec![0xED, 0xA0, 0xBD, 0xED, 0xB2, 0xA9],
164                "\u{FFFD}\u{FFFD}",
165            ),
166            ("stray_continuation", vec![0x80], "\u{FFFD}"),
167            ("truncated_two_byte", vec![0xC3], "\u{FFFD}"),
168            ("truncated_three_byte", vec![0xE0, 0xA0], "\u{FFFD}\u{FFFD}"),
169            (
170                "truncated_four_byte",
171                vec![0xF0, 0x9F, 0xA6],
172                "\u{FFFD}\u{FFFD}\u{FFFD}",
173            ),
174            (
175                "two_byte_bad_continuation",
176                vec![0xC3, 0x20, b'a'],
177                "\u{FFFD} a",
178            ),
179            (
180                "three_byte_bad_continuation",
181                vec![0xE4, 0xB8, 0x20, b'a'],
182                "\u{FFFD}\u{FFFD} a",
183            ),
184            ("high_byte_above_f7", vec![0xF8, b'a'], "\u{FFFD}a"),
185            (
186                "mixed_valid_and_invalid",
187                {
188                    let mut v = b"hello ".to_vec();
189                    v.extend_from_slice(&[0xED, 0xA0, 0xBD]);
190                    v.extend_from_slice(" world".as_bytes());
191                    v
192                },
193                "hello \u{FFFD} world",
194            ),
195            (
196                "long_ascii",
197                b"the quick brown fox jumps over the lazy dog".repeat(20),
198                &*Box::leak(
199                    "the quick brown fox jumps over the lazy dog"
200                        .repeat(20)
201                        .into_boxed_str(),
202                ),
203            ),
204        ]
205    }
206
207    #[test]
208    fn matches_contract() {
209        for (name, input, expected) in cases() {
210            let got = replace_invalid_utf8_and_utf16(&input);
211            assert_eq!(
212                got, expected,
213                "case `{}`: got {:?}, expected {:?}",
214                name, got, expected
215            );
216        }
217    }
218}
219
220use crate::utils::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt};
221
222#[derive(Clone, PartialEq)]
223pub enum ObjectBytes<'js> {
224    U8Array(TypedArray<'js, u8>),
225    I8Array(TypedArray<'js, i8>),
226    U16Array(TypedArray<'js, u16>),
227    I16Array(TypedArray<'js, i16>),
228    U32Array(TypedArray<'js, u32>),
229    I32Array(TypedArray<'js, i32>),
230    U64Array(TypedArray<'js, u64>),
231    I64Array(TypedArray<'js, i64>),
232    F16Array(TypedArray<'js, f16>),
233    F32Array(TypedArray<'js, f32>),
234    F64Array(TypedArray<'js, f64>),
235    U8ClampedArray(TypedArray<'js, U8Clamped>),
236    DataView(ArrayBuffer<'js>, usize, usize), // buffer, offset, length
237    Vec(Vec<u8>),
238}
239
240// Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64
241unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> {
242    type Changed<'to> = ObjectBytes<'to>;
243}
244
245impl<'js> Trace<'js> for ObjectBytes<'js> {
246    fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
247        match self {
248            ObjectBytes::U8Array(a) => a.trace(tracer),
249            ObjectBytes::I8Array(a) => a.trace(tracer),
250            ObjectBytes::U16Array(a) => a.trace(tracer),
251            ObjectBytes::I16Array(a) => a.trace(tracer),
252            ObjectBytes::U32Array(a) => a.trace(tracer),
253            ObjectBytes::I32Array(a) => a.trace(tracer),
254            ObjectBytes::U64Array(a) => a.trace(tracer),
255            ObjectBytes::I64Array(a) => a.trace(tracer),
256            ObjectBytes::F16Array(a) => a.trace(tracer),
257            ObjectBytes::F32Array(a) => a.trace(tracer),
258            ObjectBytes::F64Array(a) => a.trace(tracer),
259            ObjectBytes::U8ClampedArray(a) => a.trace(tracer),
260            ObjectBytes::DataView(ab, _, _) => ab.trace(tracer),
261            ObjectBytes::Vec(v) => v.trace(tracer),
262        }
263    }
264}
265
266impl<'js> IntoJs<'js> for ObjectBytes<'js> {
267    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
268        match self {
269            ObjectBytes::U8Array(a) => a.into_js(ctx),
270            ObjectBytes::I8Array(a) => a.into_js(ctx),
271            ObjectBytes::U16Array(a) => a.into_js(ctx),
272            ObjectBytes::I16Array(a) => a.into_js(ctx),
273            ObjectBytes::U32Array(a) => a.into_js(ctx),
274            ObjectBytes::I32Array(a) => a.into_js(ctx),
275            ObjectBytes::U64Array(a) => a.into_js(ctx),
276            ObjectBytes::I64Array(a) => a.into_js(ctx),
277            ObjectBytes::F16Array(a) => a.into_js(ctx),
278            ObjectBytes::F32Array(a) => a.into_js(ctx),
279            ObjectBytes::F64Array(a) => a.into_js(ctx),
280            ObjectBytes::U8ClampedArray(a) => a.into_js(ctx),
281            ObjectBytes::DataView(ab, _, _) => {
282                let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?;
283                ctor.construct((ab,))
284            },
285            ObjectBytes::Vec(v) => v.into_js(ctx),
286        }
287    }
288}
289
290impl<'js> TryFrom<ObjectBytes<'js>> for Vec<u8> {
291    type Error = Rc<str>;
292    fn try_from(value: ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
293        value.into_bytes_inner()
294    }
295}
296
297impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] {
298    type Error = Rc<str>;
299    fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
300        value.as_bytes_inner()
301    }
302}
303
304impl<'js> FromJs<'js> for ObjectBytes<'js> {
305    fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
306        Self::from_offset(ctx, &value, 0, None)
307    }
308}
309
310impl<'js> ObjectBytes<'js> {
311    pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result<Self> {
312        Self::from_offset(ctx, value, 0, None)
313    }
314
315    pub fn from_offset(
316        ctx: &Ctx<'js>,
317        value: &Value<'js>,
318        offset: usize,
319        length: Option<usize>,
320    ) -> Result<Self> {
321        if value.is_undefined() {
322            return Ok(ObjectBytes::Vec(vec![]));
323        }
324        if let Some(bytes) = get_string_bytes(value, offset, length)? {
325            return Ok(ObjectBytes::Vec(bytes));
326        }
327        if let Some(bytes) = get_array_bytes(value, offset, length)? {
328            return Ok(ObjectBytes::Vec(bytes));
329        }
330
331        if let Some(obj) = value.as_object() {
332            if let Some(bytes) = Self::from_array_buffer(obj)? {
333                return Ok(bytes);
334            }
335        }
336
337        if let Some(bytes) = get_coerced_string_bytes(value, offset, length) {
338            return Ok(ObjectBytes::Vec(bytes));
339        }
340
341        Err(Exception::throw_message(
342        ctx,
343        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
344    ))
345    }
346
347    pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> {
348        self.as_bytes_inner().or_throw(ctx)
349    }
350
351    /// Returns the underlying bytes, or `None` if the buffer is detached or
352    /// the DataView range is invalid (including arithmetic overflow). Unlike
353    /// [`as_bytes`], does not raise a JS exception.
354    pub fn as_bytes_opt(&self) -> Option<&[u8]> {
355        self.as_bytes_inner().ok()
356    }
357
358    /// The borrow carries 0.13's obligation to the caller: no JS may run
359    /// while the slice is alive.
360    fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc<str>> {
361        unsafe {
362        match self {
363            ObjectBytes::U8Array(array) => array.as_bytes(),
364            ObjectBytes::I8Array(array) => array.as_bytes(),
365            ObjectBytes::U16Array(array) => array.as_bytes(),
366            ObjectBytes::I16Array(array) => array.as_bytes(),
367            ObjectBytes::U32Array(array) => array.as_bytes(),
368            ObjectBytes::I32Array(array) => array.as_bytes(),
369            ObjectBytes::U64Array(array) => array.as_bytes(),
370            ObjectBytes::I64Array(array) => array.as_bytes(),
371            ObjectBytes::F16Array(array) => array.as_bytes(),
372            ObjectBytes::F32Array(array) => array.as_bytes(),
373            ObjectBytes::F64Array(array) => array.as_bytes(),
374            ObjectBytes::U8ClampedArray(array) => array.as_bytes(),
375            ObjectBytes::DataView(ab, offset, length) => ab.as_bytes().and_then(|bytes| {
376                let end = offset.checked_add(*length)?;
377                bytes.get(*offset..end)
378            }),
379            ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
380        }
381        }
382        .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
383    }
384
385    pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result<Vec<u8>> {
386        self.into_bytes_inner().or_throw(ctx)
387    }
388
389    fn into_bytes_inner(self) -> std::result::Result<Vec<u8>, Rc<str>> {
390        if let ObjectBytes::Vec(bytes) = self {
391            return Ok(bytes);
392        }
393        Ok(self.as_bytes_inner()?.to_vec())
394    }
395
396    pub fn from_array_buffer(obj: &Object<'js>) -> Result<Option<ObjectBytes<'js>>> {
397        //most common
398        if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
399            return Ok(Some(ObjectBytes::U8Array(typed_array)));
400        }
401        //second most common
402        if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
403            let len = array_buffer.len();
404            return Ok(Some(ObjectBytes::DataView(array_buffer, 0, len)));
405        }
406
407        if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
408            return Ok(Some(ObjectBytes::I8Array(typed_array)));
409        }
410
411        if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
412            return Ok(Some(ObjectBytes::U16Array(typed_array)));
413        }
414
415        if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
416            return Ok(Some(ObjectBytes::I16Array(typed_array)));
417        }
418
419        if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
420            return Ok(Some(ObjectBytes::U32Array(typed_array)));
421        }
422
423        if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
424            return Ok(Some(ObjectBytes::I32Array(typed_array)));
425        }
426
427        if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
428            return Ok(Some(ObjectBytes::U64Array(typed_array)));
429        }
430
431        if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
432            return Ok(Some(ObjectBytes::I64Array(typed_array)));
433        }
434
435        if let Ok(typed_array) = TypedArray::<f16>::from_object(obj.clone()) {
436            return Ok(Some(ObjectBytes::F16Array(typed_array)));
437        }
438
439        if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
440            return Ok(Some(ObjectBytes::F32Array(typed_array)));
441        }
442
443        if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
444            return Ok(Some(ObjectBytes::F64Array(typed_array)));
445        }
446
447        if let Ok(typed_array) = TypedArray::<U8Clamped>::from_object(obj.clone()) {
448            return Ok(Some(ObjectBytes::U8ClampedArray(typed_array)));
449        }
450
451        if let Ok(ab) = obj.get::<_, ArrayBuffer>("buffer") {
452            let offset: usize = obj.get("byteOffset").unwrap_or(0);
453            let length: usize = obj.get("byteLength").unwrap_or_else(|_| ab.len());
454            return Ok(Some(ObjectBytes::DataView(ab, offset, length)));
455        }
456
457        Ok(None)
458    }
459
460    pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
461        let buffer = match self {
462            ObjectBytes::U8Array(typed_array) => {
463                let byte_length = typed_array.len();
464                (
465                    typed_array.arraybuffer()?,
466                    byte_length,
467                    typed_array.get("byteOffset")?,
468                )
469            },
470            ObjectBytes::I8Array(typed_array) => {
471                let byte_length = typed_array.len();
472                (
473                    typed_array.arraybuffer()?,
474                    byte_length,
475                    typed_array.get("byteOffset")?,
476                )
477            },
478            ObjectBytes::U16Array(typed_array) => {
479                let byte_length = typed_array.len() * 2;
480                (
481                    typed_array.arraybuffer()?,
482                    byte_length,
483                    typed_array.get("byteOffset")?,
484                )
485            },
486            ObjectBytes::I16Array(typed_array) => {
487                let byte_length = typed_array.len() * 2;
488                (
489                    typed_array.arraybuffer()?,
490                    byte_length,
491                    typed_array.get("byteOffset")?,
492                )
493            },
494            ObjectBytes::U32Array(typed_array) => {
495                let byte_length = typed_array.len() * 4;
496                (
497                    typed_array.arraybuffer()?,
498                    byte_length,
499                    typed_array.get("byteOffset")?,
500                )
501            },
502            ObjectBytes::I32Array(typed_array) => {
503                let byte_length = typed_array.len() * 4;
504                (
505                    typed_array.arraybuffer()?,
506                    byte_length,
507                    typed_array.get("byteOffset")?,
508                )
509            },
510            ObjectBytes::U64Array(typed_array) => {
511                let byte_length = typed_array.len() * 8;
512                (
513                    typed_array.arraybuffer()?,
514                    byte_length,
515                    typed_array.get("byteOffset")?,
516                )
517            },
518            ObjectBytes::I64Array(typed_array) => {
519                let byte_length = typed_array.len() * 8;
520                (
521                    typed_array.arraybuffer()?,
522                    byte_length,
523                    typed_array.get("byteOffset")?,
524                )
525            },
526            ObjectBytes::F16Array(typed_array) => {
527                let byte_length = typed_array.len() * 2;
528                (
529                    typed_array.arraybuffer()?,
530                    byte_length,
531                    typed_array.get("byteOffset")?,
532                )
533            },
534            ObjectBytes::F32Array(typed_array) => {
535                let byte_length = typed_array.len() * 4;
536                (
537                    typed_array.arraybuffer()?,
538                    byte_length,
539                    typed_array.get("byteOffset")?,
540                )
541            },
542            ObjectBytes::F64Array(typed_array) => {
543                let byte_length = typed_array.len() * 8;
544                (
545                    typed_array.arraybuffer()?,
546                    byte_length,
547                    typed_array.get("byteOffset")?,
548                )
549            },
550            ObjectBytes::U8ClampedArray(typed_array) => {
551                let byte_length = typed_array.len();
552                (
553                    typed_array.arraybuffer()?,
554                    byte_length,
555                    typed_array.get("byteOffset")?,
556                )
557            },
558            ObjectBytes::DataView(array_buffer, offset, length) => {
559                (array_buffer.clone(), *length, *offset)
560            },
561            _ => return Ok(None),
562        };
563
564        Ok(Some(buffer))
565    }
566}
567
568#[cfg(test)]
569mod object_bytes_tests {
570    use super::{ObjectBytes, ERROR_MSG_ARRAY_BUFFER_DETACHED};
571    use rquickjs::{ArrayBuffer, Context, Runtime};
572
573    #[test]
574    fn data_view_ranges_are_checked() {
575        let rt = Runtime::new().unwrap();
576        let ctx = Context::full(&rt).unwrap();
577
578        ctx.with(|ctx| {
579            let buffer = ArrayBuffer::new_copy(ctx, [1_u8, 2, 3, 4]).unwrap();
580            for (offset, length) in [(3, 2), (usize::MAX, 1)] {
581                let bytes = ObjectBytes::DataView(buffer.clone(), offset, length);
582
583                assert_eq!(
584                    bytes.as_bytes_inner().unwrap_err().as_ref(),
585                    ERROR_MSG_ARRAY_BUFFER_DETACHED
586                );
587            }
588
589            let valid_bytes = ObjectBytes::DataView(buffer, 1, 2);
590            assert_eq!(valid_bytes.as_bytes_inner().unwrap(), &[2, 3]);
591        });
592    }
593
594    #[test]
595    fn data_view_detached_buffer_returns_error() {
596        let rt = Runtime::new().unwrap();
597        let ctx = Context::full(&rt).unwrap();
598
599        ctx.with(|ctx| {
600            let mut buffer = ArrayBuffer::new_copy(ctx, [1_u8, 2, 3, 4]).unwrap();
601            buffer.detach();
602            let bytes = ObjectBytes::DataView(buffer, 0, 4);
603
604            assert_eq!(
605                bytes.as_bytes_inner().unwrap_err().as_ref(),
606                ERROR_MSG_ARRAY_BUFFER_DETACHED
607            );
608        });
609    }
610}
611
612pub fn get_start_end_indexes(
613    source_len: usize,
614    target_len: Option<usize>,
615    offset: usize,
616) -> (usize, usize) {
617    if offset > source_len {
618        return (0, 0);
619    }
620
621    let target_len = target_len.unwrap_or(source_len - offset);
622
623    if offset + target_len > source_len {
624        return (offset, source_len);
625    }
626
627    (offset, target_len + offset)
628}
629
630pub fn get_array_bytes(
631    value: &Value<'_>,
632    offset: usize,
633    length: Option<usize>,
634) -> Result<Option<Vec<u8>>> {
635    if value.is_array() {
636        let array = value.as_array().unwrap();
637        let (start, end) = get_start_end_indexes(array.len(), length, offset);
638        let size = end - start;
639        let mut bytes: Vec<u8> = Vec::with_capacity(size);
640
641        for val in array.iter::<u8>().skip(start).take(size) {
642            let val: u8 = val?;
643            bytes.push(val);
644        }
645
646        return Ok(Some(bytes));
647    }
648    Ok(None)
649}
650
651pub fn get_coerced_string_bytes(
652    value: &Value<'_>,
653    offset: usize,
654    length: Option<usize>,
655) -> Option<Vec<u8>> {
656    if let Ok(val) = value.get::<Coerced<String>>() {
657        return Some(bytes_from_js_string(val.0, offset, length));
658    };
659    None
660}
661
662fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
663    let (start, end) = get_start_end_indexes(string.len(), length, offset);
664    string.as_bytes()[start..end].to_vec()
665}
666
667#[inline]
668pub fn get_string_bytes(
669    value: &Value<'_>,
670    offset: usize,
671    length: Option<usize>,
672) -> Result<Option<Vec<u8>>> {
673    if value.is_string() {
674        let string = get_lossy_string(value.clone())?;
675        return Ok(Some(bytes_from_js_string(string, offset, length)));
676    }
677    Ok(None)
678}
679
680pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
681    TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
682}