citadel_sdk/prefabs/client/
mod.rs

1//! Client-Side Network Components
2//!
3//! This module provides pre-built client-side networking components for the Citadel Protocol.
4//! It includes implementations for various connection patterns including single server
5//! connections, peer-to-peer networking, and broadcast capabilities.
6//!
7//! # Features
8//! - Connection builders with flexible configuration
9//! - Multiple authentication methods (Transient, Credentials)
10//! - UDP support for NAT traversal
11//! - Session security customization
12//! - Pre-shared key authentication
13//! - Connection lifecycle management
14//!
15//! # Example
16//! ```rust
17//! use citadel_sdk::prelude::*;
18//! use std::net::SocketAddr;
19//! use std::str::FromStr;
20//!
21//! # fn main() -> Result<(), NetworkError> {
22//! // Create transient connection settings
23//! let settings = DefaultServerConnectionSettingsBuilder::transient("127.0.0.1:25021")
24//!     .with_udp_mode(UdpMode::Enabled)
25//!     .build()?;
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! # Important Notes
31//! - Connection settings must be built before use
32//! - UDP mode affects NAT traversal capabilities
33//! - Pre-shared keys must match server configuration
34//! - Transient connections do not persist data
35//!
36//! # Related Components
37//! - [`broadcast`]: Group communication support
38//! - [`peer_connection`]: Peer-to-peer networking
39//! - [`single_connection`]: Single server connections
40//!
41//! [`broadcast`]: crate::prefabs::client::broadcast
42//! [`peer_connection`]: crate::prefabs::client::peer_connection
43//! [`single_connection`]: crate::prefabs::client::single_connection
44
45use crate::prefabs::client::single_connection::SingleClientServerConnectionKernel;
46use crate::prelude::*;
47use std::marker::PhantomData;
48use std::net::{SocketAddr, ToSocketAddrs};
49use uuid::Uuid;
50
51/// A kernel that assists in creating and/or connecting to a group
52pub mod broadcast;
53/// A kernel that assists in allowing multiple possible peer-to-peer connections
54pub mod peer_connection;
55/// A kernel that only makes a single client-to-server connection
56pub mod single_connection;
57
58#[async_trait]
59pub trait PrefabFunctions<'a, Arg: Send + 'a, R: Ratchet>: Sized + 'a {
60    type UserLevelInputFunction: Send + 'a;
61    /// Shared between the kernel and the on_c2s_channel_received function
62    type SharedBundle: Send + 'a;
63
64    fn get_shared_bundle(&self) -> Self::SharedBundle;
65
66    async fn on_c2s_channel_received(
67        connect_success: CitadelClientServerConnection<R>,
68        arg: Arg,
69        fx: Self::UserLevelInputFunction,
70        shared: Self::SharedBundle,
71    ) -> Result<(), NetworkError>;
72
73    fn construct(kernel: Box<dyn NetKernel<R> + 'a>) -> Self;
74
75    /// Creates a new connection with a central server entailed by the user information
76    fn new(
77        server_connection_settings: ServerConnectionSettings<R>,
78        arg: Arg,
79        on_channel_received: Self::UserLevelInputFunction,
80    ) -> Self {
81        let (tx, rx) = citadel_io::tokio::sync::oneshot::channel();
82        let server_conn_kernel = SingleClientServerConnectionKernel::<_, _, R>::new(
83            server_connection_settings,
84            |connect_success| {
85                on_channel_received_fn::<_, Self, R>(connect_success, rx, arg, on_channel_received)
86            },
87        );
88
89        let this = Self::construct(Box::new(server_conn_kernel));
90        assert!(tx.send(this.get_shared_bundle()).is_ok());
91        this
92    }
93}
94
95async fn on_channel_received_fn<'a, Arg: Send + 'a, T: PrefabFunctions<'a, Arg, R>, R: Ratchet>(
96    connect_success: CitadelClientServerConnection<R>,
97    rx_bundle: citadel_io::tokio::sync::oneshot::Receiver<T::SharedBundle>,
98    arg: Arg,
99    on_channel_received: T::UserLevelInputFunction,
100) -> Result<(), NetworkError> {
101    let shared = rx_bundle
102        .await
103        .map_err(|err| NetworkError::Generic(err.to_string()))?;
104    T::on_c2s_channel_received(connect_success, arg, on_channel_received, shared).await
105}
106
107/// Used to instantiate a client to server connection
108pub struct ServerConnectionSettingsBuilder<R: Ratchet, T: ToSocketAddrs> {
109    password: Option<SecBuffer>,
110    username: Option<String>,
111    name: Option<String>,
112    psk: Option<PreSharedKey>,
113    address: Option<T>,
114    udp_mode: Option<UdpMode>,
115    session_security_settings: Option<SessionSecuritySettings>,
116    transient_uuid: Option<Uuid>,
117    is_connect: bool,
118    _ratchet: PhantomData<R>,
119}
120
121pub type DefaultServerConnectionSettingsBuilder<T> =
122    ServerConnectionSettingsBuilder<StackedRatchet, T>;
123
124impl<R: Ratchet, T: ToSocketAddrs> ServerConnectionSettingsBuilder<R, T> {
125    /// Creates a new connection to a central server that does not persist client metadata and account information
126    /// after the connection is dropped to the server. This is ideal for applications that do not require
127    /// persistence.
128    pub fn transient(addr: T) -> Self {
129        Self::transient_with_id(addr, Uuid::new_v4())
130    }
131
132    /// See docs for `transient`. This function allows you to specify a custom UUID for the transient connection.
133    pub fn transient_with_id(addr: T, id: impl Into<Uuid>) -> Self {
134        Self {
135            password: None,
136            username: None,
137            udp_mode: None,
138            session_security_settings: None,
139            name: None,
140            psk: None,
141            transient_uuid: Some(id.into()),
142            address: Some(addr),
143            is_connect: false,
144            _ratchet: PhantomData,
145        }
146    }
147
148    /// Creates a new connection to a central server that uses a username and password for authentication. This should be used directly when
149    /// constructing a registration request. If you are logging in, use the `credentialed_login` function instead.
150    pub fn credentialed_registration<U: Into<String>, N: Into<String>, P: Into<SecBuffer>>(
151        addr: T,
152        username: U,
153        alias: N,
154        password: P,
155    ) -> Self {
156        Self {
157            password: Some(password.into()),
158            username: Some(username.into()),
159            name: Some(alias.into()),
160            psk: None,
161            transient_uuid: None,
162            address: Some(addr),
163            udp_mode: None,
164            session_security_settings: None,
165            is_connect: false,
166            _ratchet: PhantomData,
167        }
168    }
169
170    /// Creates a new connection to a central server that uses a username and password for authentication. This should be used for the login process
171    pub fn credentialed_login<U: Into<String>, P: Into<SecBuffer>>(
172        addr: T,
173        username: U,
174        password: P,
175    ) -> Self {
176        Self {
177            password: Some(password.into()),
178            username: Some(username.into()),
179            name: None,
180            psk: None,
181            transient_uuid: None,
182            address: Some(addr),
183            udp_mode: None,
184            session_security_settings: None,
185            is_connect: true,
186            _ratchet: PhantomData,
187        }
188    }
189
190    /// Adds a pre-shared key to the client-to-server connection. If the server expects a PSK, this is necessary.
191    pub fn with_session_password<V: Into<PreSharedKey>>(mut self, psk: V) -> Self {
192        self.psk = Some(psk.into());
193        self
194    }
195
196    /// Sets the UDP mode for the client-to-server connection
197    pub fn with_udp_mode(mut self, mode: UdpMode) -> Self {
198        self.udp_mode = Some(mode);
199        self
200    }
201
202    /// Disables the UDP mode for the client-to-server connection. The default setting is Disabled
203    pub fn disable_udp(self) -> Self {
204        self.with_udp_mode(UdpMode::Disabled)
205    }
206
207    pub fn enable_udp(self) -> Self {
208        self.with_udp_mode(UdpMode::Enabled)
209    }
210
211    /// Adds a session security settings to the client-to-server connection. This is necessary for the server to know how to handle the connection.
212    pub fn with_session_security_settings<V: Into<SessionSecuritySettings>>(
213        mut self,
214        settings: V,
215    ) -> Self {
216        self.session_security_settings = Some(settings.into());
217        self
218    }
219
220    /// Builds the client-to-server connection settings
221    pub fn build(self) -> Result<ServerConnectionSettings<R>, NetworkError> {
222        let server_addr = if let Some(addr) = self.address {
223            let addr = addr
224                .to_socket_addrs()
225                .map_err(|err| NetworkError::Generic(err.to_string()))?
226                .next()
227                .ok_or(NetworkError::Generic("No address found".to_string()))?;
228            Some(addr)
229        } else {
230            None
231        };
232
233        if let Some(uuid) = self.transient_uuid {
234            Ok(ServerConnectionSettings::<R>::Transient {
235                server_addr: server_addr
236                    .ok_or(NetworkError::Generic("No address found".to_string()))?,
237                uuid,
238                udp_mode: self.udp_mode.unwrap_or_default(),
239                session_security_settings: self.session_security_settings.unwrap_or_default(),
240                pre_shared_key: self.psk,
241                _ratchet: PhantomData,
242            })
243        } else if self.is_connect {
244            Ok(ServerConnectionSettings::<R>::CredentialedConnect {
245                username: self
246                    .username
247                    .ok_or(NetworkError::Generic("No username found".to_string()))?,
248                password: self
249                    .password
250                    .ok_or(NetworkError::Generic("No password found".to_string()))?,
251                udp_mode: self.udp_mode.unwrap_or_default(),
252                session_security_settings: self.session_security_settings.unwrap_or_default(),
253                pre_shared_key: self.psk,
254                _ratchet: PhantomData,
255            })
256        } else {
257            Ok(ServerConnectionSettings::<R>::CredentialedRegister {
258                address: server_addr
259                    .ok_or(NetworkError::Generic("No address found".to_string()))?,
260                username: self
261                    .username
262                    .ok_or(NetworkError::Generic("No username found".to_string()))?,
263                alias: self
264                    .name
265                    .ok_or(NetworkError::Generic("No alias found".to_string()))?,
266                password: self
267                    .password
268                    .ok_or(NetworkError::Generic("No password found".to_string()))?,
269                pre_shared_key: self.psk,
270                udp_mode: self.udp_mode.unwrap_or_default(),
271                session_security_settings: self.session_security_settings.unwrap_or_default(),
272                _ratchet: PhantomData,
273            })
274        }
275    }
276}
277
278/// The settings for a client-to-server connection
279pub enum ServerConnectionSettings<R: Ratchet> {
280    Transient {
281        server_addr: SocketAddr,
282        uuid: Uuid,
283        udp_mode: UdpMode,
284        session_security_settings: SessionSecuritySettings,
285        pre_shared_key: Option<PreSharedKey>,
286        _ratchet: PhantomData<R>,
287    },
288    CredentialedConnect {
289        username: String,
290        password: SecBuffer,
291        udp_mode: UdpMode,
292        session_security_settings: SessionSecuritySettings,
293        pre_shared_key: Option<PreSharedKey>,
294        _ratchet: PhantomData<R>,
295    },
296    CredentialedRegister {
297        address: SocketAddr,
298        username: String,
299        alias: String,
300        password: SecBuffer,
301        pre_shared_key: Option<PreSharedKey>,
302        udp_mode: UdpMode,
303        session_security_settings: SessionSecuritySettings,
304        _ratchet: PhantomData<R>,
305    },
306}
307
308impl<R: Ratchet> ServerConnectionSettings<R> {
309    pub(crate) fn udp_mode(&self) -> UdpMode {
310        match self {
311            Self::Transient { udp_mode, .. } => *udp_mode,
312            Self::CredentialedRegister { udp_mode, .. } => *udp_mode,
313            Self::CredentialedConnect { udp_mode, .. } => *udp_mode,
314        }
315    }
316
317    pub(crate) fn session_security_settings(&self) -> SessionSecuritySettings {
318        match self {
319            Self::Transient {
320                session_security_settings,
321                ..
322            } => *session_security_settings,
323            Self::CredentialedRegister {
324                session_security_settings,
325                ..
326            } => *session_security_settings,
327            Self::CredentialedConnect {
328                session_security_settings,
329                ..
330            } => *session_security_settings,
331        }
332    }
333
334    pub(crate) fn pre_shared_key(&self) -> Option<&PreSharedKey> {
335        match self {
336            Self::Transient { pre_shared_key, .. } => pre_shared_key.as_ref(),
337            Self::CredentialedRegister { pre_shared_key, .. } => pre_shared_key.as_ref(),
338            Self::CredentialedConnect { pre_shared_key, .. } => pre_shared_key.as_ref(),
339        }
340    }
341}