use crate::{BinaryRecord, FormatError};
pub const TOKEN_RECORD_SIZE: usize = 24;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TokenRecord {
id: u32,
offset: u64,
length: u32,
}
impl TokenRecord {
pub const SIZE: usize = TOKEN_RECORD_SIZE;
pub const fn new(id: u32, offset: u64, length: u32) -> Self {
Self { id, offset, length }
}
pub const fn id(self) -> u32 {
self.id
}
pub const fn offset(self) -> u64 {
self.offset
}
pub const fn length(self) -> u32 {
self.length
}
pub const fn end(self) -> u64 {
self.offset + self.length as u64
}
pub fn encode(self) -> [u8; TOKEN_RECORD_SIZE] {
let mut bytes = [0_u8; TOKEN_RECORD_SIZE];
bytes[0..4].copy_from_slice(&self.id.to_le_bytes());
bytes[8..16].copy_from_slice(&self.offset.to_le_bytes());
bytes[16..20].copy_from_slice(&self.length.to_le_bytes());
bytes
}
pub fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
if bytes.len() != Self::SIZE {
return Err(FormatError::UnexpectedRecordSize {
record: "token record",
expected: Self::SIZE,
found: bytes.len(),
});
}
let id =
u32::from_le_bytes(bytes[0..4].try_into().expect("token id must occupy four bytes"));
let offset = u64::from_le_bytes(
bytes[8..16].try_into().expect("token string offset must occupy eight bytes"),
);
let length = u32::from_le_bytes(
bytes[16..20].try_into().expect("token string length must occupy four bytes"),
);
Ok(Self::new(id, offset, length))
}
}
impl BinaryRecord for TokenRecord {
const SIZE: usize = TokenRecord::SIZE;
fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
TokenRecord::decode(bytes)
}
}
#[cfg(test)]
mod tests {
use super::{TOKEN_RECORD_SIZE, TokenRecord};
#[test]
fn encodes_token_record() {
let record = TokenRecord::new(42, 4_294_967_500, 128);
let bytes = record.encode();
assert_eq!(bytes.len(), TOKEN_RECORD_SIZE);
assert_eq!(
u32::from_le_bytes(bytes[0..4].try_into().expect("token id must occupy four bytes"),),
42,
);
assert_eq!(&bytes[4..8], &[0; 4]);
assert_eq!(
u64::from_le_bytes(
bytes[8..16].try_into().expect("string offset must occupy eight bytes"),
),
4_294_967_500,
);
assert_eq!(
u32::from_le_bytes(
bytes[16..20].try_into().expect("string length must occupy four bytes"),
),
128,
);
assert_eq!(&bytes[20..24], &[0; 4]);
}
#[test]
fn calculates_string_range_end() {
let record = TokenRecord::new(0, 500, 25);
assert_eq!(record.end(), 525);
}
#[test]
fn exposes_record_fields() {
let record = TokenRecord::new(7, 100, 12);
assert_eq!(record.id(), 7);
assert_eq!(record.offset(), 100);
assert_eq!(record.length(), 12);
}
#[test]
fn decodes_token_record() {
let original = TokenRecord::new(42, 8_589_934_592, 128);
let encoded = original.encode();
let decoded = TokenRecord::decode(&encoded).expect("encoded token record should decode");
assert_eq!(decoded, original);
}
#[test]
fn rejects_invalid_token_record_size() {
let bytes = [0_u8; TokenRecord::SIZE - 1];
let error = TokenRecord::decode(&bytes).expect_err("truncated token record should fail");
assert!(matches!(
error,
crate::FormatError::UnexpectedRecordSize {
record: "token record",
expected: TokenRecord::SIZE,
found,
} if found == TokenRecord::SIZE - 1
));
}
}