Skip to main content

urma_core/
multipart.rs

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}