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