use bytes::{BufMut, Bytes, BytesMut};
use super::DeserializeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyPrefix {
subsystem: u8,
version: u8,
tag: u8,
}
impl KeyPrefix {
pub fn new(subsystem: u8, version: u8, tag: u8) -> Self {
assert!(subsystem > 0, "subsystem 0 is reserved");
assert!(version > 0, "key version 0 is reserved");
assert!(tag > 0, "record tag 0 is reserved");
Self {
subsystem,
version,
tag,
}
}
pub fn subsystem(&self) -> u8 {
self.subsystem
}
pub fn version(&self) -> u8 {
self.version
}
pub fn tag(&self) -> u8 {
self.tag
}
pub fn from_bytes(data: &[u8]) -> Result<Self, DeserializeError> {
if data.len() < 3 {
return Err(DeserializeError {
message: format!(
"buffer too short for key prefix: need 3 bytes, got {}",
data.len()
),
});
}
let subsystem = validate_subsystem(data[0])?;
let version = validate_key_version(data[1])?;
let tag = validate_record_tag(data[2])?;
Ok(Self {
subsystem,
version,
tag,
})
}
pub fn from_bytes_with_validation(
data: &[u8],
expected_subsystem: u8,
expected_version: u8,
) -> Result<Self, DeserializeError> {
let prefix = Self::from_bytes(data)?;
if prefix.subsystem != expected_subsystem {
return Err(DeserializeError {
message: format!(
"invalid subsystem: expected 0x{:02x}, got 0x{:02x}",
expected_subsystem, prefix.subsystem
),
});
}
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.subsystem, self.version, self.tag])
}
pub fn write_to(&self, buf: &mut BytesMut) {
buf.put_u8(self.subsystem);
buf.put_u8(self.version);
buf.put_u8(self.tag);
}
}
fn validate_key_version(byte: u8) -> Result<u8, DeserializeError> {
if byte == 0 {
return Err(DeserializeError {
message: format!(
"invalid key version: 0x{:02x} (version 0 is reserved)",
byte
),
});
}
Ok(byte)
}
fn validate_subsystem(byte: u8) -> Result<u8, DeserializeError> {
if byte == 0 {
return Err(DeserializeError {
message: format!(
"invalid subsystem: 0x{:02x} (subsystem 0 is reserved)",
byte
),
});
}
Ok(byte)
}
fn validate_record_tag(byte: u8) -> Result<u8, DeserializeError> {
if byte == 0 {
return Err(DeserializeError {
message: format!("invalid record tag: 0x{:02x} (tag 0 is reserved)", byte),
});
}
Ok(byte)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_create_key_prefix() {
let subsystem = 0x10;
let version = 0x01;
let tag = 0x02;
let prefix = KeyPrefix::new(subsystem, version, tag);
assert_eq!(prefix.subsystem(), subsystem);
assert_eq!(prefix.version(), version);
assert_eq!(prefix.tag(), tag);
}
#[test]
#[should_panic(expected = "subsystem 0 is reserved")]
fn should_panic_on_zero_subsystem() {
KeyPrefix::new(0, 0x01, 0x02);
}
#[test]
#[should_panic(expected = "key version 0 is reserved")]
fn should_panic_on_zero_version() {
KeyPrefix::new(0x10, 0, 0x02);
}
#[test]
#[should_panic(expected = "record tag 0 is reserved")]
fn should_panic_on_zero_record_tag() {
KeyPrefix::new(0x10, 0x01, 0);
}
#[test]
fn should_parse_tag_from_byte() {
let bytes = [0x42, 0x17, 0x24];
let key_prefix = KeyPrefix::from_bytes(&bytes).unwrap();
assert_eq!(key_prefix.subsystem(), 0x42);
assert_eq!(key_prefix.version(), 0x17);
assert_eq!(key_prefix.tag(), 0x24);
}
#[test]
fn should_reject_zero_subsystem_byte() {
let bytes = [0x00, 0x17, 0x53];
let result = KeyPrefix::from_bytes(&bytes);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("subsystem 0 is reserved"))
);
}
#[test]
fn should_reject_zero_version_byte() {
let bytes = [0x53, 0x00, 0x24];
let result = KeyPrefix::from_bytes(&bytes);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("version 0 is reserved"))
);
}
#[test]
fn should_reject_zero_record_type_byte() {
let bytes = [0x53, 0x17, 0x00];
let result = KeyPrefix::from_bytes(&bytes);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("tag 0 is reserved"))
);
}
#[test]
fn should_write_and_read_key_prefix() {
let prefix = KeyPrefix::new(0x10, 0x01, 0x02);
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(0x10, 0x01, 0x02);
let bytes = prefix.to_bytes();
assert_eq!(bytes.len(), 3);
assert_eq!(bytes[0], 0x10);
assert_eq!(bytes[1], 0x01);
assert_eq!(bytes[2], 0x02);
}
#[test]
fn should_parse_key_prefix_with_validation() {
let expected_subsystem = 0x10;
let expected_version = 0x01;
let data = [expected_subsystem, expected_version, 0x25];
let prefix =
KeyPrefix::from_bytes_with_validation(&data, expected_subsystem, expected_version)
.unwrap();
assert_eq!(prefix.subsystem(), expected_subsystem);
assert_eq!(prefix.version(), expected_version);
assert_eq!(prefix.tag(), 0x25);
}
#[test]
fn should_reject_wrong_subsystem() {
let data = [0x10, 0x01, 0x25];
let result = KeyPrefix::from_bytes_with_validation(&data, 0x20, 0x01);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("invalid subsystem"))
);
}
#[test]
fn should_reject_wrong_version() {
let data = [0x10, 0x02, 0x10];
let result = KeyPrefix::from_bytes_with_validation(&data, 0x10, 0x01);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("invalid key version"))
);
}
#[test]
fn should_reject_short_buffer() {
let data = [0x01, 0x10];
let result = KeyPrefix::from_bytes(&data);
assert!(
matches!(result, Err(DeserializeError { message }) if message.contains("buffer too short"))
);
}
}