Skip to main content

agntcy_slim_proto/
impls.rs

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