Skip to main content

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/// Internal kernel for serverless browser-to-browser connections
56#[cfg(target_family = "wasm")]
57pub(crate) mod serverless;
58/// A kernel that only makes a single client-to-server connection
59pub mod single_connection;
60
61#[async_trait]
62pub trait PrefabFunctions<'a, Arg: Send + 'a, R: Ratchet>: Sized + 'a {
63    type UserLevelInputFunction: Send + 'a;
64    /// Shared between the kernel and the on_c2s_channel_received function
65    type SharedBundle: Send + 'a;
66
67    fn get_shared_bundle(&self) -> Self::SharedBundle;
68
69    async fn on_c2s_channel_received(
70        connect_success: CitadelClientServerConnection<R>,
71        arg: Arg,
72        fx: Self::UserLevelInputFunction,
73        shared: Self::SharedBundle,
74    ) -> Result<(), NetworkError>;
75
76    fn construct(kernel: Box<dyn NetKernel<R> + 'a>) -> Self;
77
78    /// Creates a new connection with a central server entailed by the user information
79    fn new(
80        server_connection_settings: ServerConnectionSettings<R>,
81        arg: Arg,
82        on_channel_received: Self::UserLevelInputFunction,
83    ) -> Self {
84        let (tx, rx) = citadel_io::tokio::sync::oneshot::channel();
85        let server_conn_kernel = SingleClientServerConnectionKernel::<_, _, R>::new(
86            server_connection_settings,
87            |connect_success| {
88                on_channel_received_fn::<_, Self, R>(connect_success, rx, arg, on_channel_received)
89            },
90        );
91
92        let this = Self::construct(Box::new(server_conn_kernel));
93        assert!(tx.send(this.get_shared_bundle()).is_ok());
94        this
95    }
96}
97
98async fn on_channel_received_fn<'a, Arg: Send + 'a, T: PrefabFunctions<'a, Arg, R>, R: Ratchet>(
99    connect_success: CitadelClientServerConnection<R>,
100    rx_bundle: citadel_io::tokio::sync::oneshot::Receiver<T::SharedBundle>,
101    arg: Arg,
102    on_channel_received: T::UserLevelInputFunction,
103) -> Result<(), NetworkError> {
104    let shared = rx_bundle
105        .await
106        .map_err(|err| NetworkError::Generic(err.to_string()))?;
107    T::on_c2s_channel_received(connect_success, arg, on_channel_received, shared).await
108}
109
110/// Used to instantiate a client to server connection
111pub struct ServerConnectionSettingsBuilder<R: Ratchet, T: ToSocketAddrs> {
112    password: Option<SecBuffer>,
113    username: Option<String>,
114    name: Option<String>,
115    psk: Option<PreSharedKey>,
116    address: Option<T>,
117    udp_mode: Option<UdpMode>,
118    session_security_settings: Option<SessionSecuritySettings>,
119    transient_uuid: Option<Uuid>,
120    is_connect: bool,
121    _ratchet: PhantomData<R>,
122}
123
124pub type DefaultServerConnectionSettingsBuilder<T> =
125    ServerConnectionSettingsBuilder<StackedRatchet, T>;
126
127impl<R: Ratchet, T: ToSocketAddrs> ServerConnectionSettingsBuilder<R, T> {
128    fn base(addr: T) -> Self {
129        Self {
130            password: None,
131            username: None,
132            udp_mode: None,
133            session_security_settings: None,
134            name: None,
135            psk: None,
136            transient_uuid: None,
137            address: Some(addr),
138            is_connect: false,
139            _ratchet: PhantomData,
140        }
141    }
142
143    /// Creates a new connection to a central server that does not persist client metadata and account information
144    /// after the connection is dropped to the server. This is ideal for applications that do not require
145    /// persistence.
146    pub fn transient(addr: T) -> Self {
147        Self::transient_with_id(addr, Uuid::new_v4())
148    }
149
150    /// See docs for `transient`. This function allows you to specify a custom UUID for the transient connection.
151    pub fn transient_with_id(addr: T, id: impl Into<Uuid>) -> Self {
152        Self {
153            transient_uuid: Some(id.into()),
154            ..Self::base(addr)
155        }
156    }
157
158    /// Creates a new connection to a central server that uses a username and password for authentication. This should be used directly when
159    /// constructing a registration request. If you are logging in, use the `credentialed_login` function instead.
160    pub fn credentialed_registration<U: Into<String>, N: Into<String>, P: Into<SecBuffer>>(
161        addr: T,
162        username: U,
163        alias: N,
164        password: P,
165    ) -> Self {
166        Self {
167            password: Some(password.into()),
168            username: Some(username.into()),
169            name: Some(alias.into()),
170            ..Self::base(addr)
171        }
172    }
173
174    /// Creates a new connection to a central server that uses a username and password for authentication. This should be used for the login process
175    pub fn credentialed_login<U: Into<String>, P: Into<SecBuffer>>(
176        addr: T,
177        username: U,
178        password: P,
179    ) -> Self {
180        Self {
181            password: Some(password.into()),
182            username: Some(username.into()),
183            is_connect: true,
184            ..Self::base(addr)
185        }
186    }
187
188    /// Adds a pre-shared key to the client-to-server connection. If the server expects a PSK, this is necessary.
189    pub fn with_session_password<V: Into<PreSharedKey>>(mut self, psk: V) -> Self {
190        self.psk = Some(psk.into());
191        self
192    }
193
194    /// Sets the UDP mode for the client-to-server connection
195    pub fn with_udp_mode(mut self, mode: UdpMode) -> Self {
196        self.udp_mode = Some(mode);
197        self
198    }
199
200    /// Disables the UDP mode for the client-to-server connection. The default setting is Disabled
201    pub fn disable_udp(self) -> Self {
202        self.with_udp_mode(UdpMode::Disabled)
203    }
204
205    pub fn enable_udp(self) -> Self {
206        self.with_udp_mode(UdpMode::Enabled)
207    }
208
209    /// Adds a session security settings to the client-to-server connection. This is necessary for the server to know how to handle the connection.
210    pub fn with_session_security_settings<V: Into<SessionSecuritySettings>>(
211        mut self,
212        settings: V,
213    ) -> Self {
214        self.session_security_settings = Some(settings.into());
215        self
216    }
217
218    /// Builds the client-to-server connection settings
219    pub fn build(self) -> Result<ServerConnectionSettings<R>, NetworkError> {
220        let server_addr = if let Some(addr) = self.address {
221            let addr = addr
222                .to_socket_addrs()
223                .map_err(|err| NetworkError::Generic(err.to_string()))?
224                .next()
225                .ok_or(NetworkError::Generic("No address found".to_string()))?;
226            Some(addr)
227        } else {
228            None
229        };
230
231        if let Some(uuid) = self.transient_uuid {
232            Ok(ServerConnectionSettings::<R>::Transient {
233                server_addr: server_addr
234                    .ok_or(NetworkError::Generic("No address found".to_string()))?,
235                uuid,
236                udp_mode: self.udp_mode.unwrap_or_default(),
237                session_security_settings: self.session_security_settings.unwrap_or_default(),
238                pre_shared_key: self.psk,
239                _ratchet: PhantomData,
240            })
241        } else if self.is_connect {
242            Ok(ServerConnectionSettings::<R>::CredentialedConnect {
243                username: self
244                    .username
245                    .ok_or(NetworkError::Generic("No username found".to_string()))?,
246                password: self
247                    .password
248                    .ok_or(NetworkError::Generic("No password found".to_string()))?,
249                udp_mode: self.udp_mode.unwrap_or_default(),
250                session_security_settings: self.session_security_settings.unwrap_or_default(),
251                pre_shared_key: self.psk,
252                _ratchet: PhantomData,
253            })
254        } else {
255            Ok(ServerConnectionSettings::<R>::CredentialedRegister {
256                address: server_addr
257                    .ok_or(NetworkError::Generic("No address found".to_string()))?,
258                username: self
259                    .username
260                    .ok_or(NetworkError::Generic("No username found".to_string()))?,
261                alias: self
262                    .name
263                    .ok_or(NetworkError::Generic("No alias found".to_string()))?,
264                password: self
265                    .password
266                    .ok_or(NetworkError::Generic("No password found".to_string()))?,
267                pre_shared_key: self.psk,
268                udp_mode: self.udp_mode.unwrap_or_default(),
269                session_security_settings: self.session_security_settings.unwrap_or_default(),
270                _ratchet: PhantomData,
271            })
272        }
273    }
274}
275
276/// The settings for a client-to-server connection
277pub enum ServerConnectionSettings<R: Ratchet> {
278    Transient {
279        server_addr: SocketAddr,
280        uuid: Uuid,
281        udp_mode: UdpMode,
282        session_security_settings: SessionSecuritySettings,
283        pre_shared_key: Option<PreSharedKey>,
284        _ratchet: PhantomData<R>,
285    },
286    CredentialedConnect {
287        username: String,
288        password: SecBuffer,
289        udp_mode: UdpMode,
290        session_security_settings: SessionSecuritySettings,
291        pre_shared_key: Option<PreSharedKey>,
292        _ratchet: PhantomData<R>,
293    },
294    CredentialedRegister {
295        address: SocketAddr,
296        username: String,
297        alias: String,
298        password: SecBuffer,
299        pre_shared_key: Option<PreSharedKey>,
300        udp_mode: UdpMode,
301        session_security_settings: SessionSecuritySettings,
302        _ratchet: PhantomData<R>,
303    },
304}
305
306impl<R: Ratchet> ServerConnectionSettings<R> {
307    pub(crate) fn udp_mode(&self) -> UdpMode {
308        match self {
309            Self::Transient { udp_mode, .. } => *udp_mode,
310            Self::CredentialedRegister { udp_mode, .. } => *udp_mode,
311            Self::CredentialedConnect { udp_mode, .. } => *udp_mode,
312        }
313    }
314
315    pub(crate) fn session_security_settings(&self) -> SessionSecuritySettings {
316        match self {
317            Self::Transient {
318                session_security_settings,
319                ..
320            } => *session_security_settings,
321            Self::CredentialedRegister {
322                session_security_settings,
323                ..
324            } => *session_security_settings,
325            Self::CredentialedConnect {
326                session_security_settings,
327                ..
328            } => *session_security_settings,
329        }
330    }
331
332    pub(crate) fn pre_shared_key(&self) -> Option<&PreSharedKey> {
333        match self {
334            Self::Transient { pre_shared_key, .. } => pre_shared_key.as_ref(),
335            Self::CredentialedRegister { pre_shared_key, .. } => pre_shared_key.as_ref(),
336            Self::CredentialedConnect { pre_shared_key, .. } => pre_shared_key.as_ref(),
337        }
338    }
339}