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}