1#[cfg(feature = "alloc")]
18use bacnet_types::enums::{
19 AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, PduType, RejectReason,
20 UnconfirmedServiceChoice,
21};
22#[cfg(feature = "alloc")]
23use bacnet_types::error::{DecodeError, DecodingKind, Error, RangeError};
24#[cfg(feature = "alloc")]
25use bytes::{BufMut, Bytes, BytesMut};
26
27#[cfg(feature = "alloc")]
28use crate::primitives;
29#[cfg(feature = "alloc")]
30use crate::tags;
31
32#[cfg(feature = "alloc")]
33mod formal_error;
34pub mod header;
35
36use header::{MaxApdu, MaxSegments};
37
38#[cfg(feature = "alloc")]
49pub fn validate_max_segments(value: Option<u8>) -> Result<(), Error> {
50 encode_max_segments(value).map(drop)
51}
52
53#[cfg(feature = "alloc")]
58fn encode_max_segments(value: Option<u8>) -> Result<u8, Error> {
59 match MaxSegments::from_capacity(value) {
60 Some(field) => Ok(field.bits()),
61 None => Err(RangeError::new(
62 "invalid max-segments-accepted {}; expected 2..=255 or unspecified",
63 )
64 .with_value(value.unwrap_or(0).into())
65 .into()),
66 }
67}
68
69#[cfg(feature = "alloc")]
71fn decode_max_segments(value: u8) -> Option<u8> {
72 MaxSegments::from_bits(value).count()
73}
74
75pub fn advertised_max_segments(configured: Option<u8>) -> Option<u8> {
89 match MaxSegments::from_capacity(configured)?.count() {
90 Some(255) => None,
92 decoded => decoded,
93 }
94}
95
96pub const MINIMUM_MESSAGE_SIZE: u16 = 50;
107
108#[cfg(feature = "alloc")]
116pub fn max_apdu_header_at_or_below(capacity: u32) -> Result<u16, Error> {
117 MaxApdu::at_or_below(capacity)
118 .map(MaxApdu::octets)
119 .ok_or_else(|| {
120 RangeError::new("local APDU capacity {} is below 50")
121 .with_value(capacity.into())
122 .into()
123 })
124}
125
126pub fn is_valid_max_apdu_length(value: u16) -> bool {
129 MaxApdu::from_octets(value).is_some()
130}
131
132#[cfg(feature = "alloc")]
135pub fn validate_max_apdu_length(value: u16) -> Result<(), Error> {
136 encode_max_apdu(value).map(drop)
137}
138
139#[cfg(feature = "alloc")]
141fn encode_max_apdu(value: u16) -> Result<u8, Error> {
142 match MaxApdu::from_octets(value) {
143 Some(field) => Ok(field.bits()),
144 None => Err(RangeError::new(
145 "invalid max-APDU-length {}; expected one of 50, 128, 206, 480, 1024, 1476",
146 )
147 .with_value(value.into())
148 .into()),
149 }
150}
151
152#[cfg(feature = "alloc")]
154fn decode_max_apdu(value: u8) -> Result<u16, Error> {
155 match MaxApdu::from_bits(value) {
156 Some(field) => Ok(field.octets()),
157 None => Err(DecodeError::new(
158 DecodingKind::InvalidEncoding,
159 1,
160 "reserved max-APDU-length field value {}",
161 )
162 .with_value(u64::from(value & 0x0F))
163 .into()),
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
173#[cfg(feature = "alloc")]
174pub struct ConfirmedRequest {
175 pub segmented: bool,
178 pub more_follows: bool,
180 pub segmented_response_accepted: bool,
182 pub max_segments: Option<u8>,
186 pub max_apdu_length: u16,
188 pub invoke_id: u8,
190 pub sequence_number: Option<u8>,
193 pub proposed_window_size: Option<u8>,
196 pub service_choice: ConfirmedServiceChoice,
198 pub service_request: Bytes,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
204#[cfg(feature = "alloc")]
205pub struct UnconfirmedRequest {
206 pub service_choice: UnconfirmedServiceChoice,
208 pub service_request: Bytes,
210}
211
212#[derive(Debug, Clone, PartialEq, Eq)]
214#[cfg(feature = "alloc")]
215pub struct SimpleAck {
216 pub invoke_id: u8,
218 pub service_choice: ConfirmedServiceChoice,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
224#[cfg(feature = "alloc")]
225pub struct ComplexAck {
226 pub segmented: bool,
228 pub more_follows: bool,
230 pub invoke_id: u8,
232 pub sequence_number: Option<u8>,
235 pub proposed_window_size: Option<u8>,
238 pub service_choice: ConfirmedServiceChoice,
240 pub service_ack: Bytes,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
246#[cfg(feature = "alloc")]
247pub struct SegmentAck {
248 pub negative_ack: bool,
251 pub sent_by_server: bool,
254 pub invoke_id: u8,
256 pub sequence_number: u8,
259 pub actual_window_size: u8,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265#[cfg(feature = "alloc")]
266pub struct ErrorPdu {
267 pub invoke_id: u8,
269 pub service_choice: ConfirmedServiceChoice,
271 pub error_class: ErrorClass,
273 pub error_code: ErrorCode,
275 pub error_data: Bytes,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
286#[cfg(feature = "alloc")]
287pub struct RejectPdu {
288 pub invoke_id: u8,
290 pub reject_reason: RejectReason,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
296#[cfg(feature = "alloc")]
297pub struct AbortPdu {
298 pub sent_by_server: bool,
301 pub invoke_id: u8,
303 pub abort_reason: AbortReason,
305}
306
307#[derive(Debug, Clone, PartialEq, Eq)]
309#[cfg(feature = "alloc")]
310pub enum Apdu {
311 ConfirmedRequest(ConfirmedRequest),
313 UnconfirmedRequest(UnconfirmedRequest),
315 SimpleAck(SimpleAck),
317 ComplexAck(ComplexAck),
319 SegmentAck(SegmentAck),
321 Error(ErrorPdu),
323 Reject(RejectPdu),
325 Abort(AbortPdu),
327}
328
329#[cfg(feature = "alloc")]
335pub fn encode_apdu(buf: &mut BytesMut, apdu: &Apdu) -> Result<(), Error> {
336 match apdu {
337 Apdu::ConfirmedRequest(pdu) => encode_confirmed_request(buf, pdu),
338 Apdu::UnconfirmedRequest(pdu) => {
339 encode_unconfirmed_request(buf, pdu);
340 Ok(())
341 }
342 Apdu::SimpleAck(pdu) => {
343 encode_simple_ack(buf, pdu);
344 Ok(())
345 }
346 Apdu::ComplexAck(pdu) => encode_complex_ack(buf, pdu),
347 Apdu::SegmentAck(pdu) => encode_segment_ack(buf, pdu),
348 Apdu::Error(pdu) => encode_error(buf, pdu),
349 Apdu::Reject(pdu) => {
350 encode_reject(buf, pdu);
351 Ok(())
352 }
353 Apdu::Abort(pdu) => {
354 encode_abort(buf, pdu);
355 Ok(())
356 }
357 }
358}
359
360#[cfg(feature = "alloc")]
361fn encode_confirmed_request(buf: &mut BytesMut, pdu: &ConfirmedRequest) -> Result<(), Error> {
362 let max_segments = encode_max_segments(pdu.max_segments)?;
365 let max_apdu = encode_max_apdu(pdu.max_apdu_length)?;
366 let window = pdu
367 .segmented
368 .then(|| {
369 valid_window_size(
370 "ConfirmedRequest proposed-window-size {} outside BACnet range 1..=127",
371 pdu.proposed_window_size.unwrap_or(1),
372 )
373 })
374 .transpose()?;
375
376 let mut byte0 = PduType::CONFIRMED_REQUEST.to_raw() << 4;
377 if pdu.segmented {
378 byte0 |= 0x08;
379 }
380 if pdu.more_follows {
381 byte0 |= 0x04;
382 }
383 if pdu.segmented_response_accepted {
384 byte0 |= 0x02;
385 }
386 buf.put_u8(byte0);
387
388 let byte1 = (max_segments << 4) | max_apdu;
389 buf.put_u8(byte1);
390
391 buf.put_u8(pdu.invoke_id);
392
393 if let Some(window) = window {
394 buf.put_u8(pdu.sequence_number.unwrap_or(0));
395 buf.put_u8(window);
396 }
397
398 buf.put_u8(pdu.service_choice.to_raw());
399 buf.put_slice(&pdu.service_request);
400 Ok(())
401}
402
403#[cfg(feature = "alloc")]
404fn encode_unconfirmed_request(buf: &mut BytesMut, pdu: &UnconfirmedRequest) {
405 buf.put_u8(PduType::UNCONFIRMED_REQUEST.to_raw() << 4);
406 buf.put_u8(pdu.service_choice.to_raw());
407 buf.put_slice(&pdu.service_request);
408}
409
410#[cfg(feature = "alloc")]
411fn encode_simple_ack(buf: &mut BytesMut, pdu: &SimpleAck) {
412 buf.put_u8(PduType::SIMPLE_ACK.to_raw() << 4);
413 buf.put_u8(pdu.invoke_id);
414 buf.put_u8(pdu.service_choice.to_raw());
415}
416
417#[cfg(feature = "alloc")]
418fn encode_complex_ack(buf: &mut BytesMut, pdu: &ComplexAck) -> Result<(), Error> {
419 let window = pdu
421 .segmented
422 .then(|| {
423 valid_window_size(
424 "ComplexAck proposed-window-size {} outside BACnet range 1..=127",
425 pdu.proposed_window_size.unwrap_or(1),
426 )
427 })
428 .transpose()?;
429
430 let mut byte0 = PduType::COMPLEX_ACK.to_raw() << 4;
431 if pdu.segmented {
432 byte0 |= 0x08;
433 }
434 if pdu.more_follows {
435 byte0 |= 0x04;
436 }
437 buf.put_u8(byte0);
438
439 buf.put_u8(pdu.invoke_id);
440
441 if let Some(window) = window {
442 buf.put_u8(pdu.sequence_number.unwrap_or(0));
443 buf.put_u8(window);
444 }
445
446 buf.put_u8(pdu.service_choice.to_raw());
447 buf.put_slice(&pdu.service_ack);
448 Ok(())
449}
450
451#[cfg(feature = "alloc")]
452fn encode_segment_ack(buf: &mut BytesMut, pdu: &SegmentAck) -> Result<(), Error> {
453 let window = valid_window_size(
455 "SegmentACK actual-window-size {} outside BACnet range 1..=127",
456 pdu.actual_window_size,
457 )?;
458 let mut byte0 = PduType::SEGMENT_ACK.to_raw() << 4;
459 if pdu.negative_ack {
460 byte0 |= 0x02;
461 }
462 if pdu.sent_by_server {
463 byte0 |= 0x01;
464 }
465 buf.put_u8(byte0);
466 buf.put_u8(pdu.invoke_id);
467 buf.put_u8(pdu.sequence_number);
468 buf.put_u8(window);
469 Ok(())
470}
471
472#[cfg(feature = "alloc")]
475fn valid_window_size(label: &'static str, value: u8) -> Result<u8, Error> {
476 if (1..=127).contains(&value) {
477 Ok(value)
478 } else {
479 Err(RangeError::new(label).with_value(value.into()).into())
480 }
481}
482
483#[cfg(feature = "alloc")]
484fn encode_error(buf: &mut BytesMut, pdu: &ErrorPdu) -> Result<(), Error> {
485 let formal =
489 formal_error::decode_formal_body(pdu.service_choice, &pdu.error_data).unwrap_or(None);
490 if let Some((error_class, error_code)) = formal {
491 if error_class != pdu.error_class || error_code != pdu.error_code {
492 return Err(Error::encode(
493 "formal Error body disagrees with ErrorPdu class/code",
494 ));
495 }
496 }
497
498 buf.put_u8(PduType::ERROR.to_raw() << 4);
499 buf.put_u8(pdu.invoke_id);
500 buf.put_u8(pdu.service_choice.to_raw());
501 if formal.is_some() {
502 buf.put_slice(&pdu.error_data);
503 } else {
504 primitives::encode_app_enumerated(buf, u32::from(pdu.error_class.to_raw()));
505 primitives::encode_app_enumerated(buf, u32::from(pdu.error_code.to_raw()));
506 if !pdu.error_data.is_empty() {
507 buf.put_slice(&pdu.error_data);
508 }
509 }
510 Ok(())
511}
512
513#[cfg(feature = "alloc")]
514fn encode_reject(buf: &mut BytesMut, pdu: &RejectPdu) {
515 buf.put_u8(PduType::REJECT.to_raw() << 4);
516 buf.put_u8(pdu.invoke_id);
517 buf.put_u8(pdu.reject_reason.to_raw());
518}
519
520#[cfg(feature = "alloc")]
521fn encode_abort(buf: &mut BytesMut, pdu: &AbortPdu) {
522 let mut byte0 = PduType::ABORT.to_raw() << 4;
523 if pdu.sent_by_server {
524 byte0 |= 0x01;
525 }
526 buf.put_u8(byte0);
527 buf.put_u8(pdu.invoke_id);
528 buf.put_u8(pdu.abort_reason.to_raw());
529}
530
531#[cfg(feature = "alloc")]
537pub fn decode_apdu(data: Bytes) -> Result<Apdu, Error> {
538 if data.is_empty() {
539 return Err(Error::decoding(0, "APDU data is empty"));
540 }
541
542 let pdu_type_raw = (data[0] >> 4) & 0x0F;
543 let pdu_type = PduType::from_raw(pdu_type_raw);
544
545 if pdu_type == PduType::CONFIRMED_REQUEST {
546 decode_confirmed_request(data).map(Apdu::ConfirmedRequest)
547 } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
548 decode_unconfirmed_request(data).map(Apdu::UnconfirmedRequest)
549 } else if pdu_type == PduType::SIMPLE_ACK {
550 decode_simple_ack(data).map(Apdu::SimpleAck)
551 } else if pdu_type == PduType::COMPLEX_ACK {
552 decode_complex_ack(data).map(Apdu::ComplexAck)
553 } else if pdu_type == PduType::SEGMENT_ACK {
554 decode_segment_ack(data).map(Apdu::SegmentAck)
555 } else if pdu_type == PduType::ERROR {
556 decode_error(data).map(Apdu::Error)
557 } else if pdu_type == PduType::REJECT {
558 decode_reject(data).map(Apdu::Reject)
559 } else if pdu_type == PduType::ABORT {
560 decode_abort(data).map(Apdu::Abort)
561 } else {
562 Err(DecodeError::new(
563 DecodingKind::InvalidEncoding,
564 0,
565 "unknown PDU type nibble: {:#x}",
566 )
567 .with_value(pdu_type_raw.into())
568 .into())
569 }
570}
571
572#[cfg(feature = "alloc")]
573fn decode_confirmed_request(data: Bytes) -> Result<ConfirmedRequest, Error> {
574 if data.len() < 4 {
575 return Err(Error::buffer_too_short(4, data.len()));
576 }
577
578 let byte0 = data[0];
579 let segmented = byte0 & 0x08 != 0;
580 let more_follows = byte0 & 0x04 != 0;
581 let segmented_response_accepted = byte0 & 0x02 != 0;
582
583 let byte1 = data[1];
584 let max_segments = decode_max_segments((byte1 >> 4) & 0x07);
585 let max_apdu_length = decode_max_apdu(byte1 & 0x0F)?;
586
587 let invoke_id = data[2];
588 let mut offset = 3;
589
590 let (sequence_number, proposed_window_size) = if segmented {
591 if data.len() < 6 {
592 return Err(Error::decoding(
593 offset,
594 "segmented ConfirmedRequest too short for sequence/window fields",
595 ));
596 }
597 let seq = data[offset];
598 let win = data[offset + 1];
599 offset += 2;
600 (Some(seq), Some(win))
601 } else {
602 (None, None)
603 };
604
605 if offset >= data.len() {
606 return Err(Error::decoding(
607 offset,
608 "ConfirmedRequest missing service choice",
609 ));
610 }
611 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
612 offset += 1;
613
614 let service_request = data.slice(offset..);
615
616 Ok(ConfirmedRequest {
617 segmented,
618 more_follows,
619 segmented_response_accepted,
620 max_segments,
621 max_apdu_length,
622 invoke_id,
623 sequence_number,
624 proposed_window_size,
625 service_choice,
626 service_request,
627 })
628}
629
630#[cfg(feature = "alloc")]
631fn decode_unconfirmed_request(data: Bytes) -> Result<UnconfirmedRequest, Error> {
632 if data.len() < 2 {
633 return Err(Error::buffer_too_short(2, data.len()));
634 }
635
636 let service_choice = UnconfirmedServiceChoice::from_raw(data[1]);
637 let service_request = data.slice(2..);
638
639 Ok(UnconfirmedRequest {
640 service_choice,
641 service_request,
642 })
643}
644
645#[cfg(feature = "alloc")]
646fn decode_simple_ack(data: Bytes) -> Result<SimpleAck, Error> {
647 if data.len() < 3 {
648 return Err(Error::buffer_too_short(3, data.len()));
649 }
650
651 Ok(SimpleAck {
652 invoke_id: data[1],
653 service_choice: ConfirmedServiceChoice::from_raw(data[2]),
654 })
655}
656
657#[cfg(feature = "alloc")]
658fn decode_complex_ack(data: Bytes) -> Result<ComplexAck, Error> {
659 if data.len() < 3 {
660 return Err(Error::buffer_too_short(3, data.len()));
661 }
662
663 let byte0 = data[0];
664 let segmented = byte0 & 0x08 != 0;
665 let more_follows = byte0 & 0x04 != 0;
666
667 let invoke_id = data[1];
668 let mut offset = 2;
669
670 let (sequence_number, proposed_window_size) = if segmented {
671 if data.len() < 5 {
672 return Err(Error::decoding(
673 offset,
674 "segmented ComplexAck too short for sequence/window fields",
675 ));
676 }
677 let seq = data[offset];
678 let win = data[offset + 1];
679 offset += 2;
680 (Some(seq), Some(win))
681 } else {
682 (None, None)
683 };
684
685 if offset >= data.len() {
686 return Err(Error::decoding(offset, "ComplexAck missing service choice"));
687 }
688 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
689 offset += 1;
690
691 let service_ack = data.slice(offset..);
692
693 Ok(ComplexAck {
694 segmented,
695 more_follows,
696 invoke_id,
697 sequence_number,
698 proposed_window_size,
699 service_choice,
700 service_ack,
701 })
702}
703
704#[cfg(feature = "alloc")]
705fn decode_segment_ack(data: Bytes) -> Result<SegmentAck, Error> {
706 if data.len() < 4 {
707 return Err(Error::buffer_too_short(4, data.len()));
708 }
709
710 let byte0 = data[0];
711 let raw_window = data[3];
712 if raw_window == 0 || raw_window > 127 {
713 return Err(DecodeError::new(
714 DecodingKind::InvalidEncoding,
715 3,
716 "SegmentACK actual-window-size {} outside range 1..=127",
717 )
718 .with_value(raw_window.into())
719 .into());
720 }
721 Ok(SegmentAck {
722 negative_ack: byte0 & 0x02 != 0,
723 sent_by_server: byte0 & 0x01 != 0,
724 invoke_id: data[1],
725 sequence_number: data[2],
726 actual_window_size: raw_window,
727 })
728}
729
730#[cfg(feature = "alloc")]
731fn decode_error(data: Bytes) -> Result<ErrorPdu, Error> {
732 if data.len() < 5 {
733 return Err(Error::buffer_too_short(5, data.len()));
734 }
735
736 let invoke_id = data[1];
737 let service_choice = ConfirmedServiceChoice::from_raw(data[2]);
738
739 if let Some((error_class, error_code)) =
740 formal_error::decode_formal_body(service_choice, &data[3..])?
741 {
742 return Ok(ErrorPdu {
743 invoke_id,
744 service_choice,
745 error_class,
746 error_code,
747 error_data: data.slice(3..),
748 });
749 }
750
751 let mut offset = 3;
752 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
753 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
754 return Err(Error::decoding(
755 offset,
756 "ErrorPDU error class: expected application-tagged enumerated",
757 ));
758 }
759 let class_end = tag_end
760 .checked_add(tag.length as usize)
761 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error class length overflow"))?;
762 if class_end > data.len() {
763 return Err(Error::buffer_too_short(class_end, data.len()));
764 }
765 let error_class_raw = primitives::decode_unsigned(&data[tag_end..class_end])?;
766 let error_class_raw = u16::try_from(error_class_raw).map_err(|_| {
767 DecodeError::new(
768 DecodingKind::OutOfRange,
769 tag_end,
770 "ErrorPDU error class {} exceeds u16",
771 )
772 .with_value(error_class_raw)
773 })?;
774 offset = class_end;
775
776 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
777 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
778 return Err(Error::decoding(
779 offset,
780 "ErrorPDU error code: expected application-tagged enumerated",
781 ));
782 }
783 let code_end = tag_end
784 .checked_add(tag.length as usize)
785 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error code length overflow"))?;
786 if code_end > data.len() {
787 return Err(Error::buffer_too_short(code_end, data.len()));
788 }
789 let error_code_raw = primitives::decode_unsigned(&data[tag_end..code_end])?;
790 let error_code_raw = u16::try_from(error_code_raw).map_err(|_| {
791 DecodeError::new(
792 DecodingKind::OutOfRange,
793 tag_end,
794 "ErrorPDU error code {} exceeds u16",
795 )
796 .with_value(error_code_raw)
797 })?;
798 offset = code_end;
799
800 let error_data = if offset < data.len() {
801 data.slice(offset..)
802 } else {
803 Bytes::new()
804 };
805
806 Ok(ErrorPdu {
807 invoke_id,
808 service_choice,
809 error_class: ErrorClass::from_raw(error_class_raw),
810 error_code: ErrorCode::from_raw(error_code_raw),
811 error_data,
812 })
813}
814
815#[cfg(feature = "alloc")]
816fn decode_reject(data: Bytes) -> Result<RejectPdu, Error> {
817 if data.len() < 3 {
818 return Err(Error::buffer_too_short(3, data.len()));
819 }
820
821 Ok(RejectPdu {
822 invoke_id: data[1],
823 reject_reason: RejectReason::from_raw(data[2]),
824 })
825}
826
827#[cfg(feature = "alloc")]
828fn decode_abort(data: Bytes) -> Result<AbortPdu, Error> {
829 if data.len() < 3 {
830 return Err(Error::buffer_too_short(3, data.len()));
831 }
832
833 let byte0 = data[0];
834 Ok(AbortPdu {
835 sent_by_server: byte0 & 0x01 != 0,
836 invoke_id: data[1],
837 abort_reason: AbortReason::from_raw(data[2]),
838 })
839}
840
841#[cfg(all(test, feature = "alloc"))]
846mod change_list_error_tests;
847#[cfg(all(test, feature = "alloc"))]
848mod encode_refusal_tests;
849#[cfg(all(test, feature = "alloc"))]
850mod structured_error_tests;
851#[cfg(all(test, feature = "alloc"))]
852mod tests;
853#[cfg(all(test, feature = "alloc"))]
854mod wpm_error_tests;