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