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