1use crate::{
6 Admission, Crdt, EnableWinsFlag, EnableWinsFlagDelta, EventLog, GCounterDelta, GSet, LwwMap,
7 LwwMapDelta, LwwRegister, LwwRegisterDelta, OrSet, OrSetDelta, PnCounterDelta, Record, Rga,
8 RgaDelta, VersionVector,
9};
10use alloc::{borrow::Cow, string::String, vec::Vec};
11
12pub(super) const TAG_RECORD: u8 = 0x01;
13pub(super) const TAG_EVENT_LOG_LEGACY: u8 = 0x02;
15pub(super) const TAG_EVENT_LOG: u8 = 0x03;
16pub(super) const TAG_VERSION_VECTOR: u8 = 0x04;
17pub(super) const TAG_GCOUNTER_DELTA: u8 = 0x10;
18pub(super) const TAG_PNCOUNTER_INC: u8 = 0x11;
19pub(super) const TAG_PNCOUNTER_DEC: u8 = 0x12;
20pub(super) const TAG_GSET_U64: u8 = 0x20;
21pub(super) const TAG_ORSET_U64: u8 = 0x30;
22pub(super) const TAG_ORSET_STRING: u8 = 0x35;
23pub(super) const TAG_ORSET_ADD_U64: u8 = 0x31;
24pub(super) const TAG_ORSET_REMOVE_U64: u8 = 0x32;
25pub(super) const TAG_ORSET_ADD_STRING: u8 = 0x33;
26pub(super) const TAG_ORSET_REMOVE_STRING: u8 = 0x34;
27pub(super) const TAG_RGA_U64: u8 = 0x40;
28pub(super) const TAG_RGA_INSERT_U64: u8 = 0x41;
29pub(super) const TAG_RGA_DELETE_U64: u8 = 0x42;
30pub(super) const TAG_LWW_REGISTER_DELTA_U64: u8 = 0x50;
31pub(super) const TAG_LWW_REGISTER_U64: u8 = 0x51;
32pub(super) const TAG_ENABLE_WINS_FLAG_ENABLE_U64: u8 = 0x60;
33pub(super) const TAG_ENABLE_WINS_FLAG_DISABLE_U64: u8 = 0x61;
34pub(super) const TAG_ENABLE_WINS_FLAG_U64: u8 = 0x62;
35pub(super) const TAG_LWW_MAP_SET_U64: u8 = 0x70;
36pub(super) const TAG_LWW_MAP_REMOVE_U64: u8 = 0x71;
37pub(super) const TAG_LWW_MAP_U64: u8 = 0x72;
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum WireError {
42 OwnershipViolation,
43 ZeroSequenceRecord {
45 replica: u64,
46 kind: &'static str,
47 },
48 ZeroSequenceAdd {
52 replica: u64,
54 },
55 ZeroSequenceRemove {
57 replica: u64,
59 },
60 UnexpectedEof,
61 InvalidTag,
62 TrailingBytes,
63 LengthOverflow,
64 RecordCollision,
65 DuplicateEntry,
66 NonCanonicalOrder,
68 IntegrityMismatch,
69 InvalidUtf8,
70 MissingShape,
72 LegacyEventLogFrame {
77 found: LegacyFrame,
78 },
79 DeltaTypeMismatch,
80 ReplicaCountMismatch {
81 expected: usize,
82 actual: usize,
83 },
84 ArityKindMismatch,
86 VersionAuthorLimitExceeded {
87 max_authors: usize,
88 },
89 VersionZeroReplicaLimitExceeded {
90 max_zero_replicas: usize,
91 },
92 CollectionElementLimitExceeded {
93 max_elements: usize,
94 },
95 NestedRecordLimitExceeded {
96 max_records: usize,
97 },
98 RecordLimitExceeded {
99 max_records: usize,
100 },
101 DefaultRecordLimitExceeded {
103 max_records: usize,
104 },
105 NonCanonicalVersionVector,
106}
107
108impl core::fmt::Display for WireError {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 match self {
111 Self::OwnershipViolation => {
112 f.write_str("record violates replica ownership or coordinate bounds")
113 }
114 Self::ZeroSequenceRecord { replica, kind } => write!(
115 f,
116 "{kind} record (replica {replica}, sequence 0) refused: record sequences start at 1. \
117 Recovery: remove this record from any stored log, issue the edit again from replica {replica} \
118 at a positive sequence, and write the log again"
119 ),
120 Self::ZeroSequenceAdd { replica } => write!(
121 f,
122 "OR-Set add record (replica {replica}, sequence 0) refused: add sequences start at 1. \
123 Recovery: re-add the element from replica {replica} at a positive sequence, \
124 and remove this record from any stored log before loading it again"
125 ),
126 Self::ZeroSequenceRemove { replica } => write!(
127 f,
128 "OR-Set remove record (replica {replica}, sequence 0) refused: remove sequences start at 1. \
129 Recovery: remove this record from any stored log, issue the remove again from replica {replica} \
130 at a positive sequence, and write the log again"
131 ),
132 Self::UnexpectedEof => f.write_str("unexpected end of wire input"),
133 Self::InvalidTag => f.write_str("unexpected wire tag"),
134 Self::TrailingBytes => f.write_str("unexpected trailing bytes after wire value"),
135 Self::LengthOverflow => f.write_str("wire length exceeds the representable range"),
136 Self::RecordCollision => f.write_str("record identity has conflicting payloads"),
137 Self::DuplicateEntry => {
138 f.write_str("wire state contains a duplicate entry")
139 }
140 Self::NonCanonicalOrder => f.write_str("wire state entries are not in ascending order"),
141 Self::IntegrityMismatch => f.write_str("wire frame integrity check failed"),
142 Self::InvalidUtf8 => f.write_str("wire string contains invalid UTF-8"),
143 Self::MissingShape => f.write_str("wire frame is missing required shape metadata"),
144 Self::LegacyEventLogFrame { found } => write!(
145 f,
146 "legacy EventLog frame: found {found}, expected tag 0x03 with shape header; \
147 migrate once with EventLog::migrate_legacy_wire_bytes_for(bytes, &destination) \
148 or `cargo run -p safemesh-crdt --example migrate_event_log`, giving the \
149 original replica count (safemesh-crdt README, \"Migrating a legacy EventLog\")"
150 ),
151 Self::DeltaTypeMismatch => {
152 f.write_str("wire delta schema does not match the expected type")
153 }
154 Self::ReplicaCountMismatch { expected, actual } => write!(
155 f,
156 "replica-count mismatch: expected={expected}, actual={actual}"
157 ),
158 Self::ArityKindMismatch => {
159 f.write_str("invalid or incompatible replica-domain arity kind")
160 }
161 Self::VersionAuthorLimitExceeded { max_authors } => {
162 write!(f, "wire version exceeds author limit {max_authors}")
163 }
164 Self::VersionZeroReplicaLimitExceeded { max_zero_replicas } => {
165 write!(
166 f,
167 "wire version exceeds zero-acknowledgement limit {max_zero_replicas}"
168 )
169 }
170 Self::CollectionElementLimitExceeded { max_elements } => {
171 write!(f, "wire collection exceeds element limit {max_elements}")
172 }
173 Self::NestedRecordLimitExceeded { max_records } => {
174 write!(f, "nested wire log exceeds record limit {max_records}")
175 }
176 Self::RecordLimitExceeded { max_records } => {
177 write!(f, "RecordLimitExceeded: {max_records}")
178 }
179 Self::DefaultRecordLimitExceeded { max_records } => {
180 write!(f, "RecordLimitExceeded: {max_records}; pass DecodeLimits::UNLIMITED to retry")
181 }
182 Self::NonCanonicalVersionVector => f.write_str("noncanonical wire version vector"),
183 }
184 }
185}
186
187impl core::error::Error for WireError {}
188
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum LegacyFrame {
193 Tag02,
195 Tag03Unshaped,
197}
198
199impl core::fmt::Display for LegacyFrame {
200 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201 f.write_str(match self {
202 Self::Tag02 => "tag 0x02 (no CRC, no shape header)",
203 Self::Tag03Unshaped => "tag 0x03 without shape header",
204 })
205 }
206}
207
208#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
210pub struct CollectionLimits {
211 pub max_elements: Option<usize>,
213}
214
215impl CollectionLimits {
216 pub const WIRE_DEFAULT: Self = Self {
218 max_elements: Some(4096),
219 };
220}
221
222#[derive(Clone, Copy, Debug, PartialEq, Eq)]
224pub struct DecodeLimits {
225 pub max_records: Option<usize>,
230 pub max_collection_elements: Option<usize>,
233}
234
235impl DecodeLimits {
236 pub const DEFAULT_MAX_RECORDS: usize = 100_000;
238 pub const UNLIMITED: Self = Self {
240 max_records: None,
241 max_collection_elements: None,
242 };
243}
244
245impl Default for DecodeLimits {
246 fn default() -> Self {
247 Self {
248 max_records: Some(Self::DEFAULT_MAX_RECORDS),
249 max_collection_elements: None,
250 }
251 }
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq)]
257pub enum DecodeError {
258 Wire(WireError),
259 RecordLimitExceeded {
261 max_records: usize,
262 },
263 DefaultRecordLimitExceeded {
265 max_records: usize,
266 },
267}
268
269impl DecodeError {
270 pub fn with_default_record_limit(self) -> Self {
272 match self {
273 Self::RecordLimitExceeded { max_records } => {
274 Self::DefaultRecordLimitExceeded { max_records }
275 }
276 other => other,
277 }
278 }
279}
280
281impl From<WireError> for DecodeError {
282 fn from(error: WireError) -> Self {
283 Self::Wire(error)
284 }
285}
286
287impl From<DecodeError> for WireError {
288 fn from(error: DecodeError) -> Self {
289 match error {
290 DecodeError::Wire(error) => error,
291 DecodeError::RecordLimitExceeded { max_records } => {
292 Self::RecordLimitExceeded { max_records }
293 }
294 DecodeError::DefaultRecordLimitExceeded { max_records } => {
295 Self::DefaultRecordLimitExceeded { max_records }
296 }
297 }
298 }
299}
300
301pub trait WireSchema {
305 fn wire_schema() -> Cow<'static, [u8]>;
307 const REQUIRES_ARITY: bool = false;
308}
309
310macro_rules! wire_schema {
311 ($ty:ty, $name:literal, $fixed:expr) => {
312 impl WireSchema for $ty {
313 fn wire_schema() -> Cow<'static, [u8]> {
314 Cow::Borrowed($name.as_bytes())
315 }
316 const REQUIRES_ARITY: bool = $fixed;
317 }
318 };
319}
320
321wire_schema!(GCounterDelta, "safemesh/gcounter-delta/v1", true);
322wire_schema!(PnCounterDelta, "safemesh/pncounter-delta/v1", true);
323wire_schema!(GSet<u64>, "safemesh/gset-u64/v1", false);
324wire_schema!(OrSetDelta<u64, u64>, "safemesh/orset-delta-u64-u64/v1", false);
325wire_schema!(OrSetDelta<String, u64>, "safemesh/orset-delta-utf8-u64/v1", false);
326wire_schema!(OrSet<u64, u64>, "safemesh/orset-u64-u64/v1", false);
327wire_schema!(OrSet<String, u64>, "safemesh/orset-utf8-u64/v1", false);
328wire_schema!(Rga<u64, u64>, "safemesh/rga-u64-u64/v1", false);
329wire_schema!(RgaDelta<u64, u64>, "safemesh/rga-delta-u64-u64/v1", false);
330wire_schema!(VersionVector, "safemesh/version-vector/v1", false);
331wire_schema!(
332 LwwRegisterDelta<u64>,
333 "safemesh/lww-register-delta-u64/v1",
334 false
335);
336wire_schema!(LwwRegister<u64>, "safemesh/lww-register-u64/v1", false);
337wire_schema!(
338 EnableWinsFlagDelta<u64>,
339 "safemesh/enable-wins-flag-delta-u64/v1",
340 false
341);
342wire_schema!(
343 EnableWinsFlag<u64>,
344 "safemesh/enable-wins-flag-u64/v1",
345 false
346);
347wire_schema!(LwwMapDelta<u64, u64>, "safemesh/lww-map-delta-u64-u64/v1", false);
348wire_schema!(LwwMap<u64, u64>, "safemesh/lww-map-u64-u64/v1", false);
349
350impl<D: WireSchema> WireSchema for Record<D> {
351 fn wire_schema() -> Cow<'static, [u8]> {
352 let mut schema = b"safemesh/record/v1/".to_vec();
353 schema.extend_from_slice(D::wire_schema().as_ref());
354 Cow::Owned(schema)
355 }
356}
357
358impl<D: WireSchema> WireSchema for EventLog<D> {
359 fn wire_schema() -> Cow<'static, [u8]> {
360 let mut schema = b"safemesh/event-log/v2/".to_vec();
361 schema.extend_from_slice(D::wire_schema().as_ref());
362 Cow::Owned(schema)
363 }
364}
365
366impl<D: WireDecode + WireSchema + PartialEq> EventLog<D> {
367 pub fn from_wire_bytes_with_limits(
392 bytes: &[u8],
393 limits: DecodeLimits,
394 ) -> Result<Self, DecodeError> {
395 let mut cursor = WireCursor::new(bytes);
396 let log = Self::decode_with(
397 &mut cursor,
398 CollectionLimits {
399 max_elements: limits
400 .max_collection_elements
401 .or(CollectionLimits::WIRE_DEFAULT.max_elements),
402 },
403 limits.max_records,
404 |_| {},
405 |index| {
406 if let Some(max_records) = limits.max_records {
407 if index >= max_records {
408 return Err(DecodeError::RecordLimitExceeded { max_records });
409 }
410 }
411 Ok(())
412 },
413 )?;
414 if !cursor.is_empty() {
415 return Err(WireError::TrailingBytes.into());
416 }
417 Ok(log)
418 }
419
420 pub fn from_wire_bytes_for<C: Crdt<Delta = D>>(
424 bytes: &[u8],
425 state: &C,
426 ) -> Result<Self, WireError> {
427 let log = Self::from_wire_bytes_with_limits(bytes, DecodeLimits::default())
428 .map_err(|error| WireError::from(error.with_default_record_limit()))?;
429 log.validate_for(state)?;
430 Ok(log)
431 }
432
433 pub fn from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
436 bytes: &[u8],
437 state: &C,
438 limits: DecodeLimits,
439 ) -> Result<Self, DecodeError> {
440 let log = Self::from_wire_bytes_with_limits(bytes, limits)?;
441 log.validate_for(state)?;
442 Ok(log)
443 }
444
445 pub fn records_from_wire_bytes_for<C: Crdt<Delta = D>>(
449 bytes: &[u8],
450 state: &C,
451 ) -> Result<Vec<Record<D>>, WireError>
452 where
453 D: Clone,
454 {
455 Self::records_from_wire_bytes_for_with_limits(bytes, state, DecodeLimits::default())
456 .map_err(|error| WireError::from(error.with_default_record_limit()))
457 }
458
459 pub fn records_from_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
462 bytes: &[u8],
463 state: &C,
464 limits: DecodeLimits,
465 ) -> Result<Vec<Record<D>>, DecodeError>
466 where
467 D: Clone,
468 {
469 let mut records = Vec::new();
470 let mut cursor = WireCursor::new(bytes);
471 let log = Self::decode_with(
472 &mut cursor,
473 CollectionLimits {
474 max_elements: limits
475 .max_collection_elements
476 .or(CollectionLimits::WIRE_DEFAULT.max_elements),
477 },
478 limits.max_records,
479 |record| records.push(record.clone()),
480 |index| {
481 if let Some(max_records) = limits.max_records {
482 if index >= max_records {
483 return Err(DecodeError::RecordLimitExceeded { max_records });
484 }
485 }
486 Ok(())
487 },
488 )?;
489 if !cursor.is_empty() {
490 return Err(WireError::TrailingBytes.into());
491 }
492 log.validate_for(state)?;
493 Ok(records)
494 }
495
496 fn validate_for<C: Crdt<Delta = D>>(&self, state: &C) -> Result<(), WireError> {
497 self.validate_shape(state)?;
498 for record in self.records() {
499 state.validate_record(record.id, &record.delta)?;
500 }
501 Ok(())
502 }
503}
504
505impl<D: WireDecode + WireEncode + WireSchema + PartialEq> EventLog<D> {
506 pub fn migrate_legacy_wire_bytes_for<C: Crdt<Delta = D>>(
536 bytes: &[u8],
537 state: &C,
538 ) -> Result<Vec<u8>, WireError> {
539 match Self::migrate_legacy_wire_bytes_for_with_limits(bytes, state, DecodeLimits::default())
540 {
541 Ok(bytes) => Ok(bytes),
542 Err(DecodeError::Wire(error)) => Err(error),
543 Err(DecodeError::RecordLimitExceeded { max_records })
544 | Err(DecodeError::DefaultRecordLimitExceeded { max_records }) => {
545 Err(WireError::DefaultRecordLimitExceeded { max_records })
546 }
547 }
548 }
549
550 pub fn migrate_legacy_wire_bytes_for_with_limits<C: Crdt<Delta = D>>(
556 bytes: &[u8],
557 state: &C,
558 limits: DecodeLimits,
559 ) -> Result<Vec<u8>, DecodeError> {
560 let mut cursor = WireCursor::new(bytes);
561 let mut body = match cursor.read_u8()? {
562 TAG_EVENT_LOG_LEGACY => cursor,
563 TAG_EVENT_LOG => {
564 let body = read_checked_body(&mut cursor)?;
565 if !cursor.is_empty() {
566 return Err(WireError::TrailingBytes.into());
567 }
568 if WireCursor::new(body).read_u32()? == u32::MAX {
569 Self::from_wire_bytes_for_with_limits(bytes, state, limits)?;
570 return Ok(bytes.to_vec());
571 }
572 WireCursor::new(body)
573 }
574 _ => return Err(WireError::InvalidTag.into()),
575 };
576 let mut log = EventLog::for_crdt(state);
577 for index in 0..body.read_len()? {
578 if let Some(max_records) = limits.max_records {
579 if index >= max_records {
580 return Err(DecodeError::RecordLimitExceeded { max_records });
581 }
582 }
583 let record_len = body.read_len()?;
584 let mut record_cursor = WireCursor::new(body.read_exact(record_len)?);
585 let record = Record::<D>::decode_wire_with_limits(
586 &mut record_cursor,
587 CollectionLimits {
588 max_elements: limits
589 .max_collection_elements
590 .or(CollectionLimits::WIRE_DEFAULT.max_elements),
591 },
592 limits.max_records,
593 )?;
594 if !record_cursor.is_empty() {
595 return Err(WireError::TrailingBytes.into());
596 }
597 match log.identity_admission(&record) {
598 Admission::Collision => return Err(WireError::RecordCollision.into()),
599 Admission::Accepted => log.commit_record(record),
600 Admission::Duplicate => {}
601 Admission::Invalid(_) => unreachable!("identity check does not validate a carrier"),
602 }
603 }
604 if !body.is_empty() {
605 return Err(WireError::TrailingBytes.into());
606 }
607 log.validate_for(state)?;
608 Ok(log.to_wire_bytes()?)
609 }
610}
611
612pub trait WireEncode {
616 fn encode_wire(&self, out: &mut Vec<u8>) -> Result<(), WireError>;
617
618 fn to_wire_bytes(&self) -> Result<Vec<u8>, WireError> {
619 let mut out = Vec::new();
620 self.encode_wire(&mut out)?;
621 Ok(out)
622 }
623}
624
625pub trait WireDecode: Sized {
648 fn decode_wire(cursor: &mut WireCursor<'_>) -> Result<Self, WireError>;
649
650 fn decode_wire_with_collection_limits(
653 cursor: &mut WireCursor<'_>,
654 _limits: CollectionLimits,
655 ) -> Result<Self, WireError> {
656 Self::decode_wire(cursor)
657 }
658
659 fn decode_wire_with_limits(
661 cursor: &mut WireCursor<'_>,
662 collections: CollectionLimits,
663 _max_records: Option<usize>,
664 ) -> Result<Self, WireError> {
665 Self::decode_wire_with_collection_limits(cursor, collections)
666 }
667
668 fn from_wire_bytes_with_collection_limits(
670 bytes: &[u8],
671 limits: CollectionLimits,
672 ) -> Result<Self, WireError> {
673 let mut cursor = WireCursor::new(bytes);
674 let value = Self::decode_wire_with_collection_limits(&mut cursor, limits)?;
675 if !cursor.is_empty() {
676 return Err(WireError::TrailingBytes);
677 }
678 Ok(value)
679 }
680
681 fn from_wire_bytes(bytes: &[u8]) -> Result<Self, WireError> {
682 let mut cursor = WireCursor::new(bytes);
683 let value = Self::decode_wire(&mut cursor)?;
684 if cursor.is_empty() {
685 Ok(value)
686 } else {
687 Err(WireError::TrailingBytes)
688 }
689 }
690}
691
692impl<D: WireDecode> Record<D> {
693 pub fn from_wire_bytes_with_collection_limits(
695 bytes: &[u8],
696 limits: CollectionLimits,
697 ) -> Result<Self, WireError> {
698 let mut cursor = WireCursor::new(bytes);
699 let record = Self::decode_wire_with_collection_limits(&mut cursor, limits)?;
700 if !cursor.is_empty() {
701 return Err(WireError::TrailingBytes);
702 }
703 Ok(record)
704 }
705}
706
707pub struct WireCursor<'a> {
716 bytes: &'a [u8],
717 offset: usize,
718}
719
720impl<'a> WireCursor<'a> {
721 pub fn new(bytes: &'a [u8]) -> Self {
722 WireCursor { bytes, offset: 0 }
723 }
724
725 pub fn is_empty(&self) -> bool {
726 self.offset == self.bytes.len()
727 }
728
729 pub fn read_u8(&mut self) -> Result<u8, WireError> {
731 let byte = *self
732 .bytes
733 .get(self.offset)
734 .ok_or(WireError::UnexpectedEof)?;
735 self.offset += 1;
736 Ok(byte)
737 }
738
739 pub fn read_u32(&mut self) -> Result<u32, WireError> {
741 let bytes = self.read_exact(4)?;
742 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
743 }
744
745 pub fn read_u64(&mut self) -> Result<u64, WireError> {
747 let bytes = self.read_exact(8)?;
748 Ok(u64::from_le_bytes([
749 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
750 ]))
751 }
752
753 pub fn read_len(&mut self) -> Result<usize, WireError> {
757 usize::try_from(self.read_u32()?).map_err(|_| WireError::LengthOverflow)
758 }
759
760 pub fn read_exact(&mut self, len: usize) -> Result<&'a [u8], WireError> {
765 let end = self
766 .offset
767 .checked_add(len)
768 .ok_or(WireError::LengthOverflow)?;
769 let bytes = self
770 .bytes
771 .get(self.offset..end)
772 .ok_or(WireError::UnexpectedEof)?;
773 self.offset = end;
774 Ok(bytes)
775 }
776}
777
778pub fn write_u8(out: &mut Vec<u8>, value: u8) {
780 out.push(value);
781}
782
783pub fn write_u32(out: &mut Vec<u8>, value: u32) {
785 out.extend_from_slice(&value.to_le_bytes());
786}
787
788pub fn write_u64(out: &mut Vec<u8>, value: u64) {
790 out.extend_from_slice(&value.to_le_bytes());
791}
792
793pub fn write_len(out: &mut Vec<u8>, len: usize) -> Result<(), WireError> {
797 let len = u32::try_from(len).map_err(|_| WireError::LengthOverflow)?;
798 write_u32(out, len);
799 Ok(())
800}
801
802pub fn write_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), WireError> {
806 write_len(out, bytes.len())?;
807 out.extend_from_slice(bytes);
808 Ok(())
809}
810
811pub(super) fn read_tag(cursor: &mut WireCursor<'_>, expected: u8) -> Result<(), WireError> {
815 match cursor.read_u8()? {
816 tag if tag == expected => Ok(()),
817 _ => Err(WireError::InvalidTag),
818 }
819}
820
821pub(super) fn frame_crc32(bytes: &[u8]) -> u32 {
824 let mut crc = u32::MAX;
825 for &byte in bytes {
826 crc ^= u32::from(byte);
827 for _ in 0..8 {
828 crc = (crc >> 1) ^ (0xedb8_8320 & 0u32.wrapping_sub(crc & 1));
829 }
830 }
831 !crc
832}
833
834impl From<LegacyFrame> for WireError {
835 fn from(found: LegacyFrame) -> Self {
836 Self::LegacyEventLogFrame { found }
837 }
838}
839
840fn walks_as_tag02_frame(cursor: &WireCursor<'_>) -> bool {
845 let mut rest = WireCursor::new(&cursor.bytes[cursor.offset..]);
846 let mut walk = || -> Result<(), WireError> {
847 for _ in 0..rest.read_len()? {
848 let len = rest.read_len()?;
849 if rest.read_exact(len)?.first() != Some(&TAG_RECORD) {
850 return Err(WireError::InvalidTag);
851 }
852 }
853 Ok(())
854 };
855 walk().is_ok()
856}
857
858pub(super) fn read_checked_body<'a>(cursor: &mut WireCursor<'a>) -> Result<&'a [u8], WireError> {
861 let start = cursor.offset;
862 let len = cursor.read_u32()?;
863 if cursor.read_u32()? != !len {
864 return Err(WireError::IntegrityMismatch);
865 }
866 let body = cursor.read_exact(usize::try_from(len).map_err(|_| WireError::LengthOverflow)?)?;
867 let checksum = frame_crc32(&cursor.bytes[start..cursor.offset]);
868 if cursor.read_u32()? != checksum {
869 return Err(WireError::IntegrityMismatch);
870 }
871 Ok(body)
872}
873
874impl<D: WireEncode + WireSchema> EventLog<D> {
875 pub fn encode_records(
881 replica_count: Option<usize>,
882 records: &[Record<D>],
883 out: &mut Vec<u8>,
884 ) -> Result<(), WireError> {
885 if D::REQUIRES_ARITY && replica_count.is_none() {
886 return Err(WireError::MissingShape);
887 }
888 let mut body = Vec::new();
889 write_u32(&mut body, u32::MAX);
890 write_bytes(&mut body, D::wire_schema().as_ref())?;
891 match replica_count {
892 Some(count) => {
893 write_u8(&mut body, 1);
894 write_u64(
895 &mut body,
896 u64::try_from(count).map_err(|_| WireError::LengthOverflow)?,
897 );
898 }
899 None => write_u8(&mut body, 0),
900 }
901 write_len(&mut body, records.len())?;
902 for record in records {
903 write_bytes(&mut body, &record.to_wire_bytes()?)?;
904 }
905 let len = u32::try_from(body.len()).map_err(|_| WireError::LengthOverflow)?;
906 write_u8(out, TAG_EVENT_LOG);
907 let start = out.len();
908 write_u32(out, len);
909 write_u32(out, !len);
910 out.extend_from_slice(&body);
911 let checksum = frame_crc32(&out[start..]);
912 write_u32(out, checksum);
913 Ok(())
914 }
915}
916
917impl<D: WireDecode + WireSchema + PartialEq> EventLog<D> {
918 pub(super) fn decode_with<E: From<WireError>>(
919 cursor: &mut WireCursor<'_>,
920 collection_limits: CollectionLimits,
921 nested_max_records: Option<usize>,
922 mut occurrence: impl FnMut(&Record<D>),
923 mut before_record: impl FnMut(usize) -> Result<(), E>,
924 ) -> Result<Self, E> {
925 match cursor.read_u8()? {
926 TAG_EVENT_LOG => {}
927 TAG_EVENT_LOG_LEGACY if walks_as_tag02_frame(cursor) => {
928 return Err(WireError::from(LegacyFrame::Tag02).into())
929 }
930 _ => return Err(WireError::InvalidTag.into()),
931 }
932 let mut body = WireCursor::new(read_checked_body(cursor)?);
933 if body.read_u32()? != u32::MAX {
934 return Err(WireError::from(LegacyFrame::Tag03Unshaped).into());
935 }
936 let schema_len = body.read_len()?;
937 if body.read_exact(schema_len)? != D::wire_schema().as_ref() {
938 return Err(WireError::DeltaTypeMismatch.into());
939 }
940 let replica_count = match body.read_u8()? {
941 0 if !D::REQUIRES_ARITY => None,
942 0 => return Err(WireError::MissingShape.into()),
943 1 => Some(usize::try_from(body.read_u64()?).map_err(|_| WireError::LengthOverflow)?),
944 _ => return Err(WireError::ArityKindMismatch.into()),
945 };
946 let mut log = EventLog {
947 replica_count,
948 shape_bound: true,
949 ..EventLog::new()
950 };
951 let record_count = body.read_len()?;
952 if let Some(max_records) = nested_max_records {
953 if record_count > max_records {
954 before_record(max_records)?;
955 }
956 }
957 for index in 0..record_count {
958 before_record(index)?;
959 let record_len = body.read_len()?;
960 let record_bytes = body.read_exact(record_len)?;
961 let mut record_cursor = WireCursor::new(record_bytes);
962 let record = Record::<D>::decode_wire_with_limits(
963 &mut record_cursor,
964 collection_limits,
965 nested_max_records,
966 )?;
967 if !record_cursor.is_empty() {
968 return Err(WireError::TrailingBytes.into());
969 }
970 occurrence(&record);
971 match log.identity_admission(&record) {
972 Admission::Collision => return Err(WireError::RecordCollision.into()),
973 Admission::Accepted => log.commit_record(record),
974 Admission::Duplicate => {}
975 Admission::Invalid(_) => unreachable!("identity check does not validate a carrier"),
976 }
977 }
978 if !body.is_empty() {
979 return Err(WireError::TrailingBytes.into());
980 }
981 Ok(log)
982 }
983}