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.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
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] = 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 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 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(); 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
355fn 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 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 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 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 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 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
838fn 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 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); }
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 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 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}