pub struct ApplicationNode<S: Storage> { /* private fields */ }Expand description
A complete SCP application node composing relay, identity, and storage.
Created via ApplicationNodeBuilder. The node starts a relay server,
publishes the identity’s DID document with SCPRelay service entries,
and provides accessors for each component.
The relay accepts connections from any SCP client, not just the local
identity. DID publication happens once on .build(), not continuously
(spec section 18.6.4).
The type parameter S is the platform storage backend (e.g.,
InMemoryStorage for testing, SqliteStorage for production).
See spec section 18.6 for the full design.
Implementations§
Source§impl<S: Storage + Send + Sync + 'static> ApplicationNode<S>
impl<S: Storage + Send + Sync + 'static> ApplicationNode<S>
Sourcepub fn well_known_router(&self) -> Router
pub fn well_known_router(&self) -> Router
Returns an axum Router serving GET /.well-known/scp.
The response is dynamically generated from the node’s current state:
DID, relay URL, and registered broadcast contexts. Content-Type is
application/json (provided by axum’s Json extractor).
See spec section 18.3.
Sourcepub fn relay_router(&self) -> Router
pub fn relay_router(&self) -> Router
Returns an axum Router handling WebSocket upgrade at /scp/v1.
Incoming connections are bridged to the node’s internal relay server.
See spec section 18.6.2.
Sourcepub fn broadcast_projection_router(&self) -> Router
pub fn broadcast_projection_router(&self) -> Router
Returns an axum Router serving broadcast projection endpoints.
Includes:
GET /scp/broadcast/<routing_id>/feed– recent messages feedGET /scp/broadcast/<routing_id>/messages/<blob_id>– single message
These are public endpoints with no authentication middleware – broadcast content is intended for broad distribution (spec section 18.11.6).
Served on the public HTTPS port alongside .well-known/scp and
/scp/v1.
See spec section 18.11.8.
Sourcepub fn bridge_router(&self) -> Router
pub fn bridge_router(&self) -> Router
Returns an axum Router serving bridge endpoints.
Includes POST /v1/scp/bridge/shadow for shadow identity creation.
Requires bridge authentication middleware to be applied by the caller.
See SCP-BCH-002 and spec section 12.10.
Sourcepub fn dev_router(&self) -> Option<Router>
pub fn dev_router(&self) -> Option<Router>
Returns the dev API router if the dev API is enabled.
Returns Some(Router) when [ApplicationNodeBuilder::local_api] was
called (i.e., a dev token was generated), None otherwise. The
returned router includes all /scp/dev/v1/* routes with bearer token
middleware applied.
See spec section 18.10.5.
Sourcepub async fn serve(
self,
app_router: Router,
shutdown: impl Future<Output = ()> + Send + 'static,
) -> Result<(), NodeError>
pub async fn serve( self, app_router: Router, shutdown: impl Future<Output = ()> + Send + 'static, ) -> Result<(), NodeError>
Takes ownership of the node and starts serving HTTP traffic.
Binds HTTPS (or plain HTTP when TLS is not configured) on the configured address, merging:
- Application-provided routes (
app_router) .well-known/scproute/scp/v1WebSocket upgrade route/scp/broadcast/*broadcast projection routes
SCP routes take precedence for /.well-known/scp, /scp/v1, and
/scp/broadcast/*. All other paths route to app_router.
§TLS termination
When a TLS configuration was provisioned during
[ApplicationNodeBuilder::build] (domain mode with successful ACME or
injected TLS provider), serve() terminates TLS using
tokio_rustls::TlsAcceptor and serves HTTPS/WSS. When no TLS
configuration is present (no-domain mode, or TLS provisioning opted
out), serve() falls back to plain HTTP/WS. See spec section 18.6.3.
§Dev API
When the dev API is configured (via [ApplicationNodeBuilder::local_api]),
a separate tokio task is spawned to serve the dev API on the configured
address. The dev API listener runs concurrently with the public
listener. When the dev API is not configured, serve() behaves exactly
as before – no additional listener is spawned. The dev API always
uses plain HTTP (it is bound to loopback only).
§HTTP/3
When the http3 feature is enabled and an [Http3Config] is provided
via [ApplicationNodeBuilder::http3], an HTTP/3 listener is started
on a separate QUIC endpoint. All HTTP/1.1 and HTTP/2 responses include
an Alt-Svc header advertising the HTTP/3 endpoint (spec section
10.15.1).
§Graceful shutdown
The shutdown future is awaited as a graceful shutdown signal: when
it completes, the server stops accepting new connections, drains
in-flight requests, cancels the internal shutdown token (stopping
the dev API listener if running), and shuts down the relay server.
The node is consumed – callers do not need to call
ApplicationNode::shutdown separately.
If the dev API task exits early (e.g., bind failure), the shutdown token is cancelled and the error is propagated. Likewise, if the main server exits first, the dev API task is cancelled via the shutdown token and aborted.
See spec sections 18.6.3, 18.10.5, and 18.11.8.
§Errors
Returns NodeError::Serve if either server cannot bind or
encounters a fatal I/O error.
Source§impl<S: Storage> ApplicationNode<S>
impl<S: Storage> ApplicationNode<S>
Sourcepub fn domain(&self) -> Option<&str>
pub fn domain(&self) -> Option<&str>
Returns the domain this node serves.
Returns None in zero-config no-domain mode (§10.12.8).
Sourcepub const fn relay(&self) -> &RelayHandle
pub const fn relay(&self) -> &RelayHandle
Returns a reference to the relay handle.
Sourcepub const fn identity(&self) -> &IdentityHandle
pub const fn identity(&self) -> &IdentityHandle
Returns a reference to the identity handle.
Sourcepub fn storage(&self) -> &ProtocolStore<S>
pub fn storage(&self) -> &ProtocolStore<S>
Returns a reference to the protocol store.
Sourcepub fn relay_url(&self) -> &str
pub fn relay_url(&self) -> &str
Returns the relay URL published in the DID document.
For domain mode: wss://<domain>/scp/v1 (spec section 18.5.2).
For no-domain mode: the relay URL is stored in the node state.
Sourcepub fn cert_resolver(&self) -> Option<&Arc<CertResolver>>
pub fn cert_resolver(&self) -> Option<&Arc<CertResolver>>
Returns the TLS certificate resolver for ACME hot-reload.
Returns Some in domain mode when TLS is active, None in
no-domain mode. The ACME renewal loop should call
CertResolver::update on the
returned resolver to hot-swap certificates without restarting
the server.
See spec section 18.6.3 (auto-renewal).
Sourcepub async fn register_broadcast_context(
&self,
id: String,
name: Option<String>,
) -> Result<(), NodeError>
pub async fn register_broadcast_context( &self, id: String, name: Option<String>, ) -> Result<(), NodeError>
Registers a broadcast context so it appears in subsequent
GET /.well-known/scp responses.
Only broadcast contexts may be registered (spec section 18.3 privacy constraints). Encrypted context IDs MUST NOT be exposed.
§Limits
A maximum of [MAX_BROADCAST_CONTEXTS] simultaneous broadcast
contexts may be registered per node.
§Errors
Returns NodeError::InvalidConfig if the context ID is empty,
exceeds 64 characters, contains non-hex characters, or the broadcast
context limit has been reached.
Sourcepub fn bridge_token_hex(&self) -> String
pub fn bridge_token_hex(&self) -> String
Returns the hex-encoded bridge secret for the internal relay.
This is the token that must be included as an
Authorization: Bearer <hex> header when connecting directly to
the relay’s bound address. Used by tests that bypass the axum
bridge layer.
Security: This value is a secret. Do not log or expose it.
Sourcepub fn dev_token(&self) -> Option<&str>
pub fn dev_token(&self) -> Option<&str>
Returns the dev API bearer token if the dev API is enabled.
Returns Some when ApplicationNodeBuilder::local_api was called,
None otherwise. The token format is scp_local_token_<32 hex chars>.
See spec section 18.10.2.
Sourcepub fn shutdown(&self)
pub fn shutdown(&self)
Gracefully shuts down the relay server and the tier re-evaluation background task (§10.12.1, SCP-243).
Signals the relay server, the public HTTPS listener, and the dev API listener (if running) to stop accepting new connections. In-flight connection handlers drain naturally – they are not cancelled.
See SCP-245: “Ensure graceful shutdown of dev API listener alongside main server.”
Sourcepub const fn tier_change_rx(&mut self) -> Option<&mut Receiver<NatTierChange>>
pub const fn tier_change_rx(&mut self) -> Option<&mut Receiver<NatTierChange>>
Returns a mutable reference to the tier change event receiver (§10.12.1, SCP-243).
The receiver yields NatTierChange::TierChanged events when the
periodic re-evaluation loop detects a tier change. Returns None
if the node is in domain mode with successful TLS (Tier 4).
Sourcepub async fn enable_broadcast_projection(
&self,
context_id: &str,
broadcast_key: BroadcastKey,
admission: BroadcastAdmission,
projection_policy: Option<ProjectionPolicy>,
) -> Result<(), NodeError>
pub async fn enable_broadcast_projection( &self, context_id: &str, broadcast_key: BroadcastKey, admission: BroadcastAdmission, projection_policy: Option<ProjectionPolicy>, ) -> Result<(), NodeError>
Activates HTTP broadcast projection for the given context.
Computes routing_id = SHA-256(context_id) per spec section 5.14.6,
then creates or updates a ProjectedContext in the node’s projected
contexts registry. If the context is already projected, the broadcast
key is inserted at its epoch (previous epochs are retained for the
blob TTL window).
Once enabled, the node’s HTTP endpoints serve decrypted broadcast
content at /scp/broadcast/<routing_id_hex>/feed and
/scp/broadcast/<routing_id_hex>/messages/<blob_id_hex>.
The admission mode and optional projection_policy are stored on the
ProjectedContext so that projection handlers can enforce
authentication requirements per spec section 18.11.2.1. If the context
is already projected, the key is added and admission/projection_policy
are updated (use this to propagate governance ModifyCeiling changes).
See spec sections 18.11.2 and 18.11.8.
§Limits
A maximum of 1024 simultaneous projected contexts may be registered
per node. Returns NodeError::InvalidConfig if the limit is
exceeded.
§Errors
Returns NodeError::InvalidConfig if:
- The projected context limit (1024) has been reached.
- A gated context has a
Publicdefault projection rule (violates spec section 18.11.2.1: gated contexts cannot have public projection). - A gated context has a
Publicper-author projection override.
Sourcepub async fn disable_broadcast_projection(&self, context_id: &str)
pub async fn disable_broadcast_projection(&self, context_id: &str)
Deactivates HTTP broadcast projection for the given context.
Computes routing_id = SHA-256(context_id) per spec section 5.14.6,
then removes the corresponding ProjectedContext from the registry.
All retained epoch keys are dropped.
See spec sections 18.11.2 and 18.11.8.
Sourcepub async fn propagate_ban_keys(
&self,
context_id: &str,
ban_result: &GovernanceBanResult,
)
pub async fn propagate_ban_keys( &self, context_id: &str, ban_result: &GovernanceBanResult, )
Propagates rotated broadcast keys to the projection registry after a governance ban.
After [ContextManager::execute_governance_action] returns
[GovernanceActionResult::ReadAccessRevoked], call this method with
the context_id and the [GovernanceBanResult] to ensure the
projection endpoint can decrypt content encrypted under the new
post-rotation keys.
For each rotated author, inserts the new-epoch key into the
ProjectedContext key registry. If the context is not projected
(not registered via [enable_broadcast_projection]), this is a no-op.
When the ban’s [RevocationScope] is Full, old-epoch keys are
purged from the projection registry so historical content encrypted
under pre-ban keys is no longer served. FutureOnly retains old
keys (historical content remains accessible).
Trait Implementations§
Auto Trait Implementations§
impl<S> Freeze for ApplicationNode<S>
impl<S> !RefUnwindSafe for ApplicationNode<S>
impl<S> Send for ApplicationNode<S>
impl<S> Sync for ApplicationNode<S>
impl<S> Unpin for ApplicationNode<S>
impl<S> UnsafeUnpin for ApplicationNode<S>
impl<S> !UnwindSafe for ApplicationNode<S>
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Declassify for T
impl<T> Declassify for T
type Declassified = T
fn declassify(self) -> T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more