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 obligation rquickjs 0.13 attaches to `as_bytes` is forwarded
359    /// to the caller by the returned borrow: the slice aliases engine
360    /// memory, so no JavaScript may run while it is alive. Every caller
361    /// in this crate copies out of it before returning to script.
362    fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc<str>> {
363        // SAFETY: see the note above -- the borrow carries the contract.
364        unsafe {
365        match self {
366            ObjectBytes::U8Array(array) => array.as_bytes(),
367            ObjectBytes::I8Array(array) => array.as_bytes(),
368            ObjectBytes::U16Array(array) => array.as_bytes(),
369            ObjectBytes::I16Array(array) => array.as_bytes(),
370            ObjectBytes::U32Array(array) => array.as_bytes(),
371            ObjectBytes::I32Array(array) => array.as_bytes(),
372            ObjectBytes::U64Array(array) => array.as_bytes(),
373            ObjectBytes::I64Array(array) => array.as_bytes(),
374            ObjectBytes::F16Array(array) => array.as_bytes(),
375            ObjectBytes::F32Array(array) => array.as_bytes(),
376            ObjectBytes::F64Array(array) => array.as_bytes(),
377            ObjectBytes::U8ClampedArray(array) => array.as_bytes(),
378            ObjectBytes::DataView(ab, offset, length) => ab.as_bytes().and_then(|bytes| {
379                let end = offset.checked_add(*length)?;
380                bytes.get(*offset..end)
381            }),
382            ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
383        }
384        }
385        .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
386    }
387
388    pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result<Vec<u8>> {
389        self.into_bytes_inner().or_throw(ctx)
390    }
391
392    fn into_bytes_inner(self) -> std::result::Result<Vec<u8>, Rc<str>> {
393        if let ObjectBytes::Vec(bytes) = self {
394            return Ok(bytes);
395        }
396        Ok(self.as_bytes_inner()?.to_vec())
397    }
398
399    pub fn from_array_buffer(obj: &Object<'js>) -> Result<Option<ObjectBytes<'js>>> {
400        //most common
401        if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
402            return Ok(Some(ObjectBytes::U8Array(typed_array)));
403        }
404        //second most common
405        if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
406            let len = array_buffer.len();
407            return Ok(Some(ObjectBytes::DataView(array_buffer, 0, len)));
408        }
409
410        if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
411            return Ok(Some(ObjectBytes::I8Array(typed_array)));
412        }
413
414        if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
415            return Ok(Some(ObjectBytes::U16Array(typed_array)));
416        }
417
418        if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
419            return Ok(Some(ObjectBytes::I16Array(typed_array)));
420        }
421
422        if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
423            return Ok(Some(ObjectBytes::U32Array(typed_array)));
424        }
425
426        if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
427            return Ok(Some(ObjectBytes::I32Array(typed_array)));
428        }
429
430        if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
431            return Ok(Some(ObjectBytes::U64Array(typed_array)));
432        }
433
434        if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
435            return Ok(Some(ObjectBytes::I64Array(typed_array)));
436        }
437
438        if let Ok(typed_array) = TypedArray::<f16>::from_object(obj.clone()) {
439            return Ok(Some(ObjectBytes::F16Array(typed_array)));
440        }
441
442        if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
443            return Ok(Some(ObjectBytes::F32Array(typed_array)));
444        }
445
446        if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
447            return Ok(Some(ObjectBytes::F64Array(typed_array)));
448        }
449
450        if let Ok(typed_array) = TypedArray::<U8Clamped>::from_object(obj.clone()) {
451            return Ok(Some(ObjectBytes::U8ClampedArray(typed_array)));
452        }
453
454        if let Ok(ab) = obj.get::<_, ArrayBuffer>("buffer") {
455            let offset: usize = obj.get("byteOffset").unwrap_or(0);
456            let length: usize = obj.get("byteLength").unwrap_or_else(|_| ab.len());
457            return Ok(Some(ObjectBytes::DataView(ab, offset, length)));
458        }
459
460        Ok(None)
461    }
462
463    pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
464        let buffer = match self {
465            ObjectBytes::U8Array(typed_array) => {
466                let byte_length = typed_array.len();
467                (
468                    typed_array.arraybuffer()?,
469                    byte_length,
470                    typed_array.get("byteOffset")?,
471                )
472            },
473            ObjectBytes::I8Array(typed_array) => {
474                let byte_length = typed_array.len();
475                (
476                    typed_array.arraybuffer()?,
477                    byte_length,
478                    typed_array.get("byteOffset")?,
479                )
480            },
481            ObjectBytes::U16Array(typed_array) => {
482                let byte_length = typed_array.len() * 2;
483                (
484                    typed_array.arraybuffer()?,
485                    byte_length,
486                    typed_array.get("byteOffset")?,
487                )
488            },
489            ObjectBytes::I16Array(typed_array) => {
490                let byte_length = typed_array.len() * 2;
491                (
492                    typed_array.arraybuffer()?,
493                    byte_length,
494                    typed_array.get("byteOffset")?,
495                )
496            },
497            ObjectBytes::U32Array(typed_array) => {
498                let byte_length = typed_array.len() * 4;
499                (
500                    typed_array.arraybuffer()?,
501                    byte_length,
502                    typed_array.get("byteOffset")?,
503                )
504            },
505            ObjectBytes::I32Array(typed_array) => {
506                let byte_length = typed_array.len() * 4;
507                (
508                    typed_array.arraybuffer()?,
509                    byte_length,
510                    typed_array.get("byteOffset")?,
511                )
512            },
513            ObjectBytes::U64Array(typed_array) => {
514                let byte_length = typed_array.len() * 8;
515                (
516                    typed_array.arraybuffer()?,
517                    byte_length,
518                    typed_array.get("byteOffset")?,
519                )
520            },
521            ObjectBytes::I64Array(typed_array) => {
522                let byte_length = typed_array.len() * 8;
523                (
524                    typed_array.arraybuffer()?,
525                    byte_length,
526                    typed_array.get("byteOffset")?,
527                )
528            },
529            ObjectBytes::F16Array(typed_array) => {
530                let byte_length = typed_array.len() * 2;
531                (
532                    typed_array.arraybuffer()?,
533                    byte_length,
534                    typed_array.get("byteOffset")?,
535                )
536            },
537            ObjectBytes::F32Array(typed_array) => {
538                let byte_length = typed_array.len() * 4;
539                (
540                    typed_array.arraybuffer()?,
541                    byte_length,
542                    typed_array.get("byteOffset")?,
543                )
544            },
545            ObjectBytes::F64Array(typed_array) => {
546                let byte_length = typed_array.len() * 8;
547                (
548                    typed_array.arraybuffer()?,
549                    byte_length,
550                    typed_array.get("byteOffset")?,
551                )
552            },
553            ObjectBytes::U8ClampedArray(typed_array) => {
554                let byte_length = typed_array.len();
555                (
556                    typed_array.arraybuffer()?,
557                    byte_length,
558                    typed_array.get("byteOffset")?,
559                )
560            },
561            ObjectBytes::DataView(array_buffer, offset, length) => {
562                (array_buffer.clone(), *length, *offset)
563            },
564            _ => return Ok(None),
565        };
566
567        Ok(Some(buffer))
568    }
569}
570
571#[cfg(test)]
572mod object_bytes_tests {
573    use super::{ObjectBytes, ERROR_MSG_ARRAY_BUFFER_DETACHED};
574    use rquickjs::{ArrayBuffer, Context, Runtime};
575
576    #[test]
577    fn data_view_ranges_are_checked() {
578        let rt = Runtime::new().unwrap();
579        let ctx = Context::full(&rt).unwrap();
580
581        ctx.with(|ctx| {
582            let buffer = ArrayBuffer::new_copy(ctx, [1_u8, 2, 3, 4]).unwrap();
583            for (offset, length) in [(3, 2), (usize::MAX, 1)] {
584                let bytes = ObjectBytes::DataView(buffer.clone(), offset, length);
585
586                assert_eq!(
587                    bytes.as_bytes_inner().unwrap_err().as_ref(),
588                    ERROR_MSG_ARRAY_BUFFER_DETACHED
589                );
590            }
591
592            let valid_bytes = ObjectBytes::DataView(buffer, 1, 2);
593            assert_eq!(valid_bytes.as_bytes_inner().unwrap(), &[2, 3]);
594        });
595    }
596
597    #[test]
598    fn data_view_detached_buffer_returns_error() {
599        let rt = Runtime::new().unwrap();
600        let ctx = Context::full(&rt).unwrap();
601
602        ctx.with(|ctx| {
603            let mut buffer = ArrayBuffer::new_copy(ctx, [1_u8, 2, 3, 4]).unwrap();
604            buffer.detach();
605            let bytes = ObjectBytes::DataView(buffer, 0, 4);
606
607            assert_eq!(
608                bytes.as_bytes_inner().unwrap_err().as_ref(),
609                ERROR_MSG_ARRAY_BUFFER_DETACHED
610            );
611        });
612    }
613}
614
615pub fn get_start_end_indexes(
616    source_len: usize,
617    target_len: Option<usize>,
618    offset: usize,
619) -> (usize, usize) {
620    if offset > source_len {
621        return (0, 0);
622    }
623
624    let target_len = target_len.unwrap_or(source_len - offset);
625
626    if offset + target_len > source_len {
627        return (offset, source_len);
628    }
629
630    (offset, target_len + offset)
631}
632
633pub fn get_array_bytes(
634    value: &Value<'_>,
635    offset: usize,
636    length: Option<usize>,
637) -> Result<Option<Vec<u8>>> {
638    if value.is_array() {
639        let array = value.as_array().unwrap();
640        let (start, end) = get_start_end_indexes(array.len(), length, offset);
641        let size = end - start;
642        let mut bytes: Vec<u8> = Vec::with_capacity(size);
643
644        for val in array.iter::<u8>().skip(start).take(size) {
645            let val: u8 = val?;
646            bytes.push(val);
647        }
648
649        return Ok(Some(bytes));
650    }
651    Ok(None)
652}
653
654pub fn get_coerced_string_bytes(
655    value: &Value<'_>,
656    offset: usize,
657    length: Option<usize>,
658) -> Option<Vec<u8>> {
659    if let Ok(val) = value.get::<Coerced<String>>() {
660        return Some(bytes_from_js_string(val.0, offset, length));
661    };
662    None
663}
664
665fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
666    let (start, end) = get_start_end_indexes(string.len(), length, offset);
667    string.as_bytes()[start..end].to_vec()
668}
669
670#[inline]
671pub fn get_string_bytes(
672    value: &Value<'_>,
673    offset: usize,
674    length: Option<usize>,
675) -> Result<Option<Vec<u8>>> {
676    if value.is_string() {
677        let string = get_lossy_string(value.clone())?;
678        return Ok(Some(bytes_from_js_string(string, offset, length)));
679    }
680    Ok(None)
681}
682
683pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
684    TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
685}