Skip to main content

rs_matter/bdx/
read.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//! The *receive* side of the BDX streaming engine: the [`BdxReader`] handle and
19//! the two ways to obtain one - [`BdxDownloadInitiator`] (initiate a download) and
20//! [`BdxUploadResponder`] (respond to a peer's upload).
21
22use super::nego::*;
23use super::*;
24
25/// A reader over a BDX transfer - the Receiver side.
26///
27/// Obtained from [`Exchange::download`](BdxDownloadInitiator::download) on the initiating side, or
28/// from [`BdxUploadResponder::reply`] on the responding side. [`read`](Self::read)
29/// drives the protocol as needed and copies the next bytes of the transfer into
30/// the caller's buffer, returning `0` at the end of the transfer. It also
31/// implements [`embedded_io_async::Read`] (delegating to the inherent method).
32pub struct BdxReader<'a> {
33    exchange: Exchange<'a>,
34    drive: Drive,
35    /// The negotiated definite length of the transfer, if the sender committed
36    /// to one.
37    len: Option<u64>,
38    /// Driver: the counter to put in the next `BlockQuery`. Follower: the
39    /// expected counter of the next incoming block.
40    counter: u32,
41    /// The counter of the block currently held in the exchange RX buffer.
42    held_counter: u32,
43    /// Whether the held block is the final (`BlockEof`) block.
44    held_eof: bool,
45    /// How many bytes of the held block's data have been consumed.
46    block_pos: usize,
47    /// Whether a (partially consumed) block is held in the exchange RX buffer.
48    holding: bool,
49    /// Whether the transfer has completed.
50    finished: bool,
51}
52
53impl<'a> BdxReader<'a> {
54    pub(super) const fn new(exchange: Exchange<'a>, drive: Drive, len: Option<u64>) -> Self {
55        Self {
56            exchange,
57            drive,
58            len,
59            counter: 0,
60            held_counter: 0,
61            held_eof: false,
62            block_pos: 0,
63            holding: false,
64            finished: false,
65        }
66    }
67
68    /// The total length of the transfer in bytes, if the sender committed to a
69    /// definite length during negotiation (`None` for an indefinite transfer).
70    #[allow(clippy::len_without_is_empty)] // A transfer length, not a collection count.
71    pub fn len(&self) -> Option<u64> {
72        self.len
73    }
74
75    /// Read the next bytes of the transfer into `buf`, returning the number of
76    /// bytes read. Returns `0` once the whole transfer has been received.
77    ///
78    /// This is also the [`embedded_io_async::Read`] implementation; the inherent
79    /// method is kept so callers need not import the trait.
80    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
81        if buf.is_empty() {
82            return Ok(0);
83        }
84
85        loop {
86            if self.finished {
87                return Ok(0);
88            }
89
90            if self.holding {
91                // Serve from the block held in the exchange RX buffer.
92                let n = {
93                    let payload = self.exchange.rx()?.payload();
94                    let data = &payload[BLOCK_HEADER_LEN..];
95                    if self.block_pos < data.len() {
96                        let remaining = &data[self.block_pos..];
97                        let n = remaining.len().min(buf.len());
98                        buf[..n].copy_from_slice(&remaining[..n]);
99                        Some(n)
100                    } else {
101                        None
102                    }
103                };
104
105                if let Some(n) = n {
106                    self.block_pos += n;
107                    return Ok(n);
108                }
109
110                // The held block is fully consumed - acknowledge / advance.
111                self.release_block().await?;
112                continue;
113            }
114
115            // Nothing held and not finished: fetch the next block.
116            self.receive_block().await?;
117        }
118    }
119
120    /// Obtain the next block, holding it in the exchange RX buffer.
121    async fn receive_block(&mut self) -> Result<(), Error> {
122        enum Outcome {
123            Ok(bool),
124            BadCounter,
125            Unexpected,
126            Aborted(Error),
127        }
128
129        if matches!(self.drive, Drive::Driver) {
130            // Request the next block (this also acknowledges the previous one).
131            self.send_control(OpCode::BlockQuery, self.counter).await?;
132        }
133
134        self.exchange.recv_fetch().await?;
135        let meta = self.exchange.rx()?.meta();
136        let outcome = {
137            let payload = self.exchange.rx()?.payload();
138            match classify(&meta, payload) {
139                Ok(op) if matches!(op, OpCode::Block | OpCode::BlockEof) => {
140                    let block = Block::parse(payload)?;
141                    if block.block_counter != self.counter {
142                        Outcome::BadCounter
143                    } else {
144                        Outcome::Ok(op == OpCode::BlockEof)
145                    }
146                }
147                Ok(_) => Outcome::Unexpected,
148                Err(e) => Outcome::Aborted(e),
149            }
150        };
151
152        match outcome {
153            Outcome::Ok(is_eof) => {
154                // Keep the block held; `read` serves its data directly from RX.
155                self.held_counter = self.counter;
156                self.held_eof = is_eof;
157                self.counter = self.counter.wrapping_add(1);
158                self.block_pos = 0;
159                self.holding = true;
160                Ok(())
161            }
162            Outcome::BadCounter => {
163                self.exchange.rx_done()?;
164                abort(&mut self.exchange, BdxStatus::BadBlockCounter).await
165            }
166            Outcome::Unexpected => {
167                self.exchange.rx_done()?;
168                abort(&mut self.exchange, BdxStatus::UnexpectedMessage).await
169            }
170            Outcome::Aborted(e) => {
171                self.exchange.rx_done()?;
172                Err(e)
173            }
174        }
175    }
176
177    /// Acknowledge the consumed block and release the RX buffer, finalizing the
178    /// transfer if it was the last block.
179    async fn release_block(&mut self) -> Result<(), Error> {
180        let counter = self.held_counter;
181
182        if self.held_eof {
183            self.send_control(OpCode::BlockAckEof, counter).await?;
184            self.exchange.rx_done()?;
185            self.exchange.acknowledge().await?;
186            self.finished = true;
187        } else if matches!(self.drive, Drive::Follower) {
188            // Sender-driven: acknowledge so the next block is sent.
189            self.send_control(OpCode::BlockAck, counter).await?;
190            self.exchange.rx_done()?;
191        } else {
192            // Receiver-driven: the next `BlockQuery` is the acknowledgement.
193            self.exchange.rx_done()?;
194        }
195
196        self.holding = false;
197
198        Ok(())
199    }
200
201    /// Send a counter-only control message (`BlockQuery`/`BlockAck`/`BlockAckEof`).
202    async fn send_control(&mut self, opcode: OpCode, counter: u32) -> Result<(), Error> {
203        self.exchange
204            .send_with(|_, wb| {
205                BlockQuery {
206                    block_counter: counter,
207                }
208                .write(wb)?;
209                Ok(Some(opcode.into()))
210            })
211            .await
212    }
213}
214
215impl embedded_io_async::ErrorType for BdxReader<'_> {
216    type Error = Error;
217}
218
219impl embedded_io_async::Read for BdxReader<'_> {
220    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
221        BdxReader::read(self, buf).await
222    }
223}
224
225/// An extension trait for initiating a BDX *download*: `download` makes this node the
226/// (typically driving) Receiver and returns a [`BdxReader`].
227pub trait BdxDownloadInitiator<'a> {
228    /// Initiate a BDX download of `file_designator`, negotiate the transfer, and
229    /// return a reader positioned at the start of the requested data.
230    ///
231    /// `offset` (when `Some` and non-zero) requests the download to resume from
232    /// that byte offset of the file - e.g. to continue an interrupted download.
233    /// The sender may refuse with [`StartOffsetNotSupported`](BdxStatus::StartOffsetNotSupported);
234    /// the returned reader's [`len`](BdxReader::len), if known, is the number of
235    /// bytes *remaining* from the offset.
236    async fn download(
237        self,
238        file_designator: &[u8],
239        offset: Option<u64>,
240    ) -> Result<BdxReader<'a>, Error>;
241}
242
243impl<'a> BdxDownloadInitiator<'a> for Exchange<'a> {
244    async fn download(
245        mut self,
246        file_designator: &[u8],
247        offset: Option<u64>,
248    ) -> Result<BdxReader<'a>, Error> {
249        // We stream blocks straight out of the exchange RX buffer, so we propose
250        // the largest block that buffer can hold.
251        send_init(
252            &mut self,
253            OpCode::ReceiveInit,
254            MAX_RX_BLOCK_SIZE,
255            offset,
256            file_designator,
257        )
258        .await?;
259
260        match recv_accept(&mut self, true).await? {
261            // We are the receiver: we drive iff receiver-drive was selected.
262            Some((tc, _mbs, length)) => {
263                let drive = if tc.receiver_drive {
264                    Drive::Driver
265                } else {
266                    Drive::Follower
267                };
268                Ok(BdxReader::new(self, drive, length))
269            }
270            None => abort(&mut self, BdxStatus::TransferMethodNotSupported).await,
271        }
272    }
273}
274
275/// The responding side of a [`upload`](super::BdxUploadInitiator::upload): a peer requested an
276/// upload (sent a `SendInit`), so this node becomes the Receiver. Inspect the
277/// request via [`fd`](Self::fd)/[`len`](Self::len), then [`reply`](Self::reply)
278/// to obtain a [`BdxReader`], or [`reject`](Self::reject) it.
279pub struct BdxUploadResponder<'a> {
280    exchange: Exchange<'a>,
281    transfer_control: TransferControl,
282    max_block_size: u16,
283    length: Option<u64>,
284    start_offset: u64,
285}
286
287impl<'a> BdxUploadResponder<'a> {
288    /// Receive the incoming `SendInit` on `exchange`, holding it until
289    /// [`reply`](Self::reply)/[`reject`](Self::reject).
290    pub async fn accept(mut exchange: Exchange<'a>) -> Result<Self, Error> {
291        let (transfer_control, max_block_size, length, start_offset) =
292            recv_init_hold(&mut exchange, OpCode::SendInit).await?;
293
294        Ok(Self {
295            exchange,
296            transfer_control,
297            max_block_size,
298            length,
299            start_offset,
300        })
301    }
302
303    /// The file designator the initiator is sending (borrowed from the held init).
304    pub fn fd(&self) -> &[u8] {
305        held_fd(&self.exchange)
306    }
307
308    /// The definite length the initiator committed to, if any.
309    #[allow(clippy::len_without_is_empty)] // A transfer length, not a collection count.
310    pub fn len(&self) -> Option<u64> {
311        self.length
312    }
313
314    /// The byte offset of the file at which the initiator is sending: the bytes
315    /// streamed by the returned [`BdxReader`] correspond to the file starting at
316    /// this offset (`0` for a transfer from the start). The receiving application
317    /// decides what a non-zero offset means for its storage (resume/overwrite).
318    pub fn start_offset(&self) -> u64 {
319        self.start_offset
320    }
321
322    /// Accept the transfer and start receiving, returning a [`BdxReader`].
323    pub async fn reply(mut self) -> Result<BdxReader<'a>, Error> {
324        // Prefer to let the initiating sender drive (its `BdxWriter` is the
325        // "driving sender"); otherwise drive ourselves.
326        let tc = self.transfer_control;
327        let drive = if tc.sender_drive {
328            Drive::Follower
329        } else if tc.receiver_drive {
330            Drive::Driver
331        } else {
332            self.exchange.rx_done()?;
333            return abort(&mut self.exchange, BdxStatus::TransferMethodNotSupported).await;
334        };
335
336        // Cap the sender's proposed block size by what our RX buffer can hold.
337        let mbs = self.max_block_size.clamp(1, MAX_RX_BLOCK_SIZE);
338        let length = self.length;
339
340        self.exchange.rx_done()?;
341
342        // A `SendAccept` carries no length; the receiver learned it from the `SendInit`.
343        send_accept(
344            &mut self.exchange,
345            false,
346            TransferControl::select(drive == Drive::Follower),
347            mbs,
348            None,
349        )
350        .await?;
351
352        Ok(BdxReader::new(self.exchange, drive, length))
353    }
354
355    /// Reject the transfer with the given status.
356    pub async fn reject(mut self, status: BdxStatus) -> Result<(), Error> {
357        self.exchange.rx_done()?;
358        send_status_report(&mut self.exchange, status).await
359    }
360}