use crate::{DataDecoder, DataValue};
#[derive(Debug, Clone)]
pub struct DataFormatValidationResult {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub stats: DataFormatStats,
}
impl DataFormatValidationResult {
#[must_use]
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
stats: DataFormatStats::default(),
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.errors.is_empty()
}
pub fn error(&mut self, msg: String) {
self.errors.push(msg);
}
pub fn warning(&mut self, msg: String) {
self.warnings.push(msg);
}
}
impl Default for DataFormatValidationResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct DataFormatStats {
pub strings_checked: u32,
pub maps_checked: u32,
pub arrays_checked: u32,
pub values_checked: u32,
}
pub fn validate_data_value_utf8(
data_section: &[u8],
offset: usize,
base_offset: usize,
) -> Result<u32, String> {
let decoder = DataDecoder::new(data_section, base_offset);
let offset_u32 =
u32::try_from(offset).map_err(|_| format!("Offset {offset} exceeds u32::MAX"))?;
match decoder.decode(offset_u32) {
Ok(value) => validate_value_strings_utf8(&value),
Err(e) => Err(format!("Failed to decode data value: {e}")),
}
}
pub fn validate_value_strings_utf8(value: &DataValue) -> Result<u32, String> {
let mut count = 0u32;
match value {
DataValue::String(_s) => {
count += 1;
}
DataValue::Map(map) => {
for val in map.values() {
count += 1;
count += validate_value_strings_utf8(val)?;
}
}
DataValue::Array(arr) => {
for val in arr {
count += validate_value_strings_utf8(val)?;
}
}
DataValue::Pointer(_)
| DataValue::Double(_)
| DataValue::Bytes(_)
| DataValue::Uint16(_)
| DataValue::Uint32(_)
| DataValue::Int32(_)
| DataValue::Uint64(_)
| DataValue::Uint128(_)
| DataValue::Bool(_)
| DataValue::Float(_)
| DataValue::Timestamp(_) => {}
}
Ok(count)
}
#[must_use]
pub fn validate_data_section(
data_section: &[u8],
base_offset: usize,
offsets_to_check: &[u32],
) -> DataFormatValidationResult {
let mut result = DataFormatValidationResult::new();
if data_section.is_empty() {
result.warning("Data section is empty".to_string());
return result;
}
let decoder = DataDecoder::new(data_section, base_offset);
if offsets_to_check.is_empty() {
result.warning("No specific offsets to validate".to_string());
} else {
for &offset in offsets_to_check {
match decoder.decode(offset) {
Ok(value) => {
result.stats.values_checked += 1;
match validate_value_strings_utf8(&value) {
Ok(count) => {
result.stats.strings_checked += count;
}
Err(e) => {
result.error(format!("Invalid UTF-8 at offset {offset}: {e}"));
}
}
update_stats_for_value(&value, &mut result.stats);
}
Err(e) => {
result.error(format!("Failed to decode at offset {offset}: {e}"));
}
}
}
}
result
}
fn update_stats_for_value(value: &DataValue, stats: &mut DataFormatStats) {
match value {
DataValue::Map(m) => {
stats.maps_checked += 1;
for val in m.values() {
update_stats_for_value(val, stats);
}
}
DataValue::Array(arr) => {
stats.arrays_checked += 1;
for val in arr {
update_stats_for_value(val, stats);
}
}
_ => {}
}
}
pub const MAX_POINTER_DEPTH: usize = 32;
pub const MAX_TOTAL_DEPTH: usize = 64;
#[derive(Debug)]
pub enum PointerValidationError {
Cycle { offset: usize },
DepthExceeded { depth: usize },
InvalidOffset { offset: usize, reason: String },
InvalidType { offset: usize, type_id: u8 },
}
impl std::fmt::Display for PointerValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cycle { offset } => {
write!(f, "Pointer cycle detected at offset {offset}")
}
Self::DepthExceeded { depth } => {
write!(f, "Depth {depth} exceeds limit")
}
Self::InvalidOffset { offset, reason } => {
write!(f, "Invalid offset {offset} ({reason})")
}
Self::InvalidType { offset, type_id } => {
write!(f, "Invalid type {type_id} at offset {offset}")
}
}
}
}
impl std::error::Error for PointerValidationError {}
#[derive(Debug, Clone)]
pub struct PointerValidationResult {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub stats: PointerValidationStats,
}
#[derive(Debug, Clone, Default)]
pub struct PointerValidationStats {
pub pointers_checked: usize,
pub cycles_detected: usize,
pub max_depth: usize,
pub invalid_pointers: usize,
}
impl PointerValidationResult {
#[must_use]
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
stats: PointerValidationStats::default(),
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.errors.is_empty()
}
}
impl Default for PointerValidationResult {
fn default() -> Self {
Self::new()
}
}
pub fn validate_data_value_pointers(
data_section: &[u8],
offset: usize,
path: &mut std::collections::HashSet<usize>,
depth: usize,
) -> Result<usize, PointerValidationError> {
if depth > MAX_TOTAL_DEPTH {
return Err(PointerValidationError::DepthExceeded { depth });
}
if path.contains(&offset) {
return Err(PointerValidationError::Cycle { offset });
}
if offset >= data_section.len() {
return Err(PointerValidationError::InvalidOffset {
offset,
reason: "Offset beyond data section".to_string(),
});
}
path.insert(offset);
let ctrl = data_section[offset];
let type_id = ctrl >> 5;
let payload = ctrl & 0x1F;
let mut cursor = offset + 1;
let mut max_child_depth = depth;
let result = (|| {
match type_id {
0 => {
if cursor >= data_section.len() {
return Err(PointerValidationError::InvalidOffset {
offset,
reason: "Extended type truncated".to_string(),
});
}
let raw_ext_type = data_section[cursor];
cursor += 1;
let ext_type_id = 7 + raw_ext_type;
match ext_type_id {
11 => {
let count = decode_size_for_validation(data_section, &mut cursor, payload)?;
for _ in 0..count {
let child_depth = validate_data_value_pointers(
data_section,
cursor,
path,
depth + 1,
)?;
max_child_depth = max_child_depth.max(child_depth);
cursor = skip_data_value(data_section, cursor)?;
}
}
8 | 9 | 10 | 14 | 15 => {
}
_ => {
return Err(PointerValidationError::InvalidType {
offset,
type_id: ext_type_id,
});
}
}
}
1 => {
let pointer_offset = decode_pointer_offset(data_section, &mut cursor, payload)?;
if pointer_offset >= data_section.len() {
return Err(PointerValidationError::InvalidOffset {
offset: pointer_offset,
reason: "Pointer target beyond data section".to_string(),
});
}
let child_depth =
validate_data_value_pointers(data_section, pointer_offset, path, depth + 1)?;
max_child_depth = max_child_depth.max(child_depth);
}
2..=6 => {
}
7 => {
let count = decode_size_for_validation(data_section, &mut cursor, payload)?;
for _ in 0..count {
cursor = skip_data_value(data_section, cursor)?;
let child_depth =
validate_data_value_pointers(data_section, cursor, path, depth + 1)?;
max_child_depth = max_child_depth.max(child_depth);
cursor = skip_data_value(data_section, cursor)?;
}
}
_ => {
return Err(PointerValidationError::InvalidType { offset, type_id });
}
}
Ok(max_child_depth)
})();
path.remove(&offset);
result
}
fn decode_size_for_validation(
data: &[u8],
cursor: &mut usize,
size_bits: u8,
) -> Result<usize, PointerValidationError> {
match size_bits {
0..=28 => Ok(size_bits as usize),
29 => {
if *cursor >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Size byte out of bounds".to_string(),
});
}
let size = data[*cursor] as usize;
*cursor += 1;
Ok(29 + size)
}
30 => {
if *cursor + 2 > data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Size bytes out of bounds".to_string(),
});
}
let size = u16::from_be_bytes([data[*cursor], data[*cursor + 1]]) as usize;
*cursor += 2;
Ok(29 + 256 + size)
}
31 => {
if *cursor + 3 > data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Size bytes out of bounds".to_string(),
});
}
let b0 = data[*cursor] as usize;
let b1 = data[*cursor + 1] as usize;
let b2 = data[*cursor + 2] as usize;
*cursor += 3;
Ok(29 + 256 + 65536 + ((b0 << 16) | (b1 << 8) | b2))
}
_ => Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Invalid size encoding".to_string(),
}),
}
}
fn decode_pointer_offset(
data: &[u8],
cursor: &mut usize,
payload: u8,
) -> Result<usize, PointerValidationError> {
let size_bits = (payload >> 3) & 0x3;
let offset = match size_bits {
0 => {
if *cursor >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Pointer data truncated".to_string(),
});
}
let low_3_bits = (payload & 0x7) as usize;
let next_byte = data[*cursor] as usize;
*cursor += 1;
(low_3_bits << 8) | next_byte
}
1 => {
if *cursor + 1 >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Pointer data truncated".to_string(),
});
}
let low_3_bits = (payload & 0x7) as usize;
let b0 = data[*cursor] as usize;
let b1 = data[*cursor + 1] as usize;
*cursor += 2;
2048 + ((low_3_bits << 16) | (b0 << 8) | b1)
}
2 => {
if *cursor + 2 >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Pointer data truncated".to_string(),
});
}
let low_3_bits = (payload & 0x7) as usize;
let b0 = data[*cursor] as usize;
let b1 = data[*cursor + 1] as usize;
let b2 = data[*cursor + 2] as usize;
*cursor += 3;
526336 + ((low_3_bits << 24) | (b0 << 16) | (b1 << 8) | b2)
}
3 => {
if *cursor + 3 >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Pointer data truncated".to_string(),
});
}
let b0 = data[*cursor] as usize;
let b1 = data[*cursor + 1] as usize;
let b2 = data[*cursor + 2] as usize;
let b3 = data[*cursor + 3] as usize;
*cursor += 4;
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}
_ => {
return Err(PointerValidationError::InvalidOffset {
offset: *cursor,
reason: "Invalid pointer size bits".to_string(),
});
}
};
Ok(offset)
}
fn skip_data_value(data: &[u8], offset: usize) -> Result<usize, PointerValidationError> {
if offset >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset,
reason: "Offset beyond data".to_string(),
});
}
let ctrl = data[offset];
let type_id = ctrl >> 5;
let payload = ctrl & 0x1F;
let mut cursor = offset + 1;
match type_id {
0 => {
if cursor >= data.len() {
return Err(PointerValidationError::InvalidOffset {
offset,
reason: "Extended type truncated".to_string(),
});
}
cursor += 1; let size = decode_size_for_validation(data, &mut cursor, payload)?;
Ok(cursor + size)
}
1 => {
let size_bits = (payload >> 3) & 0x3;
let ptr_size = match size_bits {
0 => 1,
1 => 2,
2 => 3,
3 => 4,
_ => 0,
};
Ok(cursor + ptr_size)
}
2 | 4 => {
let size = decode_size_for_validation(data, &mut cursor, payload)?;
Ok(cursor + size)
}
3 => Ok(cursor + 8), 5 => {
let size = decode_size_for_validation(data, &mut cursor, payload)?;
Ok(cursor + size.min(2))
}
6 => {
let size = decode_size_for_validation(data, &mut cursor, payload)?;
Ok(cursor + size.min(4))
}
7 => {
let count = decode_size_for_validation(data, &mut cursor, payload)?;
for _ in 0..count {
cursor = skip_data_value(data, cursor)?; cursor = skip_data_value(data, cursor)?; }
Ok(cursor)
}
_ => Err(PointerValidationError::InvalidType { offset, type_id }),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DataEncoder;
use std::collections::HashMap;
#[test]
fn test_validate_simple_string() {
let mut encoder = DataEncoder::new();
let value = DataValue::String("test".to_string());
let offset = encoder.encode(&value);
let data = encoder.into_bytes();
let count = validate_data_value_utf8(&data, offset as usize, 0).unwrap();
assert_eq!(count, 1);
}
#[test]
fn test_validate_map_with_strings() {
let mut encoder = DataEncoder::new();
let mut map = HashMap::new();
map.insert("key1".to_string(), DataValue::String("value1".to_string()));
map.insert("key2".to_string(), DataValue::String("value2".to_string()));
map.insert("num".to_string(), DataValue::Uint32(42));
let value = DataValue::Map(map);
let offset = encoder.encode(&value);
let data = encoder.into_bytes();
let count = validate_data_value_utf8(&data, offset as usize, 0).unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_validate_nested_structure() {
let mut encoder = DataEncoder::new();
let mut inner_map = HashMap::new();
inner_map.insert("inner".to_string(), DataValue::String("nested".to_string()));
let mut outer_map = HashMap::new();
outer_map.insert("outer".to_string(), DataValue::String("top".to_string()));
outer_map.insert("nested".to_string(), DataValue::Map(inner_map));
let value = DataValue::Map(outer_map);
let offset = encoder.encode(&value);
let data = encoder.into_bytes();
let count = validate_data_value_utf8(&data, offset as usize, 0).unwrap();
assert_eq!(count, 5);
}
#[test]
fn test_validate_array_with_strings() {
let mut encoder = DataEncoder::new();
let value = DataValue::Array(vec![
DataValue::String("a".to_string()),
DataValue::String("b".to_string()),
DataValue::Uint32(123),
]);
let offset = encoder.encode(&value);
let data = encoder.into_bytes();
let count = validate_data_value_utf8(&data, offset as usize, 0).unwrap();
assert_eq!(count, 2); }
#[test]
fn test_validate_data_section() {
let mut encoder = DataEncoder::new();
let value1 = DataValue::String("first".to_string());
let value2 = DataValue::String("second".to_string());
let offset1 = encoder.encode(&value1);
let offset2 = encoder.encode(&value2);
let data = encoder.into_bytes();
let result = validate_data_section(&data, 0, &[offset1, offset2]);
assert!(result.is_valid());
assert_eq!(result.stats.values_checked, 2);
assert_eq!(result.stats.strings_checked, 2);
}
#[test]
fn test_validate_invalid_offset() {
let mut encoder = DataEncoder::new();
encoder.encode(&DataValue::String("test".to_string()));
let data = encoder.into_bytes();
let result = validate_data_section(&data, 0, &[999]);
assert!(!result.is_valid());
assert!(!result.errors.is_empty());
}
#[test]
fn test_validate_empty_data_section() {
let data: Vec<u8> = Vec::new();
let result = validate_data_section(&data, 0, &[]);
assert!(result.is_valid());
assert_eq!(result.warnings.len(), 1);
}
}