objstore_r/mirror/
manifest.rs1use super::{Error, Result};
16
17const MAGIC: u32 = 0x4D50_5247; const VERSION: u16 = 1;
19pub(crate) const MAX_SEGMENTS: usize = 4096;
22pub(crate) const MAX_HOLDER: usize = 256;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct Segment {
28 pub start: u64,
30 pub len: u64,
32 pub sum: u64,
34}
35
36impl Segment {
37 pub(crate) fn end(&self) -> u64 {
38 self.start + self.len
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct Lease {
45 pub holder: String,
47 pub expires_ms: u64,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Default)]
52pub(crate) struct Manifest {
53 pub(crate) generation: u64,
55 pub(crate) base_len: u64,
56 pub(crate) base_sum: u64,
57 pub(crate) lease: Option<Lease>,
58 pub(crate) segments: Vec<Segment>,
59}
60
61impl Manifest {
62 pub(crate) fn wal_len(&self) -> u64 {
63 self.segments.last().map_or(0, Segment::end)
64 }
65
66 pub(crate) fn encode(&self) -> Vec<u8> {
67 let holder = self.lease.as_ref().map_or("", |l| l.holder.as_str());
68 let mut o = Vec::with_capacity(48 + holder.len() + self.segments.len() * 24);
69 o.extend_from_slice(&self.generation.to_le_bytes());
70 o.extend_from_slice(&self.base_len.to_le_bytes());
71 o.extend_from_slice(&self.base_sum.to_le_bytes());
72 o.extend_from_slice(&self.lease.as_ref().map_or(0, |l| l.expires_ms).to_le_bytes());
73 o.extend_from_slice(&(holder.len() as u16).to_le_bytes());
74 o.extend_from_slice(holder.as_bytes());
75 o.extend_from_slice(&(self.segments.len() as u32).to_le_bytes());
76 for s in &self.segments {
77 o.extend_from_slice(&s.start.to_le_bytes());
78 o.extend_from_slice(&s.len.to_le_bytes());
79 o.extend_from_slice(&s.sum.to_le_bytes());
80 }
81 crate::fenced::seal(MAGIC, VERSION, &o)
82 }
83
84 pub(crate) fn decode(b: &[u8]) -> Result<Self> {
86 let bad = |m: &str| Error::Corrupt { message: format!("HEAD: {m}") };
87 let body = crate::fenced::unseal(b, MAGIC, VERSION).map_err(|e| bad(&e.to_string()))?;
88 let mut r = graph_r::bytesio::Reader::new(body);
89 let generation = r.u64()?;
90 let base_len = r.u64()?;
91 let base_sum = r.u64()?;
92 let expires_ms = r.u64()?;
93 let holder_len = usize::from(r.u16()?);
94 if holder_len > MAX_HOLDER {
95 return Err(bad("lease holder too long"));
96 }
97 let holder =
98 std::str::from_utf8(r.bytes(holder_len)?).map_err(|_| bad("lease holder not utf-8"))?;
99 let lease = (!holder.is_empty()).then(|| Lease { holder: holder.to_owned(), expires_ms });
100 let count = r.u32()? as usize;
101 if count > MAX_SEGMENTS || count * 24 != r.remaining() {
102 return Err(bad("segment table size"));
103 }
104 let mut segments = Vec::with_capacity(count);
105 let mut end = 0u64;
106 for _ in 0..count {
107 let s = Segment { start: r.u64()?, len: r.u64()?, sum: r.u64()? };
108 if s.start != end || s.len == 0 {
109 return Err(bad("segments are not contiguous"));
110 }
111 end = s.start.checked_add(s.len).ok_or_else(|| bad("segment overflow"))?;
112 segments.push(s);
113 }
114 if generation == 0 && (!segments.is_empty() || base_len != 0) {
115 return Err(bad("data without a generation"));
116 }
117 if generation != 0 && segments.is_empty() {
118 return Err(bad("generation without a log"));
119 }
120 Ok(Self { generation, base_len, base_sum, lease, segments })
121 }
122}
123
124#[doc(hidden)]
127pub fn fuzz(b: &[u8]) {
128 if let Ok(m) = Manifest::decode(b) {
129 assert_eq!(m.encode(), b);
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn sample() -> Manifest {
138 Manifest {
139 generation: 7,
140 base_len: 1234,
141 base_sum: 99,
142 lease: Some(Lease { holder: "instance-a".into(), expires_ms: 5 }),
143 segments: vec![
144 Segment { start: 0, len: 64, sum: 1 },
145 Segment { start: 64, len: 40, sum: 2 },
146 ],
147 }
148 }
149
150 #[test]
151 fn round_trips_and_rejects_every_corruption() {
152 let m = sample();
153 let b = m.encode();
154 assert_eq!(Manifest::decode(&b).unwrap(), m);
155 assert_eq!(Manifest::decode(&Manifest::default().encode()).unwrap(), Manifest::default());
156 for i in 0..b.len() {
157 let mut c = b.clone();
158 c[i] ^= 0x40;
159 assert!(Manifest::decode(&c).is_err(), "flip at {i}");
160 }
161 for cut in 0..b.len() {
162 assert!(Manifest::decode(&b[..cut]).is_err(), "cut at {cut}");
163 }
164 fuzz(&b);
165 }
166
167 #[test]
168 fn structural_rules_hold_even_with_a_valid_checksum() {
169 let mut m = sample();
170 m.segments[1].start = 65;
171 assert!(Manifest::decode(&m.encode()).is_err(), "gap");
172 let mut m = sample();
173 m.segments.clear();
174 assert!(Manifest::decode(&m.encode()).is_err(), "generation without log");
175 let m = Manifest { generation: 0, base_len: 5, ..Manifest::default() };
176 assert!(Manifest::decode(&m.encode()).is_err());
177 }
178}