use std::collections::BTreeMap;
use std::io::{Read, Seek, SeekFrom};
use crate::catalog::{StoreMeta, StoreLocation};
use crate::VssError;
const BLOCK_SIZE: u64 = 0x4000; const BLOCK_HEADER_SIZE: usize = 128;
const DESCRIPTOR_SIZE: usize = 32;
#[derive(Debug, Clone)]
pub struct StoreInfo {
pub store_id: [u8; 16],
pub volume_size: u64,
pub creation_time: u64,
pub sequence: u64,
pub block_list_offset: u64,
pub store_header_offset: u64,
}
impl StoreInfo {
pub fn from_meta_and_location(meta: &StoreMeta, loc: &StoreLocation) -> Self {
Self {
store_id: meta.store_id,
volume_size: meta.volume_size,
creation_time: meta.creation_time,
sequence: meta.sequence,
block_list_offset: loc.block_list_offset,
store_header_offset: loc.store_header_offset,
}
}
pub fn creation_time_utc(&self) -> String {
if self.creation_time == 0 {
return "unknown".to_string();
}
let secs_since_1601 = self.creation_time / 10_000_000;
let unix_secs = secs_since_1601.saturating_sub(11_644_473_600);
let time_of_day = unix_secs % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
let mut days = (unix_secs / 86400) as i64;
let mut year = 1970i64;
loop {
let days_in_year = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) { 366 } else { 365 };
if days < days_in_year { break; }
days -= days_in_year;
year += 1;
}
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
let month_days = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let mut month = 0usize;
for (i, &md) in month_days.iter().enumerate() {
if days < md as i64 { month = i; break; }
days -= md as i64;
}
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC",
year, month + 1, days + 1, hours, minutes, seconds)
}
}
#[derive(Debug, Clone)]
pub struct BlockDescriptor {
pub original_offset: u64,
pub store_data_offset: u64,
pub flags: u32,
}
pub fn parse_block_descriptors<R: Read + Seek>(
reader: &mut R,
first_block_offset: u64,
) -> Result<BTreeMap<u64, BlockDescriptor>, VssError> {
let mut map = BTreeMap::new();
let mut current_offset = first_block_offset;
loop {
if current_offset == 0 {
break;
}
reader.seek(SeekFrom::Start(current_offset))
.map_err(VssError::Io)?;
let mut block = vec![0u8; BLOCK_SIZE as usize];
reader.read_exact(&mut block).map_err(VssError::Io)?;
let record_type = u32::from_le_bytes(block[20..24].try_into().unwrap());
if record_type != 0x03 {
break;
}
let next_offset = u64::from_le_bytes(block[40..48].try_into().unwrap());
let mut pos = BLOCK_HEADER_SIZE;
while pos + DESCRIPTOR_SIZE <= BLOCK_SIZE as usize {
let desc = &block[pos..pos + DESCRIPTOR_SIZE];
let original_offset = u64::from_le_bytes(desc[0..8].try_into().unwrap());
let _relative_offset = u64::from_le_bytes(desc[8..16].try_into().unwrap());
let store_data_offset = u64::from_le_bytes(desc[16..24].try_into().unwrap());
let flags = u32::from_le_bytes(desc[24..28].try_into().unwrap());
if original_offset == 0 && store_data_offset == 0 {
break;
}
if flags & 0x04 != 0 {
pos += DESCRIPTOR_SIZE;
continue;
}
map.insert(original_offset, BlockDescriptor {
original_offset,
store_data_offset,
flags,
});
pos += DESCRIPTOR_SIZE;
}
current_offset = next_offset;
}
Ok(map)
}