1#[derive(Debug, Clone, PartialEq, serde::Serialize)]
47#[serde(tag = "type", content = "value", rename_all = "snake_case")]
48pub enum V8Value {
49 Undefined,
51 Null,
53 Hole,
55 Bool(bool),
57 Int(i64),
59 Double(f64),
61 BigInt(String),
63 String(String),
65 Date(f64),
67 RegExp {
69 source: String,
71 flags: u32,
73 },
74 Array(Vec<V8Value>),
76 Object(Vec<(String, V8Value)>),
78 Map(Vec<(V8Value, V8Value)>),
80 Set(Vec<V8Value>),
82 ArrayBuffer(Vec<u8>),
84 NumberObject(f64),
86 StringObject(String),
88 BooleanObject(bool),
90 BigIntObject(String),
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
98pub enum V8Error {
99 #[error("truncated at offset {offset}: needed {needed} more byte(s), {available} available")]
101 Truncated {
102 offset: usize,
103 needed: usize,
104 available: usize,
105 },
106 #[error("missing V8 version header at offset {offset}: expected 0xFF, found 0x{found:02x}")]
108 BadVersion { offset: usize, found: u8 },
109 #[error("unsupported V8 serialization tag 0x{tag:02x} ({tag:?} as char) at offset {offset}")]
111 UnsupportedTag { offset: usize, tag: u8 },
112 #[error(
115 "unsupported host/embedder object at offset {offset} (kHostObject, Blink tag 0x{blink_tag:02x})"
116 )]
117 HostObject { offset: usize, blink_tag: u8 },
118 #[error("dangling object reference to id {id} at offset {offset}")]
120 BadReference { offset: usize, id: u64 },
121 #[error("malformed varint at offset {offset}")]
123 BadVarint { offset: usize },
124 #[error("odd-length UTF-16 string ({len} bytes) at offset {offset}")]
126 OddUtf16 { offset: usize, len: usize },
127 #[error("unsupported property key type at offset {offset}")]
129 BadKey { offset: usize },
130 #[error("recursion depth cap ({cap}) exceeded at offset {offset}")]
132 DepthCap { offset: usize, cap: usize },
133 #[error("value/node cap ({cap}) exceeded")]
135 NodeCap { cap: usize },
136 #[error("declared length {len} exceeds cap {cap} at offset {offset}")]
139 LengthCap { offset: usize, len: u64, cap: usize },
140}
141
142#[derive(Debug, Clone, Copy)]
145pub struct V8Limits {
146 pub max_depth: usize,
148 pub max_nodes: usize,
150}
151
152impl Default for V8Limits {
153 fn default() -> Self {
154 Self {
155 max_depth: 256,
156 max_nodes: 4_000_000,
157 }
158 }
159}
160
161const TAG_VERSION: u8 = 0xFF;
169const TAG_THE_HOLE: u8 = b'-';
170const TAG_UNDEFINED: u8 = b'_';
171const TAG_NULL: u8 = b'0';
172const TAG_TRUE: u8 = b'T';
173const TAG_FALSE: u8 = b'F';
174const TAG_INT32: u8 = b'I';
175const TAG_UINT32: u8 = b'U';
176const TAG_DOUBLE: u8 = b'N';
177const TAG_BIGINT: u8 = b'Z';
178const TAG_UTF8_STRING: u8 = b'S';
179const TAG_ONE_BYTE_STRING: u8 = b'"';
180const TAG_TWO_BYTE_STRING: u8 = b'c';
181const TAG_OBJECT_REFERENCE: u8 = b'^';
182const TAG_BEGIN_OBJECT: u8 = b'o';
183const TAG_END_OBJECT: u8 = b'{';
184const TAG_BEGIN_SPARSE_ARRAY: u8 = b'a';
185const TAG_END_SPARSE_ARRAY: u8 = b'@';
186const TAG_BEGIN_DENSE_ARRAY: u8 = b'A';
187const TAG_END_DENSE_ARRAY: u8 = b'$';
188const TAG_DATE: u8 = b'D';
189const TAG_TRUE_OBJECT: u8 = b'y';
190const TAG_FALSE_OBJECT: u8 = b'x';
191const TAG_NUMBER_OBJECT: u8 = b'n';
192const TAG_BIGINT_OBJECT: u8 = b'z';
193const TAG_STRING_OBJECT: u8 = b's';
194const TAG_REGEXP: u8 = b'R';
195const TAG_BEGIN_MAP: u8 = b';';
196const TAG_END_MAP: u8 = b':';
197const TAG_BEGIN_SET: u8 = b'\'';
198const TAG_END_SET: u8 = b',';
199const TAG_ARRAY_BUFFER: u8 = b'B';
200const TAG_HOST_OBJECT: u8 = b'\\';
201
202const BLINK_TRAILER_OFFSET: u8 = 0xFE;
204
205pub fn deserialize(bytes: &[u8]) -> Result<V8Value, V8Error> {
216 deserialize_with_limits(bytes, V8Limits::default())
217}
218
219pub fn deserialize_with_limits(bytes: &[u8], limits: V8Limits) -> Result<V8Value, V8Error> {
224 let mut r = Reader::new(bytes, limits);
225 r.read_version_header()?;
226 r.read_value(0)
227}
228
229pub fn deserialize_blink(bytes: &[u8]) -> Result<V8Value, V8Error> {
236 deserialize_blink_with_limits(bytes, V8Limits::default())
237}
238
239pub fn deserialize_blink_with_limits(bytes: &[u8], limits: V8Limits) -> Result<V8Value, V8Error> {
244 let mut r = Reader::new(bytes, limits);
245 if r.peek() == Some(TAG_VERSION) {
248 r.pos += 1;
249 let _blink_version = r.read_varint()?;
250 while r.peek() == Some(BLINK_TRAILER_OFFSET) {
253 r.pos += 1;
254 r.take(12)?;
256 }
257 }
258 r.read_version_header()?;
259 r.read_value(0)
260}
261
262#[must_use]
265pub fn is_value_tag(tag: u8) -> bool {
266 matches!(
267 tag,
268 TAG_THE_HOLE
269 | TAG_UNDEFINED
270 | TAG_NULL
271 | TAG_TRUE
272 | TAG_FALSE
273 | TAG_INT32
274 | TAG_UINT32
275 | TAG_DOUBLE
276 | TAG_BIGINT
277 | TAG_UTF8_STRING
278 | TAG_ONE_BYTE_STRING
279 | TAG_TWO_BYTE_STRING
280 | TAG_OBJECT_REFERENCE
281 | TAG_BEGIN_OBJECT
282 | TAG_BEGIN_SPARSE_ARRAY
283 | TAG_BEGIN_DENSE_ARRAY
284 | TAG_DATE
285 | TAG_TRUE_OBJECT
286 | TAG_FALSE_OBJECT
287 | TAG_NUMBER_OBJECT
288 | TAG_BIGINT_OBJECT
289 | TAG_STRING_OBJECT
290 | TAG_REGEXP
291 | TAG_BEGIN_MAP
292 | TAG_BEGIN_SET
293 | TAG_ARRAY_BUFFER
294 | TAG_HOST_OBJECT
295 )
296}
297
298impl V8Value {
299 #[must_use]
302 pub fn summary(&self) -> String {
303 match self {
304 Self::Undefined => "undefined".to_owned(),
305 Self::Null => "null".to_owned(),
306 Self::Hole => "hole".to_owned(),
307 Self::Bool(b) => format!("boolean {b}"),
308 Self::Int(i) => format!("integer {i}"),
309 Self::Double(d) => format!("number {d}"),
310 Self::BigInt(s) => format!("bigint {s}n"),
311 Self::String(s) => format!("string {:?}", ellipsize(s)),
312 Self::Date(ms) => format!("date ({ms} ms)"),
313 Self::RegExp { source, flags } => {
314 format!("regexp /{}/ (flags {flags})", ellipsize(source))
315 }
316 Self::Array(v) => format!("array ({} element{})", v.len(), plural(v.len())),
317 Self::Object(kv) => format!("object ({} key{})", kv.len(), plural(kv.len())),
318 Self::Map(kv) => format!(
319 "map ({} entr{})",
320 kv.len(),
321 if kv.len() == 1 { "y" } else { "ies" }
322 ),
323 Self::Set(v) => format!("set ({} member{})", v.len(), plural(v.len())),
324 Self::ArrayBuffer(b) => format!("arraybuffer ({} byte{})", b.len(), plural(b.len())),
325 Self::NumberObject(d) => format!("Number({d})"),
326 Self::StringObject(s) => format!("String({:?})", ellipsize(s)),
327 Self::BooleanObject(b) => format!("Boolean({b})"),
328 Self::BigIntObject(s) => format!("BigInt({s})"),
329 }
330 }
331}
332
333fn plural(n: usize) -> &'static str {
334 if n == 1 {
335 ""
336 } else {
337 "s"
338 }
339}
340
341fn ellipsize(s: &str) -> String {
342 const MAX: usize = 32;
343 if s.chars().count() <= MAX {
344 s.to_owned()
345 } else {
346 let head: String = s.chars().take(MAX).collect();
347 format!("{head}…")
348 }
349}
350
351struct Reader<'a> {
356 data: &'a [u8],
357 pos: usize,
358 limits: V8Limits,
359 id_map: Vec<Option<V8Value>>,
363 budget: usize,
365}
366
367impl<'a> Reader<'a> {
368 fn new(data: &'a [u8], limits: V8Limits) -> Self {
369 let budget = limits.max_nodes;
370 Self {
371 data,
372 pos: 0,
373 limits,
374 id_map: Vec::new(),
375 budget,
376 }
377 }
378
379 fn peek(&self) -> Option<u8> {
380 self.data.get(self.pos).copied()
381 }
382
383 fn read_u8(&mut self) -> Result<u8, V8Error> {
384 let b = self.data.get(self.pos).copied().ok_or(V8Error::Truncated {
385 offset: self.pos,
386 needed: 1,
387 available: 0,
388 })?;
389 self.pos += 1;
390 Ok(b)
391 }
392
393 fn take(&mut self, n: usize) -> Result<&'a [u8], V8Error> {
394 let end = self.pos.checked_add(n).ok_or(V8Error::Truncated {
395 offset: self.pos,
396 needed: n,
397 available: self.data.len().saturating_sub(self.pos),
398 })?;
399 let slice = self.data.get(self.pos..end).ok_or(V8Error::Truncated {
400 offset: self.pos,
401 needed: n,
402 available: self.data.len().saturating_sub(self.pos),
403 })?;
404 self.pos = end;
405 Ok(slice)
406 }
407
408 fn read_varint(&mut self) -> Result<u64, V8Error> {
410 let start = self.pos;
411 let mut result: u64 = 0;
412 let mut shift: u32 = 0;
413 loop {
414 if shift >= 64 {
415 return Err(V8Error::BadVarint { offset: start });
416 }
417 let byte = self.read_u8()?;
418 result |= u64::from(byte & 0x7f) << shift;
419 if byte & 0x80 == 0 {
420 return Ok(result);
421 }
422 shift += 7;
423 }
424 }
425
426 fn read_zigzag(&mut self) -> Result<i64, V8Error> {
428 let u = self.read_varint()?;
429 Ok(((u >> 1) as i64) ^ -((u & 1) as i64))
431 }
432
433 fn read_f64_le(&mut self) -> Result<f64, V8Error> {
434 let b = self.take(8)?;
435 let mut arr = [0u8; 8];
436 arr.copy_from_slice(b);
437 Ok(f64::from_le_bytes(arr))
438 }
439
440 fn read_version_header(&mut self) -> Result<(), V8Error> {
441 let offset = self.pos;
442 let tag = self.read_u8()?;
443 if tag != TAG_VERSION {
444 return Err(V8Error::BadVersion { offset, found: tag });
445 }
446 let _version = self.read_varint()?;
448 Ok(())
449 }
450
451 fn charge(&mut self, n: usize) -> Result<(), V8Error> {
453 if n > self.budget {
454 return Err(V8Error::NodeCap {
455 cap: self.limits.max_nodes,
456 });
457 }
458 self.budget -= n;
459 Ok(())
460 }
461
462 fn reserve_id(&mut self) -> usize {
465 let id = self.id_map.len();
466 self.id_map.push(None);
467 id
468 }
469
470 fn fill_id(&mut self, id: usize, value: &V8Value) {
471 if let Some(slot) = self.id_map.get_mut(id) {
472 *slot = Some(value.clone());
473 }
474 }
475
476 fn read_value(&mut self, depth: usize) -> Result<V8Value, V8Error> {
477 if depth > self.limits.max_depth {
478 return Err(V8Error::DepthCap {
479 offset: self.pos,
480 cap: self.limits.max_depth,
481 });
482 }
483 self.charge(1)?;
484 let offset = self.pos;
485 let tag = self.read_u8()?;
486 match tag {
487 TAG_UNDEFINED => Ok(V8Value::Undefined),
488 TAG_NULL => Ok(V8Value::Null),
489 TAG_THE_HOLE => Ok(V8Value::Hole),
490 TAG_TRUE => Ok(V8Value::Bool(true)),
491 TAG_FALSE => Ok(V8Value::Bool(false)),
492 TAG_INT32 => Ok(V8Value::Int(self.read_zigzag()?)),
493 TAG_UINT32 => Ok(V8Value::Int(self.read_varint()? as i64)),
494 TAG_DOUBLE => Ok(V8Value::Double(self.read_f64_le()?)),
495 TAG_BIGINT => Ok(V8Value::BigInt(self.read_bigint()?)),
496 TAG_UTF8_STRING => Ok(V8Value::String(self.read_utf8_string()?)),
497 TAG_ONE_BYTE_STRING => Ok(V8Value::String(self.read_one_byte_string()?)),
498 TAG_TWO_BYTE_STRING => Ok(V8Value::String(self.read_two_byte_string()?)),
499 TAG_DATE => {
500 let id = self.reserve_id();
501 let v = V8Value::Date(self.read_f64_le()?);
502 self.fill_id(id, &v);
503 Ok(v)
504 }
505 TAG_BEGIN_OBJECT => self.read_js_object(depth),
506 TAG_BEGIN_DENSE_ARRAY => self.read_dense_array(depth),
507 TAG_BEGIN_SPARSE_ARRAY => self.read_sparse_array(depth),
508 TAG_BEGIN_MAP => self.read_map(depth),
509 TAG_BEGIN_SET => self.read_set(depth),
510 TAG_ARRAY_BUFFER => self.read_array_buffer(),
511 TAG_REGEXP => self.read_regexp(depth),
512 TAG_NUMBER_OBJECT => {
513 let id = self.reserve_id();
514 let v = V8Value::NumberObject(self.read_f64_le()?);
515 self.fill_id(id, &v);
516 Ok(v)
517 }
518 TAG_TRUE_OBJECT => {
519 let id = self.reserve_id();
520 let v = V8Value::BooleanObject(true);
521 self.fill_id(id, &v);
522 Ok(v)
523 }
524 TAG_FALSE_OBJECT => {
525 let id = self.reserve_id();
526 let v = V8Value::BooleanObject(false);
527 self.fill_id(id, &v);
528 Ok(v)
529 }
530 TAG_STRING_OBJECT => {
531 let id = self.reserve_id();
532 let inner = self.read_value(depth + 1)?;
533 let V8Value::String(s) = inner else {
534 return Err(V8Error::UnsupportedTag { offset, tag });
535 };
536 let v = V8Value::StringObject(s);
537 self.fill_id(id, &v);
538 Ok(v)
539 }
540 TAG_BIGINT_OBJECT => {
541 let id = self.reserve_id();
542 let v = V8Value::BigIntObject(self.read_bigint()?);
543 self.fill_id(id, &v);
544 Ok(v)
545 }
546 TAG_OBJECT_REFERENCE => self.read_reference(),
547 TAG_HOST_OBJECT => {
548 let blink_tag = self.read_u8().unwrap_or(0);
549 Err(V8Error::HostObject { offset, blink_tag })
550 }
551 _ => Err(V8Error::UnsupportedTag { offset, tag }),
552 }
553 }
554
555 fn read_utf8_string(&mut self) -> Result<String, V8Error> {
556 let raw = self.read_varint()?;
557 let len = self.checked_len(raw)?;
558 let bytes = self.take(len)?;
559 Ok(String::from_utf8_lossy(bytes).into_owned())
560 }
561
562 fn read_one_byte_string(&mut self) -> Result<String, V8Error> {
563 let raw = self.read_varint()?;
564 let len = self.checked_len(raw)?;
565 let bytes = self.take(len)?;
566 Ok(bytes.iter().map(|&b| b as char).collect())
568 }
569
570 fn read_two_byte_string(&mut self) -> Result<String, V8Error> {
571 let offset = self.pos;
572 let raw = self.read_varint()?;
573 let len = self.checked_len(raw)?;
574 if len % 2 != 0 {
575 return Err(V8Error::OddUtf16 { offset, len });
576 }
577 let bytes = self.take(len)?;
578 let units: Vec<u16> = bytes
579 .chunks_exact(2)
580 .map(|c| u16::from_le_bytes([c[0], c[1]]))
581 .collect();
582 Ok(String::from_utf16_lossy(&units))
583 }
584
585 fn read_bigint(&mut self) -> Result<String, V8Error> {
586 let offset = self.pos;
587 let bitfield = self.read_varint()?;
588 let negative = bitfield & 1 == 1;
589 let byte_len = self.checked_len(bitfield >> 1)?;
590 let digits = self.take(byte_len)?;
591 let magnitude = le_bytes_to_decimal(digits);
592 if magnitude == "0" {
593 return Ok(magnitude);
595 }
596 if negative {
597 Ok(format!("-{magnitude}"))
598 } else {
599 let _ = offset;
600 Ok(magnitude)
601 }
602 }
603
604 fn read_array_buffer(&mut self) -> Result<V8Value, V8Error> {
605 let id = self.reserve_id();
606 let raw = self.read_varint()?;
607 let len = self.checked_len(raw)?;
608 let bytes = self.take(len)?.to_vec();
609 let v = V8Value::ArrayBuffer(bytes);
610 self.fill_id(id, &v);
611 Ok(v)
612 }
613
614 fn read_regexp(&mut self, depth: usize) -> Result<V8Value, V8Error> {
615 let offset = self.pos;
616 let id = self.reserve_id();
617 let V8Value::String(source) = self.read_value(depth + 1)? else {
618 return Err(V8Error::BadKey { offset });
619 };
620 let flags = u32::try_from(self.read_varint()?).unwrap_or(u32::MAX);
621 let v = V8Value::RegExp { source, flags };
622 self.fill_id(id, &v);
623 Ok(v)
624 }
625
626 fn read_js_object(&mut self, depth: usize) -> Result<V8Value, V8Error> {
627 let id = self.reserve_id();
628 let mut props = Vec::new();
629 loop {
630 if self.peek() == Some(TAG_END_OBJECT) {
631 self.pos += 1;
632 break;
633 }
634 let key = self.read_property_key(depth + 1)?;
635 let value = self.read_value(depth + 1)?;
636 props.push((key, value));
637 }
638 let _count = self.read_varint()?;
640 let v = V8Value::Object(props);
641 self.fill_id(id, &v);
642 Ok(v)
643 }
644
645 fn read_dense_array(&mut self, depth: usize) -> Result<V8Value, V8Error> {
646 let raw = self.read_varint()?;
647 let length = self.checked_len(raw)?;
648 let id = self.reserve_id();
649 let mut elems = Vec::new();
650 for _ in 0..length {
651 elems.push(self.read_value(depth + 1)?);
652 }
653 let offset = self.pos;
655 let end = self.read_u8()?;
656 if end != TAG_END_DENSE_ARRAY {
657 return Err(V8Error::UnsupportedTag { offset, tag: end });
658 }
659 let num_props = self.read_varint()?;
660 for _ in 0..num_props {
661 let _k = self.read_property_key(depth + 1)?;
663 let _v = self.read_value(depth + 1)?;
664 }
665 let _length_again = self.read_varint()?;
666 let v = V8Value::Array(elems);
667 self.fill_id(id, &v);
668 Ok(v)
669 }
670
671 fn read_sparse_array(&mut self, depth: usize) -> Result<V8Value, V8Error> {
672 let raw = self.read_varint()?;
673 let length = self.checked_len(raw)?;
674 let id = self.reserve_id();
675 self.charge(length)?;
678 let mut elems = vec![V8Value::Hole; length];
679 loop {
680 if self.peek() == Some(TAG_END_SPARSE_ARRAY) {
681 self.pos += 1;
682 break;
683 }
684 let key = self.read_value(depth + 1)?;
685 let value = self.read_value(depth + 1)?;
686 if let V8Value::Int(i) = key {
689 if let Ok(idx) = usize::try_from(i) {
690 if idx < elems.len() {
691 elems[idx] = value;
692 }
693 }
694 }
695 }
696 let _num_props = self.read_varint()?;
697 let _length_again = self.read_varint()?;
698 let v = V8Value::Array(elems);
699 self.fill_id(id, &v);
700 Ok(v)
701 }
702
703 fn read_map(&mut self, depth: usize) -> Result<V8Value, V8Error> {
704 let id = self.reserve_id();
705 let mut entries = Vec::new();
706 loop {
707 if self.peek() == Some(TAG_END_MAP) {
708 self.pos += 1;
709 break;
710 }
711 let key = self.read_value(depth + 1)?;
712 let value = self.read_value(depth + 1)?;
713 entries.push((key, value));
714 }
715 let _count = self.read_varint()?;
717 let v = V8Value::Map(entries);
718 self.fill_id(id, &v);
719 Ok(v)
720 }
721
722 fn read_set(&mut self, depth: usize) -> Result<V8Value, V8Error> {
723 let id = self.reserve_id();
724 let mut members = Vec::new();
725 loop {
726 if self.peek() == Some(TAG_END_SET) {
727 self.pos += 1;
728 break;
729 }
730 members.push(self.read_value(depth + 1)?);
731 }
732 let _count = self.read_varint()?;
733 let v = V8Value::Set(members);
734 self.fill_id(id, &v);
735 Ok(v)
736 }
737
738 fn read_property_key(&mut self, depth: usize) -> Result<String, V8Error> {
740 let offset = self.pos;
741 match self.read_value(depth)? {
742 V8Value::String(s) => Ok(s),
743 V8Value::Int(i) => Ok(i.to_string()),
744 V8Value::Double(d) => Ok(format!("{d}")),
745 _ => Err(V8Error::BadKey { offset }),
746 }
747 }
748
749 fn read_reference(&mut self) -> Result<V8Value, V8Error> {
750 let offset = self.pos;
751 let id = self.read_varint()?;
752 let idx = usize::try_from(id).map_err(|_| V8Error::BadReference { offset, id })?;
755 let value = self
756 .id_map
757 .get(idx)
758 .cloned()
759 .flatten()
760 .ok_or(V8Error::BadReference { offset, id })?;
761 self.charge(count_nodes(&value))?;
764 Ok(value)
765 }
766
767 fn checked_len(&self, len: u64) -> Result<usize, V8Error> {
769 let cap = self.limits.max_nodes as u64;
770 if len > cap {
771 return Err(V8Error::LengthCap {
772 offset: self.pos,
773 len,
774 cap: self.limits.max_nodes,
775 });
776 }
777 usize::try_from(len).map_err(|_| V8Error::LengthCap {
782 offset: self.pos,
783 len,
784 cap: self.limits.max_nodes,
785 })
786 }
787}
788
789fn count_nodes(v: &V8Value) -> usize {
791 match v {
792 V8Value::Array(items) | V8Value::Set(items) => {
793 1 + items.iter().map(count_nodes).sum::<usize>()
794 }
795 V8Value::Object(kv) => 1 + kv.iter().map(|(_, val)| count_nodes(val)).sum::<usize>(),
796 V8Value::Map(kv) => {
797 1 + kv
798 .iter()
799 .map(|(k, val)| count_nodes(k) + count_nodes(val))
800 .sum::<usize>()
801 }
802 _ => 1,
803 }
804}
805
806fn le_bytes_to_decimal(bytes: &[u8]) -> String {
809 let mut decimal: Vec<u8> = vec![0]; for &byte in bytes.iter().rev() {
811 let mut carry = u32::from(byte);
812 for d in &mut decimal {
813 let v = u32::from(*d) * 256 + carry;
814 *d = (v % 10) as u8;
815 carry = v / 10;
816 }
817 while carry > 0 {
818 decimal.push((carry % 10) as u8);
819 carry /= 10;
820 }
821 }
822 while decimal.len() > 1 && *decimal.last().unwrap_or(&0) == 0 {
824 decimal.pop();
825 }
826 decimal.iter().rev().map(|d| (b'0' + d) as char).collect()
827}