Skip to main content

bacnet_encoding/
segmentation.rs

1//! Segmentation: split and reassemble large APDU payloads.
2//!
3//! Per ASHRAE 135-2020 Clause 9, segmented messages use 8-bit sequence
4//! numbers with windowed flow control. This module provides the basic
5//! payload splitting and reassembly primitives.
6
7use bacnet_types::error::Error;
8use bytes::Bytes;
9use std::collections::HashMap;
10
11/// PDU types that affect segmentation overhead calculation.
12///
13/// Named `SegmentedPduType` to avoid collision with `bacnet_types::enums::PduType`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SegmentedPduType {
16    /// ConfirmedRequest: 6-byte overhead (type + max-seg/apdu + invoke + seq + window + service).
17    ConfirmedRequest,
18    /// ComplexACK: 5-byte overhead (type + invoke + seq + window + service).
19    ComplexAck,
20}
21
22/// Compute the maximum service data payload per segment.
23///
24/// This is the max APDU length minus the PDU header overhead for segmented messages.
25pub fn max_segment_payload(max_apdu_length: u16, pdu_type: SegmentedPduType) -> usize {
26    let overhead = match pdu_type {
27        SegmentedPduType::ConfirmedRequest => 6,
28        SegmentedPduType::ComplexAck => 5,
29    };
30    (max_apdu_length as usize).saturating_sub(overhead)
31}
32
33/// Split a payload into segments of at most `max_segment_size` bytes.
34///
35/// Always returns at least one segment (possibly empty).
36pub fn split_payload(payload: &[u8], max_segment_size: usize) -> Result<Vec<Bytes>, Error> {
37    if payload.is_empty() {
38        return Ok(vec![Bytes::new()]);
39    }
40    if max_segment_size == 0 {
41        return Err(Error::Segmentation(
42            "non-empty payload cannot be segmented with max segment size 0".into(),
43        ));
44    }
45    let segments: Vec<Bytes> = payload
46        .chunks(max_segment_size)
47        .map(Bytes::copy_from_slice)
48        .collect();
49    if segments.len() > 256 {
50        return Err(Error::Segmentation(format!(
51            "payload requires {} segments, maximum is 256",
52            segments.len()
53        )));
54    }
55    Ok(segments)
56}
57
58/// Collects received segments for reassembly.
59///
60/// Segments can arrive out of order. Call [`reassemble`](SegmentReceiver::reassemble)
61/// once all segments have been received.
62pub struct SegmentReceiver {
63    segments: HashMap<u8, Bytes>,
64}
65
66impl Default for SegmentReceiver {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl SegmentReceiver {
73    /// Create a new empty receiver.
74    pub fn new() -> Self {
75        Self {
76            segments: HashMap::new(),
77        }
78    }
79
80    /// Maximum BACnet APDU segment size (BACnet/IP over UDP).
81    const MAX_SEGMENT_SIZE: usize = 1476;
82
83    /// Store a received segment.
84    ///
85    /// Returns an error if the segment exceeds [`MAX_SEGMENT_SIZE`](Self::MAX_SEGMENT_SIZE).
86    pub fn receive(&mut self, sequence_number: u8, data: Bytes) -> Result<(), Error> {
87        if data.len() > Self::MAX_SEGMENT_SIZE {
88            return Err(Error::Segmentation(format!(
89                "segment size {} exceeds maximum {}",
90                data.len(),
91                Self::MAX_SEGMENT_SIZE
92            )));
93        }
94        self.segments.insert(sequence_number, data);
95        Ok(())
96    }
97
98    /// Check whether a specific segment has been received.
99    pub fn has_segment(&self, sequence_number: u8) -> bool {
100        self.segments.contains_key(&sequence_number)
101    }
102
103    /// Number of segments received so far.
104    pub fn received_count(&self) -> usize {
105        self.segments.len()
106    }
107
108    /// Reassemble all segments in order. `total_segments` is the expected count.
109    ///
110    /// Returns an error if any segment is missing or if `total_segments` exceeds
111    /// the BACnet 8-bit sequence number limit (256).
112    pub fn reassemble(&self, total_segments: usize) -> Result<Vec<u8>, Error> {
113        if total_segments > 256 {
114            return Err(Error::Segmentation(format!(
115                "total_segments {total_segments} exceeds maximum BACnet value (256)"
116            )));
117        }
118        let mut result = Vec::with_capacity(total_segments * 480);
119        for i in 0..total_segments {
120            let seq = i as u8;
121            match self.segments.get(&seq) {
122                Some(data) => result.extend_from_slice(data),
123                None => {
124                    return Err(Error::Segmentation(format!(
125                        "missing segment {} of {}",
126                        i, total_segments
127                    )));
128                }
129            }
130        }
131        Ok(result)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn max_segment_payload_confirmed_request() {
141        assert_eq!(
142            max_segment_payload(480, SegmentedPduType::ConfirmedRequest),
143            474
144        );
145        assert_eq!(
146            max_segment_payload(1476, SegmentedPduType::ConfirmedRequest),
147            1470
148        );
149    }
150
151    #[test]
152    fn max_segment_payload_complex_ack() {
153        assert_eq!(max_segment_payload(480, SegmentedPduType::ComplexAck), 475);
154        assert_eq!(
155            max_segment_payload(1476, SegmentedPduType::ComplexAck),
156            1471
157        );
158    }
159
160    #[test]
161    fn split_payload_fits_single_segment() {
162        let payload = vec![0u8; 100];
163        let segments = split_payload(&payload, 200).unwrap();
164        assert_eq!(segments.len(), 1);
165        assert_eq!(segments[0], payload);
166    }
167
168    #[test]
169    fn split_payload_exact_fit() {
170        let payload = vec![0u8; 200];
171        let segments = split_payload(&payload, 100).unwrap();
172        assert_eq!(segments.len(), 2);
173        assert_eq!(segments[0].len(), 100);
174        assert_eq!(segments[1].len(), 100);
175    }
176
177    #[test]
178    fn split_payload_remainder() {
179        let payload = vec![0u8; 250];
180        let segments = split_payload(&payload, 100).unwrap();
181        assert_eq!(segments.len(), 3);
182        assert_eq!(segments[0].len(), 100);
183        assert_eq!(segments[1].len(), 100);
184        assert_eq!(segments[2].len(), 50);
185    }
186
187    #[test]
188    fn split_empty_payload() {
189        let segments = split_payload(&[], 100).unwrap();
190        assert_eq!(segments.len(), 1);
191        assert!(segments[0].is_empty());
192    }
193
194    #[test]
195    fn reassemble_ordered_segments() {
196        let original = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
197        let segments = split_payload(&original, 3).unwrap();
198        assert_eq!(segments.len(), 4); // 3+3+3+1
199
200        let mut receiver = SegmentReceiver::new();
201        for (i, seg) in segments.iter().enumerate() {
202            receiver.receive(i as u8, seg.clone()).unwrap();
203        }
204        let reassembled = receiver.reassemble(segments.len()).unwrap();
205        assert_eq!(reassembled, original);
206    }
207
208    #[test]
209    fn reassemble_out_of_order() {
210        let mut receiver = SegmentReceiver::new();
211        receiver.receive(2, Bytes::from_static(&[5, 6])).unwrap();
212        receiver.receive(0, Bytes::from_static(&[1, 2])).unwrap();
213        receiver.receive(1, Bytes::from_static(&[3, 4])).unwrap();
214        let reassembled = receiver.reassemble(3).unwrap();
215        assert_eq!(reassembled, vec![1, 2, 3, 4, 5, 6]);
216    }
217
218    #[test]
219    fn reassemble_missing_segment_fails() {
220        let mut receiver = SegmentReceiver::new();
221        receiver.receive(0, Bytes::from_static(&[1, 2])).unwrap();
222        // Missing segment 1
223        receiver.receive(2, Bytes::from_static(&[5, 6])).unwrap();
224        assert!(receiver.reassemble(3).is_err());
225    }
226
227    #[test]
228    fn split_payload_zero_segment_size() {
229        assert!(split_payload(&[1, 2, 3], 0).is_err());
230    }
231
232    #[test]
233    fn split_payload_over_256_segments_errors() {
234        let payload = vec![0u8; 257];
235        assert!(split_payload(&payload, 1).is_err());
236    }
237}