1use std::slice::Iter;
4
5use ozzy_journal::operation::{
6 CanonicalOperation, OperationKind, canonical_body_digest,
7 logical_operation_digest_with_body_digest,
8};
9use ozzy_proto::NodeId;
10use ozzy_proto::{Envelope, Opcode};
11
12use super::{
13 COMMON_BYTES, EncodeContext, Reader, WireError, WireLimits, Writer, validate_prefix,
14 validate_scope,
15};
16use super::{ReadFields, WriteFields};
17use crate::flow::ReceiveEpoch;
18use crate::{Digest, OpNumber, Prefix, Scope};
19
20mod plan;
21use plan::Planned;
22
23pub(super) const DESCRIPTOR_BYTES: usize = 86;
24pub(super) const BATCH_BYTES: usize = COMMON_BYTES + 8 + 32 + 40 + 4;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Operation<'a> {
29 canonical: CanonicalOperation<'a>,
30 body_digest: Digest,
31 digest: Digest,
32}
33
34impl<'a> Operation<'a> {
35 pub fn from_verified(canonical: CanonicalOperation<'a>, body_digest: Digest) -> Self {
40 Self {
41 digest: logical_operation_digest_with_body_digest(&canonical, body_digest),
42 canonical,
43 body_digest,
44 }
45 }
46 pub const fn canonical(self) -> CanonicalOperation<'a> {
48 self.canonical
49 }
50 pub const fn body_digest(self) -> Digest {
52 self.body_digest
53 }
54 pub const fn prefix(self) -> Prefix {
56 Prefix {
57 op: OpNumber(self.canonical.op_number),
58 digest: self.digest,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct VerifiedOperation<'a>(Operation<'a>);
80
81impl<'a> VerifiedOperation<'a> {
82 pub const fn canonical(self) -> CanonicalOperation<'a> {
84 self.0.canonical()
85 }
86
87 pub const fn body_digest(self) -> Digest {
89 self.0.body_digest()
90 }
91
92 pub const fn prefix(self) -> Prefix {
94 self.0.prefix()
95 }
96}
97
98#[derive(Debug, Clone, Copy)]
100pub struct Prepare<'a> {
101 pub scope: Scope,
103 pub committed: Prefix,
105 pub operations: &'a [Operation<'a>],
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct PrepareEncoding {
112 pub header: [u8; 64],
114 pub metadata_bytes: usize,
116 pub payload_bytes: usize,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct PrepareMetadata {
124 pub metadata_bytes: usize,
126 pub payload_bytes: usize,
128}
129
130pub fn encode_prepare_unbound(
134 sender: NodeId,
135 prepare: Prepare<'_>,
136 metadata: &mut [u8],
137 limits: WireLimits,
138) -> Result<PrepareMetadata, WireError> {
139 Planned::new(sender, None, prepare, limits)?.write(metadata, None)
140}
141
142pub fn encode_prepare(
147 context: EncodeContext,
148 prepare: Prepare<'_>,
149 metadata: &mut [u8],
150 payload: &mut [u8],
151) -> Result<PrepareEncoding, WireError> {
152 Planned::new(context.sender, None, prepare, context.limits)?.bind(
153 context,
154 metadata,
155 Some(payload),
156 )
157}
158
159pub fn encode_prepare_metadata(
161 context: EncodeContext,
162 prepare: Prepare<'_>,
163 metadata: &mut [u8],
164) -> Result<PrepareEncoding, WireError> {
165 Planned::new(context.sender, None, prepare, context.limits)?.bind(context, metadata, None)
166}
167
168pub fn encode_flow_prepare(
174 context: EncodeContext,
175 epoch: ReceiveEpoch,
176 prepare: Prepare<'_>,
177 metadata: &mut [u8],
178 payload: &mut [u8],
179) -> Result<PrepareEncoding, WireError> {
180 Planned::new(context.sender, Some(epoch), prepare, context.limits)?.bind(
181 context,
182 metadata,
183 Some(payload),
184 )
185}
186
187pub fn encode_flow_prepare_metadata(
195 context: EncodeContext,
196 epoch: ReceiveEpoch,
197 prepare: Prepare<'_>,
198 metadata: &mut [u8],
199) -> Result<PrepareEncoding, WireError> {
200 Planned::new(context.sender, Some(epoch), prepare, context.limits)?
201 .bind(context, metadata, None)
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub struct PrepareBatch<'a> {
210 committed: Prefix,
211 batch: DecodedBatch<'a>,
212}
213
214impl<'a> PrepareBatch<'a> {
215 pub const fn scope(self) -> Scope {
217 self.batch.scope
218 }
219 pub const fn committed(self) -> Prefix {
221 self.committed
222 }
223 pub const fn predecessor(self) -> Prefix {
225 self.batch.predecessor
226 }
227 pub const fn end(self) -> Prefix {
229 self.batch.accepted
230 }
231 pub fn operations(self) -> Operations<'a> {
233 self.batch.operations()
234 }
235
236 pub fn verified_operations(
238 self,
239 ) -> impl ExactSizeIterator<Item = VerifiedOperation<'a>> + std::iter::FusedIterator {
240 self.operations().map(VerifiedOperation)
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub(super) struct DecodedBatch<'a> {
246 pub scope: Scope,
247 pub predecessor: Prefix,
248 pub accepted: Prefix,
249 descriptors: &'a [u8],
250 pub payload: &'a [u8],
251}
252
253impl<'a> DecodedBatch<'a> {
254 pub(super) fn operations(self) -> Operations<'a> {
255 Operations {
256 descriptors: self.descriptors.as_chunks::<DESCRIPTOR_BYTES>().0.iter(),
257 payload: self.payload,
258 scope: self.scope,
259 previous: self.predecessor,
260 }
261 }
262}
263
264#[derive(Debug, Clone)]
266pub struct Operations<'a> {
267 descriptors: Iter<'a, [u8; DESCRIPTOR_BYTES]>,
268 payload: &'a [u8],
269 scope: Scope,
270 previous: Prefix,
271}
272
273impl<'a> Iterator for Operations<'a> {
274 type Item = Operation<'a>;
275 #[expect(
276 clippy::expect_used,
277 reason = "decode_batch checked every descriptor and body length"
278 )]
279 fn next(&mut self) -> Option<Self::Item> {
280 let descriptor = self.descriptors.next()?;
281 let mut reader = Reader::new(descriptor);
282 let operation = read_operation(&mut reader, &mut self.payload, self.scope, self.previous)
283 .expect("validated batch");
284 self.previous = operation.prefix();
285 Some(operation)
286 }
287 fn size_hint(&self) -> (usize, Option<usize>) {
288 self.descriptors.size_hint()
289 }
290}
291
292impl ExactSizeIterator for Operations<'_> {}
293impl std::iter::FusedIterator for Operations<'_> {}
294
295pub(super) fn decode_prepare<'a>(
296 scope: Scope,
297 mut reader: Reader<'a>,
298 payload: &'a [u8],
299 limits: WireLimits,
300) -> Result<PrepareBatch<'a>, WireError> {
301 let first_op = reader.u64()?;
302 let predecessor = Prefix {
303 op: OpNumber(first_op.checked_sub(1).ok_or(WireError::Chain)?),
304 digest: reader.digest()?,
305 };
306 validate_prefix(predecessor)?;
307 let committed = reader.prefix()?;
308 let chain = Chain {
309 scope,
310 predecessor,
311 committed: Some(committed),
312 };
313 let batch = decode_batch(chain, reader, payload, limits)?;
314 Ok(PrepareBatch { committed, batch })
315}
316
317#[derive(Debug, Clone, Copy)]
319pub(super) struct Chain {
320 pub scope: Scope,
321 pub predecessor: Prefix,
322 pub committed: Option<Prefix>,
323}
324
325impl Chain {
326 fn check_start(self) -> Result<(), WireError> {
327 validate_prefix(self.predecessor)?;
328 self.check_commit(self.predecessor)
329 }
330
331 fn check_commit(self, position: Prefix) -> Result<(), WireError> {
332 match self.committed {
333 Some(committed) if committed.op == position.op && committed != position => {
334 Err(WireError::Chain)
335 }
336 _ => Ok(()),
337 }
338 }
339}
340
341pub(super) fn decode_batch<'a>(
342 chain: Chain,
343 mut reader: Reader<'a>,
344 mut payload: &'a [u8],
345 limits: WireLimits,
346) -> Result<DecodedBatch<'a>, WireError> {
347 chain.check_start()?;
348 let Chain {
349 scope, predecessor, ..
350 } = chain;
351 let count = usize::try_from(reader.u32()?).map_err(|_| WireError::Limit)?;
352 let descriptor_bytes = metadata_length(count, 0, limits)?;
353 if reader.rest().len() != descriptor_bytes {
354 return Err(WireError::Length);
355 }
356 let descriptors = reader.rest();
357 let original_payload = payload;
358 let mut previous = predecessor;
359 for _ in 0..count {
360 let operation = read_operation(&mut reader, &mut payload, scope, previous)?;
361 validate_step(scope, previous, operation)?;
362 if canonical_body_digest(operation.canonical.body) != operation.body_digest
363 || logical_operation_digest_with_body_digest(
364 &operation.canonical,
365 operation.body_digest,
366 ) != operation.digest
367 {
368 return Err(WireError::Digest);
369 }
370 chain.check_commit(operation.prefix())?;
371 previous = operation.prefix();
372 }
373 if !payload.is_empty() {
374 return Err(WireError::Payload);
375 }
376 reader.finish()?;
377 Ok(DecodedBatch {
378 scope,
379 predecessor,
380 accepted: previous,
381 descriptors,
382 payload: original_payload,
383 })
384}
385
386fn read_operation<'a>(
387 reader: &mut Reader<'_>,
388 payload: &mut &'a [u8],
389 scope: Scope,
390 previous: Prefix,
391) -> Result<Operation<'a>, WireError> {
392 let op_number = reader.u64()?;
393 let original_view = reader.u64()?;
394 let kind = u16::from_be_bytes(reader.array()?);
395 let kind = OperationKind::try_from(kind).map_err(|_| WireError::OperationKind(kind))?;
396 let bytes = usize::try_from(reader.u32()?).map_err(|_| WireError::Limit)?;
397 if bytes == 0 {
398 return Err(WireError::Limit);
399 }
400 let body_digest = reader.digest()?;
401 let digest = reader.digest()?;
402 let (body, rest) = payload.split_at_checked(bytes).ok_or(WireError::Length)?;
403 *payload = rest;
404 Ok(Operation {
405 canonical: CanonicalOperation {
406 group_id: scope.group_id,
407 configuration_epoch: scope.configuration_epoch,
408 original_view,
409 op_number,
410 previous_digest: previous.digest,
411 kind,
412 body,
413 },
414 body_digest,
415 digest,
416 })
417}
418
419fn validate_step(
420 scope: Scope,
421 previous: Prefix,
422 operation: Operation<'_>,
423) -> Result<(), WireError> {
424 let canonical = operation.canonical;
425 validate_prefix(operation.prefix())?;
426 if canonical.group_id != scope.group_id
427 || canonical.configuration_epoch != scope.configuration_epoch
428 || canonical.original_view > scope.view
429 || previous.op.0.checked_add(1) != Some(canonical.op_number)
430 || canonical.previous_digest != previous.digest
431 {
432 return Err(WireError::Chain);
433 }
434 Ok(())
435}
436
437pub(super) fn metadata_length(
438 count: usize,
439 fixed_bytes: usize,
440 limits: WireLimits,
441) -> Result<usize, WireError> {
442 if count == 0 || count > limits.max_operations || u32::try_from(count).is_err() {
443 return Err(WireError::Limit);
444 }
445 let bytes = count
446 .checked_mul(DESCRIPTOR_BYTES)
447 .and_then(|bytes| bytes.checked_add(fixed_bytes))
448 .ok_or(WireError::Limit)?;
449 if bytes > limits.envelope.max_metadata_bytes {
450 return Err(WireError::Limit);
451 }
452 Ok(bytes)
453}
454
455pub(super) fn validate_batch(
456 chain: Chain,
457 operations: &[Operation<'_>],
458 limits: WireLimits,
459) -> Result<usize, WireError> {
460 metadata_length(operations.len(), 0, limits)?;
461 chain.check_start()?;
462 let mut previous = chain.predecessor;
463 let mut bytes = 0usize;
464 for &operation in operations {
465 validate_step(chain.scope, previous, operation)?;
466 chain.check_commit(operation.prefix())?;
467 bytes = bytes
468 .checked_add(operation.canonical.body.len())
469 .ok_or(WireError::Limit)?;
470 if bytes > limits.envelope.max_payload_bytes || operation.canonical.body.is_empty() {
471 return Err(WireError::Limit);
472 }
473 previous = operation.prefix();
474 }
475 Ok(bytes)
476}
477
478pub(super) fn write_batch(
482 writer: &mut Writer<'_>,
483 mut payload: Option<&mut [u8]>,
484 operations: &[Operation<'_>],
485) -> Result<(), WireError> {
486 writer.bytes(&(operations.len() as u32).to_be_bytes())?;
487 let mut offset = 0_usize;
488 for &operation in operations {
489 let canonical = operation.canonical;
490 writer.u64(canonical.op_number)?;
491 writer.u64(canonical.original_view)?;
492 writer.bytes(&(canonical.kind as u16).to_be_bytes())?;
493 writer.bytes(&(canonical.body.len() as u32).to_be_bytes())?;
494 writer.bytes(operation.body_digest.as_bytes())?;
495 writer.bytes(operation.digest.as_bytes())?;
496 let end = offset
497 .checked_add(canonical.body.len())
498 .ok_or(WireError::Length)?;
499 if let Some(payload) = payload.as_deref_mut() {
500 let body = payload.get_mut(offset..end).ok_or(WireError::Capacity)?;
501 body.copy_from_slice(canonical.body);
502 }
503 offset = end;
504 }
505 Ok(())
506}