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 "writeUInt16BE",
62 "writeUInt16LE",
63 "readUInt32BE",
64 "readUInt32LE",
65 "readInt8",
66 "readInt16BE",
67 "readInt16LE",
68 "readInt32BE",
69 "readInt32LE",
70 "writeUInt32BE",
71 "writeUInt32LE",
72 "writeInt32BE",
73 "writeInt32LE",
74 "at",
75 "values",
76 "keys",
77 "entries",
78 "swap16",
79 "swap32",
80 "swap64",
81];
82
83pub const MODULE_METHODS: &[&str] = &["atob", "btoa", "isAscii", "isUtf8", "transcode"];
87
88pub fn module_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
90 Some(match method {
91 "atob" => {
93 let s = arg_str(args, 0);
94 let bytes = from_base64(&s);
95 let bin: String = bytes.iter().map(|b| *b as char).collect();
96 Ok(with_host(|h| h.new_str(bin)))
97 }
98 "btoa" => {
100 let s = arg_str(args, 0);
101 let bytes: Vec<u8> = s.chars().map(|c| c as u32 as u8).collect();
102 let b64 = to_base64(&bytes);
103 Ok(with_host(|h| h.new_str(b64)))
104 }
105 "isAscii" => {
106 let bytes = input_bytes(args.first());
107 Ok(Value::Bool(bytes.iter().all(|b| *b < 0x80)))
108 }
109 "isUtf8" => {
110 let bytes = input_bytes(args.first());
111 Ok(Value::Bool(std::str::from_utf8(&bytes).is_ok()))
112 }
113 "transcode" => {
116 let src = input_bytes(args.first());
117 let from = arg_str(args, 1);
118 let to = arg_str(args, 2);
119 let s = bytes_to_string(&src, &from);
120 let out = string_to_bytes(&s, &to);
121 Ok(from_bytes(&out))
122 }
123 _ => return None,
124 })
125}
126
127fn input_bytes(v: Option<&Value>) -> Vec<u8> {
129 match v {
130 None => Vec::new(),
131 Some(v) => {
132 if let Some(s) = with_host(|h| h.as_str(v)) {
133 s.into_bytes()
134 } else {
135 bytes_of(v)
136 }
137 }
138 }
139}
140
141fn bytes_to_string(bytes: &[u8], enc: &str) -> String {
143 match enc.to_ascii_lowercase().as_str() {
144 "ascii" | "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
145 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
146 let units: Vec<u16> = bytes
147 .chunks_exact(2)
148 .map(|c| u16::from_le_bytes([c[0], c[1]]))
149 .collect();
150 String::from_utf16_lossy(&units)
151 }
152 _ => String::from_utf8_lossy(bytes).into_owned(),
153 }
154}
155
156fn string_to_bytes(s: &str, enc: &str) -> Vec<u8> {
162 match enc.to_ascii_lowercase().as_str() {
163 "ascii" => s
164 .chars()
165 .map(|c| if c.is_ascii() { c as u8 } else { b'?' })
166 .collect(),
167 "latin1" | "binary" => s.chars().map(|c| c as u32 as u8).collect(),
168 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
169 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
170 }
171 _ => s.as_bytes().to_vec(),
172 }
173}
174
175fn part_bytes(v: &Value) -> Vec<u8> {
185 match with_host(|h| h.as_str(v)) {
186 Some(s) => s.into_bytes(),
187 None => bytes_of(v),
188 }
189}
190
191fn gather_parts(parts: &Value) -> Vec<u8> {
193 let items = with_host(|h| match h.get(parts) {
194 Some(JsObj::Array(it)) => it.clone(),
195 _ => Vec::new(),
196 });
197 let mut out = Vec::new();
198 for it in &items {
199 out.extend(part_bytes(it));
200 }
201 out
202}
203
204fn opt_type(opts: Option<&Value>) -> String {
206 match opts {
207 Some(v) => with_host(|h| match h.get(v) {
208 Some(JsObj::Object(p)) => p.get("type").map(|x| h.str_of(x)).unwrap_or_default(),
209 _ => String::new(),
210 }),
211 None => String::new(),
212 }
213}
214
215fn build_blob(tag: &str, bytes: &[u8], typ: &str, extra: IndexMap<String, Value>) -> Value {
218 with_host(|h| {
219 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
220 let mut m = IndexMap::new();
221 m.insert("@@native".into(), h.new_str(tag.to_string()));
222 m.insert("@@bytes".into(), arr);
223 m.insert("size".into(), Value::Float(bytes.len() as f64));
224 m.insert("type".into(), h.new_str(typ.to_string()));
225 for (k, v) in extra {
226 m.insert(k, v);
227 }
228 h.new_object(m)
229 })
230}
231
232pub fn construct_blob(args: &[Value]) -> Result<Value, String> {
234 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
235 let typ = opt_type(args.get(1));
236 Ok(build_blob("Blob", &bytes, &typ, IndexMap::new()))
237}
238
239pub fn construct_file(args: &[Value]) -> Result<Value, String> {
241 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
242 let name = arg_str(args, 1);
243 let typ = opt_type(args.get(2));
244 let last_modified = args
246 .get(2)
247 .map(|v| {
248 with_host(|h| match h.get(v) {
249 Some(JsObj::Object(p)) => {
250 p.get("lastModified").map(|x| h.to_number(x)).unwrap_or(0.0)
251 }
252 _ => 0.0,
253 })
254 })
255 .unwrap_or(0.0);
256 let extra = with_host(|h| {
257 let mut m = IndexMap::new();
258 m.insert("name".to_string(), h.new_str(name));
259 m.insert("lastModified".to_string(), Value::Float(last_modified));
260 m
261 });
262 Ok(build_blob("File", &bytes, &typ, extra))
263}
264
265pub const BLOB_METHODS: &[&str] = &["text", "arrayBuffer", "bytes", "slice"];
267
268pub fn blob_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
273 let bytes = bytes_of(recv);
274 match method {
275 "text" => {
276 let s = String::from_utf8_lossy(&bytes).into_owned();
277 let sv = with_host(|h| h.new_str(s));
278 Ok(crate::host::promise_of(&sv))
279 }
280 "arrayBuffer" | "bytes" => {
281 let buf = from_bytes(&bytes);
282 Ok(crate::host::promise_of(&buf))
283 }
284 "slice" => {
285 let (s, e) = slice_bounds(args, bytes.len());
286 let typ = if args.len() > 2 {
287 arg_str(args, 2)
288 } else {
289 String::new()
290 };
291 Ok(build_blob("Blob", &bytes[s..e], &typ, IndexMap::new()))
292 }
293 _ => Err(crate::host::type_error(&format!(
294 "blob.{method} is not a function"
295 ))),
296 }
297}
298
299pub fn from_bytes(bytes: &[u8]) -> Value {
301 with_host(|h| {
302 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
303 let mut m = IndexMap::new();
304 m.insert("@@native".into(), h.new_str("Buffer"));
305 m.insert("@@bytes".into(), arr);
306 m.insert("length".into(), Value::Float(bytes.len() as f64));
307 m.insert("byteLength".into(), Value::Float(bytes.len() as f64));
310 m.insert("byteOffset".into(), Value::Float(0.0));
311 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(1.0));
312 let obj = h.new_object(m);
313 h.ensure_native_protos();
317 if let Some(p) = h.native_proto("Buffer") {
318 h.set_proto(&obj, p);
319 }
320 for k in ["length", "byteLength", "byteOffset", "BYTES_PER_ELEMENT"] {
323 h.hide_prop(&obj, k);
324 }
325 obj
326 })
327}
328
329fn bytes_of(recv: &Value) -> Vec<u8> {
330 with_host(|h| match h.get(recv) {
331 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|v| h.get(v)) {
332 Some(JsObj::Array(items)) => items.iter().map(|v| h.to_number(v) as u8).collect(),
333 _ => Vec::new(),
334 },
335 _ => Vec::new(),
336 })
337}
338
339fn bytes_handle(recv: &Value) -> Option<Value> {
341 with_host(|h| match h.get(recv) {
342 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
343 _ => None,
344 })
345}
346
347pub fn byte_get(recv: &Value, index: &str) -> Value {
354 let i: usize = match index.parse() {
355 Ok(i) => i,
356 Err(_) => return Value::Undef,
357 };
358 let arr = match bytes_handle(recv) {
359 Some(a) => a,
360 None => return Value::Undef,
361 };
362 with_host(|h| match h.get(&arr) {
363 Some(JsObj::Array(items)) => match items.get(i) {
364 Some(v) => Value::Float(h.to_number(v)),
365 None => Value::Undef,
366 },
367 _ => Value::Undef,
368 })
369}
370
371pub fn byte_set(recv: &Value, index: &str, val: &Value) -> bool {
377 if super::native_tag(recv).as_deref() != Some("Buffer") {
378 return false;
379 }
380 let i: usize = match index.parse() {
381 Ok(i) => i,
382 Err(_) => return false,
383 };
384 let arr = match bytes_handle(recv) {
385 Some(a) => a,
386 None => return false,
387 };
388 let b = with_host(|h| h.to_number(val)) as i64 as u8;
389 with_host(|h| {
390 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
391 if let Some(slot) = items.get_mut(i) {
393 *slot = Value::Float(b as f64);
394 }
395 }
396 });
397 true
398}
399
400pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
401 Some(match method {
402 "from" => from(args),
403 "alloc" => {
404 let n = super::arg_num(args, 0).max(0.0) as usize;
405 let pat = if args.len() > 1 {
408 let enc = if args.len() > 2 {
409 arg_str(args, 2)
410 } else {
411 "utf8".into()
412 };
413 fill_pattern(args, 1, &enc)
414 } else {
415 vec![0]
416 };
417 let bytes: Vec<u8> = if pat.is_empty() {
418 vec![0u8; n]
419 } else {
420 (0..n).map(|i| pat[i % pat.len()]).collect()
421 };
422 Ok(from_bytes(&bytes))
423 }
424 "allocUnsafe" | "allocUnsafeSlow" => Ok(from_bytes(&vec![
428 0u8;
429 super::arg_num(args, 0).max(0.0)
430 as usize
431 ])),
432 "concat" => concat(args),
433 "of" => Ok(from_bytes(
437 &args
438 .iter()
439 .map(|v| crate::host::with_host(|h| h.to_number(v)) as u8)
440 .collect::<Vec<u8>>(),
441 )),
442 "isEncoding" => Ok(Value::Bool(matches!(
446 super::arg_str(args, 0).to_ascii_lowercase().as_str(),
447 "utf8"
448 | "utf-8"
449 | "ucs2"
450 | "ucs-2"
451 | "utf16le"
452 | "utf-16le"
453 | "latin1"
454 | "binary"
455 | "base64"
456 | "base64url"
457 | "hex"
458 | "ascii"
459 ))),
460 "compare" => {
462 let a = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
463 let b = bytes_of(&args.get(1).cloned().unwrap_or(Value::Undef));
464 Ok(Value::Float(match a.cmp(&b) {
465 std::cmp::Ordering::Less => -1.0,
466 std::cmp::Ordering::Equal => 0.0,
467 std::cmp::Ordering::Greater => 1.0,
468 }))
469 }
470 "isBuffer" => Ok(Value::Bool(
471 super::native_tag(&args.first().cloned().unwrap_or(Value::Undef)).as_deref()
472 == Some("Buffer"),
473 )),
474 "byteLength" => {
475 if let Some(n) = view_byte_length(&args.first().cloned().unwrap_or(Value::Undef)) {
478 return Some(Ok(Value::Float(n)));
479 }
480 let enc = args
481 .get(1)
482 .map(|_| arg_str(args, 1))
483 .unwrap_or_else(|| "utf8".into());
484 Ok(Value::Float(
485 decode_str(&arg_str(args, 0), &enc).len() as f64
486 ))
487 }
488 _ => return None,
489 })
490}
491
492pub fn bytes_like(v: &Value) -> Option<Vec<u8>> {
507 if let Some(elems) = crate::stdlib::typedarray::elems_of(v) {
509 return Some(elems.iter().map(|x| *x as i64 as u8).collect());
510 }
511 if super::native_tag(v).as_deref() == Some("ArrayBuffer") {
515 let n = with_host(|h| match h.get(v) {
516 Some(JsObj::Object(p)) => p.get("byteLength").map(|b| h.to_number(b) as usize),
517 _ => None,
518 });
519 return n.map(|n| vec![0u8; n]);
520 }
521 with_host(|h| match h.get(v) {
523 Some(JsObj::Array(items)) => {
524 Some(items.iter().map(|x| h.to_number(x) as i64 as u8).collect())
525 }
526 _ => None,
527 })
528}
529
530fn view_byte_length(v: &Value) -> Option<f64> {
534 match super::native_tag(v).as_deref() {
535 Some("Buffer") | Some("TypedArray") | Some("ArrayBuffer") => {
536 with_host(|h| match h.get(v) {
537 Some(JsObj::Object(p)) => p.get("byteLength").map(|b| h.to_number(b)),
538 _ => None,
539 })
540 }
541 _ => None,
542 }
543}
544
545fn from(args: &[Value]) -> Result<Value, String> {
546 let v = args.first().cloned().unwrap_or(Value::Undef);
547 if let Some(bytes) = bytes_like(&v) {
549 return Ok(from_bytes(&bytes));
550 }
551 let enc = if args.len() > 1 {
553 arg_str(args, 1)
554 } else {
555 "utf8".into()
556 };
557 Ok(from_bytes(&decode_str(&arg_str(args, 0), &enc)))
558}
559
560fn concat(args: &[Value]) -> Result<Value, String> {
561 let list = with_host(
562 |h| match h.get(&args.first().cloned().unwrap_or(Value::Undef)) {
563 Some(JsObj::Array(items)) => items.clone(),
564 _ => Vec::new(),
565 },
566 );
567 let mut out = Vec::new();
568 for b in &list {
569 out.extend(bytes_like(b).unwrap_or_default());
571 }
572 Ok(from_bytes(&out))
573}
574
575pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
577 let bytes = bytes_of(recv);
578 match method {
579 "toString" => {
582 let enc = match args.first() {
583 None | Some(Value::Undef) => "utf8".into(),
584 _ => arg_str(args, 0),
585 };
586 let len = bytes.len();
587 let clamp = |i: usize| -> usize {
588 let n = super::arg_num(args, i);
589 if n.is_nan() {
590 0
591 } else {
592 n.clamp(0.0, len as f64) as usize
593 }
594 };
595 let start = if args.len() > 1 { clamp(1) } else { 0 };
596 let end = if args.len() > 2 { clamp(2) } else { len };
597 let slice = if start < end {
599 &bytes[start..end]
600 } else {
601 &[][..]
602 };
603 Ok(with_host(|h| h.new_str(encode_bytes(slice, &enc))))
604 }
605 "toJSON" => Ok(with_host(|h| {
606 let data = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
607 let mut m = IndexMap::new();
608 m.insert("type".into(), h.new_str("Buffer"));
609 m.insert("data".into(), data);
610 h.new_object(m)
611 })),
612 "equals" => {
613 let other = bytes_like(&args.first().cloned().unwrap_or(Value::Undef));
615 Ok(Value::Bool(other.is_some_and(|o| bytes == o)))
616 }
617 "slice" | "subarray" => {
618 let (s, e) = slice_bounds(args, bytes.len());
619 Ok(from_bytes(&bytes[s..e]))
620 }
621 "readUInt8" => {
622 let i = read_offset(args, 1, bytes.len())?;
623 Ok(Value::Float(bytes[i] as f64))
624 }
625 "includes" | "indexOf" | "lastIndexOf" => {
629 let len = bytes.len();
630 let last = method == "lastIndexOf";
631 let (from, enc) = match args.get(1) {
633 None | Some(Value::Undef) => (None, arg_str(args, 2)),
634 Some(v) if with_host(|h| h.as_str(v)).is_some() => (None, arg_str(args, 1)),
635 _ => (Some(super::arg_num(args, 1)), arg_str(args, 2)),
636 };
637 let enc = if enc.is_empty() { "utf8".into() } else { enc };
638 let target = args.first().cloned().unwrap_or(Value::Undef);
640 let needle = match &target {
641 Value::Int(_) | Value::Float(_) => vec![super::arg_num(args, 0) as u8],
642 _ if bytes_like(&target).is_some() => bytes_like(&target).unwrap_or_default(),
644 _ => decode_str(&arg_str(args, 0), &enc),
645 };
646 let from = from.map(|n| {
649 if n.is_nan() {
650 0
651 } else if n < 0.0 {
652 (len as f64 + n).max(0.0) as usize
653 } else {
654 (n as usize).min(len)
655 }
656 });
657 let pos = if needle.is_empty() {
659 Some(from.unwrap_or(if last { len } else { 0 }).min(len))
660 } else if last {
661 let hi = (from.unwrap_or(len) + needle.len()).min(len);
664 bytes[..hi]
665 .windows(needle.len())
666 .rposition(|w| w == needle.as_slice())
667 } else {
668 let lo = from.unwrap_or(0);
669 bytes[lo..]
670 .windows(needle.len())
671 .position(|w| w == needle.as_slice())
672 .map(|p| p + lo)
673 };
674 if method == "includes" {
675 Ok(Value::Bool(pos.is_some()))
676 } else {
677 Ok(Value::Float(pos.map(|p| p as f64).unwrap_or(-1.0)))
678 }
679 }
680 "compare" => {
682 let other = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
683 Ok(Value::Float(match bytes.cmp(&other) {
684 std::cmp::Ordering::Less => -1.0,
685 std::cmp::Ordering::Equal => 0.0,
686 std::cmp::Ordering::Greater => 1.0,
687 }))
688 }
689 "readUInt16BE" => {
691 let i = read_offset(args, 2, bytes.len())?;
692 let v = ((bytes[i] as u16) << 8) | bytes[i + 1] as u16;
693 Ok(Value::Float(v as f64))
694 }
695 "readUInt16LE" => {
696 let i = read_offset(args, 2, bytes.len())?;
697 let v = (bytes[i] as u16) | ((bytes[i + 1] as u16) << 8);
698 Ok(Value::Float(v as f64))
699 }
700 "readUInt32BE" | "readUInt32LE" | "readInt32BE" | "readInt32LE" => {
703 let i = read_offset(args, 4, bytes.len())?;
704 let at = |k: usize| bytes[i + k] as u32;
705 let v = if method.ends_with("BE") {
706 (at(0) << 24) | (at(1) << 16) | (at(2) << 8) | at(3)
707 } else {
708 at(0) | (at(1) << 8) | (at(2) << 16) | (at(3) << 24)
709 };
710 Ok(Value::Float(if method.starts_with("readInt") {
711 v as i32 as f64
712 } else {
713 v as f64
714 }))
715 }
716 "readInt8" => {
717 let i = read_offset(args, 1, bytes.len())?;
718 Ok(Value::Float(bytes[i] as i8 as f64))
719 }
720 "readInt16BE" | "readInt16LE" => {
721 let i = read_offset(args, 2, bytes.len())?;
722 let at = |k: usize| bytes[i + k] as u16;
723 let v = if method.ends_with("BE") {
724 (at(0) << 8) | at(1)
725 } else {
726 at(0) | (at(1) << 8)
727 };
728 Ok(Value::Float(v as i16 as f64))
729 }
730 "at" => {
732 let i = super::arg_num(args, 0);
733 let idx = if i < 0.0 { i + bytes.len() as f64 } else { i };
734 Ok(match bytes.get(idx.max(-1.0) as usize) {
735 Some(b) if idx >= 0.0 => Value::Float(*b as f64),
736 _ => Value::Undef,
737 })
738 }
739 "values" | "keys" | "entries" => {
741 let items: Vec<Value> = with_host(|h| match method {
742 "keys" => (0..bytes.len()).map(|i| Value::Float(i as f64)).collect(),
743 "values" => bytes.iter().map(|b| Value::Float(*b as f64)).collect(),
744 _ => bytes
745 .iter()
746 .enumerate()
747 .map(|(i, b)| {
748 h.new_array(vec![Value::Float(i as f64), Value::Float(*b as f64)])
749 })
750 .collect(),
751 });
752 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
753 }
754 "writeUInt8" => {
756 let mut b = bytes.clone();
757 let off = super::arg_num(args, 1).max(0.0) as usize;
758 if off < b.len() {
759 b[off] = super::arg_num(args, 0) as u8;
760 }
761 set_bytes(recv, &b);
762 Ok(Value::Float((off + 1) as f64))
763 }
764 "writeUInt16BE" | "writeUInt16LE" => {
765 let mut b = bytes.clone();
766 let val = super::arg_num(args, 0) as u16;
767 let off = super::arg_num(args, 1).max(0.0) as usize;
768 let (hi, lo) = ((val >> 8) as u8, (val & 0xff) as u8);
769 let (b0, b1) = if method == "writeUInt16BE" {
770 (hi, lo)
771 } else {
772 (lo, hi)
773 };
774 if off + 1 < b.len() {
775 b[off] = b0;
776 b[off + 1] = b1;
777 }
778 set_bytes(recv, &b);
779 Ok(Value::Float((off + 2) as f64))
780 }
781 "writeUInt32BE" | "writeUInt32LE" | "writeInt32BE" | "writeInt32LE" => {
782 let mut b = bytes.clone();
783 let val = super::arg_num(args, 0) as i64 as u32;
784 let off = super::arg_num(args, 1).max(0.0) as usize;
785 let be = [
786 (val >> 24) as u8,
787 (val >> 16) as u8,
788 (val >> 8) as u8,
789 val as u8,
790 ];
791 let out: Vec<u8> = if method.ends_with("BE") {
792 be.to_vec()
793 } else {
794 be.iter().rev().copied().collect()
795 };
796 if off + 3 < b.len() {
797 b[off..off + 4].copy_from_slice(&out);
798 }
799 set_bytes(recv, &b);
800 Ok(Value::Float((off + 4) as f64))
801 }
802 "write" => {
806 let mut b = bytes.clone();
807 let Some((off, max, enc)) = write_args(args, b.len()) else {
808 return Err(crate::host::range_error(&format!(
809 "The value of \"offset\" is out of range. It must be >= 0 && <= {}. Received {}",
810 b.len(),
811 super::arg_num(args, 1)
812 )));
813 };
814 let src = truncate_chars(&arg_str(args, 0), &enc, max);
815 let n = src.len().min(b.len().saturating_sub(off));
816 b[off..off + n].copy_from_slice(&src[..n]);
817 set_bytes(recv, &b);
818 Ok(Value::Float(n as f64))
819 }
820 "swap16" | "swap32" | "swap64" => {
824 let group = match method {
825 "swap16" => 2,
826 "swap32" => 4,
827 _ => 8,
828 };
829 if bytes.len() % group != 0 {
830 return Err(crate::host::coded_error(
831 "RangeError",
832 "ERR_INVALID_BUFFER_SIZE",
833 &format!("Buffer size must be a multiple of {}-bits", group * 8),
834 ));
835 }
836 let mut b = bytes.clone();
837 for c in b.chunks_mut(group) {
838 c.reverse();
839 }
840 set_bytes(recv, &b);
841 Ok(recv.clone())
842 }
843 "fill" => {
849 let mut b = bytes.clone();
850 let len = b.len();
851 let (start, end, enc) = if arg_is_str(args, 1) {
852 (0, len, arg_str(args, 1))
853 } else if arg_is_str(args, 2) {
854 let s = (super::arg_num(args, 1).max(0.0) as usize).min(len);
855 (s, len, arg_str(args, 2))
856 } else {
857 let s = if args.len() > 1 {
858 (super::arg_num(args, 1).max(0.0) as usize).min(len)
859 } else {
860 0
861 };
862 let e = if args.len() > 2 {
863 (super::arg_num(args, 2).max(0.0) as usize).min(len)
864 } else {
865 len
866 };
867 let enc = if args.len() > 3 {
868 arg_str(args, 3)
869 } else {
870 "utf8".into()
871 };
872 (s, e, enc)
873 };
874 let pat = fill_pattern(args, 0, &enc);
875 if !pat.is_empty() {
876 for (k, slot) in b[start..end.max(start)].iter_mut().enumerate() {
877 *slot = pat[k % pat.len()];
878 }
879 }
880 set_bytes(recv, &b);
881 Ok(recv.clone())
882 }
883 "copy" => {
885 let target = args.first().cloned().unwrap_or(Value::Undef);
886 let mut tb = bytes_of(&target);
887 let tstart = if args.len() > 1 {
888 super::arg_num(args, 1).max(0.0) as usize
889 } else {
890 0
891 };
892 let sstart = if args.len() > 2 {
893 super::arg_num(args, 2).max(0.0) as usize
894 } else {
895 0
896 };
897 let send = if args.len() > 3 {
898 (super::arg_num(args, 3) as usize).min(bytes.len())
899 } else {
900 bytes.len()
901 };
902 let mut n = 0;
903 for (k, &byte) in bytes[sstart..send.max(sstart)].iter().enumerate() {
904 if tstart + k < tb.len() {
905 tb[tstart + k] = byte;
906 n += 1;
907 }
908 }
909 set_bytes(&target, &tb);
910 Ok(Value::Float(n as f64))
911 }
912 "set" => {
915 let src = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
916 let offset = if args.len() > 1 {
917 super::arg_num(args, 1).max(0.0) as usize
918 } else {
919 0
920 };
921 if offset + src.len() > bytes.len() {
922 return Err(crate::host::range_error("offset is out of bounds"));
923 }
924 let mut out = bytes.clone();
925 out[offset..offset + src.len()].copy_from_slice(&src);
926 set_bytes(recv, &out);
927 Ok(Value::Undef)
928 }
929 _ if crate::stdlib::typedarray::PROTOTYPE_METHODS.contains(&method) => {
937 crate::stdlib::typedarray::instance_call(recv, method, args)
938 }
939 _ => Err(crate::host::type_error(&format!(
940 "buffer.{method} is not a function"
941 ))),
942 }
943}
944
945fn fill_pattern(args: &[Value], idx: usize, enc: &str) -> Vec<u8> {
947 match args.get(idx) {
948 None => vec![0],
949 Some(v) => {
950 let is_str = matches!(v, Value::Str(_))
951 || with_host(|h| matches!(h.get(v), Some(JsObj::Str(_))));
952 if is_str {
953 decode_str(&arg_str(args, idx), enc)
954 } else {
955 vec![super::arg_num(args, idx) as u8]
956 }
957 }
958 }
959}
960
961fn arg_is_str(args: &[Value], i: usize) -> bool {
964 args.get(i)
965 .is_some_and(|v| with_host(|h| h.as_str(v)).is_some())
966}
967
968fn set_bytes(recv: &Value, new: &[u8]) {
970 with_host(|h| {
971 let arr = match h.get(recv) {
972 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
973 _ => None,
974 };
975 if let Some(a) = arr {
976 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
977 *items = new.iter().map(|b| Value::Float(*b as f64)).collect();
978 }
979 }
980 });
981}
982
983fn slice_bounds(args: &[Value], len: usize) -> (usize, usize) {
984 let norm = |n: f64| -> usize {
985 if n < 0.0 {
986 (len as f64 + n).max(0.0) as usize
987 } else {
988 (n as usize).min(len)
989 }
990 };
991 let s = if args.is_empty() {
992 0
993 } else {
994 norm(super::arg_num(args, 0))
995 };
996 let e = if args.len() < 2 {
997 len
998 } else {
999 norm(super::arg_num(args, 1))
1000 };
1001 (s.min(e), e.max(s))
1002}
1003
1004pub(crate) fn decode_str(s: &str, enc: &str) -> Vec<u8> {
1012 match enc.to_ascii_lowercase().as_str() {
1013 "hex" => from_hex(s),
1014 "base64" | "base64url" => from_base64(s),
1015 "ascii" | "latin1" | "binary" => s.encode_utf16().map(|u| u as u8).collect(),
1020 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
1023 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
1024 }
1025 _ => s.as_bytes().to_vec(),
1026 }
1027}
1028
1029pub(crate) fn encode_bytes(bytes: &[u8], enc: &str) -> String {
1030 match enc.to_ascii_lowercase().as_str() {
1031 "hex" => to_hex(bytes),
1032 "base64" => to_base64(bytes),
1033 "base64url" => super::to_base64url(bytes),
1034 "ascii" => bytes.iter().map(|b| (*b & 0x7f) as char).collect(),
1037 "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
1038 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
1040 let units: Vec<u16> = bytes
1041 .chunks_exact(2)
1042 .map(|c| u16::from_le_bytes([c[0], c[1]]))
1043 .collect();
1044 crate::utf16::to_string_lossy(&units)
1045 }
1046 _ => String::from_utf8_lossy(bytes).into_owned(),
1047 }
1048}
1049
1050fn write_args(args: &[Value], len: usize) -> Option<(usize, usize, String)> {
1058 let num = |i: usize| super::arg_num(args, i);
1059 if args.len() < 2 {
1061 return Some((0, len, "utf8".into()));
1062 }
1063 if arg_is_str(args, 1) {
1064 return Some((0, len, arg_str(args, 1)));
1065 }
1066 let off = num(1);
1067 if !(0.0..=len as f64).contains(&off) {
1068 return None;
1069 }
1070 let off = off as usize;
1071 if args.len() < 3 {
1073 return Some((off, len - off, "utf8".into()));
1074 }
1075 if arg_is_str(args, 2) {
1076 return Some((off, len - off, arg_str(args, 2)));
1077 }
1078 let max = len - off;
1079 let n = (num(2).max(0.0) as usize).min(max);
1080 let enc = if args.len() > 3 {
1081 arg_str(args, 3)
1082 } else {
1083 "utf8".into()
1084 };
1085 Some((off, n, enc))
1086}
1087
1088fn truncate_chars(s: &str, enc: &str, max: usize) -> Vec<u8> {
1095 let bytes = decode_str(s, enc);
1096 if bytes.len() <= max {
1097 return bytes;
1098 }
1099 match enc.to_ascii_lowercase().as_str() {
1100 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => bytes[..max - max % 2].to_vec(),
1101 "hex" | "base64" | "base64url" | "ascii" | "latin1" | "binary" => bytes[..max].to_vec(),
1102 _ => {
1103 let mut end = max;
1104 while end > 0 && (bytes[end] & 0xC0) == 0x80 {
1105 end -= 1;
1106 }
1107 bytes[..end].to_vec()
1108 }
1109 }
1110}
1111
1112fn read_offset(args: &[Value], size: usize, len: usize) -> Result<usize, String> {
1127 if len < size {
1128 return Err(crate::host::coded_error(
1129 "RangeError",
1130 "ERR_BUFFER_OUT_OF_BOUNDS",
1131 "Attempt to access memory outside buffer bounds",
1132 ));
1133 }
1134 let max = len - size;
1135 let raw = match args.first() {
1136 None | Some(Value::Undef) => 0.0,
1137 Some(_) => super::arg_num(args, 0),
1138 };
1139 if raw.fract() != 0.0 || raw.is_nan() {
1142 return Err(crate::host::coded_error(
1143 "RangeError",
1144 "ERR_OUT_OF_RANGE",
1145 &format!(
1146 "The value of \"offset\" is out of range. It must be an integer. Received {}",
1147 crate::host::fmt_number(raw)
1148 ),
1149 ));
1150 }
1151 let off = raw;
1152 if off < 0.0 || off > max as f64 {
1153 return Err(crate::host::coded_error(
1154 "RangeError",
1155 "ERR_OUT_OF_RANGE",
1156 &format!(
1157 "The value of \"offset\" is out of range. It must be >= 0 and <= {max}. \
1158 Received {}",
1159 crate::host::fmt_number(raw)
1160 ),
1161 ));
1162 }
1163 Ok(off as usize)
1164}