Skip to main content

rs_matter/bdx/
write.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 *send* side of the BDX streaming engine: the [`BdxWriter`] handle and the
19//! two ways to obtain one - [`BdxUploadInitiator`] (initiate an upload) and
20//! [`BdxDownloadResponder`] (respond to a peer's download).
21
22use super::nego::*;
23use super::*;
24
25/// A writer over a BDX transfer - the Sender side.
26///
27/// Obtained from [`Exchange::upload`](BdxUploadInitiator::upload) on the initiating side, or
28/// from [`BdxDownloadResponder::reply`] on the responding side. [`write`](Self::write)
29/// stages and sends the data, driving the protocol as needed;
30/// [`finish`](Self::finish) flushes the final block and completes the transfer.
31/// It also implements [`embedded_io_async::Write`] (delegating to the inherent
32/// `write`).
33///
34/// The caller supplies the staging buffer `buf`, which doubles as the upper bound
35/// on the block size (so there is no hidden, MCU-unfriendly internal allocation).
36/// It must be non-empty.
37pub struct BdxWriter<'a, 'b> {
38    exchange: Exchange<'a>,
39    drive: Drive,
40    /// The caller-provided staging buffer. At most `max_block_size` of its bytes
41    /// hold the block currently being assembled.
42    buf: &'b mut [u8],
43    max_block_size: usize,
44    /// Driver: the counter for the next block to send. Follower: the expected
45    /// counter of the next `BlockQuery`.
46    counter: u32,
47    block_len: usize,
48}
49
50impl<'a, 'b> BdxWriter<'a, 'b> {
51    pub(super) fn new(
52        exchange: Exchange<'a>,
53        drive: Drive,
54        buf: &'b mut [u8],
55        max_block_size: u16,
56    ) -> Self {
57        // We can never stage more than the buffer holds; negotiation already
58        // bounded `max_block_size`, but clamp defensively.
59        let max_block_size = (max_block_size as usize).min(buf.len());
60
61        Self {
62            exchange,
63            drive,
64            buf,
65            max_block_size,
66            counter: 0,
67            block_len: 0,
68        }
69    }
70
71    /// Stage and send `data`, returning the number of bytes accepted (`< data.len()`
72    /// only when the current block fills; call again with the remainder).
73    ///
74    /// This is also the [`embedded_io_async::Write`] implementation; the inherent
75    /// method is kept so callers need not import the trait.
76    pub async fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
77        if data.is_empty() {
78            return Ok(0);
79        }
80
81        let space = self.max_block_size - self.block_len;
82        let n = space.min(data.len());
83        self.buf[self.block_len..self.block_len + n].copy_from_slice(&data[..n]);
84        self.block_len += n;
85
86        if self.block_len == self.max_block_size {
87            self.send_block(false).await?;
88        }
89
90        Ok(n)
91    }
92
93    /// The largest block this writer will send (and the length of the buffer
94    /// returned by [`block_buf`](Self::block_buf)).
95    pub fn max_block_size(&self) -> usize {
96        self.max_block_size
97    }
98
99    /// The writer's own staging buffer (exactly [`max_block_size`](Self::max_block_size)
100    /// bytes), to be filled in place and then sent with [`commit`](Self::commit).
101    ///
102    /// This is the zero-extra-buffer alternative to [`write`](Self::write): a
103    /// caller streaming from another source (a flash region, a socket) can read
104    /// straight into this slice instead of into its own buffer and copying. Do not
105    /// interleave it with [`write`](Self::write), which stages into the same space.
106    pub fn block_buf(&mut self) -> &mut [u8] {
107        &mut self.buf[..self.max_block_size]
108    }
109
110    /// Send `len` bytes - previously written into [`block_buf`](Self::block_buf) -
111    /// as one block. `len` must not exceed [`max_block_size`](Self::max_block_size).
112    pub async fn commit(&mut self, len: usize) -> Result<(), Error> {
113        if len > self.max_block_size {
114            // Truncating here would silently drop the tail of the caller's block;
115            // surface the contract violation instead.
116            return Err(ErrorCode::Invalid.into());
117        }
118        self.block_len = len;
119
120        self.send_block(false).await
121    }
122
123    /// Flush the final (possibly empty) block and complete the transfer.
124    pub async fn finish(mut self) -> Result<(), Error> {
125        self.send_block(true).await?;
126
127        self.exchange.acknowledge().await
128    }
129
130    /// Send the staged bytes as one block, driving/awaiting acknowledgement per
131    /// the negotiated drive mode.
132    async fn send_block(&mut self, is_eof: bool) -> Result<(), Error> {
133        let counter = self.counter;
134
135        if matches!(self.drive, Drive::Follower) {
136            // Receiver-driven: wait to be asked for this block.
137            self.recv_control(OpCode::BlockQuery, counter).await?;
138        }
139
140        let opcode = if is_eof {
141            OpCode::BlockEof
142        } else {
143            OpCode::Block
144        };
145        let len = self.block_len;
146        {
147            let data = &self.buf[..len];
148            self.exchange
149                .send_with(|_, wb| {
150                    Block {
151                        block_counter: counter,
152                        data,
153                    }
154                    .write(wb)?;
155                    Ok(Some(opcode.into()))
156                })
157                .await?;
158        }
159        self.block_len = 0;
160
161        if matches!(self.drive, Drive::Driver) {
162            let ack = if is_eof {
163                OpCode::BlockAckEof
164            } else {
165                OpCode::BlockAck
166            };
167            self.recv_control(ack, counter).await?;
168        } else if is_eof {
169            // Receiver-driven: the receiver acknowledges the final block.
170            self.recv_control(OpCode::BlockAckEof, counter).await?;
171        }
172
173        self.counter = self.counter.wrapping_add(1);
174
175        Ok(())
176    }
177
178    /// Await a specific counter-only control message and validate its counter.
179    async fn recv_control(&mut self, expected: OpCode, expected_counter: u32) -> Result<(), Error> {
180        enum Outcome {
181            Ok,
182            BadCounter,
183            Unexpected,
184            Aborted(Error),
185        }
186
187        self.exchange.recv_fetch().await?;
188        let meta = self.exchange.rx()?.meta();
189        let outcome = {
190            let payload = self.exchange.rx()?.payload();
191            match classify(&meta, payload) {
192                Ok(op) if op == expected => {
193                    if BlockQuery::parse(payload)?.block_counter == expected_counter {
194                        Outcome::Ok
195                    } else {
196                        Outcome::BadCounter
197                    }
198                }
199                Ok(_) => Outcome::Unexpected,
200                Err(e) => Outcome::Aborted(e),
201            }
202        };
203
204        self.exchange.rx_done()?;
205
206        match outcome {
207            Outcome::Ok => Ok(()),
208            Outcome::BadCounter => abort(&mut self.exchange, BdxStatus::BadBlockCounter).await,
209            Outcome::Unexpected => abort(&mut self.exchange, BdxStatus::UnexpectedMessage).await,
210            Outcome::Aborted(e) => Err(e),
211        }
212    }
213}
214
215impl embedded_io_async::ErrorType for BdxWriter<'_, '_> {
216    type Error = Error;
217}
218
219impl embedded_io_async::Write for BdxWriter<'_, '_> {
220    async fn write(&mut self, data: &[u8]) -> Result<usize, Self::Error> {
221        BdxWriter::write(self, data).await
222    }
223
224    /// Send any staged-but-unsent bytes as a (non-final) block.
225    async fn flush(&mut self) -> Result<(), Self::Error> {
226        if self.block_len > 0 {
227            self.send_block(false).await?;
228        }
229
230        Ok(())
231    }
232}
233
234/// An extension trait for initiating a BDX *upload*: `upload` makes this node the
235/// (typically driving) Sender and returns a [`BdxWriter`].
236pub trait BdxUploadInitiator<'a> {
237    /// Initiate a BDX upload of `file_designator`, negotiate the transfer, and
238    /// return a writer ready to stream the data. `buf` is the (non-empty) staging
239    /// buffer the writer assembles blocks in; its length bounds the block size.
240    ///
241    /// `offset` (when `Some` and non-zero) declares that the data written to the
242    /// returned writer begins at that byte offset of the file - e.g. to resume an
243    /// interrupted upload. The receiver may refuse with
244    /// [`StartOffsetNotSupported`](BdxStatus::StartOffsetNotSupported); otherwise
245    /// the caller is responsible for feeding only the bytes from `offset` onward.
246    async fn upload<'b>(
247        self,
248        buf: &'b mut [u8],
249        file_designator: &[u8],
250        offset: Option<u64>,
251    ) -> Result<BdxWriter<'a, 'b>, Error>;
252}
253
254impl<'a> BdxUploadInitiator<'a> for Exchange<'a> {
255    async fn upload<'b>(
256        mut self,
257        buf: &'b mut [u8],
258        file_designator: &[u8],
259        offset: Option<u64>,
260    ) -> Result<BdxWriter<'a, 'b>, Error> {
261        if buf.is_empty() {
262            // An empty staging buffer would propose a max block size of 0 and yield
263            // a writer that can never make progress; reject it up front.
264            return Err(ErrorCode::Invalid.into());
265        }
266
267        // We can never send a block larger than our staging buffer or our TX buffer.
268        let pmbs = buf.len().min(MAX_TX_BLOCK_SIZE as usize) as u16;
269        send_init(&mut self, OpCode::SendInit, pmbs, offset, file_designator).await?;
270
271        match recv_accept(&mut self, false).await? {
272            // We are the sender: we drive iff sender-drive was selected.
273            Some((tc, mbs, _length)) => {
274                let drive = if tc.sender_drive {
275                    Drive::Driver
276                } else {
277                    Drive::Follower
278                };
279                Ok(BdxWriter::new(self, drive, buf, mbs))
280            }
281            None => abort(&mut self, BdxStatus::TransferMethodNotSupported).await,
282        }
283    }
284}
285
286/// The responding side of a [`download`](super::BdxDownloadInitiator::download): a peer requested a
287/// download (sent a `ReceiveInit`), so this node becomes the Sender. Inspect the
288/// request via [`fd`](Self::fd), then [`reply`](Self::reply) to obtain a
289/// [`BdxWriter`], or [`reject`](Self::reject) it.
290pub struct BdxDownloadResponder<'a> {
291    exchange: Exchange<'a>,
292    transfer_control: TransferControl,
293    max_block_size: u16,
294    start_offset: u64,
295}
296
297impl<'a> BdxDownloadResponder<'a> {
298    /// Receive the incoming `ReceiveInit` on `exchange`, holding it until
299    /// [`reply`](Self::reply)/[`reject`](Self::reject).
300    pub async fn accept(mut exchange: Exchange<'a>) -> Result<Self, Error> {
301        let (transfer_control, max_block_size, _length, start_offset) =
302            recv_init_hold(&mut exchange, OpCode::ReceiveInit).await?;
303
304        Ok(Self {
305            exchange,
306            transfer_control,
307            max_block_size,
308            start_offset,
309        })
310    }
311
312    /// The file designator the initiator requested (borrowed from the held init).
313    pub fn fd(&self) -> &[u8] {
314        held_fd(&self.exchange)
315    }
316
317    /// The byte offset of the file from which the initiator asked the transfer to
318    /// begin (`0` for a transfer from the start): the sender should start sending
319    /// from here, and advertise the *remaining* length in [`reply`](Self::reply).
320    /// Reject with [`StartOffsetNotSupported`](BdxStatus::StartOffsetNotSupported)
321    /// if the offset cannot be honored.
322    pub fn start_offset(&self) -> u64 {
323        self.start_offset
324    }
325
326    /// Accept the transfer and start sending, staging blocks in the (non-empty)
327    /// caller-provided buffer `buf` (its length bounds the block size). `length`
328    /// advertises a definite transfer length (enabling the receiver's progress
329    /// reporting) when known.
330    pub async fn reply<'b>(
331        mut self,
332        buf: &'b mut [u8],
333        length: Option<u64>,
334    ) -> Result<BdxWriter<'a, 'b>, Error> {
335        if buf.is_empty() {
336            // Our staging buffer is unusable, so we can never serve a block. Reject
337            // the peer gracefully rather than panicking on the block-size clamp below.
338            self.exchange.rx_done()?;
339            return abort(&mut self.exchange, BdxStatus::TransferFailedUnknownError).await;
340        }
341
342        // Prefer to let the initiating receiver drive (its `BdxReader` is the
343        // "driving receiver"); otherwise drive ourselves.
344        let tc = self.transfer_control;
345        let drive = if tc.receiver_drive {
346            Drive::Follower
347        } else if tc.sender_drive {
348            Drive::Driver
349        } else {
350            self.exchange.rx_done()?;
351            return abort(&mut self.exchange, BdxStatus::TransferMethodNotSupported).await;
352        };
353
354        // Cap the receiver's proposed block size by our staging buffer and TX buffer.
355        let cap = buf.len().min(MAX_TX_BLOCK_SIZE as usize) as u16;
356        let mbs = self.max_block_size.clamp(1, cap);
357
358        self.exchange.rx_done()?;
359
360        send_accept(
361            &mut self.exchange,
362            true,
363            TransferControl::select(drive == Drive::Driver),
364            mbs,
365            length,
366        )
367        .await?;
368
369        Ok(BdxWriter::new(self.exchange, drive, buf, mbs))
370    }
371
372    /// Reject the transfer with the given status (e.g. `FileDesignatorUnknown`).
373    pub async fn reject(mut self, status: BdxStatus) -> Result<(), Error> {
374        self.exchange.rx_done()?;
375        send_status_report(&mut self.exchange, status).await
376    }
377}