1use 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 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
124fn 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 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 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 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 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 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(); 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
350fn 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 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 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 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 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
830fn 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 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); }
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 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 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}