use crate::error::{Error, Result};
use crate::hash::Algorithm;
pub const MAGIC: &[u8; 4] = b"CAFS";
pub const VERSION: u8 = 1;
pub const HEADER_SIZE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectType {
Blob = 1,
Tree = 2,
}
impl ObjectType {
pub fn to_u8(self) -> u8 {
self as u8
}
pub fn from_u8(value: u8) -> Result<Self> {
match value {
1 => Ok(ObjectType::Blob),
2 => Ok(ObjectType::Tree),
_ => Err(Error::invalid_hash(format!(
"Invalid object type: {}",
value
))),
}
}
pub fn as_str(&self) -> &'static str {
match self {
ObjectType::Blob => "blob",
ObjectType::Tree => "tree",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectHeader {
pub version: u8,
pub object_type: ObjectType,
pub algorithm: Algorithm,
pub payload_len: u64,
}
impl ObjectHeader {
pub fn new(object_type: ObjectType, algorithm: Algorithm, payload_len: u64) -> Self {
Self {
version: VERSION,
object_type,
algorithm,
payload_len,
}
}
pub fn encode(&self) -> [u8; HEADER_SIZE] {
let mut buf = [0u8; HEADER_SIZE];
buf[0..4].copy_from_slice(MAGIC);
buf[4] = self.version;
buf[5] = self.object_type.to_u8();
buf[6] = self.algorithm.id();
buf[8..16].copy_from_slice(&self.payload_len.to_le_bytes());
buf
}
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < HEADER_SIZE {
return Err(Error::invalid_hash(format!(
"Header too short: {} bytes (expected {})",
buf.len(),
HEADER_SIZE
)));
}
if &buf[0..4] != MAGIC {
return Err(Error::invalid_hash(format!(
"Invalid magic: expected {:?}, got {:?}",
MAGIC,
&buf[0..4]
)));
}
let version = buf[4];
if version != VERSION {
return Err(Error::invalid_hash(format!(
"Unsupported version: {} (expected {})",
version, VERSION
)));
}
let object_type = ObjectType::from_u8(buf[5])?;
let algorithm = Algorithm::from_id(buf[6])?;
if buf[7] != 0 {
return Err(Error::invalid_hash(format!(
"Reserved byte must be 0, got {}",
buf[7]
)));
}
let mut len_bytes = [0u8; 8];
len_bytes.copy_from_slice(&buf[8..16]);
let payload_len = u64::from_le_bytes(len_bytes);
Ok(Self {
version,
object_type,
algorithm,
payload_len,
})
}
pub fn validate(&self) -> Result<()> {
if self.version != VERSION {
return Err(Error::invalid_hash(format!(
"Unsupported version: {}",
self.version
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_type_conversions() {
assert_eq!(ObjectType::Blob.to_u8(), 1);
assert_eq!(ObjectType::Tree.to_u8(), 2);
assert_eq!(ObjectType::from_u8(1).unwrap(), ObjectType::Blob);
assert_eq!(ObjectType::from_u8(2).unwrap(), ObjectType::Tree);
assert!(ObjectType::from_u8(0).is_err());
assert!(ObjectType::from_u8(3).is_err());
}
#[test]
fn test_header_encode_decode_blob() {
let header = ObjectHeader::new(ObjectType::Blob, Algorithm::Blake3, 1024);
let encoded = header.encode();
assert_eq!(encoded.len(), HEADER_SIZE);
assert_eq!(&encoded[0..4], MAGIC);
let decoded = ObjectHeader::decode(&encoded).unwrap();
assert_eq!(decoded, header);
}
#[test]
fn test_header_encode_decode_tree() {
let header = ObjectHeader::new(ObjectType::Tree, Algorithm::Blake3, 512);
let encoded = header.encode();
let decoded = ObjectHeader::decode(&encoded).unwrap();
assert_eq!(decoded, header);
}
#[test]
fn test_header_decode_invalid_magic() {
let mut buf = [0u8; HEADER_SIZE];
buf[0..4].copy_from_slice(b"XXXX");
buf[4] = VERSION;
buf[5] = ObjectType::Blob.to_u8();
buf[6] = Algorithm::Blake3.id();
assert!(ObjectHeader::decode(&buf).is_err());
}
#[test]
fn test_header_decode_invalid_version() {
let mut buf = [0u8; HEADER_SIZE];
buf[0..4].copy_from_slice(MAGIC);
buf[4] = 99; buf[5] = ObjectType::Blob.to_u8();
buf[6] = Algorithm::Blake3.id();
assert!(ObjectHeader::decode(&buf).is_err());
}
#[test]
fn test_header_decode_invalid_type() {
let mut buf = [0u8; HEADER_SIZE];
buf[0..4].copy_from_slice(MAGIC);
buf[4] = VERSION;
buf[5] = 99; buf[6] = Algorithm::Blake3.id();
assert!(ObjectHeader::decode(&buf).is_err());
}
#[test]
fn test_header_decode_reserved_nonzero() {
let mut buf = [0u8; HEADER_SIZE];
buf[0..4].copy_from_slice(MAGIC);
buf[4] = VERSION;
buf[5] = ObjectType::Blob.to_u8();
buf[6] = Algorithm::Blake3.id();
buf[7] = 1;
assert!(ObjectHeader::decode(&buf).is_err());
}
#[test]
fn test_header_payload_len() {
let header = ObjectHeader::new(ObjectType::Blob, Algorithm::Blake3, 0x123456789ABCDEF0);
let encoded = header.encode();
let decoded = ObjectHeader::decode(&encoded).unwrap();
assert_eq!(decoded.payload_len, 0x123456789ABCDEF0);
}
#[test]
fn test_header_too_short() {
let buf = [0u8; 10]; assert!(ObjectHeader::decode(&buf).is_err());
}
#[test]
fn test_header_validate() {
let header = ObjectHeader::new(ObjectType::Blob, Algorithm::Blake3, 100);
assert!(header.validate().is_ok());
}
}