Skip to main content

ferrijs_std/buffer/
class.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::{mem::MaybeUninit, slice};
4
5use crate::encoding::Encoder;
6use crate::iterable_enum;
7use crate::utils::{
8    bytes::{get_array_bytes, get_start_end_indexes, ObjectBytes},
9    error_messages::{ERROR_MSG_ARRAY_BUFFER_DETACHED, ERROR_MSG_NOT_ARRAY_BUFFER},
10    primordials::Primordial,
11    result::ResultExt,
12    string::{get_coerced_string, get_string},
13};
14use rquickjs::{
15    atom::PredefinedAtom,
16    function::{Constructor, Opt},
17    prelude::{Func, Rest, This},
18    Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, JsLifetime, Object, Result, TypedArray,
19    Value,
20};
21
22#[derive(JsLifetime)]
23pub struct BufferPrimordials<'js> {
24    constructor: Constructor<'js>,
25}
26
27impl<'js> Primordial<'js> for BufferPrimordials<'js> {
28    fn new(ctx: &Ctx<'js>) -> Result<Self>
29    where
30        Self: Sized,
31    {
32        let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
33
34        Ok(Self { constructor })
35    }
36}
37
38pub struct Buffer(pub Vec<u8>);
39
40fn resolve_view_bytes<'js>(
41    ctx: &Ctx<'js>,
42    array_buffer: ArrayBuffer<'js>,
43    byte_length: usize,
44    byte_offset: usize,
45) -> Result<&'js mut [u8]> {
46    let raw = array_buffer
47        .as_raw()
48        .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED)
49        .or_throw(ctx)?;
50
51    if byte_offset > raw.len() || byte_length > raw.len() - byte_offset {
52        return Err(Exception::throw_range(
53            ctx,
54            "The value of \"byteOffset\" is out of range",
55        ));
56    }
57
58    // SAFETY: bounds checked above.
59    Ok(unsafe { slice::from_raw_parts_mut(raw.cast::<u8>().as_ptr().add(byte_offset), byte_length) })
60}
61
62impl<'js> IntoJs<'js> for Buffer {
63    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
64        let array_buffer = ArrayBuffer::new(ctx.clone(), self.0)?;
65        Self::from_array_buffer(ctx, array_buffer)
66    }
67}
68
69impl<'js> Buffer {
70    pub fn alloc(length: usize) -> Self {
71        Self(vec![0; length])
72    }
73
74    pub fn to_string(&self, ctx: &Ctx<'js>, encoding: &str) -> Result<String> {
75        Encoder::from_str(encoding)
76            .and_then(|enc| enc.encode_to_string(self.0.as_ref(), true))
77            .or_throw(ctx)
78    }
79
80    fn from_array_buffer(ctx: &Ctx<'js>, buffer: ArrayBuffer<'js>) -> Result<Value<'js>> {
81        BufferPrimordials::get(ctx)?
82            .constructor
83            .construct((buffer,))
84    }
85
86    fn from_array_buffer_offset_length(
87        ctx: &Ctx<'js>,
88        array_buffer: ArrayBuffer<'js>,
89        offset: usize,
90        length: usize,
91    ) -> Result<Value<'js>> {
92        BufferPrimordials::get(ctx)?
93            .constructor
94            .construct((array_buffer, offset, length))
95    }
96
97    fn from_encoding(
98        ctx: &Ctx<'js>,
99        mut bytes: Vec<u8>,
100        encoding: Option<String>,
101    ) -> Result<Value<'js>> {
102        if let Some(encoding) = encoding {
103            let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
104            bytes = encoder.decode(bytes).or_throw(ctx)?;
105        }
106        Buffer(bytes).into_js(ctx)
107    }
108
109    fn from_string_encoding(
110        ctx: &Ctx<'js>,
111        string: String,
112        encoding: Option<String>,
113    ) -> Result<Value<'js>> {
114        let bytes = if let Some(encoding) = encoding {
115            let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
116            encoder.decode_from_string(string).or_throw(ctx)?
117        } else {
118            string.into_bytes()
119        };
120        Buffer(bytes).into_js(ctx)
121    }
122}
123
124// Static Methods
125fn alloc<'js>(
126    ctx: Ctx<'js>,
127    length: usize,
128    fill: Opt<Value<'js>>,
129    encoding: Opt<String>,
130) -> Result<Value<'js>> {
131    if let Some(value) = fill.0 {
132        if let Some(value) = value.as_string() {
133            let string = value.to_string()?;
134
135            if let Some(encoding) = encoding.0 {
136                let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
137                let bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
138                return alloc_byte_ref(&ctx, &bytes, length);
139            }
140
141            let byte_ref = string.as_bytes();
142
143            return alloc_byte_ref(&ctx, byte_ref, length);
144        }
145        if let Some(value) = value.as_int() {
146            let bytes = vec![value as u8; length];
147            return Buffer(bytes).into_js(&ctx);
148        }
149        if let Some(obj) = value.as_object() {
150            if let Some(ob) = ObjectBytes::from_array_buffer(obj)? {
151                let bytes = ob.as_bytes(&ctx)?;
152                return alloc_byte_ref(&ctx, bytes, length);
153            }
154        }
155    }
156
157    Buffer(vec![0; length]).into_js(&ctx)
158}
159
160fn alloc_byte_ref<'js>(ctx: &Ctx<'js>, byte_ref: &[u8], length: usize) -> Result<Value<'js>> {
161    let mut bytes = vec![0; length];
162    let byte_ref_length = byte_ref.len();
163    for i in 0..length {
164        bytes[i] = byte_ref[i % byte_ref_length];
165    }
166    Buffer(bytes).into_js(ctx)
167}
168
169fn alloc_unsafe(ctx: Ctx<'_>, size: usize) -> Result<Value<'_>> {
170    let mut bytes: Vec<MaybeUninit<u8>> = Vec::with_capacity(size);
171    unsafe {
172        bytes.set_len(size);
173    }
174
175    Buffer(maybeuninit_to_u8(bytes)).into_js(&ctx)
176}
177
178fn maybeuninit_to_u8(vec: Vec<MaybeUninit<u8>>) -> Vec<u8> {
179    let len = vec.len();
180    let capacity = vec.capacity();
181    let ptr = vec.as_ptr() as *mut u8;
182
183    std::mem::forget(vec);
184
185    // This conversion is safe because MaybeUninit has the same memory layout as u8, meaning the underlying bytes are identical.
186    // Since Vec<MaybeUninit> and Vec share the same memory representation, a simple reinterpretation of the pointer is valid.
187    // Additionally, Vec::from_raw_parts correctly reconstructs the vector using the original length and capacity, ensuring that memory ownership remains consistent.
188    // The call to std::mem::forget(vec) prevents the original Vec<MaybeUninit> from being dropped, avoiding double frees or memory corruption.
189    // However, this conversion is only safe if all elements of MaybeUninit are properly initialized.
190    // If any uninitialized values exist, reading them as u8 would lead to undefined behavior.
191    unsafe { Vec::from_raw_parts(ptr, len, capacity) }
192}
193
194fn alloc_unsafe_slow(ctx: Ctx<'_>, size: usize) -> Result<Value<'_>> {
195    let layout = std::alloc::Layout::array::<u8>(size).or_throw(&ctx)?;
196
197    let bytes = unsafe {
198        let ptr = std::alloc::alloc(layout);
199        if ptr.is_null() {
200            return Err(Exception::throw_internal(&ctx, "Memory allocation failed"));
201        }
202        Vec::from_raw_parts(ptr, size, size)
203    };
204    Buffer(bytes).into_js(&ctx)
205}
206
207fn byte_length<'js>(ctx: Ctx<'js>, value: Value<'js>, encoding: Opt<String>) -> Result<usize> {
208    //slow path
209    if let Some(encoding) = encoding.0 {
210        let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
211        let a = ObjectBytes::from(&ctx, &value)?;
212        let bytes = a.as_bytes(&ctx)?;
213        return Ok(encoder.decode(bytes).or_throw(&ctx)?.len());
214    }
215    //fast path
216    if let Some(val) = value.as_string() {
217        return Ok(val.to_string()?.len());
218    }
219
220    if value.is_array() {
221        let array = value.as_array().unwrap();
222
223        for val in array.iter::<u8>() {
224            val.or_throw_msg(&ctx, "array value is not u8")?;
225        }
226
227        return Ok(array.len());
228    }
229
230    if let Some(obj) = value.as_object() {
231        if let Some(ob) = ObjectBytes::from_array_buffer(obj)? {
232            return Ok(ob.as_bytes(&ctx)?.len());
233        }
234    }
235
236    Err(Exception::throw_message(
237        &ctx,
238        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or string",
239    ))
240}
241
242fn concat<'js>(ctx: Ctx<'js>, list: Array<'js>, max_length: Opt<usize>) -> Result<Value<'js>> {
243    let mut bytes = Vec::new();
244    let mut total_length = 0;
245    let mut length;
246    for value in list.iter::<Object>() {
247        let typed_array = TypedArray::<u8>::from_object(value?)?;
248        // SAFETY: copied before the iterator advances, which is the only
249        // thing here that re-enters JS.
250        let bytes_ref: &[u8] = unsafe { typed_array.as_bytes() }.unwrap_or_default();
251
252        length = bytes_ref.len();
253
254        if length == 0 {
255            continue;
256        }
257
258        if let Some(max_length) = max_length.0 {
259            total_length += length;
260            if total_length > max_length {
261                let diff = max_length - (total_length - length);
262                bytes.extend_from_slice(&bytes_ref[0..diff]);
263                break;
264            }
265        }
266        bytes.extend_from_slice(bytes_ref);
267    }
268
269    Buffer(bytes).into_js(&ctx)
270}
271
272fn from<'js>(
273    ctx: Ctx<'js>,
274    value: Value<'js>,
275    offset_or_encoding: Opt<Value<'js>>,
276    length: Opt<usize>,
277) -> Result<Value<'js>> {
278    let mut encoding: Option<String> = None;
279    let mut offset = 0;
280
281    if let Some(offset_or_encoding) = offset_or_encoding.0 {
282        if offset_or_encoding.is_string() {
283            encoding = Some(offset_or_encoding.get()?);
284        } else if offset_or_encoding.is_number() {
285            offset = offset_or_encoding.get()?;
286        }
287    }
288
289    // WARN: This is currently bugged for strings that can't be converted to utf8
290    // See https://github.com/quickjs-ng/quickjs/issues/992
291    if let Some(string) = get_string(&value)? {
292        return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
293    }
294    if let Some(bytes) = get_array_bytes(&value, offset, length.0)? {
295        return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
296    }
297
298    if let Some(obj) = value.as_object() {
299        if let Some(ab_bytes) = ObjectBytes::from_array_buffer(obj)? {
300            let bytes = ab_bytes.as_bytes(&ctx)?;
301            let (start, end) = get_start_end_indexes(bytes.len(), length.0, offset);
302
303            //buffers from buffer should be copied
304            if obj
305                .get::<_, Option<String>>(PredefinedAtom::Meta)?
306                .as_deref()
307                == Some(stringify!(Buffer))
308                || encoding.is_some()
309            {
310                let bytes = bytes.into();
311                return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
312            } else {
313                let (array_buffer, _, source_offset) = ab_bytes.get_array_buffer()?.unwrap(); //we know it's an array buffer
314                return Buffer::from_array_buffer_offset_length(
315                    &ctx,
316                    array_buffer,
317                    start + source_offset,
318                    end - start,
319                );
320            }
321        }
322    }
323
324    if let Some(string) = get_coerced_string(&value) {
325        return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
326    }
327
328    Err(Exception::throw_message(
329        &ctx,
330        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
331    ))
332}
333
334fn is_buffer<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<bool> {
335    if let Some(object) = value.as_object() {
336        let constructor = BufferPrimordials::get(&ctx)?;
337        return Ok(object.is_instance_of(&constructor.constructor));
338    }
339
340    Ok(false)
341}
342
343fn is_encoding(value: Value) -> Result<bool> {
344    if let Some(js_string) = value.as_string() {
345        let std_string = js_string.to_string()?;
346        return Ok(Encoder::from_str(std_string.as_str()).is_ok());
347    }
348
349    Ok(false)
350}
351
352// Prototype Methods
353fn copy<'js>(
354    this: This<Object<'js>>,
355    ctx: Ctx<'js>,
356    target: ObjectBytes<'js>,
357    args: Rest<usize>,
358) -> Result<usize> {
359    let mut args_iter = args.0.into_iter();
360    let target_start = args_iter.next().unwrap_or_default();
361    let source_start = args_iter.next().unwrap_or_default();
362    let source_end = args_iter.next().unwrap_or_else(|| this.0.len());
363
364    let source_bytes = ObjectBytes::from(&ctx, this.0.as_inner())?;
365    let source_bytes = source_bytes.as_bytes(&ctx)?;
366
367    if source_start > source_bytes.len() {
368        return Err(Exception::throw_range(
369            &ctx,
370            "The value of \"sourceStart\" is out of range",
371        ));
372    }
373
374    // sourceEnd is clamped (not an error), unlike sourceStart above.
375    let source_end = source_end.min(source_bytes.len());
376
377    let mut copyable_length = 0;
378
379    if source_start >= source_end {
380        return Ok(copyable_length);
381    }
382
383    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
384        target.get_array_buffer()?
385    {
386        let target_bytes =
387            resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
388
389        if target_start <= target_bytes.len() {
390            copyable_length = (source_end - source_start).min(target_bytes.len() - target_start);
391
392            target_bytes[target_start..target_start + copyable_length]
393                .copy_from_slice(&source_bytes[source_start..source_start + copyable_length]);
394        }
395    }
396
397    Ok(copyable_length)
398}
399
400fn subarray<'js>(
401    this: This<Object<'js>>,
402    ctx: Ctx<'js>,
403    start: Opt<isize>,
404    end: Opt<isize>,
405) -> Result<Value<'js>> {
406    let view = TypedArray::<u8>::from_object(this.0.clone())?;
407
408    let array_buffer = view.arraybuffer()?;
409    let view_offset = this.0.get::<_, isize>("byteOffset")?;
410    let view_length = this.0.get::<_, isize>("byteLength")?;
411
412    let start_index = start.map_or(0, |s| {
413        if s < 0 {
414            (view_length + s).max(0)
415        } else {
416            s.min(view_length)
417        }
418    });
419
420    let end_index = end.map_or(view_length, |e| {
421        if e < 0 {
422            (view_length + e).max(0)
423        } else {
424            e.min(view_length)
425        }
426    });
427
428    let length = (end_index - start_index).max(0) as usize;
429    let new_offset = (view_offset + start_index).max(0) as usize;
430
431    Buffer::from_array_buffer_offset_length(&ctx, array_buffer, new_offset, length)
432}
433
434fn to_string(
435    this: This<Object<'_>>,
436    ctx: Ctx,
437    encoding: Opt<String>,
438    start: Opt<i32>,
439    end: Opt<i32>,
440) -> Result<String> {
441    let typed_array = TypedArray::<u8>::from_object(this.0)?;
442    // SAFETY: the slicing and the encoder are pure Rust; nothing here
443    // re-enters script.
444    let bytes: &[u8] = unsafe { typed_array.as_bytes() }.unwrap_or_default();
445
446    let start = start
447        .0
448        .map(|s| s.max(0) as usize)
449        .unwrap_or(0)
450        .min(bytes.len());
451    let end = end
452        .0
453        .map(|e| e.max(0) as usize)
454        .unwrap_or(bytes.len())
455        .min(bytes.len());
456    let bytes = &bytes[start..end];
457
458    let encoder = Encoder::from_optional_str(encoding.as_deref()).or_throw(&ctx)?;
459    encoder.encode_to_string(bytes, true).or_throw(&ctx)
460}
461
462fn write<'js>(
463    this: This<Object<'js>>,
464    ctx: Ctx<'js>,
465    string: String,
466    args: Rest<Value<'js>>,
467) -> Result<usize> {
468    let (offset, length, encoding) = get_write_parameters(&ctx, &args, this.0.len())?;
469
470    let target = ObjectBytes::from(&ctx, this.0.as_inner())?;
471
472    let mut writable_length = 0;
473
474    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
475        target.get_array_buffer()?
476    {
477        let target_bytes =
478            resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
479
480        let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
481
482        if encoder.as_label() == "utf-8" {
483            let (source_slice, valid_length) = safe_byte_slice(&string, length.min(string.len()));
484            writable_length = valid_length;
485            target_bytes[offset..offset + writable_length].copy_from_slice(source_slice);
486        } else {
487            let decode_bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
488            writable_length = length.min(decode_bytes.len());
489            target_bytes[offset..offset + writable_length]
490                .copy_from_slice(&decode_bytes[..writable_length]);
491        };
492    }
493
494    Ok(writable_length)
495}
496
497fn get_write_parameters<'js>(
498    ctx: &Ctx<'js>,
499    args: &Rest<Value<'js>>,
500    len: usize,
501) -> Result<(usize, usize, String)> {
502    let mut offset = 0;
503    let mut length = len;
504    let mut encoding = "utf8".to_owned();
505
506    if let Some(v1) = args.0.first() {
507        if let Some(s) = v1.as_string() {
508            return Ok((0, len, s.to_string()?));
509        }
510        offset = v1.as_int().unwrap_or(0) as usize;
511        if offset > len {
512            return Err(Exception::throw_range(
513                ctx,
514                "The value of \"offset\" is out of range",
515            ));
516        }
517        length = len - offset;
518    }
519
520    if let Some(v2) = args.0.get(1) {
521        if let Some(s) = v2.as_string() {
522            return Ok((offset, len - offset, s.to_string()?));
523        }
524        length = v2
525            .as_int()
526            .map_or(len - offset, |l| (l as usize).min(len - offset));
527    }
528
529    if let Some(v3) = args.0.get(2) {
530        if let Some(s) = v3.as_string() {
531            encoding = s.to_string()?;
532        }
533    }
534
535    Ok((offset, length, encoding))
536}
537
538fn safe_byte_slice(s: &str, end: usize) -> (&[u8], usize) {
539    let bytes = s.as_bytes();
540
541    if bytes.len() <= end {
542        return (bytes, bytes.len());
543    }
544
545    let valid_end = s
546        .char_indices()
547        .map(|(i, _)| i)
548        .rfind(|&i| i <= end)
549        .unwrap_or(0);
550
551    (&bytes[0..valid_end], valid_end)
552}
553
554#[derive(Clone, Copy)]
555pub enum Endian {
556    Little,
557    Big,
558}
559
560#[derive(Clone, Copy, PartialEq, Eq)]
561pub enum NumberKind {
562    Int8,
563    UInt8,
564    Int16,
565    UInt16,
566    Int32,
567    UInt32,
568    Float32,
569    Float64,
570    BigInt,
571    BigUInt,
572}
573
574impl NumberKind {
575    pub fn bits(&self) -> u8 {
576        match self {
577            NumberKind::Int8 => 8,
578            NumberKind::UInt8 => 8,
579            NumberKind::Int16 => 16,
580            NumberKind::UInt16 => 16,
581            NumberKind::Int32 => 32,
582            NumberKind::UInt32 => 32,
583            NumberKind::Float32 => 32,
584            NumberKind::Float64 => 64,
585            NumberKind::BigInt => 64,
586            NumberKind::BigUInt => 64,
587        }
588    }
589
590    pub fn is_signed(&self) -> bool {
591        matches!(
592            self,
593            NumberKind::Int8 | NumberKind::Int16 | NumberKind::Int32
594        )
595    }
596
597    pub fn prototype(&self) -> &'static [(Endian, &'static str, Option<&'static str>)] {
598        match self {
599            NumberKind::Int8 => &[(Endian::Little, "Int8", None)],
600            NumberKind::UInt8 => &[(Endian::Little, "UInt8", Some("Uint8"))],
601            NumberKind::Int16 => &[
602                (Endian::Little, "Int16LE", None),
603                (Endian::Big, "Int16BE", None),
604            ],
605            NumberKind::UInt16 => &[
606                (Endian::Little, "UInt16LE", Some("Uint16LE")),
607                (Endian::Big, "UInt16BE", Some("Uint16BE")),
608            ],
609            NumberKind::Int32 => &[
610                (Endian::Little, "Int32LE", None),
611                (Endian::Big, "Int32BE", None),
612            ],
613            NumberKind::UInt32 => &[
614                (Endian::Little, "UInt32LE", Some("Uint32LE")),
615                (Endian::Big, "UInt32BE", Some("Uint32BE")),
616            ],
617            NumberKind::Float32 => &[
618                (Endian::Little, "FloatLE", None),
619                (Endian::Big, "FloatBE", None),
620            ],
621            NumberKind::Float64 => &[
622                (Endian::Little, "DoubleLE", None),
623                (Endian::Big, "DoubleBE", None),
624            ],
625            NumberKind::BigInt => &[
626                (Endian::Little, "BigInt64LE", None),
627                (Endian::Big, "BigInt64BE", None),
628            ],
629            NumberKind::BigUInt => &[
630                (Endian::Little, "BigUInt64LE", Some("BigUint64LE")),
631                (Endian::Big, "BigUInt64BE", Some("BigUint64BE")),
632            ],
633        }
634    }
635}
636
637iterable_enum!(
638    NumberKind, Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32, Float64, BigInt, BigUInt
639);
640
641#[allow(clippy::too_many_arguments)]
642fn write_buf<'js>(
643    this: &This<Object<'js>>,
644    ctx: &Ctx<'js>,
645    value: &Value<'js>,
646    offset: &Opt<usize>,
647    endian: Endian,
648    kind: NumberKind,
649) -> Result<usize> {
650    let offset = offset.0.unwrap_or_default();
651
652    // Extract and convert value
653    let (byte_count, bytes) = match kind {
654        NumberKind::BigInt => {
655            let Some(bigint) = value.as_big_int() else {
656                return Err(Exception::throw_type(ctx, "Expected BigInt"));
657            };
658            let (byte_count, val) = (8, bigint.clone().to_i64().or_throw(ctx)? as u64);
659            (byte_count, endian_bytes(val, endian))
660        },
661        NumberKind::BigUInt => {
662            return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
663        },
664        NumberKind::Float32 => {
665            let Some(float_val) = value.as_float() else {
666                return Err(Exception::throw_type(ctx, "Expected number"));
667            };
668            match endian {
669                Endian::Big => (4, (float_val as f32).to_bits().to_be_bytes().to_vec()),
670                Endian::Little => (4, (float_val as f32).to_bits().to_le_bytes().to_vec()),
671            }
672        },
673        NumberKind::Float64 => {
674            let Some(float_val) = value.as_float() else {
675                return Err(Exception::throw_type(ctx, "Expected number"));
676            };
677            match endian {
678                Endian::Big => (8, float_val.to_bits().to_be_bytes().to_vec()),
679                Endian::Little => (8, float_val.to_bits().to_le_bytes().to_vec()),
680            }
681        },
682        NumberKind::Int8
683        | NumberKind::UInt8
684        | NumberKind::Int16
685        | NumberKind::UInt16
686        | NumberKind::Int32
687        | NumberKind::UInt32 => {
688            let Some(int_val) = value.as_number() else {
689                return Err(Exception::throw_type(ctx, "Expected number"));
690            };
691            let int_val = int_val as i64;
692            let bit_mask = (1i64 << kind.bits()) - 1;
693            let max_val = if kind.is_signed() {
694                (1i64 << (kind.bits() - 1)) - 1
695            } else {
696                bit_mask
697            };
698            let min_val = if kind.is_signed() { -max_val - 1 } else { 0 };
699
700            if int_val < min_val || int_val > max_val {
701                return Err(Exception::throw_range(ctx, "Value out of range"));
702            }
703
704            let masked = int_val & bit_mask;
705            (
706                (kind.bits() / 8) as usize,
707                shifted_bytes(masked as u64, kind.bits(), endian),
708            )
709        },
710    };
711
712    if offset >= this.0.len() || offset + byte_count > this.0.len() {
713        return Err(Exception::throw_range(
714            ctx,
715            "The specified offset is out of range",
716        ));
717    }
718
719    let target = ObjectBytes::from(ctx, this.0.as_inner())?;
720    let mut writable_length = 0;
721
722    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
723        target.get_array_buffer()?
724    {
725        let target_bytes =
726            resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
727
728        writable_length = offset + bytes.len();
729        target_bytes[offset..writable_length].copy_from_slice(&bytes);
730    }
731
732    Ok(writable_length)
733}
734
735fn read_buf<'js>(
736    this: &This<Object<'js>>,
737    ctx: &Ctx<'js>,
738    offset: &Opt<usize>,
739    endian: Endian,
740    kind: NumberKind,
741) -> Result<Value<'js>> {
742    // Retrieve the array buffer
743    let target = ObjectBytes::from(ctx, this.0.as_inner())?;
744    let Some((array_buffer, target_byte_length, target_byte_offset)) = target.get_array_buffer()?
745    else {
746        return Err(Exception::throw_message(ctx, ERROR_MSG_NOT_ARRAY_BUFFER));
747    };
748    let target_bytes =
749        resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
750
751    // Enforce the bounds
752    let start = offset.0.unwrap_or_default();
753    let end = start + (kind.bits() / 8) as usize;
754    if end > target_bytes.len() {
755        return Err(Exception::throw_range(
756            ctx,
757            "The value of \"offset\" is out of range",
758        ));
759    }
760
761    let bytes = &target_bytes[start..end];
762
763    let value = match kind {
764        NumberKind::BigInt => {
765            let value = match endian {
766                Endian::Big => i64::from_be_bytes(bytes.try_into().unwrap()),
767                Endian::Little => i64::from_le_bytes(bytes.try_into().unwrap()),
768            };
769            Value::new_big_int(ctx.clone(), value)?
770        },
771        NumberKind::BigUInt => {
772            return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
773        },
774        NumberKind::Float32 => {
775            let value = match endian {
776                Endian::Big => f32::from_be_bytes(bytes.try_into().unwrap()),
777                Endian::Little => f32::from_le_bytes(bytes.try_into().unwrap()),
778            };
779            Value::new_float(ctx.clone(), value as f64)
780        },
781        NumberKind::Float64 => {
782            let value = match endian {
783                Endian::Big => f64::from_be_bytes(bytes.try_into().unwrap()),
784                Endian::Little => f64::from_le_bytes(bytes.try_into().unwrap()),
785            };
786            Value::new_float(ctx.clone(), value)
787        },
788        NumberKind::Int8 => {
789            let value = match endian {
790                Endian::Big => i8::from_be_bytes(bytes.try_into().unwrap()),
791                Endian::Little => i8::from_le_bytes(bytes.try_into().unwrap()),
792            };
793            Value::new_int(ctx.clone(), value as i32)
794        },
795        NumberKind::UInt8 => {
796            let value = match endian {
797                Endian::Big => u8::from_be_bytes(bytes.try_into().unwrap()),
798                Endian::Little => u8::from_le_bytes(bytes.try_into().unwrap()),
799            };
800            Value::new_int(ctx.clone(), value as i32)
801        },
802        NumberKind::Int16 => {
803            let value = match endian {
804                Endian::Big => i16::from_be_bytes(bytes.try_into().unwrap()),
805                Endian::Little => i16::from_le_bytes(bytes.try_into().unwrap()),
806            };
807            Value::new_int(ctx.clone(), value as i32)
808        },
809        NumberKind::UInt16 => {
810            let value = match endian {
811                Endian::Big => u16::from_be_bytes(bytes.try_into().unwrap()),
812                Endian::Little => u16::from_le_bytes(bytes.try_into().unwrap()),
813            };
814            Value::new_int(ctx.clone(), value as i32)
815        },
816        NumberKind::Int32 => {
817            let value = match endian {
818                Endian::Big => i32::from_be_bytes(bytes.try_into().unwrap()),
819                Endian::Little => i32::from_le_bytes(bytes.try_into().unwrap()),
820            };
821            Value::new_int(ctx.clone(), value)
822        },
823        NumberKind::UInt32 => {
824            let value = match endian {
825                Endian::Big => u32::from_be_bytes(bytes.try_into().unwrap()),
826                Endian::Little => u32::from_le_bytes(bytes.try_into().unwrap()),
827            };
828            Value::new_float(ctx.clone(), value as f64)
829        },
830    };
831    Ok(value)
832}
833
834// Pure mathematical byte generation
835fn endian_bytes(mut val: u64, endian: Endian) -> Vec<u8> {
836    let mut bytes = vec![0u8; 8];
837
838    #[allow(clippy::needless_range_loop)]
839    for i in 0..8 {
840        bytes[i] = match endian {
841            Endian::Big => (val >> (56 - i * 8)) as u8,
842            Endian::Little => (val >> (i * 8)) as u8,
843        };
844        // Clear processed bits
845        match endian {
846            Endian::Big => val &= !(0xFF << ((7 - i) * 8)),
847            Endian::Little => val &= !(0xFF << (i * 8)),
848        }
849    }
850    bytes
851}
852
853fn shifted_bytes(mut val: u64, bits: u8, endian: Endian) -> Vec<u8> {
854    let byte_count = (bits / 8) as usize;
855    let mut bytes = vec![0u8; byte_count];
856
857    #[allow(clippy::needless_range_loop)]
858    for i in 0..byte_count {
859        let shift = match endian {
860            Endian::Big => (byte_count - 1 - i) * 8,
861            Endian::Little => i * 8,
862        };
863        bytes[i] = (val >> shift) as u8;
864        val &= !(0xFF << shift); // Clear processed bits
865    }
866    bytes
867}
868
869pub(crate) fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> {
870    let _ = &constructor.set("alloc", Func::from(alloc))?;
871    let _ = &constructor.set("allocUnsafe", Func::from(alloc_unsafe))?;
872    let _ = &constructor.set("allocUnsafeSlow", Func::from(alloc_unsafe_slow))?;
873    let _ = &constructor.set("byteLength", Func::from(byte_length))?;
874    let _ = &constructor.set("concat", Func::from(concat))?;
875    let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?;
876    let _ = &constructor.set("isBuffer", Func::from(is_buffer))?;
877    let _ = &constructor.set("isEncoding", Func::from(is_encoding))?;
878
879    let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?;
880    prototype.set("copy", Func::from(copy))?;
881    prototype.set("subarray", Func::from(subarray))?;
882    prototype.set(PredefinedAtom::ToString, Func::from(to_string))?;
883    prototype.set("write", Func::from(write))?;
884
885    // Set all write and read methods
886    for kind in NumberKind::iter() {
887        for (endian, name, alias) in kind.prototype() {
888            let write_func = Function::new(ctx.clone(), |t, c, v, o| {
889                write_buf(&t, &c, &v, &o, *endian, *kind)
890            })?;
891            let read_func =
892                Function::new(ctx.clone(), |t, c, o| read_buf(&t, &c, &o, *endian, *kind))?;
893            if let Some(alias) = alias {
894                prototype.set(["write", alias].concat(), write_func.clone())?;
895                prototype.set(["read", alias].concat(), read_func.clone())?;
896            }
897            prototype.set(["write", name].concat(), write_func)?;
898            prototype.set(["read", name].concat(), read_func)?;
899        }
900    }
901
902    //not assessable from js
903    prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?;
904
905    ctx.globals().set(stringify!(Buffer), constructor)?;
906
907    Ok(())
908}
909
910#[cfg(test)]
911mod tests {
912    use crate::test::{call_test, test_async_with, ModuleEvaluator};
913
914    use crate::buffer::BufferModule;
915
916    #[tokio::test]
917    async fn test_subarray() {
918        test_async_with(|ctx| {
919            Box::pin(async move {
920                crate::buffer::init(&ctx).unwrap();
921                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
922                    .await
923                    .unwrap();
924
925                let data = "hello world".to_string().into_bytes();
926                let module = ModuleEvaluator::eval_js(
927                    ctx.clone(),
928                    "test",
929                    r#"
930                        import { Buffer } from 'buffer';
931
932                        export async function test(data) {
933                            let buffer = Buffer.from(data);
934                            let sub = buffer.subarray(6, 11); // "world" part
935                            return sub.toString();
936                        }
937                    "#,
938                )
939                .await
940                .unwrap();
941                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
942                assert_eq!(result, "world");
943            })
944        })
945        .await;
946    }
947
948    #[tokio::test]
949    async fn test_subarray_partial() {
950        test_async_with(|ctx| {
951            Box::pin(async move {
952                crate::buffer::init(&ctx).unwrap();
953                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
954                    .await
955                    .unwrap();
956
957                let data = "hello world".to_string().into_bytes();
958                let module = ModuleEvaluator::eval_js(
959                    ctx.clone(),
960                    "test",
961                    r#"
962                        import { Buffer } from 'buffer';
963
964                        export async function test(data) {
965                            let buffer = Buffer.from(data);
966                            let sub = buffer.subarray(0, 5); // "hello" part
967                            return sub.toString();
968                        }
969                    "#,
970                )
971                .await
972                .unwrap();
973                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
974                assert_eq!(result, "hello");
975            })
976        })
977        .await;
978    }
979
980    #[tokio::test]
981    async fn test_subarray_out_of_bounds() {
982        test_async_with(|ctx| {
983            Box::pin(async move {
984                crate::buffer::init(&ctx).unwrap();
985                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
986                    .await
987                    .unwrap();
988
989                let data = "hello world".to_string().into_bytes();
990                let module = ModuleEvaluator::eval_js(
991                    ctx.clone(),
992                    "test",
993                    r#"
994                        import { Buffer } from 'buffer';
995
996                        export async function test(data) {
997                            let buffer = Buffer.from(data);
998                            let sub = buffer.subarray(6, 20); // "world" part but goes out of bounds
999                            return sub.toString();
1000                        }
1001                    "#,
1002                )
1003                .await
1004                .unwrap();
1005                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
1006                assert_eq!(result, "world");
1007            })
1008        })
1009        .await;
1010    }
1011
1012    #[tokio::test]
1013    async fn test_read_int_32_be() {
1014        test_async_with(|ctx| {
1015            Box::pin(async move {
1016                crate::buffer::init(&ctx).unwrap();
1017                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
1018                    .await
1019                    .unwrap();
1020
1021                let data = "hello world".to_string().into_bytes();
1022                let module = ModuleEvaluator::eval_js(
1023                    ctx.clone(),
1024                    "test",
1025                    r#"
1026                        import { Buffer } from 'buffer';
1027
1028                        export async function test(data) {
1029                            const buf = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
1030                            return buf.readInt32BE();
1031                        }
1032                    "#,
1033                )
1034                .await
1035                .unwrap();
1036                let result = call_test::<i32, _>(&ctx, &module, (data,)).await;
1037                assert_eq!(result, 0x01020304);
1038            })
1039        })
1040        .await;
1041    }
1042}