1use std::collections::HashMap;
5use std::fmt::Display;
6use std::hash::{Hash, Hasher};
7use std::time::Duration;
8
9use slim_version::version;
10use thiserror::Error;
11use twox_hash::XxHash64;
12
13use crate::dataplane::proto::v1::command_payload::CommandPayloadType;
14use crate::dataplane::proto::v1::content::ContentType;
15use crate::dataplane::proto::v1::link::LinkType as ProtoLinkType;
16use crate::dataplane::proto::v1::message::MessageType;
17use crate::dataplane::proto::v1::message::MessageType::Link as ProtoLinkMessageType;
18use crate::dataplane::proto::v1::message::MessageType::Publish as ProtoPublishType;
19use crate::dataplane::proto::v1::message::MessageType::Subscribe as ProtoSubscribeType;
20use crate::dataplane::proto::v1::message::MessageType::SubscriptionAck as ProtoSubscriptionAckType;
21use crate::dataplane::proto::v1::message::MessageType::Unsubscribe as ProtoUnsubscribeType;
22use crate::dataplane::proto::v1::{
23 ApplicationPayload, CommandPayload, Content, DiscoveryReplyPayload, DiscoveryRequestPayload,
24 EncodedName, GroupAckPayload, GroupAddPayload, GroupClosePayload, GroupNackPayload,
25 GroupProposalPayload, GroupRemovePayload, GroupWelcomePayload, HeartbeatPayload,
26 JoinReplyPayload, JoinRequestPayload, LeaveReplyPayload, LeaveRequestPayload,
27 Link as ProtoLink, LinkConnectionType, LinkNegotiationPayload, Message as ProtoMessage,
28 MlsPayload, MlsSettings as ProtoMlsSettings, Name as ProtoName, NameId, Participant,
29 ParticipantSettings, ParticipantState, Publish as ProtoPublish, SessionHeader,
30 SessionMessageType, SessionType as ProtoSessionType, SlimHeader, StringName,
31 Subscribe as ProtoSubscribe, SubscriptionAck as ProtoSubscriptionAck, TimerSettings,
32 Unsubscribe as ProtoUnsubscribe, UpdateParticipantStatePayload,
33};
34
35fn calculate_hash<T: Hash + ?Sized>(t: &T) -> u64 {
36 let mut hasher = XxHash64::default();
37 t.hash(&mut hasher);
38 hasher.finish()
39}
40
41#[derive(Error, Debug, Clone, PartialEq, Eq)]
42pub enum NameError {
43 #[error("invalid name id format: {0}")]
44 InvalidNameIdFormat(String),
45 #[error("invalid name format: {0}")]
46 InvalidNameFormat(String),
47}
48
49pub const DELETE_GROUP: &str = "DELETE_GROUP";
53
54pub const PUBLISH_TO: &str = "PUBLISH_TO";
58
59pub const LEAVE_REPLY_SENT: &str = "LEAVE_REPLY_SENT";
65
66pub const LEAVING_SESSION: &str = "LEAVING_SESSION";
70
71pub const TRUE_VAL: &str = "TRUE";
73
74pub const FALSE_VAL: &str = "FALSE";
76
77pub const MAX_PUBLISH_ID: u32 = u32::MAX / 2;
82
83pub const DEFAULT_TTL: u32 = 8;
85
86#[derive(Error, Debug, PartialEq)]
87pub enum MessageError {
88 #[error("SLIM header not found")]
89 SlimHeaderNotFound,
90 #[error("source not found")]
91 SourceNotFound,
92 #[error("source encoded name not found")]
93 SourceEncodedNameNotFound,
94 #[error("destination not found")]
95 DestinationNotFound,
96 #[error("destination encoded name not found")]
97 DestinationEncodedNameNotFound,
98 #[error("session header not found")]
99 SessionHeaderNotFound,
100 #[error("message type not found")]
101 MessageTypeNotFound,
102 #[error("incoming connection not found")]
103 IncomingConnectionNotFound,
104 #[error("content type is not set")]
105 ContentTypeNotSet,
106 #[error("content is not an application payload")]
107 NotApplicationPayload,
108 #[error("content is not a command payload")]
109 NotCommandPayload,
110 #[error("link type is not set")]
111 LinkTypeNotSet,
112 #[error("invalid command payload type: expected {expected}, got {got}")]
113 InvalidCommandPayloadType {
114 expected: Box<String>,
115 got: Box<String>,
116 },
117 #[error("builder error: source is required")]
118 BuilderErrorSourceRequired,
119 #[error("builder error: destination is required")]
120 BuilderErrorDestinationRequired,
121 #[error("participant name not found")]
122 ParticipantNameNotFound,
123 #[error("participant settings not found")]
124 ParticipantSettingsNotFound,
125}
126
127#[derive(Debug, Clone)]
129pub struct SlimHeaderFlags {
130 pub fanout: u32,
131 pub recv_from: Option<u64>,
132 pub forward_to: Option<u64>,
133 pub incoming_conn: Option<u64>,
134 pub error: Option<bool>,
135 pub ttl: u32,
136}
137
138impl Default for SlimHeaderFlags {
139 fn default() -> Self {
140 Self {
141 fanout: 1,
142 recv_from: None,
143 forward_to: None,
144 incoming_conn: None,
145 error: None,
146 ttl: DEFAULT_TTL,
147 }
148 }
149}
150
151impl Display for SlimHeaderFlags {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(
154 f,
155 "fanout: {}, recv_from: {:?}, forward_to: {:?}, incoming_conn: {:?}, error: {:?}, ttl: {:?}",
156 self.fanout, self.recv_from, self.forward_to, self.incoming_conn, self.error, self.ttl
157 )
158 }
159}
160
161impl SlimHeaderFlags {
162 pub fn new(
163 fanout: u32,
164 recv_from: Option<u64>,
165 forward_to: Option<u64>,
166 incoming_conn: Option<u64>,
167 error: Option<bool>,
168 ) -> Self {
169 Self {
170 fanout,
171 recv_from,
172 forward_to,
173 incoming_conn,
174 error,
175 ttl: DEFAULT_TTL,
176 }
177 }
178
179 pub fn with_fanout(self, fanout: u32) -> Self {
180 Self { fanout, ..self }
181 }
182
183 pub fn with_recv_from(self, recv_from: u64) -> Self {
184 Self {
185 recv_from: Some(recv_from),
186 ..self
187 }
188 }
189
190 pub fn with_forward_to(self, forward_to: u64) -> Self {
191 Self {
192 forward_to: Some(forward_to),
193 ..self
194 }
195 }
196
197 pub fn with_incoming_conn(self, incoming_conn: u64) -> Self {
198 Self {
199 incoming_conn: Some(incoming_conn),
200 ..self
201 }
202 }
203
204 pub fn with_error(self, error: bool) -> Self {
205 Self {
206 error: Some(error),
207 ..self
208 }
209 }
210
211 pub fn with_ttl(self, ttl: u32) -> Self {
212 Self { ttl, ..self }
213 }
214}
215
216impl NameId {
217 pub const NULL_COMPONENT: u128 = u128::MAX;
218 pub const RESERVED_IDS: u128 = 50;
219
220 pub const fn is_reserved_id(id: u128) -> bool {
221 id >= (u128::MAX - Self::RESERVED_IDS)
222 }
223}
224
225impl std::fmt::Display for NameId {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 let s: String = (*self).into();
228 write!(f, "{}", s)
229 }
230}
231
232impl From<u128> for NameId {
233 fn from(id: u128) -> Self {
234 NameId {
235 id_0: (id >> 64) as u64,
236 id_1: (id & 0xFFFFFFFFFFFFFFFF) as u64,
237 }
238 }
239}
240
241impl From<NameId> for u128 {
242 fn from(name_id: NameId) -> Self {
243 (name_id.id_0 as u128) << 64 | (name_id.id_1 as u128)
244 }
245}
246
247impl TryFrom<String> for NameId {
248 type Error = NameError;
249
250 fn try_from(s: String) -> Result<Self, Self::Error> {
251 match s.as_str() {
252 "NULL_COMPONENT" => Ok(Self::from(Self::NULL_COMPONENT)),
253 _ => {
254 if let Ok(uuid) = uuid::Uuid::parse_str(&s) {
255 return Ok(Self::from(uuid.as_u128()));
256 }
257 Err(NameError::InvalidNameIdFormat(s))
258 }
259 }
260 }
261}
262
263impl From<NameId> for String {
264 fn from(name_id: NameId) -> String {
265 let val: u128 = name_id.into();
266 match val {
267 NameId::NULL_COMPONENT => "NULL_COMPONENT".to_string(),
268 id => uuid::Uuid::from_u128(id).to_string(),
269 }
270 }
271}
272
273impl EncodedName {
274 pub fn id(&self) -> u128 {
275 self.name_id
276 .as_ref()
277 .map_or(NameId::NULL_COMPONENT, |nid| (*nid).into())
278 }
279
280 pub fn string_id(&self) -> String {
281 self.name_id
282 .as_ref()
283 .map_or("NULL_COMPONENT".to_string(), |nid| (*nid).into())
284 }
285}
286
287impl ProtoName {
288 pub fn from_strings(components: [impl Into<String>; 3]) -> Self {
289 let [s0, s1, s2] = components.map(Into::into);
290 Self {
291 name: Some(EncodedName {
292 component_0: calculate_hash(&s0),
293 component_1: calculate_hash(&s1),
294 component_2: calculate_hash(&s2),
295 name_id: Some(NameId::from(NameId::NULL_COMPONENT)),
296 }),
297 str_name: Some(StringName {
298 str_component_0: s0,
299 str_component_1: s1,
300 str_component_2: s2,
301 }),
302 }
303 }
304
305 pub fn with_id(mut self, id: u128) -> Self {
306 self.name.as_mut().expect("encoded name missing").name_id = Some(NameId::from(id));
307 self
308 }
309
310 pub fn id(&self) -> u128 {
311 self.name.as_ref().expect("encoded name missing").id()
312 }
313
314 pub fn string_id(&self) -> String {
315 self.name
316 .as_ref()
317 .expect("encoded name missing")
318 .string_id()
319 }
320
321 pub fn name_id(&self) -> Option<NameId> {
322 self.name.as_ref().expect("encoded name missing").name_id
323 }
324
325 pub fn has_id(&self) -> bool {
326 self.id() != NameId::NULL_COMPONENT
327 }
328
329 pub fn set_id(&mut self, id: u128) {
330 self.name.as_mut().expect("encoded name missing").name_id = Some(NameId::from(id));
331 }
332
333 pub fn reset_id(&mut self) {
334 self.name.as_mut().expect("encoded name missing").name_id =
335 Some(NameId::from(NameId::NULL_COMPONENT));
336 }
337
338 pub fn match_prefix(&self, other: &ProtoName) -> bool {
339 let a = self.name.as_ref().expect("encoded name missing");
340 let b = other.name.as_ref().expect("encoded name missing");
341 a.component_0 == b.component_0
342 && a.component_1 == b.component_1
343 && a.component_2 == b.component_2
344 }
345
346 pub fn str_components(&self) -> (&str, &str, &str) {
347 let s = self.str_name.as_ref().expect("string name missing");
348 (&s.str_component_0, &s.str_component_1, &s.str_component_2)
349 }
350
351 pub fn parse_name(s: &str) -> Result<ProtoName, NameError> {
352 let trimmed = s.trim();
353 if trimmed.is_empty() {
354 return Err(NameError::InvalidNameFormat(s.to_string()));
355 }
356 let parts: Vec<&str> = trimmed.split('/').map(str::trim).collect();
357 if parts.len() != 3 || parts.iter().any(|p| p.is_empty()) {
358 return Err(NameError::InvalidNameFormat(s.to_string()));
359 }
360 Ok(ProtoName::from_strings([parts[0], parts[1], parts[2]]))
361 }
362}
363
364impl std::fmt::Display for ProtoName {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 if let Some(s) = &self.str_name {
367 write!(
368 f,
369 "{}/{}/{}/{}",
370 s.str_component_0,
371 s.str_component_1,
372 s.str_component_2,
373 self.name
374 .as_ref()
375 .and_then(|n| n.name_id)
376 .map_or("NULL_COMPONENT".to_string(), |id| id.to_string())
377 )
378 } else if let Some(enc) = &self.name {
379 write!(
380 f,
381 "{}/{}/{}/{}",
382 enc.component_0,
383 enc.component_1,
384 enc.component_2,
385 enc.name_id
386 .as_ref()
387 .map_or("NULL_COMPONENT".to_string(), |id| id.to_string())
388 )
389 } else {
390 write!(f, "<empty>")
391 }
392 }
393}
394
395impl ParticipantSettings {
396 pub fn bidirectional() -> Self {
397 Self {
398 sends_data: true,
399 receives_data: true,
400 }
401 }
402
403 pub fn send_only() -> Self {
404 Self {
405 sends_data: true,
406 receives_data: false,
407 }
408 }
409
410 pub fn receive_only() -> Self {
411 Self {
412 sends_data: false,
413 receives_data: true,
414 }
415 }
416
417 pub fn is_sender(&self) -> bool {
418 self.sends_data
419 }
420
421 pub fn is_receiver(&self) -> bool {
422 self.receives_data
423 }
424}
425
426impl Participant {
427 pub fn new(name: ProtoName, settings: ParticipantSettings) -> Self {
428 Self {
429 name: Some(name),
430 settings: Some(settings),
431 }
432 }
433
434 pub fn get_name(&self) -> Result<ProtoName, MessageError> {
435 match &self.name {
436 Some(name) => Ok(name.clone()),
437 None => Err(MessageError::ParticipantNameNotFound),
438 }
439 }
440
441 pub fn get_settings(&self) -> Result<&ParticipantSettings, MessageError> {
442 match &self.settings {
443 Some(settings) => Ok(settings),
444 None => Err(MessageError::ParticipantSettingsNotFound),
445 }
446 }
447}
448
449impl Display for MessageType {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 match self {
452 MessageType::Publish(_) => write!(f, "publish"),
453 MessageType::Subscribe(_) => write!(f, "subscribe"),
454 MessageType::Unsubscribe(_) => write!(f, "unsubscribe"),
455 MessageType::Link(_) => write!(f, "link"),
456 MessageType::SubscriptionAck(_) => write!(f, "subscription_ack"),
457 }
458 }
459}
460
461impl SlimHeader {
462 pub fn new(
463 source: ProtoName,
464 destination: ProtoName,
465 identity: &str,
466 flags: Option<SlimHeaderFlags>,
467 ) -> Self {
468 let flags = flags.unwrap_or_default();
469 Self {
470 source: Some(source),
471 destination: Some(destination),
472 identity: identity.to_string(),
473 fanout: flags.fanout,
474 version: version().to_string(),
475 recv_from: flags.recv_from,
476 forward_to: flags.forward_to,
477 incoming_conn: flags.incoming_conn,
478 error: flags.error,
479 header_mac: None,
480 ttl: flags.ttl,
481 e2e_header_sig: None,
482 }
483 }
484
485 pub fn clear_flags(&mut self) {
486 self.recv_from = None;
487 self.forward_to = None;
488 }
489
490 pub fn get_fanout(&self) -> u32 {
491 self.fanout
492 }
493
494 pub fn get_recv_from(&self) -> Option<u64> {
495 self.recv_from
496 }
497
498 pub fn get_forward_to(&self) -> Option<u64> {
499 self.forward_to
500 }
501
502 pub fn get_incoming_conn(&self) -> Option<u64> {
503 self.incoming_conn
504 }
505
506 pub fn get_error(&self) -> Option<bool> {
507 self.error
508 }
509
510 pub fn get_source(&self) -> ProtoName {
511 self.source.clone().expect("source not found")
512 }
513
514 pub fn get_encoded_source(&self) -> EncodedName {
515 self.source
516 .as_ref()
517 .expect("source not found")
518 .name
519 .expect("source encoded name not found")
520 }
521
522 pub fn get_dst(&self) -> ProtoName {
523 self.destination.clone().expect("destination not found")
524 }
525
526 pub fn get_encoded_dst(&self) -> EncodedName {
527 self.destination
528 .as_ref()
529 .expect("destination not found")
530 .name
531 .expect("destination encoded name not found")
532 }
533
534 pub fn get_identity(&self) -> String {
535 self.identity.clone()
536 }
537
538 pub fn get_version(&self) -> String {
539 self.version.clone()
540 }
541
542 pub fn set_source(&mut self, source: ProtoName) {
543 self.source = Some(source);
544 }
545
546 pub fn set_destination(&mut self, dst: ProtoName) {
547 self.destination = Some(dst);
548 }
549
550 pub fn set_identity(&mut self, identity: String) {
551 self.identity = identity;
552 }
553
554 pub fn set_fanout(&mut self, fanout: u32) {
555 self.fanout = fanout;
556 }
557
558 pub fn set_recv_from(&mut self, recv_from: Option<u64>) {
559 self.recv_from = recv_from;
560 }
561
562 pub fn set_forward_to(&mut self, forward_to: Option<u64>) {
563 self.forward_to = forward_to;
564 }
565
566 pub fn set_error(&mut self, error: Option<bool>) {
567 self.error = error;
568 }
569
570 pub fn set_incoming_conn(&mut self, incoming_conn: Option<u64>) {
571 self.incoming_conn = incoming_conn;
572 }
573
574 pub fn set_error_flag(&mut self, error: Option<bool>) {
575 self.error = error;
576 }
577
578 pub fn get_ttl(&self) -> u32 {
579 self.ttl
580 }
581
582 pub fn set_ttl(&mut self, ttl: u32) {
583 self.ttl = ttl;
584 }
585
586 pub fn decrement_ttl(&mut self) -> u32 {
587 self.ttl = self.ttl.saturating_sub(1);
588 self.ttl
589 }
590
591 pub fn get_connections(&self) -> (u64, Option<u64>, Option<u64>) {
592 let incoming = self
593 .get_incoming_conn()
594 .expect("incoming connection not found");
595 (incoming, self.get_recv_from(), self.get_forward_to())
596 }
597}
598
599impl SessionHeader {
600 pub fn new(
601 session_type: i32,
602 session_message_type: i32,
603 session_id: u32,
604 message_id: u32,
605 ) -> Self {
606 Self {
607 session_type,
608 session_message_type,
609 session_id,
610 message_id,
611 }
612 }
613
614 pub fn get_session_id(&self) -> u32 {
615 self.session_id
616 }
617
618 pub fn get_message_id(&self) -> u32 {
619 self.message_id
620 }
621
622 pub fn set_session_id(&mut self, session_id: u32) {
623 self.session_id = session_id;
624 }
625
626 pub fn set_message_id(&mut self, message_id: u32) {
627 self.message_id = message_id;
628 }
629
630 pub fn clear(&mut self) {
631 self.session_id = 0;
632 self.message_id = 0;
633 }
634}
635
636impl SessionMessageType {
637 pub fn is_command_message(&self) -> bool {
638 matches!(
639 self,
640 SessionMessageType::DiscoveryRequest
641 | SessionMessageType::DiscoveryReply
642 | SessionMessageType::JoinRequest
643 | SessionMessageType::JoinReply
644 | SessionMessageType::LeaveRequest
645 | SessionMessageType::LeaveReply
646 | SessionMessageType::GroupAdd
647 | SessionMessageType::GroupRemove
648 | SessionMessageType::GroupWelcome
649 | SessionMessageType::GroupClose
650 | SessionMessageType::GroupProposal
651 | SessionMessageType::GroupAck
652 | SessionMessageType::GroupNack
653 | SessionMessageType::Heartbeat
654 | SessionMessageType::UpdateParticipantState
655 )
656 }
657
658 pub fn is_post_session_control(&self) -> bool {
659 matches!(
660 self,
661 SessionMessageType::LeaveRequest
662 | SessionMessageType::LeaveReply
663 | SessionMessageType::GroupAdd
664 | SessionMessageType::GroupRemove
665 | SessionMessageType::GroupClose
666 | SessionMessageType::GroupProposal
667 | SessionMessageType::GroupAck
668 | SessionMessageType::GroupNack
669 | SessionMessageType::Heartbeat
670 | SessionMessageType::UpdateParticipantState
671 )
672 }
673}
674
675impl ProtoSubscribe {
676 fn new(
677 source: ProtoName,
678 dst: ProtoName,
679 identity: Option<&str>,
680 flags: Option<SlimHeaderFlags>,
681 ) -> Self {
682 let id = identity.unwrap_or("");
683 let header = Some(SlimHeader::new(source, dst, id, flags));
684 ProtoSubscribe {
685 header,
686 subscription_id: 0,
687 }
688 }
689}
690
691impl From<ProtoMessage> for ProtoSubscribe {
692 fn from(message: ProtoMessage) -> Self {
693 match message.message_type {
694 Some(ProtoSubscribeType(s)) => s,
695 _ => panic!("message type is not subscribe"),
696 }
697 }
698}
699
700impl ProtoUnsubscribe {
701 fn new(
702 source: ProtoName,
703 dst: ProtoName,
704 identity: Option<&str>,
705 flags: Option<SlimHeaderFlags>,
706 ) -> Self {
707 let id = identity.unwrap_or("");
708 let header = Some(SlimHeader::new(source, dst, id, flags));
709 ProtoUnsubscribe {
710 header,
711 subscription_id: 0,
712 }
713 }
714}
715
716impl From<ProtoMessage> for ProtoUnsubscribe {
717 fn from(message: ProtoMessage) -> Self {
718 match message.message_type {
719 Some(ProtoUnsubscribeType(u)) => u,
720 _ => panic!("message type is not unsubscribe"),
721 }
722 }
723}
724
725impl ProtoPublish {
726 fn with_header(
727 header: Option<SlimHeader>,
728 session: Option<SessionHeader>,
729 payload: Option<Content>,
730 ) -> Self {
731 ProtoPublish {
732 header,
733 session,
734 msg: payload,
735 }
736 }
737
738 pub fn get_slim_header(&self) -> &SlimHeader {
739 self.header.as_ref().expect("SLIM header missing")
740 }
741
742 pub fn get_session_header(&self) -> &SessionHeader {
743 self.session.as_ref().expect("session header missing")
744 }
745
746 pub fn get_slim_header_as_mut(&mut self) -> &mut SlimHeader {
747 self.header.as_mut().expect("SLIM header missing")
748 }
749
750 pub fn get_session_header_as_mut(&mut self) -> &mut SessionHeader {
751 self.session.as_mut().expect("session header missing")
752 }
753
754 pub fn get_payload(&self) -> &Content {
755 self.msg.as_ref().expect("payload missing")
756 }
757
758 pub fn set_payload(&mut self, payload: Content) {
759 self.msg = Some(payload);
760 }
761
762 pub fn is_command(&self) -> bool {
763 match &self
764 .get_payload()
765 .content_type
766 .as_ref()
767 .expect("content missing")
768 {
769 ContentType::AppPayload(_) => false,
770 ContentType::CommandPayload(_) => true,
771 }
772 }
773
774 pub fn get_application_payload(&self) -> &ApplicationPayload {
775 match self
776 .get_payload()
777 .content_type
778 .as_ref()
779 .expect("content missing")
780 {
781 ContentType::AppPayload(application_payload) => application_payload,
782 ContentType::CommandPayload(_) => panic!("the payload is not an application payload"),
783 }
784 }
785
786 pub fn get_command_payload(&self) -> &CommandPayload {
787 match self
788 .get_payload()
789 .content_type
790 .as_ref()
791 .expect("content missing")
792 {
793 ContentType::AppPayload(_) => panic!("the payload is not a command payload"),
794 ContentType::CommandPayload(command_payload) => command_payload,
795 }
796 }
797}
798
799impl From<ProtoMessage> for ProtoPublish {
800 fn from(message: ProtoMessage) -> Self {
801 match message.message_type {
802 Some(ProtoPublishType(p)) => p,
803 _ => panic!("message type is not publish"),
804 }
805 }
806}
807
808macro_rules! impl_payload_extractors {
809 ($($method_name:ident => $getter_method:ident($payload_type:ty)),* $(,)?) => {
810 $(
811 pub fn $method_name(&self) -> Result<&$payload_type, MessageError> {
812 self.extract_command_payload()?.$getter_method()
813 }
814 )*
815 };
816}
817
818impl ProtoMessage {
819 fn new(metadata: HashMap<String, String>, message_type: MessageType) -> Self {
820 ProtoMessage {
821 metadata,
822 message_type: Some(message_type),
823 }
824 }
825
826 fn validate_link(link: &ProtoLink) -> Result<(), MessageError> {
827 if link.link_type.is_none() {
828 return Err(MessageError::LinkTypeNotSet);
829 }
830 Ok(())
831 }
832
833 fn validate_routed_header(slim_header: &SlimHeader) -> Result<(), MessageError> {
834 match &slim_header.source {
835 None => return Err(MessageError::SourceNotFound),
836 Some(src) if src.name.is_none() => return Err(MessageError::SourceEncodedNameNotFound),
837 _ => {}
838 }
839 match &slim_header.destination {
840 None => return Err(MessageError::DestinationNotFound),
841 Some(dst) if dst.name.is_none() => {
842 return Err(MessageError::DestinationEncodedNameNotFound);
843 }
844 _ => {}
845 }
846 Ok(())
847 }
848
849 fn validate_publish(p: &ProtoPublish) -> Result<(), MessageError> {
850 let hdr = p.header.as_ref().ok_or(MessageError::SlimHeaderNotFound)?;
851 Self::validate_routed_header(hdr)?;
852 if p.session.is_none() {
853 return Err(MessageError::SessionHeaderNotFound);
854 }
855 Ok(())
856 }
857
858 fn validate_subscribe(s: &ProtoSubscribe) -> Result<(), MessageError> {
859 let hdr = s.header.as_ref().ok_or(MessageError::SlimHeaderNotFound)?;
860 Self::validate_routed_header(hdr)
861 }
862
863 fn validate_unsubscribe(u: &ProtoUnsubscribe) -> Result<(), MessageError> {
864 let hdr = u.header.as_ref().ok_or(MessageError::SlimHeaderNotFound)?;
865 Self::validate_routed_header(hdr)
866 }
867
868 pub fn validate(&self) -> Result<(), MessageError> {
869 match &self.message_type {
870 None => Err(MessageError::MessageTypeNotFound),
871 Some(ProtoLinkMessageType(link)) => Self::validate_link(link),
872 Some(ProtoPublishType(p)) => Self::validate_publish(p),
873 Some(ProtoSubscribeType(s)) => Self::validate_subscribe(s),
874 Some(ProtoUnsubscribeType(u)) => Self::validate_unsubscribe(u),
875 Some(ProtoSubscriptionAckType(_)) => Ok(()),
876 }
877 }
878
879 pub fn insert_metadata(&mut self, key: String, val: String) {
880 self.metadata.insert(key, val);
881 }
882
883 pub fn remove_metadata(&mut self, key: &str) -> Option<String> {
884 self.metadata.remove(key)
885 }
886
887 pub fn contains_metadata(&self, key: &str) -> bool {
888 self.metadata.contains_key(key)
889 }
890
891 pub fn get_metadata(&self, key: &str) -> Option<&String> {
892 self.metadata.get(key)
893 }
894
895 pub fn get_metadata_map(&self) -> HashMap<String, String> {
896 self.metadata.clone()
897 }
898
899 pub fn set_metadata_map(&mut self, map: HashMap<String, String>) {
900 for (k, v) in &map {
901 self.insert_metadata(k.to_string(), v.to_string());
902 }
903 }
904
905 pub fn get_slim_header(&self) -> &SlimHeader {
906 match &self.message_type {
907 Some(ProtoPublishType(publish)) => {
908 publish.header.as_ref().expect("SLIM header missing")
909 }
910 Some(ProtoSubscribeType(sub)) => sub.header.as_ref().expect("SLIM header missing"),
911 Some(ProtoUnsubscribeType(unsub)) => {
912 unsub.header.as_ref().expect("SLIM header missing")
913 }
914 Some(ProtoLinkMessageType(_)) | Some(ProtoSubscriptionAckType(_)) | None => {
915 panic!("SLIM header not found")
916 }
917 }
918 }
919
920 pub fn get_slim_header_mut(&mut self) -> &mut SlimHeader {
921 match &mut self.message_type {
922 Some(ProtoPublishType(publish)) => {
923 publish.header.as_mut().expect("SLIM header missing")
924 }
925 Some(ProtoSubscribeType(sub)) => sub.header.as_mut().expect("SLIM header missing"),
926 Some(ProtoUnsubscribeType(unsub)) => {
927 unsub.header.as_mut().expect("SLIM header missing")
928 }
929 Some(ProtoLinkMessageType(_)) | Some(ProtoSubscriptionAckType(_)) | None => {
930 panic!("SLIM header not found")
931 }
932 }
933 }
934
935 pub fn try_get_slim_header(&self) -> Option<&SlimHeader> {
936 match &self.message_type {
937 Some(ProtoPublishType(publish)) => publish.header.as_ref(),
938 Some(ProtoSubscribeType(sub)) => sub.header.as_ref(),
939 Some(ProtoUnsubscribeType(unsub)) => unsub.header.as_ref(),
940 Some(ProtoLinkMessageType(_)) | Some(ProtoSubscriptionAckType(_)) | None => None,
941 }
942 }
943
944 pub fn get_session_header(&self) -> &SessionHeader {
945 match &self.message_type {
946 Some(ProtoPublishType(publish)) => {
947 publish.session.as_ref().expect("session header missing")
948 }
949 Some(ProtoSubscribeType(_))
950 | Some(ProtoUnsubscribeType(_))
951 | Some(ProtoLinkMessageType(_))
952 | Some(ProtoSubscriptionAckType(_))
953 | None => panic!("session header not found"),
954 }
955 }
956
957 pub fn get_session_header_mut(&mut self) -> &mut SessionHeader {
958 match &mut self.message_type {
959 Some(ProtoPublishType(publish)) => {
960 publish.session.as_mut().expect("session header missing")
961 }
962 Some(ProtoSubscribeType(_))
963 | Some(ProtoUnsubscribeType(_))
964 | Some(ProtoLinkMessageType(_))
965 | Some(ProtoSubscriptionAckType(_))
966 | None => panic!("session header not found"),
967 }
968 }
969
970 pub fn try_get_session_header(&self) -> Option<&SessionHeader> {
971 match &self.message_type {
972 Some(ProtoPublishType(publish)) => publish.session.as_ref(),
973 Some(ProtoSubscribeType(_))
974 | Some(ProtoUnsubscribeType(_))
975 | Some(ProtoLinkMessageType(_))
976 | Some(ProtoSubscriptionAckType(_))
977 | None => None,
978 }
979 }
980
981 pub fn try_get_session_header_mut(&mut self) -> Option<&mut SessionHeader> {
982 match &mut self.message_type {
983 Some(ProtoPublishType(publish)) => publish.session.as_mut(),
984 Some(ProtoSubscribeType(_))
985 | Some(ProtoUnsubscribeType(_))
986 | Some(ProtoLinkMessageType(_))
987 | Some(ProtoSubscriptionAckType(_))
988 | None => None,
989 }
990 }
991
992 pub fn get_id(&self) -> u32 {
993 self.get_session_header().get_message_id()
994 }
995
996 pub fn get_source(&self) -> ProtoName {
997 self.get_slim_header().get_source()
998 }
999
1000 pub fn get_encoded_source(&self) -> EncodedName {
1001 self.get_slim_header().get_encoded_source()
1002 }
1003
1004 pub fn get_dst(&self) -> ProtoName {
1005 self.get_slim_header().get_dst()
1006 }
1007
1008 pub fn get_encoded_dst(&self) -> EncodedName {
1009 self.get_slim_header().get_encoded_dst()
1010 }
1011
1012 pub fn get_identity(&self) -> String {
1013 self.get_slim_header().get_identity()
1014 }
1015
1016 pub fn get_fanout(&self) -> u32 {
1017 self.get_slim_header().get_fanout()
1018 }
1019
1020 pub fn get_recv_from(&self) -> Option<u64> {
1021 self.get_slim_header().get_recv_from()
1022 }
1023
1024 pub fn get_forward_to(&self) -> Option<u64> {
1025 self.get_slim_header().get_forward_to()
1026 }
1027
1028 pub fn get_error(&self) -> Option<bool> {
1029 self.get_slim_header().get_error()
1030 }
1031
1032 pub fn get_incoming_conn(&self) -> u64 {
1033 self.get_slim_header()
1034 .get_incoming_conn()
1035 .expect("incoming connection not found")
1036 }
1037
1038 pub fn try_get_incoming_conn(&self) -> Option<u64> {
1039 self.get_slim_header().get_incoming_conn()
1040 }
1041
1042 pub fn get_type(&self) -> &MessageType {
1043 match &self.message_type {
1044 Some(t) => t,
1045 None => panic!("message type not found"),
1046 }
1047 }
1048
1049 pub fn get_payload(&self) -> Option<&Content> {
1050 match &self.message_type {
1051 Some(ProtoPublishType(p)) => p.msg.as_ref(),
1052 Some(ProtoSubscribeType(_))
1053 | Some(ProtoUnsubscribeType(_))
1054 | Some(ProtoLinkMessageType(_))
1055 | Some(ProtoSubscriptionAckType(_))
1056 | None => panic!("payload not found"),
1057 }
1058 }
1059
1060 pub fn set_payload(&mut self, payload: Content) {
1061 match &mut self.message_type {
1062 Some(ProtoPublishType(p)) => p.set_payload(payload),
1063 Some(ProtoSubscribeType(_))
1064 | Some(ProtoUnsubscribeType(_))
1065 | Some(ProtoLinkMessageType(_))
1066 | Some(ProtoSubscriptionAckType(_))
1067 | None => panic!("no payload allowed"),
1068 }
1069 }
1070
1071 pub fn get_session_message_type(&self) -> SessionMessageType {
1072 self.get_session_header().session_message_type()
1073 }
1074
1075 pub fn clear_slim_header(&mut self) {
1076 if self.is_link() || self.is_subscription_ack() {
1077 return;
1078 }
1079 self.get_slim_header_mut().clear_flags();
1080 }
1081
1082 pub fn set_recv_from(&mut self, recv_from: Option<u64>) {
1083 self.get_slim_header_mut().set_recv_from(recv_from);
1084 }
1085
1086 pub fn set_forward_to(&mut self, forward_to: Option<u64>) {
1087 self.get_slim_header_mut().set_forward_to(forward_to);
1088 }
1089
1090 pub fn set_error(&mut self, error: Option<bool>) {
1091 self.get_slim_header_mut().set_error(error);
1092 }
1093
1094 pub fn set_fanout(&mut self, fanout: u32) {
1095 self.get_slim_header_mut().set_fanout(fanout);
1096 }
1097
1098 pub fn set_incoming_conn(&mut self, incoming_conn: Option<u64>) {
1099 self.get_slim_header_mut().set_incoming_conn(incoming_conn);
1100 }
1101
1102 pub fn set_error_flag(&mut self, error: Option<bool>) {
1103 self.get_slim_header_mut().set_error_flag(error);
1104 }
1105
1106 pub fn get_ttl(&self) -> u32 {
1107 self.get_slim_header().get_ttl()
1108 }
1109
1110 pub fn set_ttl(&mut self, ttl: u32) {
1111 self.get_slim_header_mut().set_ttl(ttl);
1112 }
1113
1114 pub fn decrement_ttl(&mut self) -> u32 {
1115 self.get_slim_header_mut().decrement_ttl()
1116 }
1117
1118 pub fn set_session_message_type(&mut self, message_type: SessionMessageType) {
1119 self.get_session_header_mut()
1120 .set_session_message_type(message_type);
1121 }
1122
1123 pub fn set_session_type(&mut self, session_type: ProtoSessionType) {
1124 self.get_session_header_mut().set_session_type(session_type);
1125 }
1126
1127 pub fn get_session_type(&self) -> ProtoSessionType {
1128 self.get_session_header().session_type()
1129 }
1130
1131 pub fn set_message_id(&mut self, message_id: u32) {
1132 self.get_session_header_mut().set_message_id(message_id);
1133 }
1134
1135 pub fn is_publish(&self) -> bool {
1136 matches!(self.get_type(), MessageType::Publish(_))
1137 }
1138
1139 pub fn is_subscribe(&self) -> bool {
1140 matches!(self.get_type(), MessageType::Subscribe(_))
1141 }
1142
1143 pub fn is_unsubscribe(&self) -> bool {
1144 matches!(self.get_type(), MessageType::Unsubscribe(_))
1145 }
1146
1147 pub fn is_link(&self) -> bool {
1148 matches!(self.get_type(), MessageType::Link(_))
1149 }
1150
1151 pub fn get_link_negotiation_payload(&self) -> Option<LinkNegotiationPayload> {
1152 match &self.message_type {
1153 Some(ProtoLinkMessageType(link)) => match &link.link_type {
1154 Some(ProtoLinkType::LinkNegotiation(payload)) => Some(payload.clone()),
1155 _ => None,
1156 },
1157 _ => None,
1158 }
1159 }
1160
1161 pub fn is_subscription_ack(&self) -> bool {
1162 matches!(self.get_type(), MessageType::SubscriptionAck(_))
1163 }
1164
1165 pub fn is_traceable(&self) -> bool {
1166 !self.is_link() && !self.is_subscription_ack()
1167 }
1168
1169 pub fn get_subscription_ack(&self) -> &ProtoSubscriptionAck {
1170 match &self.message_type {
1171 Some(ProtoSubscriptionAckType(ack)) => ack,
1172 _ => panic!("message type is not subscription_ack"),
1173 }
1174 }
1175
1176 pub fn get_subscription_id(&self) -> Option<u64> {
1177 match &self.message_type {
1178 Some(ProtoSubscribeType(s)) if s.subscription_id != 0 => Some(s.subscription_id),
1179 Some(ProtoUnsubscribeType(u)) if u.subscription_id != 0 => Some(u.subscription_id),
1180 _ => None,
1181 }
1182 }
1183
1184 pub fn take_subscription_id(&mut self) -> Option<u64> {
1185 match &mut self.message_type {
1186 Some(ProtoSubscribeType(s)) if s.subscription_id != 0 => {
1187 Some(std::mem::take(&mut s.subscription_id))
1188 }
1189 Some(ProtoUnsubscribeType(u)) if u.subscription_id != 0 => {
1190 Some(std::mem::take(&mut u.subscription_id))
1191 }
1192 _ => None,
1193 }
1194 }
1195
1196 pub fn set_subscription_id(&mut self, subscription_id: u64) {
1197 match &mut self.message_type {
1198 Some(ProtoSubscribeType(s)) => s.subscription_id = subscription_id,
1199 Some(ProtoUnsubscribeType(u)) => u.subscription_id = subscription_id,
1200 _ => {}
1201 }
1202 }
1203
1204 pub fn extract_command_payload(&self) -> Result<&CommandPayload, MessageError> {
1205 self.get_payload()
1206 .ok_or(MessageError::ContentTypeNotSet)?
1207 .as_command_payload()
1208 }
1209
1210 impl_payload_extractors! {
1211 extract_discovery_request => as_discovery_request_payload(DiscoveryRequestPayload),
1212 extract_discovery_reply => as_discovery_reply_payload(DiscoveryReplyPayload),
1213 extract_join_request => as_join_request_payload(JoinRequestPayload),
1214 extract_join_reply => as_join_reply_payload(JoinReplyPayload),
1215 extract_leave_request => as_leave_request_payload(LeaveRequestPayload),
1216 extract_leave_reply => as_leave_reply_payload(LeaveReplyPayload),
1217 extract_group_add => as_group_add_payload(GroupAddPayload),
1218 extract_group_remove => as_group_remove_payload(GroupRemovePayload),
1219 extract_group_welcome => as_welcome_payload(GroupWelcomePayload),
1220 extract_group_close => as_group_close_payload(GroupClosePayload),
1221 extract_group_proposal => as_group_proposal_payload(GroupProposalPayload),
1222 extract_group_ack => as_group_ack_payload(GroupAckPayload),
1223 extract_group_nack => as_group_nack_payload(GroupNackPayload),
1224 extract_heartbeat => as_heartbeat_payload(HeartbeatPayload),
1225 extract_update_participant_state => as_update_participant_state_payload(UpdateParticipantStatePayload),
1226 }
1227
1228 pub fn builder() -> ProtoMessageBuilder {
1229 ProtoMessageBuilder::new()
1230 }
1231}
1232
1233impl Content {
1234 pub fn as_application_payload(&self) -> Result<&ApplicationPayload, MessageError> {
1235 match &self.content_type {
1236 Some(ContentType::AppPayload(app_payload)) => Ok(app_payload),
1237 Some(ContentType::CommandPayload(_)) => Err(MessageError::NotApplicationPayload),
1238 None => Err(MessageError::ContentTypeNotSet),
1239 }
1240 }
1241
1242 pub fn as_command_payload(&self) -> Result<&CommandPayload, MessageError> {
1243 match &self.content_type {
1244 Some(ContentType::AppPayload(_)) => Err(MessageError::NotCommandPayload),
1245 Some(ContentType::CommandPayload(comm_payload)) => Ok(comm_payload),
1246 None => Err(MessageError::ContentTypeNotSet),
1247 }
1248 }
1249}
1250
1251impl ApplicationPayload {
1252 pub fn new(payload_type: &str, blob: Vec<u8>) -> Self {
1253 Self {
1254 payload_type: payload_type.to_string(),
1255 blob,
1256 }
1257 }
1258
1259 pub fn as_content(&self) -> Content {
1260 Content {
1261 content_type: Some(ContentType::AppPayload(self.clone())),
1262 }
1263 }
1264}
1265
1266macro_rules! impl_command_payload_getters {
1267 ($($method_name:ident => $variant:ident($payload_type:ty)),* $(,)?) => {
1268 $(
1269 pub fn $method_name(&self) -> Result<&$payload_type, MessageError> {
1270 match &self.command_payload_type {
1271 Some(CommandPayloadType::$variant(payload)) => Ok(payload),
1272 Some(other) => Err(MessageError::InvalidCommandPayloadType {
1273 expected: Box::new(stringify!($variant).to_string()),
1274 got: Box::new(format!("{:?}", other)),
1275 }),
1276 None => Err(MessageError::InvalidCommandPayloadType {
1277 expected: Box::new(stringify!($variant).to_string()),
1278 got: Box::new("None".to_string()),
1279 }),
1280 }
1281 }
1282 )*
1283 };
1284}
1285
1286impl CommandPayload {
1287 pub fn as_content(self) -> Content {
1288 Content {
1289 content_type: Some(ContentType::CommandPayload(self)),
1290 }
1291 }
1292
1293 impl_command_payload_getters! {
1294 as_discovery_request_payload => DiscoveryRequest(DiscoveryRequestPayload),
1295 as_discovery_reply_payload => DiscoveryReply(DiscoveryReplyPayload),
1296 as_join_request_payload => JoinRequest(JoinRequestPayload),
1297 as_join_reply_payload => JoinReply(JoinReplyPayload),
1298 as_leave_request_payload => LeaveRequest(LeaveRequestPayload),
1299 as_leave_reply_payload => LeaveReply(LeaveReplyPayload),
1300 as_group_add_payload => GroupAdd(GroupAddPayload),
1301 as_group_remove_payload => GroupRemove(GroupRemovePayload),
1302 as_welcome_payload => GroupWelcome(GroupWelcomePayload),
1303 as_group_close_payload => GroupClose(GroupClosePayload),
1304 as_group_proposal_payload => GroupProposal(GroupProposalPayload),
1305 as_group_ack_payload => GroupAck(GroupAckPayload),
1306 as_group_nack_payload => GroupNack(GroupNackPayload),
1307 as_heartbeat_payload => Heartbeat(HeartbeatPayload),
1308 as_update_participant_state_payload => UpdateParticipantState(UpdateParticipantStatePayload),
1309 }
1310
1311 pub fn builder() -> CommandPayloadBuilder {
1312 CommandPayloadBuilder::new()
1313 }
1314}
1315
1316impl AsRef<ProtoPublish> for ProtoMessage {
1317 fn as_ref(&self) -> &ProtoPublish {
1318 match &self.message_type {
1319 Some(ProtoPublishType(p)) => p,
1320 _ => panic!("message type is not publish"),
1321 }
1322 }
1323}
1324
1325pub struct CommandPayloadBuilder;
1326
1327impl CommandPayloadBuilder {
1328 pub fn new() -> Self {
1329 Self
1330 }
1331
1332 pub fn discovery_request(self) -> CommandPayload {
1333 let payload = DiscoveryRequestPayload {};
1334 CommandPayload {
1335 command_payload_type: Some(CommandPayloadType::DiscoveryRequest(payload)),
1336 }
1337 }
1338
1339 pub fn discovery_reply(self) -> CommandPayload {
1340 let payload = DiscoveryReplyPayload {};
1341 CommandPayload {
1342 command_payload_type: Some(CommandPayloadType::DiscoveryReply(payload)),
1343 }
1344 }
1345
1346 #[allow(deprecated)]
1347 pub fn join_request(
1348 self,
1349 max_retries: Option<u32>,
1350 timer_duration: Option<Duration>,
1351 data_channel: Option<ProtoName>,
1352 control_channel: Option<ProtoName>,
1353 mls_settings: Option<ProtoMlsSettings>,
1354 ) -> CommandPayload {
1355 let timer_settings = match (timer_duration, max_retries) {
1356 (Some(t), Some(m)) => Some(TimerSettings {
1357 timeout: t.as_millis() as u32,
1358 max_retries: m,
1359 }),
1360 _ => None,
1361 };
1362
1363 let payload = JoinRequestPayload {
1364 timer_settings,
1365 channel: data_channel,
1366 control: control_channel,
1367 mls_settings,
1368 };
1369 CommandPayload {
1370 command_payload_type: Some(CommandPayloadType::JoinRequest(payload)),
1371 }
1372 }
1373
1374 pub fn join_reply(
1375 self,
1376 key_package: Option<Vec<u8>>,
1377 participant: Participant,
1378 ) -> CommandPayload {
1379 let payload = JoinReplyPayload {
1380 key_package,
1381 participant: Some(participant),
1382 };
1383 CommandPayload {
1384 command_payload_type: Some(CommandPayloadType::JoinReply(payload)),
1385 }
1386 }
1387
1388 pub fn leave_request(self) -> CommandPayload {
1389 let payload = LeaveRequestPayload {};
1390 CommandPayload {
1391 command_payload_type: Some(CommandPayloadType::LeaveRequest(payload)),
1392 }
1393 }
1394
1395 pub fn leave_reply(self) -> CommandPayload {
1396 let payload = LeaveReplyPayload {};
1397 CommandPayload {
1398 command_payload_type: Some(CommandPayloadType::LeaveReply(payload)),
1399 }
1400 }
1401
1402 pub fn group_add(
1403 self,
1404 new_participant: Participant,
1405 participants: Vec<Participant>,
1406 mls: Option<MlsPayload>,
1407 ) -> CommandPayload {
1408 let payload = GroupAddPayload {
1409 new_participant: Some(new_participant),
1410 participants,
1411 mls,
1412 };
1413 CommandPayload {
1414 command_payload_type: Some(CommandPayloadType::GroupAdd(payload)),
1415 }
1416 }
1417
1418 pub fn group_remove(
1419 self,
1420 removed_participant: ProtoName,
1421 participants: Vec<ProtoName>,
1422 mls: Option<MlsPayload>,
1423 ) -> CommandPayload {
1424 let payload = GroupRemovePayload {
1425 removed_participant: Some(removed_participant),
1426 participants,
1427 mls,
1428 };
1429 CommandPayload {
1430 command_payload_type: Some(CommandPayloadType::GroupRemove(payload)),
1431 }
1432 }
1433
1434 pub fn group_welcome(
1435 self,
1436 participants: Vec<Participant>,
1437 mls: Option<MlsPayload>,
1438 ) -> CommandPayload {
1439 let payload = GroupWelcomePayload { participants, mls };
1440 CommandPayload {
1441 command_payload_type: Some(CommandPayloadType::GroupWelcome(payload)),
1442 }
1443 }
1444
1445 pub fn group_close(self, participants: Vec<ProtoName>) -> CommandPayload {
1446 let payload = GroupClosePayload { participants };
1447 CommandPayload {
1448 command_payload_type: Some(CommandPayloadType::GroupClose(payload)),
1449 }
1450 }
1451
1452 pub fn group_proposal(
1453 self,
1454 source: Option<ProtoName>,
1455 mls_proposal: Vec<u8>,
1456 ) -> CommandPayload {
1457 let payload = GroupProposalPayload {
1458 source,
1459 mls_proposal,
1460 };
1461 CommandPayload {
1462 command_payload_type: Some(CommandPayloadType::GroupProposal(payload)),
1463 }
1464 }
1465
1466 pub fn group_ack(self) -> CommandPayload {
1467 let payload = GroupAckPayload {};
1468 CommandPayload {
1469 command_payload_type: Some(CommandPayloadType::GroupAck(payload)),
1470 }
1471 }
1472
1473 pub fn group_nack(self) -> CommandPayload {
1474 let payload = GroupNackPayload {};
1475 CommandPayload {
1476 command_payload_type: Some(CommandPayloadType::GroupNack(payload)),
1477 }
1478 }
1479
1480 pub fn heartbeat(self, epoch: u64) -> CommandPayload {
1481 let payload = HeartbeatPayload { epoch };
1482 CommandPayload {
1483 command_payload_type: Some(CommandPayloadType::Heartbeat(payload)),
1484 }
1485 }
1486
1487 pub fn update_participant_state(
1488 self,
1489 participant: ProtoName,
1490 new_state: ParticipantState,
1491 epoch: u64,
1492 ) -> CommandPayload {
1493 let payload = UpdateParticipantStatePayload {
1494 participant: Some(participant),
1495 new_state: new_state as i32,
1496 epoch,
1497 };
1498 CommandPayload {
1499 command_payload_type: Some(CommandPayloadType::UpdateParticipantState(payload)),
1500 }
1501 }
1502}
1503
1504impl Default for CommandPayloadBuilder {
1505 fn default() -> Self {
1506 Self::new()
1507 }
1508}
1509
1510pub struct ProtoMessageBuilder {
1511 source: Option<ProtoName>,
1512 destination: Option<ProtoName>,
1513 identity: Option<String>,
1514 flags: Option<SlimHeaderFlags>,
1515 session_type: Option<ProtoSessionType>,
1516 session_message_type: Option<SessionMessageType>,
1517 session_id: Option<u32>,
1518 message_id: Option<u32>,
1519 payload: Option<Content>,
1520 metadata: HashMap<String, String>,
1521 subscription_id: Option<u64>,
1522}
1523
1524impl ProtoMessageBuilder {
1525 pub fn new() -> Self {
1526 Self {
1527 source: None,
1528 destination: None,
1529 identity: None,
1530 flags: None,
1531 session_type: None,
1532 session_message_type: None,
1533 session_id: None,
1534 message_id: None,
1535 payload: None,
1536 metadata: HashMap::new(),
1537 subscription_id: None,
1538 }
1539 }
1540
1541 pub fn source(mut self, source: ProtoName) -> Self {
1542 self.source = Some(source);
1543 self
1544 }
1545
1546 pub fn destination(mut self, destination: ProtoName) -> Self {
1547 self.destination = Some(destination);
1548 self
1549 }
1550
1551 pub fn identity(mut self, identity: impl Into<String>) -> Self {
1552 self.identity = Some(identity.into());
1553 self
1554 }
1555
1556 pub fn flags(mut self, flags: SlimHeaderFlags) -> Self {
1557 self.flags = Some(flags);
1558 self
1559 }
1560
1561 pub fn fanout(mut self, fanout: u32) -> Self {
1562 self.flags.get_or_insert_default().fanout = fanout;
1563 self
1564 }
1565
1566 pub fn recv_from(mut self, recv_from: u64) -> Self {
1567 self.flags.get_or_insert_default().recv_from = Some(recv_from);
1568 self
1569 }
1570
1571 pub fn forward_to(mut self, forward_to: u64) -> Self {
1572 self.flags.get_or_insert_default().forward_to = Some(forward_to);
1573 self
1574 }
1575
1576 pub fn incoming_conn(mut self, incoming_conn: u64) -> Self {
1577 self.flags.get_or_insert_default().incoming_conn = Some(incoming_conn);
1578 self
1579 }
1580
1581 pub fn error(mut self, error: bool) -> Self {
1582 self.flags.get_or_insert_default().error = Some(error);
1583 self
1584 }
1585
1586 pub fn ttl(mut self, ttl: u32) -> Self {
1587 self.flags.get_or_insert_default().ttl = ttl;
1588 self
1589 }
1590
1591 pub fn session_type(mut self, session_type: ProtoSessionType) -> Self {
1592 self.session_type = Some(session_type);
1593 self
1594 }
1595
1596 pub fn session_message_type(mut self, session_message_type: SessionMessageType) -> Self {
1597 self.session_message_type = Some(session_message_type);
1598 self
1599 }
1600
1601 pub fn session_id(mut self, session_id: u32) -> Self {
1602 self.session_id = Some(session_id);
1603 self
1604 }
1605
1606 pub fn message_id(mut self, message_id: u32) -> Self {
1607 self.message_id = Some(message_id);
1608 self
1609 }
1610
1611 pub fn payload(mut self, payload: Content) -> Self {
1612 self.payload = Some(payload);
1613 self
1614 }
1615
1616 pub fn application_payload(mut self, payload_type: &str, blob: Vec<u8>) -> Self {
1617 let app_payload = ApplicationPayload::new(payload_type, blob);
1618 self.payload = Some(app_payload.as_content());
1619 self
1620 }
1621
1622 pub fn command_payload(mut self, payload: CommandPayload) -> Self {
1623 self.payload = Some(payload.as_content());
1624 self
1625 }
1626
1627 pub fn with_slim_header(mut self, header: SlimHeader) -> Self {
1628 if let Some(src) = header.source.clone() {
1629 self.source = Some(src);
1630 }
1631 if let Some(dst) = header.destination.clone() {
1632 self.destination = Some(dst);
1633 }
1634 if !header.identity.is_empty() {
1635 self.identity = Some(header.identity.clone());
1636 }
1637
1638 self.flags = Some(SlimHeaderFlags {
1639 fanout: header.fanout,
1640 recv_from: header.recv_from,
1641 forward_to: header.forward_to,
1642 incoming_conn: header.incoming_conn,
1643 error: header.error,
1644 ttl: header.ttl,
1645 });
1646 self
1647 }
1648
1649 pub fn with_session_header(mut self, header: SessionHeader) -> Self {
1650 self.session_type = Some(header.session_type());
1651 self.session_message_type = Some(header.session_message_type());
1652 self.session_id = Some(header.session_id);
1653 self.message_id = Some(header.message_id);
1654 self
1655 }
1656
1657 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1658 self.metadata.insert(key.into(), value.into());
1659 self
1660 }
1661
1662 pub fn metadata_map(mut self, map: HashMap<String, String>) -> Self {
1663 self.metadata.extend(map);
1664 self
1665 }
1666
1667 pub fn subscription_id(mut self, id: u64) -> Self {
1668 self.subscription_id = Some(id);
1669 self
1670 }
1671
1672 pub fn build_publish(self) -> Result<ProtoMessage, MessageError> {
1673 let source = self
1674 .source
1675 .ok_or(MessageError::BuilderErrorSourceRequired)?;
1676 let destination = self
1677 .destination
1678 .ok_or(MessageError::BuilderErrorDestinationRequired)?;
1679
1680 let slim_header = Some(SlimHeader::new(
1681 source,
1682 destination,
1683 self.identity.as_deref().unwrap_or(""),
1684 self.flags,
1685 ));
1686
1687 let session_header = if self.session_type.is_some() || self.session_message_type.is_some() {
1688 Some(SessionHeader::new(
1689 self.session_type
1690 .unwrap_or(ProtoSessionType::PointToPoint)
1691 .into(),
1692 self.session_message_type
1693 .unwrap_or(SessionMessageType::Msg)
1694 .into(),
1695 self.session_id.unwrap_or(0),
1696 self.message_id.unwrap_or_else(rand::random),
1697 ))
1698 } else {
1699 Some(SessionHeader::default())
1700 };
1701
1702 let publish = ProtoPublish::with_header(slim_header, session_header, self.payload);
1703 Ok(ProtoMessage::new(self.metadata, ProtoPublishType(publish)))
1704 }
1705
1706 pub fn build_subscribe(self) -> Result<ProtoMessage, MessageError> {
1707 let source = self
1708 .source
1709 .ok_or(MessageError::BuilderErrorSourceRequired)?;
1710 let destination = self
1711 .destination
1712 .ok_or(MessageError::BuilderErrorDestinationRequired)?;
1713
1714 let mut subscribe =
1715 ProtoSubscribe::new(source, destination, self.identity.as_deref(), self.flags);
1716 subscribe.subscription_id = self.subscription_id.unwrap_or_default();
1717
1718 Ok(ProtoMessage::new(
1719 self.metadata,
1720 ProtoSubscribeType(subscribe),
1721 ))
1722 }
1723
1724 pub fn build_unsubscribe(self) -> Result<ProtoMessage, MessageError> {
1725 let source = self
1726 .source
1727 .ok_or(MessageError::BuilderErrorSourceRequired)?;
1728 let destination = self
1729 .destination
1730 .ok_or(MessageError::BuilderErrorDestinationRequired)?;
1731
1732 let mut unsubscribe =
1733 ProtoUnsubscribe::new(source, destination, self.identity.as_deref(), self.flags);
1734 unsubscribe.subscription_id = self.subscription_id.unwrap_or_default();
1735
1736 Ok(ProtoMessage::new(
1737 self.metadata,
1738 ProtoUnsubscribeType(unsubscribe),
1739 ))
1740 }
1741
1742 pub fn build_subscription_ack(
1743 self,
1744 subscription_id: u64,
1745 success: bool,
1746 error: impl Into<String>,
1747 ) -> ProtoMessage {
1748 let ack = ProtoSubscriptionAck {
1749 subscription_id,
1750 success,
1751 error: error.into(),
1752 };
1753 ProtoMessage::new(self.metadata, ProtoSubscriptionAckType(ack))
1754 }
1755
1756 #[allow(clippy::too_many_arguments)]
1757 pub fn build_link_negotiation(
1758 self,
1759 link_id: impl Into<String>,
1760 slim_version: impl Into<String>,
1761 is_reply: bool,
1762 link_ecdh_public_key: Option<Vec<u8>>,
1763 connection_type: LinkConnectionType,
1764 node_id: impl Into<String>,
1765 deployment_name: impl Into<String>,
1766 ) -> ProtoMessage {
1767 let link = ProtoLink {
1768 link_type: Some(ProtoLinkType::LinkNegotiation(LinkNegotiationPayload {
1769 link_id: link_id.into(),
1770 slim_version: slim_version.into(),
1771 is_reply,
1772 link_ecdh_public_key: link_ecdh_public_key.unwrap_or_default(),
1773 connection_type: connection_type.into(),
1774 node_id: node_id.into(),
1775 deployment_name: deployment_name.into(),
1776 })),
1777 };
1778 ProtoMessage::new(self.metadata, ProtoLinkMessageType(link))
1779 }
1780}
1781
1782impl Default for ProtoMessageBuilder {
1783 fn default() -> Self {
1784 Self::new()
1785 }
1786}
1787
1788#[cfg(test)]
1789mod name_tests {
1790 use super::*;
1791
1792 #[test]
1793 fn test_proto_name_from_strings() {
1794 let n1 = ProtoName::from_strings(["Org", "Default", "App"]);
1795 let n2 = ProtoName::from_strings(["Org", "Default", "App"]);
1796 assert_eq!(n1, n2);
1797 let n3 = ProtoName::from_strings(["Other", "Default", "App"]);
1798 assert_ne!(n1, n3);
1799 }
1800
1801 #[test]
1802 fn test_proto_name_with_id() {
1803 let n = ProtoName::from_strings(["a", "b", "c"]).with_id(42);
1804 assert_eq!(n.id(), 42);
1805 assert!(n.has_id());
1806 }
1807
1808 #[test]
1809 fn test_proto_name_reset_id() {
1810 let mut n = ProtoName::from_strings(["a", "b", "c"]).with_id(42);
1811 n.reset_id();
1812 assert_eq!(n.id(), NameId::NULL_COMPONENT);
1813 assert!(!n.has_id());
1814 }
1815
1816 #[test]
1817 fn test_proto_name_match_prefix() {
1818 let n1 = ProtoName::from_strings(["Org", "Default", "App"]).with_id(1);
1819 let n2 = ProtoName::from_strings(["Org", "Default", "App"]).with_id(999);
1820 assert!(n1.match_prefix(&n2));
1821
1822 let n3 = ProtoName::from_strings(["Other", "Default", "App"]).with_id(1);
1823 assert!(!n1.match_prefix(&n3));
1824 }
1825
1826 #[test]
1827 fn test_proto_name_str_components() {
1828 let n = ProtoName::from_strings(["org", "ns", "app"]);
1829 let (a, b, c) = n.str_components();
1830 assert_eq!(a, "org");
1831 assert_eq!(b, "ns");
1832 assert_eq!(c, "app");
1833 }
1834
1835 #[test]
1836 fn test_proto_name_hash_stability() {
1837 let proto = ProtoName::from_strings(["Org", "Default", "App"]).with_id(7);
1838 let enc = proto.name.unwrap();
1839 let proto2 = ProtoName::from_strings(["Org", "Default", "App"]).with_id(7);
1840 let enc2 = proto2.name.unwrap();
1841 assert_eq!(enc, enc2);
1842 assert_eq!(enc.id(), 7);
1843 }
1844
1845 #[test]
1846 fn test_name_id_roundtrip() {
1847 let values = [0u128, 1, 42, u128::MAX / 2, u128::MAX - 4];
1848 for v in values {
1849 let nid = NameId::from(v);
1850 let result: u128 = nid.into();
1851 assert_eq!(result, v, "roundtrip failed for {v}");
1852 }
1853 }
1854
1855 #[test]
1856 fn test_name_id_from_string_valid_uuid() {
1857 let nid = NameId::try_from("00000000-0000-0000-0000-00000000002a".to_string())
1858 .expect("valid UUID string should parse");
1859 let result: u128 = nid.into();
1860 assert_eq!(result, 42);
1861 }
1862
1863 #[test]
1864 fn test_name_id_from_string_invalid() {
1865 assert!(NameId::try_from("not-a-uuid".to_string()).is_err());
1866 assert!(NameId::try_from("".to_string()).is_err());
1867 }
1868
1869 #[test]
1870 fn test_name_id_display_uuid() {
1871 let nid = NameId::from(42);
1872 assert_eq!(nid.to_string(), "00000000-0000-0000-0000-00000000002a");
1873 }
1874
1875 #[test]
1876 fn test_name_id_display_reserved() {
1877 let str_nid: String = NameId::from(NameId::NULL_COMPONENT).into();
1878 assert_eq!(str_nid, "NULL_COMPONENT");
1879 }
1880
1881 #[test]
1882 fn test_name_id_is_reserved() {
1883 assert!(NameId::is_reserved_id(NameId::NULL_COMPONENT));
1884 assert!(!NameId::is_reserved_id(0));
1885 assert!(!NameId::is_reserved_id(42));
1886 }
1887
1888 #[test]
1889 fn test_proto_name_default_id_is_null_component() {
1890 let n = ProtoName::from_strings(["a", "b", "c"]);
1891 assert_eq!(n.id(), NameId::NULL_COMPONENT);
1892 assert!(!n.has_id());
1893 }
1894
1895 #[test]
1896 fn test_proto_name_set_id() {
1897 let mut n = ProtoName::from_strings(["a", "b", "c"]);
1898 n.set_id(123);
1899 assert_eq!(n.id(), 123);
1900 assert!(n.has_id());
1901 }
1902
1903 #[test]
1904 fn test_proto_name_string_id() {
1905 let n = ProtoName::from_strings(["a", "b", "c"]).with_id(42);
1906 assert_eq!(n.string_id(), "00000000-0000-0000-0000-00000000002a");
1907 }
1908
1909 #[test]
1910 fn test_proto_name_display_with_id() {
1911 let n = ProtoName::from_strings(["org", "ns", "app"]).with_id(1);
1912 let s = format!("{}", n);
1913 assert_eq!(s, "org/ns/app/00000000-0000-0000-0000-000000000001");
1914 }
1915
1916 #[test]
1917 fn test_proto_name_display_null_component() {
1918 let n = ProtoName::from_strings(["org", "ns", "app"]);
1919 let s = format!("{}", n);
1920 assert_eq!(s, "org/ns/app/NULL_COMPONENT");
1921 }
1922
1923 #[test]
1924 fn test_encoded_name_id_when_name_id_none() {
1925 let enc = EncodedName {
1926 component_0: 0,
1927 component_1: 0,
1928 component_2: 0,
1929 name_id: None,
1930 };
1931 assert_eq!(enc.id(), NameId::NULL_COMPONENT);
1932 assert_eq!(enc.string_id(), "NULL_COMPONENT");
1933 }
1934
1935 #[test]
1936 fn test_parse_name_valid() {
1937 let n = ProtoName::parse_name("org/ns/app").unwrap();
1938 let (a, b, c) = n.str_components();
1939 assert_eq!(a, "org");
1940 assert_eq!(b, "ns");
1941 assert_eq!(c, "app");
1942 }
1943
1944 #[test]
1945 fn test_parse_name_trims_whitespace() {
1946 let n = ProtoName::parse_name(" org / ns / app ").unwrap();
1947 let (a, b, c) = n.str_components();
1948 assert_eq!(a, "org");
1949 assert_eq!(b, "ns");
1950 assert_eq!(c, "app");
1951 }
1952
1953 #[test]
1954 fn test_parse_name_empty_string() {
1955 assert!(ProtoName::parse_name("").is_err());
1956 assert!(ProtoName::parse_name(" ").is_err());
1957 }
1958
1959 #[test]
1960 fn test_parse_name_too_few_parts() {
1961 assert!(ProtoName::parse_name("org/ns").is_err());
1962 assert!(ProtoName::parse_name("org").is_err());
1963 }
1964
1965 #[test]
1966 fn test_parse_name_too_many_parts() {
1967 assert!(ProtoName::parse_name("a/b/c/d").is_err());
1968 }
1969
1970 #[test]
1971 fn test_parse_name_empty_component() {
1972 assert!(ProtoName::parse_name("org//app").is_err());
1973 assert!(ProtoName::parse_name("/ns/app").is_err());
1974 assert!(ProtoName::parse_name("org/ns/").is_err());
1975 }
1976}
1977
1978#[cfg(test)]
1979mod message_tests {
1980 use super::*;
1981
1982 fn test_subscription_template(
1983 subscription: bool,
1984 source: ProtoName,
1985 dst: ProtoName,
1986 identity: Option<&str>,
1987 flags: Option<SlimHeaderFlags>,
1988 ) {
1989 let sub = {
1990 let mut builder = ProtoMessage::builder()
1991 .source(source.clone())
1992 .destination(dst.clone());
1993
1994 if let Some(id) = identity {
1995 builder = builder.identity(id);
1996 }
1997
1998 if let Some(f) = flags.clone() {
1999 builder = builder.flags(f);
2000 }
2001
2002 if subscription {
2003 builder.build_subscribe().unwrap()
2004 } else {
2005 builder.build_unsubscribe().unwrap()
2006 }
2007 };
2008
2009 let flags = if flags.is_none() {
2010 Some(SlimHeaderFlags::default())
2011 } else {
2012 flags
2013 };
2014
2015 assert!(!sub.is_publish());
2016 assert_eq!(sub.is_subscribe(), subscription);
2017 assert_eq!(sub.is_unsubscribe(), !subscription);
2018 assert_eq!(flags.as_ref().unwrap().recv_from, sub.get_recv_from());
2019 assert_eq!(flags.as_ref().unwrap().forward_to, sub.get_forward_to());
2020 assert_eq!(None, sub.try_get_incoming_conn());
2021 assert_eq!(source, sub.get_source());
2022 assert_eq!(dst, sub.get_dst());
2023 }
2024
2025 fn test_publish_template(
2026 source: ProtoName,
2027 dst: ProtoName,
2028 identity: Option<&str>,
2029 flags: Option<SlimHeaderFlags>,
2030 ) {
2031 let mut builder = ProtoMessage::builder()
2032 .source(source.clone())
2033 .destination(dst.clone())
2034 .application_payload("str", b"this is the content of the message".to_vec());
2035
2036 if let Some(id) = identity {
2037 builder = builder.identity(id);
2038 }
2039
2040 if let Some(f) = flags.clone() {
2041 builder = builder.flags(f);
2042 }
2043
2044 let pub_msg = builder.build_publish().unwrap();
2045 let flags = flags.or_else(|| Some(SlimHeaderFlags::default()));
2046
2047 assert!(pub_msg.is_publish());
2048 assert!(!pub_msg.is_subscribe());
2049 assert!(!pub_msg.is_unsubscribe());
2050 assert_eq!(flags.as_ref().unwrap().recv_from, pub_msg.get_recv_from());
2051 assert_eq!(flags.as_ref().unwrap().forward_to, pub_msg.get_forward_to());
2052 assert_eq!(None, pub_msg.try_get_incoming_conn());
2053 assert_eq!(source, pub_msg.get_source());
2054 assert_eq!(dst, pub_msg.get_dst());
2055 assert_eq!(flags.as_ref().unwrap().fanout, pub_msg.get_fanout());
2056 }
2057
2058 #[test]
2059 fn test_subscription() {
2060 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2061 let dst = ProtoName::from_strings(["org", "ns", "type"]).with_id(2);
2062 test_subscription_template(true, source.clone(), dst.clone(), None, None);
2063 test_subscription_template(
2064 true,
2065 source.clone(),
2066 dst.clone(),
2067 None,
2068 Some(SlimHeaderFlags::default().with_recv_from(50)),
2069 );
2070 test_subscription_template(
2071 true,
2072 source,
2073 dst,
2074 None,
2075 Some(SlimHeaderFlags::default().with_forward_to(30)),
2076 );
2077 }
2078
2079 #[test]
2080 fn test_unsubscription() {
2081 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2082 let dst = ProtoName::from_strings(["org", "ns", "type"]).with_id(2);
2083 test_subscription_template(false, source.clone(), dst.clone(), None, None);
2084 test_subscription_template(
2085 false,
2086 source.clone(),
2087 dst.clone(),
2088 None,
2089 Some(SlimHeaderFlags::default().with_recv_from(50)),
2090 );
2091 test_subscription_template(
2092 false,
2093 source,
2094 dst,
2095 None,
2096 Some(SlimHeaderFlags::default().with_forward_to(30)),
2097 );
2098 }
2099
2100 #[test]
2101 fn test_publish() {
2102 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2103 let mut dst = ProtoName::from_strings(["org", "ns", "type"]);
2104 test_publish_template(
2105 source.clone(),
2106 dst.clone(),
2107 None,
2108 Some(SlimHeaderFlags::default()),
2109 );
2110 dst.set_id(2);
2111 test_publish_template(
2112 source.clone(),
2113 dst.clone(),
2114 None,
2115 Some(SlimHeaderFlags::default()),
2116 );
2117 dst.reset_id();
2118 test_publish_template(
2119 source.clone(),
2120 dst.clone(),
2121 None,
2122 Some(SlimHeaderFlags::default().with_recv_from(50)),
2123 );
2124 test_publish_template(
2125 source,
2126 dst,
2127 None,
2128 Some(SlimHeaderFlags::default().with_forward_to(30)),
2129 );
2130 }
2131
2132 #[test]
2133 fn test_conversions() {
2134 let name = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2135 let dst = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2136 let proto_subscribe = ProtoMessage::builder()
2137 .source(name.clone())
2138 .destination(dst.clone())
2139 .flags(
2140 SlimHeaderFlags::default()
2141 .with_recv_from(2)
2142 .with_forward_to(3),
2143 )
2144 .build_subscribe()
2145 .unwrap();
2146 let proto_subscribe = ProtoSubscribe::from(proto_subscribe);
2147 assert_eq!(proto_subscribe.header.as_ref().unwrap().get_source(), name);
2148 assert_eq!(proto_subscribe.header.as_ref().unwrap().get_dst(), dst);
2149
2150 let proto_unsubscribe = ProtoMessage::builder()
2151 .source(name.clone())
2152 .destination(dst.clone())
2153 .flags(
2154 SlimHeaderFlags::default()
2155 .with_recv_from(2)
2156 .with_forward_to(3),
2157 )
2158 .build_unsubscribe()
2159 .unwrap();
2160 let proto_unsubscribe = ProtoUnsubscribe::from(proto_unsubscribe);
2161 assert_eq!(
2162 proto_unsubscribe.header.as_ref().unwrap().get_source(),
2163 name
2164 );
2165 assert_eq!(proto_unsubscribe.header.as_ref().unwrap().get_dst(), dst);
2166
2167 let proto_publish = ProtoMessage::builder()
2168 .source(name.clone())
2169 .destination(dst.clone())
2170 .flags(
2171 SlimHeaderFlags::default()
2172 .with_recv_from(2)
2173 .with_forward_to(3),
2174 )
2175 .application_payload("str", b"this is the content of the message".to_vec())
2176 .build_publish()
2177 .unwrap();
2178 let proto_publish = ProtoPublish::from(proto_publish);
2179 assert_eq!(proto_publish.header.as_ref().unwrap().get_source(), name);
2180 assert_eq!(proto_publish.header.as_ref().unwrap().get_dst(), dst);
2181 }
2182
2183 #[test]
2184 fn test_panic() {
2185 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2186 let dst = ProtoName::from_strings(["org", "ns", "type"]).with_id(2);
2187 let msg = ProtoMessage::builder()
2188 .source(source)
2189 .destination(dst)
2190 .flags(
2191 SlimHeaderFlags::default()
2192 .with_recv_from(2)
2193 .with_forward_to(3),
2194 )
2195 .build_subscribe()
2196 .unwrap();
2197
2198 assert!(std::panic::catch_unwind(|| ProtoUnsubscribe::from(msg.clone())).is_err());
2199 assert!(std::panic::catch_unwind(|| ProtoPublish::from(msg.clone())).is_err());
2200 assert!(std::panic::catch_unwind(|| ProtoSubscribe::from(msg)).is_ok());
2201 }
2202
2203 #[test]
2204 fn test_panic_header() {
2205 let header = SlimHeader {
2206 source: None,
2207 destination: None,
2208 identity: String::new(),
2209 fanout: 0,
2210 version: version().to_string(),
2211 recv_from: None,
2212 forward_to: None,
2213 incoming_conn: None,
2214 error: None,
2215 header_mac: None,
2216 ttl: DEFAULT_TTL,
2217 e2e_header_sig: None,
2218 };
2219
2220 assert!(std::panic::catch_unwind(|| header.get_source()).is_err());
2221 assert!(std::panic::catch_unwind(|| header.get_dst()).is_err());
2222 assert!(std::panic::catch_unwind(|| header.get_recv_from()).is_ok());
2223 assert!(std::panic::catch_unwind(|| header.get_forward_to()).is_ok());
2224 assert!(std::panic::catch_unwind(|| header.get_incoming_conn()).is_ok());
2225 assert!(std::panic::catch_unwind(|| header.get_error()).is_ok());
2226 }
2227
2228 #[test]
2229 fn test_panic_session_header() {
2230 let header = SessionHeader::new(0, 0, 0, 0);
2231 assert!(std::panic::catch_unwind(|| header.get_session_id()).is_ok());
2232 assert!(std::panic::catch_unwind(|| header.get_message_id()).is_ok());
2233 }
2234
2235 #[test]
2236 fn test_panic_proto_message() {
2237 let message = ProtoMessage {
2238 metadata: HashMap::new(),
2239 message_type: None,
2240 };
2241 assert!(std::panic::catch_unwind(|| message.get_slim_header()).is_err());
2242 assert!(std::panic::catch_unwind(|| message.get_type()).is_err());
2243 assert!(std::panic::catch_unwind(|| message.get_source()).is_err());
2244 assert!(std::panic::catch_unwind(|| message.get_dst()).is_err());
2245 assert!(std::panic::catch_unwind(|| message.get_recv_from()).is_err());
2246 assert!(std::panic::catch_unwind(|| message.get_forward_to()).is_err());
2247 assert!(std::panic::catch_unwind(|| message.get_incoming_conn()).is_err());
2248 assert!(std::panic::catch_unwind(|| message.get_fanout()).is_err());
2249 }
2250
2251 #[test]
2252 fn test_service_type_to_int() {
2253 let total_service_types = SessionMessageType::UpdateParticipantState as i32;
2254 for i in 0..total_service_types {
2255 let service_type =
2256 SessionMessageType::try_from(i).expect("failed to convert int to service type");
2257 assert_eq!(i32::from(service_type), i32::from(service_type));
2258 }
2259 assert!(SessionMessageType::try_from(total_service_types + 1).is_err());
2260 }
2261
2262 #[test]
2263 fn test_proto_message_builder() {
2264 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2265 let dest = ProtoName::from_strings(["org", "ns", "app"]).with_id(2);
2266
2267 let msg = ProtoMessage::builder()
2268 .source(source.clone())
2269 .destination(dest.clone())
2270 .application_payload("test", b"hello world".to_vec())
2271 .build_publish()
2272 .unwrap();
2273 assert!(msg.is_publish());
2274 assert_eq!(msg.get_source(), source);
2275 assert_eq!(msg.get_dst(), dest);
2276
2277 let msg = ProtoMessage::builder()
2278 .source(source.clone())
2279 .destination(dest.clone())
2280 .session_type(ProtoSessionType::Multicast)
2281 .session_message_type(SessionMessageType::Msg)
2282 .session_id(42)
2283 .message_id(100)
2284 .fanout(256)
2285 .application_payload("test", b"broadcast".to_vec())
2286 .build_publish()
2287 .unwrap();
2288 assert_eq!(msg.get_session_type(), ProtoSessionType::Multicast);
2289 assert_eq!(msg.get_id(), 100);
2290 assert_eq!(msg.get_fanout(), 256);
2291
2292 let msg = ProtoMessage::builder()
2293 .source(source.clone())
2294 .destination(dest.clone())
2295 .metadata("key1", "value1")
2296 .metadata("key2", "value2")
2297 .application_payload("test", vec![1, 2, 3])
2298 .build_publish()
2299 .unwrap();
2300 assert_eq!(msg.get_metadata("key1"), Some(&"value1".to_string()));
2301 assert_eq!(msg.get_metadata("key2"), Some(&"value2".to_string()));
2302
2303 let msg = ProtoMessage::builder()
2304 .source(source.clone())
2305 .destination(dest.clone())
2306 .recv_from(10)
2307 .build_subscribe()
2308 .unwrap();
2309 assert!(msg.is_subscribe());
2310 assert_eq!(msg.get_recv_from(), Some(10));
2311
2312 let msg = ProtoMessage::builder()
2313 .source(source)
2314 .destination(dest)
2315 .forward_to(20)
2316 .build_unsubscribe()
2317 .unwrap();
2318 assert!(msg.is_unsubscribe());
2319 assert_eq!(msg.get_forward_to(), Some(20));
2320 }
2321
2322 #[test]
2323 fn test_command_payload_builder() {
2324 let dest = ProtoName::from_strings(["org", "ns", "app"]);
2325
2326 assert!(CommandPayload::builder()
2327 .discovery_request()
2328 .as_discovery_request_payload()
2329 .is_ok());
2330 assert!(CommandPayload::builder()
2331 .discovery_reply()
2332 .as_discovery_reply_payload()
2333 .is_ok());
2334
2335 let payload = CommandPayload::builder().join_request(
2336 Some(5),
2337 Some(Duration::from_secs(10)),
2338 Some(dest.clone()),
2339 None,
2340 Some(ProtoMlsSettings::default()),
2341 );
2342 let extracted = payload.as_join_request_payload().unwrap();
2343 assert!(extracted.mls_settings.is_some());
2344 assert!(extracted.timer_settings.is_some());
2345
2346 let participant = Participant::new(dest.clone(), ParticipantSettings::bidirectional());
2347 let payload =
2348 CommandPayload::builder().join_reply(Some(vec![1, 2, 3]), participant.clone());
2349 let extracted = payload.as_join_reply_payload().unwrap();
2350 assert_eq!(extracted.key_package, Some(vec![1, 2, 3]));
2351 assert_eq!(extracted.participant, Some(participant.clone()));
2352
2353 assert!(CommandPayload::builder()
2354 .leave_request()
2355 .as_leave_request_payload()
2356 .is_ok());
2357 assert!(CommandPayload::builder()
2358 .leave_reply()
2359 .as_leave_reply_payload()
2360 .is_ok());
2361
2362 let participants = vec![participant.clone()];
2363 let payload =
2364 CommandPayload::builder().group_add(participant.clone(), participants.clone(), None);
2365 let extracted = payload.as_group_add_payload().unwrap();
2366 assert_eq!(extracted.new_participant, Some(participant));
2367 assert_eq!(extracted.participants, participants);
2368
2369 let payload =
2370 CommandPayload::builder().group_remove(dest.clone(), vec![dest.clone()], None);
2371 assert!(payload
2372 .as_group_remove_payload()
2373 .unwrap()
2374 .removed_participant
2375 .is_some());
2376 assert!(!CommandPayload::builder()
2377 .group_welcome(
2378 vec![Participant::new(
2379 dest.clone(),
2380 ParticipantSettings::bidirectional()
2381 )],
2382 None
2383 )
2384 .as_welcome_payload()
2385 .unwrap()
2386 .participants
2387 .is_empty());
2388 assert_eq!(
2389 CommandPayload::builder()
2390 .group_proposal(Some(dest.clone()), vec![4, 5, 6])
2391 .as_group_proposal_payload()
2392 .unwrap()
2393 .mls_proposal,
2394 vec![4, 5, 6]
2395 );
2396 assert!(CommandPayload::builder()
2397 .group_ack()
2398 .as_group_ack_payload()
2399 .is_ok());
2400 assert!(CommandPayload::builder()
2401 .group_nack()
2402 .as_group_nack_payload()
2403 .is_ok());
2404 assert!(CommandPayload::builder()
2405 .heartbeat(0)
2406 .as_heartbeat_payload()
2407 .is_ok());
2408 assert!(CommandPayload::builder()
2409 .update_participant_state(
2410 ProtoName::from_strings(["org", "ns", "test"]),
2411 ParticipantState::Offline,
2412 42,
2413 )
2414 .as_update_participant_state_payload()
2415 .is_ok());
2416 }
2417
2418 #[test]
2419 fn test_builder_with_command_payload() {
2420 let source = ProtoName::from_strings(["org", "ns", "type"]).with_id(1);
2421 let dest = ProtoName::from_strings(["org", "ns", "app"]).with_id(2);
2422 let cmd_payload = CommandPayload::builder().discovery_request();
2423
2424 let msg = ProtoMessage::builder()
2425 .source(source)
2426 .destination(dest)
2427 .session_type(ProtoSessionType::PointToPoint)
2428 .session_message_type(SessionMessageType::DiscoveryRequest)
2429 .session_id(1)
2430 .command_payload(cmd_payload)
2431 .build_publish()
2432 .unwrap();
2433
2434 assert!(msg.is_publish());
2435 assert_eq!(
2436 msg.get_session_message_type(),
2437 SessionMessageType::DiscoveryRequest
2438 );
2439 }
2440
2441 #[test]
2442 fn test_validate_link_without_link_type() {
2443 let link = ProtoLink { link_type: None };
2444 let msg = ProtoMessage::new(HashMap::new(), ProtoLinkMessageType(link));
2445 assert!(matches!(msg.validate(), Err(MessageError::LinkTypeNotSet)));
2446 }
2447
2448 #[test]
2449 fn test_validate_link_with_link_type() {
2450 let link = ProtoLink {
2451 link_type: Some(ProtoLinkType::LinkNegotiation(LinkNegotiationPayload {
2452 link_id: "abc".into(),
2453 slim_version: "1.0.0".into(),
2454 is_reply: false,
2455 link_ecdh_public_key: vec![],
2456 connection_type: 0,
2457 node_id: String::new(),
2458 deployment_name: String::new(),
2459 })),
2460 };
2461 let msg = ProtoMessage::new(HashMap::new(), ProtoLinkMessageType(link));
2462 assert!(msg.validate().is_ok());
2463 }
2464
2465 #[test]
2466 fn test_build_link_negotiation_request() {
2467 let msg = ProtoMessage::builder().build_link_negotiation(
2468 "my-id",
2469 "1.2.3",
2470 false,
2471 None,
2472 LinkConnectionType::Remote,
2473 "",
2474 "",
2475 );
2476 assert!(msg.is_link());
2477 assert!(!msg.is_publish());
2478 assert!(!msg.is_subscribe());
2479 assert!(msg.validate().is_ok());
2480 }
2481
2482 #[test]
2483 fn test_build_link_negotiation_reply() {
2484 let msg = ProtoMessage::builder().build_link_negotiation(
2485 "my-id",
2486 "1.2.3",
2487 true,
2488 None,
2489 LinkConnectionType::Remote,
2490 "",
2491 "",
2492 );
2493 assert!(msg.is_link());
2494 assert!(msg.validate().is_ok());
2495 }
2496
2497 #[test]
2498 fn test_validate_subscribe_missing_source_encoded_name() {
2499 let valid = ProtoName::from_strings(["org", "ns", "agent"]);
2500 let hdr = SlimHeader {
2501 source: Some(ProtoName {
2502 name: None,
2503 str_name: None,
2504 }),
2505 destination: Some(valid),
2506 ..Default::default()
2507 };
2508 let msg = ProtoMessage::new(
2509 HashMap::new(),
2510 ProtoSubscribeType(ProtoSubscribe {
2511 header: Some(hdr),
2512 ..Default::default()
2513 }),
2514 );
2515 assert!(matches!(
2516 msg.validate(),
2517 Err(MessageError::SourceEncodedNameNotFound)
2518 ));
2519 }
2520
2521 #[test]
2522 fn test_validate_subscribe_missing_destination_encoded_name() {
2523 let valid = ProtoName::from_strings(["org", "ns", "agent"]);
2524 let hdr = SlimHeader {
2525 source: Some(valid),
2526 destination: Some(ProtoName {
2527 name: None,
2528 str_name: None,
2529 }),
2530 ..Default::default()
2531 };
2532 let msg = ProtoMessage::new(
2533 HashMap::new(),
2534 ProtoSubscribeType(ProtoSubscribe {
2535 header: Some(hdr),
2536 ..Default::default()
2537 }),
2538 );
2539 assert!(matches!(
2540 msg.validate(),
2541 Err(MessageError::DestinationEncodedNameNotFound)
2542 ));
2543 }
2544
2545 #[test]
2546 fn test_participant_settings_convenience_methods() {
2547 let bidirectional = ParticipantSettings::bidirectional();
2548 assert!(bidirectional.sends_data);
2549 assert!(bidirectional.receives_data);
2550 assert!(bidirectional.is_sender());
2551 assert!(bidirectional.is_receiver());
2552
2553 let send_only = ParticipantSettings::send_only();
2554 assert!(send_only.sends_data);
2555 assert!(!send_only.receives_data);
2556 assert!(send_only.is_sender());
2557 assert!(!send_only.is_receiver());
2558
2559 let receive_only = ParticipantSettings::receive_only();
2560 assert!(!receive_only.sends_data);
2561 assert!(receive_only.receives_data);
2562 assert!(!receive_only.is_sender());
2563 assert!(receive_only.is_receiver());
2564 }
2565}
2566
2567impl From<slim_config::conn_type::ConnType> for crate::dataplane::proto::v1::LinkConnectionType {
2569 fn from(ct: slim_config::conn_type::ConnType) -> Self {
2570 match ct {
2571 slim_config::conn_type::ConnType::Peer => {
2572 crate::dataplane::proto::v1::LinkConnectionType::Peer
2573 }
2574 slim_config::conn_type::ConnType::Edge => {
2575 crate::dataplane::proto::v1::LinkConnectionType::Edge
2576 }
2577 _ => crate::dataplane::proto::v1::LinkConnectionType::Remote,
2578 }
2579 }
2580}