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