use std::borrow::Cow;
use std::fmt;
use std::sync::OnceLock;
use crate::constants::{
DESCRIPTOR_SIZE, ENTRY_HEADER_SIZE, SECTOR_SIZE, SIGNATURE_DESC, SIGNATURE_LOGE, SIGNATURE_ZERO,
};
use crate::error::{Error, Result, SignaturePosition};
use crate::types::{Crc32c, Guid};
#[derive(Clone, Copy)]
pub struct Log<'a> {
data: &'a [u8],
}
impl<'a> Log<'a> {
pub(crate) fn new(data: &'a [u8]) -> Result<Self> {
if !data.len().is_multiple_of(SECTOR_SIZE as usize) {
return Err(Error::LogEntryCorrupted(
"log buffer size is not a multiple of 4KB".into(),
));
}
Ok(Self { data })
}
#[must_use]
pub(crate) fn len(&self) -> usize {
self.data.len()
}
#[must_use]
#[cfg(test)]
pub(crate) fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn entry(&self, index: usize) -> Result<Entry<'_>> {
let mut offset: usize = 0;
for i in 0..=index {
if offset >= self.data.len() {
return Err(Error::InvalidParameter(format!(
"log entry index {index} out of bounds"
)));
}
if i == index {
return self.parse_entry_at(offset);
}
let entry_length = u32_at(&self.data[offset + 8..offset + 12]).ok_or_else(|| {
Error::LogEntryCorrupted("log buffer too small for entry header".into())
})?;
if entry_length == 0 || !(entry_length as usize).is_multiple_of(SECTOR_SIZE as usize) {
return Err(Error::LogEntryCorrupted(format!(
"invalid entry length {entry_length} at index {i}"
)));
}
offset += entry_length as usize;
}
Err(Error::InvalidParameter(format!(
"log entry index {index} not found"
)))
}
pub(crate) fn entry_at(&self, offset: usize) -> Result<Entry<'_>> {
self.parse_entry_at(offset)
}
pub fn entries(&self) -> impl Iterator<Item = Entry<'_>> + '_ {
LogEntryIter {
log: self,
offset: 0,
done: false,
}
}
fn parse_entry_at(&self, offset: usize) -> Result<Entry<'_>> {
if offset + ENTRY_HEADER_SIZE as usize > self.data.len() {
return Err(Error::LogEntryCorrupted(
"insufficient data for log entry header".into(),
));
}
let entry_data = &self.data[offset..];
let sig = &entry_data[0..4];
if sig != SIGNATURE_LOGE.into_inner().to_le_bytes() {
let mut found = [0u8; 4];
found.copy_from_slice(sig);
return Err(Error::InvalidSignature {
position: SignaturePosition::LogEntry,
expected: crate::error::pad_signature_4to8(
SIGNATURE_LOGE.into_inner().to_le_bytes(),
),
found: crate::error::pad_signature_4to8(found),
});
}
let entry_length = u32_at(&entry_data[8..12])
.ok_or_else(|| Error::LogEntryCorrupted("entry_length read failed".into()))?;
if entry_length == 0 || !(entry_length as usize).is_multiple_of(SECTOR_SIZE as usize) {
return Err(Error::LogEntryCorrupted(format!(
"entry length {entry_length} is not a multiple of 4KB"
)));
}
let total = entry_length as usize;
if offset + total > self.data.len() {
return Err(Error::LogEntryCorrupted(format!(
"entry extends beyond log buffer (offset={offset}, length={total}, buf={})",
self.data.len()
)));
}
Ok(Entry {
data: &entry_data[..total],
assembled_sectors: OnceLock::new(),
})
}
}
struct LogEntryIter<'a> {
log: &'a Log<'a>,
offset: usize,
done: bool,
}
impl<'a> Iterator for LogEntryIter<'a> {
type Item = Entry<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.offset >= self.log.data.len() {
return None;
}
if self.offset + ENTRY_HEADER_SIZE as usize > self.log.data.len() {
self.done = true;
return None;
}
let remaining = &self.log.data[self.offset..];
if remaining[0..4] != SIGNATURE_LOGE.into_inner().to_le_bytes() {
self.done = true;
return None;
}
if let Ok(entry) = self.log.parse_entry_at(self.offset) {
let entry_length = entry.header().entry_length() as usize;
self.offset += entry_length;
Some(entry)
} else {
self.done = true;
None
}
}
}
#[derive(Debug)]
pub struct Entry<'a> {
data: &'a [u8],
assembled_sectors: OnceLock<Vec<OnceLock<[u8; SECTOR_SIZE as usize]>>>,
}
impl<'a> Entry<'a> {
pub fn header(&self) -> LogEntryHeader<'_> {
LogEntryHeader {
data: &self.data[..ENTRY_HEADER_SIZE as usize],
}
}
pub fn descriptor(&self, index: usize) -> Result<Descriptor<'_>> {
let desc_count = self.header().descriptor_count() as usize;
if index >= desc_count {
return Err(Error::InvalidParameter(format!(
"descriptor index {index} out of range (count={desc_count})"
)));
}
let raw = self.descriptor_bytes(index)?;
let sig = &raw[0..4];
if sig == SIGNATURE_DESC.into_inner().to_le_bytes() {
Ok(Descriptor::Data(DataDescriptor { data: raw }))
} else if sig == SIGNATURE_ZERO.into_inner().to_le_bytes() {
Ok(Descriptor::Zero(ZeroDescriptor { data: raw }))
} else {
let mut found = [0u8; 4];
found.copy_from_slice(sig);
Err(Error::LogEntryCorrupted(format!(
"LOG_DESCRIPTOR_SIGNATURE_INVALID: unknown signature {found:?} at descriptor {index}"
)))
}
}
pub fn descriptors(&self) -> impl Iterator<Item = Result<Descriptor<'_>>> + '_ {
let count = self.header().descriptor_count() as usize;
(0..count).map(|i| self.descriptor(i))
}
pub fn data(&self) -> impl Iterator<Item = DataSector<'_>> + '_ {
let caches = self.assembled_sectors.get_or_init(|| {
let desc_count = self.header().descriptor_count() as usize;
let mut data_count = 0;
for di in 0..desc_count {
if let Ok(Descriptor::Data(_)) = self.descriptor(di) {
data_count += 1;
}
}
(0..data_count).map(|_| OnceLock::new()).collect()
});
let desc_count = self.header().descriptor_count() as usize;
let desc_sectors = if desc_count == 0 {
1 } else {
let after_first = desc_count.saturating_sub(126);
1 + after_first.div_ceil(128)
};
let data_offset = desc_sectors * SECTOR_SIZE as usize;
let entry_length = self.data.len();
let raw_data = self.data;
let mut data_indices: Vec<usize> = Vec::new();
for di in 0..desc_count {
if let Ok(Descriptor::Data(_)) = self.descriptor(di) {
data_indices.push(di);
}
}
data_indices
.into_iter()
.enumerate()
.filter_map(move |(sector_idx, di)| {
let sector_start = data_offset + sector_idx * SECTOR_SIZE as usize;
if sector_start + SECTOR_SIZE as usize > entry_length {
return None;
}
let Ok(Descriptor::Data(desc)) = self.descriptor(di) else {
return None;
};
Some(DataSector {
data: &raw_data[sector_start..sector_start + SECTOR_SIZE as usize],
leading_bytes: desc.leading_bytes_raw(),
trailing_bytes: desc.trailing_bytes_raw(),
cache: &caches[sector_idx],
})
})
}
pub(crate) fn verify_checksum(&self) -> Result<()> {
let stored = self.header().checksum();
let computed = Crc32c::from_raw(crate::common::crc32c_zeroed_checksum(self.data));
if computed != stored {
return Err(Error::InvalidChecksum {
expected: stored.value(),
actual: computed.value(),
});
}
Ok(())
}
fn descriptor_bytes(&self, index: usize) -> Result<&'a [u8]> {
let abs_offset = if index < 126 {
ENTRY_HEADER_SIZE as usize + index * DESCRIPTOR_SIZE as usize
} else {
let remaining = index - 126;
let sector_index = remaining / 128;
let within_sector = remaining % 128;
SECTOR_SIZE as usize * (1 + sector_index) + within_sector * DESCRIPTOR_SIZE as usize
};
if abs_offset + DESCRIPTOR_SIZE as usize > self.data.len() {
return Err(Error::LogEntryCorrupted(format!(
"descriptor {index} at offset {abs_offset} extends beyond entry"
)));
}
Ok(&self.data[abs_offset..abs_offset + DESCRIPTOR_SIZE as usize])
}
}
pub struct LogEntryHeader<'a> {
data: &'a [u8],
}
impl<'a> LogEntryHeader<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[0..4].try_into().expect("header is 64 bytes")
}
#[must_use]
pub fn checksum(&self) -> Crc32c {
Crc32c::from_raw(u32_at(&self.data[4..8]).unwrap_or(0))
}
#[must_use]
pub fn entry_length(&self) -> u32 {
u32_at(&self.data[8..12]).unwrap_or(0)
}
#[must_use]
pub fn tail(&self) -> u32 {
u32_at(&self.data[12..16]).unwrap_or(0)
}
#[must_use]
pub fn sequence_number(&self) -> u64 {
u64_at(&self.data[16..24]).unwrap_or(0)
}
#[must_use]
pub fn descriptor_count(&self) -> u32 {
u32_at(&self.data[24..28]).unwrap_or(0)
}
#[must_use]
pub fn reserved(&self) -> u32 {
u32_at(&self.data[28..32]).unwrap_or(0)
}
#[must_use]
pub fn log_guid(&self) -> Guid {
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&self.data[32..48]);
Guid::from_bytes(bytes)
}
#[must_use]
pub fn flushed_file_offset(&self) -> u64 {
u64_at(&self.data[48..56]).unwrap_or(0)
}
#[must_use]
pub fn last_file_offset(&self) -> u64 {
u64_at(&self.data[56..64]).unwrap_or(0)
}
}
pub enum Descriptor<'a> {
Data(DataDescriptor<'a>),
Zero(ZeroDescriptor<'a>),
}
impl Descriptor<'_> {
#[must_use]
pub(crate) fn sequence_number(&self) -> u64 {
match self {
Descriptor::Data(d) => d.sequence_number(),
Descriptor::Zero(z) => z.sequence_number(),
}
}
}
impl fmt::Debug for Descriptor<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Descriptor::Data(d) => f
.debug_struct("Descriptor::Data")
.field("file_offset", &d.file_offset())
.field("sequence_number", &d.sequence_number())
.finish(),
Descriptor::Zero(z) => f
.debug_struct("Descriptor::Zero")
.field("file_offset", &z.file_offset())
.field("zero_length", &z.zero_length())
.field("sequence_number", &z.sequence_number())
.finish(),
}
}
}
pub struct DataDescriptor<'a> {
data: &'a [u8],
}
impl<'a> DataDescriptor<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[0..4].try_into().expect("descriptor is 32 bytes")
}
#[must_use]
pub fn trailing_bytes(&self) -> u32 {
u32_at(&self.data[4..8]).unwrap_or(0)
}
#[must_use]
pub fn leading_bytes(&self) -> u64 {
u64_at(&self.data[8..16]).unwrap_or(0)
}
#[must_use]
pub fn file_offset(&self) -> u64 {
u64_at(&self.data[16..24]).unwrap_or(0)
}
#[must_use]
pub fn sequence_number(&self) -> u64 {
u64_at(&self.data[24..32]).unwrap_or(0)
}
#[must_use]
pub(crate) fn leading_bytes_raw(&self) -> &'a [u8] {
&self.data[8..16]
}
#[must_use]
pub(crate) fn trailing_bytes_raw(&self) -> &'a [u8] {
&self.data[4..8]
}
}
pub struct ZeroDescriptor<'a> {
data: &'a [u8],
}
impl<'a> ZeroDescriptor<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[0..4].try_into().expect("descriptor is 32 bytes")
}
#[must_use]
pub fn reserved(&self) -> u32 {
u32_at(&self.data[4..8]).unwrap_or(0)
}
#[must_use]
pub fn zero_length(&self) -> u64 {
u64_at(&self.data[8..16]).unwrap_or(0)
}
#[must_use]
pub fn file_offset(&self) -> u64 {
u64_at(&self.data[16..24]).unwrap_or(0)
}
#[must_use]
pub fn sequence_number(&self) -> u64 {
u64_at(&self.data[24..32]).unwrap_or(0)
}
}
pub struct DataSector<'a> {
pub(super) data: &'a [u8],
leading_bytes: &'a [u8],
trailing_bytes: &'a [u8],
cache: &'a OnceLock<[u8; SECTOR_SIZE as usize]>,
}
impl<'a> DataSector<'a> {
#[must_use]
pub fn signature(&self) -> &'a [u8; 4] {
self.data[0..4]
.try_into()
.expect("data sector is 4096 bytes")
}
#[must_use]
pub fn sequence_number(&self) -> u64 {
let high = u32::from_le_bytes(self.data[4..8].try_into().unwrap_or([0; 4]));
let low = u32::from_le_bytes(self.data[4092..4096].try_into().unwrap_or([0; 4]));
(u64::from(high) << 32) | u64::from(low)
}
#[must_use]
pub fn data(&self) -> Cow<'a, [u8]> {
let assembled = self.cache.get_or_init(|| {
let mut buf = [0u8; SECTOR_SIZE as usize];
buf[0..8].copy_from_slice(&self.leading_bytes[..8]);
buf[8..4092].copy_from_slice(&self.data[8..4092]);
buf[4092..4096].copy_from_slice(&self.trailing_bytes[..4]);
buf
});
Cow::Borrowed(assembled)
}
}
#[cfg(test)]
pub(crate) struct DataSectorAssembly {
buf: [u8; SECTOR_SIZE as usize],
}
#[cfg(test)]
impl DataSectorAssembly {
pub fn new(descriptor: &DataDescriptor<'_>, sector: &DataSector<'_>) -> Self {
let mut buf = [0u8; SECTOR_SIZE as usize];
buf[0..8].copy_from_slice(descriptor.leading_bytes_raw());
buf[8..4092].copy_from_slice(§or.data[8..4092]);
buf[4092..4096].copy_from_slice(descriptor.trailing_bytes_raw());
Self { buf }
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.buf
}
}
fn u32_at(buf: &[u8]) -> Option<u32> {
if buf.len() < 4 {
return None;
}
Some(u32::from_le_bytes(buf[..4].try_into().unwrap()))
}
fn u64_at(buf: &[u8]) -> Option<u64> {
if buf.len() < 8 {
return None;
}
Some(u64::from_le_bytes(buf[..8].try_into().unwrap()))
}