Skip to main content

citadel_sdk/builder/
node_builder_path_policy.rs

1//! A server's path policy is required configuration: [`NodeBuilder::build`] refuses a server
2//! node without one, and a peer node with one. There is no default policy.
3
4use super::NodeBuilder;
5use crate::prelude::{NodeType, PathPolicy, PlatformOps, Ratchet, ServerOnlySessionInitSettings};
6use citadel_io::ErrorCode;
7
8impl<R: Ratchet, T: PlatformOps> NodeBuilder<R, T> {
9    /// The paths this server lets its peers use, their order, attempt timeouts and traffic
10    /// classes (see [`PathPolicy`]). Required for [`NodeType::Server`]; refused on a peer. The
11    /// server sends it to each client at connect and stamps each peer pair's effective plan into
12    /// their key exchange.
13    pub fn with_path_policy(&mut self, policy: PathPolicy) -> &mut Self {
14        self.path_policy = Some(policy);
15        self
16    }
17}
18
19pub(super) fn check_path_policy(
20    node_type: Option<&NodeType>,
21    policy: Option<&PathPolicy>,
22) -> Result<(), citadel_io::NetworkError> {
23    let is_server = matches!(node_type, Some(NodeType::Server(_)));
24    match (is_server, policy) {
25        (true, None) => Err(citadel_io::error!(ErrorCode::NodePathPolicyMissing)),
26        (false, Some(_)) => Err(citadel_io::error!(ErrorCode::NodePathPolicyNotServer)),
27        _ => Ok(()),
28    }
29}
30
31/// The server-only session settings with the policy folded in. A peer has neither.
32pub(super) fn server_settings(
33    settings: Option<ServerOnlySessionInitSettings>,
34    policy: Option<PathPolicy>,
35) -> Option<ServerOnlySessionInitSettings> {
36    match policy {
37        Some(policy) => {
38            let mut settings = settings.unwrap_or_default();
39            settings.path_policy = Some(policy);
40            Some(settings)
41        }
42        None => settings,
43    }
44}
45
46#[cfg(all(test, not(target_family = "wasm")))]
47mod tests {
48    use crate::builder::node_builder::DefaultNodeBuilder;
49    use crate::prefabs::server::empty::EmptyKernel;
50    use crate::prelude::NodeType;
51    use crate::test_common::{localhost_path_policy, LOCALHOST_STUN_SERVERS};
52    use citadel_io::ErrorCode;
53
54    fn build_error(node_type: NodeType, with_policy: bool) -> Option<ErrorCode> {
55        let mut builder = DefaultNodeBuilder::default();
56        let _ = builder
57            .with_node_type(node_type)
58            .with_stun_servers(LOCALHOST_STUN_SERVERS);
59        if with_policy {
60            let _ = builder.with_path_policy(localhost_path_policy());
61        }
62        match builder.build(EmptyKernel::default()) {
63            Ok(_) => None,
64            Err(err) => Some(
65                err.downcast_ref::<citadel_io::NetworkError>()
66                    .expect("a typed error")
67                    .code(),
68            ),
69        }
70    }
71
72    #[test]
73    fn a_server_without_a_path_policy_is_refused() {
74        let server = NodeType::server("127.0.0.1:0").unwrap();
75        assert_eq!(
76            build_error(server, false),
77            Some(ErrorCode::NodePathPolicyMissing)
78        );
79        assert_eq!(build_error(server, true), None);
80    }
81
82    #[test]
83    fn a_peer_with_a_path_policy_is_refused() {
84        assert_eq!(
85            build_error(NodeType::Peer, true),
86            Some(ErrorCode::NodePathPolicyNotServer)
87        );
88        assert_eq!(build_error(NodeType::Peer, false), None);
89    }
90}