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::options::{InvalidUtf8, ParseOptions};
15use crate::binary::types::{
16 APPINFO_MAGIC_40, APPINFO_MAGIC_41, BinaryType, PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40,
17 PACKAGEINFO_MAGIC_BASE,
18};
19use crate::error::{Error, Result, with_offset};
20use crate::value::{Obj, Value, Vdf};
21
22const APPINFO_HEADER_SIZE: usize = 8;
26
27const APPINFO_HEADER_AFTER_SIZE: usize = 60;
29
30const APPINFO_ENTRY_HEADER_SIZE: usize = APPINFO_HEADER_SIZE + APPINFO_HEADER_AFTER_SIZE;
32
33const APPINFO_VDF_DATA_OFFSET: usize = APPINFO_ENTRY_HEADER_SIZE;
35
36fn ensure_read_u32_le(input: &[u8]) -> Result<(&[u8], u32)> {
40 read_u32_le(input)
41 .map(|value| (&input[4..], value))
42 .ok_or(Error::UnexpectedEndOfInput {
43 context: "reading u32",
44 offset: 0,
45 expected: 4,
46 actual: input.len(),
47 })
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Default)]
54struct ParseConfig<'input, 'table> {
55 key_mode: KeyMode<'input, 'table>,
57 invalid_utf8: InvalidUtf8,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Default)]
63enum KeyMode<'input, 'table> {
64 #[default]
66 NullTerminated,
67 StringTableIndex {
69 string_table: &'table StringTable<'input>,
70 },
71}
72
73#[derive(Clone, Debug, PartialEq)]
78struct StringTable<'a> {
79 strings: Vec<Cow<'a, str>>,
80}
81
82impl<'a> StringTable<'a> {
83 fn get(&self, index: usize) -> Result<Cow<'a, str>> {
89 self.strings
90 .get(index)
91 .cloned()
92 .ok_or(Error::InvalidStringIndex {
93 index,
94 max: self.strings.len(),
95 })
96 }
97}
98
99impl<'a> KeyMode<'a, '_> {
100 fn parse_key(
105 &self,
106 input: &'a [u8],
107 invalid_utf8: InvalidUtf8,
108 ) -> Result<(&'a [u8], Cow<'a, str>)> {
109 match self {
110 KeyMode::NullTerminated => {
111 let (rest, s) = parse_null_terminated_string(input, invalid_utf8)?;
112 Ok((rest, s))
113 }
114 KeyMode::StringTableIndex { string_table } => {
115 let (rest, index) = ensure_read_u32_le(input)?;
116 let s = string_table.get(index as usize)?;
117 Ok((rest, s))
118 }
119 }
120 }
121}
122
123pub fn parse(input: &[u8]) -> Result<Vdf<'_>> {
134 parse_with(input, ParseOptions::default())
135}
136
137pub fn parse_with(input: &[u8], options: ParseOptions) -> Result<Vdf<'_>> {
139 if let Some(magic) = read_u32_le(input) {
141 if magic == APPINFO_MAGIC_40 || magic == APPINFO_MAGIC_41 {
142 return parse_appinfo_with(input, options);
143 }
144 if magic == PACKAGEINFO_MAGIC_39 || magic == PACKAGEINFO_MAGIC_40 {
145 return parse_packageinfo_with(input, options);
146 }
147 }
148
149 parse_shortcuts_with(input, options)
151}
152
153pub fn parse_shortcuts(input: &[u8]) -> Result<Vdf<'_>> {
171 parse_shortcuts_with(input, ParseOptions::default())
172}
173
174pub fn parse_shortcuts_with(input: &[u8], options: ParseOptions) -> Result<Vdf<'_>> {
176 let config = ParseConfig {
177 invalid_utf8: options.invalid_utf8,
178 ..ParseConfig::default()
179 };
180 let (_rest, obj) = parse_object(input, &config)?;
181
182 Ok(Vdf::new("root", Value::Obj(obj)))
183}
184
185pub fn parse_appinfo(input: &[u8]) -> Result<Vdf<'_>> {
212 parse_appinfo_with(input, ParseOptions::default())
213}
214
215pub fn parse_appinfo_with(input: &[u8], options: ParseOptions) -> Result<Vdf<'_>> {
217 if input.len() < 16 {
218 return Err(Error::UnexpectedEndOfInput {
219 context: "reading appinfo header",
220 offset: input.len(),
221 expected: 16,
222 actual: input.len(),
223 });
224 }
225
226 let Some(magic) = read_u32_le(input) else {
227 return Err(Error::UnexpectedEndOfInput {
228 context: "reading magic number",
229 offset: 0,
230 expected: 4,
231 actual: input.len(),
232 });
233 };
234 let Some(universe) = read_u32_le(&input[4..]) else {
235 return Err(Error::UnexpectedEndOfInput {
236 context: "reading universe",
237 offset: 4,
238 expected: 4,
239 actual: input.len() - 4,
240 });
241 };
242
243 let (string_table_offset, mut rest) = match magic {
244 APPINFO_MAGIC_40 => (None, &input[8..]),
245 APPINFO_MAGIC_41 => {
246 let Some(offset) = read_u64_le(&input[8..]) else {
247 return Err(Error::UnexpectedEndOfInput {
248 context: "reading string table offset",
249 offset: 8,
250 expected: 8,
251 actual: input.len() - 8,
252 });
253 };
254 (Some(offset as usize), &input[16..])
255 }
256 _ => {
257 return Err(Error::InvalidMagic {
258 found: magic,
259 expected: &[APPINFO_MAGIC_40, APPINFO_MAGIC_41],
260 });
261 }
262 };
263
264 let string_table = if let Some(offset) = string_table_offset {
266 if offset >= input.len() {
267 return Err(Error::UnexpectedEndOfInput {
268 context: "reading string table",
269 offset,
270 expected: 4,
271 actual: input.len() - offset,
272 });
273 }
274 Some(
275 parse_string_table(&input[offset..], options.invalid_utf8)
276 .map_err(with_offset(offset))?,
277 )
278 } else {
279 None
280 };
281
282 let mut obj = Obj::new();
283
284 let apps_end_offset = string_table_offset.unwrap_or(input.len());
286
287 let config = ParseConfig {
289 key_mode: if let Some(string_table) = &string_table {
290 KeyMode::StringTableIndex { string_table }
291 } else {
292 KeyMode::NullTerminated
293 },
294 invalid_utf8: options.invalid_utf8,
295 };
296
297 loop {
298 let current_offset = input.len() - rest.len();
300 if current_offset >= apps_end_offset {
301 break;
302 }
303
304 if rest.len() < APPINFO_ENTRY_HEADER_SIZE {
306 return Err(Error::UnexpectedEndOfInput {
307 context: "reading app entry header",
308 offset: current_offset,
309 expected: APPINFO_ENTRY_HEADER_SIZE,
310 actual: rest.len(),
311 });
312 }
313
314 let Some(app_id) = read_u32_le(rest) else {
316 return Err(Error::UnexpectedEndOfInput {
317 context: "reading app id",
318 offset: current_offset,
319 expected: 4,
320 actual: rest.len(),
321 });
322 };
323 if app_id == 0 {
324 break;
325 }
326
327 let Some(size) = read_u32_le(&rest[4..]) else {
329 return Err(Error::UnexpectedEndOfInput {
330 context: "reading entry size",
331 offset: current_offset + 4,
332 expected: 4,
333 actual: rest.len() - 4,
334 });
335 };
336 let size = size as usize;
337
338 let vdf_size = size - APPINFO_HEADER_AFTER_SIZE;
340 let vdf_end = APPINFO_VDF_DATA_OFFSET + vdf_size;
341
342 if vdf_end > rest.len() {
343 return Err(Error::UnexpectedEndOfInput {
344 context: "reading VDF data",
345 offset: current_offset + vdf_end,
346 expected: vdf_end,
347 actual: rest.len(),
348 });
349 }
350
351 let vdf_data = &rest[APPINFO_VDF_DATA_OFFSET..vdf_end];
352 let vdf_offset = current_offset + APPINFO_VDF_DATA_OFFSET;
353
354 let (_vdf_rest, app_obj) =
355 parse_object(vdf_data, &config).map_err(with_offset(vdf_offset))?;
356
357 obj.insert(Cow::Owned(app_id.to_string()), Value::Obj(app_obj));
359 rest = &rest[vdf_end..];
360 }
361
362 Ok(Vdf::new(
363 format!("appinfo_universe_{}", universe),
364 Value::Obj(obj),
365 ))
366}
367
368fn parse_object<'a>(input: &'a [u8], config: &ParseConfig<'a, '_>) -> Result<(&'a [u8], Obj<'a>)> {
384 let mut obj = Obj::new();
385 let mut rest = input;
386
387 loop {
388 match rest {
389 [] => {
390 break Ok((rest, obj));
392 }
393 [type_byte, remainder @ ..] => {
394 let type_byte = *type_byte;
395 let typ = BinaryType::from_byte(type_byte);
396 let offset = input.len() - remainder.len();
397 rest = remainder;
398
399 match typ {
400 Some(BinaryType::ObjectEnd) => {
401 return Ok((rest, obj));
403 }
404 Some(BinaryType::None) => {
405 let key_offset = input.len() - rest.len();
407 let (new_rest, key) = config
408 .key_mode
409 .parse_key(rest, config.invalid_utf8)
410 .map_err(with_offset(key_offset))?;
411 let (new_rest, nested_obj) = parse_object(new_rest, config)?;
412 obj.insert(key, Value::Obj(nested_obj));
413 rest = new_rest;
414 }
415 Some(BinaryType::String) => {
416 let key_offset = input.len() - rest.len();
419 let (new_rest, key) = config
420 .key_mode
421 .parse_key(rest, config.invalid_utf8)
422 .map_err(with_offset(key_offset))?;
423 let value_offset = input.len() - new_rest.len();
424 let (new_rest, value) =
425 parse_null_terminated_string(new_rest, config.invalid_utf8)
426 .map_err(with_offset(value_offset))?;
427 obj.insert(key, Value::Str(value));
428 rest = new_rest;
429 }
430 Some(BinaryType::Int32) => {
431 let key_offset = input.len() - rest.len();
432 let (new_rest, key) = config
433 .key_mode
434 .parse_key(rest, config.invalid_utf8)
435 .map_err(with_offset(key_offset))?;
436 let value_offset = input.len() - new_rest.len();
437 let (new_rest, value) =
438 parse_value_int32(new_rest).map_err(with_offset(value_offset))?;
439 obj.insert(key, value);
440 rest = new_rest;
441 }
442 Some(BinaryType::UInt64) => {
443 let key_offset = input.len() - rest.len();
444 let (new_rest, key) = config
445 .key_mode
446 .parse_key(rest, config.invalid_utf8)
447 .map_err(with_offset(key_offset))?;
448 let value_offset = input.len() - new_rest.len();
449 let (new_rest, value) =
450 parse_value_uint64(new_rest).map_err(with_offset(value_offset))?;
451 obj.insert(key, value);
452 rest = new_rest;
453 }
454 Some(BinaryType::Float) => {
455 let key_offset = input.len() - rest.len();
456 let (new_rest, key) = config
457 .key_mode
458 .parse_key(rest, config.invalid_utf8)
459 .map_err(with_offset(key_offset))?;
460 let value_offset = input.len() - new_rest.len();
461 let (new_rest, value) =
462 parse_value_float(new_rest).map_err(with_offset(value_offset))?;
463 obj.insert(key, value);
464 rest = new_rest;
465 }
466 Some(BinaryType::Ptr) => {
467 let key_offset = input.len() - rest.len();
468 let (new_rest, key) = config
469 .key_mode
470 .parse_key(rest, config.invalid_utf8)
471 .map_err(with_offset(key_offset))?;
472 let value_offset = input.len() - new_rest.len();
473 let (new_rest, value) =
474 parse_value_ptr(new_rest).map_err(with_offset(value_offset))?;
475 obj.insert(key, value);
476 rest = new_rest;
477 }
478 Some(BinaryType::WString) => {
479 let key_offset = input.len() - rest.len();
480 let (new_rest, key) = config
481 .key_mode
482 .parse_key(rest, config.invalid_utf8)
483 .map_err(with_offset(key_offset))?;
484 let value_offset = input.len() - new_rest.len();
485 let (new_rest, value) = parse_value_wstring(new_rest, config.invalid_utf8)
486 .map_err(with_offset(value_offset))?;
487 obj.insert(key, value);
488 rest = new_rest;
489 }
490 Some(BinaryType::Color) => {
491 let key_offset = input.len() - rest.len();
492 let (new_rest, key) = config
493 .key_mode
494 .parse_key(rest, config.invalid_utf8)
495 .map_err(with_offset(key_offset))?;
496 let value_offset = input.len() - new_rest.len();
497 let (new_rest, value) =
498 parse_value_color(new_rest).map_err(with_offset(value_offset))?;
499 obj.insert(key, value);
500 rest = new_rest;
501 }
502 None => {
503 return Err(Error::UnknownType { type_byte, offset });
505 }
506 }
507 }
508 }
509 }
510}
511
512fn parse_value_int32<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
516 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
517 context: "reading int32",
518 offset: 0,
519 expected: 4,
520 actual: input.len(),
521 })?)
522 .map_err(|_| Error::UnexpectedEndOfInput {
523 context: "reading int32",
524 offset: 0,
525 expected: 4,
526 actual: input.len(),
527 })?;
528 let value = i32::from_le_bytes(arr);
529 Ok((&input[4..], Value::I32(value)))
530}
531
532fn parse_value_uint64<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
534 let arr = <[u8; 8]>::try_from(input.get(..8).ok_or(Error::UnexpectedEndOfInput {
535 context: "reading uint64",
536 offset: 0,
537 expected: 8,
538 actual: input.len(),
539 })?)
540 .map_err(|_| Error::UnexpectedEndOfInput {
541 context: "reading uint64",
542 offset: 0,
543 expected: 8,
544 actual: input.len(),
545 })?;
546 let value = u64::from_le_bytes(arr);
547 Ok((&input[8..], Value::U64(value)))
548}
549
550fn parse_value_float<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
552 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
553 context: "reading float",
554 offset: 0,
555 expected: 4,
556 actual: input.len(),
557 })?)
558 .map_err(|_| Error::UnexpectedEndOfInput {
559 context: "reading float",
560 offset: 0,
561 expected: 4,
562 actual: input.len(),
563 })?;
564 let value = f32::from_le_bytes(arr);
565 Ok((&input[4..], Value::Float(value)))
566}
567
568fn parse_value_ptr<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
570 let (rest, value) = ensure_read_u32_le(input)?;
571 Ok((rest, Value::Pointer(value)))
572}
573
574fn parse_value_wstring<'a>(
576 input: &'a [u8],
577 invalid_utf8: InvalidUtf8,
578) -> Result<(&'a [u8], Value<'a>)> {
579 let (rest, string) = parse_null_terminated_wstring(input, invalid_utf8)?;
580 Ok((rest, Value::Str(Cow::Owned(string))))
581}
582
583fn parse_value_color<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
585 let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
586 context: "reading color",
587 offset: 0,
588 expected: 4,
589 actual: input.len(),
590 })?)
591 .map_err(|_| Error::UnexpectedEndOfInput {
592 context: "reading color",
593 offset: 0,
594 expected: 4,
595 actual: input.len(),
596 })?;
597 Ok((&input[4..], Value::Color(arr)))
598}
599
600fn decode_utf8(bytes: &[u8], invalid_utf8: InvalidUtf8) -> Result<Cow<'_, str>> {
609 match invalid_utf8 {
610 InvalidUtf8::Strict => {
611 core::str::from_utf8(bytes)
612 .map(Cow::Borrowed)
613 .map_err(|e| Error::InvalidUtf8 {
614 offset: e.valid_up_to(),
615 })
616 }
617 InvalidUtf8::Lossy => Ok(String::from_utf8_lossy(bytes)),
618 }
619}
620
621fn parse_null_terminated_string(
626 input: &[u8],
627 invalid_utf8: InvalidUtf8,
628) -> Result<(&[u8], Cow<'_, str>)> {
629 let null_pos = input
630 .iter()
631 .position(|&b| b == 0)
632 .ok_or(Error::UnexpectedEndOfInput {
633 context: "reading null-terminated string",
634 offset: 0,
635 expected: 1,
636 actual: input.len(),
637 })?;
638
639 let string = decode_utf8(&input[..null_pos], invalid_utf8)?;
640
641 Ok((&input[null_pos + 1..], string))
642}
643
644fn parse_null_terminated_wstring(
649 input: &[u8],
650 invalid_utf8: InvalidUtf8,
651) -> Result<(&[u8], String)> {
652 let mut i = 0;
654 while i + 1 < input.len() {
655 if input[i] == 0 && input[i + 1] == 0 {
656 break;
657 }
658 i += 2;
659 }
660
661 if i + 1 >= input.len() {
662 return Err(Error::UnexpectedEndOfInput {
663 context: "reading null-terminated wide string",
664 offset: i,
665 expected: 2,
666 actual: input.len().saturating_sub(i),
667 });
668 }
669
670 let utf16_units = input[..i]
672 .chunks_exact(2)
673 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]));
674
675 let string: String = match invalid_utf8 {
677 InvalidUtf8::Strict => char::decode_utf16(utf16_units)
678 .enumerate()
679 .map(|(pos, r)| {
680 r.map_err(|_| Error::InvalidUtf16 {
681 offset: pos * 2,
682 position: pos,
683 })
684 })
685 .collect::<core::result::Result<_, _>>()?,
686 InvalidUtf8::Lossy => char::decode_utf16(utf16_units)
687 .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
688 .collect(),
689 };
690
691 Ok((&input[i + 2..], string))
692}
693
694fn parse_string_table(input: &[u8], invalid_utf8: InvalidUtf8) -> Result<StringTable<'_>> {
702 let (mut rest, string_count) = ensure_read_u32_le(input)?;
703 let string_count = string_count as usize;
704
705 let mut strings = Vec::with_capacity(string_count);
706
707 for _ in 0..string_count {
709 if rest.is_empty() {
710 return Err(Error::UnexpectedEndOfInput {
711 context: "reading string table entry",
712 offset: input.len() - rest.len(),
713 expected: 1,
714 actual: 0,
715 });
716 }
717 let (new_rest, string) = parse_null_terminated_string(rest, invalid_utf8)?;
718 strings.push(string);
719 rest = new_rest;
720 }
721
722 Ok(StringTable { strings })
723}
724
725const 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<'_>> {
749 parse_packageinfo_with(input, ParseOptions::default())
750}
751
752pub fn parse_packageinfo_with(input: &[u8], options: ParseOptions) -> Result<Vdf<'_>> {
754 if input.len() < 8 {
755 return Err(Error::UnexpectedEndOfInput {
756 context: "reading packageinfo header",
757 offset: input.len(),
758 expected: 8,
759 actual: input.len(),
760 });
761 }
762
763 let Some(magic) = read_u32_le(input) else {
764 return Err(Error::UnexpectedEndOfInput {
765 context: "reading magic number",
766 offset: 0,
767 expected: 4,
768 actual: input.len(),
769 });
770 };
771
772 let version = magic & 0xFF;
774 let magic_base = magic >> 8;
775
776 if magic_base != PACKAGEINFO_MAGIC_BASE {
777 return Err(Error::InvalidMagic {
778 found: magic,
779 expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
780 });
781 }
782
783 if version != 39 && version != 40 {
784 return Err(Error::InvalidMagic {
785 found: magic,
786 expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
787 });
788 }
789
790 let Some(universe) = read_u32_le(&input[4..]) else {
791 return Err(Error::UnexpectedEndOfInput {
792 context: "reading universe",
793 offset: 4,
794 expected: 4,
795 actual: input.len() - 4,
796 });
797 };
798
799 let has_token = version >= 40;
800 let header_size = if has_token {
801 PACKAGEINFO_ENTRY_HEADER_SIZE_V40
802 } else {
803 PACKAGEINFO_ENTRY_HEADER_SIZE_V39
804 };
805
806 let mut rest = &input[8..];
807 let mut obj = Obj::new();
808
809 loop {
810 if rest.len() < 4 {
812 break;
814 }
815
816 let Some(package_id) = read_u32_le(rest) else {
818 break;
819 };
820
821 if package_id == 0xFFFFFFFF {
823 break;
824 }
825
826 if rest.len() < header_size {
828 return Err(Error::UnexpectedEndOfInput {
829 context: "reading package entry header",
830 offset: input.len() - rest.len(),
831 expected: header_size,
832 actual: rest.len(),
833 });
834 }
835
836 let hash_offset = 4;
838 let change_number_offset = hash_offset + 20;
839
840 let Some(change_number) = read_u32_le(&rest[change_number_offset..]) else {
841 return Err(Error::UnexpectedEndOfInput {
842 context: "reading change number",
843 offset: input.len() - rest.len() + change_number_offset,
844 expected: 4,
845 actual: rest.len() - change_number_offset,
846 });
847 };
848
849 let vdf_data_offset = if has_token {
851 change_number_offset + 4 + 8
852 } else {
853 change_number_offset + 4
854 };
855
856 let vdf_data = &rest[vdf_data_offset..];
858
859 let config = ParseConfig {
861 invalid_utf8: options.invalid_utf8,
862 ..ParseConfig::default()
863 };
864
865 let (_vdf_rest, package_obj) =
866 parse_object(vdf_data, &config).map_err(with_offset(input.len() - vdf_data.len()))?;
867
868 let mut package_with_meta = Obj::new();
870
871 package_with_meta.insert(Cow::Borrowed("packageid"), Value::I32(package_id as i32));
873 package_with_meta.insert(
874 Cow::Borrowed("change_number"),
875 Value::U64(change_number as u64),
876 );
877 package_with_meta.insert(
878 Cow::Borrowed("sha1"),
879 Value::Str(Cow::Owned(hex::encode(
880 &rest[hash_offset..hash_offset + 20],
881 ))),
882 );
883
884 for (key, value) in package_obj.iter() {
886 package_with_meta.insert(key.clone(), value.clone());
887 }
888
889 obj.insert(
891 Cow::Owned(package_id.to_string()),
892 Value::Obj(package_with_meta),
893 );
894
895 let vdf_end = vdf_data.len() - _vdf_rest.len();
898 rest = &rest[vdf_data_offset + vdf_end..];
899 }
900
901 Ok(Vdf::new(
902 format!("packageinfo_universe_{}", universe),
903 Value::Obj(obj),
904 ))
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910
911 #[test]
912 fn test_parse_simple_object() {
913 let data: &[u8] = &[
915 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, ];
922
923 let result = parse_shortcuts(data);
924 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
925
926 let vdf = result.unwrap();
927 assert_eq!(vdf.key(), "root");
928
929 let obj = vdf.as_obj().unwrap();
930 let test_obj = obj.get("test").and_then(|v| v.as_obj());
931 assert!(test_obj.is_some());
932
933 let test_obj = test_obj.unwrap();
934 let value = test_obj.get("key").and_then(|v| v.as_str());
935 assert_eq!(value, Some("value"));
936 }
937
938 #[test]
939 fn test_parse_nested_objects() {
940 let data: &[u8] = &[
942 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, ];
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 outer = obj.get("outer").and_then(|v| v.as_obj()).unwrap();
959 let inner = outer.get("inner").and_then(|v| v.as_obj()).unwrap();
960 let value = inner.get("key").and_then(|v| v.as_str());
961 assert_eq!(value, Some("value"));
962 }
963
964 #[test]
965 fn test_parse_int32_value() {
966 let data: &[u8] = &[
968 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, ];
975
976 let result = parse_shortcuts(data);
977 assert!(result.is_ok());
978
979 let vdf = result.unwrap();
980 let obj = vdf.as_obj().unwrap();
981 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
982 let value = root.get("number").and_then(|v| v.as_i32());
983 assert_eq!(value, Some(42));
984 }
985
986 #[test]
987 fn test_parse_uint64_value() {
988 let data: &[u8] = &[
990 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, ];
997
998 let result = parse_shortcuts(data);
999 assert!(result.is_ok());
1000
1001 let vdf = result.unwrap();
1002 let obj = vdf.as_obj().unwrap();
1003 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
1004 let value = root.get("number").and_then(|v| v.as_u64());
1005 assert_eq!(value, Some(4294967295));
1006 }
1007
1008 #[test]
1009 fn test_parse_float_value() {
1010 let data: &[u8] = &[
1012 0x00, b'r', b'o', b'o', b't', 0x00, 0x03, b'v', b'a', b'l', 0x00, 0x00, 0x00, 0x80, 0x3F, 0x08, ];
1019
1020 let result = parse_shortcuts(data);
1021 assert!(result.is_ok());
1022
1023 let vdf = result.unwrap();
1024 let obj = vdf.as_obj().unwrap();
1025 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
1026 let value = root.get("val").and_then(|v| v.as_float());
1027 assert_eq!(value, Some(1.0));
1028 }
1029
1030 #[test]
1031 fn test_parse_ptr_value() {
1032 let data: &[u8] = &[
1034 0x00, b'r', b'o', b'o', b't', 0x00, 0x04, b'p', b't', b'r', 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x08, ];
1041
1042 let result = parse_shortcuts(data);
1043 assert!(result.is_ok());
1044
1045 let vdf = result.unwrap();
1046 let obj = vdf.as_obj().unwrap();
1047 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
1048 let value = root.get("ptr").and_then(|v| v.as_pointer());
1049 assert_eq!(value, Some(0x12efcdab));
1050 }
1051
1052 #[test]
1053 fn test_parse_color_value() {
1054 let data: &[u8] = &[
1056 0x00, b'r', b'o', b'o', b't', 0x00, 0x06, b'c', b'o', b'l', 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x08, ];
1063
1064 let result = parse_shortcuts(data);
1065 assert!(result.is_ok());
1066
1067 let vdf = result.unwrap();
1068 let obj = vdf.as_obj().unwrap();
1069 let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
1070 let value = root.get("col").and_then(|v| v.as_color());
1071 assert_eq!(value, Some([255, 0, 0, 255]));
1072 }
1073
1074 #[test]
1077 fn test_parse_unknown_type_byte() {
1078 let data: &[u8] = &[
1079 0x00, b't', b'e', b's', b't', 0x00, 0xFF, b'k', b'e', b'y', 0x00,
1081 ];
1082 assert!(matches!(
1083 parse_shortcuts(data),
1084 Err(Error::UnknownType {
1085 type_byte: 0xFF,
1086 ..
1087 })
1088 ));
1089 }
1090
1091 #[test]
1092 fn test_parse_truncated_object_start() {
1093 let data: &[u8] = &[0x00]; assert!(matches!(
1095 parse_shortcuts(data),
1096 Err(Error::UnexpectedEndOfInput { .. })
1097 ));
1098 }
1099
1100 #[test]
1101 fn test_parse_truncated_string_value() {
1102 let data: &[u8] = &[
1103 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00,
1105 ];
1107 assert!(matches!(
1108 parse_shortcuts(data),
1109 Err(Error::UnexpectedEndOfInput { .. })
1110 ));
1111 }
1112
1113 #[test]
1114 fn test_parse_invalid_utf8_string_strict() {
1115 let data: &[u8] = &[
1116 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00, 0xFF, 0xFF, 0x00, ];
1119 let opts = ParseOptions::new().invalid_utf8(InvalidUtf8::Strict);
1120 assert!(matches!(
1121 parse_shortcuts_with(data, opts),
1122 Err(Error::InvalidUtf8 { .. })
1123 ));
1124 }
1125
1126 #[test]
1127 fn test_parse_invalid_utf8_string_lossy_default() {
1128 let data: &[u8] = &[
1132 0x00, b't', b'e', b's', b't', 0x00, 0x01, b'n', b'a', b'm', b'e', 0x00, b'v', 0xFD, b't', 0x00, 0x08, ];
1137 let vdf = parse_shortcuts(data).unwrap();
1138 let obj = vdf.as_obj().unwrap();
1139 let test = obj.get("test").and_then(|v| v.as_obj()).unwrap();
1140 assert_eq!(
1141 test.get("name").and_then(|v| v.as_str()),
1142 Some("v\u{fffd}t")
1143 );
1144 }
1145
1146 #[test]
1147 fn test_parse_invalid_utf8_key_lossy() {
1148 let data: &[u8] = &[
1150 0x00, b'k', 0xFD, 0x00, 0x01, b'a', 0x00, b'b', 0x00, 0x08,
1153 ];
1154 let vdf = parse_shortcuts(data).unwrap();
1155 let obj = vdf.as_obj().unwrap();
1156 assert!(obj.get("k\u{fffd}").is_some());
1157 }
1158
1159 #[test]
1160 fn test_valid_utf8_still_borrowed_lossy() {
1161 let data: &[u8] = &[
1163 0x00, b't', 0x00, 0x01, b'k', 0x00, b'v', 0x00, 0x08,
1166 ];
1167 let vdf = parse_shortcuts(data).unwrap();
1168 let obj = vdf.as_obj().unwrap();
1169 let inner = obj.get("t").and_then(|v| v.as_obj()).unwrap();
1170 match inner.get("k").unwrap() {
1171 Value::Str(Cow::Borrowed(_)) => {}
1172 other => panic!("expected borrowed str, got {other:?}"),
1173 }
1174 }
1175
1176 #[test]
1177 fn test_parse_truncated_int32_value() {
1178 let data: &[u8] = &[
1179 0x00, b't', b'e', b's', b't', 0x00, 0x02, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
1182 assert!(matches!(
1183 parse_shortcuts(data),
1184 Err(Error::UnexpectedEndOfInput { .. })
1185 ));
1186 }
1187
1188 #[test]
1189 fn test_parse_truncated_uint64_value() {
1190 let data: &[u8] = &[
1191 0x00, b't', b'e', b's', b't', 0x00, 0x07, b'k', b'e', b'y', 0x00, 0x01, 0x02, 0x03, 0x04, ];
1194 assert!(matches!(
1195 parse_shortcuts(data),
1196 Err(Error::UnexpectedEndOfInput { .. })
1197 ));
1198 }
1199
1200 #[test]
1201 fn test_parse_truncated_float_value() {
1202 let data: &[u8] = &[
1203 0x00, b't', b'e', b's', b't', 0x00, 0x03, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
1206 assert!(matches!(
1207 parse_shortcuts(data),
1208 Err(Error::UnexpectedEndOfInput { .. })
1209 ));
1210 }
1211
1212 #[test]
1213 fn test_parse_truncated_color_value() {
1214 let data: &[u8] = &[
1215 0x00, b't', b'e', b's', b't', 0x00, 0x06, b'k', b'e', b'y', 0x00, 0xFF, 0x00, ];
1218 assert!(matches!(
1219 parse_shortcuts(data),
1220 Err(Error::UnexpectedEndOfInput { .. })
1221 ));
1222 }
1223
1224 #[test]
1225 fn test_parse_wstring_unpaired_surrogate() {
1226 let data: &[u8] = &[
1229 0x00, b't', b'e', b's', b't', 0x00, 0x05, b'k', b'e', b'y', 0x00, 0xD8, 0x00, 0x00,
1231 0x00, ];
1233 assert!(parse_shortcuts(data).is_ok());
1234 }
1235
1236 #[test]
1237 fn test_parse_appinfo_invalid_magic() {
1238 let data: &[u8] = &[
1239 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1243 ];
1244 assert!(matches!(
1245 parse_appinfo(data),
1246 Err(Error::InvalidMagic { .. })
1247 ));
1248 }
1249
1250 #[test]
1251 fn test_parse_appinfo_truncated_header() {
1252 let data: &[u8] = &[
1253 0x28, 0x44, 0x56, 0x07, ];
1255 assert!(matches!(
1256 parse_appinfo(data),
1257 Err(Error::UnexpectedEndOfInput { .. })
1258 ));
1259 }
1260
1261 #[test]
1262 fn test_parse_appinfo_v41_invalid_string_table_offset() {
1263 let data: &[u8] = &[
1265 0x28, 0x44, 0x56, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1268 0x00, ];
1270 assert!(matches!(
1271 parse_appinfo(data),
1272 Err(Error::UnexpectedEndOfInput { .. })
1273 ));
1274 }
1275
1276 fn build_v41_appinfo(key_bytes: &[u8]) -> alloc::vec::Vec<u8> {
1281 let mut data = alloc::vec::Vec::new();
1282 data.extend_from_slice(&APPINFO_MAGIC_41.to_le_bytes());
1283 data.extend_from_slice(&1u32.to_le_bytes()); let st_offset_pos = data.len();
1285 data.extend_from_slice(&[0u8; 8]); data.extend_from_slice(&10u32.to_le_bytes()); let size_pos = data.len();
1290 data.extend_from_slice(&[0u8; 4]); let after_size_start = data.len();
1292 data.extend_from_slice(&[0u8; APPINFO_HEADER_AFTER_SIZE]); data.push(0x01); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(b"value\x00");
1297 data.push(0x08); let size = (data.len() - after_size_start) as u32;
1299 data[size_pos..size_pos + 4].copy_from_slice(&size.to_le_bytes());
1300
1301 let st_offset = data.len() as u64;
1305 data[st_offset_pos..st_offset_pos + 8].copy_from_slice(&st_offset.to_le_bytes());
1306 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(key_bytes);
1308 data.push(0x00); data
1311 }
1312
1313 #[test]
1314 fn test_parse_appinfo_v41_invalid_utf8_string_table_lossy() {
1315 let data = build_v41_appinfo(&[b'n', b'a', 0xFD, b'e']);
1317 let vdf = parse_appinfo(&data).unwrap();
1318 let obj = vdf.as_obj().unwrap();
1319 let app = obj.get("10").and_then(|v| v.as_obj()).unwrap();
1320 assert_eq!(
1322 app.get("na\u{fffd}e").and_then(|v| v.as_str()),
1323 Some("value")
1324 );
1325 }
1326
1327 #[test]
1328 fn test_parse_appinfo_v41_invalid_utf8_string_table_strict() {
1329 let data = build_v41_appinfo(&[b'n', b'a', 0xFD, b'e']);
1330 let opts = ParseOptions::new().invalid_utf8(InvalidUtf8::Strict);
1331 assert!(matches!(
1332 parse_appinfo_with(&data, opts),
1333 Err(Error::InvalidUtf8 { .. })
1334 ));
1335 }
1336
1337 #[test]
1338 fn test_parse_appinfo_v41_valid_string_table_roundtrips() {
1339 let data = build_v41_appinfo(b"name");
1341 let vdf = parse_appinfo(&data).unwrap();
1342 let obj = vdf.as_obj().unwrap();
1343 let app = obj.get("10").and_then(|v| v.as_obj()).unwrap();
1344 assert_eq!(app.get("name").and_then(|v| v.as_str()), Some("value"));
1345 }
1346
1347 #[test]
1348 fn test_parse_packageinfo_invalid_magic() {
1349 let data: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00];
1350 assert!(matches!(
1351 parse_packageinfo(data),
1352 Err(Error::InvalidMagic { .. })
1353 ));
1354 }
1355
1356 #[test]
1357 fn test_parse_packageinfo_truncated_header() {
1358 let data: &[u8] = &[0x27]; assert!(matches!(
1360 parse_packageinfo(data),
1361 Err(Error::UnexpectedEndOfInput { .. })
1362 ));
1363 }
1364
1365 #[test]
1366 fn test_parse_appinfo_with_terminator() {
1367 let data: &[u8] = &[
1370 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,
1375 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1376 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1377 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1378 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1379 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1380 0x00, 0x00, 0x00, 0x00,
1381 ];
1382 let result = parse_appinfo(data);
1383 if let Err(e) = &result {
1384 panic!("parse_appinfo failed with: {:?}", e);
1385 }
1386 assert!(
1387 result.is_ok(),
1388 "Appinfo with terminator should parse successfully"
1389 );
1390 let vdf = result.unwrap();
1391 let obj = vdf.as_obj().unwrap();
1392 assert_eq!(obj.len(), 0, "Should have no apps");
1393 }
1394
1395 #[test]
1396 fn test_parse_autodetect_fallback_to_shortcuts() {
1397 let data: &[u8] = &[
1399 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, ];
1403 let result = parse(data);
1404 assert!(result.is_ok());
1405 }
1406
1407 #[test]
1410 fn test_parse_packageinfo_v39_invalid_magic_base() {
1411 let data: &[u8] = &[
1413 0x27, 0xBE, 0xBA, 0xFE, 0x00, 0x00, 0x00, 0x00, ];
1416 assert!(matches!(
1417 parse_packageinfo(data),
1418 Err(Error::InvalidMagic { .. })
1419 ));
1420 }
1421
1422 #[test]
1423 fn test_parse_packageinfo_invalid_version() {
1424 let data: &[u8] = &[
1427 0x26, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, ];
1430 assert!(matches!(
1431 parse_packageinfo(data),
1432 Err(Error::InvalidMagic { .. })
1433 ));
1434 }
1435
1436 #[test]
1437 fn test_parse_packageinfo_v39_truncated_universe() {
1438 let data: &[u8] = &[
1440 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, ];
1443 assert!(matches!(
1444 parse_packageinfo(data),
1445 Err(Error::UnexpectedEndOfInput { .. })
1446 ));
1447 }
1448
1449 #[test]
1450 fn test_parse_packageinfo_v39_with_terminator() {
1451 let data: &[u8] = &[
1453 0x27, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
1457 let result = parse_packageinfo(data);
1458 assert!(
1459 result.is_ok(),
1460 "parse_packageinfo failed: {:?}",
1461 result.err()
1462 );
1463 let vdf = result.unwrap();
1464 assert_eq!(vdf.key(), "packageinfo_universe_1");
1465 let obj = vdf.as_obj().unwrap();
1466 assert_eq!(obj.len(), 0, "Should have no packages");
1467 }
1468
1469 #[test]
1470 fn test_parse_packageinfo_v40_with_terminator() {
1471 let data: &[u8] = &[
1473 0x28, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
1477 let result = parse_packageinfo(data);
1478 assert!(
1479 result.is_ok(),
1480 "parse_packageinfo failed: {:?}",
1481 result.err()
1482 );
1483 let vdf = result.unwrap();
1484 assert_eq!(vdf.key(), "packageinfo_universe_1");
1485 let obj = vdf.as_obj().unwrap();
1486 assert_eq!(obj.len(), 0, "Should have no packages");
1487 }
1488
1489 #[test]
1490 fn test_parse_packageinfo_v39_truncated_entry_header() {
1491 let data: &[u8] = &[
1494 0x27, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
1499 ];
1500 assert!(matches!(
1501 parse_packageinfo(data),
1502 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
1503 ));
1504 }
1505
1506 #[test]
1507 fn test_parse_packageinfo_v40_truncated_entry_header() {
1508 let data: &[u8] = &[
1511 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,
1516 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
1517 ];
1518 assert!(matches!(
1519 parse_packageinfo(data),
1520 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
1521 ));
1522 }
1523
1524 #[test]
1525 fn test_parse_packageinfo_v39_with_minimal_vdf() {
1526 let data: &[u8] = &[
1528 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,
1534 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,
1542 ];
1543 let result = parse_packageinfo(data);
1544 assert!(
1545 result.is_ok(),
1546 "parse_packageinfo failed: {:?}",
1547 result.err()
1548 );
1549 let vdf = result.unwrap();
1550 assert_eq!(vdf.key(), "packageinfo_universe_0");
1551
1552 let obj = vdf.as_obj().unwrap();
1553 assert_eq!(obj.len(), 1);
1554 let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
1555 assert_eq!(package.get("k").and_then(|v| v.as_str()), Some("value"));
1556 }
1557
1558 #[test]
1559 fn test_parse_packageinfo_v40_with_minimal_vdf() {
1560 let data: &[u8] = &[
1562 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,
1568 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0x2A, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
1571 0x02, b'x', 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
1578 ];
1579 let result = parse_packageinfo(data);
1580 assert!(
1581 result.is_ok(),
1582 "parse_packageinfo failed: {:?}",
1583 result.err()
1584 );
1585 let vdf = result.unwrap();
1586 assert_eq!(vdf.key(), "packageinfo_universe_0");
1587
1588 let obj = vdf.as_obj().unwrap();
1589 assert_eq!(obj.len(), 1);
1590 let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
1591 assert_eq!(package.get("x").and_then(|v| v.as_i32()), Some(5));
1592 }
1593
1594 #[test]
1595 fn test_parse_packageinfo_multiple_packages() {
1596 let data: &[u8] = &[
1598 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,
1603 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,
1609 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, b'a', 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
1614 ];
1615 let result = parse_packageinfo(data);
1616 assert!(
1617 result.is_ok(),
1618 "parse_packageinfo failed: {:?}",
1619 result.err()
1620 );
1621 let vdf = result.unwrap();
1622 let obj = vdf.as_obj().unwrap();
1623 assert_eq!(obj.len(), 2);
1624 assert!(obj.get("1").is_some());
1625 assert!(obj.get("2").is_some());
1626 }
1627
1628 #[test]
1629 fn test_parse_packageinfo_empty_input() {
1630 let data: &[u8] = &[];
1632 assert!(matches!(
1633 parse_packageinfo(data),
1634 Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading packageinfo header"
1635 ));
1636 }
1637
1638 #[test]
1639 fn test_parse_packageinfo_only_magic() {
1640 let data: &[u8] = &[
1642 0x27, 0x55, 0x56, 0x06, ];
1644 assert!(matches!(
1645 parse_packageinfo(data),
1646 Err(Error::UnexpectedEndOfInput { .. })
1647 ));
1648 }
1649}