use super::FH_BLOCK_SIZE;
use crate::{
Result,
blocks::common::{
BlockHeader, BlockParse, debug_assert_aligned, read_u8, read_u64, validate_buffer_size,
},
};
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone)]
pub struct FileHistoryBlock {
pub header: BlockHeader,
pub next_fh_addr: u64,
pub comment_addr: u64,
pub time_ns: u64,
pub tz_offset_min: i16,
pub dst_offset_min: i16,
pub time_flags: u8,
}
impl BlockParse<'_> for FileHistoryBlock {
const ID: &'static str = "##FH";
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let header = Self::parse_header(bytes)?;
validate_buffer_size(bytes, FH_BLOCK_SIZE)?;
Ok(Self {
header,
next_fh_addr: read_u64(bytes, 24),
comment_addr: read_u64(bytes, 32),
time_ns: read_u64(bytes, 40),
tz_offset_min: i16::from_le_bytes([bytes[48], bytes[49]]),
dst_offset_min: i16::from_le_bytes([bytes[50], bytes[51]]),
time_flags: read_u8(bytes, 52),
})
}
}
impl FileHistoryBlock {
pub fn new(time_ns: u64) -> Self {
Self {
header: BlockHeader {
id: String::from("##FH"),
reserved: 0,
length: FH_BLOCK_SIZE as u64,
link_count: 2,
},
next_fh_addr: 0,
comment_addr: 0,
time_ns,
tz_offset_min: 0,
dst_offset_min: 0,
time_flags: 0,
}
}
#[cfg(feature = "std")]
pub fn now() -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let time_ns = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
Self::new(time_ns)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut buffer = Vec::with_capacity(FH_BLOCK_SIZE);
buffer.extend_from_slice(&self.header.to_bytes()?);
buffer.extend_from_slice(&self.next_fh_addr.to_le_bytes());
buffer.extend_from_slice(&self.comment_addr.to_le_bytes());
buffer.extend_from_slice(&self.time_ns.to_le_bytes());
buffer.extend_from_slice(&self.tz_offset_min.to_le_bytes());
buffer.extend_from_slice(&self.dst_offset_min.to_le_bytes());
buffer.push(self.time_flags);
buffer.extend_from_slice(&[0u8; 3]);
debug_assert_aligned(buffer.len());
Ok(buffer)
}
}
impl Default for FileHistoryBlock {
fn default() -> Self {
Self::new(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let original = FileHistoryBlock {
header: BlockHeader {
id: String::from("##FH"),
reserved: 0,
length: FH_BLOCK_SIZE as u64,
link_count: 2,
},
next_fh_addr: 0x1000,
comment_addr: 0x2000,
time_ns: 1_704_067_200_000_000_000, tz_offset_min: 60,
dst_offset_min: 60,
time_flags: 0x03,
};
let bytes = original.to_bytes().unwrap();
assert_eq!(bytes.len(), FH_BLOCK_SIZE);
let parsed = FileHistoryBlock::from_bytes(&bytes).unwrap();
assert_eq!(parsed.next_fh_addr, original.next_fh_addr);
assert_eq!(parsed.comment_addr, original.comment_addr);
assert_eq!(parsed.time_ns, original.time_ns);
assert_eq!(parsed.tz_offset_min, original.tz_offset_min);
assert_eq!(parsed.dst_offset_min, original.dst_offset_min);
assert_eq!(parsed.time_flags, original.time_flags);
}
#[test]
fn default_values() {
let block = FileHistoryBlock::default();
assert_eq!(block.header.id, "##FH");
assert_eq!(block.header.link_count, 2);
assert_eq!(block.next_fh_addr, 0);
assert_eq!(block.comment_addr, 0);
}
}