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