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
253impl AbortPdu {
254    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
255        let b0 = r.read_u8()?;
256        if (b0 >> 4) != ApduType::Abort as u8 {
257            return Err(DecodeError::InvalidValue);
258        }
259        Ok(Self {
260            server: (b0 & 0x01) != 0,
261            invoke_id: r.read_u8()?,
262            reason: r.read_u8()?,
263        })
264    }
265}
266
267/// A BACnet Segment-ACK PDU used during segmented transfers.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub struct SegmentAck {
270    pub negative_ack: bool,
271    pub sent_by_server: bool,
272    pub invoke_id: u8,
273    pub sequence_number: u8,
274    pub actual_window_size: u8,
275}
276
277impl SegmentAck {
278    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
279        let mut b0 = (ApduType::SegmentAck as u8) << 4;
280        if self.negative_ack {
281            b0 |= 0b0000_0010;
282        }
283        if self.sent_by_server {
284            b0 |= 0b0000_0001;
285        }
286        w.write_u8(b0)?;
287        w.write_u8(self.invoke_id)?;
288        w.write_u8(self.sequence_number)?;
289        w.write_u8(self.actual_window_size)?;
290        Ok(())
291    }
292
293    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
294        let b0 = r.read_u8()?;
295        if (b0 >> 4) != ApduType::SegmentAck as u8 {
296            return Err(DecodeError::InvalidValue);
297        }
298        Ok(Self {
299            negative_ack: (b0 & 0b0000_0010) != 0,
300            sent_by_server: (b0 & 0b0000_0001) != 0,
301            invoke_id: r.read_u8()?,
302            sequence_number: r.read_u8()?,
303            actual_window_size: r.read_u8()?,
304        })
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::BacnetError;
311    use crate::encoding::reader::Reader;
312
313    #[test]
314    fn bacnet_error_decodes_without_details() {
315        let mut r = Reader::new(&[0x50, 1, 15]);
316        let e = BacnetError::decode(&mut r).unwrap();
317        assert_eq!(e.invoke_id, 1);
318        assert_eq!(e.service_choice, 15);
319        assert_eq!(e.error_class, None);
320        assert_eq!(e.error_code, None);
321    }
322
323    #[test]
324    fn bacnet_error_decodes_with_details() {
325        let mut r = Reader::new(&[0x50, 1, 15, 0x09, 0x02, 0x19, 0x20]);
326        let e = BacnetError::decode(&mut r).unwrap();
327        assert_eq!(e.invoke_id, 1);
328        assert_eq!(e.service_choice, 15);
329        assert_eq!(e.error_class, Some(2));
330        assert_eq!(e.error_code, Some(32));
331    }
332
333    #[test]
334    fn bacnet_error_decodes_application_enumerated_details() {
335        let mut r = Reader::new(&[0x50, 1, 15, 0x91, 0x02, 0x91, 0x20]);
336        let e = BacnetError::decode(&mut r).unwrap();
337        assert_eq!(e.invoke_id, 1);
338        assert_eq!(e.service_choice, 15);
339        assert_eq!(e.error_class, Some(2));
340        assert_eq!(e.error_code, Some(32));
341    }
342
343    #[test]
344    fn bacnet_error_decodes_opening_wrapped_application_details() {
345        let mut r = Reader::new(&[0x50, 1, 15, 0x0E, 0x91, 0x02, 0x91, 0x20, 0x0F]);
346        let e = BacnetError::decode(&mut r).unwrap();
347        assert_eq!(e.invoke_id, 1);
348        assert_eq!(e.service_choice, 15);
349        assert_eq!(e.error_class, Some(2));
350        assert_eq!(e.error_code, Some(32));
351    }
352}