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;