mkit_core/pack/window/
cursor.rs1use super::{MAX_ENTRIES, MAX_TOTAL_PAYLOAD, PackError, Tree, geometry};
8use crate::hash::{self, Hash};
9
10#[derive(Debug, Clone)]
12pub struct WindowCursor {
13 pub(super) pack_len: u64,
14 pub(super) window_size: u64,
15 pub(super) pos: u64,
16 pub(super) payload_sum: u64,
17 pub(super) version: u32,
18 pub(super) count: u32,
19 pub(super) index: u32,
20 pub(super) first_non_raw: Option<u32>,
21 pub(super) completed: u64,
22 pub(super) expected: Option<Hash>,
23 pub(super) anchor: Option<Hash>,
24 pub(super) window_prefix: Option<Hash>,
25 pub(super) trailer_tree: Tree,
26 pub(super) id_tree: Tree,
27}
28
29impl WindowCursor {
30 pub(super) fn initial(pack_len: u64, window_size: u64, expected: Option<Hash>) -> Self {
31 Self {
32 pack_len,
33 window_size,
34 pos: 12,
35 payload_sum: 0,
36 version: 0,
37 count: 0,
38 index: 0,
39 first_non_raw: None,
40 completed: 0,
41 expected,
42 anchor: None,
43 window_prefix: None,
44 trailer_tree: Tree::new(),
45 id_tree: Tree::new(),
46 }
47 }
48 pub(super) fn split(&self) -> u64 {
49 self.pack_len - 32
50 } pub(super) fn validate(&self) -> Result<(), PackError> {
52 let bad = PackError::PackfileCorrupted;
53 geometry(self.pack_len, self.window_size).map_err(|_| PackError::PackfileCorrupted)?;
54 let framing = u64::from(self.index)
55 .checked_mul(5)
56 .and_then(|n| n.checked_add(12))
57 .and_then(|n| n.checked_add(self.payload_sum));
58 let max_len = MAX_TOTAL_PAYLOAD
59 .checked_add(u64::from(self.count) * 5)
60 .and_then(|n| n.checked_add(44));
61 let window_start = self.completed.checked_mul(self.window_size);
62 if window_start.is_none_or(|start| self.window_prefix.is_some() != (self.pos > start))
63 || (self.expected.is_none() && self.anchor.is_none())
64 || !matches!(self.version, 1 | 2)
65 || self.count > MAX_ENTRIES
66 || self.index > self.count
67 || !(12..=self.split()).contains(&self.pos)
68 || framing != Some(self.pos)
69 || (self.index == 0 && self.payload_sum != 0)
70 || self.payload_sum > MAX_TOTAL_PAYLOAD
71 || max_len.is_none_or(|n| self.pack_len > n)
72 || self.completed != self.pos / self.window_size
73 || self.first_non_raw.is_some_and(|i| i >= self.index)
74 || !self
75 .trailer_tree
76 .validate(self.completed, self.split(), self.window_size)
77 || (self.expected.is_some()
78 && !self
79 .id_tree
80 .validate(self.completed, self.pack_len, self.window_size))
81 || (self.expected.is_none() && self.id_tree != Tree::new())
82 {
83 return Err(bad);
84 }
85 Ok(())
86 }
87
88 #[must_use]
90 pub fn to_bytes(&self) -> Vec<u8> {
91 let mut out = vec![1];
92 for n in [self.pack_len, self.window_size, self.pos, self.payload_sum] {
93 out.extend_from_slice(&n.to_le_bytes());
94 }
95 for n in [self.version, self.count, self.index] {
96 out.extend_from_slice(&n.to_le_bytes());
97 }
98 out.push(u8::from(self.first_non_raw.is_some()));
99 if let Some(index) = self.first_non_raw {
100 out.extend_from_slice(&index.to_le_bytes());
101 }
102 out.extend_from_slice(&self.completed.to_le_bytes());
103 write_option(&mut out, self.expected);
104 write_option(&mut out, self.anchor);
105 write_option(&mut out, self.window_prefix);
106 for tree in [&self.trailer_tree, &self.id_tree] {
107 out.push(u8::try_from(tree.stack.len()).unwrap_or(u8::MAX));
110 for cv in &tree.stack {
111 out.extend_from_slice(cv);
112 }
113 write_option(&mut out, tree.root);
114 }
115 out.extend_from_slice(&hash::hash(&out));
116 out
117 }
118
119 pub fn from_bytes(bytes: &[u8]) -> Result<Self, PackError> {
125 if bytes.len() > 4096 || bytes.len() < 32 {
126 return Err(PackError::PackfileCorrupted);
127 }
128 let (body, checksum) = bytes.split_at(bytes.len() - 32);
129 if hash::hash(body).as_slice() != checksum {
130 return Err(PackError::PackfileCorrupted);
131 }
132 let mut r = Input(body);
133 if r.byte()? != 1 {
134 return Err(PackError::PackfileCorrupted);
135 }
136 let mut state = Self {
137 pack_len: r.u64()?,
138 window_size: r.u64()?,
139 pos: r.u64()?,
140 payload_sum: r.u64()?,
141 version: r.u32()?,
142 count: r.u32()?,
143 index: r.u32()?,
144 first_non_raw: None,
145 completed: 0,
146 expected: None,
147 anchor: None,
148 window_prefix: None,
149 trailer_tree: Tree::new(),
150 id_tree: Tree::new(),
151 };
152 state.first_non_raw = if r.tag()? { Some(r.u32()?) } else { None };
153 state.completed = r.u64()?;
154 state.expected = r.optional_hash()?;
155 state.anchor = r.optional_hash()?;
156 state.window_prefix = r.optional_hash()?;
157 state.trailer_tree = r.tree()?;
158 state.id_tree = r.tree()?;
159 if !r.0.is_empty() {
160 return Err(PackError::PackfileCorrupted);
161 }
162 state.validate()?;
163 Ok(state)
164 }
165}
166fn write_option(out: &mut Vec<u8>, value: Option<Hash>) {
167 out.push(u8::from(value.is_some()));
168 if let Some(hash) = value {
169 out.extend_from_slice(&hash);
170 }
171}
172struct Input<'a>(&'a [u8]);
173impl Input<'_> {
174 fn array<const N: usize>(&mut self) -> Result<[u8; N], PackError> {
175 let bytes = self.0.get(..N).ok_or(PackError::PackfileCorrupted)?;
176 let out = bytes.try_into().map_err(|_| PackError::PackfileCorrupted)?;
177 self.0 = &self.0[N..];
178 Ok(out)
179 }
180 fn byte(&mut self) -> Result<u8, PackError> {
181 Ok(self.array::<1>()?[0])
182 }
183 fn tag(&mut self) -> Result<bool, PackError> {
184 match self.byte()? {
185 0 => Ok(false),
186 1 => Ok(true),
187 _ => Err(PackError::PackfileCorrupted),
188 }
189 }
190 fn u64(&mut self) -> Result<u64, PackError> {
191 Ok(u64::from_le_bytes(self.array()?))
192 }
193 fn u32(&mut self) -> Result<u32, PackError> {
194 Ok(u32::from_le_bytes(self.array()?))
195 }
196 fn optional_hash(&mut self) -> Result<Option<Hash>, PackError> {
197 if self.tag()? {
198 Ok(Some(self.array()?))
199 } else {
200 Ok(None)
201 }
202 }
203 fn tree(&mut self) -> Result<Tree, PackError> {
204 let depth = usize::from(self.byte()?);
205 if depth > 64 {
206 return Err(PackError::PackfileCorrupted);
207 }
208 let mut stack = Vec::new();
209 stack
210 .try_reserve_exact(depth)
211 .map_err(|_| PackError::PackfileTooLarge)?;
212 for _ in 0..depth {
213 stack.push(self.array()?);
214 }
215 Ok(Tree {
216 stack,
217 root: self.optional_hash()?,
218 })
219 }
220}