1use crate::{
2 error::{Error, ensure},
3 format::RecordKind,
4};
5use bitcoin::{Txid, hashes::Hash};
6use sha2::{Digest, Sha256};
7
8mod consistency;
9mod inventory;
10mod proof;
11pub use consistency::MultipartConsistency;
12pub use inventory::ManifestInventory;
13pub use proof::{RecordRequest, VerifiedRecord};
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct Geometry {
16 length: u64,
17 parts: u32,
18 leaves: u16,
19}
20
21impl Geometry {
22 pub const RECORD_BYTES: usize = 256 * 1024;
23 pub const DATA_BYTES: usize = Self::RECORD_BYTES - 12;
24 pub const FANOUT: u16 = 511;
25 pub const MAX_PARTS: u32 = 32_630;
26 pub const MAX_OBJECT_BYTES: u64 = 8_553_279_476;
27 pub const MAX_NODES: u32 = 32_695;
28
29 pub fn new(length: u64) -> Result<Self, Error> {
30 ensure!(
31 length <= Geometry::MAX_OBJECT_BYTES,
32 "multipart length outside bounds"
33 );
34 let parts = u32::try_from(length.div_ceil(u64::try_from(Geometry::DATA_BYTES)?).max(1))?;
35 let leaves = u16::try_from(parts.div_ceil(u32::from(Geometry::FANOUT)))?;
36 Ok(Self {
37 length,
38 parts,
39 leaves,
40 })
41 }
42
43 pub fn length(self) -> u64 {
44 self.length
45 }
46 pub fn parts(self) -> u32 {
47 self.parts
48 }
49 pub fn leaves(self) -> u16 {
50 self.leaves
51 }
52 pub fn nodes(self) -> u32 {
53 self.parts + u32::from(self.leaves) + 1
54 }
55
56 pub fn part_length(self, index: u32) -> Result<usize, Error> {
57 ensure!(index < self.parts, "part index outside object");
58 let offset = u64::from(index) * u64::try_from(Geometry::DATA_BYTES)?;
59 Ok(usize::try_from(
60 (self.length - offset).min(u64::try_from(Geometry::DATA_BYTES)?),
61 )?)
62 }
63
64 pub fn leaf_entries(self, index: u16) -> Result<u16, Error> {
65 ensure!(index < self.leaves, "leaf index outside object");
66 Ok(u16::try_from(
67 (self.parts - u32::from(index) * u32::from(Geometry::FANOUT))
68 .min(u32::from(Geometry::FANOUT)),
69 )?)
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct ChildReference {
75 pub txid: Txid,
76 pub record_hash: [u8; 32],
77}
78
79impl ChildReference {
80 pub fn new(txid: Txid, record: &[u8]) -> Result<Self, Error> {
81 MultipartRecord::decode(record)?;
82 Ok(Self {
83 txid,
84 record_hash: Sha256::digest(record).into(),
85 })
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct DataPart {
91 pub index: u32,
92 pub payload: Vec<u8>,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
96pub struct LeafManifest {
97 pub index: u16,
98 pub entries: Vec<ChildReference>,
99}
100
101impl LeafManifest {
102 pub fn first_index(&self) -> u32 {
103 u32::from(self.index) * u32::from(Geometry::FANOUT)
104 }
105
106 pub fn validate(&self) -> Result<(), Error> {
107 ensure!(self.index < Geometry::FANOUT, "leaf index outside bounds");
108 ensure!(
109 (1..=usize::from(Geometry::FANOUT)).contains(&self.entries.len()),
110 "leaf entry count outside bounds"
111 );
112 Ok(())
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct RootManifest {
118 pub length: u64,
119 pub payload_hash: [u8; 32],
120 pub profile: [u8; 8],
121 pub entries: Vec<ChildReference>,
122}
123
124impl RootManifest {
125 pub fn geometry(&self) -> Result<Geometry, Error> {
126 let geometry = Geometry::new(self.length)?;
127 ensure!(
128 self.entries.len() == usize::from(geometry.leaves()),
129 "root leaf count disagrees with length"
130 );
131 Ok(geometry)
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
136pub enum MultipartRecord {
137 Data(DataPart),
138 Leaf(LeafManifest),
139 Root(RootManifest),
140}
141
142impl MultipartRecord {
143 pub fn kind(&self) -> RecordKind {
144 match self {
145 Self::Data(_) => RecordKind::DataPart,
146 Self::Leaf(_) => RecordKind::LeafManifest,
147 Self::Root(_) => RecordKind::RootManifest,
148 }
149 }
150
151 pub fn encode(&self) -> Result<Vec<u8>, Error> {
152 let mut bytes = self.kind().prefix().to_vec();
153 match self {
154 Self::Data(part) => {
155 ensure!(
156 part.index < Geometry::MAX_PARTS,
157 "part index outside bounds"
158 );
159 ensure!(
160 part.payload.len() <= Geometry::DATA_BYTES,
161 "data record exceeds cap"
162 );
163 bytes.extend_from_slice(&part.index.to_le_bytes());
164 bytes.extend_from_slice(&part.payload);
165 }
166 Self::Leaf(leaf) => {
167 leaf.validate()?;
168 bytes.extend_from_slice(&leaf.index.to_le_bytes());
169 bytes.extend_from_slice(&u16::try_from(leaf.entries.len())?.to_le_bytes());
170 bytes.extend_from_slice(&leaf.first_index().to_le_bytes());
171 encode_references(&mut bytes, &leaf.entries);
172 }
173 Self::Root(root) => {
174 let geometry = root.geometry()?;
175 bytes.extend_from_slice(&root.length.to_le_bytes());
176 bytes.extend_from_slice(&root.payload_hash);
177 bytes.extend_from_slice(&geometry.parts().to_le_bytes());
178 bytes.extend_from_slice(&geometry.leaves().to_le_bytes());
179 bytes.extend_from_slice(&[0, 0]);
180 bytes.extend_from_slice(&root.profile);
181 encode_references(&mut bytes, &root.entries);
182 }
183 }
184 Ok(bytes)
185 }
186
187 pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
188 let kind = RecordKind::parse(bytes)?;
189 ensure!(
190 bytes.len() <= Geometry::RECORD_BYTES,
191 "multipart record exceeds cap"
192 );
193 match kind {
194 RecordKind::DataPart => decode_data(bytes),
195 RecordKind::LeafManifest => decode_leaf(bytes),
196 RecordKind::RootManifest => decode_root(bytes),
197 _ => Err(Error::Invalid("not a multipart record".into())),
198 }
199 }
200}
201
202fn encode_references(bytes: &mut Vec<u8>, entries: &[ChildReference]) {
203 for entry in entries {
204 bytes.extend_from_slice(entry.txid.as_byte_array());
205 bytes.extend_from_slice(&entry.record_hash);
206 }
207}
208
209fn decode_references(bytes: &[u8], count: u16) -> Result<Vec<ChildReference>, Error> {
210 ensure!(
211 (1..=Geometry::FANOUT).contains(&count),
212 "reference count outside bounds"
213 );
214 ensure!(
215 bytes.len() == usize::from(count) * 64,
216 "incorrect reference bytes or trailing data"
217 );
218 bytes
219 .as_chunks::<64>()
220 .0
221 .iter()
222 .map(|entry| {
223 Ok(ChildReference {
224 txid: Txid::from_byte_array(entry[..32].try_into()?),
225 record_hash: entry[32..].try_into()?,
226 })
227 })
228 .collect()
229}
230
231fn decode_data(bytes: &[u8]) -> Result<MultipartRecord, Error> {
232 ensure!(bytes.len() >= 12, "truncated data part");
233 let index = u32::from_le_bytes(bytes[8..12].try_into()?);
234 ensure!(index < Geometry::MAX_PARTS, "part index outside bounds");
235 Ok(MultipartRecord::Data(DataPart {
236 index,
237 payload: bytes[12..].to_vec(),
238 }))
239}
240
241fn decode_leaf(bytes: &[u8]) -> Result<MultipartRecord, Error> {
242 ensure!(bytes.len() >= 16, "truncated leaf manifest");
243 let index = u16::from_le_bytes(bytes[8..10].try_into()?);
244 let count = u16::from_le_bytes(bytes[10..12].try_into()?);
245 let first = u32::from_le_bytes(bytes[12..16].try_into()?);
246 ensure!(
247 index < Geometry::FANOUT && first == u32::from(index) * u32::from(Geometry::FANOUT),
248 "noncanonical leaf position"
249 );
250 Ok(MultipartRecord::Leaf(LeafManifest {
251 index,
252 entries: decode_references(&bytes[16..], count)?,
253 }))
254}
255
256fn decode_root(bytes: &[u8]) -> Result<MultipartRecord, Error> {
257 ensure!(bytes.len() >= 64, "truncated root manifest");
258 let length = u64::from_le_bytes(bytes[8..16].try_into()?);
259 let geometry = Geometry::new(length)?;
260 let parts = u32::from_le_bytes(bytes[48..52].try_into()?);
261 let leaves = u16::from_le_bytes(bytes[52..54].try_into()?);
262 ensure!(
263 parts == geometry.parts() && leaves == geometry.leaves(),
264 "noncanonical root geometry"
265 );
266 ensure!(bytes[54..56] == [0, 0], "nonzero root reserved field");
267 Ok(MultipartRecord::Root(RootManifest {
268 length,
269 payload_hash: bytes[16..48].try_into()?,
270 profile: bytes[56..64].try_into()?,
271 entries: decode_references(&bytes[64..], leaves)?,
272 }))
273}