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