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