1use crate::types::*;
54use minicbor::{data::Tag, decode, encode, Decode, Decoder, Encode, Encoder};
55use uuid::Uuid;
56
57pub const MUTATION_PROTOCOL_VERSION: u32 = 1;
58
59#[derive(Encode, Decode, Debug, PartialEq, Clone)]
60pub enum RootwardsMessage {
61 #[n(1001)]
62 ChildAuthAttempt {
63 #[n(0)]
65 child_participant_id: ParticipantId,
66 #[n(1)]
68 version: u32,
69 #[n(2)]
71 token: Vec<u8>,
72 },
73 #[n(1012)]
74 MutatorAnnouncement {
75 #[n(0)]
77 participant_id: ParticipantId,
78 #[n(1)]
79 mutator_id: MutatorId,
80 #[n(2)]
81 mutator_attrs: AttrKvs,
82 },
83 #[n(1023)]
84 MutatorRetirement {
85 #[n(0)]
87 participant_id: ParticipantId,
88 #[n(1)]
89 mutator_id: MutatorId,
90 },
91 #[n(1044)]
92 UpdateTriggerState {
93 #[n(0)]
94 mutator_id: MutatorId,
95 #[n(1)]
96 mutation_id: MutationId,
97 #[n(2)]
100 maybe_trigger_crdt: Option<TriggerCRDT>,
101 },
102}
103
104impl RootwardsMessage {
105 pub fn name(&self) -> &'static str {
106 match self {
107 RootwardsMessage::ChildAuthAttempt { .. } => "ChildAuthAttempt",
108 RootwardsMessage::MutatorAnnouncement { .. } => "MutatorAnnouncement",
109 RootwardsMessage::MutatorRetirement { .. } => "MutatorRetirement",
110 RootwardsMessage::UpdateTriggerState { .. } => "UpdateTriggerState",
111 }
112 }
113}
114
115#[derive(Encode, Decode, Debug, PartialEq, Clone)]
116pub enum LeafwardsMessage {
117 #[n(2001)]
118 ChildAuthOutcome {
119 #[n(0)]
121 child_participant_id: ParticipantId,
122 #[n(1)]
124 version: u32,
125 #[n(2)]
127 ok: bool,
128
129 #[n(3)]
131 message: Option<String>,
132 },
133 #[n(2002)]
134 UnauthenticatedResponse {},
135 #[n(2013)]
136 RequestForMutatorAnnouncements {},
137 #[n(2024)]
138 NewMutation {
139 #[n(0)]
140 mutator_id: MutatorId,
141 #[n(1)]
142 mutation_id: MutationId,
143 #[n(2)]
148 maybe_trigger_mask: Option<TriggerCRDT>,
149 #[n(3)]
150 params: AttrKvs,
151 },
152 #[n(2035)]
153 ClearSingleMutation {
154 #[n(0)]
155 mutator_id: MutatorId,
156 #[n(1)]
157 mutation_id: MutationId,
158 #[n(2)]
159 reset_if_active: bool,
160 },
161 #[n(2036)]
162 ClearMutationsForMutator {
163 #[n(0)]
164 mutator_id: MutatorId,
165 #[n(2)]
166 reset_if_active: bool,
167 },
168 #[n(2037)]
169 ClearMutations {},
170 #[n(2044)]
171 UpdateTriggerState {
172 #[n(0)]
173 mutator_id: MutatorId,
174 #[n(1)]
175 mutation_id: MutationId,
176 #[n(2)]
179 maybe_trigger_crdt: Option<TriggerCRDT>,
180 },
181}
182
183impl LeafwardsMessage {
184 pub fn name(&self) -> &'static str {
185 match self {
186 LeafwardsMessage::ChildAuthOutcome { .. } => "ChildAuthOutcome",
187 LeafwardsMessage::UnauthenticatedResponse { .. } => "UnauthenticatedResponse",
188 LeafwardsMessage::RequestForMutatorAnnouncements { .. } => {
189 "RequestForMutatorAnnouncements"
190 }
191 LeafwardsMessage::NewMutation { .. } => "NewMutation",
192 LeafwardsMessage::ClearSingleMutation { .. } => "ClearSingleMutation",
193 LeafwardsMessage::ClearMutationsForMutator { .. } => "ClearMutationsForMutator",
194 LeafwardsMessage::ClearMutations { .. } => "ClearMutations",
195 LeafwardsMessage::UpdateTriggerState { .. } => "UpdateTriggerState",
196 }
197 }
198}
199
200const TAG_PARTICIPANT_ID: Tag = Tag::Unassigned(40200);
201const TAG_MUTATOR_ID: Tag = Tag::Unassigned(40201);
202const TAG_MUTATION_ID: Tag = Tag::Unassigned(40202);
203
204impl Encode for ParticipantId {
205 fn encode<W: encode::Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
206 e.tag(TAG_PARTICIPANT_ID)?.bytes(self.as_ref().as_bytes())?;
207 Ok(())
208 }
209}
210
211impl<'b> Decode<'b> for ParticipantId {
212 fn decode(d: &mut Decoder<'b>) -> Result<Self, decode::Error> {
213 let t = d.tag()?;
214 if t != TAG_PARTICIPANT_ID {
215 return Err(decode::Error::Message("Expected TAG_PARTICIPANT_ID"));
216 }
217
218 Uuid::from_slice(d.bytes()?)
219 .map(Into::into)
220 .map_err(|_uuid_err| decode::Error::Message("Error decoding uuid for ParticipantId"))
221 }
222}
223
224impl Encode for MutatorId {
225 fn encode<W: encode::Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
226 e.tag(TAG_MUTATOR_ID)?.bytes(self.as_ref().as_bytes())?;
227 Ok(())
228 }
229}
230
231impl<'b> Decode<'b> for MutatorId {
232 fn decode(d: &mut Decoder<'b>) -> Result<Self, decode::Error> {
233 let t = d.tag()?;
234 if t != TAG_MUTATOR_ID {
235 return Err(decode::Error::Message("Expected TAG_MUTATOR_ID"));
236 }
237
238 Uuid::from_slice(d.bytes()?)
239 .map(Into::into)
240 .map_err(|_uuid_err| decode::Error::Message("Error decoding uuid for MutatorId"))
241 }
242}
243
244impl Encode for MutationId {
245 fn encode<W: encode::Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
246 e.tag(TAG_MUTATION_ID)?.bytes(self.as_ref().as_bytes())?;
247 Ok(())
248 }
249}
250
251impl<'b> Decode<'b> for MutationId {
252 fn decode(d: &mut Decoder<'b>) -> Result<Self, decode::Error> {
253 let t = d.tag()?;
254 if t != TAG_MUTATION_ID {
255 return Err(decode::Error::Message("Expected TAG_MUTATION_ID"));
256 }
257
258 Uuid::from_slice(d.bytes()?)
259 .map(Into::into)
260 .map_err(|_uuid_err| decode::Error::Message("Error decoding uuid for MutationId"))
261 }
262}
263
264impl Encode for TriggerCRDT {
265 fn encode<W: encode::Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
266 e.array(self.as_ref().len() as u64)?;
267 for byte in self.as_ref().iter() {
268 e.u8(*byte)?;
269 }
270
271 Ok(())
272 }
273}
274
275impl<'b> Decode<'b> for TriggerCRDT {
276 fn decode(d: &mut Decoder<'b>) -> Result<Self, decode::Error> {
277 let arr_len = d.array()?;
278
279 if let Some(len) = arr_len {
280 let mut bytes = Vec::with_capacity(len as usize);
281 for _ in 0..len {
282 bytes.push(d.u8()?);
283 }
284 Ok(TriggerCRDT::new(bytes))
285 } else {
286 Err(decode::Error::Message(
287 "missing array length for TriggerCRDT",
288 ))
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use proptest::prelude::*;
297
298 fn participant_id() -> impl Strategy<Value = ParticipantId> {
299 any::<[u8; 16]>().prop_map(|arr| Uuid::from_bytes(arr).into())
300 }
301 fn mutator_id() -> impl Strategy<Value = MutatorId> {
302 any::<[u8; 16]>().prop_map(|arr| Uuid::from_bytes(arr).into())
303 }
304 fn mutation_id() -> impl Strategy<Value = MutationId> {
305 any::<[u8; 16]>().prop_map(|arr| Uuid::from_bytes(arr).into())
306 }
307 fn trigger_crdt() -> impl Strategy<Value = TriggerCRDT> {
308 proptest::collection::vec(any::<u8>(), 1..=5).prop_map(|v| v.into_iter().into())
309 }
310
311 fn attr_kv() -> impl Strategy<Value = AttrKv> {
312 (
313 ".+",
314 modality_mutator_protocol::proptest_strategies::attr_val(),
315 )
316 .prop_map(|(k, v)| AttrKv { key: k, value: v })
317 }
318 fn attr_kvs() -> impl Strategy<Value = AttrKvs> {
319 proptest::collection::vec(attr_kv(), 1..=5).prop_map(AttrKvs)
320 }
321
322 fn rootwards_message() -> impl Strategy<Value = RootwardsMessage> {
323 prop_oneof![
324 (
325 any::<u32>(),
326 participant_id(),
327 proptest::collection::vec(any::<u8>(), 0..100)
328 )
329 .prop_map(|(version, participant, token)| {
330 RootwardsMessage::ChildAuthAttempt {
331 child_participant_id: participant,
332 version,
333 token,
334 }
335 }),
336 (participant_id(), mutator_id(), attr_kvs()).prop_map(
337 |(participant_id, mutator_id, mutator_attrs)| {
338 RootwardsMessage::MutatorAnnouncement {
339 participant_id,
340 mutator_id,
341 mutator_attrs,
342 }
343 }
344 ),
345 (participant_id(), mutator_id()).prop_map(|(participant_id, mutator_id)| {
346 RootwardsMessage::MutatorRetirement {
347 participant_id,
348 mutator_id,
349 }
350 }),
351 (
352 mutator_id(),
353 mutation_id(),
354 proptest::option::of(trigger_crdt())
355 )
356 .prop_map(|(mutator_id, mutation_id, maybe_trigger_crdt)| {
357 RootwardsMessage::UpdateTriggerState {
358 mutator_id,
359 mutation_id,
360 maybe_trigger_crdt,
361 }
362 }),
363 ]
364 }
365 fn leafwards_message() -> impl Strategy<Value = LeafwardsMessage> {
366 prop_oneof![
367 (
368 any::<u32>(),
369 participant_id(),
370 any::<bool>(),
371 proptest::option::of(".+")
372 )
373 .prop_map(|(version, child_participant_id, ok, message)| {
374 LeafwardsMessage::ChildAuthOutcome {
375 version,
376 child_participant_id,
377 ok,
378 message,
379 }
380 }),
381 (mutator_id(), any::<bool>()).prop_map(|(mutator_id, reset_if_active)| {
382 LeafwardsMessage::ClearMutationsForMutator {
383 mutator_id,
384 reset_if_active,
385 }
386 }),
387 (mutator_id(), mutation_id(), any::<bool>()).prop_map(
388 |(mutator_id, mutation_id, reset_if_active)| {
389 LeafwardsMessage::ClearSingleMutation {
390 mutator_id,
391 mutation_id,
392 reset_if_active,
393 }
394 }
395 ),
396 (
397 mutator_id(),
398 mutation_id(),
399 proptest::option::of(trigger_crdt()),
400 attr_kvs()
401 )
402 .prop_map(|(mutator_id, mutation_id, maybe_trigger_mask, params)| {
403 LeafwardsMessage::NewMutation {
404 mutator_id,
405 mutation_id,
406 maybe_trigger_mask,
407 params,
408 }
409 }),
410 Just(LeafwardsMessage::RequestForMutatorAnnouncements {}),
411 (
412 mutator_id(),
413 mutation_id(),
414 proptest::option::of(trigger_crdt())
415 )
416 .prop_map(|(mutator_id, mutation_id, maybe_trigger_crdt)| {
417 LeafwardsMessage::UpdateTriggerState {
418 mutator_id,
419 mutation_id,
420 maybe_trigger_crdt,
421 }
422 }),
423 ]
424 }
425
426 #[test]
427 fn round_trip_rootwards() {
428 proptest!(|(msg in rootwards_message())| {
429 let mut buf = vec![];
430 minicbor::encode(&msg , &mut buf)?;
431
432 let msg_prime: RootwardsMessage = minicbor::decode(&buf)?;
433 prop_assert_eq!(msg, msg_prime);
434 });
435 }
436 #[test]
437 fn round_trip_leafwards() {
438 proptest!(|(msg in leafwards_message())| {
439 let mut buf = vec![];
440 minicbor::encode(&msg , &mut buf)?;
441
442 let msg_prime: LeafwardsMessage = minicbor::decode(&buf)?;
443 prop_assert_eq!(msg, msg_prime);
444 });
445 }
446
447 #[test]
448 fn round_trip_update_trigger_state_bidirectional() {
449 proptest!(|((mutator_id, mutation_id, maybe_trigger_crdt) in (mutator_id(), mutation_id(), proptest::option::of(trigger_crdt())))| {
450 let mut rootwards_buf = vec![];
451 let rootwards_msg = RootwardsMessage::UpdateTriggerState{
452 mutator_id, mutation_id, maybe_trigger_crdt
453 };
454 minicbor::encode(&rootwards_msg , &mut rootwards_buf)?;
455
456 let rootwards_msg_prime: RootwardsMessage = minicbor::decode(&rootwards_buf)?;
457 if let RootwardsMessage::UpdateTriggerState{
458 mutator_id, mutation_id, maybe_trigger_crdt
459 } = rootwards_msg_prime {
460 let mut leafwards_buf = vec![];
461 let leafwards_msg = LeafwardsMessage::UpdateTriggerState{
462 mutator_id, mutation_id, maybe_trigger_crdt
463 };
464 minicbor::encode(&leafwards_msg, &mut leafwards_buf)?;
465 let leafwards_msg_prime: LeafwardsMessage = minicbor::decode(&leafwards_buf)?;
466 if let LeafwardsMessage::UpdateTriggerState{
467 mutator_id, mutation_id, maybe_trigger_crdt
468 } = leafwards_msg_prime {
469 let rootwards_via_leafwards = RootwardsMessage::UpdateTriggerState { mutator_id, mutation_id, maybe_trigger_crdt };
470 prop_assert_eq!(rootwards_msg, rootwards_via_leafwards);
471 } else {
472 panic!("Wrong leafwards variant");
473 }
474 } else {
475 panic!("Wrong rootwards variant");
476 }
477 });
478 }
479}