Skip to main content

bacnet_encoding/apdu/
header.rs

1//! The fixed header of the APDUs that carry service parameters, read and
2//! written without an allocator (#1614): an Unconfirmed-Request, an
3//! unsegmented Confirmed-Request or ComplexACK, and an Error (Clauses
4//! 20.1.2, 20.1.3, 20.1.5 and 20.1.7); and the two answers that are all
5//! header, a Reject and an Abort (Clauses 20.1.8 and 20.1.9), so a device
6//! without a heap can refuse what it doesn't serve (#1771).
7//!
8//! [`ApduHeader::decode`] splits an APDU into its header and the service
9//! parameters after it, borrowed in place; [`ApduHeader::encode_to_sink`]
10//! and [`ApduHeader::encode_with_parameters_to_sink`] write the same octets
11//! the owned `encode_apdu` does. A segmented Confirmed-Request or ComplexACK
12//! is refused with [`DecodingKind::Unsupported`]: reassembling one needs
13//! storage this profile doesn't assume. A device that can't take segmented
14//! requests answers one with an Abort (Clause 5.4.5.1). A SimpleACK or a
15//! SegmentACK is refused the same way.
16//!
17//! ```
18//! use bacnet_encoding::apdu::header::{ApduHeader, MaxApdu, MaxSegments};
19//! use bacnet_types::enums::ConfirmedServiceChoice;
20//!
21//! // A ReadProperty request: invoke ID 42, segmented replies accepted, any
22//! // number of segments, APDUs up to 1476 octets.
23//! let apdu = [0x02, 0x05, 0x2A, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x19, 0x55];
24//! let (header, parameters) = ApduHeader::decode(&apdu)?;
25//! let ApduHeader::ConfirmedRequest(request) = header else { unreachable!() };
26//! assert_eq!(request.invoke_id, 42);
27//! assert_eq!(request.service_choice, ConfirmedServiceChoice::READ_PROPERTY);
28//! assert_eq!(request.max_apdu.octets(), 1476);
29//! assert_eq!(request.max_segments, MaxSegments::UNSPECIFIED);
30//! assert_eq!(parameters, &apdu[4..]);
31//!
32//! // The ComplexACK that answers it, written in front of its parameters.
33//! let mut storage = [0; 16];
34//! let mut tail = &mut storage[..];
35//! let ack = ApduHeader::ComplexAck { invoke_id: 42, service_choice: request.service_choice };
36//! let written = ack.encode_with_parameters_to_sink(&mut tail, &[0x0C])?;
37//! assert_eq!(storage[..written], [0x30, 0x2A, 0x0C, 0x0C]);
38//! # Ok::<(), Box<dyn core::error::Error>>(())
39//! ```
40
41use bacnet_types::enums::{
42    AbortReason, ConfirmedServiceChoice, PduType, RejectReason, UnconfirmedServiceChoice,
43};
44use bacnet_types::error::{DecodeError, DecodingKind, Error};
45
46use crate::sink::{EncodeSink, SinkFull};
47
48/// The max-segments-accepted field of a Confirmed-Request (Clause 20.1.2.4):
49/// three bits naming 2, 4, 8, 16, 32 or 64 segments, more than 64, or an
50/// unspecified number.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub struct MaxSegments(u8);
53
54impl MaxSegments {
55    /// B'000': the number of segments is unspecified.
56    pub const UNSPECIFIED: Self = Self(0);
57    /// B'111': more than 64 segments.
58    pub const MORE_THAN_64: Self = Self(7);
59
60    /// The field for a configured capacity: unspecified for `None`, and a
61    /// count rounded down to the largest one the field names, so it never
62    /// promises more than configured (65 and up are "more than 64"). `None`
63    /// for a capacity of 0 or 1 segments, which the field can't express:
64    /// decline segmented replies instead.
65    pub const fn from_capacity(capacity: Option<u8>) -> Option<Self> {
66        Some(Self(match capacity {
67            None => 0,
68            Some(0..=1) => return None,
69            Some(2..=3) => 1,
70            Some(4..=7) => 2,
71            Some(8..=15) => 3,
72            Some(16..=31) => 4,
73            Some(32..=63) => 5,
74            Some(64) => 6,
75            Some(65..=u8::MAX) => 7,
76        }))
77    }
78
79    /// The field read from the low three bits of `bits`.
80    pub const fn from_bits(bits: u8) -> Self {
81        Self(bits & 0x07)
82    }
83
84    /// The field's three bits.
85    pub const fn bits(self) -> u8 {
86        self.0
87    }
88
89    /// The segments the field names: `None` when unspecified, and 255 for
90    /// more than 64, as the owned decoder reports them.
91    pub const fn count(self) -> Option<u8> {
92        match self.0 {
93            0 => None,
94            7 => Some(255),
95            bits => Some(1 << bits),
96        }
97    }
98}
99
100/// The max-APDU-length-accepted field of a Confirmed-Request (Clause
101/// 20.1.2.5): one of six sizes, 50 (MinimumMessageSize), 128, 206, 480, 1024
102/// and 1476 octets. The other ten codes are reserved.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
104pub struct MaxApdu(u8);
105
106/// The octets each [`MaxApdu`] code names.
107const MAX_APDU_OCTETS: [u16; 6] = [50, 128, 206, 480, 1024, 1476];
108
109impl MaxApdu {
110    /// Up to 50 octets, MinimumMessageSize.
111    pub const OCTETS_50: Self = Self(0);
112    /// Up to 128 octets.
113    pub const OCTETS_128: Self = Self(1);
114    /// Up to 206 octets.
115    pub const OCTETS_206: Self = Self(2);
116    /// Up to 480 octets.
117    pub const OCTETS_480: Self = Self(3);
118    /// Up to 1024 octets.
119    pub const OCTETS_1024: Self = Self(4);
120    /// Up to 1476 octets, or more.
121    pub const OCTETS_1476: Self = Self(5);
122
123    /// The field naming exactly `octets`, or `None` for a size it doesn't
124    /// name.
125    pub const fn from_octets(octets: u16) -> Option<Self> {
126        let mut code = 0;
127        while code < MAX_APDU_OCTETS.len() {
128            if MAX_APDU_OCTETS[code] == octets {
129                return Some(Self(code as u8));
130            }
131            code += 1;
132        }
133        None
134    }
135
136    /// The largest size the field names that is at most `capacity` octets;
137    /// `None` below 50.
138    pub const fn at_or_below(capacity: u32) -> Option<Self> {
139        let mut code = MAX_APDU_OCTETS.len();
140        while code > 0 {
141            code -= 1;
142            if MAX_APDU_OCTETS[code] as u32 <= capacity {
143                return Some(Self(code as u8));
144            }
145        }
146        None
147    }
148
149    /// The field read from the low four bits of `bits`; `None` for a
150    /// reserved code.
151    pub const fn from_bits(bits: u8) -> Option<Self> {
152        let code = bits & 0x0F;
153        if (code as usize) < MAX_APDU_OCTETS.len() {
154            Some(Self(code))
155        } else {
156            None
157        }
158    }
159
160    /// The field's four bits.
161    pub const fn bits(self) -> u8 {
162        self.0
163    }
164
165    /// The size the field names, in octets.
166    pub const fn octets(self) -> u16 {
167        MAX_APDU_OCTETS[self.0 as usize]
168    }
169}
170
171/// The header of an unsegmented Confirmed-Request (Clause 20.1.2).
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
173pub struct ConfirmedRequestHeader {
174    /// Whether the requester takes a segmented reply.
175    pub segmented_response_accepted: bool,
176    /// The most segments the requester takes in a reply.
177    pub max_segments: MaxSegments,
178    /// The largest APDU the requester takes.
179    pub max_apdu: MaxApdu,
180    /// The identifier the reply echoes.
181    pub invoke_id: u8,
182    /// The service requested.
183    pub service_choice: ConfirmedServiceChoice,
184}
185
186/// The header of an APDU that carries service parameters, or the whole of
187/// a Reject or an Abort, as the allocation-free profile reads and writes it.
188///
189/// More PDU types may join it, so a `match` on it needs a wildcard arm.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
191#[non_exhaustive]
192pub enum ApduHeader {
193    /// An unsegmented Confirmed-Request: four octets.
194    ConfirmedRequest(ConfirmedRequestHeader),
195    /// An Unconfirmed-Request for this service: two octets.
196    UnconfirmedRequest(UnconfirmedServiceChoice),
197    /// An unsegmented ComplexACK: three octets.
198    ComplexAck {
199        /// The invoke ID of the request it answers.
200        invoke_id: u8,
201        /// The service it answers.
202        service_choice: ConfirmedServiceChoice,
203    },
204    /// The header of an Error: three octets, followed by the error
205    /// parameters (for most services, the application-tagged error class
206    /// and code).
207    Error {
208        /// The invoke ID of the request it answers.
209        invoke_id: u8,
210        /// The service that failed.
211        service_choice: ConfirmedServiceChoice,
212    },
213    /// A whole Reject: three octets, with no parameters after them.
214    Reject {
215        /// The invoke ID of the request it rejects.
216        invoke_id: u8,
217        /// Why.
218        reason: RejectReason,
219    },
220    /// A whole Abort: three octets, with no parameters after them.
221    Abort {
222        /// Whether the server sent it (the SRV bit), rather than the client.
223        server: bool,
224        /// Which transaction it ends, by invoke ID.
225        invoke_id: u8,
226        /// Why.
227        reason: AbortReason,
228    },
229}
230
231/// The longest header this module writes.
232const MAX_HEADER: usize = 4;
233
234impl ApduHeader {
235    /// Split `apdu` into its header and the service parameters after it.
236    ///
237    /// Refuses, at offset 0 unless noted: an empty APDU; one whose header is
238    /// cut short ([`Error::BufferTooShort`]); a reserved max-APDU code (at
239    /// offset 1), segmented or not; a segmented Confirmed-Request or
240    /// ComplexACK, a SimpleACK and a SegmentACK, with
241    /// [`DecodingKind::Unsupported`]; and a PDU type nibble past 7. The
242    /// more-follows bit of an unsegmented PDU is ignored, as Clause 20.1.2.2
243    /// asks, and so are the reserved bits of an Error, a Reject or an Abort.
244    /// A Reject or an Abort is the whole PDU: what follows its three octets
245    /// is returned as the parameters, for the caller to refuse.
246    pub fn decode(apdu: &[u8]) -> Result<(Self, &[u8]), Error> {
247        let Some(&first) = apdu.first() else {
248            return Err(Error::decoding(0, "APDU data is empty"));
249        };
250        let pdu_type = PduType::from_raw(first >> 4);
251        let segmented = first & 0x08 != 0;
252        if pdu_type == PduType::CONFIRMED_REQUEST {
253            let [_, fields, invoke_id, service, ..] = *apdu else {
254                return Err(Error::buffer_too_short(4, apdu.len()));
255            };
256            // A reserved size code is malformed whether or not the request is
257            // segmented, as the owned decoder judges it.
258            let Some(max_apdu) = MaxApdu::from_bits(fields) else {
259                return Err(DecodeError::new(
260                    DecodingKind::InvalidEncoding,
261                    1,
262                    "reserved max-APDU-length field value {}",
263                )
264                .with_value(u64::from(fields & 0x0F))
265                .into());
266            };
267            if segmented {
268                return Err(segmented_refusal("segmented ConfirmedRequest"));
269            }
270            let header = ConfirmedRequestHeader {
271                segmented_response_accepted: first & 0x02 != 0,
272                max_segments: MaxSegments::from_bits(fields >> 4),
273                max_apdu,
274                invoke_id,
275                service_choice: ConfirmedServiceChoice::from_raw(service),
276            };
277            Ok((Self::ConfirmedRequest(header), &apdu[4..]))
278        } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
279            let [_, service, ..] = *apdu else {
280                return Err(Error::buffer_too_short(2, apdu.len()));
281            };
282            let service = UnconfirmedServiceChoice::from_raw(service);
283            Ok((Self::UnconfirmedRequest(service), &apdu[2..]))
284        } else if pdu_type == PduType::COMPLEX_ACK {
285            let [_, invoke_id, service, ..] = *apdu else {
286                return Err(Error::buffer_too_short(3, apdu.len()));
287            };
288            if segmented {
289                return Err(segmented_refusal("segmented ComplexAck"));
290            }
291            let service_choice = ConfirmedServiceChoice::from_raw(service);
292            Ok((
293                Self::ComplexAck {
294                    invoke_id,
295                    service_choice,
296                },
297                &apdu[3..],
298            ))
299        } else if pdu_type == PduType::ERROR
300            || pdu_type == PduType::REJECT
301            || pdu_type == PduType::ABORT
302        {
303            let [_, invoke_id, third, ..] = *apdu else {
304                return Err(Error::buffer_too_short(3, apdu.len()));
305            };
306            let header = if pdu_type == PduType::ERROR {
307                Self::Error {
308                    invoke_id,
309                    service_choice: ConfirmedServiceChoice::from_raw(third),
310                }
311            } else if pdu_type == PduType::REJECT {
312                Self::Reject {
313                    invoke_id,
314                    reason: RejectReason::from_raw(third),
315                }
316            } else {
317                Self::Abort {
318                    server: first & 0x01 != 0,
319                    invoke_id,
320                    reason: AbortReason::from_raw(third),
321                }
322            };
323            Ok((header, &apdu[3..]))
324        } else if first >> 4 <= 7 {
325            Err(DecodeError::new(
326                DecodingKind::Unsupported,
327                0,
328                "PDU type {} carries no service parameters this header reads",
329            )
330            .with_value(u64::from(first >> 4))
331            .into())
332        } else {
333            Err(DecodeError::new(
334                DecodingKind::InvalidEncoding,
335                0,
336                "unknown PDU type nibble: {:#x}",
337            )
338            .with_value(u64::from(first >> 4))
339            .into())
340        }
341    }
342
343    /// The header's length in octets: 4 for a Confirmed-Request, 2 for an
344    /// Unconfirmed-Request and 3 for the rest.
345    pub const fn encoded_len(&self) -> usize {
346        match self {
347            Self::ConfirmedRequest(_) => 4,
348            Self::UnconfirmedRequest(_) => 2,
349            Self::ComplexAck { .. }
350            | Self::Error { .. }
351            | Self::Reject { .. }
352            | Self::Abort { .. } => 3,
353        }
354    }
355
356    /// The header's octets, in a buffer whose first
357    /// [`encoded_len`](Self::encoded_len) octets hold them.
358    fn octets(&self) -> [u8; MAX_HEADER] {
359        match *self {
360            Self::ConfirmedRequest(header) => [
361                (PduType::CONFIRMED_REQUEST.to_raw() << 4)
362                    | (u8::from(header.segmented_response_accepted) << 1),
363                (header.max_segments.bits() << 4) | header.max_apdu.bits(),
364                header.invoke_id,
365                header.service_choice.to_raw(),
366            ],
367            Self::UnconfirmedRequest(service) => [
368                PduType::UNCONFIRMED_REQUEST.to_raw() << 4,
369                service.to_raw(),
370                0,
371                0,
372            ],
373            Self::ComplexAck {
374                invoke_id,
375                service_choice,
376            } => [
377                PduType::COMPLEX_ACK.to_raw() << 4,
378                invoke_id,
379                service_choice.to_raw(),
380                0,
381            ],
382            Self::Error {
383                invoke_id,
384                service_choice,
385            } => [
386                PduType::ERROR.to_raw() << 4,
387                invoke_id,
388                service_choice.to_raw(),
389                0,
390            ],
391            Self::Reject { invoke_id, reason } => {
392                [PduType::REJECT.to_raw() << 4, invoke_id, reason.to_raw(), 0]
393            }
394            Self::Abort {
395                server,
396                invoke_id,
397                reason,
398            } => [
399                (PduType::ABORT.to_raw() << 4) | u8::from(server),
400                invoke_id,
401                reason.to_raw(),
402                0,
403            ],
404        }
405    }
406
407    /// Append the header in one write; returns the octets appended.
408    pub fn encode_to_sink<S: EncodeSink + ?Sized>(&self, sink: &mut S) -> Result<usize, SinkFull> {
409        self.encode_with_parameters_to_sink(sink, &[])
410    }
411
412    /// Append the header and the service `parameters` after it as one value
413    /// ([`EncodeSink::write_parts`]); returns the octets appended.
414    pub fn encode_with_parameters_to_sink<S: EncodeSink + ?Sized>(
415        &self,
416        sink: &mut S,
417        parameters: &[u8],
418    ) -> Result<usize, SinkFull> {
419        let octets = self.octets();
420        let header = &octets[..self.encoded_len()];
421        sink.write_parts(&[header, parameters])?;
422        Ok(header.len() + parameters.len())
423    }
424}
425
426/// The refusal of a segmented PDU, which this header doesn't reassemble.
427#[cold]
428fn segmented_refusal(label: &'static str) -> Error {
429    DecodeError::new(DecodingKind::Unsupported, 0, label)
430        .within("APDU header")
431        .into()
432}
433
434#[cfg(test)]
435mod tests;