1use alloc::borrow::Cow;
8use alloc::format;
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11use core::str;
12
13use crate::binary::byte_reader::{read_u32_le, read_u64_le};
14use crate::binary::types::{
15 APPINFO_MAGIC_40, APPINFO_MAGIC_41, BinaryType, PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40,
16 PACKAGEINFO_MAGIC_BASE,
17};
18use crate::error::{Error, Result, with_offset};
19use crate::value::{Obj, Value, Vdf};
20
21const APPINFO_HEADER_SIZE: usize = 8;
25
26const APPINFO_HEADER_AFTER_SIZE: usize = 60;
28
29const APPINFO_ENTRY_HEADER_SIZE: usize = APPINFO_HEADER_SIZE + APPINFO_HEADER_AFTER_SIZE;
31
32const APPINFO_VDF_DATA_OFFSET: usize = APPINFO_ENTRY_HEADER_SIZE;
34
35fn ensure_read_u32_le(input: &[u8]) -> Result<(&[u8], u32)> {
39 read_u32_le(input)
40 .map(|value| (&input[4..], value))
41 .ok_or(Error::UnexpectedEndOfInput {
42 context: "reading u32",
43 offset: 0,
44 expected: 4,
45 actual: input.len(),
46 })
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Default)]
53struct ParseConfig<'input, 'table> {
54 key_mode: KeyMode<'input, 'table>,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Default)]
60enum KeyMode<'input, 'table> {
61 #[default]
63 NullTerminated,
64 StringTableIndex {
66 string_table: &'table StringTable<'input>,
67 },
68}
69
70#[derive(Clone, Debug, PartialEq)]
75struct StringTable<'a> {
76 strings: Vec<&'a str>,
77}
78
79impl<'a> StringTable<'a> {
80 fn get(&self, index: usize) -> Result<&'a str> {
82 self.strings
83 .get(index)
84 .copied()
85 .ok_or(Error::InvalidStringIndex {
86 index,
87 max: self.strings.len(),
88 })
89 }
90}
91
92impl<'a> KeyMode<'a, '_> {
93 fn parse_key(&self, input: &'a [u8]) -> Result<(&'a [u8], Cow<'a, str>)> {
95 match self {
96 KeyMode::NullTerminated => {
97 let (rest, s) = parse_null_terminated_string_borrowed(input)?;
98 Ok((rest, Cow::Borrowed(s)))
99 }
100 KeyMode::StringTableIndex { string_table } => {
101 let (rest, index) = ensure_read_u32_le(input)?;
102 let s = string_table.get(index as usize)?;
103 Ok((rest, Cow::Borrowed(s)))
104 }
105 }
106 }
107}
108
109pub fn parse(input: &[u8]) -> Result<Vdf<'_>> {
117 if let Some(magic) = read_u32_le(input) {
119 if magic == APPINFO_MAGIC_40 || magic == APPINFO_MAGIC_41 {
120 return parse_appinfo(input);
121 }
122 if magic == PACKAGEINFO_MAGIC_39 || magic == PACKAGEINFO_MAGIC_40 {
123 return parse_packageinfo(input);
124 }
125 }
126
127 parse_shortcuts(input)
129}
130
131pub fn parse_shortcuts(input: &[u8]) -> Result<Vdf<'_>> {
146 let config = ParseConfig::default();
147 let (_rest, obj) = parse_object(input, &config)?;
148
149 Ok(Vdf::new("root", Value::Obj(obj)))
150}
151
152pub fn parse_appinfo(input: &[u8]) -> Result<Vdf<'_>> {
176 if input.len() < 16 {
177 return Err(Error::UnexpectedEndOfInput {
178 context: "reading appinfo header",
179 offset: input.len(),
180 expected: 16,
181 actual: input.len(),
182 });
183 }
184
185 let Some(magic) = read_u32_le(input) else {
186 return Err(Error::UnexpectedEndOfInput {
187 context: "reading magic number",
188 offset: 0,
189 expected: 4,
190 actual: input.len(),
191 });
192 };
193 let Some(universe) = read_u32_le(&input[4..]) else {
194 return Err(Error::UnexpectedEndOfInput {
195 context: "reading universe",
196 offset: 4,
197 expected: 4,
198 actual: input.len() - 4,
199 });
200 };
201
202 let (string_table_offset, mut rest) = match magic {
203 APPINFO_MAGIC_40 => (None, &input[8..]),
204 APPINFO_MAGIC_41 => {
205 let Some(offset) = read_u64_le(&input[8..]) else {
206 return Err(Error::UnexpectedEndOfInput {
207 context: "reading string table offset",
208 offset: 8,
209 expected: 8,
210 actual: input.len() - 8,
211 });
212 };
213 (Some(offset as usize), &input[16..])
214 }
215 _ => {
216 return Err(Error::InvalidMagic {
217 found: magic,
218 expected: &[APPINFO_MAGIC_40, APPINFO_MAGIC_41],
219 });
220 }
221 };
222
223 let string_table = if let Some(offset) = string_table_offset {
225 if offset >= input.len() {
226 return Err(Error::UnexpectedEndOfInput {
227 context: "reading string table",
228 offset,
229 expected: 4,
230 actual: input.len() - offset,
231 });
232 }
233 Some(parse_string_table(&input[offset..]).map_err(with_offset(offset))?)
234 } else {
235 None
236 };
237
238 let mut obj = Obj::new();
239
240 let apps_end_offset = string_table_offset.unwrap_or(input.len());
242
243 let config = ParseConfig {
245 key_mode: if let Some(string_table) = &string_table {
246 KeyMode::StringTableIndex { string_table }
247 } else {
248 KeyMode::NullTerminated
249 },
250 };
251
252 loop {
253 let current_offset = input.len() - rest.len();
255 if current_offset >= apps_end_offset {
256 break;
257 }
258
259 if rest.len() < APPINFO_ENTRY_HEADER_SIZE {
261 return Err(Error::UnexpectedEndOfInput {
262 context: "reading app entry header",
263 offset: current_offset,
264 expected: APPINFO_ENTRY_HEADER_SIZE,
265 actual: rest.len(),
266 });
267 }
268
269 let Some(app_id) = read_u32_le(rest) else {
271 return Err(Error::UnexpectedEndOfInput {
272 context: "reading app id",
273 offset: current_offset,
274 expected: 4,
275 actual: rest.len(),
276 });
277 };
278 if app_id == 0 {
279 break;
280 }
281
282 let Some(size) = read_u32_le(&rest[4..]) else {
284 return Err(Error::UnexpectedEndOfInput {
285 context: "reading entry size",
286 offset: current_offset + 4,
287 expected: 4,
288 actual: rest.len() - 4,
289 });
290 };
291 let size = size as usize;
292
293 let vdf_size = size - APPINFO_HEADER_AFTER_SIZE;
295 let vdf_end = APPINFO_VDF_DATA_OFFSET + vdf_size;
296
297 if vdf_end > rest.len() {
298 return Err(Error::UnexpectedEndOfInput {
299 context: "reading VDF data",
300 offset: current_offset + vdf_end,
301 expected: vdf_end,
302 actual: rest.len(),
303 });
304 }
305
306 let vdf_data = &rest[APPINFO_VDF_DATA_OFFSET..vdf_end];
307 let vdf_offset = current_offset + APPINFO_VDF_DATA_OFFSET;
308
309 let (_vdf_rest, app_obj) =
310 parse_object(vdf_data, &config).map_err(with_offset(vdf_offset))?;
311
312 obj.insert(Cow::Owned(app_id.to_string()), Value::Obj(app_obj));
314 rest = &rest[vdf_end..];
315 }
316
317 Ok(Vdf::new(
318 format!("appinfo_universe_{}", universe),
319 Value::Obj(obj),
320 ))
321}
322
323fn parse_object<'a>(input: &'a [u8], config: &ParseConfig<'a, '_>) -> Result<(&'a [u8], Obj<'a>)> {
339 let mut obj = Obj::new();
340 let mut rest = input;
341
342 loop {
343 match rest {
344 [] => {
345 break Ok((rest, obj));
347 }
348 [type_byte, remainder @ ..] => {
349 let type_byte = *type_byte;
350 let typ = BinaryType::from_byte(type_byte);
351 let offset = input.len() - remainder.len();
352 rest = remainder;
353
354 match typ {
355 Some(BinaryType::ObjectEnd) => {
356 return Ok((rest, obj));
358 }
359 Some(BinaryType::None) => {
360 let key_offset = input.len() - rest.len();
362 let (new_rest, key) = config
363 .key_mode
364 .parse_key(rest)
365 .map_err(with_offset(key_offset))?;
366 let (new_rest, nested_obj) = parse_object(new_rest, config)?;
367 obj.insert(key, Value::Obj(nested_obj));
368 rest = new_rest;
369 }
370 Some(BinaryType::String) => {
371 let key_offset = input.len() - rest.len();
374 let (new_rest, key) = config
375 .key_mode
376 .parse_key(rest)
377 .map_err(with_offset(key_offset))?;
378 let value_offset = input.len() - new_rest.len();
379 let (new_rest, value) = parse_null_terminated_string_borrowed(new_rest)
380 .map_err(with_offset(value_offset))?;
381 obj.insert(key, Value::Str(Cow::Borrowed(value)));
382 rest = new_rest;
383 }
384 Some(BinaryType::Int32) => {
385 let key_offset = input.len() - rest.len();
386 let (new_rest, key) = config
387 .key_mode
388 .parse_key(rest)
389 .map_err(with_offset(key_offset))?;
390 let value_offset = input.len() - new_rest.len();
391 let (new_rest, value) =
392 parse_value_int32(new_rest).map_err(with_offset(value_offset))?;
393 obj.insert(key, value);
394 rest = new_rest;
395 }
396 Some(BinaryType::UInt64) => {
397 let key_offset = input.len() - rest.len();
398 let (new_rest, key) = config
399 .key_mode
400 .parse_key(rest)
401 .map_err(with_offset(key_offset))?;
402 let value_offset = input.len() - new_rest.len();
403 let (new_rest, value) =
404 parse_value_uint64(new_rest).map_err(with_offset(value_offset))?;
405 obj.insert(key, value);
406 rest = new_rest;
407 }
408 Some(BinaryType::Float) => {
409 let key_offset = input.len() - rest.len();
410 let (new_rest, key) = config
411 .key_mode
412 .parse_key(rest)
413 .map_err(with_offset(key_offset))?;
414 let value_offset = input.len() - new_rest.len();
415 let (new_rest, value) =
416 parse_value_float(new_rest).map_err(with_offset(value_offset))?;
417 obj.insert(key, value);
418 rest = new_rest;
419 }
420 Some(BinaryType::Ptr) => {
421 let key_offset = input.len() - rest.len();
422 let (new_rest, key) = config
423 .key_mode
424 .parse_key(rest)
425 .map_err(with_offset(key_offset))?;
426 let value_offset = input.len() - new_rest.len();
427 let (new_rest, value) =
428 parse_value_ptr(new_rest).map_err(with_offset(value_offset))?;
429 obj.insert(key, value);
430 rest = new_rest;
431 }
432 Some(BinaryType::WString) => {
433 let key_offset = input.len() - rest.len();
434 let (new_rest, key) = config
435 .key_mode
436 .parse_key(rest)
437 .map_err(with_offset(key_offset))?;
438 let value_offset = input.len() - new_rest.len();
439 let (new_rest, value) =
440 parse_value_wstring(new_rest).map_err(with_offset(value_offset))?;
441 obj.insert(key, value);
442 rest = new_rest;
443 }
444 Some(BinaryType::Color) => {
445 let key_offset = input.len() - rest.len();
446 let (new_rest, key) = config
447 .key_mode
448 .parse_key(rest)
449 .map_err(with_offset(key_offset))?;
450 let value_offset = input.len() - new_rest.len();
451 let (new_rest, value) =
452 parse_value_color(new_rest).map_err(with_offset(value_offset))?;
453 obj.insert(key, value);
454 rest = new_rest;
455 }
456 None => {
457 return Err(Error::UnknownType { type_byte, offset });
459 }
460 }
461 }
462 }
463 }
464}
465
466fn parse_value_int32<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
470 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
471 context: "reading int32",
472 offset: 0,
473 expected: 4,
474 actual: input.len(),
475 })?)
476 .map_err(|_| Error::UnexpectedEndOfInput {
477 context: "reading int32",
478 offset: 0,
479 expected: 4,
480 actual: input.len(),
481 })?;
482 let value = i32::from_le_bytes(arr);
483 Ok((&input[4..], Value::I32(value)))
484}
485
486fn parse_value_uint64<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
488 let arr = <[u8; 8]>::try_from(input.get(..8).ok_or(Error::UnexpectedEndOfInput {
489 context: "reading uint64",
490 offset: 0,
491 expected: 8,
492 actual: input.len(),
493 })?)
494 .map_err(|_| Error::UnexpectedEndOfInput {
495 context: "reading uint64",
496 offset: 0,
497 expected: 8,
498 actual: input.len(),
499 })?;
500 let value = u64::from_le_bytes(arr);
501 Ok((&input[8..], Value::U64(value)))
502}
503
504fn parse_value_float<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
506 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
507 context: "reading float",
508 offset: 0,
509 expected: 4,
510 actual: input.len(),
511 })?)
512 .map_err(|_| Error::UnexpectedEndOfInput {
513 context: "reading float",
514 offset: 0,
515 expected: 4,
516 actual: input.len(),
517 })?;
518 let value = f32::from_le_bytes(arr);
519 Ok((&input[4..], Value::Float(value)))
520}
521
522fn parse_value_ptr<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
524 let (rest, value) = ensure_read_u32_le(input)?;
525 Ok((rest, Value::Pointer(value)))
526}
527
528fn parse_value_wstring<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
530 let (rest, string) = parse_null_terminated_wstring(input)?;
531 Ok((rest, Value::Str(Cow::Owned(string))))
532}
533
534fn parse_value_color<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
536 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
537 context: "reading color",
538 offset: 0,
539 expected: 4,
540 actual: input.len(),
541 })?)
542 .map_err(|_| Error::UnexpectedEndOfInput {
543 context: "reading color",
544 offset: 0,
545 expected: 4,
546 actual: input.len(),
547 })?;
548 Ok((&input[4..], Value::Color(arr)))
549}
550
551fn parse_null_terminated_string_borrowed(input: &[u8]) -> Result<(&[u8], &str)> {
557 let null_pos = input
558 .iter()
559 .position(|&b| b == 0)
560 .ok_or(Error::UnexpectedEndOfInput {
561 context: "reading null-terminated string",
562 offset: 0,
563 expected: 1,
564 actual: input.len(),
565 })?;
566
567 let bytes = &input[..null_pos];
568 let string = core::str::from_utf8(bytes).map_err(|e| Error::InvalidUtf8 {
569 offset: e.valid_up_to(),
570 })?;
571
572 Ok((&input[null_pos + 1..], string))
573}
574
575fn parse_null_terminated_wstring(input: &[u8]) -> Result<(&[u8], String)> {
580 let mut i = 0;
582 while i + 1 < input.len() {
583 if input[i] == 0 && input[i + 1] == 0 {
584 break;
585 }
586 i += 2;
587 }
588
589 if i + 1 >= input.len() {
590 return Err(Error::UnexpectedEndOfInput {
591 context: "reading null-terminated wide string",
592 offset: i,
593 expected: 2,
594 actual: input.len().saturating_sub(i),
595 });
596 }
597
598 let utf16_units = input[..i]
600 .chunks_exact(2)
601 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]));
602
603 let string: String = char::decode_utf16(utf16_units)
605 .enumerate()
606 .map(|(pos, r)| {
607 r.map_err(|_| Error::InvalidUtf16 {
608 offset: pos * 2,
609 position: pos,
610 })
611 })
612 .collect::<core::result::Result<_, _>>()?;
613
614 Ok((&input[i + 2..], string))
615}
616
617fn parse_string_table(input: &[u8]) -> Result<StringTable<'_>> {
625 let (mut rest, string_count) = ensure_read_u32_le(input)?;
626 let string_count = string_count as usize;
627
628 let mut strings = Vec::with_capacity(string_count);
629
630 for _ in 0..string_count {
632 if rest.is_empty() {
633 return Err(Error::UnexpectedEndOfInput {
634 context: "reading string table entry",
635 offset: input.len() - rest.len(),
636 expected: 1,
637 actual: 0,
638 });
639 }
640 let (new_rest, string) = parse_null_terminated_string_borrowed(rest)?;
641 strings.push(string);
642 rest = new_rest;
643 }
644
645 Ok(StringTable { strings })
646}
647
648const PACKAGEINFO_ENTRY_HEADER_SIZE_V39: usize = 4 + 20 + 4; const PACKAGEINFO_ENTRY_HEADER_SIZE_V40: usize = 4 + 20 + 4 + 8; pub fn parse_packageinfo(input: &[u8]) -> Result<Vdf<'_>> {
669 if input.len() < 8 {
670 return Err(Error::UnexpectedEndOfInput {
671 context: "reading packageinfo header",
672 offset: input.len(),
673 expected: 8,
674 actual: input.len(),
675 });
676 }
677
678 let Some(magic) = read_u32_le(input) else {
679 return Err(Error::UnexpectedEndOfInput {
680 context: "reading magic number",
681 offset: 0,
682 expected: 4,
683 actual: input.len(),
684 });
685 };
686
687 let version = magic & 0xFF;
689 let magic_base = magic >> 8;
690
691 if magic_base != PACKAGEINFO_MAGIC_BASE {
692 return Err(Error::InvalidMagic {
693 found: magic,
694 expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
695 });
696 }
697
698 if version != 39 && version != 40 {
699 return Err(Error::InvalidMagic {
700 found: magic,
701 expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
702 });
703 }
704
705 let Some(universe) = read_u32_le(&input[4..]) else {
706 return Err(Error::UnexpectedEndOfInput {
707 context: "reading universe",
708 offset: 4,
709 expected: 4,
710 actual: input.len() - 4,
711 });
712 };
713
714 let has_token = version >= 40;
715 let header_size = if has_token {
716 PACKAGEINFO_ENTRY_HEADER_SIZE_V40
717 } else {
718 PACKAGEINFO_ENTRY_HEADER_SIZE_V39
719 };
720
721 let mut rest = &input[8..];
722 let mut obj = Obj::new();
723
724 loop {
725 if rest.len() < 4 {
727 break;
729 }
730
731 let Some(package_id) = read_u32_le(rest) else {
733 break;
734 };
735
736 if package_id == 0xFFFFFFFF {
738 break;
739 }
740
741 if rest.len() < header_size {
743 return Err(Error::UnexpectedEndOfInput {
744 context: "reading package entry header",
745 offset: input.len() - rest.len(),
746 expected: header_size,
747 actual: rest.len(),
748 });
749 }
750
751 let hash_offset = 4;
753 let change_number_offset = hash_offset + 20;
754
755 let Some(change_number) = read_u32_le(&rest[change_number_offset..]) else {
756 return Err(Error::UnexpectedEndOfInput {
757 context: "reading change number",
758 offset: input.len() - rest.len() + change_number_offset,
759 expected: 4,
760 actual: rest.len() - change_number_offset,
761 });
762 };
763
764 let vdf_data_offset = if has_token {
766 change_number_offset + 4 + 8
767 } else {
768 change_number_offset + 4
769 };
770
771 let vdf_data = &rest[vdf_data_offset..];
773
774 let config = ParseConfig::default(); let (_vdf_rest, package_obj) =
777 parse_object(vdf_data, &config).map_err(with_offset(input.len() - vdf_data.len()))?;
778
779 let mut package_with_meta = Obj::new();
781
782 package_with_meta.insert(Cow::Borrowed("packageid"), Value::I32(package_id as i32));
784 package_with_meta.insert(
785 Cow::Borrowed("change_number"),
786 Value::U64(change_number as u64),
787 );
788 package_with_meta.insert(
789 Cow::Borrowed("sha1"),
790 Value::Str(Cow::Owned(hex::encode(
791 &rest[hash_offset..hash_offset + 20],
792 ))),
793 );
794
795 for (key, value) in package_obj.iter() {
797 package_with_meta.insert(key.clone(), value.clone());
798 }
799
800 obj.insert(
802 Cow::Owned(package_id.to_string()),
803 Value::Obj(package_with_meta),
804 );
805
806 let vdf_end = vdf_data.len() - _vdf_rest.len();
809 rest = &rest[vdf_data_offset + vdf_end..];
810 }
811
812 Ok(Vdf::new(
813 format!("packageinfo_universe_{}", universe),
814 Value::Obj(obj),
815 ))
816}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821
822 #[test]
823 fn test_parse_simple_object() {
824 let data: &[u8] = &[
826 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00, b'v', b'a', b'l', b'u', b'e', 0x00, 0x08, ];
833
834 let result = parse_shortcuts(data);
835 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
836
837 let vdf = result.unwrap();
838 assert_eq!(vdf.key(), "root");
839
840 let obj = vdf.as_obj().unwrap();
841 let test_obj = obj.get("test").and_then(|v| v.as_obj());
842 assert!(test_obj.is_some());
843
844 let test_obj = test_obj.unwrap();
845 let value = test_obj.get("key").and_then(|v| v.as_str());
846 assert_eq!(value, Some("value"));
847 }
848
849 #[test]
850 fn test_parse_nested_objects() {
851 let data: &[u8] = &[
853 0x00, b'o', b'u', b't', b'e', b'r', 0x00, 0x00, b'i', b'n', b'n', b'e', b'r', 0x00, 0x01, b'k', b'e', b'y', 0x00, b'v', b'a', b'l', b'u', b'e', 0x00, 0x08, 0x08, ];
863
864 let result = parse_shortcuts(data);
865 assert!(result.is_ok());
866
867 let vdf = result.unwrap();
868 let obj = vdf.as_obj().unwrap();
869 let outer = obj.get("outer").and_then(|v| v.as_obj()).unwrap();
870 let inner = outer.get("inner").and_then(|v| v.as_obj()).unwrap();
871 let value = inner.get("key").and_then(|v| v.as_str());
872 assert_eq!(value, Some("value"));
873 }
874
875 #[test]
876 fn test_parse_int32_value() {
877 let data: &[u8] = &[
879 0x00, b'r', b'o', b'o', b't', 0x00, 0x02, b'n', b'u', b'm', b'b', b'e', b'r', 0x00, 42, 0, 0, 0, 0x08, ];
886
887 let result = parse_shortcuts(data);
888 assert!(result.is_ok());
889
890 let vdf = result.unwrap();
891 let obj = vdf.as_obj().unwrap();
892 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
893 let value = root.get("number").and_then(|v| v.as_i32());
894 assert_eq!(value, Some(42));
895 }
896
897 #[test]
898 fn test_parse_uint64_value() {
899 let data: &[u8] = &[
901 0x00, b'r', b'o', b'o', b't', 0x00, 0x07, b'n', b'u', b'm', b'b', b'e', b'r', 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x08, ];
908
909 let result = parse_shortcuts(data);
910 assert!(result.is_ok());
911
912 let vdf = result.unwrap();
913 let obj = vdf.as_obj().unwrap();
914 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
915 let value = root.get("number").and_then(|v| v.as_u64());
916 assert_eq!(value, Some(4294967295));
917 }
918
919 #[test]
920 fn test_parse_float_value() {
921 let data: &[u8] = &[
923 0x00, b'r', b'o', b'o', b't', 0x00, 0x03, b'v', b'a', b'l', 0x00, 0x00, 0x00, 0x80, 0x3F, 0x08, ];
930
931 let result = parse_shortcuts(data);
932 assert!(result.is_ok());
933
934 let vdf = result.unwrap();
935 let obj = vdf.as_obj().unwrap();
936 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
937 let value = root.get("val").and_then(|v| v.as_float());
938 assert_eq!(value, Some(1.0));
939 }
940
941 #[test]
942 fn test_parse_ptr_value() {
943 let data: &[u8] = &[
945 0x00, b'r', b'o', b'o', b't', 0x00, 0x04, b'p', b't', b'r', 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x08, ];
952
953 let result = parse_shortcuts(data);
954 assert!(result.is_ok());
955
956 let vdf = result.unwrap();
957 let obj = vdf.as_obj().unwrap();
958 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
959 let value = root.get("ptr").and_then(|v| v.as_pointer());
960 assert_eq!(value, Some(0x12efcdab));
961 }
962
963 #[test]
964 fn test_parse_color_value() {
965 let data: &[u8] = &[
967 0x00, b'r', b'o', b'o', b't', 0x00, 0x06, b'c', b'o', b'l', 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x08, ];
974
975 let result = parse_shortcuts(data);
976 assert!(result.is_ok());
977
978 let vdf = result.unwrap();
979 let obj = vdf.as_obj().unwrap();
980 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
981 let value = root.get("col").and_then(|v| v.as_color());
982 assert_eq!(value, Some([255, 0, 0, 255]));
983 }
984
985 #[test]
988 fn test_parse_unknown_type_byte() {
989 let data: &[u8] = &[
990 0x00, b't', b'e', b's', b't', 0x00, 0xFF, b'k', b'e', b'y', 0x00,
992 ];
993 assert!(matches!(
994 parse_shortcuts(data),
995 Err(Error::UnknownType {
996 type_byte: 0xFF,
997 ..
998 })
999 ));
1000 }
1001
1002 #[test]
1003 fn test_parse_truncated_object_start() {
1004 let data: &[u8] = &[0x00]; assert!(matches!(
1006 parse_shortcuts(data),
1007 Err(Error::UnexpectedEndOfInput { .. })
1008 ));
1009 }
1010
1011 #[test]
1012 fn test_parse_truncated_string_value() {
1013 let data: &[u8] = &[
1014 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00,
1016 ];
1018 assert!(matches!(
1019 parse_shortcuts(data),
1020 Err(Error::UnexpectedEndOfInput { .. })
1021 ));
1022 }
1023
1024 #[test]
1025 fn test_parse_invalid_utf8_string() {
1026 let data: &[u8] = &[
1027 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00, 0xFF, 0xFF, 0x00, ];
1030 assert!(matches!(
1031 parse_shortcuts(data),
1032 Err(Error::InvalidUtf8 { .. })
1033 ));
1034 }
1035
1036 #[test]
1037 fn test_parse_truncated_int32_value() {
1038 let data: &[u8] = &[
1039 0x00, b't', b'e', b's', b't', 0x00, 0x02, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
1042 assert!(matches!(
1043 parse_shortcuts(data),
1044 Err(Error::UnexpectedEndOfInput { .. })
1045 ));
1046 }
1047
1048 #[test]
1049 fn test_parse_truncated_uint64_value() {
1050 let data: &[u8] = &[
1051 0x00, b't', b'e', b's', b't', 0x00, 0x07, b'k', b'e', b'y', 0x00, 0x01, 0x02, 0x03, 0x04, ];
1054 assert!(matches!(
1055 parse_shortcuts(data),
1056 Err(Error::UnexpectedEndOfInput { .. })
1057 ));
1058 }
1059
1060 #[test]
1061 fn test_parse_truncated_float_value() {
1062 let data: &[u8] = &[
1063 0x00, b't', b'e', b's', b't', 0x00, 0x03, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
1066 assert!(matches!(
1067 parse_shortcuts(data),
1068 Err(Error::UnexpectedEndOfInput { .. })
1069 ));
1070 }
1071
1072 #[test]
1073 fn test_parse_truncated_color_value() {
1074 let data: &[u8] = &[
1075 0x00, b't', b'e', b's', b't', 0x00, 0x06, b'k', b'e', b'y', 0x00, 0xFF, 0x00, ];
1078 assert!(matches!(
1079 parse_shortcuts(data),
1080 Err(Error::UnexpectedEndOfInput { .. })
1081 ));
1082 }
1083
1084 #[test]
1085 fn test_parse_wstring_unpaired_surrogate() {
1086 let data: &[u8] = &[
1089 0x00, b't', b'e', b's', b't', 0x00, 0x05, b'k', b'e', b'y', 0x00, 0xD8, 0x00, 0x00,
1091 0x00, ];
1093 assert!(parse_shortcuts(data).is_ok());
1094 }
1095
1096 #[test]
1097 fn test_parse_appinfo_invalid_magic() {
1098 let data: &[u8] = &[
1099 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1103 ];
1104 assert!(matches!(
1105 parse_appinfo(data),
1106 Err(Error::InvalidMagic { .. })
1107 ));
1108 }
1109
1110 #[test]
1111 fn test_parse_appinfo_truncated_header() {
1112 let data: &[u8] = &[
1113 0x28, 0x44, 0x56, 0x07, ];
1115 assert!(matches!(
1116 parse_appinfo(data),
1117 Err(Error::UnexpectedEndOfInput { .. })
1118 ));
1119 }
1120
1121 #[test]
1122 fn test_parse_appinfo_v41_invalid_string_table_offset() {
1123 let data: &[u8] = &[
1125 0x28, 0x44, 0x56, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1128 0x00, ];
1130 assert!(matches!(
1131 parse_appinfo(data),
1132 Err(Error::UnexpectedEndOfInput { .. })
1133 ));
1134 }
1135
1136 #[test]
1137 fn test_parse_packageinfo_invalid_magic() {
1138 let data: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00];
1139 assert!(matches!(
1140 parse_packageinfo(data),
1141 Err(Error::InvalidMagic { .. })
1142 ));
1143 }
1144
1145 #[test]
1146 fn test_parse_packageinfo_truncated_header() {
1147 let data: &[u8] = &[0x27]; assert!(matches!(
1149 parse_packageinfo(data),
1150 Err(Error::UnexpectedEndOfInput { .. })
1151 ));
1152 }
1153
1154 #[test]
1155 fn test_parse_appinfo_with_terminator() {
1156 let data: &[u8] = &[
1159 0x28, 0x44, 0x56, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1164 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1165 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1166 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1167 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1168 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1169 0x00, 0x00, 0x00, 0x00,
1170 ];
1171 let result = parse_appinfo(data);
1172 if let Err(e) = &result {
1173 panic!("parse_appinfo failed with: {:?}", e);
1174 }
1175 assert!(
1176 result.is_ok(),
1177 "Appinfo with terminator should parse successfully"
1178 );
1179 let vdf = result.unwrap();
1180 let obj = vdf.as_obj().unwrap();
1181 assert_eq!(obj.len(), 0, "Should have no apps");
1182 }
1183
1184 #[test]
1185 fn test_parse_autodetect_fallback_to_shortcuts() {
1186 let data: &[u8] = &[
1188 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00, b'v', b'a', b'l', b'u', b'e', 0x00, 0x08, ];
1192 let result = parse(data);
1193 assert!(result.is_ok());
1194 }
1195
1196 #[test]
1199 fn test_parse_packageinfo_v39_invalid_magic_base() {
1200 let data: &[u8] = &[
1202 0x27, 0xBE, 0xBA, 0xFE, 0x00, 0x00, 0x00, 0x00, ];
1205 assert!(matches!(
1206 parse_packageinfo(data),
1207 Err(Error::InvalidMagic { .. })
1208 ));
1209 }
1210
1211 #[test]
1212 fn test_parse_packageinfo_invalid_version() {
1213 let data: &[u8] = &[
1216 0x26, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, ];
1219 assert!(matches!(
1220 parse_packageinfo(data),
1221 Err(Error::InvalidMagic { .. })
1222 ));
1223 }
1224
1225 #[test]
1226 fn test_parse_packageinfo_v39_truncated_universe() {
1227 let data: &[u8] = &[
1229 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, ];
1232 assert!(matches!(
1233 parse_packageinfo(data),
1234 Err(Error::UnexpectedEndOfInput { .. })
1235 ));
1236 }
1237
1238 #[test]
1239 fn test_parse_packageinfo_v39_with_terminator() {
1240 let data: &[u8] = &[
1242 0x27, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
1246 let result = parse_packageinfo(data);
1247 assert!(
1248 result.is_ok(),
1249 "parse_packageinfo failed: {:?}",
1250 result.err()
1251 );
1252 let vdf = result.unwrap();
1253 assert_eq!(vdf.key(), "packageinfo_universe_1");
1254 let obj = vdf.as_obj().unwrap();
1255 assert_eq!(obj.len(), 0, "Should have no packages");
1256 }
1257
1258 #[test]
1259 fn test_parse_packageinfo_v40_with_terminator() {
1260 let data: &[u8] = &[
1262 0x28, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
1266 let result = parse_packageinfo(data);
1267 assert!(
1268 result.is_ok(),
1269 "parse_packageinfo failed: {:?}",
1270 result.err()
1271 );
1272 let vdf = result.unwrap();
1273 assert_eq!(vdf.key(), "packageinfo_universe_1");
1274 let obj = vdf.as_obj().unwrap();
1275 assert_eq!(obj.len(), 0, "Should have no packages");
1276 }
1277
1278 #[test]
1279 fn test_parse_packageinfo_v39_truncated_entry_header() {
1280 let data: &[u8] = &[
1283 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
1288 ];
1289 assert!(matches!(
1290 parse_packageinfo(data),
1291 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
1292 ));
1293 }
1294
1295 #[test]
1296 fn test_parse_packageinfo_v40_truncated_entry_header() {
1297 let data: &[u8] = &[
1300 0x28, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x01, 0x02, 0x03, 0x04,
1305 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
1306 ];
1307 assert!(matches!(
1308 parse_packageinfo(data),
1309 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
1310 ));
1311 }
1312
1313 #[test]
1314 fn test_parse_packageinfo_v39_with_minimal_vdf() {
1315 let data: &[u8] = &[
1317 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1323 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x01, b'k', 0x00, b'v', b'a', b'l', b'u', b'e', 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
1331 ];
1332 let result = parse_packageinfo(data);
1333 assert!(
1334 result.is_ok(),
1335 "parse_packageinfo failed: {:?}",
1336 result.err()
1337 );
1338 let vdf = result.unwrap();
1339 assert_eq!(vdf.key(), "packageinfo_universe_0");
1340
1341 let obj = vdf.as_obj().unwrap();
1342 assert_eq!(obj.len(), 1);
1343 let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
1344 assert_eq!(package.get("k").and_then(|v| v.as_str()), Some("value"));
1345 }
1346
1347 #[test]
1348 fn test_parse_packageinfo_v40_with_minimal_vdf() {
1349 let data: &[u8] = &[
1351 0x28, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
1357 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0x2A, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
1360 0x02, b'x', 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
1367 ];
1368 let result = parse_packageinfo(data);
1369 assert!(
1370 result.is_ok(),
1371 "parse_packageinfo failed: {:?}",
1372 result.err()
1373 );
1374 let vdf = result.unwrap();
1375 assert_eq!(vdf.key(), "packageinfo_universe_0");
1376
1377 let obj = vdf.as_obj().unwrap();
1378 assert_eq!(obj.len(), 1);
1379 let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
1380 assert_eq!(package.get("x").and_then(|v| v.as_i32()), Some(5));
1381 }
1382
1383 #[test]
1384 fn test_parse_packageinfo_multiple_packages() {
1385 let data: &[u8] = &[
1387 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1392 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, b'x', 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1398 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, b'a', 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
1403 ];
1404 let result = parse_packageinfo(data);
1405 assert!(
1406 result.is_ok(),
1407 "parse_packageinfo failed: {:?}",
1408 result.err()
1409 );
1410 let vdf = result.unwrap();
1411 let obj = vdf.as_obj().unwrap();
1412 assert_eq!(obj.len(), 2);
1413 assert!(obj.get("1").is_some());
1414 assert!(obj.get("2").is_some());
1415 }
1416
1417 #[test]
1418 fn test_parse_packageinfo_empty_input() {
1419 let data: &[u8] = &[];
1421 assert!(matches!(
1422 parse_packageinfo(data),
1423 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading packageinfo header"
1424 ));
1425 }
1426
1427 #[test]
1428 fn test_parse_packageinfo_only_magic() {
1429 let data: &[u8] = &[
1431 0x27, 0x55, 0x56, 0x06, ];
1433 assert!(matches!(
1434 parse_packageinfo(data),
1435 Err(Error::UnexpectedEndOfInput { .. })
1436 ));
1437 }
1438}