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