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