//! Objects: blobs (file content), chunk lists (big files) and trees. use crate::{Id, Result, corrupt}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pub enum Kind { Blob = 1, /// A big file: a list of (chunk blob ID, length). Chunks = 2, Tree = 3, } impl Kind { pub fn from_u8(b: u8) -> Option { match b { 1 => Some(Kind::Blob), 2 => Some(Kind::Chunks), 3 => Some(Kind::Tree), _ => None, } } } /// An object. Its ID is worked out once (the hash is most of what making a /// folder costs), so `kind` and `data` aren't changed once it's made. #[derive(Debug, Clone)] pub struct Object { pub kind: Kind, pub data: Vec, id: std::sync::OnceLock, } impl PartialEq for Object { fn eq(&self, other: &Object) -> bool { self.kind == other.kind && self.data == other.data } } impl Eq for Object {} impl Object { pub fn new(kind: Kind, data: Vec) -> Object { Object { kind, data, id: std::sync::OnceLock::new() } } pub fn blob(data: Vec) -> Object { Object::new(Kind::Blob, data) } /// An object whose ID was just checked against its content (`bundle::verify`). pub fn checked(kind: Kind, data: Vec, id: Id) -> Object { Object { kind, data, id: std::sync::OnceLock::from(id) } } pub fn id(&self) -> Id { *self.id.get_or_init(|| Id::of(self.kind as u8, &self.data)) } } /// A big file's chunks, in order. pub fn encode_chunks(chunks: &[(Id, u64)]) -> Vec { let mut out = Vec::with_capacity(chunks.len() * 40); for (id, len) in chunks { out.extend_from_slice(&id.0); out.extend_from_slice(&len.to_le_bytes()); } out } pub fn decode_chunks(data: &[u8]) -> Result> { if data.len() % 40 != 0 { return Err(corrupt("bad chunk list")); } Ok(data .chunks(40) .map(|c| (Id::from_slice(&c[..32]).unwrap(), u64::from_le_bytes(c[32..40].try_into().unwrap()))) .collect()) }