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