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}