use alloc::borrow::Cow;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::str;
use crate::binary::byte_reader::{read_u32_le, read_u64_le};
use crate::binary::types::{
APPINFO_MAGIC_40, APPINFO_MAGIC_41, BinaryType, PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40,
PACKAGEINFO_MAGIC_BASE,
};
use crate::error::{Error, Result, with_offset};
use crate::value::{Obj, Value, Vdf};
const APPINFO_HEADER_SIZE: usize = 8;
const APPINFO_HEADER_AFTER_SIZE: usize = 60;
const APPINFO_ENTRY_HEADER_SIZE: usize = APPINFO_HEADER_SIZE + APPINFO_HEADER_AFTER_SIZE;
const APPINFO_VDF_DATA_OFFSET: usize = APPINFO_ENTRY_HEADER_SIZE;
fn ensure_read_u32_le(input: &[u8]) -> Result<(&[u8], u32)> {
read_u32_le(input)
.map(|value| (&input[4..], value))
.ok_or(Error::UnexpectedEndOfInput {
context: "reading u32",
offset: 0,
expected: 4,
actual: input.len(),
})
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
struct ParseConfig<'input, 'table> {
key_mode: KeyMode<'input, 'table>,
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
enum KeyMode<'input, 'table> {
#[default]
NullTerminated,
StringTableIndex {
string_table: &'table StringTable<'input>,
},
}
#[derive(Clone, Debug, PartialEq)]
struct StringTable<'a> {
strings: Vec<&'a str>,
}
impl<'a> StringTable<'a> {
fn get(&self, index: usize) -> Result<&'a str> {
self.strings
.get(index)
.copied()
.ok_or(Error::InvalidStringIndex {
index,
max: self.strings.len(),
})
}
}
impl<'a> KeyMode<'a, '_> {
fn parse_key(&self, input: &'a [u8]) -> Result<(&'a [u8], Cow<'a, str>)> {
match self {
KeyMode::NullTerminated => {
let (rest, s) = parse_null_terminated_string_borrowed(input)?;
Ok((rest, Cow::Borrowed(s)))
}
KeyMode::StringTableIndex { string_table } => {
let (rest, index) = ensure_read_u32_le(input)?;
let s = string_table.get(index as usize)?;
Ok((rest, Cow::Borrowed(s)))
}
}
}
}
pub fn parse(input: &[u8]) -> Result<Vdf<'_>> {
if let Some(magic) = read_u32_le(input) {
if magic == APPINFO_MAGIC_40 || magic == APPINFO_MAGIC_41 {
return parse_appinfo(input);
}
if magic == PACKAGEINFO_MAGIC_39 || magic == PACKAGEINFO_MAGIC_40 {
return parse_packageinfo(input);
}
}
parse_shortcuts(input)
}
pub fn parse_shortcuts(input: &[u8]) -> Result<Vdf<'_>> {
let config = ParseConfig::default();
let (_rest, obj) = parse_object(input, &config)?;
Ok(Vdf::new("root", Value::Obj(obj)))
}
pub fn parse_appinfo(input: &[u8]) -> Result<Vdf<'_>> {
if input.len() < 16 {
return Err(Error::UnexpectedEndOfInput {
context: "reading appinfo header",
offset: input.len(),
expected: 16,
actual: input.len(),
});
}
let Some(magic) = read_u32_le(input) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading magic number",
offset: 0,
expected: 4,
actual: input.len(),
});
};
let Some(universe) = read_u32_le(&input[4..]) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading universe",
offset: 4,
expected: 4,
actual: input.len() - 4,
});
};
let (string_table_offset, mut rest) = match magic {
APPINFO_MAGIC_40 => (None, &input[8..]),
APPINFO_MAGIC_41 => {
let Some(offset) = read_u64_le(&input[8..]) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading string table offset",
offset: 8,
expected: 8,
actual: input.len() - 8,
});
};
(Some(offset as usize), &input[16..])
}
_ => {
return Err(Error::InvalidMagic {
found: magic,
expected: &[APPINFO_MAGIC_40, APPINFO_MAGIC_41],
});
}
};
let string_table = if let Some(offset) = string_table_offset {
if offset >= input.len() {
return Err(Error::UnexpectedEndOfInput {
context: "reading string table",
offset,
expected: 4,
actual: input.len() - offset,
});
}
Some(parse_string_table(&input[offset..]).map_err(with_offset(offset))?)
} else {
None
};
let mut obj = Obj::new();
let apps_end_offset = string_table_offset.unwrap_or(input.len());
let config = ParseConfig {
key_mode: if let Some(string_table) = &string_table {
KeyMode::StringTableIndex { string_table }
} else {
KeyMode::NullTerminated
},
};
loop {
let current_offset = input.len() - rest.len();
if current_offset >= apps_end_offset {
break;
}
if rest.len() < APPINFO_ENTRY_HEADER_SIZE {
return Err(Error::UnexpectedEndOfInput {
context: "reading app entry header",
offset: current_offset,
expected: APPINFO_ENTRY_HEADER_SIZE,
actual: rest.len(),
});
}
let Some(app_id) = read_u32_le(rest) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading app id",
offset: current_offset,
expected: 4,
actual: rest.len(),
});
};
if app_id == 0 {
break;
}
let Some(size) = read_u32_le(&rest[4..]) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading entry size",
offset: current_offset + 4,
expected: 4,
actual: rest.len() - 4,
});
};
let size = size as usize;
let vdf_size = size - APPINFO_HEADER_AFTER_SIZE;
let vdf_end = APPINFO_VDF_DATA_OFFSET + vdf_size;
if vdf_end > rest.len() {
return Err(Error::UnexpectedEndOfInput {
context: "reading VDF data",
offset: current_offset + vdf_end,
expected: vdf_end,
actual: rest.len(),
});
}
let vdf_data = &rest[APPINFO_VDF_DATA_OFFSET..vdf_end];
let vdf_offset = current_offset + APPINFO_VDF_DATA_OFFSET;
let (_vdf_rest, app_obj) =
parse_object(vdf_data, &config).map_err(with_offset(vdf_offset))?;
obj.insert(Cow::Owned(app_id.to_string()), Value::Obj(app_obj));
rest = &rest[vdf_end..];
}
Ok(Vdf::new(
format!("appinfo_universe_{}", universe),
Value::Obj(obj),
))
}
fn parse_object<'a>(input: &'a [u8], config: &ParseConfig<'a, '_>) -> Result<(&'a [u8], Obj<'a>)> {
let mut obj = Obj::new();
let mut rest = input;
loop {
match rest {
[] => {
break Ok((rest, obj));
}
[type_byte, remainder @ ..] => {
let type_byte = *type_byte;
let typ = BinaryType::from_byte(type_byte);
let offset = input.len() - remainder.len();
rest = remainder;
match typ {
Some(BinaryType::ObjectEnd) => {
return Ok((rest, obj));
}
Some(BinaryType::None) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let (new_rest, nested_obj) = parse_object(new_rest, config)?;
obj.insert(key, Value::Obj(nested_obj));
rest = new_rest;
}
Some(BinaryType::String) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) = parse_null_terminated_string_borrowed(new_rest)
.map_err(with_offset(value_offset))?;
obj.insert(key, Value::Str(Cow::Borrowed(value)));
rest = new_rest;
}
Some(BinaryType::Int32) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_int32(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
Some(BinaryType::UInt64) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_uint64(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
Some(BinaryType::Float) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_float(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
Some(BinaryType::Ptr) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_ptr(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
Some(BinaryType::WString) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_wstring(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
Some(BinaryType::Color) => {
let key_offset = input.len() - rest.len();
let (new_rest, key) = config
.key_mode
.parse_key(rest)
.map_err(with_offset(key_offset))?;
let value_offset = input.len() - new_rest.len();
let (new_rest, value) =
parse_value_color(new_rest).map_err(with_offset(value_offset))?;
obj.insert(key, value);
rest = new_rest;
}
None => {
return Err(Error::UnknownType { type_byte, offset });
}
}
}
}
}
}
fn parse_value_int32<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
context: "reading int32",
offset: 0,
expected: 4,
actual: input.len(),
})?)
.map_err(|_| Error::UnexpectedEndOfInput {
context: "reading int32",
offset: 0,
expected: 4,
actual: input.len(),
})?;
let value = i32::from_le_bytes(arr);
Ok((&input[4..], Value::I32(value)))
}
fn parse_value_uint64<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let arr = <[u8; 8]>::try_from(input.get(..8).ok_or(Error::UnexpectedEndOfInput {
context: "reading uint64",
offset: 0,
expected: 8,
actual: input.len(),
})?)
.map_err(|_| Error::UnexpectedEndOfInput {
context: "reading uint64",
offset: 0,
expected: 8,
actual: input.len(),
})?;
let value = u64::from_le_bytes(arr);
Ok((&input[8..], Value::U64(value)))
}
fn parse_value_float<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
context: "reading float",
offset: 0,
expected: 4,
actual: input.len(),
})?)
.map_err(|_| Error::UnexpectedEndOfInput {
context: "reading float",
offset: 0,
expected: 4,
actual: input.len(),
})?;
let value = f32::from_le_bytes(arr);
Ok((&input[4..], Value::Float(value)))
}
fn parse_value_ptr<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let (rest, value) = ensure_read_u32_le(input)?;
Ok((rest, Value::Pointer(value)))
}
fn parse_value_wstring<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let (rest, string) = parse_null_terminated_wstring(input)?;
Ok((rest, Value::Str(Cow::Owned(string))))
}
fn parse_value_color<'a>(input: &'a [u8]) -> Result<(&'a [u8], Value<'a>)> {
let arr = <[u8; 4]>::try_from(input.get(..4).ok_or(Error::UnexpectedEndOfInput {
context: "reading color",
offset: 0,
expected: 4,
actual: input.len(),
})?)
.map_err(|_| Error::UnexpectedEndOfInput {
context: "reading color",
offset: 0,
expected: 4,
actual: input.len(),
})?;
Ok((&input[4..], Value::Color(arr)))
}
fn parse_null_terminated_string_borrowed(input: &[u8]) -> Result<(&[u8], &str)> {
let null_pos = input
.iter()
.position(|&b| b == 0)
.ok_or(Error::UnexpectedEndOfInput {
context: "reading null-terminated string",
offset: 0,
expected: 1,
actual: input.len(),
})?;
let bytes = &input[..null_pos];
let string = core::str::from_utf8(bytes).map_err(|e| Error::InvalidUtf8 {
offset: e.valid_up_to(),
})?;
Ok((&input[null_pos + 1..], string))
}
fn parse_null_terminated_wstring(input: &[u8]) -> Result<(&[u8], String)> {
let mut i = 0;
while i + 1 < input.len() {
if input[i] == 0 && input[i + 1] == 0 {
break;
}
i += 2;
}
if i + 1 >= input.len() {
return Err(Error::UnexpectedEndOfInput {
context: "reading null-terminated wide string",
offset: i,
expected: 2,
actual: input.len().saturating_sub(i),
});
}
let utf16_units = input[..i]
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]));
let string: String = char::decode_utf16(utf16_units)
.enumerate()
.map(|(pos, r)| {
r.map_err(|_| Error::InvalidUtf16 {
offset: pos * 2,
position: pos,
})
})
.collect::<core::result::Result<_, _>>()?;
Ok((&input[i + 2..], string))
}
fn parse_string_table(input: &[u8]) -> Result<StringTable<'_>> {
let (mut rest, string_count) = ensure_read_u32_le(input)?;
let string_count = string_count as usize;
let mut strings = Vec::with_capacity(string_count);
for _ in 0..string_count {
if rest.is_empty() {
return Err(Error::UnexpectedEndOfInput {
context: "reading string table entry",
offset: input.len() - rest.len(),
expected: 1,
actual: 0,
});
}
let (new_rest, string) = parse_null_terminated_string_borrowed(rest)?;
strings.push(string);
rest = new_rest;
}
Ok(StringTable { strings })
}
const 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<'_>> {
if input.len() < 8 {
return Err(Error::UnexpectedEndOfInput {
context: "reading packageinfo header",
offset: input.len(),
expected: 8,
actual: input.len(),
});
}
let Some(magic) = read_u32_le(input) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading magic number",
offset: 0,
expected: 4,
actual: input.len(),
});
};
let version = magic & 0xFF;
let magic_base = magic >> 8;
if magic_base != PACKAGEINFO_MAGIC_BASE {
return Err(Error::InvalidMagic {
found: magic,
expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
});
}
if version != 39 && version != 40 {
return Err(Error::InvalidMagic {
found: magic,
expected: &[PACKAGEINFO_MAGIC_39, PACKAGEINFO_MAGIC_40],
});
}
let Some(universe) = read_u32_le(&input[4..]) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading universe",
offset: 4,
expected: 4,
actual: input.len() - 4,
});
};
let has_token = version >= 40;
let header_size = if has_token {
PACKAGEINFO_ENTRY_HEADER_SIZE_V40
} else {
PACKAGEINFO_ENTRY_HEADER_SIZE_V39
};
let mut rest = &input[8..];
let mut obj = Obj::new();
loop {
if rest.len() < 4 {
break;
}
let Some(package_id) = read_u32_le(rest) else {
break;
};
if package_id == 0xFFFFFFFF {
break;
}
if rest.len() < header_size {
return Err(Error::UnexpectedEndOfInput {
context: "reading package entry header",
offset: input.len() - rest.len(),
expected: header_size,
actual: rest.len(),
});
}
let hash_offset = 4;
let change_number_offset = hash_offset + 20;
let Some(change_number) = read_u32_le(&rest[change_number_offset..]) else {
return Err(Error::UnexpectedEndOfInput {
context: "reading change number",
offset: input.len() - rest.len() + change_number_offset,
expected: 4,
actual: rest.len() - change_number_offset,
});
};
let vdf_data_offset = if has_token {
change_number_offset + 4 + 8
} else {
change_number_offset + 4
};
let vdf_data = &rest[vdf_data_offset..];
let config = ParseConfig::default();
let (_vdf_rest, package_obj) =
parse_object(vdf_data, &config).map_err(with_offset(input.len() - vdf_data.len()))?;
let mut package_with_meta = Obj::new();
package_with_meta.insert(Cow::Borrowed("packageid"), Value::I32(package_id as i32));
package_with_meta.insert(
Cow::Borrowed("change_number"),
Value::U64(change_number as u64),
);
package_with_meta.insert(
Cow::Borrowed("sha1"),
Value::Str(Cow::Owned(hex::encode(
&rest[hash_offset..hash_offset + 20],
))),
);
for (key, value) in package_obj.iter() {
package_with_meta.insert(key.clone(), value.clone());
}
obj.insert(
Cow::Owned(package_id.to_string()),
Value::Obj(package_with_meta),
);
let vdf_end = vdf_data.len() - _vdf_rest.len();
rest = &rest[vdf_data_offset + vdf_end..];
}
Ok(Vdf::new(
format!("packageinfo_universe_{}", universe),
Value::Obj(obj),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_object() {
let data: &[u8] = &[
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, ];
let result = parse_shortcuts(data);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let vdf = result.unwrap();
assert_eq!(vdf.key(), "root");
let obj = vdf.as_obj().unwrap();
let test_obj = obj.get("test").and_then(|v| v.as_obj());
assert!(test_obj.is_some());
let test_obj = test_obj.unwrap();
let value = test_obj.get("key").and_then(|v| v.as_str());
assert_eq!(value, Some("value"));
}
#[test]
fn test_parse_nested_objects() {
let data: &[u8] = &[
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, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let outer = obj.get("outer").and_then(|v| v.as_obj()).unwrap();
let inner = outer.get("inner").and_then(|v| v.as_obj()).unwrap();
let value = inner.get("key").and_then(|v| v.as_str());
assert_eq!(value, Some("value"));
}
#[test]
fn test_parse_int32_value() {
let data: &[u8] = &[
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, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
let value = root.get("number").and_then(|v| v.as_i32());
assert_eq!(value, Some(42));
}
#[test]
fn test_parse_uint64_value() {
let data: &[u8] = &[
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, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
let value = root.get("number").and_then(|v| v.as_u64());
assert_eq!(value, Some(4294967295));
}
#[test]
fn test_parse_float_value() {
let data: &[u8] = &[
0x00, b'r', b'o', b'o', b't', 0x00, 0x03, b'v', b'a', b'l', 0x00, 0x00, 0x00, 0x80, 0x3F, 0x08, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
let value = root.get("val").and_then(|v| v.as_float());
assert_eq!(value, Some(1.0));
}
#[test]
fn test_parse_ptr_value() {
let data: &[u8] = &[
0x00, b'r', b'o', b'o', b't', 0x00, 0x04, b'p', b't', b'r', 0x00, 0xAB, 0xCD, 0xEF, 0x12, 0x08, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
let value = root.get("ptr").and_then(|v| v.as_pointer());
assert_eq!(value, Some(0x12efcdab));
}
#[test]
fn test_parse_color_value() {
let data: &[u8] = &[
0x00, b'r', b'o', b'o', b't', 0x00, 0x06, b'c', b'o', b'l', 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x08, ];
let result = parse_shortcuts(data);
assert!(result.is_ok());
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
let root = obj.get("root").and_then(|v| v.as_obj()).unwrap();
let value = root.get("col").and_then(|v| v.as_color());
assert_eq!(value, Some([255, 0, 0, 255]));
}
#[test]
fn test_parse_unknown_type_byte() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0xFF, b'k', b'e', b'y', 0x00,
];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnknownType {
type_byte: 0xFF,
..
})
));
}
#[test]
fn test_parse_truncated_object_start() {
let data: &[u8] = &[0x00]; assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_truncated_string_value() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00,
];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_invalid_utf8_string() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x01, b'k', b'e', b'y', 0x00, 0xFF, 0xFF, 0x00, ];
assert!(matches!(
parse_shortcuts(data),
Err(Error::InvalidUtf8 { .. })
));
}
#[test]
fn test_parse_truncated_int32_value() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x02, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_truncated_uint64_value() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x07, b'k', b'e', b'y', 0x00, 0x01, 0x02, 0x03, 0x04, ];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_truncated_float_value() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x03, b'k', b'e', b'y', 0x00, 0x01, 0x02, ];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_truncated_color_value() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x06, b'k', b'e', b'y', 0x00, 0xFF, 0x00, ];
assert!(matches!(
parse_shortcuts(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_wstring_unpaired_surrogate() {
let data: &[u8] = &[
0x00, b't', b'e', b's', b't', 0x00, 0x05, b'k', b'e', b'y', 0x00, 0xD8, 0x00, 0x00,
0x00, ];
assert!(parse_shortcuts(data).is_ok());
}
#[test]
fn test_parse_appinfo_invalid_magic() {
let data: &[u8] = &[
0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
assert!(matches!(
parse_appinfo(data),
Err(Error::InvalidMagic { .. })
));
}
#[test]
fn test_parse_appinfo_truncated_header() {
let data: &[u8] = &[
0x28, 0x44, 0x56, 0x07, ];
assert!(matches!(
parse_appinfo(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_appinfo_v41_invalid_string_table_offset() {
let data: &[u8] = &[
0x28, 0x44, 0x56, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, ];
assert!(matches!(
parse_appinfo(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_packageinfo_invalid_magic() {
let data: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00];
assert!(matches!(
parse_packageinfo(data),
Err(Error::InvalidMagic { .. })
));
}
#[test]
fn test_parse_packageinfo_truncated_header() {
let data: &[u8] = &[0x27]; assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_appinfo_with_terminator() {
let data: &[u8] = &[
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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let result = parse_appinfo(data);
if let Err(e) = &result {
panic!("parse_appinfo failed with: {:?}", e);
}
assert!(
result.is_ok(),
"Appinfo with terminator should parse successfully"
);
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 0, "Should have no apps");
}
#[test]
fn test_parse_autodetect_fallback_to_shortcuts() {
let data: &[u8] = &[
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, ];
let result = parse(data);
assert!(result.is_ok());
}
#[test]
fn test_parse_packageinfo_v39_invalid_magic_base() {
let data: &[u8] = &[
0x27, 0xBE, 0xBA, 0xFE, 0x00, 0x00, 0x00, 0x00, ];
assert!(matches!(
parse_packageinfo(data),
Err(Error::InvalidMagic { .. })
));
}
#[test]
fn test_parse_packageinfo_invalid_version() {
let data: &[u8] = &[
0x26, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, ];
assert!(matches!(
parse_packageinfo(data),
Err(Error::InvalidMagic { .. })
));
}
#[test]
fn test_parse_packageinfo_v39_truncated_universe() {
let data: &[u8] = &[
0x27, 0x55, 0x56, 0x06, 0x00, 0x00, ];
assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
#[test]
fn test_parse_packageinfo_v39_with_terminator() {
let data: &[u8] = &[
0x27, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
let result = parse_packageinfo(data);
assert!(
result.is_ok(),
"parse_packageinfo failed: {:?}",
result.err()
);
let vdf = result.unwrap();
assert_eq!(vdf.key(), "packageinfo_universe_1");
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 0, "Should have no packages");
}
#[test]
fn test_parse_packageinfo_v40_with_terminator() {
let data: &[u8] = &[
0x28, 0x55, 0x56, 0x06, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
let result = parse_packageinfo(data);
assert!(
result.is_ok(),
"parse_packageinfo failed: {:?}",
result.err()
);
let vdf = result.unwrap();
assert_eq!(vdf.key(), "packageinfo_universe_1");
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 0, "Should have no packages");
}
#[test]
fn test_parse_packageinfo_v39_truncated_entry_header() {
let data: &[u8] = &[
0x27, 0x55, 0x56, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
];
assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
));
}
#[test]
fn test_parse_packageinfo_v40_truncated_entry_header() {
let data: &[u8] = &[
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,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
];
assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading package entry header"
));
}
#[test]
fn test_parse_packageinfo_v39_with_minimal_vdf() {
let data: &[u8] = &[
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,
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,
];
let result = parse_packageinfo(data);
assert!(
result.is_ok(),
"parse_packageinfo failed: {:?}",
result.err()
);
let vdf = result.unwrap();
assert_eq!(vdf.key(), "packageinfo_universe_0");
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 1);
let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
assert_eq!(package.get("k").and_then(|v| v.as_str()), Some("value"));
}
#[test]
fn test_parse_packageinfo_v40_with_minimal_vdf() {
let data: &[u8] = &[
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,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0x2A, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x02, b'x', 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
];
let result = parse_packageinfo(data);
assert!(
result.is_ok(),
"parse_packageinfo failed: {:?}",
result.err()
);
let vdf = result.unwrap();
assert_eq!(vdf.key(), "packageinfo_universe_0");
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 1);
let package = obj.get("1").and_then(|v| v.as_obj()).unwrap();
assert_eq!(package.get("x").and_then(|v| v.as_i32()), Some(5));
}
#[test]
fn test_parse_packageinfo_multiple_packages() {
let data: &[u8] = &[
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,
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,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, b'a', 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
];
let result = parse_packageinfo(data);
assert!(
result.is_ok(),
"parse_packageinfo failed: {:?}",
result.err()
);
let vdf = result.unwrap();
let obj = vdf.as_obj().unwrap();
assert_eq!(obj.len(), 2);
assert!(obj.get("1").is_some());
assert!(obj.get("2").is_some());
}
#[test]
fn test_parse_packageinfo_empty_input() {
let data: &[u8] = &[];
assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { context, .. }) if context == "reading packageinfo header"
));
}
#[test]
fn test_parse_packageinfo_only_magic() {
let data: &[u8] = &[
0x27, 0x55, 0x56, 0x06, ];
assert!(matches!(
parse_packageinfo(data),
Err(Error::UnexpectedEndOfInput { .. })
));
}
}