1use super::{arg_str, from_base64, from_hex, to_base64, to_hex};
6use crate::host::{with_host, JsObj};
7use fusevm::Value;
8use indexmap::IndexMap;
9
10pub const STATIC_METHODS: &[&str] = &[
28 "from",
29 "alloc",
30 "allocUnsafe",
31 "allocUnsafeSlow",
32 "concat",
33 "isBuffer",
34 "isEncoding",
35 "byteLength",
36 "compare",
37 "of",
38];
39
40pub const INSTANCE_METHODS: &[&str] = &[
44 "toString",
45 "set",
46 "toJSON",
47 "equals",
48 "slice",
49 "subarray",
50 "readUInt8",
51 "includes",
52 "indexOf",
53 "lastIndexOf",
54 "write",
55 "copy",
56 "fill",
57 "compare",
58 "readUInt16BE",
59 "readUInt16LE",
60 "writeUInt8",
61 "writeInt8",
62 "writeInt16BE",
63 "writeInt16LE",
64 "readFloatBE",
65 "readFloatLE",
66 "writeFloatBE",
67 "writeFloatLE",
68 "readDoubleBE",
69 "readDoubleLE",
70 "writeDoubleBE",
71 "writeDoubleLE",
72 "readBigInt64BE",
73 "readBigInt64LE",
74 "readBigUInt64BE",
75 "readBigUInt64LE",
76 "writeBigInt64BE",
77 "writeBigInt64LE",
78 "writeBigUInt64BE",
79 "writeBigUInt64LE",
80 "readIntBE",
81 "readIntLE",
82 "readUIntBE",
83 "readUIntLE",
84 "writeIntBE",
85 "writeIntLE",
86 "writeUIntBE",
87 "writeUIntLE",
88 "writeUInt16BE",
89 "writeUInt16LE",
90 "readUInt32BE",
91 "readUInt32LE",
92 "readInt8",
93 "readInt16BE",
94 "readInt16LE",
95 "readInt32BE",
96 "readInt32LE",
97 "writeUInt32BE",
98 "writeUInt32LE",
99 "writeInt32BE",
100 "writeInt32LE",
101 "at",
102 "values",
103 "keys",
104 "entries",
105 "swap16",
106 "swap32",
107 "swap64",
108];
109
110pub const MODULE_METHODS: &[&str] = &["atob", "btoa", "isAscii", "isUtf8", "transcode"];
114
115pub fn module_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
117 Some(match method {
118 "atob" => {
120 let s = arg_str(args, 0);
121 let bytes = from_base64(&s);
122 let bin: String = bytes.iter().map(|b| *b as char).collect();
123 Ok(with_host(|h| h.new_str(bin)))
124 }
125 "btoa" => {
127 let s = arg_str(args, 0);
128 let bytes: Vec<u8> = s.chars().map(|c| c as u32 as u8).collect();
129 let b64 = to_base64(&bytes);
130 Ok(with_host(|h| h.new_str(b64)))
131 }
132 "isAscii" => {
133 let bytes = input_bytes(args.first());
134 Ok(Value::Bool(bytes.iter().all(|b| *b < 0x80)))
135 }
136 "isUtf8" => {
137 let bytes = input_bytes(args.first());
138 Ok(Value::Bool(std::str::from_utf8(&bytes).is_ok()))
139 }
140 "transcode" => {
143 let src = input_bytes(args.first());
144 let from = arg_str(args, 1);
145 let to = arg_str(args, 2);
146 let s = bytes_to_string(&src, &from);
147 let out = string_to_bytes(&s, &to);
148 Ok(from_bytes(&out))
149 }
150 _ => return None,
151 })
152}
153
154fn input_bytes(v: Option<&Value>) -> Vec<u8> {
156 match v {
157 None => Vec::new(),
158 Some(v) => {
159 if let Some(s) = with_host(|h| h.as_str(v)) {
160 s.into_bytes()
161 } else {
162 bytes_of(v)
163 }
164 }
165 }
166}
167
168fn bytes_to_string(bytes: &[u8], enc: &str) -> String {
170 match enc.to_ascii_lowercase().as_str() {
171 "ascii" | "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
172 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
173 let units: Vec<u16> = bytes
174 .chunks_exact(2)
175 .map(|c| u16::from_le_bytes([c[0], c[1]]))
176 .collect();
177 String::from_utf16_lossy(&units)
178 }
179 _ => String::from_utf8_lossy(bytes).into_owned(),
180 }
181}
182
183fn string_to_bytes(s: &str, enc: &str) -> Vec<u8> {
189 match enc.to_ascii_lowercase().as_str() {
190 "ascii" => s
191 .chars()
192 .map(|c| if c.is_ascii() { c as u8 } else { b'?' })
193 .collect(),
194 "latin1" | "binary" => s.chars().map(|c| c as u32 as u8).collect(),
195 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
196 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
197 }
198 _ => s.as_bytes().to_vec(),
199 }
200}
201
202fn part_bytes(v: &Value) -> Vec<u8> {
212 match with_host(|h| h.as_str(v)) {
213 Some(s) => s.into_bytes(),
214 None => bytes_of(v),
215 }
216}
217
218fn gather_parts(parts: &Value) -> Vec<u8> {
220 let items = with_host(|h| match h.get(parts) {
221 Some(JsObj::Array(it)) => it.clone(),
222 _ => Vec::new(),
223 });
224 let mut out = Vec::new();
225 for it in &items {
226 out.extend(part_bytes(it));
227 }
228 out
229}
230
231fn opt_type(opts: Option<&Value>) -> String {
233 match opts {
234 Some(v) => with_host(|h| match h.get(v) {
235 Some(JsObj::Object(p)) => p.get("type").map(|x| h.str_of(x)).unwrap_or_default(),
236 _ => String::new(),
237 }),
238 None => String::new(),
239 }
240}
241
242fn build_blob(tag: &str, bytes: &[u8], typ: &str, extra: IndexMap<String, Value>) -> Value {
245 with_host(|h| {
246 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
247 let mut m = IndexMap::new();
248 m.insert("@@native".into(), h.new_str(tag.to_string()));
249 m.insert("@@bytes".into(), arr);
250 m.insert("size".into(), Value::Float(bytes.len() as f64));
251 m.insert("type".into(), h.new_str(typ.to_string()));
252 for (k, v) in extra {
253 m.insert(k, v);
254 }
255 h.new_object(m)
256 })
257}
258
259pub fn construct_blob(args: &[Value]) -> Result<Value, String> {
261 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
262 let typ = opt_type(args.get(1));
263 Ok(build_blob("Blob", &bytes, &typ, IndexMap::new()))
264}
265
266pub fn construct_file(args: &[Value]) -> Result<Value, String> {
268 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
269 let name = arg_str(args, 1);
270 let typ = opt_type(args.get(2));
271 let last_modified = args
273 .get(2)
274 .map(|v| {
275 with_host(|h| match h.get(v) {
276 Some(JsObj::Object(p)) => {
277 p.get("lastModified").map(|x| h.to_number(x)).unwrap_or(0.0)
278 }
279 _ => 0.0,
280 })
281 })
282 .unwrap_or(0.0);
283 let extra = with_host(|h| {
284 let mut m = IndexMap::new();
285 m.insert("name".to_string(), h.new_str(name));
286 m.insert("lastModified".to_string(), Value::Float(last_modified));
287 m
288 });
289 Ok(build_blob("File", &bytes, &typ, extra))
290}
291
292pub const BLOB_METHODS: &[&str] = &["text", "arrayBuffer", "bytes", "slice"];
294
295pub fn blob_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
300 let bytes = bytes_of(recv);
301 match method {
302 "text" => {
303 let s = String::from_utf8_lossy(&bytes).into_owned();
304 let sv = with_host(|h| h.new_str(s));
305 Ok(crate::host::promise_of(&sv))
306 }
307 "arrayBuffer" | "bytes" => {
308 let buf = from_bytes(&bytes);
309 Ok(crate::host::promise_of(&buf))
310 }
311 "slice" => {
312 let (s, e) = slice_bounds(args, bytes.len());
313 let typ = if args.len() > 2 {
314 arg_str(args, 2)
315 } else {
316 String::new()
317 };
318 Ok(build_blob("Blob", &bytes[s..e], &typ, IndexMap::new()))
319 }
320 _ => Err(crate::host::type_error(&format!(
321 "blob.{method} is not a function"
322 ))),
323 }
324}
325
326pub fn share_array_buffer(ab: &Value, off: usize, len: usize) -> Value {
329 let store = crate::stdlib::typedarray::buffer_store(ab);
330 with_host(|h| {
331 let mut m = IndexMap::new();
332 m.insert("@@native".into(), h.new_str("Buffer"));
333 m.insert(
334 "@@bytes".into(),
335 store.unwrap_or_else(|| h.new_array(Vec::new())),
336 );
337 m.insert("@@buffer".into(), ab.clone());
338 m.insert("buffer".into(), ab.clone());
339 m.insert("length".into(), Value::Float(len as f64));
340 m.insert("byteLength".into(), Value::Float(len as f64));
341 m.insert("byteOffset".into(), Value::Float(off as f64));
342 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(1.0));
343 let obj = h.new_object(m);
344 h.ensure_native_protos();
345 if let Some(p) = h.native_proto("Buffer") {
346 h.set_proto(&obj, p);
347 }
348 for k in [
349 "buffer",
350 "length",
351 "byteLength",
352 "byteOffset",
353 "BYTES_PER_ELEMENT",
354 ] {
355 h.hide_prop(&obj, k);
356 }
357 obj
358 })
359}
360
361pub fn from_bytes(bytes: &[u8]) -> Value {
363 with_host(|h| {
364 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
365 let mut m = IndexMap::new();
366 m.insert("@@native".into(), h.new_str("Buffer"));
367 m.insert("@@bytes".into(), arr);
368 m.insert("length".into(), Value::Float(bytes.len() as f64));
369 m.insert("byteLength".into(), Value::Float(bytes.len() as f64));
372 m.insert("byteOffset".into(), Value::Float(0.0));
373 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(1.0));
374 let obj = h.new_object(m);
375 h.ensure_native_protos();
379 if let Some(p) = h.native_proto("Buffer") {
380 h.set_proto(&obj, p);
381 }
382 for k in ["length", "byteLength", "byteOffset", "BYTES_PER_ELEMENT"] {
385 h.hide_prop(&obj, k);
386 }
387 obj
388 })
389}
390
391fn window(recv: &Value) -> (usize, usize) {
398 with_host(|h| match h.get(recv) {
399 Some(JsObj::Object(p)) => {
400 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0) as usize;
401 let store = match p.get("@@bytes").and_then(|v| h.get(v)) {
402 Some(JsObj::Array(items)) => items.len(),
403 _ => 0,
404 };
405 let len = p
406 .get("length")
407 .map(|l| h.to_number(l) as usize)
408 .unwrap_or(store);
409 (off.min(store), len.min(store.saturating_sub(off)))
410 }
411 _ => (0, 0),
412 })
413}
414
415fn bytes_of(recv: &Value) -> Vec<u8> {
416 let (off, len) = window(recv);
417 with_host(|h| match h.get(recv) {
418 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|v| h.get(v)) {
419 Some(JsObj::Array(items)) => items[off..off + len]
420 .iter()
421 .map(|v| h.to_number(v) as u8)
422 .collect(),
423 _ => Vec::new(),
424 },
425 _ => Vec::new(),
426 })
427}
428
429fn bytes_handle(recv: &Value) -> Option<Value> {
431 with_host(|h| match h.get(recv) {
432 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
433 _ => None,
434 })
435}
436
437pub fn byte_get(recv: &Value, index: &str) -> Value {
444 let i: usize = match index.parse() {
445 Ok(i) => i,
446 Err(_) => return Value::Undef,
447 };
448 let arr = match bytes_handle(recv) {
449 Some(a) => a,
450 None => return Value::Undef,
451 };
452 let (off, len) = window(recv);
453 if i >= len {
454 return Value::Undef;
455 }
456 with_host(|h| match h.get(&arr) {
457 Some(JsObj::Array(items)) => match items.get(off + i) {
458 Some(v) => Value::Float(h.to_number(v)),
459 None => Value::Undef,
460 },
461 _ => Value::Undef,
462 })
463}
464
465pub fn byte_set(recv: &Value, index: &str, val: &Value) -> bool {
471 if super::native_tag(recv).as_deref() != Some("Buffer") {
472 return false;
473 }
474 let i: usize = match index.parse() {
475 Ok(i) => i,
476 Err(_) => return false,
477 };
478 let arr = match bytes_handle(recv) {
479 Some(a) => a,
480 None => return false,
481 };
482 let b = with_host(|h| h.to_number(val)) as i64 as u8;
483 let (off, len) = window(recv);
486 if i >= len {
487 return true;
488 }
489 with_host(|h| {
490 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
491 if let Some(slot) = items.get_mut(off + i) {
493 *slot = Value::Float(b as f64);
494 }
495 }
496 });
497 true
498}
499
500const K_MAX_LENGTH: f64 = 9_007_199_254_740_991.0;
502
503fn validate_size(args: &[Value]) -> Result<usize, String> {
510 let v = args.first().cloned().unwrap_or(Value::Undef);
511 if with_host(|h| h.type_of(&v)) != "number" {
512 return Err(crate::host::invalid_arg_type(
513 "size", "argument", "number", &v,
514 ));
515 }
516 let n = with_host(|h| h.to_number(&v));
517 if n.is_nan() || !(0.0..=K_MAX_LENGTH).contains(&n) {
518 return Err(crate::host::coded_error(
519 "RangeError",
520 "ERR_OUT_OF_RANGE",
521 &format!(
522 "The value of \"size\" is out of range. It must be >= 0 && <= {}. Received {}",
523 crate::host::fmt_number(K_MAX_LENGTH),
524 out_of_range_received(n)
525 ),
526 ));
527 }
528 Ok(n as usize)
529}
530
531fn out_of_range_received(n: f64) -> String {
536 let shown = crate::host::fmt_number(n);
537 if n.fract() != 0.0 || n.abs() <= 4_294_967_296.0 {
538 return shown;
539 }
540 let start = usize::from(shown.starts_with('-'));
541 let mut i = shown.len();
542 let mut groups = String::new();
543 while i >= start + 4 {
544 groups = format!("_{}{groups}", &shown[i - 3..i]);
545 i -= 3;
546 }
547 format!("{}{groups}", &shown[..i])
548}
549
550pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
551 Some(match method {
552 "from" => from(args),
553 "alloc" => {
554 let n = match validate_size(args) {
555 Ok(n) => n,
556 Err(e) => return Some(Err(e)),
557 };
558 let pat = if args.len() > 1 {
561 let enc = if args.len() > 2 {
562 arg_str(args, 2)
563 } else {
564 "utf8".into()
565 };
566 fill_pattern(args, 1, &enc)
567 } else {
568 vec![0]
569 };
570 let bytes: Vec<u8> = if pat.is_empty() {
571 vec![0u8; n]
572 } else {
573 (0..n).map(|i| pat[i % pat.len()]).collect()
574 };
575 Ok(from_bytes(&bytes))
576 }
577 "allocUnsafe" | "allocUnsafeSlow" => validate_size(args).map(|n| from_bytes(&vec![0u8; n])),
581 "concat" => concat(args),
582 "of" => Ok(from_bytes(
586 &args
587 .iter()
588 .map(|v| crate::host::with_host(|h| h.to_number(v)) as u8)
589 .collect::<Vec<u8>>(),
590 )),
591 "isEncoding" => Ok(Value::Bool(matches!(
595 super::arg_str(args, 0).to_ascii_lowercase().as_str(),
596 "utf8"
597 | "utf-8"
598 | "ucs2"
599 | "ucs-2"
600 | "utf16le"
601 | "utf-16le"
602 | "latin1"
603 | "binary"
604 | "base64"
605 | "base64url"
606 | "hex"
607 | "ascii"
608 ))),
609 "compare" => {
611 let a = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
612 let b = bytes_of(&args.get(1).cloned().unwrap_or(Value::Undef));
613 Ok(Value::Float(match a.cmp(&b) {
614 std::cmp::Ordering::Less => -1.0,
615 std::cmp::Ordering::Equal => 0.0,
616 std::cmp::Ordering::Greater => 1.0,
617 }))
618 }
619 "isBuffer" => Ok(Value::Bool(
620 super::native_tag(&args.first().cloned().unwrap_or(Value::Undef)).as_deref()
621 == Some("Buffer"),
622 )),
623 "byteLength" => {
624 if let Some(n) = view_byte_length(&args.first().cloned().unwrap_or(Value::Undef)) {
627 return Some(Ok(Value::Float(n)));
628 }
629 let enc = args
630 .get(1)
631 .map(|_| arg_str(args, 1))
632 .unwrap_or_else(|| "utf8".into());
633 Ok(Value::Float(
634 decode_str(&arg_str(args, 0), &enc).len() as f64
635 ))
636 }
637 _ => return None,
638 })
639}
640
641pub fn view_bytes(v: &Value) -> Option<Vec<u8>> {
663 match super::native_tag(v).as_deref() {
664 Some("DataView") => {
670 let n = with_host(|h| match h.get(v) {
671 Some(JsObj::Object(p)) => p.get("byteLength").map(|l| h.to_number(l) as usize),
672 _ => None,
673 })
674 .unwrap_or(0);
675 crate::stdlib::typedarray::view_bytes(v, 0, n)
676 }
677 Some("Buffer") | Some("TypedArray") | Some("ArrayBuffer") => bytes_like(v),
678 _ => None,
679 }
680}
681
682pub fn bytes_like(v: &Value) -> Option<Vec<u8>> {
683 if let Some(elems) = crate::stdlib::typedarray::elems_of(v) {
685 return Some(elems.iter().map(|x| *x as i64 as u8).collect());
686 }
687 if super::native_tag(v).as_deref() == Some("ArrayBuffer") {
690 return Some(crate::stdlib::typedarray::buffer_bytes_snapshot(v).unwrap_or_default());
691 }
692 with_host(|h| match h.get(v) {
694 Some(JsObj::Array(items)) => {
695 Some(items.iter().map(|x| h.to_number(x) as i64 as u8).collect())
696 }
697 _ => None,
698 })
699}
700
701fn view_byte_length(v: &Value) -> Option<f64> {
705 match super::native_tag(v).as_deref() {
706 Some("Buffer") | Some("TypedArray") | Some("ArrayBuffer") => {
707 with_host(|h| match h.get(v) {
708 Some(JsObj::Object(p)) => p.get("byteLength").map(|b| h.to_number(b)),
709 _ => None,
710 })
711 }
712 _ => None,
713 }
714}
715
716fn from(args: &[Value]) -> Result<Value, String> {
717 let v = args.first().cloned().unwrap_or(Value::Undef);
718 if super::native_tag(&v).as_deref() == Some("ArrayBuffer") {
722 let total = crate::stdlib::typedarray::buffer_byte_length(&v);
723 let off = (super::arg_num(args, 1).max(0.0) as usize).min(total);
724 let len = match args.get(2) {
725 Some(Value::Undef) | None => total - off,
726 Some(_) => (super::arg_num(args, 2).max(0.0) as usize).min(total - off),
727 };
728 return Ok(share_array_buffer(&v, off, len));
729 }
730 if let Some(bytes) = bytes_like(&v) {
732 return Ok(from_bytes(&bytes));
733 }
734 if with_host(|h| h.as_str(&v)).is_some() || matches!(v, Value::Str(_)) {
736 let enc = if args.len() > 1 {
737 arg_str(args, 1)
738 } else {
739 "utf8".into()
740 };
741 return Ok(from_bytes(&decode_str(&arg_str(args, 0), &enc)));
742 }
743 if super::native_tag(&v).as_deref() == Some("DataView") {
746 return Ok(from_bytes(&[]));
747 }
748 if matches!(v, Value::Obj(_)) {
752 let len = crate::builtins::get_property(&v, "length").unwrap_or(Value::Undef);
753 if !matches!(len, Value::Undef) {
754 let n = with_host(|h| h.to_number(&len));
755 if n.is_finite() && n >= 0.0 {
756 let n = n as usize;
757 let mut out = Vec::with_capacity(n);
758 for i in 0..n {
759 let e =
760 crate::builtins::get_property(&v, &i.to_string()).unwrap_or(Value::Undef);
761 let b = with_host(|h| h.to_number(&e));
762 out.push(if b.is_finite() { b as i64 as u8 } else { 0 });
763 }
764 return Ok(from_bytes(&out));
765 }
766 }
767 }
768 Err(crate::host::plain_coded_error(
772 "TypeError",
773 "ERR_INVALID_ARG_TYPE",
774 &format!(
775 "The first argument must be of type string or an instance of \
776Buffer, ArrayBuffer, or Array or an Array-like Object. Received {}",
777 received_label(&v)
778 ),
779 ))
780}
781
782fn received_label(v: &Value) -> String {
785 if matches!(v, Value::Undef) {
786 return "undefined".into();
787 }
788 if with_host(|h| h.is_null(v)) {
789 return "null".into();
790 }
791 let ty = with_host(|h| h.type_of(v));
792 if ty == "object" || ty == "function" {
793 let ctor = with_host(|h| h.ctor_name(v));
794 let ctor = if ctor.is_empty() {
795 "Object".into()
796 } else {
797 ctor
798 };
799 return format!("an instance of {ctor}");
800 }
801 let shown = with_host(|h| h.inspect(v));
802 format!("type {ty} ({shown})")
803}
804
805fn concat(args: &[Value]) -> Result<Value, String> {
806 let list = with_host(
807 |h| match h.get(&args.first().cloned().unwrap_or(Value::Undef)) {
808 Some(JsObj::Array(items)) => items.clone(),
809 _ => Vec::new(),
810 },
811 );
812 let mut out = Vec::new();
813 for b in &list {
814 out.extend(bytes_like(b).unwrap_or_default());
816 }
817 Ok(from_bytes(&out))
818}
819
820pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
822 let bytes = bytes_of(recv);
823 match method {
824 "toString" => {
827 let enc = match args.first() {
828 None | Some(Value::Undef) => "utf8".into(),
829 _ => arg_str(args, 0),
830 };
831 let len = bytes.len();
832 let clamp = |i: usize| -> usize {
833 let n = super::arg_num(args, i);
834 if n.is_nan() {
835 0
836 } else {
837 n.clamp(0.0, len as f64) as usize
838 }
839 };
840 let start = if args.len() > 1 { clamp(1) } else { 0 };
841 let end = if args.len() > 2 { clamp(2) } else { len };
842 let slice = if start < end {
844 &bytes[start..end]
845 } else {
846 &[][..]
847 };
848 Ok(with_host(|h| h.new_str(encode_bytes(slice, &enc))))
849 }
850 "toJSON" => Ok(with_host(|h| {
851 let data = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
852 let mut m = IndexMap::new();
853 m.insert("type".into(), h.new_str("Buffer"));
854 m.insert("data".into(), data);
855 h.new_object(m)
856 })),
857 "equals" => {
858 let other = bytes_like(&args.first().cloned().unwrap_or(Value::Undef));
860 Ok(Value::Bool(other.is_some_and(|o| bytes == o)))
861 }
862 "slice" | "subarray" => {
863 let (s, e) = slice_bounds(args, bytes.len());
864 Ok(from_bytes(&bytes[s..e]))
865 }
866 "readUInt8" => {
867 let i = read_offset(args, 1, bytes.len())?;
868 Ok(Value::Float(bytes[i] as f64))
869 }
870 "includes" | "indexOf" | "lastIndexOf" => {
874 let len = bytes.len();
875 let last = method == "lastIndexOf";
876 let (from, enc) = match args.get(1) {
878 None | Some(Value::Undef) => (None, arg_str(args, 2)),
879 Some(v) if with_host(|h| h.as_str(v)).is_some() => (None, arg_str(args, 1)),
880 _ => (Some(super::arg_num(args, 1)), arg_str(args, 2)),
881 };
882 let enc = if enc.is_empty() { "utf8".into() } else { enc };
883 let target = args.first().cloned().unwrap_or(Value::Undef);
885 let needle = match &target {
886 Value::Int(_) | Value::Float(_) => vec![super::arg_num(args, 0) as u8],
887 _ if bytes_like(&target).is_some() => bytes_like(&target).unwrap_or_default(),
889 _ => decode_str(&arg_str(args, 0), &enc),
890 };
891 let from = from.map(|n| {
894 if n.is_nan() {
895 0
896 } else if n < 0.0 {
897 (len as f64 + n).max(0.0) as usize
898 } else {
899 (n as usize).min(len)
900 }
901 });
902 let pos = if needle.is_empty() {
904 Some(from.unwrap_or(if last { len } else { 0 }).min(len))
905 } else if last {
906 let hi = (from.unwrap_or(len) + needle.len()).min(len);
909 bytes[..hi]
910 .windows(needle.len())
911 .rposition(|w| w == needle.as_slice())
912 } else {
913 let lo = from.unwrap_or(0);
914 bytes[lo..]
915 .windows(needle.len())
916 .position(|w| w == needle.as_slice())
917 .map(|p| p + lo)
918 };
919 if method == "includes" {
920 Ok(Value::Bool(pos.is_some()))
921 } else {
922 Ok(Value::Float(pos.map(|p| p as f64).unwrap_or(-1.0)))
923 }
924 }
925 "compare" => {
927 let other = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
928 Ok(Value::Float(match bytes.cmp(&other) {
929 std::cmp::Ordering::Less => -1.0,
930 std::cmp::Ordering::Equal => 0.0,
931 std::cmp::Ordering::Greater => 1.0,
932 }))
933 }
934 "readUInt16BE" => {
936 let i = read_offset(args, 2, bytes.len())?;
937 let v = ((bytes[i] as u16) << 8) | bytes[i + 1] as u16;
938 Ok(Value::Float(v as f64))
939 }
940 "readUInt16LE" => {
941 let i = read_offset(args, 2, bytes.len())?;
942 let v = (bytes[i] as u16) | ((bytes[i + 1] as u16) << 8);
943 Ok(Value::Float(v as f64))
944 }
945 "readUInt32BE" | "readUInt32LE" | "readInt32BE" | "readInt32LE" => {
948 let i = read_offset(args, 4, bytes.len())?;
949 let at = |k: usize| bytes[i + k] as u32;
950 let v = if method.ends_with("BE") {
951 (at(0) << 24) | (at(1) << 16) | (at(2) << 8) | at(3)
952 } else {
953 at(0) | (at(1) << 8) | (at(2) << 16) | (at(3) << 24)
954 };
955 Ok(Value::Float(if method.starts_with("readInt") {
956 v as i32 as f64
957 } else {
958 v as f64
959 }))
960 }
961 "readInt8" => {
962 let i = read_offset(args, 1, bytes.len())?;
963 Ok(Value::Float(bytes[i] as i8 as f64))
964 }
965 "readInt16BE" | "readInt16LE" => {
966 let i = read_offset(args, 2, bytes.len())?;
967 let at = |k: usize| bytes[i + k] as u16;
968 let v = if method.ends_with("BE") {
969 (at(0) << 8) | at(1)
970 } else {
971 at(0) | (at(1) << 8)
972 };
973 Ok(Value::Float(v as i16 as f64))
974 }
975 "at" => {
977 let i = super::arg_num(args, 0);
978 let idx = if i < 0.0 { i + bytes.len() as f64 } else { i };
979 Ok(match bytes.get(idx.max(-1.0) as usize) {
980 Some(b) if idx >= 0.0 => Value::Float(*b as f64),
981 _ => Value::Undef,
982 })
983 }
984 "values" | "keys" | "entries" | "@@iterator" => {
989 let items: Vec<Value> = with_host(|h| match method {
990 "keys" => (0..bytes.len()).map(|i| Value::Float(i as f64)).collect(),
991 "values" | "@@iterator" => bytes.iter().map(|b| Value::Float(*b as f64)).collect(),
992 _ => bytes
993 .iter()
994 .enumerate()
995 .map(|(i, b)| {
996 h.new_array(vec![Value::Float(i as f64), Value::Float(*b as f64)])
997 })
998 .collect(),
999 });
1000 Ok(with_host(|h| {
1001 h.alloc(JsObj::Iter {
1002 items,
1003 idx: 0,
1004 array: None,
1005 })
1006 }))
1007 }
1008 "readFloatBE" | "readFloatLE" => {
1011 let i = read_offset(args, 4, bytes.len())?;
1012 let mut raw = [0u8; 4];
1013 raw.copy_from_slice(&bytes[i..i + 4]);
1014 if method.ends_with("LE") {
1015 raw.reverse();
1016 }
1017 Ok(Value::Float(f32::from_be_bytes(raw) as f64))
1018 }
1019 "readDoubleBE" | "readDoubleLE" => {
1020 let i = read_offset(args, 8, bytes.len())?;
1021 let mut raw = [0u8; 8];
1022 raw.copy_from_slice(&bytes[i..i + 8]);
1023 if method.ends_with("LE") {
1024 raw.reverse();
1025 }
1026 Ok(Value::Float(f64::from_be_bytes(raw)))
1027 }
1028 "writeFloatBE" | "writeFloatLE" => {
1029 let mut raw = (super::arg_num(args, 0) as f32).to_be_bytes();
1030 if method.ends_with("LE") {
1031 raw.reverse();
1032 }
1033 let off = super::arg_num(args, 1).max(0.0) as usize;
1034 store_bytes(recv, &bytes, off, &raw)?;
1035 Ok(Value::Float((off + 4) as f64))
1036 }
1037 "writeDoubleBE" | "writeDoubleLE" => {
1038 let mut raw = super::arg_num(args, 0).to_be_bytes();
1039 if method.ends_with("LE") {
1040 raw.reverse();
1041 }
1042 let off = super::arg_num(args, 1).max(0.0) as usize;
1043 store_bytes(recv, &bytes, off, &raw)?;
1044 Ok(Value::Float((off + 8) as f64))
1045 }
1046 "readBigInt64BE" | "readBigInt64LE" | "readBigUInt64BE" | "readBigUInt64LE" => {
1049 let i = read_offset(args, 8, bytes.len())?;
1050 let mut raw = [0u8; 8];
1051 raw.copy_from_slice(&bytes[i..i + 8]);
1052 if method.ends_with("LE") {
1053 raw.reverse();
1054 }
1055 let n = if method.starts_with("readBigInt") {
1056 num_bigint::BigInt::from(i64::from_be_bytes(raw))
1057 } else {
1058 num_bigint::BigInt::from(u64::from_be_bytes(raw))
1059 };
1060 Ok(with_host(|h| h.alloc(JsObj::BigInt(n))))
1061 }
1062 "writeBigInt64BE" | "writeBigInt64LE" | "writeBigUInt64BE" | "writeBigUInt64LE" => {
1063 let v = args.first().cloned().unwrap_or(Value::Undef);
1064 let n = with_host(|h| match h.get(&v) {
1065 Some(JsObj::BigInt(b)) => b.clone(),
1066 _ => num_bigint::BigInt::from(h.to_number(&v) as i64),
1067 });
1068 let bits = num_traits::ToPrimitive::to_i64(&n)
1071 .map(|x| x as u64)
1072 .or_else(|| num_traits::ToPrimitive::to_u64(&n))
1073 .unwrap_or(0);
1074 let mut raw = bits.to_be_bytes();
1075 if method.ends_with("LE") {
1076 raw.reverse();
1077 }
1078 let off = super::arg_num(args, 1).max(0.0) as usize;
1079 store_bytes(recv, &bytes, off, &raw)?;
1080 Ok(Value::Float((off + 8) as f64))
1081 }
1082 "readIntBE" | "readIntLE" | "readUIntBE" | "readUIntLE" => {
1085 let off = super::arg_num(args, 0).max(0.0) as usize;
1086 let width = (super::arg_num(args, 1).max(1.0) as usize).min(6);
1087 if off + width > bytes.len() {
1088 return Err(range_error_out_of_bounds());
1089 }
1090 let mut acc: u64 = 0;
1091 for k in 0..width {
1092 let b = if method.ends_with("BE") {
1093 bytes[off + k]
1094 } else {
1095 bytes[off + width - 1 - k]
1096 };
1097 acc = (acc << 8) | b as u64;
1098 }
1099 let signed = method.starts_with("readInt");
1100 let out = if signed {
1101 let shift = 64 - (width * 8);
1103 ((acc << shift) as i64 >> shift) as f64
1104 } else {
1105 acc as f64
1106 };
1107 Ok(Value::Float(out))
1108 }
1109 "writeIntBE" | "writeIntLE" | "writeUIntBE" | "writeUIntLE" => {
1110 let val = super::arg_num(args, 0) as i64 as u64;
1111 let off = super::arg_num(args, 1).max(0.0) as usize;
1112 let width = (super::arg_num(args, 2).max(1.0) as usize).min(6);
1113 let mut raw: Vec<u8> = (0..width)
1114 .map(|k| (val >> (8 * (width - 1 - k))) as u8)
1115 .collect();
1116 if method.ends_with("LE") {
1117 raw.reverse();
1118 }
1119 store_bytes(recv, &bytes, off, &raw)?;
1120 Ok(Value::Float((off + width) as f64))
1121 }
1122 "writeInt8" => {
1123 let off = super::arg_num(args, 1).max(0.0) as usize;
1124 store_bytes(recv, &bytes, off, &[super::arg_num(args, 0) as i64 as u8])?;
1125 Ok(Value::Float((off + 1) as f64))
1126 }
1127 "writeInt16BE" | "writeInt16LE" => {
1128 let val = super::arg_num(args, 0) as i64 as u16;
1129 let mut raw = val.to_be_bytes();
1130 if method.ends_with("LE") {
1131 raw.reverse();
1132 }
1133 let off = super::arg_num(args, 1).max(0.0) as usize;
1134 store_bytes(recv, &bytes, off, &raw)?;
1135 Ok(Value::Float((off + 2) as f64))
1136 }
1137 "writeUInt8" => {
1139 let off = super::arg_num(args, 1).max(0.0) as usize;
1140 store_bytes(recv, &bytes, off, &[super::arg_num(args, 0) as u8])?;
1141 Ok(Value::Float((off + 1) as f64))
1142 }
1143 "writeUInt16BE" | "writeUInt16LE" => {
1144 let mut b = bytes.clone();
1145 let val = super::arg_num(args, 0) as u16;
1146 let off = super::arg_num(args, 1).max(0.0) as usize;
1147 let (hi, lo) = ((val >> 8) as u8, (val & 0xff) as u8);
1148 let (b0, b1) = if method == "writeUInt16BE" {
1149 (hi, lo)
1150 } else {
1151 (lo, hi)
1152 };
1153 let _ = &mut b;
1154 store_bytes(recv, &bytes, off, &[b0, b1])?;
1155 Ok(Value::Float((off + 2) as f64))
1156 }
1157 "writeUInt32BE" | "writeUInt32LE" | "writeInt32BE" | "writeInt32LE" => {
1158 let mut b = bytes.clone();
1159 let val = super::arg_num(args, 0) as i64 as u32;
1160 let off = super::arg_num(args, 1).max(0.0) as usize;
1161 let be = [
1162 (val >> 24) as u8,
1163 (val >> 16) as u8,
1164 (val >> 8) as u8,
1165 val as u8,
1166 ];
1167 let out: Vec<u8> = if method.ends_with("BE") {
1168 be.to_vec()
1169 } else {
1170 be.iter().rev().copied().collect()
1171 };
1172 let _ = &mut b;
1173 store_bytes(recv, &bytes, off, &out)?;
1174 Ok(Value::Float((off + 4) as f64))
1175 }
1176 "write" => {
1180 let mut b = bytes.clone();
1181 let Some((off, max, enc)) = write_args(args, b.len()) else {
1182 return Err(crate::host::range_error(&format!(
1183 "The value of \"offset\" is out of range. It must be >= 0 && <= {}. Received {}",
1184 b.len(),
1185 super::arg_num(args, 1)
1186 )));
1187 };
1188 let src = truncate_chars(&arg_str(args, 0), &enc, max);
1189 let n = src.len().min(b.len().saturating_sub(off));
1190 b[off..off + n].copy_from_slice(&src[..n]);
1191 set_bytes(recv, &b);
1192 Ok(Value::Float(n as f64))
1193 }
1194 "swap16" | "swap32" | "swap64" => {
1198 let group = match method {
1199 "swap16" => 2,
1200 "swap32" => 4,
1201 _ => 8,
1202 };
1203 if bytes.len() % group != 0 {
1204 return Err(crate::host::coded_error(
1205 "RangeError",
1206 "ERR_INVALID_BUFFER_SIZE",
1207 &format!("Buffer size must be a multiple of {}-bits", group * 8),
1208 ));
1209 }
1210 let mut b = bytes.clone();
1211 for c in b.chunks_mut(group) {
1212 c.reverse();
1213 }
1214 set_bytes(recv, &b);
1215 Ok(recv.clone())
1216 }
1217 "fill" => {
1223 let mut b = bytes.clone();
1224 let len = b.len();
1225 let (start, end, enc) = if arg_is_str(args, 1) {
1226 (0, len, arg_str(args, 1))
1227 } else if arg_is_str(args, 2) {
1228 let s = (super::arg_num(args, 1).max(0.0) as usize).min(len);
1229 (s, len, arg_str(args, 2))
1230 } else {
1231 let s = if args.len() > 1 {
1232 (super::arg_num(args, 1).max(0.0) as usize).min(len)
1233 } else {
1234 0
1235 };
1236 let e = if args.len() > 2 {
1237 (super::arg_num(args, 2).max(0.0) as usize).min(len)
1238 } else {
1239 len
1240 };
1241 let enc = if args.len() > 3 {
1242 arg_str(args, 3)
1243 } else {
1244 "utf8".into()
1245 };
1246 (s, e, enc)
1247 };
1248 let pat = fill_pattern(args, 0, &enc);
1249 if !pat.is_empty() {
1250 for (k, slot) in b[start..end.max(start)].iter_mut().enumerate() {
1251 *slot = pat[k % pat.len()];
1252 }
1253 }
1254 set_bytes(recv, &b);
1255 Ok(recv.clone())
1256 }
1257 "copy" => {
1259 let target = args.first().cloned().unwrap_or(Value::Undef);
1260 let mut tb = bytes_of(&target);
1261 let tstart = if args.len() > 1 {
1262 super::arg_num(args, 1).max(0.0) as usize
1263 } else {
1264 0
1265 };
1266 let sstart = if args.len() > 2 {
1267 super::arg_num(args, 2).max(0.0) as usize
1268 } else {
1269 0
1270 };
1271 let send = if args.len() > 3 {
1272 (super::arg_num(args, 3) as usize).min(bytes.len())
1273 } else {
1274 bytes.len()
1275 };
1276 let mut n = 0;
1277 for (k, &byte) in bytes[sstart..send.max(sstart)].iter().enumerate() {
1278 if tstart + k < tb.len() {
1279 tb[tstart + k] = byte;
1280 n += 1;
1281 }
1282 }
1283 set_bytes(&target, &tb);
1284 Ok(Value::Float(n as f64))
1285 }
1286 "set" => {
1289 let src = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
1290 let offset = if args.len() > 1 {
1291 super::arg_num(args, 1).max(0.0) as usize
1292 } else {
1293 0
1294 };
1295 if offset + src.len() > bytes.len() {
1296 return Err(crate::host::range_error("offset is out of bounds"));
1297 }
1298 let mut out = bytes.clone();
1299 out[offset..offset + src.len()].copy_from_slice(&src);
1300 set_bytes(recv, &out);
1301 Ok(Value::Undef)
1302 }
1303 _ if crate::stdlib::typedarray::PROTOTYPE_METHODS.contains(&method) => {
1311 crate::stdlib::typedarray::instance_call(recv, method, args)
1312 }
1313 _ => Err(crate::host::type_error(&format!(
1314 "buffer.{method} is not a function"
1315 ))),
1316 }
1317}
1318
1319fn fill_pattern(args: &[Value], idx: usize, enc: &str) -> Vec<u8> {
1321 match args.get(idx) {
1322 None => vec![0],
1323 Some(v) => {
1324 let is_str = matches!(v, Value::Str(_))
1325 || with_host(|h| matches!(h.get(v), Some(JsObj::Str(_))));
1326 if is_str {
1327 decode_str(&arg_str(args, idx), enc)
1328 } else {
1329 vec![super::arg_num(args, idx) as u8]
1330 }
1331 }
1332 }
1333}
1334
1335fn arg_is_str(args: &[Value], i: usize) -> bool {
1338 args.get(i)
1339 .is_some_and(|v| with_host(|h| h.as_str(v)).is_some())
1340}
1341
1342fn store_bytes(recv: &Value, bytes: &[u8], off: usize, out: &[u8]) -> Result<(), String> {
1350 if off + out.len() > bytes.len() {
1351 return Err(range_error_out_of_bounds());
1352 }
1353 let mut b = bytes.to_vec();
1354 b[off..off + out.len()].copy_from_slice(out);
1355 set_bytes(recv, &b);
1356 Ok(())
1357}
1358
1359fn range_error_out_of_bounds() -> String {
1360 crate::host::plain_coded_error(
1361 "RangeError",
1362 "ERR_OUT_OF_RANGE",
1363 "Attempt to access memory outside buffer bounds",
1364 )
1365}
1366
1367fn set_bytes(recv: &Value, new: &[u8]) {
1368 let (off, _) = window(recv);
1369 with_host(|h| {
1370 let arr = match h.get(recv) {
1371 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
1372 _ => None,
1373 };
1374 if let Some(a) = arr {
1375 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
1376 for (i, b) in new.iter().enumerate() {
1379 if off + i < items.len() {
1380 items[off + i] = Value::Float(*b as f64);
1381 }
1382 }
1383 }
1384 }
1385 });
1386}
1387
1388fn slice_bounds(args: &[Value], len: usize) -> (usize, usize) {
1389 let norm = |n: f64| -> usize {
1390 if n < 0.0 {
1391 (len as f64 + n).max(0.0) as usize
1392 } else {
1393 (n as usize).min(len)
1394 }
1395 };
1396 let s = if args.is_empty() {
1397 0
1398 } else {
1399 norm(super::arg_num(args, 0))
1400 };
1401 let e = if args.len() < 2 {
1402 len
1403 } else {
1404 norm(super::arg_num(args, 1))
1405 };
1406 (s.min(e), e.max(s))
1407}
1408
1409pub(crate) fn decode_str(s: &str, enc: &str) -> Vec<u8> {
1417 match enc.to_ascii_lowercase().as_str() {
1418 "hex" => from_hex(s),
1419 "base64" | "base64url" => from_base64(s),
1420 "ascii" | "latin1" | "binary" => s.encode_utf16().map(|u| u as u8).collect(),
1425 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
1428 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
1429 }
1430 _ => s.as_bytes().to_vec(),
1431 }
1432}
1433
1434pub(crate) fn encode_bytes(bytes: &[u8], enc: &str) -> String {
1435 match enc.to_ascii_lowercase().as_str() {
1436 "hex" => to_hex(bytes),
1437 "base64" => to_base64(bytes),
1438 "base64url" => super::to_base64url(bytes),
1439 "ascii" => bytes.iter().map(|b| (*b & 0x7f) as char).collect(),
1442 "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
1443 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
1445 let units: Vec<u16> = bytes
1446 .chunks_exact(2)
1447 .map(|c| u16::from_le_bytes([c[0], c[1]]))
1448 .collect();
1449 crate::utf16::to_string_lossy(&units)
1450 }
1451 _ => String::from_utf8_lossy(bytes).into_owned(),
1452 }
1453}
1454
1455fn write_args(args: &[Value], len: usize) -> Option<(usize, usize, String)> {
1463 let num = |i: usize| super::arg_num(args, i);
1464 if args.len() < 2 {
1466 return Some((0, len, "utf8".into()));
1467 }
1468 if arg_is_str(args, 1) {
1469 return Some((0, len, arg_str(args, 1)));
1470 }
1471 let off = num(1);
1472 if !(0.0..=len as f64).contains(&off) {
1473 return None;
1474 }
1475 let off = off as usize;
1476 if args.len() < 3 {
1478 return Some((off, len - off, "utf8".into()));
1479 }
1480 if arg_is_str(args, 2) {
1481 return Some((off, len - off, arg_str(args, 2)));
1482 }
1483 let max = len - off;
1484 let n = (num(2).max(0.0) as usize).min(max);
1485 let enc = if args.len() > 3 {
1486 arg_str(args, 3)
1487 } else {
1488 "utf8".into()
1489 };
1490 Some((off, n, enc))
1491}
1492
1493fn truncate_chars(s: &str, enc: &str, max: usize) -> Vec<u8> {
1500 let bytes = decode_str(s, enc);
1501 if bytes.len() <= max {
1502 return bytes;
1503 }
1504 match enc.to_ascii_lowercase().as_str() {
1505 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => bytes[..max - max % 2].to_vec(),
1506 "hex" | "base64" | "base64url" | "ascii" | "latin1" | "binary" => bytes[..max].to_vec(),
1507 _ => {
1508 let mut end = max;
1509 while end > 0 && (bytes[end] & 0xC0) == 0x80 {
1510 end -= 1;
1511 }
1512 bytes[..end].to_vec()
1513 }
1514 }
1515}
1516
1517fn read_offset(args: &[Value], size: usize, len: usize) -> Result<usize, String> {
1532 if len < size {
1533 return Err(crate::host::coded_error(
1534 "RangeError",
1535 "ERR_BUFFER_OUT_OF_BOUNDS",
1536 "Attempt to access memory outside buffer bounds",
1537 ));
1538 }
1539 let max = len - size;
1540 let raw = match args.first() {
1541 None | Some(Value::Undef) => 0.0,
1542 Some(_) => super::arg_num(args, 0),
1543 };
1544 if raw.fract() != 0.0 || raw.is_nan() {
1547 return Err(crate::host::coded_error(
1548 "RangeError",
1549 "ERR_OUT_OF_RANGE",
1550 &format!(
1551 "The value of \"offset\" is out of range. It must be an integer. Received {}",
1552 crate::host::fmt_number(raw)
1553 ),
1554 ));
1555 }
1556 let off = raw;
1557 if off < 0.0 || off > max as f64 {
1558 return Err(crate::host::coded_error(
1559 "RangeError",
1560 "ERR_OUT_OF_RANGE",
1561 &format!(
1562 "The value of \"offset\" is out of range. It must be >= 0 and <= {max}. \
1563 Received {}",
1564 crate::host::fmt_number(raw)
1565 ),
1566 ));
1567 }
1568 Ok(off as usize)
1569}