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