Skip to main content

ProviderServer

Struct ProviderServer 

Source
pub struct ProviderServer<D> { /* private fields */ }
Expand description

A multi-session OTA provider server: accepts inbound CASE sessions as the responder (one per pooled credential), then dispatches server-side InvokeRequests. Generic over the datagram transport so it runs over TokioUdpTransport in production and InMemoryDatagram in tests.

This productionizes the responder accept-flow proven in the actor’s loopback tests (run_loopback_device): Sigma1→Sigma2→Sigma3→SessionManager register, then secured IM dispatch on the established session.

The credential pool is consumed one entry per accept_case call. When the pool is exhausted, accept_case (and any caller such as serve_ota_once) returns Error::Operational with the message "provider server: credential pool exhausted". The pool is sized by the caller — serve_ota mints four entries (first session + post-reboot session

  • retry slack) from the persisted fabric.

Implementations§

Source§

impl<D: AsyncDatagram> ProviderServer<D>

Source

pub fn new( io: D, credentials: Vec<CaseCredentials>, roots: TrustedRoots, base_session_id: u16, now: MatterTime, ) -> Self

Build a provider server bound to io, authenticating from the credentials pool (our operational identities). roots and now are used to validate the peer’s certificate chain on each accept.

base_session_id is the first secured session id advertised in Sigma2; the Nth accept uses base_session_id.wrapping_add(N) so consecutive sessions never reuse the same local id.

The pool is consumed one entry per accept. When it is empty, the next call to Self::serve_ota_once (or any method that calls accept_case) returns an Error::Operational containing "provider server: credential pool exhausted".

Source

pub fn with_record_sink( self, sink: Box<dyn Fn(ResumptionRecord) + Send + Sync>, ) -> Self

Register a callback that is invoked once per completed accept with the fresh ResumptionRecord the handshake produced (rotated on the resumed path, brand-new on the full path). The caller can use this to persist the record immediately — a future that is cancelled after accept_case completes but before the caller stores the record would otherwise lose the rotation. The sink is called synchronously and must not block; spawn an async task if async work is needed.

Source

pub fn with_resumption_records(self, records: Vec<ResumptionRecord>) -> Self

Seed the server with known CASE resumption records (see the field docs). An inbound resumption-requesting Sigma1 matching one of these by id is accepted via Sigma2_Resume; anything else falls back to a full handshake.

Source

pub fn with_expected_peer(self, node_id: u64) -> Self

Pin the peer: an accepted session must authenticate as node_id or the accept fails (consuming its pooled credential). Without this, any member of the fabric could consume the serve.

Source

pub async fn accept_and_dispatch_once<H>( self, handler: H, max_invokes: usize, ) -> Result<usize, Error>

Accept ONE inbound CASE session, then dispatch up to max_invokes server-side InvokeRequests through handler, replying to each on its exchange. Returns the number of invokes dispatched.

handler maps a parsed InvokeRequest to the encoded InvokeResponse message bytes (e.g. via matter_interaction::build_invoke_response_*).

§Errors

Returns Error::Operational on a transport, CASE-handshake, or framing failure (including a non-NewSession Sigma1 or an unexpected opcode), or Error::Transport / Error::InteractionModel from the session / IM layers.

Source

pub async fn serve_ota_once( self, offer: ImageOffer, image: Vec<u8>, max_block_size: u16, ) -> Result<(), Error>

Accept CASE sessions in sequence, serving image to the requestor over the full OTA flow — QueryImageQueryImageResponse, a BDX transfer, then ApplyUpdateRequestApplyUpdateResponse (Proceed) — and completing once NotifyUpdateApplied is received on ANY session. A real requestor downloads and applies on its first session, reboots into the new image, and sends NotifyUpdateApplied on a fresh session; this method spans that reboot by running an outer loop over accept_case calls.

Unsecured frames (session id 0) arriving while a secured session is being served are recognised as new-session-establishment attempts; they are carried into the next outer iteration as the first_frame for the next accept_case call, so no handshake bytes are lost.

The caller owns the deadline: wrap serve_ota_once in tokio::time::timeout (or similar) to bound a requestor that never returns. Pool exhaustion (all credentials consumed) and a per-session step budget are the two error paths.

The fresh ResumptionRecord the accept handshake produced is re-seeded and forwarded to the record_sink (if set via Self::with_record_sink) before the OTA dispatch loop begins — the caller need not wait for the full OTA flow to persist the rotation.

offer shapes the QueryImageResponse (its ImageURI/UpdateToken); max_block_size caps each BDX block. All replies are unreliable (piggyback ack) — happy-path, localhost-validated. Messages route by ProtocolId: Interaction-Model invokes go to the matter-ota handlers, ProtocolId::BDX messages drive a matter_bdx::BlockSender.

§Errors

Error::Operational on a CASE/transport/codec failure, a BDX abort, an unexpected OTA command, or if a session exhausts its step budget without an unsecured carry-frame; Error::Transport / Error::InteractionModel from the session / IM layers.

Auto Trait Implementations§

§

impl<D> !RefUnwindSafe for ProviderServer<D>

§

impl<D> !UnwindSafe for ProviderServer<D>

§

impl<D> Freeze for ProviderServer<D>
where D: Freeze,

§

impl<D> Send for ProviderServer<D>
where D: Send,

§

impl<D> Sync for ProviderServer<D>
where D: Sync,

§

impl<D> Unpin for ProviderServer<D>
where D: Unpin,

§

impl<D> UnsafeUnpin for ProviderServer<D>
where D: UnsafeUnpin,

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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