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