use std::collections::HashMap;
use winnow::binary::{le_u8, le_u16};
use winnow::error::StrContext;
use winnow::stream::Location;
use winnow::token::{literal, take};
use winnow::{ModalResult, Parser};
use super::helpers::varint_size;
use super::parser::Input;
const GCOL_SIGNATURE: [u8; 4] = [0x47, 0x43, 0x4F, 0x4C];
#[derive(Debug, Clone)]
pub struct GcolObject {
pub heap_object_index: u16,
pub object_size: u64,
pub value: u64,
}
#[derive(Debug, Clone, Default)]
pub struct GlobalHeap {
objects: HashMap<(u64, u16), GcolObject>,
}
impl GlobalHeap {
pub fn new() -> Self {
Self {
objects: HashMap::new(),
}
}
pub fn get(&self, address: u64, reference: u16) -> Option<&GcolObject> {
self.objects.get(&(address, reference))
}
pub fn insert(&mut self, address: u64, objects: Vec<GcolObject>) {
for obj in objects {
self.objects.insert((address, obj.heap_object_index), obj);
}
}
pub fn has_collection(&self, address: u64) -> bool {
self.objects.keys().any(|(addr, _)| *addr == address)
}
}
pub(crate) fn gcol_read(input: &mut Input) -> ModalResult<Vec<GcolObject>> {
let size_of_lengths = input.state.size_of_lengths();
literal(GCOL_SIGNATURE)
.context(StrContext::Label("GCOL signature"))
.parse_next(input)?;
le_u8
.verify(|v| *v == 1)
.context(StrContext::Label("GCOL version"))
.context(StrContext::Expected("1".into()))
.parse_next(input)?;
take(3usize).parse_next(input)?;
let collection_size = varint_size(size_of_lengths)
.verify(|s| *s <= 0x4_0000_0000) .context(StrContext::Label("GCOL collection size"))
.parse_next(input)?;
let start_pos = input.current_token_start();
let end_pos = start_pos + collection_size as usize - 8;
let mut objects = Vec::new();
while input.current_token_start() + 8 + size_of_lengths as usize <= end_pos {
let heap_object_index = le_u16.parse_next(input)?;
if heap_object_index == 0 {
break;
}
let _reference_count = le_u16.parse_next(input)?;
take(4usize).parse_next(input)?;
let object_size = varint_size(size_of_lengths).parse_next(input)?;
if object_size > 8 {
log::warn!(
"GCOL object {} has size {} > 8, skipping value read",
heap_object_index,
object_size
);
take(object_size as usize).parse_next(input)?;
continue;
}
let value = if object_size > 0 {
varint_size(object_size as u8).parse_next(input)?
} else {
0
};
log::info!(
"GCOL object {} size {} value {:#x}",
heap_object_index,
object_size,
value
);
objects.push(GcolObject {
heap_object_index,
object_size,
value,
});
}
Ok(objects)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_global_heap_lookup() {
let mut heap = GlobalHeap::new();
let objects = vec![
GcolObject {
heap_object_index: 1,
object_size: 4,
value: 0x12345678,
},
GcolObject {
heap_object_index: 2,
object_size: 8,
value: 0xDEADBEEF,
},
];
heap.insert(0x1000, objects);
assert!(heap.has_collection(0x1000));
assert!(!heap.has_collection(0x2000));
let obj1 = heap.get(0x1000, 1).unwrap();
assert_eq!(obj1.value, 0x12345678);
let obj2 = heap.get(0x1000, 2).unwrap();
assert_eq!(obj2.value, 0xDEADBEEF);
assert!(heap.get(0x1000, 3).is_none());
assert!(heap.get(0x2000, 1).is_none());
}
}