libfcp_server/server.rs
1// Copyright Nixort <https://github.com/Nixort> 2026.
2//
3// License: GNU General Public License v3.0 only.
4// You can find the license file in the project root.
5//
6// Federated CFR Connect Protocol (FCP).
7
8//! Signed federation configuration publisher.
9
10use alloc::vec::Vec;
11
12use libfcp_core::{
13 EndpointIdentity, FederationConfiguration, FederationId, FederationMember,
14 SignedFederationConfiguration, SigningIdentity,
15};
16
17use crate::Error;
18
19/// Server-side authority that publishes one federation's signed settings.
20///
21/// The server owns policy publication only. Applications still decide how a
22/// client authenticates this authority key and how the signed bytes are carried
23/// to clients. The server is not a signaling relay and does not participate in
24/// a peer's WebRTC or CFR state machine.
25pub struct FederationServer {
26 signer: SigningIdentity,
27 configuration: FederationConfiguration,
28}
29
30impl FederationServer {
31 /// Creates an empty epoch-zero configuration with the supplied authority identity.
32 pub fn new(federation: FederationId, signer: SigningIdentity) -> Result<Self, Error> {
33 let authority = signer.endpoint();
34 let configuration = FederationConfiguration::new(federation, authority, 0, Vec::new())?;
35 Ok(Self {
36 signer,
37 configuration,
38 })
39 }
40
41 /// Returns the pinned authority identity clients must authenticate out of band.
42 pub fn authority(&self) -> EndpointIdentity {
43 self.configuration.authority
44 }
45
46 /// Returns the currently configured federation namespace.
47 pub fn federation(&self) -> FederationId {
48 self.configuration.federation
49 }
50
51 /// Returns the current monotonic policy version.
52 pub fn epoch(&self) -> u64 {
53 self.configuration.epoch
54 }
55
56 /// Replaces all explicit member bindings at a strictly newer epoch.
57 ///
58 /// An application must apply its admission and identity policy before calling
59 /// this method. In particular, an FCP endpoint identity and CFR identity key are
60 /// not considered equivalent simply because this server publishes them in one
61 /// record.
62 pub fn replace_members(
63 &mut self,
64 epoch: u64,
65 members: Vec<FederationMember>,
66 ) -> Result<(), Error> {
67 if epoch <= self.configuration.epoch {
68 return Err(Error::NonIncreasingEpoch);
69 }
70 self.configuration = FederationConfiguration::new(
71 self.configuration.federation,
72 self.configuration.authority,
73 epoch,
74 members,
75 )?;
76 Ok(())
77 }
78
79 /// Produces the canonical signed configuration artifact for application-selected delivery.
80 pub fn publish(&self) -> Result<SignedFederationConfiguration, Error> {
81 Ok(SignedFederationConfiguration::sign(
82 self.configuration.clone(),
83 &self.signer,
84 )?)
85 }
86}