Skip to main content

rs_matter/bdx/
handler.rs

1/*
2 *
3 *    Copyright (c) 2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! Composing BDX handlers behind a single `PROTO_ID_BDX` exchange handler.
19//!
20//! [`Bdx`] is the [`ExchangeHandler`] that owns the BDX protocol in a responder's
21//! handler chain. For each incoming transfer it accepts the opening `*Init` into a
22//! [`BdxResponder`] (a download or an upload) and dispatches it to a
23//! [`BdxHandler`].
24//!
25//! Multiple handlers are composed by nesting [`ChainedBdxHandler`]s (terminated by
26//! [`EmptyBdxHandler`]), so several can share `PROTO_ID_BDX` while owning disjoint
27//! file-designator namespaces - e.g. an OTA Provider serving images alongside a
28//! Diagnostic Logs client receiving logs.
29
30use crate::respond::ExchangeHandler;
31
32use super::nego::abort;
33use super::*;
34
35/// An accepted, not-yet-answered BDX transfer, in one of the two directions:
36/// `Download` (a peer wants to download from us - we send) or `Upload` (a peer
37/// wants to upload to us - we receive).
38pub enum BdxResponder<'a> {
39    /// A peer's download request; reply to send the data via a [`BdxWriter`].
40    Download(BdxDownloadResponder<'a>),
41    /// A peer's upload request; reply to receive the data via a [`BdxReader`].
42    Upload(BdxUploadResponder<'a>),
43}
44
45impl BdxResponder<'_> {
46    /// The file designator the peer named (borrowed from the held `*Init`).
47    pub fn fd(&self) -> &[u8] {
48        match self {
49            Self::Download(responder) => responder.fd(),
50            Self::Upload(responder) => responder.fd(),
51        }
52    }
53
54    /// Reject the transfer with the given status.
55    pub async fn reject(self, status: BdxStatus) -> Result<(), Error> {
56        match self {
57            Self::Download(responder) => responder.reject(status).await,
58            Self::Upload(responder) => responder.reject(status).await,
59        }
60    }
61}
62
63/// A device-specific BDX handler: one or more "files" - identified by their BDX
64/// file designator and direction - that this node serves (a download) or
65/// processes (an upload).
66///
67/// [`handles`](Self::handles) selects which transfers this handler owns; only a
68/// [`BdxResponder`] it claims is passed to [`handle`](Self::handle). The default
69/// `handle` rejects with `FileDesignatorUnknown`.
70pub trait BdxHandler {
71    /// Whether this handler owns the transfer described by `responder` (by its
72    /// direction and file designator). Consulted before [`handle`](Self::handle),
73    /// so it must not consume the responder.
74    async fn handles(&self, _responder: &BdxResponder<'_>) -> bool {
75        false
76    }
77
78    /// Handle a transfer this handler [`handles`](Self::handles): reply on (and
79    /// drive) the [`BdxResponder`], or [`reject`](BdxResponder::reject) it.
80    async fn handle(&self, responder: BdxResponder<'_>) -> Result<(), Error> {
81        responder.reject(BdxStatus::FileDesignatorUnknown).await
82    }
83}
84
85/// `&T` is a [`BdxHandler`] whenever `T` is, so a handler can be composed (into a
86/// [`ChainedBdxHandler`]) or wrapped (in [`Bdx`]) by shared reference, without
87/// giving up ownership of it.
88impl<T> BdxHandler for &T
89where
90    T: BdxHandler,
91{
92    async fn handles(&self, responder: &BdxResponder<'_>) -> bool {
93        T::handles(self, responder).await
94    }
95
96    async fn handle(&self, responder: BdxResponder<'_>) -> Result<(), Error> {
97        T::handle(self, responder).await
98    }
99}
100
101/// A [`BdxHandler`] that handles nothing - the terminator of a
102/// [`ChainedBdxHandler`]. Every transfer routed to it is rejected with
103/// `FileDesignatorUnknown`.
104pub struct EmptyBdxHandler;
105
106impl BdxHandler for EmptyBdxHandler {}
107
108/// Two [`BdxHandler`]s composed into one: `handler` is consulted first (via
109/// [`handles`](BdxHandler::handles)), then `next`. Nest these (terminated by
110/// [`EmptyBdxHandler`]) to compose more than two.
111pub struct ChainedBdxHandler<H, T> {
112    /// The handler consulted first.
113    pub handler: H,
114    /// The handler consulted if `handler` does not match.
115    pub next: T,
116}
117
118impl<H, T> ChainedBdxHandler<H, T> {
119    /// Create a chained handler consulting `handler` before `next`.
120    pub const fn new(handler: H, next: T) -> Self {
121        Self { handler, next }
122    }
123}
124
125impl<H, T> BdxHandler for ChainedBdxHandler<H, T>
126where
127    H: BdxHandler,
128    T: BdxHandler,
129{
130    async fn handles(&self, responder: &BdxResponder<'_>) -> bool {
131        self.handler.handles(responder).await || self.next.handles(responder).await
132    }
133
134    async fn handle(&self, responder: BdxResponder<'_>) -> Result<(), Error> {
135        if self.handler.handles(&responder).await {
136            self.handler.handle(responder).await
137        } else {
138            self.next.handle(responder).await
139        }
140    }
141}
142
143/// An [`ExchangeHandler`] for the BDX protocol ([`PROTO_ID_BDX`]) that accepts each
144/// incoming transfer into a [`BdxResponder`] and dispatches it to the wrapped
145/// [`BdxHandler`].
146///
147/// Chain it into your responder's handler for `PROTO_ID_BDX`:
148///
149/// ```ignore
150/// use rs_matter::bdx::{Bdx, PROTO_ID_BDX};
151///
152/// let bdx = Bdx::new(OtaBdxHandler::new(&buffers, &images));
153/// let handler = im_and_sc_handler.chain(PROTO_ID_BDX, bdx);
154/// ```
155///
156/// Serve several BDX handlers at once by nesting [`ChainedBdxHandler`]s
157/// (terminated by [`EmptyBdxHandler`]):
158///
159/// ```ignore
160/// let bdx = Bdx::new(ChainedBdxHandler::new(
161///     ota_server,
162///     ChainedBdxHandler::new(logs_sink, EmptyBdxHandler),
163/// ));
164/// ```
165pub struct Bdx<T>(T);
166
167impl<T> Bdx<T> {
168    /// Wrap a [`BdxHandler`] (often a [`ChainedBdxHandler`]) as a BDX
169    /// [`ExchangeHandler`].
170    pub const fn new(handler: T) -> Self {
171        Self(handler)
172    }
173
174    /// A reference to the wrapped handler.
175    pub fn handler(&self) -> &T {
176        &self.0
177    }
178}
179
180impl<T: BdxHandler> ExchangeHandler for Bdx<T> {
181    async fn handle(&self, mut exchange: Exchange<'_>) -> Result<(), Error> {
182        // Peek the opening `*Init` to learn the transfer's direction, then accept
183        // it into the matching responder (which keeps the `*Init` held).
184        exchange.recv_fetch().await?;
185        let meta = exchange.rx()?.meta();
186
187        let responder = match opcode(&meta) {
188            // A peer wants to download a file from us: we are the Sender.
189            Some(OpCode::ReceiveInit) => {
190                BdxResponder::Download(BdxDownloadResponder::accept(exchange).await?)
191            }
192            // A peer wants to upload a file to us: we are the Receiver.
193            Some(OpCode::SendInit) => {
194                BdxResponder::Upload(BdxUploadResponder::accept(exchange).await?)
195            }
196            // Any other opcode cannot open a transfer.
197            _ => {
198                exchange.rx_done()?;
199
200                return abort(&mut exchange, BdxStatus::UnexpectedMessage).await;
201            }
202        };
203
204        if self.0.handles(&responder).await {
205            self.0.handle(responder).await
206        } else {
207            responder.reject(BdxStatus::FileDesignatorUnknown).await
208        }
209    }
210}