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).
36/// If the payload would produce more than 256 segments (the BACnet sequence
37/// number limit), falls back to returning the entire payload as a single
38/// unsegmented segment.
39pub fn split_payload(payload: &[u8], max_segment_size: usize) -> Vec<Bytes> {
40    if max_segment_size == 0 || payload.is_empty() {
41        return vec![Bytes::copy_from_slice(payload)];
42    }
43    let segments: Vec<Bytes> = payload
44        .chunks(max_segment_size)
45        .map(Bytes::copy_from_slice)
46        .collect();
47    if segments.len() > 256 {
48        return vec![Bytes::copy_from_slice(payload)];
49    }
50    segments
51}
52
53/// Collects received segments for reassembly.
54///
55/// Segments can arrive out of order. Call [`reassemble`](SegmentReceiver::reassemble)
56/// once all segments have been received.
57pub struct SegmentReceiver {
58    segments: HashMap<u8, Bytes>,
59}
60
61impl Default for SegmentReceiver {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl SegmentReceiver {
68    /// Create a new empty receiver.
69    pub fn new() -> Self {
70        Self {
71            segments: HashMap::new(),
72        }
73    }
74
75    /// Maximum BACnet APDU segment size (BACnet/IP over UDP).
76    const MAX_SEGMENT_SIZE: usize = 1476;
77
78    /// Store a received segment.
79    ///
80    /// Returns an error if the segment exceeds [`MAX_SEGMENT_SIZE`](Self::MAX_SEGMENT_SIZE).
81    pub fn receive(&mut self, sequence_number: u8, data: Bytes) -> Result<(), Error> {
82        if data.len() > Self::MAX_SEGMENT_SIZE {
83            return Err(Error::Segmentation(format!(
84                "segment size {} exceeds maximum {}",
85                data.len(),
86                Self::MAX_SEGMENT_SIZE
87            )));
88        }
89        self.segments.insert(sequence_number, data);
90        Ok(())
91    }
92
93    /// Check whether a specific segment has been received.
94    pub fn has_segment(&self, sequence_number: u8) -> bool {
95        self.segments.contains_key(&sequence_number)
96    }
97
98    /// Number of segments received so far.
99    pub fn received_count(&self) -> usize {
100        self.segments.len()
101    }
102
103    /// Reassemble all segments in order. `total_segments` is the expected count.
104    ///
105    /// Returns an error if any segment is missing or if `total_segments` exceeds
106    /// the BACnet 8-bit sequence number limit (256).
107    pub fn reassemble(&self, total_segments: usize) -> Result<Vec<u8>, Error> {
108        if total_segments > 256 {
109            return Err(Error::Segmentation(format!(
110                "total_segments {total_segments} exceeds maximum BACnet value (256)"
111            )));
112        }
113        let mut result = Vec::new();
114        for i in 0..total_segments {
115            let seq = i as u8;
116            match self.segments.get(&seq) {
117                Some(data) => result.extend_from_slice(data),
118                None => {
119                    return Err(Error::Segmentation(format!(
120                        "missing segment {} of {}",
121                        i, total_segments
122                    )));
123                }
124            }
125        }
126        Ok(result)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn max_segment_payload_confirmed_request() {
136        assert_eq!(
137            max_segment_payload(480, SegmentedPduType::ConfirmedRequest),
138            474
139        );
140        assert_eq!(
141            max_segment_payload(1476, SegmentedPduType::ConfirmedRequest),
142            1470
143        );
144    }
145
146    #[test]
147    fn max_segment_payload_complex_ack() {
148        assert_eq!(max_segment_payload(480, SegmentedPduType::ComplexAck), 475);
149        assert_eq!(
150            max_segment_payload(1476, SegmentedPduType::ComplexAck),
151            1471
152        );
153    }
154
155    #[test]
156    fn split_payload_fits_single_segment() {
157        let payload = vec![0u8; 100];
158        let segments = split_payload(&payload, 200);
159        assert_eq!(segments.len(), 1);
160        assert_eq!(segments[0], payload);
161    }
162
163    #[test]
164    fn split_payload_exact_fit() {
165        let payload = vec![0u8; 200];
166        let segments = split_payload(&payload, 100);
167        assert_eq!(segments.len(), 2);
168        assert_eq!(segments[0].len(), 100);
169        assert_eq!(segments[1].len(), 100);
170    }
171
172    #[test]
173    fn split_payload_remainder() {
174        let payload = vec![0u8; 250];
175        let segments = split_payload(&payload, 100);
176        assert_eq!(segments.len(), 3);
177        assert_eq!(segments[0].len(), 100);
178        assert_eq!(segments[1].len(), 100);
179        assert_eq!(segments[2].len(), 50);
180    }
181
182    #[test]
183    fn split_empty_payload() {
184        let segments = split_payload(&[], 100);
185        assert_eq!(segments.len(), 1);
186        assert!(segments[0].is_empty());
187    }
188
189    #[test]
190    fn reassemble_ordered_segments() {
191        let original = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
192        let segments = split_payload(&original, 3);
193        assert_eq!(segments.len(), 4); // 3+3+3+1
194
195        let mut receiver = SegmentReceiver::new();
196        for (i, seg) in segments.iter().enumerate() {
197            receiver.receive(i as u8, seg.clone()).unwrap();
198        }
199        let reassembled = receiver.reassemble(segments.len()).unwrap();
200        assert_eq!(reassembled, original);
201    }
202
203    #[test]
204    fn reassemble_out_of_order() {
205        let mut receiver = SegmentReceiver::new();
206        receiver.receive(2, Bytes::from_static(&[5, 6])).unwrap();
207        receiver.receive(0, Bytes::from_static(&[1, 2])).unwrap();
208        receiver.receive(1, Bytes::from_static(&[3, 4])).unwrap();
209        let reassembled = receiver.reassemble(3).unwrap();
210        assert_eq!(reassembled, vec![1, 2, 3, 4, 5, 6]);
211    }
212
213    #[test]
214    fn reassemble_missing_segment_fails() {
215        let mut receiver = SegmentReceiver::new();
216        receiver.receive(0, Bytes::from_static(&[1, 2])).unwrap();
217        // Missing segment 1
218        receiver.receive(2, Bytes::from_static(&[5, 6])).unwrap();
219        assert!(receiver.reassemble(3).is_err());
220    }
221
222    #[test]
223    fn split_payload_zero_segment_size() {
224        // Should not panic — returns entire payload as single segment
225        let result = split_payload(&[1, 2, 3], 0);
226        assert_eq!(result, vec![vec![1, 2, 3]]);
227    }
228}