const ENTRY_SIZE: usize = 28;
#[derive(Debug, Clone, Copy)]
pub(crate) struct MapEntry {
pub map_offset: u64,
pub length: u64,
pub target_offset: u64,
pub target_id: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Tile {
Unknown,
Unreadable,
}
impl Tile {
pub(crate) fn seed(self) -> &'static [u8] {
match self {
Tile::Unknown => b"UNKNOWN",
Tile::Unreadable => b"UNREADABLEDATA",
}
}
pub(crate) fn byte_at(self, p: u64) -> u8 {
const TILE: u64 = 1024 * 1024;
let seed = self.seed();
seed[((p % TILE) % seed.len() as u64) as usize]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TargetKind {
ImageStream,
Fill(u8),
Tile(Tile),
Unknown,
}
pub(crate) struct LoadedMap {
pub entries: Vec<MapEntry>,
pub targets: Vec<TargetKind>,
pub gap_default: TargetKind,
}
impl LoadedMap {
pub(crate) fn unreadable_regions(&self) -> Vec<(u64, u64)> {
self.entries
.iter()
.filter(|e| {
matches!(
self.targets.get(e.target_id as usize),
Some(TargetKind::Tile(Tile::Unreadable))
)
})
.map(|e| (e.map_offset, e.length))
.collect()
}
}
pub(crate) fn parse_map_entries(data: &[u8]) -> Vec<MapEntry> {
let n = data.len() / ENTRY_SIZE;
let mut entries: Vec<MapEntry> = (0..n)
.map(|i| {
let off = i * ENTRY_SIZE;
MapEntry {
map_offset: u64::from_le_bytes(data[off..off + 8].try_into().expect("slice")),
length: u64::from_le_bytes(data[off + 8..off + 16].try_into().expect("slice")),
target_offset: u64::from_le_bytes(
data[off + 16..off + 24].try_into().expect("slice"),
),
target_id: u32::from_le_bytes(data[off + 24..off + 28].try_into().expect("slice")),
}
})
.filter(|e| e.length > 0)
.collect();
entries.sort_by_key(|e| e.map_offset);
entries
}
pub(crate) fn parse_idx(data: &str, image_stream_arn: &str) -> Vec<TargetKind> {
data.lines()
.filter(|l| !l.trim().is_empty())
.map(|line| classify_target(line.trim(), image_stream_arn))
.collect()
}
fn classify_target(s: &str, image_stream_arn: &str) -> TargetKind {
if s == image_stream_arn {
TargetKind::ImageStream
} else if s.ends_with("#Zero") || s == "aff4:Zero" {
TargetKind::Fill(0x00)
} else if s.ends_with("#UnknownData") {
TargetKind::Tile(Tile::Unknown)
} else if s.ends_with("#UnreadableData") {
TargetKind::Tile(Tile::Unreadable)
} else if let Some(byte) = symbolic_stream_byte(s) {
TargetKind::Fill(byte)
} else {
TargetKind::Unknown
}
}
fn symbolic_stream_byte(s: &str) -> Option<u8> {
let after = s.split("SymbolicStream").nth(1)?;
let hex = after.strip_prefix('#').unwrap_or(after);
if hex.len() == 2 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
u8::from_str_radix(hex, 16).ok()
} else {
None
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ResolvedRegion {
pub kind: TargetKind,
pub target_offset: u64,
pub bytes_in_region: u64,
}
pub(crate) fn resolve(map: &LoadedMap, virtual_pos: u64, virtual_size: u64) -> ResolvedRegion {
let idx = map.entries.partition_point(|e| e.map_offset <= virtual_pos);
if idx > 0 {
let e = &map.entries[idx - 1];
if virtual_pos < e.map_offset + e.length {
let offset_in_entry = virtual_pos - e.map_offset;
let kind = map
.targets
.get(e.target_id as usize)
.copied()
.unwrap_or(TargetKind::Unknown);
return ResolvedRegion {
kind,
target_offset: e.target_offset + offset_in_entry,
bytes_in_region: e.length - offset_in_entry,
};
}
}
let gap_end = map.entries.get(idx).map_or(virtual_size, |e| e.map_offset);
let bytes_in_gap = gap_end.saturating_sub(virtual_pos).max(1);
ResolvedRegion {
kind: map.gap_default,
target_offset: 0,
bytes_in_region: bytes_in_gap,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unreadable_tile_bytes() {
let t = Tile::Unreadable;
assert_eq!(t.seed(), b"UNREADABLEDATA");
let head: Vec<u8> = (0..14).map(|i| t.byte_at(i)).collect();
assert_eq!(head, b"UNREADABLEDATA");
assert_eq!(t.byte_at(1024 * 1024), b'U');
}
#[test]
fn classify_target_variants() {
let arn = "aff4://image-stream";
assert_eq!(classify_target(arn, arn), TargetKind::ImageStream);
assert_eq!(
classify_target("http://aff4.org/Schema#Zero", arn),
TargetKind::Fill(0x00)
);
assert_eq!(
classify_target("http://aff4.org/Schema#SymbolicStream61", arn),
TargetKind::Fill(0x61)
);
assert_eq!(
classify_target("http://aff4.org/Schema#UnknownData", arn),
TargetKind::Tile(Tile::Unknown)
);
assert_eq!(
classify_target("http://aff4.org/Schema#UnreadableData", arn),
TargetKind::Tile(Tile::Unreadable)
);
assert_eq!(
classify_target("aff4://some-other-stream", arn),
TargetKind::Unknown
);
assert_eq!(
classify_target("http://aff4.org/Schema#SymbolicStreamZZ", arn),
TargetKind::Unknown
);
}
#[test]
fn symbolic_stream_byte_afflib_form() {
assert_eq!(
symbolic_stream_byte("http://afflib.org/2012/SymbolicStream#FF"),
Some(0xFF)
);
assert_eq!(symbolic_stream_byte("no symbolic here"), None);
}
#[test]
fn resolve_inside_entry_and_gap_after_entry() {
let map = LoadedMap {
entries: vec![MapEntry {
map_offset: 0,
length: 256,
target_offset: 0,
target_id: 0,
}],
targets: vec![TargetKind::ImageStream],
gap_default: TargetKind::Fill(0x00),
};
let inside = resolve(&map, 100, 1024);
assert_eq!(inside.kind, TargetKind::ImageStream);
assert_eq!(inside.target_offset, 100);
let gap = resolve(&map, 300, 1024);
assert_eq!(gap.kind, TargetKind::Fill(0x00));
}
}