Skip to main content

zerodds_coap_bridge/
blockwise.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Block-Wise Transfer — RFC 7959.
5//!
6//! Spec §2: Block-Option (Block1 / Block2) erlaubt segmentierten
7//! Payload-Transfer. Encoding eines Block-Wertes:
8//!
9//! ```text
10//!   NUM (4..20 bit) || M (1 bit) || SZX (3 bit)
11//! ```
12//!
13//! `SZX` codiert die Block-Size als `2^(SZX+4)` (16 bytes..1024 bytes).
14//! `M` = "more"-Flag.
15
16use alloc::vec::Vec;
17
18/// Block-Option-Number (RFC 7959 §2.1).
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u16)]
21pub enum BlockOption {
22    /// `Block1` (Option-Number 27) — Transfer im Request-Body.
23    Block1 = 27,
24    /// `Block2` (Option-Number 23) — Transfer im Response-Body.
25    Block2 = 23,
26}
27
28/// Block-Wert (Spec §2.2).
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct BlockValue {
31    /// Block-Number (NUM).
32    pub num: u32,
33    /// "More"-Flag.
34    pub more: bool,
35    /// `SZX` (0..=6, 7 reserved); Spec verbietet 7.
36    pub szx: u8,
37}
38
39/// Block-Codec-Fehler.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum BlockError {
42    /// `SZX = 7` ist reserved.
43    ReservedSzx,
44    /// `NUM > 2^20 - 1`.
45    NumTooLarge,
46    /// Encoded value uebersteigt 3 Bytes.
47    EncodingTooLong,
48    /// Decode: 0 oder mehr als 3 Bytes.
49    BadEncodingLength,
50}
51
52impl BlockValue {
53    /// Block-Size in Bytes (Spec §2.2: 2^(SZX+4)).
54    ///
55    /// # Errors
56    /// `ReservedSzx` wenn `szx == 7`.
57    pub fn block_size(&self) -> Result<usize, BlockError> {
58        if self.szx > 6 {
59            return Err(BlockError::ReservedSzx);
60        }
61        Ok(1usize << (self.szx + 4))
62    }
63
64    /// Encode zu 1..=3 Bytes. Spec §2.2.
65    ///
66    /// # Errors
67    /// `ReservedSzx` / `NumTooLarge`.
68    pub fn encode(&self) -> Result<Vec<u8>, BlockError> {
69        if self.szx > 6 {
70            return Err(BlockError::ReservedSzx);
71        }
72        if self.num >= (1 << 20) {
73            return Err(BlockError::NumTooLarge);
74        }
75        let raw = (self.num << 4) | (u32::from(self.more) << 3) | u32::from(self.szx);
76        let mut out = Vec::with_capacity(3);
77        if raw < 0x100 {
78            out.push(raw as u8);
79        } else if raw < 0x1_0000 {
80            out.push(((raw >> 8) & 0xff) as u8);
81            out.push((raw & 0xff) as u8);
82        } else {
83            out.push(((raw >> 16) & 0xff) as u8);
84            out.push(((raw >> 8) & 0xff) as u8);
85            out.push((raw & 0xff) as u8);
86        }
87        Ok(out)
88    }
89
90    /// Decode von 1..=3 Bytes.
91    ///
92    /// # Errors
93    /// `BadEncodingLength` / `ReservedSzx`.
94    pub fn decode(bytes: &[u8]) -> Result<Self, BlockError> {
95        let raw: u32 = match bytes.len() {
96            1 => u32::from(bytes[0]),
97            2 => (u32::from(bytes[0]) << 8) | u32::from(bytes[1]),
98            3 => (u32::from(bytes[0]) << 16) | (u32::from(bytes[1]) << 8) | u32::from(bytes[2]),
99            _ => return Err(BlockError::BadEncodingLength),
100        };
101        let szx = (raw & 0x7) as u8;
102        if szx == 7 {
103            return Err(BlockError::ReservedSzx);
104        }
105        let more = (raw >> 3) & 0x1 != 0;
106        let num = raw >> 4;
107        Ok(Self { num, more, szx })
108    }
109}
110
111/// Reassembler — sammelt Block-Slices in der korrekten Reihenfolge.
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct BlockReassembler {
114    /// Erwartete Block-Size (Bytes).
115    block_size: usize,
116    /// Akkumuliertes Payload.
117    buf: Vec<u8>,
118    /// Naechste erwartete `num`.
119    next_num: u32,
120    /// `true` wenn der letzte Block (`!more`) angekommen ist.
121    complete: bool,
122}
123
124impl BlockReassembler {
125    /// Konstruktor — fixiert die Block-Size beim ersten Block.
126    #[must_use]
127    pub fn new(block_size: usize) -> Self {
128        Self {
129            block_size,
130            buf: Vec::new(),
131            next_num: 0,
132            complete: false,
133        }
134    }
135
136    /// Akzeptiere einen Block-Slice.
137    ///
138    /// # Errors
139    /// Static-String wenn die Block-Sequenz inkonsistent ist (falsche
140    /// Reihenfolge, Block-Size-Wechsel mit Daten, unerwartete more-
141    /// Flag-Kombination).
142    pub fn accept(&mut self, value: BlockValue, payload: &[u8]) -> Result<(), &'static str> {
143        if self.complete {
144            return Err("reassembler already complete");
145        }
146        let size = value.block_size().map_err(|_| "block size invalid")?;
147        if value.num != self.next_num {
148            return Err("block out of order");
149        }
150        if size != self.block_size {
151            // Spec §2.2 erlaubt SZX-Verkleinerung beim ersten Block.
152            if self.next_num == 0 {
153                self.block_size = size;
154            } else {
155                return Err("block size changed mid-transfer");
156            }
157        }
158        if value.more && payload.len() != size {
159            return Err("intermediate block size mismatch");
160        }
161        if !value.more && payload.len() > size {
162            return Err("final block too large");
163        }
164        self.buf.extend_from_slice(payload);
165        self.next_num += 1;
166        if !value.more {
167            self.complete = true;
168        }
169        Ok(())
170    }
171
172    /// `true` wenn alle Blocks empfangen.
173    #[must_use]
174    pub fn is_complete(&self) -> bool {
175        self.complete
176    }
177
178    /// Entnimmt das gesammelte Payload.
179    #[must_use]
180    pub fn into_payload(self) -> Vec<u8> {
181        self.buf
182    }
183
184    /// Aktuelle Length.
185    #[must_use]
186    pub fn len(&self) -> usize {
187        self.buf.len()
188    }
189
190    /// `true` wenn keine Bytes empfangen.
191    #[must_use]
192    pub fn is_empty(&self) -> bool {
193        self.buf.is_empty()
194    }
195}
196
197#[cfg(test)]
198#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn block_size_matches_spec_szx_table() {
204        // Spec §2.2: szx 0=16, 1=32, 2=64, 3=128, 4=256, 5=512, 6=1024.
205        let expected = [16, 32, 64, 128, 256, 512, 1024];
206        for (szx, sz) in expected.iter().enumerate() {
207            let v = BlockValue {
208                num: 0,
209                more: false,
210                szx: szx as u8,
211            };
212            assert_eq!(v.block_size().unwrap(), *sz);
213        }
214    }
215
216    #[test]
217    fn szx_7_rejected() {
218        let v = BlockValue {
219            num: 0,
220            more: false,
221            szx: 7,
222        };
223        assert_eq!(v.block_size(), Err(BlockError::ReservedSzx));
224    }
225
226    #[test]
227    fn round_trip_small_block_value() {
228        let v = BlockValue {
229            num: 0,
230            more: true,
231            szx: 6,
232        };
233        let bytes = v.encode().unwrap();
234        let back = BlockValue::decode(&bytes).unwrap();
235        assert_eq!(v, back);
236    }
237
238    #[test]
239    fn round_trip_large_block_value() {
240        let v = BlockValue {
241            num: 0xfffff,
242            more: false,
243            szx: 0,
244        };
245        let bytes = v.encode().unwrap();
246        let back = BlockValue::decode(&bytes).unwrap();
247        assert_eq!(v, back);
248    }
249
250    #[test]
251    fn num_too_large_rejected() {
252        let v = BlockValue {
253            num: 1 << 20,
254            more: false,
255            szx: 0,
256        };
257        assert_eq!(v.encode(), Err(BlockError::NumTooLarge));
258    }
259
260    #[test]
261    fn decode_rejects_bad_length() {
262        assert_eq!(
263            BlockValue::decode(&[0; 4]),
264            Err(BlockError::BadEncodingLength)
265        );
266        assert_eq!(BlockValue::decode(&[]), Err(BlockError::BadEncodingLength));
267    }
268
269    #[test]
270    fn reassembler_combines_blocks_in_order() {
271        let mut r = BlockReassembler::new(16);
272        r.accept(
273            BlockValue {
274                num: 0,
275                more: true,
276                szx: 0,
277            },
278            &[1u8; 16],
279        )
280        .unwrap();
281        r.accept(
282            BlockValue {
283                num: 1,
284                more: true,
285                szx: 0,
286            },
287            &[2u8; 16],
288        )
289        .unwrap();
290        r.accept(
291            BlockValue {
292                num: 2,
293                more: false,
294                szx: 0,
295            },
296            &[3u8; 8],
297        )
298        .unwrap();
299        assert!(r.is_complete());
300        let buf = r.into_payload();
301        assert_eq!(buf.len(), 16 + 16 + 8);
302    }
303
304    #[test]
305    fn out_of_order_block_rejected() {
306        let mut r = BlockReassembler::new(16);
307        let err = r
308            .accept(
309                BlockValue {
310                    num: 5,
311                    more: true,
312                    szx: 0,
313                },
314                &[1u8; 16],
315            )
316            .unwrap_err();
317        assert_eq!(err, "block out of order");
318    }
319
320    #[test]
321    fn intermediate_block_size_must_match() {
322        let mut r = BlockReassembler::new(16);
323        r.accept(
324            BlockValue {
325                num: 0,
326                more: true,
327                szx: 0,
328            },
329            &[1u8; 16],
330        )
331        .unwrap();
332        let err = r
333            .accept(
334                BlockValue {
335                    num: 1,
336                    more: true,
337                    szx: 0,
338                },
339                &[2u8; 8],
340            )
341            .unwrap_err();
342        assert_eq!(err, "intermediate block size mismatch");
343    }
344
345    #[test]
346    fn final_block_can_be_smaller() {
347        let mut r = BlockReassembler::new(16);
348        r.accept(
349            BlockValue {
350                num: 0,
351                more: false,
352                szx: 0,
353            },
354            &[7u8; 5],
355        )
356        .unwrap();
357        assert!(r.is_complete());
358        assert_eq!(r.len(), 5);
359    }
360}