Skip to main content

ApplicationNode

Struct ApplicationNode 

Source
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>

Source

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.

Source

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.

Source

pub fn broadcast_projection_router(&self) -> Router

Returns an axum Router serving broadcast projection endpoints.

Includes:

  • GET /scp/broadcast/<routing_id>/feed – recent messages feed
  • GET /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.

Source

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.

Source

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.

Source

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:

  1. Application-provided routes (app_router)
  2. .well-known/scp route
  3. /scp/v1 WebSocket upgrade route
  4. /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>

Source

pub fn domain(&self) -> Option<&str>

Returns the domain this node serves.

Returns None in zero-config no-domain mode (§10.12.8).

Source

pub const fn relay(&self) -> &RelayHandle

Returns a reference to the relay handle.

Source

pub const fn identity(&self) -> &IdentityHandle

Returns a reference to the identity handle.

Source

pub fn storage(&self) -> &ProtocolStore<S>

Returns a reference to the protocol store.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.”

Source

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).

Source

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 Public default projection rule (violates spec section 18.11.2.1: gated contexts cannot have public projection).
  • A gated context has a Public per-author projection override.
Source

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.

Source

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§

Source§

impl<S: Storage + Debug> Debug for ApplicationNode<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Classify for T

Source§

type Classified = T

Source§

fn classify(self) -> T

Source§

impl<T> Declassify for T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more