use crate::consts::{HASH_FIELD_SIZE, LABEL_SIZE, NIL_UID, TYPE_RESERVED, UID_SIZE};
use crate::error::Error;
use crate::hash::HashAlgo;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PartitionEntry {
pub partition_type: u32,
pub uid: [u8; UID_SIZE],
pub label: [u8; LABEL_SIZE],
pub start_offset: u64,
pub max_length: u64,
pub used_bytes: u64,
pub data_hash_algo: HashAlgo,
pub data_hash: [u8; HASH_FIELD_SIZE],
}
impl PartitionEntry {
pub fn to_bytes(&self) -> [u8; 141] {
let mut b = [0u8; 141];
b[0..4].copy_from_slice(&self.partition_type.to_le_bytes());
b[4..20].copy_from_slice(&self.uid);
b[20..52].copy_from_slice(&self.label);
b[52..60].copy_from_slice(&self.start_offset.to_le_bytes());
b[60..68].copy_from_slice(&self.max_length.to_le_bytes());
b[68..76].copy_from_slice(&self.used_bytes.to_le_bytes());
b[76] = self.data_hash_algo.id();
b[77..141].copy_from_slice(&self.data_hash);
b
}
pub fn from_bytes(b: &[u8; 141]) -> Result<Self, Error> {
let partition_type = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
let mut uid = [0u8; UID_SIZE];
uid.copy_from_slice(&b[4..20]);
let mut label = [0u8; LABEL_SIZE];
label.copy_from_slice(&b[20..52]);
let start_offset = u64::from_le_bytes(b[52..60].try_into().unwrap());
let max_length = u64::from_le_bytes(b[60..68].try_into().unwrap());
let used_bytes = u64::from_le_bytes(b[68..76].try_into().unwrap());
let data_hash_algo = HashAlgo::from_id(b[76])?;
let mut data_hash = [0u8; HASH_FIELD_SIZE];
data_hash.copy_from_slice(&b[77..141]);
Ok(PartitionEntry {
partition_type,
uid,
label,
start_offset,
max_length,
used_bytes,
data_hash_algo,
data_hash,
})
}
pub fn validate(&self) -> Result<(), Error> {
if self.partition_type == TYPE_RESERVED {
return Err(Error::ReservedType);
}
if self.uid == NIL_UID {
return Err(Error::NilUid);
}
if self.used_bytes > self.max_length {
return Err(Error::UsedExceedsMax);
}
decode_label(&self.label)?; Ok(())
}
pub fn label_string(&self) -> Result<String, Error> {
decode_label(&self.label)
}
pub fn free_bytes(&self) -> u64 {
self.max_length.saturating_sub(self.used_bytes)
}
}
pub fn encode_label(s: &str) -> Result<[u8; LABEL_SIZE], Error> {
let bytes = s.as_bytes();
if bytes.len() > LABEL_SIZE {
return Err(Error::InvalidLabel);
}
for &c in bytes {
if c == 0 || c >= 0x80 {
return Err(Error::InvalidLabel);
}
}
let mut l = [0u8; LABEL_SIZE];
l[..bytes.len()].copy_from_slice(bytes);
Ok(l)
}
pub fn decode_label(label: &[u8; LABEL_SIZE]) -> Result<String, Error> {
let mut end = LABEL_SIZE;
for (i, &c) in label.iter().enumerate() {
if c == 0 {
end = i;
break;
}
if c >= 0x80 {
return Err(Error::InvalidLabel);
}
}
Ok(String::from_utf8_lossy(&label[..end]).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> PartitionEntry {
PartitionEntry {
partition_type: 7,
uid: [1; 16],
label: encode_label("hello").unwrap(),
start_offset: 1024,
max_length: 4096,
used_bytes: 100,
data_hash_algo: HashAlgo::Sha256,
data_hash: HashAlgo::Sha256.compute(b"x"),
}
}
#[test]
fn entry_roundtrip() {
let e = sample();
assert_eq!(PartitionEntry::from_bytes(&e.to_bytes()).unwrap(), e);
}
#[test]
fn label_roundtrip() {
let l = encode_label("config.bin").unwrap();
assert_eq!(decode_label(&l).unwrap(), "config.bin");
}
#[test]
fn full_label_no_terminator() {
let s = "a".repeat(32);
let l = encode_label(&s).unwrap();
assert_eq!(decode_label(&l).unwrap(), s);
}
#[test]
fn label_too_long() {
assert!(matches!(
encode_label(&"a".repeat(33)),
Err(Error::InvalidLabel)
));
}
#[test]
fn validate_rejects_reserved_and_nil() {
let mut e = sample();
e.partition_type = 0;
assert!(matches!(e.validate(), Err(Error::ReservedType)));
let mut e = sample();
e.uid = [0; 16];
assert!(matches!(e.validate(), Err(Error::NilUid)));
}
}