citadel_sdk/
responses.rs

1//! Protocol Response Helpers
2//!
3//! This module provides helper functions for handling responses to various protocol
4//! operations in the Citadel Protocol. It simplifies the process of sending responses
5//! to peer registration, connection, and group invitation requests.
6//!
7//! # Features
8//! - Peer registration response handling
9//! - Peer connection response management
10//! - Group invitation response processing
11//! - Automatic ticket management
12//! - Connection type reversal handling
13//! - Username resolution and validation
14//!
15//! # Example
16//! ```rust
17//! use citadel_sdk::prelude::*;
18//! use citadel_sdk::responses;
19//!
20//! async fn handle_peer_request<R: Ratchet>(
21//!     signal: PeerSignal,
22//!     remote: &impl Remote<R>
23//! ) -> Result<(), NetworkError> {
24//!     // Accept a peer registration request
25//!     let ticket = responses::peer_register(signal, true, remote).await?;
26//!     
27//!     Ok(())
28//! }
29//! ```
30//!
31//! # Important Notes
32//! - Responses must match request tickets
33//! - Connection types are automatically reversed
34//! - Username resolution is handled internally
35//! - Group responses require server connection
36//!
37//! # Related Components
38//! - [`Remote`]: Network communication interface
39//! - [`PeerSignal`]: Peer communication events
40//! - [`NodeResult`]: Network operation results
41//! - [`Ticket`]: Request/response correlation
42//!
43//! [`Remote`]: crate::prelude::Remote
44//! [`PeerSignal`]: crate::prelude::PeerSignal
45//! [`NodeResult`]: crate::prelude::NodeResult
46//! [`Ticket`]: crate::prelude::Ticket
47
48use crate::prelude::*;
49
50/// Given the `input_signal` from the peer, this function sends a register response to the target peer
51pub async fn peer_register<R: Ratchet>(
52    input_signal: PeerSignal,
53    accept: bool,
54    remote: &impl Remote<R>,
55) -> Result<Ticket, NetworkError> {
56    if let PeerSignal::PostRegister {
57        peer_conn_type: v_conn,
58        inviter_username: username,
59        invitee_username: username_opt,
60        ticket_opt: ticket,
61        invitee_response: None,
62    } = input_signal
63    {
64        let this_cid = v_conn.get_original_target_cid();
65        let ticket = get_ticket(ticket)?;
66        let resp = if accept {
67            let username = remote
68                .account_manager()
69                .get_username_by_cid(this_cid)
70                .await
71                .map_err(|err| NetworkError::Generic(err.into_string()))?
72                .ok_or(NetworkError::InvalidRequest(
73                    "Unable to find local username implied by signal",
74                ))?;
75            PeerResponse::Accept(Some(username))
76        } else {
77            PeerResponse::Decline
78        };
79
80        // v_conn must be reversed when rebounding a signal
81        let signal = PeerSignal::PostRegister {
82            peer_conn_type: v_conn.reverse(),
83            inviter_username: username,
84            invitee_username: username_opt,
85            ticket_opt: Some(ticket),
86            invitee_response: Some(resp),
87        };
88        remote
89            .send_with_custom_ticket(
90                ticket,
91                NodeRequest::PeerCommand(PeerCommand {
92                    session_cid: this_cid,
93                    command: signal,
94                }),
95            )
96            .await
97            .map(|_| ticket)
98    } else {
99        Err(NetworkError::InternalError(
100            "Input signal is not a valid PostRegister",
101        ))
102    }
103}
104
105/// Given the `input_signal` from the peer, this function sends a connect response to the target peer
106pub async fn peer_connect<R: Ratchet>(
107    input_signal: PeerSignal,
108    accept: bool,
109    remote: &impl Remote<R>,
110    peer_session_password: Option<PreSharedKey>,
111) -> Result<Ticket, NetworkError> {
112    if let PeerSignal::PostConnect {
113        peer_conn_type: v_conn,
114        ticket_opt: ticket,
115        invitee_response: None,
116        session_security_settings: sess_sec,
117        udp_mode,
118        session_password: None,
119    } = input_signal
120    {
121        let this_cid = v_conn.get_original_target_cid();
122        let ticket = get_ticket(ticket)?;
123        let resp = if accept {
124            // we do not need a username here, unlike in postregister
125            PeerResponse::Accept(None)
126        } else {
127            PeerResponse::Decline
128        };
129
130        let signal = NodeRequest::PeerCommand(PeerCommand {
131            session_cid: this_cid,
132            command: PeerSignal::PostConnect {
133                peer_conn_type: v_conn.reverse(),
134                ticket_opt: Some(ticket),
135                invitee_response: Some(resp),
136                session_security_settings: sess_sec,
137                udp_mode,
138                session_password: peer_session_password,
139            },
140        });
141        remote
142            .send_with_custom_ticket(ticket, signal)
143            .await
144            .map(|_| ticket)
145    } else {
146        Err(NetworkError::InternalError(
147            "Input signal is not a valid PostConnect",
148        ))
149    }
150}
151
152/// Given a group invite signal, this function sends a response to the server
153pub async fn group_invite<R: Ratchet>(
154    invite_signal: NodeResult<R>,
155    accept: bool,
156    remote: &impl Remote<R>,
157) -> Result<Ticket, NetworkError> {
158    if let NodeResult::GroupEvent(GroupEvent {
159        session_cid: cid,
160        ticket,
161        event: GroupBroadcast::Invitation { sender: _, key },
162    }) = invite_signal
163    {
164        let resp = if accept {
165            GroupBroadcast::AcceptMembership { target: cid, key }
166        } else {
167            GroupBroadcast::DeclineMembership { target: cid, key }
168        };
169
170        let request = NodeRequest::GroupBroadcastCommand(GroupBroadcastCommand {
171            session_cid: cid,
172            command: resp,
173        });
174        remote
175            .send_with_custom_ticket(ticket, request)
176            .await
177            .map(|_| ticket)
178    } else {
179        Err(NetworkError::InternalError(
180            "Input signal is not a group invitation",
181        ))
182    }
183}
184
185fn get_ticket(ticket: Option<Ticket>) -> Result<Ticket, NetworkError> {
186    ticket.ok_or(NetworkError::InvalidPacket(
187        "This event was improperly formed",
188    ))
189}