use std::time::{Duration, SystemTime, UNIX_EPOCH};
const VERSION: u8 = 1;
pub(crate) const HEADER: usize = 9;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BadEnvelope;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Envelope<'a> {
pub(crate) expires_at: Option<u64>,
pub(crate) payload: &'a [u8],
}
impl Envelope<'_> {
pub(crate) const fn is_expired(&self, now_ms: u64) -> bool {
match self.expires_at {
Some(at) => now_ms == 0 || at <= now_ms,
None => false,
}
}
}
pub(crate) fn encode(payload: &[u8], expires_at: Option<u64>) -> Vec<u8> {
let mut bytes = Vec::with_capacity(HEADER + payload.len());
bytes.push(VERSION);
bytes.extend_from_slice(&expires_at.unwrap_or(0).to_be_bytes());
bytes.extend_from_slice(payload);
bytes
}
pub(crate) const fn decode(bytes: &[u8]) -> Result<Envelope<'_>, BadEnvelope> {
let Some((&VERSION, rest)) = bytes.split_first() else {
return Err(BadEnvelope);
};
let Some((expiry, payload)) = rest.split_first_chunk::<8>() else {
return Err(BadEnvelope);
};
let expires_at = match u64::from_be_bytes(*expiry) {
0 => None,
at => Some(at),
};
Ok(Envelope {
expires_at,
payload,
})
}
pub(crate) fn expiry(now_ms: u64, ttl: Duration) -> u64 {
let ttl_ms = u64::try_from(ttl.as_millis()).unwrap_or(u64::MAX).max(1);
now_ms.saturating_add(ttl_ms)
}
pub(crate) fn keep(bytes: &[u8], now_ms: u64) -> bool {
now_ms == 0 || decode(bytes).map_or(true, |envelope| !envelope.is_expired(now_ms))
}
pub(crate) fn now_ms() -> u64 {
unix_ms(SystemTime::now())
}
pub(crate) fn unix_ms(time: SystemTime) -> u64 {
time.duration_since(UNIX_EPOCH).map_or(0, |since| {
u64::try_from(since.as_millis()).unwrap_or(u64::MAX)
})
}
#[cfg(test)]
mod tests;