Skip to main content

bacnet_encoding/apdu/
mod.rs

1//! APDU encoding and decoding per ASHRAE 135-2020 Clause 20.1.
2//!
3//! Covers all eight PDU types:
4//! - `ConfirmedRequest` (Clause 20.1.2)
5//! - `UnconfirmedRequest` (Clause 20.1.3)
6//! - `SimpleAck` (Clause 20.1.4)
7//! - `ComplexAck` (Clause 20.1.5)
8//! - `SegmentAck` (Clause 20.1.6)
9//! - `ErrorPdu` (Clause 20.1.7)
10//! - `RejectPdu` (Clause 20.1.8)
11//! - `AbortPdu` (Clause 20.1.9)
12//!
13//! Those owned PDUs need `alloc`. Without it, [`header`] reads and writes the
14//! header of an Unconfirmed-Request, an unsegmented Confirmed-Request or an
15//! unsegmented ComplexACK, borrowing the service parameters in place.
16
17#[cfg(feature = "alloc")]
18use bacnet_types::enums::{
19    AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, PduType, RejectReason,
20    UnconfirmedServiceChoice,
21};
22#[cfg(feature = "alloc")]
23use bacnet_types::error::{DecodeError, DecodingKind, Error, RangeError};
24#[cfg(feature = "alloc")]
25use bytes::{BufMut, Bytes, BytesMut};
26
27#[cfg(feature = "alloc")]
28use crate::primitives;
29#[cfg(feature = "alloc")]
30use crate::tags;
31
32#[cfg(feature = "alloc")]
33mod formal_error;
34pub mod header;
35#[cfg(feature = "alloc")]
36mod remote_error;
37
38use header::{MaxApdu, MaxSegments};
39
40// ---------------------------------------------------------------------------
41// Max-segments encoding
42// ---------------------------------------------------------------------------
43
44/// Validate a configured maximum segment count for a Confirmed-Request header.
45///
46/// Clause 20.1.2.4 has no encoding for a finite capacity below two segments.
47/// To decline segmented responses, set `segmented-response-accepted = false`
48/// and use `None` rather than `Some(1)`. Only with `alloc`;
49/// [`MaxSegments::from_capacity`] is the allocation-free check.
50#[cfg(feature = "alloc")]
51pub fn validate_max_segments(value: Option<u8>) -> Result<(), Error> {
52    encode_max_segments(value).map(drop)
53}
54
55/// Encode a max-segments value to a 3-bit field.
56///
57/// Finite counts that fall between the Clause 20.1.2.4 rungs are rounded down
58/// so the wire header never promises more receive capacity than configured.
59#[cfg(feature = "alloc")]
60fn encode_max_segments(value: Option<u8>) -> Result<u8, Error> {
61    match MaxSegments::from_capacity(value) {
62        Some(field) => Ok(field.bits()),
63        None => Err(RangeError::new(
64            "invalid max-segments-accepted {}; expected 2..=255 or unspecified",
65        )
66        .with_value(value.unwrap_or(0).into())
67        .into()),
68    }
69}
70
71/// Decode a 3-bit max-segments field.
72#[cfg(feature = "alloc")]
73fn decode_max_segments(value: u8) -> Option<u8> {
74    MaxSegments::from_bits(value).count()
75}
76
77/// The segment count a configured `max-segments-accepted` actually promises a peer.
78///
79/// Clause 20.1.2.4 carries this parameter in three bits, and only B'001'
80/// through B'110' name a number (2, 4, 8, 16, 32, 64). B'000' leaves capacity
81/// unspecified; B'111' advertises capacity above 64 without an upper bound.
82/// Neither tells the peer a limit, so both yield `None`.
83///
84/// This deliberately round-trips through the wire encoding rather than reading
85/// `configured` directly: a value such as `Some(100)` encodes as B'111', so the
86/// peer was told only that capacity exceeds 64, not that it is 100. Answering
87/// from the configured number would claim a promise that was never sent.
88/// Invalid finite capacities below two yield `None`; APDU encoding and client
89/// startup reject those values through `validate_max_segments`.
90pub fn advertised_max_segments(configured: Option<u8>) -> Option<u8> {
91    match MaxSegments::from_capacity(configured)?.count() {
92        // B'111' advertises capacity above 64, not a specific upper bound.
93        Some(255) => None,
94        decoded => decoded,
95    }
96}
97
98// ---------------------------------------------------------------------------
99// Max-APDU-length encoding
100// ---------------------------------------------------------------------------
101
102/// MinimumMessageSize: the smallest APDU any BACnet device accepts.
103///
104/// Clause 20.1.2.5 assigns the lowest max-APDU-length-accepted code, `B'0000'`,
105/// to the 50-octet MinimumMessageSize capacity. Clause 12.11.18 sets the same
106/// floor for `Max_APDU_Length_Accepted`, as does Clause 5.2.1.2 for a remote
107/// peer's receive capacity.
108pub const MINIMUM_MESSAGE_SIZE: u16 = 50;
109
110/// Floor a raw local receive capacity to a Confirmed-Request header value.
111///
112/// Device `Max_APDU_Length_Accepted` and I-Am retain the raw unsigned value.
113/// Only the Clause 20.1.2.5 header uses this six-code representation; flooring
114/// must not change byte budgets or the Device declaration. Values below
115/// MinimumMessageSize are invalid, while larger values saturate at 1476.
116/// Only with `alloc`; [`MaxApdu::at_or_below`] is the allocation-free form.
117#[cfg(feature = "alloc")]
118pub fn max_apdu_header_at_or_below(capacity: u32) -> Result<u16, Error> {
119    MaxApdu::at_or_below(capacity)
120        .map(MaxApdu::octets)
121        .ok_or_else(|| {
122            RangeError::new("local APDU capacity {} is below 50")
123                .with_value(capacity.into())
124                .into()
125        })
126}
127
128/// Return true when `value` is one of the BACnet max-APDU-length encodings
129/// defined by ASHRAE 135-2020 Clause 20.1.2.5.
130pub fn is_valid_max_apdu_length(value: u16) -> bool {
131    MaxApdu::from_octets(value).is_some()
132}
133
134/// Validate an exact Confirmed-Request max-APDU-length header value. Only
135/// with `alloc`.
136#[cfg(feature = "alloc")]
137pub fn validate_max_apdu_length(value: u16) -> Result<(), Error> {
138    encode_max_apdu(value).map(drop)
139}
140
141/// Encode a max-APDU-length to a 4-bit field.
142#[cfg(feature = "alloc")]
143fn encode_max_apdu(value: u16) -> Result<u8, Error> {
144    match MaxApdu::from_octets(value) {
145        Some(field) => Ok(field.bits()),
146        None => Err(RangeError::new(
147            "invalid max-APDU-length {}; expected one of 50, 128, 206, 480, 1024, 1476",
148        )
149        .with_value(value.into())
150        .into()),
151    }
152}
153
154/// Decode a 4-bit max-APDU-length field.
155#[cfg(feature = "alloc")]
156fn decode_max_apdu(value: u8) -> Result<u16, Error> {
157    match MaxApdu::from_bits(value) {
158        Some(field) => Ok(field.octets()),
159        None => Err(DecodeError::new(
160            DecodingKind::InvalidEncoding,
161            1,
162            "reserved max-APDU-length field value {}",
163        )
164        .with_value(u64::from(value & 0x0F))
165        .into()),
166    }
167}
168
169// ---------------------------------------------------------------------------
170// PDU structs
171// ---------------------------------------------------------------------------
172
173/// Confirmed-Request PDU (Clause 20.1.2).
174#[derive(Debug, Clone, PartialEq, Eq)]
175#[cfg(feature = "alloc")]
176pub struct ConfirmedRequest {
177    /// Set when this PDU is one segment of a segmented request; adds the sequence and window
178    /// fields.
179    pub segmented: bool,
180    /// Set on every segment except the last one of a segmented request.
181    pub more_follows: bool,
182    /// Whether the requester can receive the reply as a segmented message.
183    pub segmented_response_accepted: bool,
184    /// Most segments the requester will accept in a reply; `None` leaves the limit unspecified.
185    /// Must be `None` or at least 2; the 3-bit wire field rounds down to 2, 4, 8, 16, 32 or 64, and
186    /// anything above 64 encodes as "more than 64".
187    pub max_segments: Option<u8>,
188    /// Largest APDU, in octets, the requester can receive; must be 50, 128, 206, 480, 1024 or 1476.
189    pub max_apdu_length: u16,
190    /// Identifier the requester uses to match the reply to this request.
191    pub invoke_id: u8,
192    /// Segment number within a segmented request; `None` for unsegmented PDUs. `None` on a
193    /// segmented PDU encodes as 0.
194    pub sequence_number: Option<u8>,
195    /// Proposed window: how many segments the sender will transmit per SegmentACK (1..=127);
196    /// `None` for unsegmented PDUs. `None` on a segmented PDU encodes as 1.
197    pub proposed_window_size: Option<u8>,
198    /// Which confirmed service is being requested.
199    pub service_choice: ConfirmedServiceChoice,
200    /// Raw encoded service request parameters (the bytes after the header).
201    pub service_request: Bytes,
202}
203
204/// Unconfirmed-Request PDU (Clause 20.1.3).
205#[derive(Debug, Clone, PartialEq, Eq)]
206#[cfg(feature = "alloc")]
207pub struct UnconfirmedRequest {
208    /// Which unconfirmed service this PDU carries.
209    pub service_choice: UnconfirmedServiceChoice,
210    /// Raw encoded service request parameters.
211    pub service_request: Bytes,
212}
213
214/// SimpleACK PDU (Clause 20.1.4).
215#[derive(Debug, Clone, PartialEq, Eq)]
216#[cfg(feature = "alloc")]
217pub struct SimpleAck {
218    /// Invoke ID of the confirmed request being acknowledged.
219    pub invoke_id: u8,
220    /// Confirmed service being acknowledged.
221    pub service_choice: ConfirmedServiceChoice,
222}
223
224/// ComplexACK PDU (Clause 20.1.5).
225#[derive(Debug, Clone, PartialEq, Eq)]
226#[cfg(feature = "alloc")]
227pub struct ComplexAck {
228    /// Set when this PDU is one segment of a segmented reply; adds the sequence and window fields.
229    pub segmented: bool,
230    /// Set on every segment except the last one of a segmented reply.
231    pub more_follows: bool,
232    /// Invoke ID of the confirmed request this reply answers.
233    pub invoke_id: u8,
234    /// Segment number within a segmented reply; `None` for unsegmented PDUs. `None` on a
235    /// segmented PDU encodes as 0.
236    pub sequence_number: Option<u8>,
237    /// Proposed window: how many segments the sender will transmit per SegmentACK (1..=127);
238    /// `None` for unsegmented PDUs. `None` on a segmented PDU encodes as 1.
239    pub proposed_window_size: Option<u8>,
240    /// Confirmed service this reply belongs to.
241    pub service_choice: ConfirmedServiceChoice,
242    /// Raw encoded service acknowledgment parameters.
243    pub service_ack: Bytes,
244}
245
246/// SegmentACK PDU (Clause 20.1.6).
247#[derive(Debug, Clone, PartialEq, Eq)]
248#[cfg(feature = "alloc")]
249pub struct SegmentAck {
250    /// Set when a segment arrived out of order; the peer resends starting with the segment after
251    /// `sequence_number`.
252    pub negative_ack: bool,
253    /// Set when the SegmentACK comes from the server side (the device answering a request), clear
254    /// when sent by the requester.
255    pub sent_by_server: bool,
256    /// Invoke ID of the segmented transaction being acknowledged.
257    pub invoke_id: u8,
258    /// Last segment received in order. It and every earlier segment are acknowledged; the peer
259    /// continues (or, on a negative ack, resends) from the next one, modulo 256.
260    pub sequence_number: u8,
261    /// Window size, in segments (1..=127), the receiver will accept from now on.
262    pub actual_window_size: u8,
263}
264
265/// Error PDU (Clause 20.1.7).
266#[derive(Debug, Clone, PartialEq, Eq)]
267#[cfg(feature = "alloc")]
268pub struct ErrorPdu {
269    /// Invoke ID of the confirmed request that failed.
270    pub invoke_id: u8,
271    /// Confirmed service that produced the error.
272    pub service_choice: ConfirmedServiceChoice,
273    /// Error class from the standard Error production.
274    pub error_class: ErrorClass,
275    /// Error code from the standard Error production.
276    pub error_code: ErrorCode,
277    /// Service-specific bytes following the error class and code; empty for a plain class/code
278    /// error. For a service whose Clause 21 production replaces the plain pair
279    /// (WritePropertyMultiple-Error, ChangeList-Error, CreateObject-Error,
280    /// SubscribeCOVPropertyMultiple-Error, ConfirmedPrivateTransfer-Error, VTClose-Error) it
281    /// holds the whole error body when that decodes in the formal form; a legacy class/code
282    /// error from those services keeps only the bytes after the code.
283    pub error_data: Bytes,
284}
285
286/// Reject PDU (Clause 20.1.8).
287#[derive(Debug, Clone, PartialEq, Eq)]
288#[cfg(feature = "alloc")]
289pub struct RejectPdu {
290    /// Invoke ID of the rejected request.
291    pub invoke_id: u8,
292    /// Why the request was rejected as malformed or unsupported.
293    pub reject_reason: RejectReason,
294}
295
296/// Abort PDU (Clause 20.1.9).
297#[derive(Debug, Clone, PartialEq, Eq)]
298#[cfg(feature = "alloc")]
299pub struct AbortPdu {
300    /// Set when the abort comes from the server side of the transaction, clear when sent by the
301    /// requester.
302    pub sent_by_server: bool,
303    /// Invoke ID of the transaction this PDU ends.
304    pub invoke_id: u8,
305    /// Why the transaction was aborted.
306    pub abort_reason: AbortReason,
307}
308
309/// Sum type for all APDU PDU types.
310#[derive(Debug, Clone, PartialEq, Eq)]
311#[cfg(feature = "alloc")]
312pub enum Apdu {
313    /// Confirmed service request.
314    ConfirmedRequest(ConfirmedRequest),
315    /// Unconfirmed service request.
316    UnconfirmedRequest(UnconfirmedRequest),
317    /// Acknowledgment carrying no result data.
318    SimpleAck(SimpleAck),
319    /// Acknowledgment carrying result data, possibly segmented.
320    ComplexAck(ComplexAck),
321    /// Flow-control acknowledgment of received segments.
322    SegmentAck(SegmentAck),
323    /// Service failure reported with an error class and code.
324    Error(ErrorPdu),
325    /// Request refused before execution.
326    Reject(RejectPdu),
327    /// Transaction terminated early.
328    Abort(AbortPdu),
329}
330
331// ---------------------------------------------------------------------------
332// Encoding
333// ---------------------------------------------------------------------------
334
335/// Encode an APDU to wire format.
336#[cfg(feature = "alloc")]
337pub fn encode_apdu(buf: &mut BytesMut, apdu: &Apdu) -> Result<(), Error> {
338    match apdu {
339        Apdu::ConfirmedRequest(pdu) => encode_confirmed_request(buf, pdu),
340        Apdu::UnconfirmedRequest(pdu) => {
341            encode_unconfirmed_request(buf, pdu);
342            Ok(())
343        }
344        Apdu::SimpleAck(pdu) => {
345            encode_simple_ack(buf, pdu);
346            Ok(())
347        }
348        Apdu::ComplexAck(pdu) => encode_complex_ack(buf, pdu),
349        Apdu::SegmentAck(pdu) => encode_segment_ack(buf, pdu),
350        Apdu::Error(pdu) => encode_error(buf, pdu),
351        Apdu::Reject(pdu) => {
352            encode_reject(buf, pdu);
353            Ok(())
354        }
355        Apdu::Abort(pdu) => {
356            encode_abort(buf, pdu);
357            Ok(())
358        }
359    }
360}
361
362#[cfg(feature = "alloc")]
363fn encode_confirmed_request(buf: &mut BytesMut, pdu: &ConfirmedRequest) -> Result<(), Error> {
364    // Every refusal comes before the first octet, so a refused APDU leaves
365    // `buf` as it was.
366    let max_segments = encode_max_segments(pdu.max_segments)?;
367    let max_apdu = encode_max_apdu(pdu.max_apdu_length)?;
368    let window = pdu
369        .segmented
370        .then(|| {
371            valid_window_size(
372                "ConfirmedRequest proposed-window-size {} outside BACnet range 1..=127",
373                pdu.proposed_window_size.unwrap_or(1),
374            )
375        })
376        .transpose()?;
377
378    let mut byte0 = PduType::CONFIRMED_REQUEST.to_raw() << 4;
379    if pdu.segmented {
380        byte0 |= 0x08;
381    }
382    if pdu.more_follows {
383        byte0 |= 0x04;
384    }
385    if pdu.segmented_response_accepted {
386        byte0 |= 0x02;
387    }
388    buf.put_u8(byte0);
389
390    let byte1 = (max_segments << 4) | max_apdu;
391    buf.put_u8(byte1);
392
393    buf.put_u8(pdu.invoke_id);
394
395    if let Some(window) = window {
396        buf.put_u8(pdu.sequence_number.unwrap_or(0));
397        buf.put_u8(window);
398    }
399
400    buf.put_u8(pdu.service_choice.to_raw());
401    buf.put_slice(&pdu.service_request);
402    Ok(())
403}
404
405#[cfg(feature = "alloc")]
406fn encode_unconfirmed_request(buf: &mut BytesMut, pdu: &UnconfirmedRequest) {
407    buf.put_u8(PduType::UNCONFIRMED_REQUEST.to_raw() << 4);
408    buf.put_u8(pdu.service_choice.to_raw());
409    buf.put_slice(&pdu.service_request);
410}
411
412#[cfg(feature = "alloc")]
413fn encode_simple_ack(buf: &mut BytesMut, pdu: &SimpleAck) {
414    buf.put_u8(PduType::SIMPLE_ACK.to_raw() << 4);
415    buf.put_u8(pdu.invoke_id);
416    buf.put_u8(pdu.service_choice.to_raw());
417}
418
419#[cfg(feature = "alloc")]
420fn encode_complex_ack(buf: &mut BytesMut, pdu: &ComplexAck) -> Result<(), Error> {
421    // Refused before the first octet, so `buf` stays as it was.
422    let window = pdu
423        .segmented
424        .then(|| {
425            valid_window_size(
426                "ComplexAck proposed-window-size {} outside BACnet range 1..=127",
427                pdu.proposed_window_size.unwrap_or(1),
428            )
429        })
430        .transpose()?;
431
432    let mut byte0 = PduType::COMPLEX_ACK.to_raw() << 4;
433    if pdu.segmented {
434        byte0 |= 0x08;
435    }
436    if pdu.more_follows {
437        byte0 |= 0x04;
438    }
439    buf.put_u8(byte0);
440
441    buf.put_u8(pdu.invoke_id);
442
443    if let Some(window) = window {
444        buf.put_u8(pdu.sequence_number.unwrap_or(0));
445        buf.put_u8(window);
446    }
447
448    buf.put_u8(pdu.service_choice.to_raw());
449    buf.put_slice(&pdu.service_ack);
450    Ok(())
451}
452
453#[cfg(feature = "alloc")]
454fn encode_segment_ack(buf: &mut BytesMut, pdu: &SegmentAck) -> Result<(), Error> {
455    // Refused before the first octet, so `buf` stays as it was.
456    let window = valid_window_size(
457        "SegmentACK actual-window-size {} outside BACnet range 1..=127",
458        pdu.actual_window_size,
459    )?;
460    let mut byte0 = PduType::SEGMENT_ACK.to_raw() << 4;
461    if pdu.negative_ack {
462        byte0 |= 0x02;
463    }
464    if pdu.sent_by_server {
465        byte0 |= 0x01;
466    }
467    buf.put_u8(byte0);
468    buf.put_u8(pdu.invoke_id);
469    buf.put_u8(pdu.sequence_number);
470    buf.put_u8(window);
471    Ok(())
472}
473
474/// `value` when it is a window size (1 to 127), or the [`Error::Range`]
475/// `label` names it in, with a placeholder for the value.
476#[cfg(feature = "alloc")]
477fn valid_window_size(label: &'static str, value: u8) -> Result<u8, Error> {
478    if (1..=127).contains(&value) {
479        Ok(value)
480    } else {
481        Err(RangeError::new(label).with_value(value.into()).into())
482    }
483}
484
485#[cfg(feature = "alloc")]
486fn encode_error(buf: &mut BytesMut, pdu: &ErrorPdu) -> Result<(), Error> {
487    // A legacy generic error may carry arbitrary service data beginning with
488    // an opening tag. Only suppress the generic pair for a complete,
489    // structurally valid formal body of a service that defines one.
490    let formal =
491        formal_error::decode_formal_body(pdu.service_choice, &pdu.error_data).unwrap_or(None);
492    if let Some((error_class, error_code)) = formal
493        && (error_class != pdu.error_class || error_code != pdu.error_code)
494    {
495        return Err(Error::encode(
496            "formal Error body disagrees with ErrorPdu class/code",
497        ));
498    }
499
500    buf.put_u8(PduType::ERROR.to_raw() << 4);
501    buf.put_u8(pdu.invoke_id);
502    buf.put_u8(pdu.service_choice.to_raw());
503    if formal.is_some() {
504        buf.put_slice(&pdu.error_data);
505    } else {
506        primitives::encode_app_enumerated(buf, u32::from(pdu.error_class.to_raw()));
507        primitives::encode_app_enumerated(buf, u32::from(pdu.error_code.to_raw()));
508        if !pdu.error_data.is_empty() {
509            buf.put_slice(&pdu.error_data);
510        }
511    }
512    Ok(())
513}
514
515#[cfg(feature = "alloc")]
516fn encode_reject(buf: &mut BytesMut, pdu: &RejectPdu) {
517    buf.put_u8(PduType::REJECT.to_raw() << 4);
518    buf.put_u8(pdu.invoke_id);
519    buf.put_u8(pdu.reject_reason.to_raw());
520}
521
522#[cfg(feature = "alloc")]
523fn encode_abort(buf: &mut BytesMut, pdu: &AbortPdu) {
524    let mut byte0 = PduType::ABORT.to_raw() << 4;
525    if pdu.sent_by_server {
526        byte0 |= 0x01;
527    }
528    buf.put_u8(byte0);
529    buf.put_u8(pdu.invoke_id);
530    buf.put_u8(pdu.abort_reason.to_raw());
531}
532
533// ---------------------------------------------------------------------------
534// Decoding
535// ---------------------------------------------------------------------------
536
537/// Decode an APDU from raw bytes.
538#[cfg(feature = "alloc")]
539pub fn decode_apdu(data: Bytes) -> Result<Apdu, Error> {
540    if data.is_empty() {
541        return Err(Error::decoding(0, "APDU data is empty"));
542    }
543
544    let pdu_type_raw = (data[0] >> 4) & 0x0F;
545    let pdu_type = PduType::from_raw(pdu_type_raw);
546
547    if pdu_type == PduType::CONFIRMED_REQUEST {
548        decode_confirmed_request(data).map(Apdu::ConfirmedRequest)
549    } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
550        decode_unconfirmed_request(data).map(Apdu::UnconfirmedRequest)
551    } else if pdu_type == PduType::SIMPLE_ACK {
552        decode_simple_ack(data).map(Apdu::SimpleAck)
553    } else if pdu_type == PduType::COMPLEX_ACK {
554        decode_complex_ack(data).map(Apdu::ComplexAck)
555    } else if pdu_type == PduType::SEGMENT_ACK {
556        decode_segment_ack(data).map(Apdu::SegmentAck)
557    } else if pdu_type == PduType::ERROR {
558        decode_error(data).map(Apdu::Error)
559    } else if pdu_type == PduType::REJECT {
560        decode_reject(data).map(Apdu::Reject)
561    } else if pdu_type == PduType::ABORT {
562        decode_abort(data).map(Apdu::Abort)
563    } else {
564        Err(DecodeError::new(
565            DecodingKind::InvalidEncoding,
566            0,
567            "unknown PDU type nibble: {:#x}",
568        )
569        .with_value(pdu_type_raw.into())
570        .into())
571    }
572}
573
574#[cfg(feature = "alloc")]
575fn decode_confirmed_request(data: Bytes) -> Result<ConfirmedRequest, Error> {
576    if data.len() < 4 {
577        return Err(Error::buffer_too_short(4, data.len()));
578    }
579
580    let byte0 = data[0];
581    let segmented = byte0 & 0x08 != 0;
582    let more_follows = byte0 & 0x04 != 0;
583    let segmented_response_accepted = byte0 & 0x02 != 0;
584
585    let byte1 = data[1];
586    let max_segments = decode_max_segments((byte1 >> 4) & 0x07);
587    let max_apdu_length = decode_max_apdu(byte1 & 0x0F)?;
588
589    let invoke_id = data[2];
590    let mut offset = 3;
591
592    let (sequence_number, proposed_window_size) = if segmented {
593        if data.len() < 6 {
594            return Err(Error::decoding(
595                offset,
596                "segmented ConfirmedRequest too short for sequence/window fields",
597            ));
598        }
599        let seq = data[offset];
600        let win = data[offset + 1];
601        offset += 2;
602        (Some(seq), Some(win))
603    } else {
604        (None, None)
605    };
606
607    if offset >= data.len() {
608        return Err(Error::decoding(
609            offset,
610            "ConfirmedRequest missing service choice",
611        ));
612    }
613    let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
614    offset += 1;
615
616    let service_request = data.slice(offset..);
617
618    Ok(ConfirmedRequest {
619        segmented,
620        more_follows,
621        segmented_response_accepted,
622        max_segments,
623        max_apdu_length,
624        invoke_id,
625        sequence_number,
626        proposed_window_size,
627        service_choice,
628        service_request,
629    })
630}
631
632#[cfg(feature = "alloc")]
633fn decode_unconfirmed_request(data: Bytes) -> Result<UnconfirmedRequest, Error> {
634    if data.len() < 2 {
635        return Err(Error::buffer_too_short(2, data.len()));
636    }
637
638    let service_choice = UnconfirmedServiceChoice::from_raw(data[1]);
639    let service_request = data.slice(2..);
640
641    Ok(UnconfirmedRequest {
642        service_choice,
643        service_request,
644    })
645}
646
647#[cfg(feature = "alloc")]
648fn decode_simple_ack(data: Bytes) -> Result<SimpleAck, Error> {
649    if data.len() < 3 {
650        return Err(Error::buffer_too_short(3, data.len()));
651    }
652
653    Ok(SimpleAck {
654        invoke_id: data[1],
655        service_choice: ConfirmedServiceChoice::from_raw(data[2]),
656    })
657}
658
659#[cfg(feature = "alloc")]
660fn decode_complex_ack(data: Bytes) -> Result<ComplexAck, Error> {
661    if data.len() < 3 {
662        return Err(Error::buffer_too_short(3, data.len()));
663    }
664
665    let byte0 = data[0];
666    let segmented = byte0 & 0x08 != 0;
667    let more_follows = byte0 & 0x04 != 0;
668
669    let invoke_id = data[1];
670    let mut offset = 2;
671
672    let (sequence_number, proposed_window_size) = if segmented {
673        if data.len() < 5 {
674            return Err(Error::decoding(
675                offset,
676                "segmented ComplexAck too short for sequence/window fields",
677            ));
678        }
679        let seq = data[offset];
680        let win = data[offset + 1];
681        offset += 2;
682        (Some(seq), Some(win))
683    } else {
684        (None, None)
685    };
686
687    if offset >= data.len() {
688        return Err(Error::decoding(offset, "ComplexAck missing service choice"));
689    }
690    let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
691    offset += 1;
692
693    let service_ack = data.slice(offset..);
694
695    Ok(ComplexAck {
696        segmented,
697        more_follows,
698        invoke_id,
699        sequence_number,
700        proposed_window_size,
701        service_choice,
702        service_ack,
703    })
704}
705
706#[cfg(feature = "alloc")]
707fn decode_segment_ack(data: Bytes) -> Result<SegmentAck, Error> {
708    if data.len() < 4 {
709        return Err(Error::buffer_too_short(4, data.len()));
710    }
711
712    let byte0 = data[0];
713    let raw_window = data[3];
714    if raw_window == 0 || raw_window > 127 {
715        return Err(DecodeError::new(
716            DecodingKind::InvalidEncoding,
717            3,
718            "SegmentACK actual-window-size {} outside range 1..=127",
719        )
720        .with_value(raw_window.into())
721        .into());
722    }
723    Ok(SegmentAck {
724        negative_ack: byte0 & 0x02 != 0,
725        sent_by_server: byte0 & 0x01 != 0,
726        invoke_id: data[1],
727        sequence_number: data[2],
728        actual_window_size: raw_window,
729    })
730}
731
732#[cfg(feature = "alloc")]
733fn decode_error(data: Bytes) -> Result<ErrorPdu, Error> {
734    if data.len() < 5 {
735        return Err(Error::buffer_too_short(5, data.len()));
736    }
737
738    let invoke_id = data[1];
739    let service_choice = ConfirmedServiceChoice::from_raw(data[2]);
740
741    if let Some((error_class, error_code)) =
742        formal_error::decode_formal_body(service_choice, &data[3..])?
743    {
744        return Ok(ErrorPdu {
745            invoke_id,
746            service_choice,
747            error_class,
748            error_code,
749            error_data: data.slice(3..),
750        });
751    }
752
753    let mut offset = 3;
754    let (tag, tag_end) = tags::decode_tag(&data, offset)?;
755    if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
756        return Err(Error::decoding(
757            offset,
758            "ErrorPDU error class: expected application-tagged enumerated",
759        ));
760    }
761    let class_end = tag_end
762        .checked_add(tag.length as usize)
763        .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error class length overflow"))?;
764    if class_end > data.len() {
765        return Err(Error::buffer_too_short(class_end, data.len()));
766    }
767    let error_class_raw = primitives::decode_unsigned(&data[tag_end..class_end])?;
768    let error_class_raw = u16::try_from(error_class_raw).map_err(|_| {
769        DecodeError::new(
770            DecodingKind::OutOfRange,
771            tag_end,
772            "ErrorPDU error class {} exceeds u16",
773        )
774        .with_value(error_class_raw)
775    })?;
776    offset = class_end;
777
778    let (tag, tag_end) = tags::decode_tag(&data, offset)?;
779    if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
780        return Err(Error::decoding(
781            offset,
782            "ErrorPDU error code: expected application-tagged enumerated",
783        ));
784    }
785    let code_end = tag_end
786        .checked_add(tag.length as usize)
787        .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error code length overflow"))?;
788    if code_end > data.len() {
789        return Err(Error::buffer_too_short(code_end, data.len()));
790    }
791    let error_code_raw = primitives::decode_unsigned(&data[tag_end..code_end])?;
792    let error_code_raw = u16::try_from(error_code_raw).map_err(|_| {
793        DecodeError::new(
794            DecodingKind::OutOfRange,
795            tag_end,
796            "ErrorPDU error code {} exceeds u16",
797        )
798        .with_value(error_code_raw)
799    })?;
800    offset = code_end;
801
802    let error_data = if offset < data.len() {
803        data.slice(offset..)
804    } else {
805        Bytes::new()
806    };
807
808    Ok(ErrorPdu {
809        invoke_id,
810        service_choice,
811        error_class: ErrorClass::from_raw(error_class_raw),
812        error_code: ErrorCode::from_raw(error_code_raw),
813        error_data,
814    })
815}
816
817#[cfg(feature = "alloc")]
818fn decode_reject(data: Bytes) -> Result<RejectPdu, Error> {
819    if data.len() < 3 {
820        return Err(Error::buffer_too_short(3, data.len()));
821    }
822
823    Ok(RejectPdu {
824        invoke_id: data[1],
825        reject_reason: RejectReason::from_raw(data[2]),
826    })
827}
828
829#[cfg(feature = "alloc")]
830fn decode_abort(data: Bytes) -> Result<AbortPdu, Error> {
831    if data.len() < 3 {
832        return Err(Error::buffer_too_short(3, data.len()));
833    }
834
835    let byte0 = data[0];
836    Ok(AbortPdu {
837        sent_by_server: byte0 & 0x01 != 0,
838        invoke_id: data[1],
839        abort_reason: AbortReason::from_raw(data[2]),
840    })
841}
842
843// ---------------------------------------------------------------------------
844// Tests
845// ---------------------------------------------------------------------------
846
847#[cfg(all(test, feature = "alloc"))]
848mod change_list_error_tests;
849#[cfg(all(test, feature = "alloc"))]
850mod encode_refusal_tests;
851#[cfg(all(test, feature = "alloc"))]
852mod remote_error_tests;
853#[cfg(all(test, feature = "alloc"))]
854mod structured_error_tests;
855#[cfg(all(test, feature = "alloc"))]
856mod tests;
857#[cfg(all(test, feature = "alloc"))]
858mod wpm_error_tests;