Skip to main content

bacnet_encoding/
apdu.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
23// ---------------------------------------------------------------------------
24// Max-segments encoding
25// ---------------------------------------------------------------------------
26
27/// Decoded max-segments values indexed by the 3-bit field (0-7).
28/// `None` means unspecified (0).
29const MAX_SEGMENTS_DECODE: [Option<u8>; 8] = [
30    None,      // 0 = unspecified
31    Some(2),   // 1
32    Some(4),   // 2
33    Some(8),   // 3
34    Some(16),  // 4
35    Some(32),  // 5
36    Some(64),  // 6
37    Some(255), // 7 = >64 segments accepted
38];
39
40/// Encode a max-segments value to a 3-bit field.
41fn encode_max_segments(value: Option<u8>) -> u8 {
42    match value {
43        None => 0,
44        Some(2) => 1,
45        Some(4) => 2,
46        Some(8) => 3,
47        Some(16) => 4,
48        Some(32) => 5,
49        Some(64) => 6,
50        Some(_) => 7, // >64
51    }
52}
53
54/// Decode a 3-bit max-segments field.
55fn decode_max_segments(value: u8) -> Option<u8> {
56    MAX_SEGMENTS_DECODE[(value & 0x07) as usize]
57}
58
59// ---------------------------------------------------------------------------
60// Max-APDU-length encoding
61// ---------------------------------------------------------------------------
62
63/// Decoded max-APDU-length values indexed by the 4-bit field.
64const MAX_APDU_DECODE: [u16; 6] = [50, 128, 206, 480, 1024, 1476];
65
66/// Encode a max-APDU-length to a 4-bit field.
67fn encode_max_apdu(value: u16) -> u8 {
68    match value {
69        50 => 0,
70        128 => 1,
71        206 => 2,
72        480 => 3,
73        1024 => 4,
74        _ => 5, // 1476 (default)
75    }
76}
77
78/// Decode a 4-bit max-APDU-length field.
79fn decode_max_apdu(value: u8) -> u16 {
80    let idx = (value & 0x0F) as usize;
81    if idx < MAX_APDU_DECODE.len() {
82        MAX_APDU_DECODE[idx]
83    } else {
84        1476
85    }
86}
87
88// ---------------------------------------------------------------------------
89// PDU structs
90// ---------------------------------------------------------------------------
91
92/// Confirmed-Request PDU (Clause 20.1.2).
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct ConfirmedRequest {
95    pub segmented: bool,
96    pub more_follows: bool,
97    pub segmented_response_accepted: bool,
98    pub max_segments: Option<u8>,
99    pub max_apdu_length: u16,
100    pub invoke_id: u8,
101    pub sequence_number: Option<u8>,
102    pub proposed_window_size: Option<u8>,
103    pub service_choice: ConfirmedServiceChoice,
104    pub service_request: Bytes,
105}
106
107/// Unconfirmed-Request PDU (Clause 20.1.3).
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct UnconfirmedRequest {
110    pub service_choice: UnconfirmedServiceChoice,
111    pub service_request: Bytes,
112}
113
114/// SimpleACK PDU (Clause 20.1.4).
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct SimpleAck {
117    pub invoke_id: u8,
118    pub service_choice: ConfirmedServiceChoice,
119}
120
121/// ComplexACK PDU (Clause 20.1.5).
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ComplexAck {
124    pub segmented: bool,
125    pub more_follows: bool,
126    pub invoke_id: u8,
127    pub sequence_number: Option<u8>,
128    pub proposed_window_size: Option<u8>,
129    pub service_choice: ConfirmedServiceChoice,
130    pub service_ack: Bytes,
131}
132
133/// SegmentACK PDU (Clause 20.1.6).
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct SegmentAck {
136    pub negative_ack: bool,
137    pub sent_by_server: bool,
138    pub invoke_id: u8,
139    pub sequence_number: u8,
140    pub actual_window_size: u8,
141}
142
143/// Error PDU (Clause 20.1.7).
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct ErrorPdu {
146    pub invoke_id: u8,
147    pub service_choice: ConfirmedServiceChoice,
148    pub error_class: ErrorClass,
149    pub error_code: ErrorCode,
150    pub error_data: Bytes,
151}
152
153/// Reject PDU (Clause 20.1.8).
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct RejectPdu {
156    pub invoke_id: u8,
157    pub reject_reason: RejectReason,
158}
159
160/// Abort PDU (Clause 20.1.9).
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct AbortPdu {
163    pub sent_by_server: bool,
164    pub invoke_id: u8,
165    pub abort_reason: AbortReason,
166}
167
168/// Sum type for all APDU PDU types.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum Apdu {
171    ConfirmedRequest(ConfirmedRequest),
172    UnconfirmedRequest(UnconfirmedRequest),
173    SimpleAck(SimpleAck),
174    ComplexAck(ComplexAck),
175    SegmentAck(SegmentAck),
176    Error(ErrorPdu),
177    Reject(RejectPdu),
178    Abort(AbortPdu),
179}
180
181// ---------------------------------------------------------------------------
182// Encoding
183// ---------------------------------------------------------------------------
184
185/// Encode an APDU to wire format.
186pub fn encode_apdu(buf: &mut BytesMut, apdu: &Apdu) {
187    match apdu {
188        Apdu::ConfirmedRequest(pdu) => encode_confirmed_request(buf, pdu),
189        Apdu::UnconfirmedRequest(pdu) => encode_unconfirmed_request(buf, pdu),
190        Apdu::SimpleAck(pdu) => encode_simple_ack(buf, pdu),
191        Apdu::ComplexAck(pdu) => encode_complex_ack(buf, pdu),
192        Apdu::SegmentAck(pdu) => encode_segment_ack(buf, pdu),
193        Apdu::Error(pdu) => encode_error(buf, pdu),
194        Apdu::Reject(pdu) => encode_reject(buf, pdu),
195        Apdu::Abort(pdu) => encode_abort(buf, pdu),
196    }
197}
198
199fn encode_confirmed_request(buf: &mut BytesMut, pdu: &ConfirmedRequest) {
200    let mut byte0 = PduType::CONFIRMED_REQUEST.to_raw() << 4;
201    if pdu.segmented {
202        byte0 |= 0x08;
203    }
204    if pdu.more_follows {
205        byte0 |= 0x04;
206    }
207    if pdu.segmented_response_accepted {
208        byte0 |= 0x02;
209    }
210    buf.put_u8(byte0);
211
212    let byte1 = (encode_max_segments(pdu.max_segments) << 4) | encode_max_apdu(pdu.max_apdu_length);
213    buf.put_u8(byte1);
214
215    buf.put_u8(pdu.invoke_id);
216
217    if pdu.segmented {
218        buf.put_u8(pdu.sequence_number.unwrap_or(0));
219        buf.put_u8(pdu.proposed_window_size.unwrap_or(1).clamp(1, 127));
220    }
221
222    buf.put_u8(pdu.service_choice.to_raw());
223    buf.put_slice(&pdu.service_request);
224}
225
226fn encode_unconfirmed_request(buf: &mut BytesMut, pdu: &UnconfirmedRequest) {
227    buf.put_u8(PduType::UNCONFIRMED_REQUEST.to_raw() << 4);
228    buf.put_u8(pdu.service_choice.to_raw());
229    buf.put_slice(&pdu.service_request);
230}
231
232fn encode_simple_ack(buf: &mut BytesMut, pdu: &SimpleAck) {
233    buf.put_u8(PduType::SIMPLE_ACK.to_raw() << 4);
234    buf.put_u8(pdu.invoke_id);
235    buf.put_u8(pdu.service_choice.to_raw());
236}
237
238fn encode_complex_ack(buf: &mut BytesMut, pdu: &ComplexAck) {
239    let mut byte0 = PduType::COMPLEX_ACK.to_raw() << 4;
240    if pdu.segmented {
241        byte0 |= 0x08;
242    }
243    if pdu.more_follows {
244        byte0 |= 0x04;
245    }
246    buf.put_u8(byte0);
247
248    buf.put_u8(pdu.invoke_id);
249
250    if pdu.segmented {
251        buf.put_u8(pdu.sequence_number.unwrap_or(0));
252        buf.put_u8(pdu.proposed_window_size.unwrap_or(1).clamp(1, 127));
253    }
254
255    buf.put_u8(pdu.service_choice.to_raw());
256    buf.put_slice(&pdu.service_ack);
257}
258
259fn encode_segment_ack(buf: &mut BytesMut, pdu: &SegmentAck) {
260    let mut byte0 = PduType::SEGMENT_ACK.to_raw() << 4;
261    if pdu.negative_ack {
262        byte0 |= 0x02;
263    }
264    if pdu.sent_by_server {
265        byte0 |= 0x01;
266    }
267    buf.put_u8(byte0);
268    buf.put_u8(pdu.invoke_id);
269    buf.put_u8(pdu.sequence_number);
270    buf.put_u8(pdu.actual_window_size.clamp(1, 127));
271}
272
273fn encode_error(buf: &mut BytesMut, pdu: &ErrorPdu) {
274    buf.put_u8(PduType::ERROR.to_raw() << 4);
275    buf.put_u8(pdu.invoke_id);
276    buf.put_u8(pdu.service_choice.to_raw());
277    primitives::encode_app_enumerated(buf, pdu.error_class.to_raw() as u32);
278    primitives::encode_app_enumerated(buf, pdu.error_code.to_raw() as u32);
279    if !pdu.error_data.is_empty() {
280        buf.put_slice(&pdu.error_data);
281    }
282}
283
284fn encode_reject(buf: &mut BytesMut, pdu: &RejectPdu) {
285    buf.put_u8(PduType::REJECT.to_raw() << 4);
286    buf.put_u8(pdu.invoke_id);
287    buf.put_u8(pdu.reject_reason.to_raw());
288}
289
290fn encode_abort(buf: &mut BytesMut, pdu: &AbortPdu) {
291    let mut byte0 = PduType::ABORT.to_raw() << 4;
292    if pdu.sent_by_server {
293        byte0 |= 0x01;
294    }
295    buf.put_u8(byte0);
296    buf.put_u8(pdu.invoke_id);
297    buf.put_u8(pdu.abort_reason.to_raw());
298}
299
300// ---------------------------------------------------------------------------
301// Decoding
302// ---------------------------------------------------------------------------
303
304/// Decode an APDU from raw bytes.
305pub fn decode_apdu(data: Bytes) -> Result<Apdu, Error> {
306    if data.is_empty() {
307        return Err(Error::decoding(0, "APDU data is empty"));
308    }
309
310    let pdu_type_raw = (data[0] >> 4) & 0x0F;
311    let pdu_type = PduType::from_raw(pdu_type_raw);
312
313    if pdu_type == PduType::CONFIRMED_REQUEST {
314        decode_confirmed_request(data).map(Apdu::ConfirmedRequest)
315    } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
316        decode_unconfirmed_request(data).map(Apdu::UnconfirmedRequest)
317    } else if pdu_type == PduType::SIMPLE_ACK {
318        decode_simple_ack(data).map(Apdu::SimpleAck)
319    } else if pdu_type == PduType::COMPLEX_ACK {
320        decode_complex_ack(data).map(Apdu::ComplexAck)
321    } else if pdu_type == PduType::SEGMENT_ACK {
322        decode_segment_ack(data).map(Apdu::SegmentAck)
323    } else if pdu_type == PduType::ERROR {
324        decode_error(data).map(Apdu::Error)
325    } else if pdu_type == PduType::REJECT {
326        decode_reject(data).map(Apdu::Reject)
327    } else if pdu_type == PduType::ABORT {
328        decode_abort(data).map(Apdu::Abort)
329    } else {
330        Err(Error::decoding(
331            0,
332            format!("unknown PDU type nibble: {:#x}", pdu_type_raw),
333        ))
334    }
335}
336
337fn decode_confirmed_request(data: Bytes) -> Result<ConfirmedRequest, Error> {
338    if data.len() < 4 {
339        return Err(Error::buffer_too_short(4, data.len()));
340    }
341
342    let byte0 = data[0];
343    let segmented = byte0 & 0x08 != 0;
344    let more_follows = byte0 & 0x04 != 0;
345    let segmented_response_accepted = byte0 & 0x02 != 0;
346
347    let byte1 = data[1];
348    let max_segments = decode_max_segments((byte1 >> 4) & 0x07);
349    let max_apdu_length = decode_max_apdu(byte1 & 0x0F);
350
351    let invoke_id = data[2];
352    let mut offset = 3;
353
354    let (sequence_number, proposed_window_size) = if segmented {
355        if data.len() < 6 {
356            return Err(Error::decoding(
357                offset,
358                "segmented ConfirmedRequest too short for sequence/window fields",
359            ));
360        }
361        let seq = data[offset];
362        let win = data[offset + 1];
363        offset += 2;
364        (Some(seq), Some(win))
365    } else {
366        (None, None)
367    };
368
369    if offset >= data.len() {
370        return Err(Error::decoding(
371            offset,
372            "ConfirmedRequest missing service choice",
373        ));
374    }
375    let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
376    offset += 1;
377
378    let service_request = data.slice(offset..);
379
380    Ok(ConfirmedRequest {
381        segmented,
382        more_follows,
383        segmented_response_accepted,
384        max_segments,
385        max_apdu_length,
386        invoke_id,
387        sequence_number,
388        proposed_window_size,
389        service_choice,
390        service_request,
391    })
392}
393
394fn decode_unconfirmed_request(data: Bytes) -> Result<UnconfirmedRequest, Error> {
395    if data.len() < 2 {
396        return Err(Error::buffer_too_short(2, data.len()));
397    }
398
399    let service_choice = UnconfirmedServiceChoice::from_raw(data[1]);
400    let service_request = data.slice(2..);
401
402    Ok(UnconfirmedRequest {
403        service_choice,
404        service_request,
405    })
406}
407
408fn decode_simple_ack(data: Bytes) -> Result<SimpleAck, Error> {
409    if data.len() < 3 {
410        return Err(Error::buffer_too_short(3, data.len()));
411    }
412
413    Ok(SimpleAck {
414        invoke_id: data[1],
415        service_choice: ConfirmedServiceChoice::from_raw(data[2]),
416    })
417}
418
419fn decode_complex_ack(data: Bytes) -> Result<ComplexAck, Error> {
420    if data.len() < 3 {
421        return Err(Error::buffer_too_short(3, data.len()));
422    }
423
424    let byte0 = data[0];
425    let segmented = byte0 & 0x08 != 0;
426    let more_follows = byte0 & 0x04 != 0;
427
428    let invoke_id = data[1];
429    let mut offset = 2;
430
431    let (sequence_number, proposed_window_size) = if segmented {
432        if data.len() < 5 {
433            return Err(Error::decoding(
434                offset,
435                "segmented ComplexAck too short for sequence/window fields",
436            ));
437        }
438        let seq = data[offset];
439        let win = data[offset + 1];
440        offset += 2;
441        (Some(seq), Some(win))
442    } else {
443        (None, None)
444    };
445
446    if offset >= data.len() {
447        return Err(Error::decoding(offset, "ComplexAck missing service choice"));
448    }
449    let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
450    offset += 1;
451
452    let service_ack = data.slice(offset..);
453
454    Ok(ComplexAck {
455        segmented,
456        more_follows,
457        invoke_id,
458        sequence_number,
459        proposed_window_size,
460        service_choice,
461        service_ack,
462    })
463}
464
465fn decode_segment_ack(data: Bytes) -> Result<SegmentAck, Error> {
466    if data.len() < 4 {
467        return Err(Error::buffer_too_short(4, data.len()));
468    }
469
470    let byte0 = data[0];
471    Ok(SegmentAck {
472        negative_ack: byte0 & 0x02 != 0,
473        sent_by_server: byte0 & 0x01 != 0,
474        invoke_id: data[1],
475        sequence_number: data[2],
476        actual_window_size: data[3],
477    })
478}
479
480fn decode_error(data: Bytes) -> Result<ErrorPdu, Error> {
481    if data.len() < 5 {
482        return Err(Error::buffer_too_short(5, data.len()));
483    }
484
485    let invoke_id = data[1];
486    let service_choice = ConfirmedServiceChoice::from_raw(data[2]);
487
488    let mut offset = 3;
489    let (tag, tag_end) = tags::decode_tag(&data, offset)?;
490    if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
491        return Err(Error::decoding(
492            offset,
493            "ErrorPDU error class: expected application-tagged enumerated",
494        ));
495    }
496    let class_end = tag_end
497        .checked_add(tag.length as usize)
498        .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error class length overflow"))?;
499    if class_end > data.len() {
500        return Err(Error::decoding(
501            tag_end,
502            "ErrorPDU truncated at error class",
503        ));
504    }
505    let error_class_raw = primitives::decode_unsigned(&data[tag_end..class_end])? as u16;
506    offset = class_end;
507
508    let (tag, tag_end) = tags::decode_tag(&data, offset)?;
509    if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
510        return Err(Error::decoding(
511            offset,
512            "ErrorPDU error code: expected application-tagged enumerated",
513        ));
514    }
515    let code_end = tag_end
516        .checked_add(tag.length as usize)
517        .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error code length overflow"))?;
518    if code_end > data.len() {
519        return Err(Error::decoding(tag_end, "ErrorPDU truncated at error code"));
520    }
521    let error_code_raw = primitives::decode_unsigned(&data[tag_end..code_end])? as u16;
522    offset = code_end;
523
524    let error_data = if offset < data.len() {
525        data.slice(offset..)
526    } else {
527        Bytes::new()
528    };
529
530    Ok(ErrorPdu {
531        invoke_id,
532        service_choice,
533        error_class: ErrorClass::from_raw(error_class_raw),
534        error_code: ErrorCode::from_raw(error_code_raw),
535        error_data,
536    })
537}
538
539fn decode_reject(data: Bytes) -> Result<RejectPdu, Error> {
540    if data.len() < 3 {
541        return Err(Error::buffer_too_short(3, data.len()));
542    }
543
544    Ok(RejectPdu {
545        invoke_id: data[1],
546        reject_reason: RejectReason::from_raw(data[2]),
547    })
548}
549
550fn decode_abort(data: Bytes) -> Result<AbortPdu, Error> {
551    if data.len() < 3 {
552        return Err(Error::buffer_too_short(3, data.len()));
553    }
554
555    let byte0 = data[0];
556    Ok(AbortPdu {
557        sent_by_server: byte0 & 0x01 != 0,
558        invoke_id: data[1],
559        abort_reason: AbortReason::from_raw(data[2]),
560    })
561}
562
563// ---------------------------------------------------------------------------
564// Tests
565// ---------------------------------------------------------------------------
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570
571    fn encode_to_vec(apdu: &Apdu) -> Vec<u8> {
572        let mut buf = BytesMut::with_capacity(64);
573        encode_apdu(&mut buf, apdu);
574        buf.to_vec()
575    }
576
577    // --- Max-segments / max-APDU helpers ---
578
579    #[test]
580    fn max_segments_round_trip() {
581        assert_eq!(decode_max_segments(encode_max_segments(None)), None);
582        assert_eq!(decode_max_segments(encode_max_segments(Some(2))), Some(2));
583        assert_eq!(decode_max_segments(encode_max_segments(Some(4))), Some(4));
584        assert_eq!(decode_max_segments(encode_max_segments(Some(8))), Some(8));
585        assert_eq!(decode_max_segments(encode_max_segments(Some(16))), Some(16));
586        assert_eq!(decode_max_segments(encode_max_segments(Some(32))), Some(32));
587        assert_eq!(decode_max_segments(encode_max_segments(Some(64))), Some(64));
588        assert_eq!(
589            decode_max_segments(encode_max_segments(Some(100))),
590            Some(255)
591        );
592    }
593
594    #[test]
595    fn max_apdu_round_trip() {
596        assert_eq!(decode_max_apdu(encode_max_apdu(50)), 50);
597        assert_eq!(decode_max_apdu(encode_max_apdu(128)), 128);
598        assert_eq!(decode_max_apdu(encode_max_apdu(206)), 206);
599        assert_eq!(decode_max_apdu(encode_max_apdu(480)), 480);
600        assert_eq!(decode_max_apdu(encode_max_apdu(1024)), 1024);
601        assert_eq!(decode_max_apdu(encode_max_apdu(1476)), 1476);
602        // Unknown value defaults to 1476
603        assert_eq!(decode_max_apdu(encode_max_apdu(9999)), 1476);
604    }
605
606    // --- ConfirmedRequest ---
607
608    #[test]
609    fn confirmed_request_non_segmented_round_trip() {
610        let pdu = ConfirmedRequest {
611            segmented: false,
612            more_follows: false,
613            segmented_response_accepted: true,
614            max_segments: Some(4),
615            max_apdu_length: 1476,
616            invoke_id: 42,
617            sequence_number: None,
618            proposed_window_size: None,
619            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
620            service_request: Bytes::from_static(&[0x0C, 0x02, 0x00, 0x00, 0x01]),
621        };
622        let apdu = Apdu::ConfirmedRequest(pdu);
623        let encoded = encode_to_vec(&apdu);
624        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
625        assert_eq!(apdu, decoded);
626    }
627
628    #[test]
629    fn confirmed_request_segmented_round_trip() {
630        let pdu = ConfirmedRequest {
631            segmented: true,
632            more_follows: true,
633            segmented_response_accepted: true,
634            max_segments: Some(64),
635            max_apdu_length: 480,
636            invoke_id: 7,
637            sequence_number: Some(3),
638            proposed_window_size: Some(16),
639            service_choice: ConfirmedServiceChoice::WRITE_PROPERTY,
640            service_request: Bytes::from_static(&[0xAA, 0xBB]),
641        };
642        let apdu = Apdu::ConfirmedRequest(pdu);
643        let encoded = encode_to_vec(&apdu);
644        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
645        assert_eq!(apdu, decoded);
646    }
647
648    #[test]
649    fn confirmed_request_wire_format() {
650        let pdu = ConfirmedRequest {
651            segmented: false,
652            more_follows: false,
653            segmented_response_accepted: false,
654            max_segments: None,
655            max_apdu_length: 1476,
656            invoke_id: 1,
657            sequence_number: None,
658            proposed_window_size: None,
659            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
660            service_request: Bytes::new(),
661        };
662        let encoded = encode_to_vec(&Apdu::ConfirmedRequest(pdu));
663        // byte0: (0<<4) | 0 = 0x00
664        // byte1: (0<<4) | 5 = 0x05  (unspecified segments, 1476 apdu)
665        // invoke_id: 0x01
666        // service_choice: ReadProperty = 12 = 0x0C
667        assert_eq!(&encoded[..4], &[0x00, 0x05, 0x01, 0x0C]);
668    }
669
670    // --- UnconfirmedRequest ---
671
672    #[test]
673    fn unconfirmed_request_round_trip() {
674        let pdu = UnconfirmedRequest {
675            service_choice: UnconfirmedServiceChoice::WHO_IS,
676            service_request: Bytes::from_static(&[0x01, 0x02, 0x03]),
677        };
678        let apdu = Apdu::UnconfirmedRequest(pdu);
679        let encoded = encode_to_vec(&apdu);
680        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
681        assert_eq!(apdu, decoded);
682    }
683
684    #[test]
685    fn unconfirmed_request_wire_format() {
686        let pdu = UnconfirmedRequest {
687            service_choice: UnconfirmedServiceChoice::I_AM,
688            service_request: Bytes::new(),
689        };
690        let encoded = encode_to_vec(&Apdu::UnconfirmedRequest(pdu));
691        // byte0: (1<<4) = 0x10
692        // service_choice: IAm = 0
693        assert_eq!(encoded, vec![0x10, 0x00]);
694    }
695
696    // --- SimpleAck ---
697
698    #[test]
699    fn simple_ack_round_trip() {
700        let pdu = SimpleAck {
701            invoke_id: 99,
702            service_choice: ConfirmedServiceChoice::WRITE_PROPERTY,
703        };
704        let apdu = Apdu::SimpleAck(pdu);
705        let encoded = encode_to_vec(&apdu);
706        assert_eq!(encoded.len(), 3);
707        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
708        assert_eq!(apdu, decoded);
709    }
710
711    #[test]
712    fn simple_ack_wire_format() {
713        let pdu = SimpleAck {
714            invoke_id: 5,
715            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
716        };
717        let encoded = encode_to_vec(&Apdu::SimpleAck(pdu));
718        // byte0: (2<<4) = 0x20
719        assert_eq!(encoded, vec![0x20, 0x05, 0x0C]);
720    }
721
722    // --- ComplexAck ---
723
724    #[test]
725    fn complex_ack_non_segmented_round_trip() {
726        let pdu = ComplexAck {
727            segmented: false,
728            more_follows: false,
729            invoke_id: 42,
730            sequence_number: None,
731            proposed_window_size: None,
732            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
733            service_ack: Bytes::from_static(&[0xDE, 0xAD]),
734        };
735        let apdu = Apdu::ComplexAck(pdu);
736        let encoded = encode_to_vec(&apdu);
737        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
738        assert_eq!(apdu, decoded);
739    }
740
741    #[test]
742    fn complex_ack_segmented_round_trip() {
743        let pdu = ComplexAck {
744            segmented: true,
745            more_follows: false,
746            invoke_id: 10,
747            sequence_number: Some(5),
748            proposed_window_size: Some(8),
749            service_choice: ConfirmedServiceChoice::READ_PROPERTY_MULTIPLE,
750            service_ack: Bytes::from_static(&[0x01]),
751        };
752        let apdu = Apdu::ComplexAck(pdu);
753        let encoded = encode_to_vec(&apdu);
754        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
755        assert_eq!(apdu, decoded);
756    }
757
758    // --- SegmentAck ---
759
760    #[test]
761    fn segment_ack_round_trip() {
762        let pdu = SegmentAck {
763            negative_ack: true,
764            sent_by_server: false,
765            invoke_id: 55,
766            sequence_number: 12,
767            actual_window_size: 4,
768        };
769        let apdu = Apdu::SegmentAck(pdu);
770        let encoded = encode_to_vec(&apdu);
771        assert_eq!(encoded.len(), 4);
772        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
773        assert_eq!(apdu, decoded);
774    }
775
776    #[test]
777    fn segment_ack_flags() {
778        // Both flags set
779        let pdu = SegmentAck {
780            negative_ack: true,
781            sent_by_server: true,
782            invoke_id: 1,
783            sequence_number: 0,
784            actual_window_size: 1,
785        };
786        let encoded = encode_to_vec(&Apdu::SegmentAck(pdu));
787        // byte0: (4<<4) | 0x02 | 0x01 = 0x43
788        assert_eq!(encoded[0], 0x43);
789    }
790
791    // --- Error ---
792
793    #[test]
794    fn error_round_trip() {
795        let pdu = ErrorPdu {
796            invoke_id: 10,
797            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
798            error_class: ErrorClass::PROPERTY,
799            error_code: ErrorCode::UNKNOWN_PROPERTY,
800            error_data: Bytes::new(),
801        };
802        let apdu = Apdu::Error(pdu);
803        let encoded = encode_to_vec(&apdu);
804        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
805        assert_eq!(apdu, decoded);
806    }
807
808    #[test]
809    fn error_with_trailing_data_round_trip() {
810        let pdu = ErrorPdu {
811            invoke_id: 20,
812            service_choice: ConfirmedServiceChoice::CREATE_OBJECT,
813            error_class: ErrorClass::OBJECT,
814            error_code: ErrorCode::NO_OBJECTS_OF_SPECIFIED_TYPE,
815            error_data: Bytes::from_static(&[0x01, 0x02, 0x03]),
816        };
817        let apdu = Apdu::Error(pdu);
818        let encoded = encode_to_vec(&apdu);
819        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
820        assert_eq!(apdu, decoded);
821    }
822
823    // --- Reject ---
824
825    #[test]
826    fn reject_round_trip() {
827        let pdu = RejectPdu {
828            invoke_id: 77,
829            reject_reason: RejectReason::INVALID_TAG,
830        };
831        let apdu = Apdu::Reject(pdu);
832        let encoded = encode_to_vec(&apdu);
833        assert_eq!(encoded.len(), 3);
834        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
835        assert_eq!(apdu, decoded);
836    }
837
838    // --- Abort ---
839
840    #[test]
841    fn abort_round_trip() {
842        let pdu = AbortPdu {
843            sent_by_server: true,
844            invoke_id: 33,
845            abort_reason: AbortReason::BUFFER_OVERFLOW,
846        };
847        let apdu = Apdu::Abort(pdu);
848        let encoded = encode_to_vec(&apdu);
849        assert_eq!(encoded.len(), 3);
850        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
851        assert_eq!(apdu, decoded);
852    }
853
854    #[test]
855    fn abort_server_flag() {
856        let pdu = AbortPdu {
857            sent_by_server: true,
858            invoke_id: 0,
859            abort_reason: AbortReason::OTHER,
860        };
861        let encoded = encode_to_vec(&Apdu::Abort(pdu));
862        // byte0: (7<<4) | 0x01 = 0x71
863        assert_eq!(encoded[0], 0x71);
864
865        let pdu = AbortPdu {
866            sent_by_server: false,
867            invoke_id: 0,
868            abort_reason: AbortReason::OTHER,
869        };
870        let encoded = encode_to_vec(&Apdu::Abort(pdu));
871        // byte0: (7<<4) = 0x70
872        assert_eq!(encoded[0], 0x70);
873    }
874
875    // --- Decode errors ---
876
877    #[test]
878    fn decode_empty_data() {
879        assert!(decode_apdu(Bytes::new()).is_err());
880    }
881
882    #[test]
883    fn decode_unknown_pdu_type() {
884        // PDU type nibble 0x0F (reserved)
885        assert!(decode_apdu(Bytes::from_static(&[0xF0])).is_err());
886    }
887
888    #[test]
889    fn decode_truncated_confirmed_request() {
890        // Only 2 bytes, need at least 4
891        assert!(decode_apdu(Bytes::from_static(&[0x00, 0x05])).is_err());
892    }
893
894    #[test]
895    fn decode_truncated_simple_ack() {
896        // Only 2 bytes, need 3
897        assert!(decode_apdu(Bytes::from_static(&[0x20, 0x01])).is_err());
898    }
899
900    // --- Segmented APDU edge cases ---
901
902    #[test]
903    fn decode_truncated_segmented_confirmed_request() {
904        // Segmented flag set but not enough bytes for sequence/window
905        // byte0: (0<<4) | 0x08 (segmented) = 0x08
906        // byte1: max-segments/apdu = 0x05
907        // invoke_id: 0x01
908        // Missing: sequence_number, window_size, service_choice
909        assert!(decode_apdu(Bytes::from_static(&[0x08, 0x05, 0x01])).is_err());
910    }
911
912    #[test]
913    fn decode_segmented_confirmed_request_missing_service() {
914        // Segmented, has sequence/window, but no service choice
915        // byte0: 0x08 (segmented), byte1: 0x05, invoke_id: 1, seq: 0, win: 1
916        assert!(decode_apdu(Bytes::from_static(&[0x08, 0x05, 0x01, 0x00, 0x01])).is_err());
917    }
918
919    #[test]
920    fn decode_truncated_segmented_complex_ack() {
921        // Segmented ComplexAck but too short for sequence/window
922        // byte0: (3<<4) | 0x08 = 0x38
923        // invoke_id: 0x01
924        // Missing: sequence_number, window_size
925        assert!(decode_apdu(Bytes::from_static(&[0x38, 0x01])).is_err());
926    }
927
928    #[test]
929    fn decode_complex_ack_missing_service_choice() {
930        // Non-segmented ComplexAck, only 2 bytes (need 3 minimum)
931        assert!(decode_apdu(Bytes::from_static(&[0x30, 0x01])).is_err());
932    }
933
934    #[test]
935    fn decode_truncated_segment_ack() {
936        // SegmentAck needs exactly 4 bytes
937        assert!(decode_apdu(Bytes::from_static(&[0x40, 0x01, 0x02])).is_err());
938    }
939
940    #[test]
941    fn decode_truncated_error_pdu() {
942        // Error PDU needs at least 5 bytes (type, invoke, service, error_class tag+value)
943        assert!(decode_apdu(Bytes::from_static(&[0x50, 0x01, 0x0C, 0x91])).is_err());
944    }
945
946    #[test]
947    fn decode_truncated_reject() {
948        // Reject needs 3 bytes
949        assert!(decode_apdu(Bytes::from_static(&[0x60, 0x01])).is_err());
950    }
951
952    #[test]
953    fn decode_truncated_abort() {
954        // Abort needs 3 bytes
955        assert!(decode_apdu(Bytes::from_static(&[0x70, 0x01])).is_err());
956    }
957
958    // --- APDU round-trip edge cases ---
959
960    #[test]
961    fn confirmed_request_empty_service_data() {
962        let pdu = ConfirmedRequest {
963            segmented: false,
964            more_follows: false,
965            segmented_response_accepted: false,
966            max_segments: None,
967            max_apdu_length: 1476,
968            invoke_id: 0,
969            sequence_number: None,
970            proposed_window_size: None,
971            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
972            service_request: Bytes::new(),
973        };
974        let apdu = Apdu::ConfirmedRequest(pdu);
975        let encoded = encode_to_vec(&apdu);
976        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
977        assert_eq!(apdu, decoded);
978    }
979
980    #[test]
981    fn confirmed_request_invoke_id_zero() {
982        let pdu = ConfirmedRequest {
983            segmented: false,
984            more_follows: false,
985            segmented_response_accepted: true,
986            max_segments: Some(64),
987            max_apdu_length: 1476,
988            invoke_id: 0,
989            sequence_number: None,
990            proposed_window_size: None,
991            service_choice: ConfirmedServiceChoice::WRITE_PROPERTY,
992            service_request: Bytes::from_static(&[0xAA]),
993        };
994        let apdu = Apdu::ConfirmedRequest(pdu);
995        let encoded = encode_to_vec(&apdu);
996        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
997        assert_eq!(apdu, decoded);
998    }
999
1000    #[test]
1001    fn confirmed_request_invoke_id_255() {
1002        let pdu = ConfirmedRequest {
1003            segmented: false,
1004            more_follows: false,
1005            segmented_response_accepted: true,
1006            max_segments: None,
1007            max_apdu_length: 480,
1008            invoke_id: 255,
1009            sequence_number: None,
1010            proposed_window_size: None,
1011            service_choice: ConfirmedServiceChoice::READ_PROPERTY,
1012            service_request: Bytes::from_static(&[0x01]),
1013        };
1014        let apdu = Apdu::ConfirmedRequest(pdu);
1015        let encoded = encode_to_vec(&apdu);
1016        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
1017        assert_eq!(apdu, decoded);
1018    }
1019
1020    #[test]
1021    fn segmented_request_sequence_zero() {
1022        let pdu = ConfirmedRequest {
1023            segmented: true,
1024            more_follows: true,
1025            segmented_response_accepted: true,
1026            max_segments: Some(64),
1027            max_apdu_length: 480,
1028            invoke_id: 5,
1029            sequence_number: Some(0),
1030            proposed_window_size: Some(1),
1031            service_choice: ConfirmedServiceChoice::READ_PROPERTY_MULTIPLE,
1032            service_request: Bytes::from_static(&[0x01, 0x02]),
1033        };
1034        let apdu = Apdu::ConfirmedRequest(pdu);
1035        let encoded = encode_to_vec(&apdu);
1036        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
1037        assert_eq!(apdu, decoded);
1038    }
1039
1040    #[test]
1041    fn error_pdu_truncated_error_class() {
1042        // Error PDU with invoke_id and service choice but error class tag truncated
1043        // type=5<<4=0x50, invoke=1, service=12, then truncated tag
1044        assert!(decode_apdu(Bytes::from_static(&[0x50, 0x01, 0x0C])).is_err());
1045    }
1046
1047    #[test]
1048    fn error_pdu_truncated_error_code() {
1049        // Error PDU with error class but error code tag truncated
1050        // type=0x50, invoke=1, service=12, error_class(enum 0, 1byte)=0x91 0x00, then truncated
1051        let mut buf = BytesMut::with_capacity(16);
1052        buf.put_u8(0x50); // Error PDU
1053        buf.put_u8(1); // invoke_id
1054        buf.put_u8(0x0C); // service_choice (ReadProperty)
1055        primitives::encode_app_enumerated(&mut buf, 2); // error_class = PROPERTY
1056        assert!(decode_apdu(buf.freeze()).is_err());
1057    }
1058
1059    #[test]
1060    fn unconfirmed_request_empty_service_data() {
1061        let pdu = UnconfirmedRequest {
1062            service_choice: UnconfirmedServiceChoice::WHO_IS,
1063            service_request: Bytes::new(),
1064        };
1065        let apdu = Apdu::UnconfirmedRequest(pdu);
1066        let encoded = encode_to_vec(&apdu);
1067        let decoded = decode_apdu(Bytes::from(encoded)).unwrap();
1068        assert_eq!(apdu, decoded);
1069    }
1070}