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: 0.13 requires that no JS run while the slice is alive.
249        // `bytes_ref` is copied into `bytes` and dropped before the loop
250        // advances the iterator, which is the only thing here that can
251        // re-enter JS. A detached buffer reads as empty and is skipped
252        // by the `length == 0` arm below.
253        let bytes_ref: &[u8] = unsafe { typed_array.as_bytes() }.unwrap_or_default();
254
255        length = bytes_ref.len();
256
257        if length == 0 {
258            continue;
259        }
260
261        if let Some(max_length) = max_length.0 {
262            total_length += length;
263            if total_length > max_length {
264                let diff = max_length - (total_length - length);
265                bytes.extend_from_slice(&bytes_ref[0..diff]);
266                break;
267            }
268        }
269        bytes.extend_from_slice(bytes_ref);
270    }
271
272    Buffer(bytes).into_js(&ctx)
273}
274
275fn from<'js>(
276    ctx: Ctx<'js>,
277    value: Value<'js>,
278    offset_or_encoding: Opt<Value<'js>>,
279    length: Opt<usize>,
280) -> Result<Value<'js>> {
281    let mut encoding: Option<String> = None;
282    let mut offset = 0;
283
284    if let Some(offset_or_encoding) = offset_or_encoding.0 {
285        if offset_or_encoding.is_string() {
286            encoding = Some(offset_or_encoding.get()?);
287        } else if offset_or_encoding.is_number() {
288            offset = offset_or_encoding.get()?;
289        }
290    }
291
292    // WARN: This is currently bugged for strings that can't be converted to utf8
293    // See https://github.com/quickjs-ng/quickjs/issues/992
294    if let Some(string) = get_string(&value)? {
295        return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
296    }
297    if let Some(bytes) = get_array_bytes(&value, offset, length.0)? {
298        return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
299    }
300
301    if let Some(obj) = value.as_object() {
302        if let Some(ab_bytes) = ObjectBytes::from_array_buffer(obj)? {
303            let bytes = ab_bytes.as_bytes(&ctx)?;
304            let (start, end) = get_start_end_indexes(bytes.len(), length.0, offset);
305
306            //buffers from buffer should be copied
307            if obj
308                .get::<_, Option<String>>(PredefinedAtom::Meta)?
309                .as_deref()
310                == Some(stringify!(Buffer))
311                || encoding.is_some()
312            {
313                let bytes = bytes.into();
314                return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
315            } else {
316                let (array_buffer, _, source_offset) = ab_bytes.get_array_buffer()?.unwrap(); //we know it's an array buffer
317                return Buffer::from_array_buffer_offset_length(
318                    &ctx,
319                    array_buffer,
320                    start + source_offset,
321                    end - start,
322                );
323            }
324        }
325    }
326
327    if let Some(string) = get_coerced_string(&value) {
328        return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx);
329    }
330
331    Err(Exception::throw_message(
332        &ctx,
333        "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
334    ))
335}
336
337fn is_buffer<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<bool> {
338    if let Some(object) = value.as_object() {
339        let constructor = BufferPrimordials::get(&ctx)?;
340        return Ok(object.is_instance_of(&constructor.constructor));
341    }
342
343    Ok(false)
344}
345
346fn is_encoding(value: Value) -> Result<bool> {
347    if let Some(js_string) = value.as_string() {
348        let std_string = js_string.to_string()?;
349        return Ok(Encoder::from_str(std_string.as_str()).is_ok());
350    }
351
352    Ok(false)
353}
354
355// Prototype Methods
356fn copy<'js>(
357    this: This<Object<'js>>,
358    ctx: Ctx<'js>,
359    target: ObjectBytes<'js>,
360    args: Rest<usize>,
361) -> Result<usize> {
362    let mut args_iter = args.0.into_iter();
363    let target_start = args_iter.next().unwrap_or_default();
364    let source_start = args_iter.next().unwrap_or_default();
365    let source_end = args_iter.next().unwrap_or_else(|| this.0.len());
366
367    let source_bytes = ObjectBytes::from(&ctx, this.0.as_inner())?;
368    let source_bytes = source_bytes.as_bytes(&ctx)?;
369
370    if source_start > source_bytes.len() {
371        return Err(Exception::throw_range(
372            &ctx,
373            "The value of \"sourceStart\" is out of range",
374        ));
375    }
376
377    // sourceEnd is clamped (not an error), unlike sourceStart above.
378    let source_end = source_end.min(source_bytes.len());
379
380    let mut copyable_length = 0;
381
382    if source_start >= source_end {
383        return Ok(copyable_length);
384    }
385
386    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
387        target.get_array_buffer()?
388    {
389        let target_bytes =
390            resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
391
392        if target_start <= target_bytes.len() {
393            copyable_length = (source_end - source_start).min(target_bytes.len() - target_start);
394
395            target_bytes[target_start..target_start + copyable_length]
396                .copy_from_slice(&source_bytes[source_start..source_start + copyable_length]);
397        }
398    }
399
400    Ok(copyable_length)
401}
402
403fn subarray<'js>(
404    this: This<Object<'js>>,
405    ctx: Ctx<'js>,
406    start: Opt<isize>,
407    end: Opt<isize>,
408) -> Result<Value<'js>> {
409    let view = TypedArray::<u8>::from_object(this.0.clone())?;
410
411    let array_buffer = view.arraybuffer()?;
412    let view_offset = this.0.get::<_, isize>("byteOffset")?;
413    let view_length = this.0.get::<_, isize>("byteLength")?;
414
415    let start_index = start.map_or(0, |s| {
416        if s < 0 {
417            (view_length + s).max(0)
418        } else {
419            s.min(view_length)
420        }
421    });
422
423    let end_index = end.map_or(view_length, |e| {
424        if e < 0 {
425            (view_length + e).max(0)
426        } else {
427            e.min(view_length)
428        }
429    });
430
431    let length = (end_index - start_index).max(0) as usize;
432    let new_offset = (view_offset + start_index).max(0) as usize;
433
434    Buffer::from_array_buffer_offset_length(&ctx, array_buffer, new_offset, length)
435}
436
437fn to_string(
438    this: This<Object<'_>>,
439    ctx: Ctx,
440    encoding: Opt<String>,
441    start: Opt<i32>,
442    end: Opt<i32>,
443) -> Result<String> {
444    let typed_array = TypedArray::<u8>::from_object(this.0)?;
445    // SAFETY: nothing between here and the last use of `bytes` runs JS --
446    // the slicing is arithmetic and the encoder is pure Rust. `or_throw`
447    // raises an exception rather than calling back into script.
448    let bytes: &[u8] = unsafe { typed_array.as_bytes() }.unwrap_or_default();
449
450    let start = start
451        .0
452        .map(|s| s.max(0) as usize)
453        .unwrap_or(0)
454        .min(bytes.len());
455    let end = end
456        .0
457        .map(|e| e.max(0) as usize)
458        .unwrap_or(bytes.len())
459        .min(bytes.len());
460    let bytes = &bytes[start..end];
461
462    let encoder = Encoder::from_optional_str(encoding.as_deref()).or_throw(&ctx)?;
463    encoder.encode_to_string(bytes, true).or_throw(&ctx)
464}
465
466fn write<'js>(
467    this: This<Object<'js>>,
468    ctx: Ctx<'js>,
469    string: String,
470    args: Rest<Value<'js>>,
471) -> Result<usize> {
472    let (offset, length, encoding) = get_write_parameters(&ctx, &args, this.0.len())?;
473
474    let target = ObjectBytes::from(&ctx, this.0.as_inner())?;
475
476    let mut writable_length = 0;
477
478    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
479        target.get_array_buffer()?
480    {
481        let target_bytes =
482            resolve_view_bytes(&ctx, array_buffer, target_byte_length, target_byte_offset)?;
483
484        let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
485
486        if encoder.as_label() == "utf-8" {
487            let (source_slice, valid_length) = safe_byte_slice(&string, length.min(string.len()));
488            writable_length = valid_length;
489            target_bytes[offset..offset + writable_length].copy_from_slice(source_slice);
490        } else {
491            let decode_bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
492            writable_length = length.min(decode_bytes.len());
493            target_bytes[offset..offset + writable_length]
494                .copy_from_slice(&decode_bytes[..writable_length]);
495        };
496    }
497
498    Ok(writable_length)
499}
500
501fn get_write_parameters<'js>(
502    ctx: &Ctx<'js>,
503    args: &Rest<Value<'js>>,
504    len: usize,
505) -> Result<(usize, usize, String)> {
506    let mut offset = 0;
507    let mut length = len;
508    let mut encoding = "utf8".to_owned();
509
510    if let Some(v1) = args.0.first() {
511        if let Some(s) = v1.as_string() {
512            return Ok((0, len, s.to_string()?));
513        }
514        offset = v1.as_int().unwrap_or(0) as usize;
515        if offset > len {
516            return Err(Exception::throw_range(
517                ctx,
518                "The value of \"offset\" is out of range",
519            ));
520        }
521        length = len - offset;
522    }
523
524    if let Some(v2) = args.0.get(1) {
525        if let Some(s) = v2.as_string() {
526            return Ok((offset, len - offset, s.to_string()?));
527        }
528        length = v2
529            .as_int()
530            .map_or(len - offset, |l| (l as usize).min(len - offset));
531    }
532
533    if let Some(v3) = args.0.get(2) {
534        if let Some(s) = v3.as_string() {
535            encoding = s.to_string()?;
536        }
537    }
538
539    Ok((offset, length, encoding))
540}
541
542fn safe_byte_slice(s: &str, end: usize) -> (&[u8], usize) {
543    let bytes = s.as_bytes();
544
545    if bytes.len() <= end {
546        return (bytes, bytes.len());
547    }
548
549    let valid_end = s
550        .char_indices()
551        .map(|(i, _)| i)
552        .rfind(|&i| i <= end)
553        .unwrap_or(0);
554
555    (&bytes[0..valid_end], valid_end)
556}
557
558#[derive(Clone, Copy)]
559pub enum Endian {
560    Little,
561    Big,
562}
563
564#[derive(Clone, Copy, PartialEq, Eq)]
565pub enum NumberKind {
566    Int8,
567    UInt8,
568    Int16,
569    UInt16,
570    Int32,
571    UInt32,
572    Float32,
573    Float64,
574    BigInt,
575    BigUInt,
576}
577
578impl NumberKind {
579    pub fn bits(&self) -> u8 {
580        match self {
581            NumberKind::Int8 => 8,
582            NumberKind::UInt8 => 8,
583            NumberKind::Int16 => 16,
584            NumberKind::UInt16 => 16,
585            NumberKind::Int32 => 32,
586            NumberKind::UInt32 => 32,
587            NumberKind::Float32 => 32,
588            NumberKind::Float64 => 64,
589            NumberKind::BigInt => 64,
590            NumberKind::BigUInt => 64,
591        }
592    }
593
594    pub fn is_signed(&self) -> bool {
595        matches!(
596            self,
597            NumberKind::Int8 | NumberKind::Int16 | NumberKind::Int32
598        )
599    }
600
601    pub fn prototype(&self) -> &'static [(Endian, &'static str, Option<&'static str>)] {
602        match self {
603            NumberKind::Int8 => &[(Endian::Little, "Int8", None)],
604            NumberKind::UInt8 => &[(Endian::Little, "UInt8", Some("Uint8"))],
605            NumberKind::Int16 => &[
606                (Endian::Little, "Int16LE", None),
607                (Endian::Big, "Int16BE", None),
608            ],
609            NumberKind::UInt16 => &[
610                (Endian::Little, "UInt16LE", Some("Uint16LE")),
611                (Endian::Big, "UInt16BE", Some("Uint16BE")),
612            ],
613            NumberKind::Int32 => &[
614                (Endian::Little, "Int32LE", None),
615                (Endian::Big, "Int32BE", None),
616            ],
617            NumberKind::UInt32 => &[
618                (Endian::Little, "UInt32LE", Some("Uint32LE")),
619                (Endian::Big, "UInt32BE", Some("Uint32BE")),
620            ],
621            NumberKind::Float32 => &[
622                (Endian::Little, "FloatLE", None),
623                (Endian::Big, "FloatBE", None),
624            ],
625            NumberKind::Float64 => &[
626                (Endian::Little, "DoubleLE", None),
627                (Endian::Big, "DoubleBE", None),
628            ],
629            NumberKind::BigInt => &[
630                (Endian::Little, "BigInt64LE", None),
631                (Endian::Big, "BigInt64BE", None),
632            ],
633            NumberKind::BigUInt => &[
634                (Endian::Little, "BigUInt64LE", Some("BigUint64LE")),
635                (Endian::Big, "BigUInt64BE", Some("BigUint64BE")),
636            ],
637        }
638    }
639}
640
641iterable_enum!(
642    NumberKind, Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32, Float64, BigInt, BigUInt
643);
644
645#[allow(clippy::too_many_arguments)]
646fn write_buf<'js>(
647    this: &This<Object<'js>>,
648    ctx: &Ctx<'js>,
649    value: &Value<'js>,
650    offset: &Opt<usize>,
651    endian: Endian,
652    kind: NumberKind,
653) -> Result<usize> {
654    let offset = offset.0.unwrap_or_default();
655
656    // Extract and convert value
657    let (byte_count, bytes) = match kind {
658        NumberKind::BigInt => {
659            let Some(bigint) = value.as_big_int() else {
660                return Err(Exception::throw_type(ctx, "Expected BigInt"));
661            };
662            let (byte_count, val) = (8, bigint.clone().to_i64().or_throw(ctx)? as u64);
663            (byte_count, endian_bytes(val, endian))
664        },
665        NumberKind::BigUInt => {
666            return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
667        },
668        NumberKind::Float32 => {
669            let Some(float_val) = value.as_float() else {
670                return Err(Exception::throw_type(ctx, "Expected number"));
671            };
672            match endian {
673                Endian::Big => (4, (float_val as f32).to_bits().to_be_bytes().to_vec()),
674                Endian::Little => (4, (float_val as f32).to_bits().to_le_bytes().to_vec()),
675            }
676        },
677        NumberKind::Float64 => {
678            let Some(float_val) = value.as_float() else {
679                return Err(Exception::throw_type(ctx, "Expected number"));
680            };
681            match endian {
682                Endian::Big => (8, float_val.to_bits().to_be_bytes().to_vec()),
683                Endian::Little => (8, float_val.to_bits().to_le_bytes().to_vec()),
684            }
685        },
686        NumberKind::Int8
687        | NumberKind::UInt8
688        | NumberKind::Int16
689        | NumberKind::UInt16
690        | NumberKind::Int32
691        | NumberKind::UInt32 => {
692            let Some(int_val) = value.as_number() else {
693                return Err(Exception::throw_type(ctx, "Expected number"));
694            };
695            let int_val = int_val as i64;
696            let bit_mask = (1i64 << kind.bits()) - 1;
697            let max_val = if kind.is_signed() {
698                (1i64 << (kind.bits() - 1)) - 1
699            } else {
700                bit_mask
701            };
702            let min_val = if kind.is_signed() { -max_val - 1 } else { 0 };
703
704            if int_val < min_val || int_val > max_val {
705                return Err(Exception::throw_range(ctx, "Value out of range"));
706            }
707
708            let masked = int_val & bit_mask;
709            (
710                (kind.bits() / 8) as usize,
711                shifted_bytes(masked as u64, kind.bits(), endian),
712            )
713        },
714    };
715
716    if offset >= this.0.len() || offset + byte_count > this.0.len() {
717        return Err(Exception::throw_range(
718            ctx,
719            "The specified offset is out of range",
720        ));
721    }
722
723    let target = ObjectBytes::from(ctx, this.0.as_inner())?;
724    let mut writable_length = 0;
725
726    if let Some((array_buffer, target_byte_length, target_byte_offset)) =
727        target.get_array_buffer()?
728    {
729        let target_bytes =
730            resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
731
732        writable_length = offset + bytes.len();
733        target_bytes[offset..writable_length].copy_from_slice(&bytes);
734    }
735
736    Ok(writable_length)
737}
738
739fn read_buf<'js>(
740    this: &This<Object<'js>>,
741    ctx: &Ctx<'js>,
742    offset: &Opt<usize>,
743    endian: Endian,
744    kind: NumberKind,
745) -> Result<Value<'js>> {
746    // Retrieve the array buffer
747    let target = ObjectBytes::from(ctx, this.0.as_inner())?;
748    let Some((array_buffer, target_byte_length, target_byte_offset)) = target.get_array_buffer()?
749    else {
750        return Err(Exception::throw_message(ctx, ERROR_MSG_NOT_ARRAY_BUFFER));
751    };
752    let target_bytes =
753        resolve_view_bytes(ctx, array_buffer, target_byte_length, target_byte_offset)?;
754
755    // Enforce the bounds
756    let start = offset.0.unwrap_or_default();
757    let end = start + (kind.bits() / 8) as usize;
758    if end > target_bytes.len() {
759        return Err(Exception::throw_range(
760            ctx,
761            "The value of \"offset\" is out of range",
762        ));
763    }
764
765    let bytes = &target_bytes[start..end];
766
767    let value = match kind {
768        NumberKind::BigInt => {
769            let value = match endian {
770                Endian::Big => i64::from_be_bytes(bytes.try_into().unwrap()),
771                Endian::Little => i64::from_le_bytes(bytes.try_into().unwrap()),
772            };
773            Value::new_big_int(ctx.clone(), value)?
774        },
775        NumberKind::BigUInt => {
776            return Err(Exception::throw_type(ctx, "Uint64 is not supported"));
777        },
778        NumberKind::Float32 => {
779            let value = match endian {
780                Endian::Big => f32::from_be_bytes(bytes.try_into().unwrap()),
781                Endian::Little => f32::from_le_bytes(bytes.try_into().unwrap()),
782            };
783            Value::new_float(ctx.clone(), value as f64)
784        },
785        NumberKind::Float64 => {
786            let value = match endian {
787                Endian::Big => f64::from_be_bytes(bytes.try_into().unwrap()),
788                Endian::Little => f64::from_le_bytes(bytes.try_into().unwrap()),
789            };
790            Value::new_float(ctx.clone(), value)
791        },
792        NumberKind::Int8 => {
793            let value = match endian {
794                Endian::Big => i8::from_be_bytes(bytes.try_into().unwrap()),
795                Endian::Little => i8::from_le_bytes(bytes.try_into().unwrap()),
796            };
797            Value::new_int(ctx.clone(), value as i32)
798        },
799        NumberKind::UInt8 => {
800            let value = match endian {
801                Endian::Big => u8::from_be_bytes(bytes.try_into().unwrap()),
802                Endian::Little => u8::from_le_bytes(bytes.try_into().unwrap()),
803            };
804            Value::new_int(ctx.clone(), value as i32)
805        },
806        NumberKind::Int16 => {
807            let value = match endian {
808                Endian::Big => i16::from_be_bytes(bytes.try_into().unwrap()),
809                Endian::Little => i16::from_le_bytes(bytes.try_into().unwrap()),
810            };
811            Value::new_int(ctx.clone(), value as i32)
812        },
813        NumberKind::UInt16 => {
814            let value = match endian {
815                Endian::Big => u16::from_be_bytes(bytes.try_into().unwrap()),
816                Endian::Little => u16::from_le_bytes(bytes.try_into().unwrap()),
817            };
818            Value::new_int(ctx.clone(), value as i32)
819        },
820        NumberKind::Int32 => {
821            let value = match endian {
822                Endian::Big => i32::from_be_bytes(bytes.try_into().unwrap()),
823                Endian::Little => i32::from_le_bytes(bytes.try_into().unwrap()),
824            };
825            Value::new_int(ctx.clone(), value)
826        },
827        NumberKind::UInt32 => {
828            let value = match endian {
829                Endian::Big => u32::from_be_bytes(bytes.try_into().unwrap()),
830                Endian::Little => u32::from_le_bytes(bytes.try_into().unwrap()),
831            };
832            Value::new_float(ctx.clone(), value as f64)
833        },
834    };
835    Ok(value)
836}
837
838// Pure mathematical byte generation
839fn endian_bytes(mut val: u64, endian: Endian) -> Vec<u8> {
840    let mut bytes = vec![0u8; 8];
841
842    #[allow(clippy::needless_range_loop)]
843    for i in 0..8 {
844        bytes[i] = match endian {
845            Endian::Big => (val >> (56 - i * 8)) as u8,
846            Endian::Little => (val >> (i * 8)) as u8,
847        };
848        // Clear processed bits
849        match endian {
850            Endian::Big => val &= !(0xFF << ((7 - i) * 8)),
851            Endian::Little => val &= !(0xFF << (i * 8)),
852        }
853    }
854    bytes
855}
856
857fn shifted_bytes(mut val: u64, bits: u8, endian: Endian) -> Vec<u8> {
858    let byte_count = (bits / 8) as usize;
859    let mut bytes = vec![0u8; byte_count];
860
861    #[allow(clippy::needless_range_loop)]
862    for i in 0..byte_count {
863        let shift = match endian {
864            Endian::Big => (byte_count - 1 - i) * 8,
865            Endian::Little => i * 8,
866        };
867        bytes[i] = (val >> shift) as u8;
868        val &= !(0xFF << shift); // Clear processed bits
869    }
870    bytes
871}
872
873pub(crate) fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> {
874    let _ = &constructor.set("alloc", Func::from(alloc))?;
875    let _ = &constructor.set("allocUnsafe", Func::from(alloc_unsafe))?;
876    let _ = &constructor.set("allocUnsafeSlow", Func::from(alloc_unsafe_slow))?;
877    let _ = &constructor.set("byteLength", Func::from(byte_length))?;
878    let _ = &constructor.set("concat", Func::from(concat))?;
879    let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?;
880    let _ = &constructor.set("isBuffer", Func::from(is_buffer))?;
881    let _ = &constructor.set("isEncoding", Func::from(is_encoding))?;
882
883    let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?;
884    prototype.set("copy", Func::from(copy))?;
885    prototype.set("subarray", Func::from(subarray))?;
886    prototype.set(PredefinedAtom::ToString, Func::from(to_string))?;
887    prototype.set("write", Func::from(write))?;
888
889    // Set all write and read methods
890    for kind in NumberKind::iter() {
891        for (endian, name, alias) in kind.prototype() {
892            let write_func = Function::new(ctx.clone(), |t, c, v, o| {
893                write_buf(&t, &c, &v, &o, *endian, *kind)
894            })?;
895            let read_func =
896                Function::new(ctx.clone(), |t, c, o| read_buf(&t, &c, &o, *endian, *kind))?;
897            if let Some(alias) = alias {
898                prototype.set(["write", alias].concat(), write_func.clone())?;
899                prototype.set(["read", alias].concat(), read_func.clone())?;
900            }
901            prototype.set(["write", name].concat(), write_func)?;
902            prototype.set(["read", name].concat(), read_func)?;
903        }
904    }
905
906    //not assessable from js
907    prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?;
908
909    ctx.globals().set(stringify!(Buffer), constructor)?;
910
911    Ok(())
912}
913
914#[cfg(test)]
915mod tests {
916    use crate::test::{call_test, test_async_with, ModuleEvaluator};
917
918    use crate::buffer::BufferModule;
919
920    #[tokio::test]
921    async fn test_subarray() {
922        test_async_with(|ctx| {
923            Box::pin(async move {
924                crate::buffer::init(&ctx).unwrap();
925                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
926                    .await
927                    .unwrap();
928
929                let data = "hello world".to_string().into_bytes();
930                let module = ModuleEvaluator::eval_js(
931                    ctx.clone(),
932                    "test",
933                    r#"
934                        import { Buffer } from 'buffer';
935
936                        export async function test(data) {
937                            let buffer = Buffer.from(data);
938                            let sub = buffer.subarray(6, 11); // "world" part
939                            return sub.toString();
940                        }
941                    "#,
942                )
943                .await
944                .unwrap();
945                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
946                assert_eq!(result, "world");
947            })
948        })
949        .await;
950    }
951
952    #[tokio::test]
953    async fn test_subarray_partial() {
954        test_async_with(|ctx| {
955            Box::pin(async move {
956                crate::buffer::init(&ctx).unwrap();
957                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
958                    .await
959                    .unwrap();
960
961                let data = "hello world".to_string().into_bytes();
962                let module = ModuleEvaluator::eval_js(
963                    ctx.clone(),
964                    "test",
965                    r#"
966                        import { Buffer } from 'buffer';
967
968                        export async function test(data) {
969                            let buffer = Buffer.from(data);
970                            let sub = buffer.subarray(0, 5); // "hello" part
971                            return sub.toString();
972                        }
973                    "#,
974                )
975                .await
976                .unwrap();
977                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
978                assert_eq!(result, "hello");
979            })
980        })
981        .await;
982    }
983
984    #[tokio::test]
985    async fn test_subarray_out_of_bounds() {
986        test_async_with(|ctx| {
987            Box::pin(async move {
988                crate::buffer::init(&ctx).unwrap();
989                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
990                    .await
991                    .unwrap();
992
993                let data = "hello world".to_string().into_bytes();
994                let module = ModuleEvaluator::eval_js(
995                    ctx.clone(),
996                    "test",
997                    r#"
998                        import { Buffer } from 'buffer';
999
1000                        export async function test(data) {
1001                            let buffer = Buffer.from(data);
1002                            let sub = buffer.subarray(6, 20); // "world" part but goes out of bounds
1003                            return sub.toString();
1004                        }
1005                    "#,
1006                )
1007                .await
1008                .unwrap();
1009                let result = call_test::<String, _>(&ctx, &module, (data,)).await;
1010                assert_eq!(result, "world");
1011            })
1012        })
1013        .await;
1014    }
1015
1016    #[tokio::test]
1017    async fn test_read_int_32_be() {
1018        test_async_with(|ctx| {
1019            Box::pin(async move {
1020                crate::buffer::init(&ctx).unwrap();
1021                ModuleEvaluator::eval_rust::<BufferModule>(ctx.clone(), "buffer")
1022                    .await
1023                    .unwrap();
1024
1025                let data = "hello world".to_string().into_bytes();
1026                let module = ModuleEvaluator::eval_js(
1027                    ctx.clone(),
1028                    "test",
1029                    r#"
1030                        import { Buffer } from 'buffer';
1031
1032                        export async function test(data) {
1033                            const buf = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]);
1034                            return buf.readInt32BE();
1035                        }
1036                    "#,
1037                )
1038                .await
1039                .unwrap();
1040                let result = call_test::<i32, _>(&ctx, &module, (data,)).await;
1041                assert_eq!(result, 0x01020304);
1042            })
1043        })
1044        .await;
1045    }
1046}