use alloc::borrow::Cow;
use alloc::string::String;
use uuid::Uuid;
use super::file_offset;
use crate::address::FileOffset;
use crate::bytes::{Bytes, decode_ascii, decode_utf16le};
use crate::error::{Error, Result};
macro_rules! heap {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct $name<'a> {
offset: FileOffset,
data: &'a [u8],
}
impl<'a> $name<'a> {
pub fn parse(data: &'a [u8], offset: usize, size: usize) -> Result<Self> {
let tail = Bytes(data).tail(offset)?;
Ok(Self {
offset: file_offset(offset, "metadata heap")?,
data: &tail[..size.min(tail.len())],
})
}
pub fn offset(&self) -> FileOffset {
self.offset
}
pub fn data(&self) -> &'a [u8] {
self.data
}
}
};
}
heap!(
StringsHeap
);
heap!(
UserStringsHeap
);
heap!(
GuidHeap
);
heap!(
BlobHeap
);
impl<'a> StringsHeap<'a> {
pub fn get(&self, index: u32) -> Result<Cow<'a, str>> {
let unterminated = || Error::UnterminatedString {
offset: u64::from(self.offset.get()) + u64::from(index),
};
let tail = self.data.get(index as usize..).ok_or_else(unterminated)?;
let length = tail.iter().position(|&b| b == 0).ok_or_else(unterminated)?;
Ok(decode_ascii(&tail[..length]))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UserString {
pub index: u32,
pub value: String,
}
impl<'a> UserStringsHeap<'a> {
pub fn get(&self, index: u32) -> Result<String> {
let (value, _) = self.entry(index as usize)?;
Ok(value)
}
pub fn iter(&self) -> UserStrings<'a> {
UserStrings {
heap: *self,
position: 1,
}
}
fn entry(&self, position: usize) -> Result<(String, usize)> {
let (length, header) = compressed_length(self.data, position)?;
let start = position + header;
let bytes = Bytes(self.data).slice(start, length)?;
let text = bytes
.get(..length.saturating_sub(1) & !1)
.unwrap_or_default();
Ok((decode_utf16le(text), header + length))
}
}
#[derive(Debug, Clone)]
pub struct UserStrings<'a> {
heap: UserStringsHeap<'a>,
position: usize,
}
impl Iterator for UserStrings<'_> {
type Item = Result<UserString>;
fn next(&mut self) -> Option<Self::Item> {
if self.heap.data.get(self.position).is_none_or(|&b| b == 0) {
return None;
}
let index = self.position as u32;
match self.heap.entry(self.position) {
Ok((value, length)) => {
self.position += length;
Some(Ok(UserString { index, value }))
}
Err(error) => {
self.position = self.heap.data.len();
Some(Err(error))
}
}
}
}
const GUID_SIZE: usize = 16;
impl GuidHeap<'_> {
pub fn get(&self, index: u32) -> Option<Uuid> {
let index = (index as usize).checked_sub(1)?;
self.guids()
.get(index)
.map(|bytes| Uuid::from_bytes_le(*bytes))
}
pub fn iter(&self) -> impl Iterator<Item = Uuid> + '_ {
self.guids().iter().map(|bytes| Uuid::from_bytes_le(*bytes))
}
pub fn len(&self) -> usize {
self.guids().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn guids(&self) -> &[[u8; GUID_SIZE]] {
self.data.as_chunks().0
}
}
impl<'a> BlobHeap<'a> {
pub fn get(&self, index: u32) -> Result<&'a [u8]> {
let (length, header) = compressed_length(self.data, index as usize)?;
Bytes(self.data).slice(index as usize + header, length)
}
}
fn compressed_length(data: &[u8], offset: usize) -> Result<(usize, usize)> {
let bytes = Bytes(data);
let first = bytes.u8(offset)?;
Ok(match first {
0x00..=0x7F => (usize::from(first), 1),
0x80..=0xBF => (
usize::from(u16::from_be_bytes(bytes.array(offset)?) & 0x3FFF),
2,
),
_ => (
(u32::from_be_bytes(bytes.array(offset)?) & 0x1FFF_FFFF) as usize,
4,
),
})
}