use bytes::{BufMut, Bytes, BytesMut};
use super::DeserializeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyPrefix {
version: u8,
tag: RecordTag,
}
impl KeyPrefix {
pub fn new(version: u8, tag: RecordTag) -> Self {
Self { version, tag }
}
pub fn version(&self) -> u8 {
self.version
}
pub fn tag(&self) -> RecordTag {
self.tag
}
pub fn from_bytes(data: &[u8]) -> Result<Self, DeserializeError> {
if data.len() < 2 {
return Err(DeserializeError {
message: format!(
"buffer too short for key prefix: need 2 bytes, got {}",
data.len()
),
});
}
let version = data[0];
let tag = RecordTag::from_byte(data[1])?;
Ok(Self { version, tag })
}
pub fn from_bytes_versioned(
data: &[u8],
expected_version: u8,
) -> Result<Self, DeserializeError> {
let prefix = Self::from_bytes(data)?;
if prefix.version != expected_version {
return Err(DeserializeError {
message: format!(
"invalid key version: expected 0x{:02x}, got 0x{:02x}",
expected_version, prefix.version
),
});
}
Ok(prefix)
}
pub fn to_bytes(&self) -> Bytes {
Bytes::from(vec![self.version, self.tag.as_byte()])
}
pub fn write_to(&self, buf: &mut BytesMut) {
buf.put_u8(self.version);
buf.put_u8(self.tag.as_byte());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordTag(u8);
impl RecordTag {
pub fn new(record_type: u8, reserved: u8) -> Self {
assert!(
record_type > 0 && record_type <= 0x0F,
"record type {} must be in range 1-15",
record_type
);
assert!(
reserved <= 0x0F,
"reserved bits {} must be in range 0-15",
reserved
);
RecordTag((record_type << 4) | reserved)
}
pub fn from_byte(byte: u8) -> Result<Self, DeserializeError> {
let record_type = (byte & 0xF0) >> 4;
if record_type == 0 {
return Err(DeserializeError {
message: format!(
"invalid record tag: 0x{:02x} (record type 0 is reserved)",
byte
),
});
}
Ok(RecordTag(byte))
}
pub fn record_type(&self) -> u8 {
(self.0 & 0xF0) >> 4
}
pub fn reserved(&self) -> u8 {
self.0 & 0x0F
}
pub fn as_byte(&self) -> u8 {
self.0
}
pub fn with_reserved(&self, reserved: u8) -> Self {
assert!(
reserved <= 0x0F,
"reserved bits {} must be in range 0-15",
reserved
);
RecordTag((self.0 & 0xF0) | reserved)
}
pub fn type_range(record_type: u8) -> std::ops::Range<u8> {
assert!(
record_type > 0 && record_type <= 0x0F,
"record type {} must be in range 1-15",
record_type
);
let start = record_type << 4;
start..(start + 0x10)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_create_record_tag() {
let record_type = 0x05;
let reserved = 0x03;
let tag = RecordTag::new(record_type, reserved);
assert_eq!(tag.as_byte(), 0x53);
assert_eq!(tag.record_type(), 0x05);
assert_eq!(tag.reserved(), 0x03);
}
#[test]
fn should_create_tag_with_zero_reserved() {
let record_type = 0x01;
let reserved = 0x00;
let tag = RecordTag::new(record_type, reserved);
assert_eq!(tag.as_byte(), 0x10);
assert_eq!(tag.record_type(), 0x01);
assert_eq!(tag.reserved(), 0x00);
}
#[test]
fn should_create_tag_with_max_values() {
let record_type = 0x0F;
let reserved = 0x0F;
let tag = RecordTag::new(record_type, reserved);
assert_eq!(tag.as_byte(), 0xFF);
assert_eq!(tag.record_type(), 0x0F);
assert_eq!(tag.reserved(), 0x0F);
}
#[test]
#[should_panic(expected = "record type 0 must be in range 1-15")]
fn should_panic_on_zero_record_type() {
RecordTag::new(0, 0);
}
#[test]
#[should_panic(expected = "record type 16 must be in range 1-15")]
fn should_panic_on_record_type_overflow() {
RecordTag::new(16, 0);
}
#[test]
#[should_panic(expected = "reserved bits 16 must be in range 0-15")]
fn should_panic_on_reserved_overflow() {
RecordTag::new(1, 16);
}
#[test]
fn should_parse_tag_from_byte() {
let byte = 0x53;
let tag = RecordTag::from_byte(byte).unwrap();
assert_eq!(tag.record_type(), 0x05);
assert_eq!(tag.reserved(), 0x03);
}
#[test]
fn should_reject_zero_record_type_byte() {
let byte = 0x0F;
let result = RecordTag::from_byte(byte);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.message
.contains("record type 0 is reserved")
);
}
#[test]
fn should_compute_type_range() {
let record_type = 0x03;
let range = RecordTag::type_range(record_type);
assert_eq!(range.start, 0x30);
assert_eq!(range.end, 0x40);
}
#[test]
fn should_create_tag_with_different_reserved_bits() {
let tag = RecordTag::new(0x05, 0x00);
let new_tag = tag.with_reserved(0x0A);
assert_eq!(new_tag.record_type(), 0x05);
assert_eq!(new_tag.reserved(), 0x0A);
assert_eq!(new_tag.as_byte(), 0x5A);
}
#[test]
#[should_panic(expected = "reserved bits 16 must be in range 0-15")]
fn should_panic_on_with_reserved_overflow() {
let tag = RecordTag::new(0x01, 0x00);
tag.with_reserved(16);
}
#[test]
fn should_create_key_prefix() {
let version = 0x01;
let tag = RecordTag::new(0x02, 0x05);
let prefix = KeyPrefix::new(version, tag);
assert_eq!(prefix.version(), version);
assert_eq!(prefix.tag().as_byte(), tag.as_byte());
}
#[test]
fn should_write_and_read_key_prefix() {
let prefix = KeyPrefix::new(0x01, RecordTag::new(0x02, 0x05));
let mut buf = BytesMut::new();
prefix.write_to(&mut buf);
let parsed = KeyPrefix::from_bytes(&buf).unwrap();
assert_eq!(parsed, prefix);
}
#[test]
fn should_serialize_key_prefix_to_bytes() {
let prefix = KeyPrefix::new(0x01, RecordTag::new(0x02, 0x05));
let bytes = prefix.to_bytes();
assert_eq!(bytes.len(), 2);
assert_eq!(bytes[0], 0x01);
assert_eq!(bytes[1], 0x25);
}
#[test]
fn should_parse_key_prefix_versioned() {
let expected_version = 0x01;
let data = [expected_version, 0x25];
let prefix = KeyPrefix::from_bytes_versioned(&data, expected_version).unwrap();
assert_eq!(prefix.version(), expected_version);
assert_eq!(prefix.tag().record_type(), 0x02);
assert_eq!(prefix.tag().reserved(), 0x05);
}
#[test]
fn should_reject_wrong_version() {
let data = [0x02, 0x10];
let result = KeyPrefix::from_bytes_versioned(&data, 0x01);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("invalid key version"));
}
#[test]
fn should_reject_short_buffer() {
let data = [0x01];
let result = KeyPrefix::from_bytes(&data);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("buffer too short"));
}
}