use super::{HeadError, Meta};
use std::{
borrow::Cow,
str::{self, FromStr},
time::{Duration, SystemTime, UNIX_EPOCH},
};
const CVD_HEAD_MAGIC: &[u8] = b"ClamAV-VDB:";
const CVD_TIMESTAMP_FMT: &[time::format_description::FormatItem] = time::macros::format_description!(
"[day padding:zero] [month repr:short] [year] [hour]:[minute] [offset_hour][offset_minute]"
);
pub struct Header {
version: usize,
n_sigs: usize,
f_level: usize,
dsig: String,
builder: String,
ctime: SystemTime,
is_old_db: bool,
ctime_str: String,
md5_str: String,
}
impl Meta for Header {
fn from_header_bytes(bytes: &[u8; 512]) -> Result<Self, HeadError>
where
Self: std::marker::Sized,
{
let mut fields = bytes
.strip_prefix(CVD_HEAD_MAGIC)
.ok_or(HeadError::BadMagic)?
.split(|b| *b == b':');
let creation_time_str = fields
.next()
.map(str::from_utf8)
.transpose()?
.ok_or(HeadError::MissingCreationTime)?;
let version = fields
.next()
.map(str::from_utf8)
.transpose()?
.ok_or(HeadError::MissingVersion)?
.parse()?;
let n_sigs: usize = fields
.next()
.map(str::from_utf8)
.transpose()?
.ok_or(HeadError::MissingNumberOfSigs)?
.parse()?;
let f_level: usize =
str::from_utf8(fields.next().ok_or(HeadError::MissingFLevel)?)?.parse()?;
let md5_str = fields
.next()
.map(str::from_utf8)
.transpose()?
.ok_or(HeadError::MissingMd5)?
.into();
let dsig = str::from_utf8(fields.next().ok_or(HeadError::MissingDSig)?)?.into();
let builder = std::str::from_utf8(fields.next().ok_or(HeadError::MissingBuilder)?)?.into();
let (ctime, is_old_db) = fields
.next()
.map(str::from_utf8)
.transpose()?
.map(str::trim_end)
.map(usize::from_str)
.transpose()?
.map(|stime| UNIX_EPOCH.checked_add(Duration::from_secs(stime as u64)))
.ok_or(HeadError::STimeTooLarge)?
.map_or_else(
|| {
time::OffsetDateTime::parse(creation_time_str, CVD_TIMESTAMP_FMT)
.map(|odt| (odt.into(), true))
},
|stime| Ok((stime, false)),
)?;
let ctime_str = time::OffsetDateTime::from(ctime)
.format(CVD_TIMESTAMP_FMT)
.expect("format timestamp");
Ok(Self {
version,
n_sigs,
f_level,
dsig,
builder,
ctime,
is_old_db,
ctime_str,
md5_str,
})
}
fn f_level(&self) -> usize {
self.f_level
}
fn n_sigs(&self) -> usize {
self.n_sigs
}
fn time_str(&self) -> std::borrow::Cow<'_, str> {
Cow::from(&self.ctime_str)
}
fn version(&self) -> usize {
self.version
}
fn md5_str(&self) -> std::borrow::Cow<'_, str> {
Cow::from(&self.md5_str)
}
fn dsig_str(&self) -> std::borrow::Cow<'_, str> {
Cow::from(&self.dsig)
}
fn builder(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::from(&self.builder)
}
fn stime(&self) -> u64 {
self.ctime
.duration_since(UNIX_EPOCH)
.expect("compute seconds since epoch")
.as_secs()
}
}
impl Header {
#[must_use]
pub fn is_old_db(&self) -> bool {
self.is_old_db
}
}