use crate::bytes::{le_u16, le_u64, u8_at};
pub const SYS_CHUNK_ARRAY_OFFSET: usize = 0x32b;
pub const DISK_KEY_SIZE: usize = 17;
pub const CHUNK_HEADER_SIZE: usize = 48;
pub const STRIPE_SIZE: usize = 32;
pub const CHUNK_ITEM_KEY: u8 = 228;
pub const BLOCK_GROUP_DATA: u64 = 1 << 0;
pub const BLOCK_GROUP_SYSTEM: u64 = 1 << 1;
pub const BLOCK_GROUP_METADATA: u64 = 1 << 2;
pub const BLOCK_GROUP_DUP: u64 = 1 << 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiskKey {
pub objectid: u64,
pub key_type: u8,
pub offset: u64,
}
impl DiskKey {
#[must_use]
pub fn parse(data: &[u8], off: usize) -> Self {
DiskKey {
objectid: le_u64(data, off),
key_type: u8_at(data, off + 8),
offset: le_u64(data, off + 9),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stripe {
pub devid: u64,
pub offset: u64,
pub dev_uuid: [u8; 16],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SysChunk {
pub key: DiskKey,
pub length: u64,
pub owner: u64,
pub stripe_len: u64,
pub chunk_type: u64,
pub num_stripes: u16,
pub sub_stripes: u16,
pub stripes: Vec<Stripe>,
}
impl SysChunk {
#[must_use]
pub fn logical_to_physical(&self, logical: u64) -> Option<u64> {
let start = self.key.offset;
let end = start.checked_add(self.length)?;
if logical < start || logical >= end {
return None;
}
let stripe = self.stripes.first()?;
let delta = logical - start;
stripe.offset.checked_add(delta)
}
#[must_use]
pub fn parse_array(block: &[u8], array_off: usize, array_size: u32) -> Vec<SysChunk> {
let mut out = Vec::new();
let declared_end = array_off.saturating_add(array_size as usize);
let end = declared_end.min(block.len());
let mut p = array_off;
loop {
let key_end = p.saturating_add(DISK_KEY_SIZE);
let hdr_end = key_end.saturating_add(CHUNK_HEADER_SIZE);
if hdr_end > end {
break;
}
let key = DiskKey::parse(block, p);
let c = key_end; let length = le_u64(block, c);
let owner = le_u64(block, c + 8);
let stripe_len = le_u64(block, c + 16);
let chunk_type = le_u64(block, c + 24);
let num_stripes = le_u16(block, c + 44);
let sub_stripes = le_u16(block, c + 46);
let stripes_start = c.saturating_add(CHUNK_HEADER_SIZE);
let stripes_bytes = usize::from(num_stripes).saturating_mul(STRIPE_SIZE);
let stripes_end = stripes_start.saturating_add(stripes_bytes);
if stripes_end > end {
break;
}
let mut stripes = Vec::with_capacity(usize::from(num_stripes));
let mut s = stripes_start;
for _ in 0..num_stripes {
let devid = le_u64(block, s);
let offset = le_u64(block, s + 8);
let mut dev_uuid = [0u8; 16];
for (i, b) in dev_uuid.iter_mut().enumerate() {
*b = u8_at(block, s + 16 + i);
}
stripes.push(Stripe {
devid,
offset,
dev_uuid,
});
s = s.saturating_add(STRIPE_SIZE);
}
out.push(SysChunk {
key,
length,
owner,
stripe_len,
chunk_type,
num_stripes,
sub_stripes,
stripes,
});
p = stripes_end;
}
out
}
}
#[cfg(test)]
mod unit {
use super::{
DiskKey, Stripe, SysChunk, CHUNK_HEADER_SIZE, DISK_KEY_SIZE, STRIPE_SIZE,
SYS_CHUNK_ARRAY_OFFSET,
};
fn chunk_entry(key_offset: u64, num_stripes: u16, present_stripes: usize) -> Vec<u8> {
let mut e = vec![0u8; DISK_KEY_SIZE + CHUNK_HEADER_SIZE + present_stripes * STRIPE_SIZE];
e[0..8].copy_from_slice(&256u64.to_le_bytes());
e[8] = 228;
e[9..17].copy_from_slice(&key_offset.to_le_bytes());
let c = DISK_KEY_SIZE;
e[c..c + 8].copy_from_slice(&8_388_608u64.to_le_bytes());
e[c + 44..c + 46].copy_from_slice(&num_stripes.to_le_bytes());
e
}
#[test]
fn parse_array_stops_when_stripes_overrun_the_declared_region() {
let entry = chunk_entry(22_020_096, 2, 0);
let mut block = vec![0u8; SYS_CHUNK_ARRAY_OFFSET + entry.len()];
block[SYS_CHUNK_ARRAY_OFFSET..].copy_from_slice(&entry);
let size = entry.len() as u32; let chunks = SysChunk::parse_array(&block, SYS_CHUNK_ARRAY_OFFSET, size);
assert!(chunks.is_empty(), "overrunning stripe count truncates");
}
#[test]
fn logical_to_physical_guards_out_of_span_and_empty_stripes() {
let c = SysChunk {
key: DiskKey {
objectid: 256,
key_type: 228,
offset: 1000,
},
length: 100,
owner: 2,
stripe_len: 65536,
chunk_type: 0x22,
num_stripes: 1,
sub_stripes: 1,
stripes: vec![Stripe {
devid: 1,
offset: 5000,
dev_uuid: [0u8; 16],
}],
};
assert_eq!(c.logical_to_physical(1000), Some(5000), "start maps");
assert_eq!(c.logical_to_physical(1050), Some(5050), "mid maps");
assert_eq!(c.logical_to_physical(999), None, "below span");
assert_eq!(c.logical_to_physical(1100), None, "at/above end");
let mut no_stripes = c.clone();
no_stripes.stripes.clear();
assert_eq!(no_stripes.logical_to_physical(1000), None);
let overflow = SysChunk {
key: DiskKey {
objectid: 256,
key_type: 228,
offset: u64::MAX - 10,
},
length: 100,
..c
};
assert_eq!(overflow.logical_to_physical(u64::MAX - 5), None);
}
}