pub(crate) const RECORD_RAW_MASK: u64 = (1u64 << 40) - 1;
pub(crate) const LEN_MAX: u64 = (u32::MAX as u64) + 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum JournalTag {
Empty = 0,
SetLen = 1,
SetBit = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct DecodeError;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct JournalRecord(pub(crate) [u8; 5]);
impl JournalRecord {
#[inline]
pub(crate) fn set_len(len: u64) -> Self {
debug_assert!(
len <= LEN_MAX,
"JournalRecord::set_len: len must be validated at API boundary"
);
let payload_lo = len as u32;
let len_hi = ((len >> 32) & 1) as u32;
Self::pack_fields(JournalTag::SetLen, false, payload_lo, len_hi)
}
#[inline]
pub(crate) fn set_bit(index: u32, value: bool) -> Self {
Self::pack_fields(JournalTag::SetBit, value, index, 0)
}
#[inline]
fn pack_fields(tag: JournalTag, value: bool, payload_lo: u32, len_hi: u32) -> Self {
let raw = (payload_lo as u64)
| (((len_hi & 1) as u64) << 32)
| (((value as u64) & 1) << 37)
| (((tag as u64) & 3) << 38);
Self::from_raw(raw)
}
#[inline]
fn from_raw(raw: u64) -> Self {
let bytes = (raw & RECORD_RAW_MASK).to_le_bytes();
Self([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]])
}
#[inline]
pub(crate) fn raw(self) -> u64 {
u64::from_le_bytes([
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], 0, 0, 0,
])
}
#[inline]
pub(crate) fn unpack(self) -> Result<(JournalTag, bool, u64), DecodeError> {
let raw = self.raw();
if raw == 0 {
return Ok((JournalTag::Empty, false, 0));
}
if (raw >> 33) & 0xF != 0 {
return Err(DecodeError);
}
let tag = match (raw >> 38) & 3 {
1 => JournalTag::SetLen,
2 => JournalTag::SetBit,
_ => return Err(DecodeError),
};
let value = ((raw >> 37) & 1) != 0;
let len_hi = (raw >> 32) & 1;
let payload_lo = raw as u32;
let payload = match tag {
JournalTag::SetLen => (len_hi << 32) | u64::from(payload_lo),
JournalTag::SetBit => {
if len_hi != 0 {
return Err(DecodeError);
}
u64::from(payload_lo)
}
JournalTag::Empty => unreachable!(),
};
Ok((tag, value, payload))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructors_round_trip_at_representation_boundaries() {
for len in [0, 1, u32::MAX as u64, LEN_MAX] {
assert_eq!(
JournalRecord::set_len(len).unpack(),
Ok((JournalTag::SetLen, false, len))
);
}
for index in [0, 1, u32::MAX] {
for value in [false, true] {
assert_eq!(
JournalRecord::set_bit(index, value).unpack(),
Ok((JournalTag::SetBit, value, u64::from(index)))
);
}
}
}
#[test]
fn raw_conversion_is_exact_little_endian() {
let record = JournalRecord([0x01, 0x23, 0x45, 0x67, 0x89]);
assert_eq!(record.raw(), 0x0089_6745_2301);
assert_eq!(JournalRecord::from_raw(record.raw()), record);
}
#[test]
fn unpack_rejects_reserved_unknown_tag_and_set_bit_high_length_bit() {
assert_eq!(JournalRecord::from_raw(1 << 33).unpack(), Err(DecodeError));
assert_eq!(JournalRecord::from_raw(3 << 38).unpack(), Err(DecodeError));
assert_eq!(
JournalRecord::from_raw((2 << 38) | (1 << 32)).unpack(),
Err(DecodeError)
);
}
}