Skip to main content

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                .ok_or(citadel_io::error!(
72                    citadel_io::ErrorCode::ResponseLocalUsernameMissing
73                ))?;
74            PeerResponse::Accept(Some(username))
75        } else {
76            PeerResponse::Decline
77        };
78
79        // v_conn must be reversed when rebounding a signal
80        let signal = PeerSignal::PostRegister {
81            peer_conn_type: v_conn.reverse(),
82            inviter_username: username,
83            invitee_username: username_opt,
84            ticket_opt: Some(ticket),
85            invitee_response: Some(resp),
86        };
87        remote
88            .send_with_custom_ticket(
89                ticket,
90                NodeRequest::PeerCommand(PeerCommand {
91                    session_cid: this_cid,
92                    command: signal,
93                }),
94            )
95            .await
96            .map(|_| ticket)
97    } else {
98        Err(citadel_io::error!(
99            citadel_io::ErrorCode::ResponseNotPostRegister
100        ))
101    }
102}
103
104/// Given the `input_signal` from the peer, this function sends a connect response to the target peer
105pub async fn peer_connect<R: Ratchet>(
106    input_signal: PeerSignal,
107    accept: bool,
108    remote: &impl Remote<R>,
109    peer_session_password: Option<PreSharedKey>,
110) -> Result<Ticket, NetworkError> {
111    if let PeerSignal::PostConnect {
112        peer_conn_type: v_conn,
113        ticket_opt: ticket,
114        invitee_response: None,
115        session_security_settings: sess_sec,
116        udp_mode,
117        session_password: None,
118    } = input_signal
119    {
120        let this_cid = v_conn.get_original_target_cid();
121        let ticket = get_ticket(ticket)?;
122        let resp = if accept {
123            // we do not need a username here, unlike in postregister
124            PeerResponse::Accept(None)
125        } else {
126            PeerResponse::Decline
127        };
128
129        let signal = NodeRequest::PeerCommand(PeerCommand {
130            session_cid: this_cid,
131            command: PeerSignal::PostConnect {
132                peer_conn_type: v_conn.reverse(),
133                ticket_opt: Some(ticket),
134                invitee_response: Some(resp),
135                session_security_settings: sess_sec,
136                udp_mode,
137                session_password: peer_session_password,
138            },
139        });
140        remote
141            .send_with_custom_ticket(ticket, signal)
142            .await
143            .map(|_| ticket)
144    } else {
145        Err(citadel_io::error!(
146            citadel_io::ErrorCode::ResponseNotPostConnect
147        ))
148    }
149}
150
151/// Given a group invite signal, this function sends a response to the server
152pub async fn group_invite<R: Ratchet>(
153    invite_signal: NodeResult<R>,
154    accept: bool,
155    remote: &impl Remote<R>,
156) -> Result<Ticket, NetworkError> {
157    if let NodeResult::GroupEvent(GroupEvent {
158        session_cid: cid,
159        ticket,
160        event: GroupBroadcast::Invitation { sender: _, key },
161    }) = invite_signal
162    {
163        let resp = if accept {
164            GroupBroadcast::AcceptMembership { target: cid, key }
165        } else {
166            GroupBroadcast::DeclineMembership { target: cid, key }
167        };
168
169        let request = NodeRequest::GroupBroadcastCommand(GroupBroadcastCommand {
170            session_cid: cid,
171            command: resp,
172        });
173        remote
174            .send_with_custom_ticket(ticket, request)
175            .await
176            .map(|_| ticket)
177    } else {
178        Err(citadel_io::error!(
179            citadel_io::ErrorCode::ResponseNotGroupInvitation
180        ))
181    }
182}
183
184fn get_ticket(ticket: Option<Ticket>) -> Result<Ticket, NetworkError> {
185    ticket.ok_or(citadel_io::error!(
186        citadel_io::ErrorCode::ResponseEventImproperlyFormed
187    ))
188}