Skip to main content

rustbac_core/apdu/
confirmed.rs

1use crate::apdu::ApduType;
2use crate::encoding::{
3    primitives::decode_unsigned,
4    reader::Reader,
5    tag::{AppTag, Tag},
6    writer::Writer,
7};
8use crate::{DecodeError, EncodeError};
9
10/// Header for a BACnet Confirmed-Request APDU, including segmentation
11/// flags, invoke ID, and the service choice byte.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ConfirmedRequestHeader {
14    pub segmented: bool,
15    pub more_follows: bool,
16    pub segmented_response_accepted: bool,
17    pub max_segments: u8,
18    pub max_apdu: u8,
19    pub invoke_id: u8,
20    pub sequence_number: Option<u8>,
21    pub proposed_window_size: Option<u8>,
22    pub service_choice: u8,
23}
24
25impl ConfirmedRequestHeader {
26    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
27        let mut b0 = (ApduType::ConfirmedRequest as u8) << 4;
28        if self.segmented {
29            b0 |= 0b0000_1000;
30        }
31        if self.more_follows {
32            b0 |= 0b0000_0100;
33        }
34        if self.segmented_response_accepted {
35            b0 |= 0b0000_0010;
36        }
37
38        w.write_u8(b0)?;
39        w.write_u8((self.max_segments << 4) | (self.max_apdu & 0x0f))?;
40        w.write_u8(self.invoke_id)?;
41        if self.segmented {
42            w.write_u8(self.sequence_number.unwrap_or(0))?;
43            w.write_u8(self.proposed_window_size.unwrap_or(1))?;
44        }
45        w.write_u8(self.service_choice)?;
46        Ok(())
47    }
48
49    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
50        let b0 = r.read_u8()?;
51        if (b0 >> 4) != ApduType::ConfirmedRequest as u8 {
52            return Err(DecodeError::InvalidValue);
53        }
54        let segmented = (b0 & 0b0000_1000) != 0;
55        let more_follows = (b0 & 0b0000_0100) != 0;
56        let segmented_response_accepted = (b0 & 0b0000_0010) != 0;
57        let seg_apdu = r.read_u8()?;
58        let invoke_id = r.read_u8()?;
59        let (sequence_number, proposed_window_size) = if segmented {
60            (Some(r.read_u8()?), Some(r.read_u8()?))
61        } else {
62            (None, None)
63        };
64        let service_choice = r.read_u8()?;
65        Ok(Self {
66            segmented,
67            more_follows,
68            segmented_response_accepted,
69            max_segments: seg_apdu >> 4,
70            max_apdu: seg_apdu & 0x0f,
71            invoke_id,
72            sequence_number,
73            proposed_window_size,
74            service_choice,
75        })
76    }
77}
78
79/// Header for a BACnet Complex-ACK APDU (a response carrying data).
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct ComplexAckHeader {
82    pub segmented: bool,
83    pub more_follows: bool,
84    pub invoke_id: u8,
85    pub sequence_number: Option<u8>,
86    pub proposed_window_size: Option<u8>,
87    pub service_choice: u8,
88}
89
90impl ComplexAckHeader {
91    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
92        let mut b0 = (ApduType::ComplexAck as u8) << 4;
93        if self.segmented {
94            b0 |= 0b0000_1000;
95        }
96        if self.more_follows {
97            b0 |= 0b0000_0100;
98        }
99        w.write_u8(b0)?;
100        w.write_u8(self.invoke_id)?;
101        if self.segmented {
102            w.write_u8(self.sequence_number.unwrap_or(0))?;
103            w.write_u8(self.proposed_window_size.unwrap_or(1))?;
104        }
105        w.write_u8(self.service_choice)
106    }
107
108    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
109        let b0 = r.read_u8()?;
110        if (b0 >> 4) != ApduType::ComplexAck as u8 {
111            return Err(DecodeError::InvalidValue);
112        }
113
114        let segmented = (b0 & 0b0000_1000) != 0;
115        let more_follows = (b0 & 0b0000_0100) != 0;
116        let invoke_id = r.read_u8()?;
117        let (sequence_number, proposed_window_size) = if segmented {
118            (Some(r.read_u8()?), Some(r.read_u8()?))
119        } else {
120            (None, None)
121        };
122        let service_choice = r.read_u8()?;
123
124        Ok(Self {
125            segmented,
126            more_follows,
127            invoke_id,
128            sequence_number,
129            proposed_window_size,
130            service_choice,
131        })
132    }
133}
134
135/// Header for a BACnet Simple-ACK APDU (acknowledgement without data).
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct SimpleAck {
138    pub invoke_id: u8,
139    pub service_choice: u8,
140}
141
142impl SimpleAck {
143    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
144        w.write_u8((ApduType::SimpleAck as u8) << 4)?;
145        w.write_u8(self.invoke_id)?;
146        w.write_u8(self.service_choice)
147    }
148
149    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
150        let b0 = r.read_u8()?;
151        if (b0 >> 4) != ApduType::SimpleAck as u8 {
152            return Err(DecodeError::InvalidValue);
153        }
154        Ok(Self {
155            invoke_id: r.read_u8()?,
156            service_choice: r.read_u8()?,
157        })
158    }
159}
160
161/// A decoded BACnet Error APDU with optional error class and code.
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct BacnetError {
164    pub invoke_id: u8,
165    pub service_choice: u8,
166    pub error_class: Option<u32>,
167    pub error_code: Option<u32>,
168}
169
170impl BacnetError {
171    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
172        let b0 = r.read_u8()?;
173        if (b0 >> 4) != ApduType::Error as u8 {
174            return Err(DecodeError::InvalidValue);
175        }
176        let invoke_id = r.read_u8()?;
177        let service_choice = r.read_u8()?;
178        let mut error_class = None;
179        let mut error_code = None;
180        if !r.is_empty() {
181            match Tag::decode(r)? {
182                Tag::Opening { tag_num: 0 } => {
183                    let class_tag = Tag::decode(r)?;
184                    error_class = Some(decode_bacnet_error_value(r, class_tag, 0)?);
185                    let code_tag = Tag::decode(r)?;
186                    error_code = Some(decode_bacnet_error_value(r, code_tag, 1)?);
187                    match Tag::decode(r)? {
188                        Tag::Closing { tag_num: 0 } => {}
189                        _ => return Err(DecodeError::InvalidTag),
190                    }
191                }
192                first_tag => {
193                    error_class = Some(decode_bacnet_error_value(r, first_tag, 0)?);
194                    let second_tag = Tag::decode(r)?;
195                    error_code = Some(decode_bacnet_error_value(r, second_tag, 1)?);
196                }
197            }
198        }
199        Ok(Self {
200            invoke_id,
201            service_choice,
202            error_class,
203            error_code,
204        })
205    }
206}
207
208fn decode_bacnet_error_value(
209    r: &mut Reader<'_>,
210    tag: Tag,
211    expected_ctx_tag: u8,
212) -> Result<u32, DecodeError> {
213    match tag {
214        Tag::Context { tag_num, len } if tag_num == expected_ctx_tag => {
215            decode_unsigned(r, len as usize)
216        }
217        Tag::Application {
218            tag: AppTag::Enumerated,
219            len,
220        } => decode_unsigned(r, len as usize),
221        _ => Err(DecodeError::InvalidTag),
222    }
223}
224
225/// A decoded BACnet Reject PDU, indicating a syntactically invalid request.
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub struct RejectPdu {
228    pub invoke_id: u8,
229    pub reason: u8,
230}
231
232impl RejectPdu {
233    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
234        let b0 = r.read_u8()?;
235        if (b0 >> 4) != ApduType::Reject as u8 {
236            return Err(DecodeError::InvalidValue);
237        }
238        Ok(Self {
239            invoke_id: r.read_u8()?,
240            reason: r.read_u8()?,
241        })
242    }
243}
244
245/// A decoded BACnet Abort PDU, indicating an aborted transaction.
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct AbortPdu {
248    pub server: bool,
249    pub invoke_id: u8,
250    pub reason: u8,
251}
252
253/// BACnet Abort reason codes.
254pub mod abort_reason {
255    /// The peer does not support segmented messages.
256    pub const SEGMENTATION_NOT_SUPPORTED: u8 = 0x04;
257}
258
259impl AbortPdu {
260    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
261        let mut b0 = (ApduType::Abort as u8) << 4;
262        if self.server {
263            b0 |= 0x01;
264        }
265        w.write_u8(b0)?;
266        w.write_u8(self.invoke_id)?;
267        w.write_u8(self.reason)
268    }
269
270    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
271        let b0 = r.read_u8()?;
272        if (b0 >> 4) != ApduType::Abort as u8 {
273            return Err(DecodeError::InvalidValue);
274        }
275        Ok(Self {
276            server: (b0 & 0x01) != 0,
277            invoke_id: r.read_u8()?,
278            reason: r.read_u8()?,
279        })
280    }
281}
282
283/// A BACnet Segment-ACK PDU used during segmented transfers.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub struct SegmentAck {
286    pub negative_ack: bool,
287    pub sent_by_server: bool,
288    pub invoke_id: u8,
289    pub sequence_number: u8,
290    pub actual_window_size: u8,
291}
292
293impl SegmentAck {
294    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
295        let mut b0 = (ApduType::SegmentAck as u8) << 4;
296        if self.negative_ack {
297            b0 |= 0b0000_0010;
298        }
299        if self.sent_by_server {
300            b0 |= 0b0000_0001;
301        }
302        w.write_u8(b0)?;
303        w.write_u8(self.invoke_id)?;
304        w.write_u8(self.sequence_number)?;
305        w.write_u8(self.actual_window_size)?;
306        Ok(())
307    }
308
309    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
310        let b0 = r.read_u8()?;
311        if (b0 >> 4) != ApduType::SegmentAck as u8 {
312            return Err(DecodeError::InvalidValue);
313        }
314        Ok(Self {
315            negative_ack: (b0 & 0b0000_0010) != 0,
316            sent_by_server: (b0 & 0b0000_0001) != 0,
317            invoke_id: r.read_u8()?,
318            sequence_number: r.read_u8()?,
319            actual_window_size: r.read_u8()?,
320        })
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::BacnetError;
327    use crate::encoding::reader::Reader;
328
329    #[test]
330    fn bacnet_error_decodes_without_details() {
331        let mut r = Reader::new(&[0x50, 1, 15]);
332        let e = BacnetError::decode(&mut r).unwrap();
333        assert_eq!(e.invoke_id, 1);
334        assert_eq!(e.service_choice, 15);
335        assert_eq!(e.error_class, None);
336        assert_eq!(e.error_code, None);
337    }
338
339    #[test]
340    fn bacnet_error_decodes_with_details() {
341        let mut r = Reader::new(&[0x50, 1, 15, 0x09, 0x02, 0x19, 0x20]);
342        let e = BacnetError::decode(&mut r).unwrap();
343        assert_eq!(e.invoke_id, 1);
344        assert_eq!(e.service_choice, 15);
345        assert_eq!(e.error_class, Some(2));
346        assert_eq!(e.error_code, Some(32));
347    }
348
349    #[test]
350    fn bacnet_error_decodes_application_enumerated_details() {
351        let mut r = Reader::new(&[0x50, 1, 15, 0x91, 0x02, 0x91, 0x20]);
352        let e = BacnetError::decode(&mut r).unwrap();
353        assert_eq!(e.invoke_id, 1);
354        assert_eq!(e.service_choice, 15);
355        assert_eq!(e.error_class, Some(2));
356        assert_eq!(e.error_code, Some(32));
357    }
358
359    #[test]
360    fn bacnet_error_decodes_opening_wrapped_application_details() {
361        let mut r = Reader::new(&[0x50, 1, 15, 0x0E, 0x91, 0x02, 0x91, 0x20, 0x0F]);
362        let e = BacnetError::decode(&mut r).unwrap();
363        assert_eq!(e.invoke_id, 1);
364        assert_eq!(e.service_choice, 15);
365        assert_eq!(e.error_class, Some(2));
366        assert_eq!(e.error_code, Some(32));
367    }
368}