1use bacnet_types::enums::{
14 AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, PduType, RejectReason,
15 UnconfirmedServiceChoice,
16};
17use bacnet_types::error::Error;
18use bytes::{BufMut, Bytes, BytesMut};
19
20use crate::primitives;
21use crate::tags;
22
23mod wpm_error;
24
25const MAX_SEGMENTS_DECODE: [Option<u8>; 8] = [
32 None, Some(2), Some(4), Some(8), Some(16), Some(32), Some(64), Some(255), ];
41
42pub fn validate_max_segments(value: Option<u8>) -> Result<(), Error> {
48 match value {
49 Some(value @ 0..=1) => Err(Error::Encoding(format!(
50 "invalid max-segments-accepted {value}; expected 2..=255 or unspecified"
51 ))),
52 _ => Ok(()),
53 }
54}
55
56fn encode_max_segments(value: Option<u8>) -> Result<u8, Error> {
61 validate_max_segments(value)?;
62 Ok(match value {
63 None => 0,
64 Some(2..=3) => 1,
65 Some(4..=7) => 2,
66 Some(8..=15) => 3,
67 Some(16..=31) => 4,
68 Some(32..=63) => 5,
69 Some(64) => 6,
70 Some(65..=u8::MAX) => 7, Some(0..=1) => unreachable!("validated above"),
72 })
73}
74
75fn decode_max_segments(value: u8) -> Option<u8> {
77 MAX_SEGMENTS_DECODE[(value & 0x07) as usize]
78}
79
80pub fn advertised_max_segments(configured: Option<u8>) -> Option<u8> {
94 let encoded = encode_max_segments(configured).ok()?;
95 match decode_max_segments(encoded) {
96 Some(255) => None,
99 decoded => decoded,
100 }
101}
102
103pub const MINIMUM_MESSAGE_SIZE: u16 = 50;
115
116const MAX_APDU_DECODE: [u16; 6] = [MINIMUM_MESSAGE_SIZE, 128, 206, 480, 1024, 1476];
118
119pub fn is_valid_max_apdu_length(value: u16) -> bool {
122 matches!(value, 50 | 128 | 206 | 480 | 1024 | 1476)
123}
124
125pub fn validate_max_apdu_length(value: u16) -> Result<(), Error> {
127 if is_valid_max_apdu_length(value) {
128 Ok(())
129 } else {
130 Err(Error::Encoding(format!(
131 "invalid max-APDU-length {value}; expected one of 50, 128, 206, 480, 1024, 1476"
132 )))
133 }
134}
135
136fn encode_max_apdu(value: u16) -> Result<u8, Error> {
138 validate_max_apdu_length(value)?;
139 match value {
140 50 => Ok(0),
141 128 => Ok(1),
142 206 => Ok(2),
143 480 => Ok(3),
144 1024 => Ok(4),
145 1476 => Ok(5),
146 _ => unreachable!("validated max-APDU-length"),
147 }
148}
149
150fn decode_max_apdu(value: u8) -> Result<u16, Error> {
152 let idx = (value & 0x0F) as usize;
153 if idx < MAX_APDU_DECODE.len() {
154 Ok(MAX_APDU_DECODE[idx])
155 } else {
156 Err(Error::decoding(
157 1,
158 format!("reserved max-APDU-length field value {idx}"),
159 ))
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct ConfirmedRequest {
170 pub segmented: bool,
171 pub more_follows: bool,
172 pub segmented_response_accepted: bool,
173 pub max_segments: Option<u8>,
174 pub max_apdu_length: u16,
175 pub invoke_id: u8,
176 pub sequence_number: Option<u8>,
177 pub proposed_window_size: Option<u8>,
178 pub service_choice: ConfirmedServiceChoice,
179 pub service_request: Bytes,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct UnconfirmedRequest {
185 pub service_choice: UnconfirmedServiceChoice,
186 pub service_request: Bytes,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct SimpleAck {
192 pub invoke_id: u8,
193 pub service_choice: ConfirmedServiceChoice,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct ComplexAck {
199 pub segmented: bool,
200 pub more_follows: bool,
201 pub invoke_id: u8,
202 pub sequence_number: Option<u8>,
203 pub proposed_window_size: Option<u8>,
204 pub service_choice: ConfirmedServiceChoice,
205 pub service_ack: Bytes,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct SegmentAck {
211 pub negative_ack: bool,
212 pub sent_by_server: bool,
213 pub invoke_id: u8,
214 pub sequence_number: u8,
215 pub actual_window_size: u8,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct ErrorPdu {
221 pub invoke_id: u8,
222 pub service_choice: ConfirmedServiceChoice,
223 pub error_class: ErrorClass,
224 pub error_code: ErrorCode,
225 pub error_data: Bytes,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct RejectPdu {
231 pub invoke_id: u8,
232 pub reject_reason: RejectReason,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct AbortPdu {
238 pub sent_by_server: bool,
239 pub invoke_id: u8,
240 pub abort_reason: AbortReason,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum Apdu {
246 ConfirmedRequest(ConfirmedRequest),
247 UnconfirmedRequest(UnconfirmedRequest),
248 SimpleAck(SimpleAck),
249 ComplexAck(ComplexAck),
250 SegmentAck(SegmentAck),
251 Error(ErrorPdu),
252 Reject(RejectPdu),
253 Abort(AbortPdu),
254}
255
256pub fn encode_apdu(buf: &mut BytesMut, apdu: &Apdu) -> Result<(), Error> {
262 match apdu {
263 Apdu::ConfirmedRequest(pdu) => encode_confirmed_request(buf, pdu),
264 Apdu::UnconfirmedRequest(pdu) => {
265 encode_unconfirmed_request(buf, pdu);
266 Ok(())
267 }
268 Apdu::SimpleAck(pdu) => {
269 encode_simple_ack(buf, pdu);
270 Ok(())
271 }
272 Apdu::ComplexAck(pdu) => encode_complex_ack(buf, pdu),
273 Apdu::SegmentAck(pdu) => encode_segment_ack(buf, pdu),
274 Apdu::Error(pdu) => encode_error(buf, pdu),
275 Apdu::Reject(pdu) => {
276 encode_reject(buf, pdu);
277 Ok(())
278 }
279 Apdu::Abort(pdu) => {
280 encode_abort(buf, pdu);
281 Ok(())
282 }
283 }
284}
285
286fn encode_confirmed_request(buf: &mut BytesMut, pdu: &ConfirmedRequest) -> Result<(), Error> {
287 let max_segments = encode_max_segments(pdu.max_segments)?;
288 let max_apdu = encode_max_apdu(pdu.max_apdu_length)?;
289
290 let mut byte0 = PduType::CONFIRMED_REQUEST.to_raw() << 4;
291 if pdu.segmented {
292 byte0 |= 0x08;
293 }
294 if pdu.more_follows {
295 byte0 |= 0x04;
296 }
297 if pdu.segmented_response_accepted {
298 byte0 |= 0x02;
299 }
300 buf.put_u8(byte0);
301
302 let byte1 = (max_segments << 4) | max_apdu;
303 buf.put_u8(byte1);
304
305 buf.put_u8(pdu.invoke_id);
306
307 if pdu.segmented {
308 buf.put_u8(pdu.sequence_number.unwrap_or(0));
309 buf.put_u8(valid_window_size(
310 "ConfirmedRequest proposed-window-size",
311 pdu.proposed_window_size.unwrap_or(1),
312 )?);
313 }
314
315 buf.put_u8(pdu.service_choice.to_raw());
316 buf.put_slice(&pdu.service_request);
317 Ok(())
318}
319
320fn encode_unconfirmed_request(buf: &mut BytesMut, pdu: &UnconfirmedRequest) {
321 buf.put_u8(PduType::UNCONFIRMED_REQUEST.to_raw() << 4);
322 buf.put_u8(pdu.service_choice.to_raw());
323 buf.put_slice(&pdu.service_request);
324}
325
326fn encode_simple_ack(buf: &mut BytesMut, pdu: &SimpleAck) {
327 buf.put_u8(PduType::SIMPLE_ACK.to_raw() << 4);
328 buf.put_u8(pdu.invoke_id);
329 buf.put_u8(pdu.service_choice.to_raw());
330}
331
332fn encode_complex_ack(buf: &mut BytesMut, pdu: &ComplexAck) -> Result<(), Error> {
333 let mut byte0 = PduType::COMPLEX_ACK.to_raw() << 4;
334 if pdu.segmented {
335 byte0 |= 0x08;
336 }
337 if pdu.more_follows {
338 byte0 |= 0x04;
339 }
340 buf.put_u8(byte0);
341
342 buf.put_u8(pdu.invoke_id);
343
344 if pdu.segmented {
345 buf.put_u8(pdu.sequence_number.unwrap_or(0));
346 buf.put_u8(valid_window_size(
347 "ComplexAck proposed-window-size",
348 pdu.proposed_window_size.unwrap_or(1),
349 )?);
350 }
351
352 buf.put_u8(pdu.service_choice.to_raw());
353 buf.put_slice(&pdu.service_ack);
354 Ok(())
355}
356
357fn encode_segment_ack(buf: &mut BytesMut, pdu: &SegmentAck) -> Result<(), Error> {
358 let mut byte0 = PduType::SEGMENT_ACK.to_raw() << 4;
359 if pdu.negative_ack {
360 byte0 |= 0x02;
361 }
362 if pdu.sent_by_server {
363 byte0 |= 0x01;
364 }
365 buf.put_u8(byte0);
366 buf.put_u8(pdu.invoke_id);
367 buf.put_u8(pdu.sequence_number);
368 buf.put_u8(valid_window_size(
369 "SegmentACK actual-window-size",
370 pdu.actual_window_size,
371 )?);
372 Ok(())
373}
374
375fn valid_window_size(field: &str, value: u8) -> Result<u8, Error> {
376 if (1..=127).contains(&value) {
377 Ok(value)
378 } else {
379 Err(Error::Encoding(format!(
380 "{field} {value} outside BACnet range 1..=127"
381 )))
382 }
383}
384
385fn encode_error(buf: &mut BytesMut, pdu: &ErrorPdu) -> Result<(), Error> {
386 let formal = if pdu.service_choice == ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE {
387 wpm_error::decode_formal_body(&pdu.error_data).unwrap_or(None)
391 } else {
392 None
393 };
394 if let Some((error_class, error_code)) = formal {
395 if error_class != pdu.error_class || error_code != pdu.error_code {
396 return Err(Error::Encoding(
397 "formal WPM Error body disagrees with ErrorPdu class/code".into(),
398 ));
399 }
400 }
401
402 buf.put_u8(PduType::ERROR.to_raw() << 4);
403 buf.put_u8(pdu.invoke_id);
404 buf.put_u8(pdu.service_choice.to_raw());
405 if formal.is_some() {
406 buf.put_slice(&pdu.error_data);
407 } else {
408 primitives::encode_app_enumerated(buf, pdu.error_class.to_raw() as u32);
409 primitives::encode_app_enumerated(buf, pdu.error_code.to_raw() as u32);
410 if !pdu.error_data.is_empty() {
411 buf.put_slice(&pdu.error_data);
412 }
413 }
414 Ok(())
415}
416
417fn encode_reject(buf: &mut BytesMut, pdu: &RejectPdu) {
418 buf.put_u8(PduType::REJECT.to_raw() << 4);
419 buf.put_u8(pdu.invoke_id);
420 buf.put_u8(pdu.reject_reason.to_raw());
421}
422
423fn encode_abort(buf: &mut BytesMut, pdu: &AbortPdu) {
424 let mut byte0 = PduType::ABORT.to_raw() << 4;
425 if pdu.sent_by_server {
426 byte0 |= 0x01;
427 }
428 buf.put_u8(byte0);
429 buf.put_u8(pdu.invoke_id);
430 buf.put_u8(pdu.abort_reason.to_raw());
431}
432
433pub fn decode_apdu(data: Bytes) -> Result<Apdu, Error> {
439 if data.is_empty() {
440 return Err(Error::decoding(0, "APDU data is empty"));
441 }
442
443 let pdu_type_raw = (data[0] >> 4) & 0x0F;
444 let pdu_type = PduType::from_raw(pdu_type_raw);
445
446 if pdu_type == PduType::CONFIRMED_REQUEST {
447 decode_confirmed_request(data).map(Apdu::ConfirmedRequest)
448 } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
449 decode_unconfirmed_request(data).map(Apdu::UnconfirmedRequest)
450 } else if pdu_type == PduType::SIMPLE_ACK {
451 decode_simple_ack(data).map(Apdu::SimpleAck)
452 } else if pdu_type == PduType::COMPLEX_ACK {
453 decode_complex_ack(data).map(Apdu::ComplexAck)
454 } else if pdu_type == PduType::SEGMENT_ACK {
455 decode_segment_ack(data).map(Apdu::SegmentAck)
456 } else if pdu_type == PduType::ERROR {
457 decode_error(data).map(Apdu::Error)
458 } else if pdu_type == PduType::REJECT {
459 decode_reject(data).map(Apdu::Reject)
460 } else if pdu_type == PduType::ABORT {
461 decode_abort(data).map(Apdu::Abort)
462 } else {
463 Err(Error::decoding(
464 0,
465 format!("unknown PDU type nibble: {:#x}", pdu_type_raw),
466 ))
467 }
468}
469
470fn decode_confirmed_request(data: Bytes) -> Result<ConfirmedRequest, Error> {
471 if data.len() < 4 {
472 return Err(Error::buffer_too_short(4, data.len()));
473 }
474
475 let byte0 = data[0];
476 let segmented = byte0 & 0x08 != 0;
477 let more_follows = byte0 & 0x04 != 0;
478 let segmented_response_accepted = byte0 & 0x02 != 0;
479
480 let byte1 = data[1];
481 let max_segments = decode_max_segments((byte1 >> 4) & 0x07);
482 let max_apdu_length = decode_max_apdu(byte1 & 0x0F)?;
483
484 let invoke_id = data[2];
485 let mut offset = 3;
486
487 let (sequence_number, proposed_window_size) = if segmented {
488 if data.len() < 6 {
489 return Err(Error::decoding(
490 offset,
491 "segmented ConfirmedRequest too short for sequence/window fields",
492 ));
493 }
494 let seq = data[offset];
495 let win = data[offset + 1];
496 offset += 2;
497 (Some(seq), Some(win))
498 } else {
499 (None, None)
500 };
501
502 if offset >= data.len() {
503 return Err(Error::decoding(
504 offset,
505 "ConfirmedRequest missing service choice",
506 ));
507 }
508 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
509 offset += 1;
510
511 let service_request = data.slice(offset..);
512
513 Ok(ConfirmedRequest {
514 segmented,
515 more_follows,
516 segmented_response_accepted,
517 max_segments,
518 max_apdu_length,
519 invoke_id,
520 sequence_number,
521 proposed_window_size,
522 service_choice,
523 service_request,
524 })
525}
526
527fn decode_unconfirmed_request(data: Bytes) -> Result<UnconfirmedRequest, Error> {
528 if data.len() < 2 {
529 return Err(Error::buffer_too_short(2, data.len()));
530 }
531
532 let service_choice = UnconfirmedServiceChoice::from_raw(data[1]);
533 let service_request = data.slice(2..);
534
535 Ok(UnconfirmedRequest {
536 service_choice,
537 service_request,
538 })
539}
540
541fn decode_simple_ack(data: Bytes) -> Result<SimpleAck, Error> {
542 if data.len() < 3 {
543 return Err(Error::buffer_too_short(3, data.len()));
544 }
545
546 Ok(SimpleAck {
547 invoke_id: data[1],
548 service_choice: ConfirmedServiceChoice::from_raw(data[2]),
549 })
550}
551
552fn decode_complex_ack(data: Bytes) -> Result<ComplexAck, Error> {
553 if data.len() < 3 {
554 return Err(Error::buffer_too_short(3, data.len()));
555 }
556
557 let byte0 = data[0];
558 let segmented = byte0 & 0x08 != 0;
559 let more_follows = byte0 & 0x04 != 0;
560
561 let invoke_id = data[1];
562 let mut offset = 2;
563
564 let (sequence_number, proposed_window_size) = if segmented {
565 if data.len() < 5 {
566 return Err(Error::decoding(
567 offset,
568 "segmented ComplexAck too short for sequence/window fields",
569 ));
570 }
571 let seq = data[offset];
572 let win = data[offset + 1];
573 offset += 2;
574 (Some(seq), Some(win))
575 } else {
576 (None, None)
577 };
578
579 if offset >= data.len() {
580 return Err(Error::decoding(offset, "ComplexAck missing service choice"));
581 }
582 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
583 offset += 1;
584
585 let service_ack = data.slice(offset..);
586
587 Ok(ComplexAck {
588 segmented,
589 more_follows,
590 invoke_id,
591 sequence_number,
592 proposed_window_size,
593 service_choice,
594 service_ack,
595 })
596}
597
598fn decode_segment_ack(data: Bytes) -> Result<SegmentAck, Error> {
599 if data.len() < 4 {
600 return Err(Error::buffer_too_short(4, data.len()));
601 }
602
603 let byte0 = data[0];
604 let raw_window = data[3];
605 if raw_window == 0 || raw_window > 127 {
606 return Err(Error::decoding(
607 3,
608 format!("SegmentACK actual-window-size {raw_window} outside range 1..=127"),
609 ));
610 }
611 Ok(SegmentAck {
612 negative_ack: byte0 & 0x02 != 0,
613 sent_by_server: byte0 & 0x01 != 0,
614 invoke_id: data[1],
615 sequence_number: data[2],
616 actual_window_size: raw_window,
617 })
618}
619
620fn decode_error(data: Bytes) -> Result<ErrorPdu, Error> {
621 if data.len() < 5 {
622 return Err(Error::buffer_too_short(5, data.len()));
623 }
624
625 let invoke_id = data[1];
626 let service_choice = ConfirmedServiceChoice::from_raw(data[2]);
627
628 if service_choice == ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE {
629 if let Some((error_class, error_code)) = wpm_error::decode_formal_body(&data[3..])? {
630 return Ok(ErrorPdu {
631 invoke_id,
632 service_choice,
633 error_class,
634 error_code,
635 error_data: data.slice(3..),
636 });
637 }
638 }
639
640 let mut offset = 3;
641 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
642 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
643 return Err(Error::decoding(
644 offset,
645 "ErrorPDU error class: expected application-tagged enumerated",
646 ));
647 }
648 let class_end = tag_end
649 .checked_add(tag.length as usize)
650 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error class length overflow"))?;
651 if class_end > data.len() {
652 return Err(Error::decoding(
653 tag_end,
654 "ErrorPDU truncated at error class",
655 ));
656 }
657 let error_class_raw = primitives::decode_unsigned(&data[tag_end..class_end])?;
658 let error_class_raw = u16::try_from(error_class_raw).map_err(|_| {
659 Error::decoding(
660 tag_end,
661 format!("ErrorPDU error class {error_class_raw} exceeds u16"),
662 )
663 })?;
664 offset = class_end;
665
666 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
667 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
668 return Err(Error::decoding(
669 offset,
670 "ErrorPDU error code: expected application-tagged enumerated",
671 ));
672 }
673 let code_end = tag_end
674 .checked_add(tag.length as usize)
675 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error code length overflow"))?;
676 if code_end > data.len() {
677 return Err(Error::decoding(tag_end, "ErrorPDU truncated at error code"));
678 }
679 let error_code_raw = primitives::decode_unsigned(&data[tag_end..code_end])?;
680 let error_code_raw = u16::try_from(error_code_raw).map_err(|_| {
681 Error::decoding(
682 tag_end,
683 format!("ErrorPDU error code {error_code_raw} exceeds u16"),
684 )
685 })?;
686 offset = code_end;
687
688 let error_data = if offset < data.len() {
689 data.slice(offset..)
690 } else {
691 Bytes::new()
692 };
693
694 Ok(ErrorPdu {
695 invoke_id,
696 service_choice,
697 error_class: ErrorClass::from_raw(error_class_raw),
698 error_code: ErrorCode::from_raw(error_code_raw),
699 error_data,
700 })
701}
702
703fn decode_reject(data: Bytes) -> Result<RejectPdu, Error> {
704 if data.len() < 3 {
705 return Err(Error::buffer_too_short(3, data.len()));
706 }
707
708 Ok(RejectPdu {
709 invoke_id: data[1],
710 reject_reason: RejectReason::from_raw(data[2]),
711 })
712}
713
714fn decode_abort(data: Bytes) -> Result<AbortPdu, Error> {
715 if data.len() < 3 {
716 return Err(Error::buffer_too_short(3, data.len()));
717 }
718
719 let byte0 = data[0];
720 Ok(AbortPdu {
721 sent_by_server: byte0 & 0x01 != 0,
722 invoke_id: data[1],
723 abort_reason: AbortReason::from_raw(data[2]),
724 })
725}
726
727#[cfg(test)]
732mod tests;
733#[cfg(test)]
734mod wpm_error_tests;