1use crate::{
23 codec::{frame_crc32, read_checked_body, read_tag},
24 write_bytes, write_len, write_u32, write_u64, write_u8, CollectionLimits, DecodeError,
25 DecodeLimits, EventLog, RecordId, WireCursor, WireDecode, WireEncode, WireError, WireSchema,
26};
27use alloc::{borrow::Cow, vec::Vec};
28
29pub(super) const TAG_COLLISION_REPORT: u8 = 0x05;
33
34#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct RecordCollision<D> {
42 pub id: RecordId,
43 pub local: D,
44 pub remote: D,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct CollisionReport<D> {
57 pub collisions: Vec<RecordCollision<D>>,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum CollisionVerdict {
64 Recorded,
67 Known,
69 Unheld,
71 Agrees,
74}
75
76impl<D: WireSchema> WireSchema for CollisionReport<D> {
77 fn wire_schema() -> Cow<'static, [u8]> {
78 let mut schema = b"safemesh/collision-report/v1/".to_vec();
79 schema.extend_from_slice(D::wire_schema().as_ref());
80 Cow::Owned(schema)
81 }
82}
83
84impl<D: WireEncode + WireSchema> WireEncode for CollisionReport<D> {
85 fn encode_wire(&self, out: &mut Vec<u8>) -> Result<(), WireError> {
86 let mut body = Vec::new();
87 write_bytes(&mut body, Self::wire_schema().as_ref())?;
88 write_len(&mut body, self.collisions.len())?;
89 for entry in &self.collisions {
90 write_u64(&mut body, entry.id.replica);
91 write_u64(&mut body, entry.id.sequence);
92 write_bytes(&mut body, &entry.local.to_wire_bytes()?)?;
93 write_bytes(&mut body, &entry.remote.to_wire_bytes()?)?;
94 }
95 let len = u32::try_from(body.len()).map_err(|_| WireError::LengthOverflow)?;
96 write_u8(out, TAG_COLLISION_REPORT);
97 let start = out.len();
98 write_u32(out, len);
99 write_u32(out, !len);
100 out.extend_from_slice(&body);
101 let checksum = frame_crc32(&out[start..]);
102 write_u32(out, checksum);
103 Ok(())
104 }
105}
106
107impl<D: WireDecode + WireSchema> CollisionReport<D> {
108 pub fn from_wire_bytes_with_limits(
114 bytes: &[u8],
115 limits: DecodeLimits,
116 ) -> Result<Self, DecodeError> {
117 let mut cursor = WireCursor::new(bytes);
118 let report = Self::decode_limited(
119 &mut cursor,
120 CollectionLimits {
121 max_elements: limits
122 .max_collection_elements
123 .or(CollectionLimits::WIRE_DEFAULT.max_elements),
124 },
125 limits.max_records,
126 limits.max_records,
127 )?;
128 if !cursor.is_empty() {
129 return Err(WireError::TrailingBytes.into());
130 }
131 Ok(report)
132 }
133
134 fn decode_limited(
135 cursor: &mut WireCursor<'_>,
136 collections: CollectionLimits,
137 max_entries: Option<usize>,
138 nested_max_records: Option<usize>,
139 ) -> Result<Self, DecodeError> {
140 read_tag(cursor, TAG_COLLISION_REPORT)?;
141 let mut body = WireCursor::new(read_checked_body(cursor)?);
142 let schema_len = body.read_len()?;
143 if body.read_exact(schema_len)? != Self::wire_schema().as_ref() {
144 return Err(WireError::DeltaTypeMismatch.into());
145 }
146 let mut collisions = Vec::new();
147 for index in 0..body.read_len()? {
148 if let Some(max_records) = max_entries {
149 if index >= max_records {
150 return Err(DecodeError::RecordLimitExceeded { max_records });
151 }
152 }
153 let id = RecordId {
154 replica: body.read_u64()?,
155 sequence: body.read_u64()?,
156 };
157 let local = decode_payload(&mut body, collections, nested_max_records)?;
158 let remote = decode_payload(&mut body, collections, nested_max_records)?;
159 collisions.push(RecordCollision { id, local, remote });
160 }
161 if !body.is_empty() {
162 return Err(WireError::TrailingBytes.into());
163 }
164 Ok(Self { collisions })
165 }
166}
167
168fn decode_payload<D: WireDecode>(
169 body: &mut WireCursor<'_>,
170 collections: CollectionLimits,
171 max_records: Option<usize>,
172) -> Result<D, WireError> {
173 let len = body.read_len()?;
174 let mut cursor = WireCursor::new(body.read_exact(len)?);
175 let payload = D::decode_wire_with_limits(&mut cursor, collections, max_records)?;
176 if !cursor.is_empty() {
177 return Err(WireError::TrailingBytes);
178 }
179 Ok(payload)
180}
181
182impl<D: WireDecode + WireSchema> WireDecode for CollisionReport<D> {
183 fn decode_wire(cursor: &mut WireCursor<'_>) -> Result<Self, WireError> {
184 Self::decode_wire_with_collection_limits(cursor, CollectionLimits::WIRE_DEFAULT)
185 }
186
187 fn decode_wire_with_collection_limits(
188 cursor: &mut WireCursor<'_>,
189 limits: CollectionLimits,
190 ) -> Result<Self, WireError> {
191 Self::decode_limited(
192 cursor,
193 limits,
194 DecodeLimits::default().max_records,
195 DecodeLimits::default().max_records,
196 )
197 .map_err(|error| WireError::from(error.with_default_record_limit()))
198 }
199
200 fn decode_wire_with_limits(
201 cursor: &mut WireCursor<'_>,
202 collections: CollectionLimits,
203 max_records: Option<usize>,
204 ) -> Result<Self, WireError> {
205 Self::decode_limited(cursor, collections, max_records, max_records).map_err(|error| {
206 match error {
207 DecodeError::Wire(error) => error,
208 DecodeError::RecordLimitExceeded { max_records }
209 | DecodeError::DefaultRecordLimitExceeded { max_records } => {
210 WireError::NestedRecordLimitExceeded { max_records }
211 }
212 }
213 })
214 }
215}
216
217impl<D> EventLog<D> {
218 pub fn collisions(&self) -> Vec<RecordCollision<D>>
222 where
223 D: Clone,
224 {
225 self.collisions
226 .iter()
227 .map(|(&id, remote)| RecordCollision {
228 id,
229 local: self.records[self.seen[&id]].delta.clone(),
230 remote: remote.clone(),
231 })
232 .collect()
233 }
234
235 pub fn collision_report(&self) -> CollisionReport<D>
238 where
239 D: Clone,
240 {
241 CollisionReport {
242 collisions: self.collisions(),
243 }
244 }
245
246 pub fn merge_collision_report(
251 &mut self,
252 report: CollisionReport<D>,
253 ) -> Vec<(RecordId, CollisionVerdict)>
254 where
255 D: PartialEq,
256 {
257 report
258 .collisions
259 .into_iter()
260 .map(|entry| (entry.id, self.merge_collision_entry(entry)))
261 .collect()
262 }
263
264 fn merge_collision_entry(&mut self, entry: RecordCollision<D>) -> CollisionVerdict
265 where
266 D: PartialEq,
267 {
268 let Some(&index) = self.seen.get(&entry.id) else {
269 return CollisionVerdict::Unheld;
270 };
271 let held = &self.records[index].delta;
272 let witness = if entry.local != *held {
275 entry.local
276 } else if entry.remote != *held {
277 entry.remote
278 } else {
279 return CollisionVerdict::Agrees;
280 };
281 if self.collisions.contains_key(&entry.id) {
282 return CollisionVerdict::Known;
283 }
284 self.collisions.insert(entry.id, witness);
285 CollisionVerdict::Recorded
286 }
287}
288
289impl<D: Clone + WireEncode + WireSchema> EventLog<D> {
290 pub fn collision_report_bytes(&self) -> Result<Option<Vec<u8>>, WireError> {
293 if self.collisions.is_empty() {
294 return Ok(None);
295 }
296 self.collision_report().to_wire_bytes().map(Some)
297 }
298}
299
300impl<D: PartialEq + WireDecode + WireSchema> EventLog<D> {
301 pub fn merge_collision_report_bytes(
304 &mut self,
305 bytes: &[u8],
306 limits: DecodeLimits,
307 ) -> Result<Vec<(RecordId, CollisionVerdict)>, DecodeError> {
308 let report = CollisionReport::from_wire_bytes_with_limits(bytes, limits)?;
309 Ok(self.merge_collision_report(report))
310 }
311}