use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(crate) const FORMAT_VERSION: u8 = 1;
pub(crate) const HEADER_LEN: usize = 9;
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum EnvelopeError {
TooShort,
BadVersion(u8),
}
pub(crate) fn encode_envelope(issued_at: SystemTime, payload_json: &[u8]) -> Vec<u8> {
let secs: i64 = match issued_at.duration_since(UNIX_EPOCH) {
Ok(d) => i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
Err(e) => {
let backwards = i64::try_from(e.duration().as_secs()).unwrap_or(i64::MAX);
backwards.checked_neg().unwrap_or(i64::MIN)
}
};
let mut out = Vec::with_capacity(HEADER_LEN + payload_json.len());
out.push(FORMAT_VERSION);
out.extend_from_slice(&secs.to_le_bytes());
out.extend_from_slice(payload_json);
out
}
pub(crate) fn decode_envelope(bytes: &[u8]) -> Result<(SystemTime, Vec<u8>), EnvelopeError> {
if bytes.len() < HEADER_LEN {
return Err(EnvelopeError::TooShort);
}
if bytes[0] != FORMAT_VERSION {
return Err(EnvelopeError::BadVersion(bytes[0]));
}
let secs_bytes: [u8; 8] = bytes[1..HEADER_LEN]
.try_into()
.expect("slice length is HEADER_LEN - 1 == 8, conversion is infallible");
let secs = i64::from_le_bytes(secs_bytes);
let issued_at = if secs >= 0 {
UNIX_EPOCH + Duration::from_secs(secs as u64)
} else {
UNIX_EPOCH - Duration::from_secs(secs.unsigned_abs())
};
let payload = bytes[HEADER_LEN..].to_vec();
Ok((issued_at, payload))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_preserves_issued_at_exactly_ac2_1() {
let issued_at = UNIX_EPOCH + Duration::from_secs(1_700_000_000);
let payload = b"some-payload-bytes";
let encoded = encode_envelope(issued_at, payload);
let (decoded_time, decoded_payload) =
decode_envelope(&encoded).expect("encoded envelope must decode");
assert_eq!(decoded_time, issued_at);
assert_eq!(decoded_payload, payload);
}
#[test]
fn round_trip_preserves_issued_at_across_representative_times_ac2_1() {
const SECS_PER_YEAR: u64 = 365 * 24 * 60 * 60;
let cases = [
UNIX_EPOCH,
UNIX_EPOCH + Duration::from_secs(SECS_PER_YEAR),
UNIX_EPOCH + Duration::from_secs(4_000_000_000),
];
for (i, issued_at) in cases.into_iter().enumerate() {
let encoded = encode_envelope(issued_at, &[]);
let (decoded_time, _) =
decode_envelope(&encoded).expect("encoded envelope must decode");
assert_eq!(decoded_time, issued_at, "case index {i} mismatch");
}
}
#[test]
fn decode_rejects_format_version_2_with_bad_version_ac6_4() {
let bytes = [2u8, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(decode_envelope(&bytes), Err(EnvelopeError::BadVersion(2)));
}
#[test]
fn decode_rejects_other_format_versions_with_bad_version_ac6_4() {
for bad in [0u8, 3, 255] {
let mut bytes = [0u8; HEADER_LEN];
bytes[0] = bad;
assert_eq!(
decode_envelope(&bytes),
Err(EnvelopeError::BadVersion(bad)),
"version {bad} should be rejected"
);
}
}
#[test]
fn encode_places_format_version_at_byte_zero() {
let some_time = UNIX_EPOCH + Duration::from_secs(123_456);
let bytes = encode_envelope(some_time, &[]);
assert_eq!(bytes.len(), HEADER_LEN);
assert_eq!(bytes[0], FORMAT_VERSION);
assert_eq!(bytes[0], 1);
}
#[test]
fn encode_writes_issued_at_little_endian() {
let secs: u64 = 0x0102_0304_0506_0708;
let issued_at = UNIX_EPOCH + Duration::from_secs(secs);
let bytes = encode_envelope(issued_at, &[]);
assert_eq!(
&bytes[1..9],
&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
);
}
#[test]
fn round_trip_passes_payload_through_unchanged() {
let some_time = UNIX_EPOCH + Duration::from_secs(1_700_000_000);
let payload = [42u8, 42, 42];
let encoded = encode_envelope(some_time, &payload);
let (_, decoded_payload) = decode_envelope(&encoded).expect("encoded envelope must decode");
assert_eq!(decoded_payload, payload);
}
#[test]
fn decode_rejects_empty_bytes_with_too_short() {
assert_eq!(decode_envelope(&[]), Err(EnvelopeError::TooShort));
}
#[test]
fn decode_rejects_eight_bytes_with_too_short() {
let bytes = [1u8, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(decode_envelope(&bytes), Err(EnvelopeError::TooShort));
}
#[test]
fn decode_rejects_header_minus_one_bytes_with_too_short() {
let bytes = vec![1u8; HEADER_LEN - 1];
assert_eq!(decode_envelope(&bytes), Err(EnvelopeError::TooShort));
}
#[test]
fn round_trip_empty_payload() {
let issued_at = UNIX_EPOCH + Duration::from_secs(1_234_567_890);
let encoded = encode_envelope(issued_at, &[]);
assert_eq!(encoded.len(), HEADER_LEN);
let (decoded_time, decoded_payload) =
decode_envelope(&encoded).expect("header-only envelope must decode");
assert_eq!(decoded_time, issued_at);
assert_eq!(decoded_payload, Vec::<u8>::new());
}
#[test]
fn round_trip_pre_epoch_time() {
let issued_at = UNIX_EPOCH - Duration::from_secs(1000);
let encoded = encode_envelope(issued_at, &[]);
let (decoded_time, _) = decode_envelope(&encoded).expect("pre-epoch envelope must decode");
assert_eq!(decoded_time, issued_at);
}
#[test]
fn decode_handles_i64_min_seconds_without_panic() {
let mut bytes = [0u8; HEADER_LEN];
bytes[0] = FORMAT_VERSION;
bytes[1..HEADER_LEN].copy_from_slice(&i64::MIN.to_le_bytes());
let (decoded_time, _) =
decode_envelope(&bytes).expect("i64::MIN seconds must not crash decode");
let expected = UNIX_EPOCH - Duration::from_secs(i64::MIN.unsigned_abs());
assert_eq!(decoded_time, expected);
}
}