1use std::mem::size_of;
5
6use crate::{contract::*, replication::opening::FRAME_LIMIT, transport};
7
8const OPAQUE_NODE_SIZES: &[usize] = &[
15 size_of::<api::heddle::api::common::CallFailure>(),
16 size_of::<api::heddle::api::common::ErrorDetail>(),
17 size_of::<api::heddle::api::common::RetryAdvice>(),
18 size_of::<api::heddle::api::common::ConflictDetail>(),
19 size_of::<api::heddle::api::common::CursorFailure>(),
20 size_of::<api::heddle::api::common::CapabilityRequirement>(),
21 size_of::<api::heddle::api::common::PolicyDenial>(),
22 size_of::<api::heddle::api::common::UnknownDetail>(),
23 size_of::<api::heddle::api::common::AmbiguousChangeIdDetail>(),
24 size_of::<api::heddle::api::common::SignupFailure>(),
25 size_of::<api::heddle::api::common::StreamFailure>(),
26 size_of::<api::heddle::api::common::HumanVerificationChallenge>(),
27 size_of::<api::heddle::api::common::OAuthLinkChallenge>(),
28 size_of::<TransferObject>(),
29 size_of::<ObjectAddress>(),
30 size_of::<String>(),
31 size_of::<Vec<u8>>(),
32];
33fn opaque_node_factor() -> usize {
34 OPAQUE_NODE_SIZES.iter().copied().max().unwrap_or(0) * 4
35}
36
37type Result<T> = std::result::Result<T, transport::Error>;
38fn invalid() -> transport::Error {
39 transport::Error::Protocol("live replication input shape exceeds bounds")
40}
41struct Field<'a> {
42 tag: u32,
43 data: Option<&'a [u8]>,
44}
45fn varint(bytes: &mut &[u8]) -> Result<u64> {
46 let mut value = 0u64;
47 for shift in (0..70).step_by(7) {
48 let (&byte, rest) = bytes.split_first().ok_or_else(invalid)?;
49 *bytes = rest;
50 if shift == 63 && byte > 1 {
51 return Err(invalid());
52 }
53 value |= u64::from(byte & 127) << shift;
54 if byte < 128 {
55 return Ok(value);
56 }
57 }
58 Err(invalid())
59}
60fn field<'a>(bytes: &mut &'a [u8]) -> Result<Option<Field<'a>>> {
61 if bytes.is_empty() {
62 return Ok(None);
63 }
64 let key = varint(bytes)?;
65 let tag = u32::try_from(key >> 3).map_err(|_| invalid())?;
66 if tag == 0 {
67 return Err(invalid());
68 }
69 let data = match key & 7 {
70 0 => {
71 varint(bytes)?;
72 None
73 }
74 1 | 5 => {
75 let n = if key & 7 == 1 { 8 } else { 4 };
76 *bytes = bytes.get(n..).ok_or_else(invalid)?;
77 None
78 }
79 2 => {
80 let n = usize::try_from(varint(bytes)?).map_err(|_| invalid())?;
81 let value = bytes.get(..n).ok_or_else(invalid)?;
82 *bytes = &bytes[n..];
83 Some(value)
84 }
85 _ => return Err(invalid()),
86 };
87 Ok(Some(Field { tag, data }))
88}
89fn data<'a>(field: &Field<'a>) -> Result<&'a [u8]> {
90 field.data.ok_or_else(invalid)
91}
92fn count(value: &mut usize, max: usize) -> Result<()> {
93 *value += 1;
94 if *value > max { Err(invalid()) } else { Ok(()) }
95}
96fn id(field: &Field<'_>) -> Result<()> {
97 if data(field)?.len() == 32 {
98 Ok(())
99 } else {
100 Err(invalid())
101 }
102}
103fn signature(mut bytes: &[u8]) -> Result<()> {
104 let (mut keys, mut signatures) = (0, 0);
105 while let Some(f) = field(&mut bytes)? {
106 match f.tag {
107 1 => {
108 count(&mut keys, 1)?;
109 id(&f)?;
110 }
111 2 => {
112 count(&mut signatures, 1)?;
113 if data(&f)?.len() != 64 {
114 return Err(invalid());
115 }
116 }
117 _ => {}
118 }
119 }
120 if keys == 1 && signatures == 1 {
121 Ok(())
122 } else {
123 Err(invalid())
124 }
125}
126fn record(mut bytes: &[u8]) -> Result<()> {
127 let (mut formats, mut canonicals, mut signatures) = (0, 0, 0);
128 while let Some(f) = field(&mut bytes)? {
129 match f.tag {
130 1 => {
131 count(&mut formats, 1)?;
132 if data(&f)?.len() > 128 {
133 return Err(invalid());
134 }
135 }
136 2 => {
137 count(&mut canonicals, 1)?;
138 data(&f)?;
139 }
140 3 => {
141 count(&mut signatures, 1)?;
142 signature(data(&f)?)?;
143 }
144 _ => {}
145 }
146 }
147 if formats == 1 && canonicals == 1 && signatures == 1 {
148 Ok(())
149 } else {
150 Err(invalid())
151 }
152}
153fn frontier(mut bytes: &[u8], heads: &mut usize, max: usize) -> Result<()> {
154 while let Some(f) = field(&mut bytes)? {
155 if f.tag == 2 {
156 count(heads, max)?;
157 id(&f)?;
158 }
159 }
160 Ok(())
161}
162fn failure(mut bytes: &[u8], opaque: &mut usize) -> Result<()> {
163 while let Some(f) = field(&mut bytes)? {
164 match f.tag {
165 2 => {
166 data(&f)?;
167 }
168 4 => {
172 *opaque = opaque.checked_add(data(&f)?.len()).ok_or_else(invalid)?;
173 }
174 _ => {}
175 }
176 }
177 Ok(())
178}
179fn rejection(mut bytes: &[u8], opaque: &mut usize) -> Result<()> {
180 while let Some(f) = field(&mut bytes)? {
181 match f.tag {
182 1 => id(&f)?,
183 2 => failure(data(&f)?, opaque)?,
184 _ => {}
185 }
186 }
187 Ok(())
188}
189pub fn reservation(bytes: &[u8], max: usize) -> Result<usize> {
193 if max == 0 || max > 64 || bytes.len() > FRAME_LIMIT {
194 return Err(invalid());
195 }
196 let original_len = bytes.len();
197 let mut bytes = bytes;
198 let mut body_count = 0;
199 let mut objects = 0usize;
200 let mut opaque = 0usize;
201 while let Some(body) = field(&mut bytes)? {
202 if !(2..=5).contains(&body.tag) {
203 return Err(invalid());
204 }
205 count(&mut body_count, 1)?;
206 let mut inner = data(&body)?;
207 let (mut first, mut second, mut third, mut fourth, mut fifth) = (0, 0, 0, 0, 0);
208 let mut heads = 0;
209 while let Some(f) = field(&mut inner)? {
210 match (body.tag, f.tag) {
211 (2, 1) => {
212 count(&mut first, max)?;
213 frontier(data(&f)?, &mut heads, max)?;
214 objects += 1;
215 }
216 (3, 1) => {
217 count(&mut first, max)?;
218 id(&f)?;
219 objects += 1;
220 }
221 (4, 1) => {
222 count(&mut first, max)?;
223 record(data(&f)?)?;
224 objects += 3;
225 }
226 (4, 2) => {
227 count(&mut second, max)?;
228 record(data(&f)?)?;
229 objects += 3;
230 }
231 (4, 3) => {
232 count(&mut third, max)?;
233 record(data(&f)?)?;
234 objects += 3;
235 }
236 (5, 1) | (5, 2) => {
237 count(&mut first, max)?;
238 id(&f)?;
239 objects += 1;
240 }
241 (5, 3) => {
242 count(&mut first, max)?;
243 count(&mut third, max)?;
244 rejection(data(&f)?, &mut opaque)?;
245 objects += 3;
246 }
247 (5, 4) => {
248 count(&mut fourth, max)?;
249 frontier(data(&f)?, &mut heads, max)?;
250 objects += 1;
251 }
252 (5, 5) => {
253 count(&mut fifth, max)?;
254 opaque = opaque.checked_add(data(&f)?.len()).ok_or_else(invalid)?;
255 objects += 1;
256 }
257 (5, 6) => {
258 data(&f)?;
259 }
260 _ => {}
261 }
262 }
263 }
264 if body_count != 1 {
265 return Err(invalid());
266 }
267 let container = size_of::<crate::replication::Frame>()
268 + size_of::<crate::replication::InputUnit>()
269 + size_of::<SignedRecord>()
270 + size_of::<RecordSignature>()
271 + size_of::<ReplicationRejection>();
272 original_len
273 .checked_mul(6)
274 .and_then(|value| value.checked_add((objects + 4) * 4 * container))
275 .and_then(|value| value.checked_add(opaque.saturating_mul(opaque_node_factor())))
276 .ok_or_else(invalid)
277}
278
279#[cfg(test)]
280mod tests {
281 use prost::Message;
282
283 use super::*;
284 fn record() -> SignedRecord {
285 SignedRecord {
286 format: "fixture".into(),
287 canonical_record: vec![1; 32],
288 signatures: vec![RecordSignature {
289 public_key: vec![2; 32],
290 signature: vec![3; 64],
291 }],
292 }
293 }
294 fn batch(operations: Vec<SignedRecord>, authority_admissions: Vec<SignedRecord>) -> Vec<u8> {
295 ReplicateThreadRequest {
296 body: Some(replicate_thread_request::Body::Operations(
297 ReplicationOperations {
298 operations,
299 authority_admissions,
300 boundary_acceptances: vec![],
301 },
302 )),
303 }
304 .encode_to_vec()
305 }
306 #[test]
307 fn protobuf_shape_preflight_bounds_record_expansion_before_decode() {
308 let valid = record();
309 let bytes = batch(vec![valid.clone(); 64], vec![valid.clone(); 64]);
310 assert!(
311 reservation(&bytes, 64).expect("maximum separate record and receipt vectors")
312 > bytes.len()
313 );
314 assert!(
315 reservation(&bytes, 63).is_err(),
316 "negotiated count applies before protobuf decode"
317 );
318 let mut duplicate = valid.clone();
319 duplicate.signatures.push(valid.signatures[0].clone());
320 assert!(
321 reservation(&batch(vec![duplicate], vec![]), 64).is_err(),
322 "multiple nested signatures must fail shape preflight"
323 );
324 let mut short = valid;
325 short.signatures[0].signature.truncate(63);
326 assert!(
327 reservation(&batch(vec![short], vec![]), 64).is_err(),
328 "signature shape is checked without allocating decoded vectors"
329 );
330 assert!(reservation(&[0x22, 0xff], 64).is_err(), "truncated length");
331 }
332 #[test]
333 fn protobuf_shape_bounds_boundary_acceptance_carriers() {
334 let valid = record();
335 let encode = |records| {
336 ReplicateThreadRequest {
337 body: Some(replicate_thread_request::Body::Operations(
338 ReplicationOperations {
339 operations: vec![valid.clone()],
340 authority_admissions: vec![],
341 boundary_acceptances: records,
342 },
343 )),
344 }
345 .encode_to_vec()
346 };
347 let maximum = encode(vec![valid.clone(); 64]);
348 assert!(reservation(&maximum, 64).expect("bounded acceptance records") > maximum.len());
349 assert!(
350 reservation(&maximum, 63).is_err(),
351 "acceptance count obeys negotiated bound"
352 );
353 let mut duplicate = valid.clone();
354 duplicate.signatures.push(valid.signatures[0].clone());
355 assert!(
356 reservation(&encode(vec![duplicate]), 64).is_err(),
357 "acceptance signature expansion is rejected before decode"
358 );
359 }
360 #[test]
361 fn protobuf_shape_preserves_legal_receipt_details_and_accounts_nested_layouts() {
362 use api::heddle::api::common::{
363 CallFailure, CapabilityRequirement, ErrorDetail, error_detail::Context,
364 };
365 let failure = CallFailure {
366 code: 9,
367 message: "needs a capability".into(),
368 error: Some(ErrorDetail {
369 context: Some(Context::Capability(CapabilityRequirement {
370 capabilities: vec![String::new(); 128],
371 })),
372 ..Default::default()
373 }),
374 };
375 let receipt = ReplicationReceipt {
376 rejected: vec![ReplicationRejection {
377 operation_id: vec![7; 32],
378 failure: Some(failure),
379 }],
380 accepted_frontiers: vec![CausalFrontier {
381 facet: 1,
382 heads: vec![vec![8; 32]],
383 }],
384 missing_objects: vec![TransferObject {
385 address: Some(ObjectAddress {
386 algorithm: "blake3".into(),
387 digest: vec![9; 32],
388 }),
389 kind: "source".into(),
390 ..Default::default()
391 }],
392 sharing_policy_version: vec![5; 32],
393 ..Default::default()
394 };
395 let response = ReplicateThreadResponse {
396 body: Some(replicate_thread_response::Body::Receipt(receipt)),
397 };
398 let bytes = response.encode_to_vec();
399 let reserved = reservation(&bytes, 64)
400 .expect("preserve all existing receipt capability and coverage fields");
401 assert_eq!(
402 ReplicateThreadResponse::decode(bytes.as_slice()).expect("prost response"),
403 response
404 );
405 assert_eq!(
406 OPAQUE_NODE_SIZES.len(),
407 17,
408 "all currently recursive error/transfer node layouts enumerated"
409 );
410 assert!(
411 OPAQUE_NODE_SIZES
412 .iter()
413 .all(|size| opaque_node_factor() >= 4 * size)
414 );
415 assert!(
416 reserved > 128 * size_of::<String>() + bytes.len(),
417 "empty repeated strings still consume vector slots"
418 );
419 }
420}