aven_protocol/wire/bootstrap/
batch.rs1use anyhow::{Result, ensure};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use super::{Component, MAX_REQUEST_BYTES};
12
13pub const CONTENT_TYPE: &str = "application/vnd.aven.bootstrap-batch";
14pub const MAGIC: &[u8; 4] = b"AVBU";
15pub const VERSION: u16 = 1;
16pub const MAX_PAYLOAD: usize = 4 * 1_048_576;
18pub const MAX_RECORDS: usize = 64;
19pub const MAX_HEADER: usize = 4096;
20const PREFIX: usize = MAGIC.len() + 2 + 4;
21pub const MAX_BYTES: usize = PREFIX + MAX_HEADER + MAX_PAYLOAD;
24
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct Header {
28 #[serde(with = "hex32")]
29 pub vault: [u8; 32],
30 #[serde(with = "hex32")]
31 pub genesis: [u8; 32],
32 #[serde(with = "hex32")]
33 pub bootstrap: [u8; 32],
34 #[serde(with = "hex32")]
35 pub commitment: [u8; 32],
36 pub records: Vec<Slot>,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct Slot {
42 #[serde(serialize_with = "component_out", deserialize_with = "component_in")]
43 pub component: Component,
44 pub index: u64,
45 pub len: u64,
46}
47
48pub struct Batch<'a> {
50 pub header: Header,
51 pub records: Vec<&'a [u8]>,
52}
53
54fn check(header: &Header) -> Result<usize> {
58 let records = &header.records;
59 ensure!(
60 !records.is_empty() && records.len() <= MAX_RECORDS,
61 "error bootstrap-batch-shape"
62 );
63 let mut payload = 0_usize;
64 for slot in records {
65 let len = usize::try_from(slot.len)
66 .ok()
67 .filter(|len| *len > 0 && *len <= MAX_REQUEST_BYTES);
68 let len = len.ok_or_else(|| anyhow::anyhow!("error bootstrap-batch-shape"))?;
69 payload = payload
70 .checked_add(len)
71 .filter(|total| *total <= MAX_PAYLOAD)
72 .ok_or_else(|| anyhow::anyhow!("error bootstrap-batch-shape"))?;
73 }
74 let catalogs = records
75 .iter()
76 .filter(|slot| slot.component.catalog().is_some())
77 .count();
78 ensure!(
79 catalogs == 0 || catalogs == records.len(),
80 "error bootstrap-batch-mixed"
81 );
82 for (position, slot) in records.iter().enumerate() {
83 ensure!(
84 records[..position]
85 .iter()
86 .all(|other| (other.component, other.index) != (slot.component, slot.index)),
87 "error bootstrap-batch-shape"
88 );
89 }
90 Ok(payload)
91}
92
93pub fn header_len(header: &Header) -> Option<usize> {
95 serde_json::to_vec(header)
96 .ok()
97 .map(|bytes| bytes.len())
98 .filter(|len| *len <= MAX_HEADER)
99}
100
101pub fn encode(header: &Header, records: &[&[u8]]) -> Result<Vec<u8>> {
102 let payload = check(header)?;
103 ensure!(
104 records.len() == header.records.len()
105 && records
106 .iter()
107 .zip(&header.records)
108 .all(|(bytes, slot)| bytes.len() as u64 == slot.len),
109 "error bootstrap-batch-shape"
110 );
111 let json = serde_json::to_vec(header)?;
112 ensure!(json.len() <= MAX_HEADER, "error bootstrap-batch-shape");
113 let mut out = Vec::with_capacity(PREFIX + json.len() + payload);
114 out.extend_from_slice(MAGIC);
115 out.extend_from_slice(&VERSION.to_be_bytes());
116 out.extend_from_slice(&(json.len() as u32).to_be_bytes());
117 out.extend_from_slice(&json);
118 for record in records {
119 out.extend_from_slice(record);
120 }
121 Ok(out)
122}
123
124pub fn decode(bytes: &[u8]) -> Result<Batch<'_>> {
125 ensure!(
126 bytes.len() >= PREFIX && bytes.len() <= MAX_BYTES,
127 "error bootstrap-batch-framing"
128 );
129 let (magic, rest) = bytes.split_at(MAGIC.len());
130 ensure!(magic == MAGIC, "error bootstrap-batch-framing");
131 let (version, rest) = rest.split_at(2);
132 ensure!(
133 u16::from_be_bytes([version[0], version[1]]) == VERSION,
134 "error bootstrap-batch-version"
135 );
136 let (len, rest) = rest.split_at(4);
137 let len = u32::from_be_bytes([len[0], len[1], len[2], len[3]]) as usize;
138 ensure!(
139 len <= MAX_HEADER && len <= rest.len(),
140 "error bootstrap-batch-framing"
141 );
142 let (json, mut body) = rest.split_at(len);
143 let header: Header = serde_json::from_slice(json)
144 .map_err(|_| anyhow::anyhow!("error bootstrap-batch-framing"))?;
145 let payload = check(&header)?;
146 ensure!(body.len() == payload, "error bootstrap-batch-framing");
147 let mut records = Vec::with_capacity(header.records.len());
148 for slot in &header.records {
149 let (record, rest) = body.split_at(slot.len as usize);
150 records.push(record);
151 body = rest;
152 }
153 Ok(Batch { header, records })
154}
155
156fn component_out<S: Serializer>(component: &Component, s: S) -> Result<S::Ok, S::Error> {
157 s.serialize_str(&hex::encode(component.key()))
158}
159
160fn component_in<'de, D: Deserializer<'de>>(d: D) -> Result<Component, D::Error> {
161 let text = String::deserialize(d)?;
162 hex::decode(&text)
163 .ok()
164 .and_then(|key| Component::from_key(&key))
165 .ok_or_else(|| serde::de::Error::custom("invalid component"))
166}
167
168mod hex32 {
169 use serde::{Deserialize, Deserializer, Serializer};
170
171 pub(super) fn serialize<S: Serializer>(value: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
172 s.serialize_str(&hex::encode(value))
173 }
174
175 pub(super) fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
176 let text = String::deserialize(d)?;
177 let mut out = [0; 32];
178 hex::decode_to_slice(&text, &mut out)
179 .map_err(|_| serde::de::Error::custom("invalid 32-byte hex"))?;
180 Ok(out)
181 }
182}
183
184#[cfg(test)]
185mod tests;