//! A flat binary layout for local indexes (oxygen#41): fixed-width //! little-endian numbers and length-prefixed bytes, read in one pass //! without building a generic value tree. Only for caches this repository //! derives and can derive again: never exchanged, never trusted beyond the //! key they are stored under, and a reader that finds anything amiss fails //! so the caller rebuilds. use crate::hash::ObjectRef; use crate::objects::ObjectError; #[derive(Default)] pub struct Writer(pub Vec); impl Writer { pub fn u8(&mut self, v: u8) { self.0.push(v); } pub fn u32(&mut self, v: u32) { self.0.extend_from_slice(&v.to_le_bytes()); } pub fn u64(&mut self, v: u64) { self.0.extend_from_slice(&v.to_le_bytes()); } /// A count, which must fit in 32 bits. pub fn count(&mut self, n: usize) { self.u32(u32::try_from(n).expect("an index count fits in 32 bits")); } pub fn fixed(&mut self, b: &[u8]) { self.0.extend_from_slice(b); } pub fn bytes(&mut self, b: &[u8]) { self.count(b.len()); self.0.extend_from_slice(b); } pub fn object(&mut self, r: &ObjectRef) { self.fixed(&r.0); } pub fn opt_object(&mut self, r: &Option) { match r { None => self.u8(0), Some(r) => { self.u8(1); self.object(r); } } } } pub struct Reader<'a> { buf: &'a [u8], pos: usize, } fn short() -> ObjectError { ObjectError::Schema("index: truncated".into()) } impl<'a> Reader<'a> { pub fn new(buf: &'a [u8]) -> Reader<'a> { Reader { buf, pos: 0 } } pub fn at_end(&self) -> bool { self.pos == self.buf.len() } fn take(&mut self, n: usize) -> Result<&'a [u8], ObjectError> { let end = self.pos.checked_add(n).ok_or_else(short)?; let s = self.buf.get(self.pos..end).ok_or_else(short)?; self.pos = end; Ok(s) } pub fn u8(&mut self) -> Result { Ok(self.take(1)?[0]) } pub fn u32(&mut self) -> Result { Ok(u32::from_le_bytes(self.fixed::<4>()?)) } pub fn u64(&mut self) -> Result { Ok(u64::from_le_bytes(self.fixed::<8>()?)) } /// A count; checked against what is left, so a corrupt one cannot ask /// for more room than the index could hold. pub fn count(&mut self) -> Result { let n = self.u32()? as usize; if n > self.buf.len() - self.pos { return Err(short()); } Ok(n) } pub fn fixed(&mut self) -> Result<[u8; N], ObjectError> { Ok(self.take(N)?.try_into().expect("N bytes")) } pub fn bytes(&mut self) -> Result<&'a [u8], ObjectError> { let n = self.u32()? as usize; self.take(n) } pub fn text(&mut self) -> Result { String::from_utf8(self.bytes()?.to_vec()) .map_err(|_| ObjectError::Schema("index: text is not UTF-8".into())) } pub fn object(&mut self) -> Result { Ok(ObjectRef(self.fixed::<32>()?)) } pub fn opt_object(&mut self) -> Result, ObjectError> { Ok(match self.u8()? { 0 => None, _ => Some(self.object()?), }) } } #[cfg(test)] mod tests { use super::*; #[test] fn numbers_and_bytes_round_trip_and_truncation_fails() { let mut w = Writer::default(); w.u8(7); w.u32(70_000); w.u64(1 << 40); w.bytes(b"path/name"); w.opt_object(&Some(ObjectRef([3; 32]))); w.opt_object(&None); let mut r = Reader::new(&w.0); assert_eq!(r.u8().unwrap(), 7); assert_eq!(r.u32().unwrap(), 70_000); assert_eq!(r.u64().unwrap(), 1 << 40); assert_eq!(r.text().unwrap(), "path/name"); assert_eq!(r.opt_object().unwrap(), Some(ObjectRef([3; 32]))); assert_eq!(r.opt_object().unwrap(), None); assert!(r.at_end()); let mut r = Reader::new(&w.0[..w.0.len() - 3]); r.fixed::<13>().unwrap(); assert!(r.text().is_ok()); assert!(r.opt_object().is_err()); } }