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
23const MAX_SEGMENTS_DECODE: [Option<u8>; 8] = [
30 None, Some(2), Some(4), Some(8), Some(16), Some(32), Some(64), Some(255), ];
39
40fn 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, }
52}
53
54fn decode_max_segments(value: u8) -> Option<u8> {
56 MAX_SEGMENTS_DECODE[(value & 0x07) as usize]
57}
58
59const MAX_APDU_DECODE: [u16; 6] = [50, 128, 206, 480, 1024, 1476];
65
66pub fn is_valid_max_apdu_length(value: u16) -> bool {
69 matches!(value, 50 | 128 | 206 | 480 | 1024 | 1476)
70}
71
72pub fn validate_max_apdu_length(value: u16) -> Result<(), Error> {
74 if is_valid_max_apdu_length(value) {
75 Ok(())
76 } else {
77 Err(Error::Encoding(format!(
78 "invalid max-APDU-length {value}; expected one of 50, 128, 206, 480, 1024, 1476"
79 )))
80 }
81}
82
83fn encode_max_apdu(value: u16) -> Result<u8, Error> {
85 validate_max_apdu_length(value)?;
86 match value {
87 50 => Ok(0),
88 128 => Ok(1),
89 206 => Ok(2),
90 480 => Ok(3),
91 1024 => Ok(4),
92 1476 => Ok(5),
93 _ => unreachable!("validated max-APDU-length"),
94 }
95}
96
97fn decode_max_apdu(value: u8) -> Result<u16, Error> {
99 let idx = (value & 0x0F) as usize;
100 if idx < MAX_APDU_DECODE.len() {
101 Ok(MAX_APDU_DECODE[idx])
102 } else {
103 Err(Error::decoding(
104 1,
105 format!("reserved max-APDU-length field value {idx}"),
106 ))
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct ConfirmedRequest {
117 pub segmented: bool,
118 pub more_follows: bool,
119 pub segmented_response_accepted: bool,
120 pub max_segments: Option<u8>,
121 pub max_apdu_length: u16,
122 pub invoke_id: u8,
123 pub sequence_number: Option<u8>,
124 pub proposed_window_size: Option<u8>,
125 pub service_choice: ConfirmedServiceChoice,
126 pub service_request: Bytes,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct UnconfirmedRequest {
132 pub service_choice: UnconfirmedServiceChoice,
133 pub service_request: Bytes,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct SimpleAck {
139 pub invoke_id: u8,
140 pub service_choice: ConfirmedServiceChoice,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct ComplexAck {
146 pub segmented: bool,
147 pub more_follows: bool,
148 pub invoke_id: u8,
149 pub sequence_number: Option<u8>,
150 pub proposed_window_size: Option<u8>,
151 pub service_choice: ConfirmedServiceChoice,
152 pub service_ack: Bytes,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct SegmentAck {
158 pub negative_ack: bool,
159 pub sent_by_server: bool,
160 pub invoke_id: u8,
161 pub sequence_number: u8,
162 pub actual_window_size: u8,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct ErrorPdu {
168 pub invoke_id: u8,
169 pub service_choice: ConfirmedServiceChoice,
170 pub error_class: ErrorClass,
171 pub error_code: ErrorCode,
172 pub error_data: Bytes,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct RejectPdu {
178 pub invoke_id: u8,
179 pub reject_reason: RejectReason,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct AbortPdu {
185 pub sent_by_server: bool,
186 pub invoke_id: u8,
187 pub abort_reason: AbortReason,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum Apdu {
193 ConfirmedRequest(ConfirmedRequest),
194 UnconfirmedRequest(UnconfirmedRequest),
195 SimpleAck(SimpleAck),
196 ComplexAck(ComplexAck),
197 SegmentAck(SegmentAck),
198 Error(ErrorPdu),
199 Reject(RejectPdu),
200 Abort(AbortPdu),
201}
202
203pub fn encode_apdu(buf: &mut BytesMut, apdu: &Apdu) -> Result<(), Error> {
209 match apdu {
210 Apdu::ConfirmedRequest(pdu) => encode_confirmed_request(buf, pdu),
211 Apdu::UnconfirmedRequest(pdu) => {
212 encode_unconfirmed_request(buf, pdu);
213 Ok(())
214 }
215 Apdu::SimpleAck(pdu) => {
216 encode_simple_ack(buf, pdu);
217 Ok(())
218 }
219 Apdu::ComplexAck(pdu) => encode_complex_ack(buf, pdu),
220 Apdu::SegmentAck(pdu) => encode_segment_ack(buf, pdu),
221 Apdu::Error(pdu) => {
222 encode_error(buf, pdu);
223 Ok(())
224 }
225 Apdu::Reject(pdu) => {
226 encode_reject(buf, pdu);
227 Ok(())
228 }
229 Apdu::Abort(pdu) => {
230 encode_abort(buf, pdu);
231 Ok(())
232 }
233 }
234}
235
236fn encode_confirmed_request(buf: &mut BytesMut, pdu: &ConfirmedRequest) -> Result<(), Error> {
237 let mut byte0 = PduType::CONFIRMED_REQUEST.to_raw() << 4;
238 if pdu.segmented {
239 byte0 |= 0x08;
240 }
241 if pdu.more_follows {
242 byte0 |= 0x04;
243 }
244 if pdu.segmented_response_accepted {
245 byte0 |= 0x02;
246 }
247 buf.put_u8(byte0);
248
249 let byte1 =
250 (encode_max_segments(pdu.max_segments) << 4) | encode_max_apdu(pdu.max_apdu_length)?;
251 buf.put_u8(byte1);
252
253 buf.put_u8(pdu.invoke_id);
254
255 if pdu.segmented {
256 buf.put_u8(pdu.sequence_number.unwrap_or(0));
257 buf.put_u8(valid_window_size(
258 "ConfirmedRequest proposed-window-size",
259 pdu.proposed_window_size.unwrap_or(1),
260 )?);
261 }
262
263 buf.put_u8(pdu.service_choice.to_raw());
264 buf.put_slice(&pdu.service_request);
265 Ok(())
266}
267
268fn encode_unconfirmed_request(buf: &mut BytesMut, pdu: &UnconfirmedRequest) {
269 buf.put_u8(PduType::UNCONFIRMED_REQUEST.to_raw() << 4);
270 buf.put_u8(pdu.service_choice.to_raw());
271 buf.put_slice(&pdu.service_request);
272}
273
274fn encode_simple_ack(buf: &mut BytesMut, pdu: &SimpleAck) {
275 buf.put_u8(PduType::SIMPLE_ACK.to_raw() << 4);
276 buf.put_u8(pdu.invoke_id);
277 buf.put_u8(pdu.service_choice.to_raw());
278}
279
280fn encode_complex_ack(buf: &mut BytesMut, pdu: &ComplexAck) -> Result<(), Error> {
281 let mut byte0 = PduType::COMPLEX_ACK.to_raw() << 4;
282 if pdu.segmented {
283 byte0 |= 0x08;
284 }
285 if pdu.more_follows {
286 byte0 |= 0x04;
287 }
288 buf.put_u8(byte0);
289
290 buf.put_u8(pdu.invoke_id);
291
292 if pdu.segmented {
293 buf.put_u8(pdu.sequence_number.unwrap_or(0));
294 buf.put_u8(valid_window_size(
295 "ComplexAck proposed-window-size",
296 pdu.proposed_window_size.unwrap_or(1),
297 )?);
298 }
299
300 buf.put_u8(pdu.service_choice.to_raw());
301 buf.put_slice(&pdu.service_ack);
302 Ok(())
303}
304
305fn encode_segment_ack(buf: &mut BytesMut, pdu: &SegmentAck) -> Result<(), Error> {
306 let mut byte0 = PduType::SEGMENT_ACK.to_raw() << 4;
307 if pdu.negative_ack {
308 byte0 |= 0x02;
309 }
310 if pdu.sent_by_server {
311 byte0 |= 0x01;
312 }
313 buf.put_u8(byte0);
314 buf.put_u8(pdu.invoke_id);
315 buf.put_u8(pdu.sequence_number);
316 buf.put_u8(valid_window_size(
317 "SegmentACK actual-window-size",
318 pdu.actual_window_size,
319 )?);
320 Ok(())
321}
322
323fn valid_window_size(field: &str, value: u8) -> Result<u8, Error> {
324 if (1..=127).contains(&value) {
325 Ok(value)
326 } else {
327 Err(Error::Encoding(format!(
328 "{field} {value} outside BACnet range 1..=127"
329 )))
330 }
331}
332
333fn encode_error(buf: &mut BytesMut, pdu: &ErrorPdu) {
334 buf.put_u8(PduType::ERROR.to_raw() << 4);
335 buf.put_u8(pdu.invoke_id);
336 buf.put_u8(pdu.service_choice.to_raw());
337 primitives::encode_app_enumerated(buf, pdu.error_class.to_raw() as u32);
338 primitives::encode_app_enumerated(buf, pdu.error_code.to_raw() as u32);
339 if !pdu.error_data.is_empty() {
340 buf.put_slice(&pdu.error_data);
341 }
342}
343
344fn encode_reject(buf: &mut BytesMut, pdu: &RejectPdu) {
345 buf.put_u8(PduType::REJECT.to_raw() << 4);
346 buf.put_u8(pdu.invoke_id);
347 buf.put_u8(pdu.reject_reason.to_raw());
348}
349
350fn encode_abort(buf: &mut BytesMut, pdu: &AbortPdu) {
351 let mut byte0 = PduType::ABORT.to_raw() << 4;
352 if pdu.sent_by_server {
353 byte0 |= 0x01;
354 }
355 buf.put_u8(byte0);
356 buf.put_u8(pdu.invoke_id);
357 buf.put_u8(pdu.abort_reason.to_raw());
358}
359
360pub fn decode_apdu(data: Bytes) -> Result<Apdu, Error> {
366 if data.is_empty() {
367 return Err(Error::decoding(0, "APDU data is empty"));
368 }
369
370 let pdu_type_raw = (data[0] >> 4) & 0x0F;
371 let pdu_type = PduType::from_raw(pdu_type_raw);
372
373 if pdu_type == PduType::CONFIRMED_REQUEST {
374 decode_confirmed_request(data).map(Apdu::ConfirmedRequest)
375 } else if pdu_type == PduType::UNCONFIRMED_REQUEST {
376 decode_unconfirmed_request(data).map(Apdu::UnconfirmedRequest)
377 } else if pdu_type == PduType::SIMPLE_ACK {
378 decode_simple_ack(data).map(Apdu::SimpleAck)
379 } else if pdu_type == PduType::COMPLEX_ACK {
380 decode_complex_ack(data).map(Apdu::ComplexAck)
381 } else if pdu_type == PduType::SEGMENT_ACK {
382 decode_segment_ack(data).map(Apdu::SegmentAck)
383 } else if pdu_type == PduType::ERROR {
384 decode_error(data).map(Apdu::Error)
385 } else if pdu_type == PduType::REJECT {
386 decode_reject(data).map(Apdu::Reject)
387 } else if pdu_type == PduType::ABORT {
388 decode_abort(data).map(Apdu::Abort)
389 } else {
390 Err(Error::decoding(
391 0,
392 format!("unknown PDU type nibble: {:#x}", pdu_type_raw),
393 ))
394 }
395}
396
397fn decode_confirmed_request(data: Bytes) -> Result<ConfirmedRequest, Error> {
398 if data.len() < 4 {
399 return Err(Error::buffer_too_short(4, data.len()));
400 }
401
402 let byte0 = data[0];
403 let segmented = byte0 & 0x08 != 0;
404 let more_follows = byte0 & 0x04 != 0;
405 let segmented_response_accepted = byte0 & 0x02 != 0;
406
407 let byte1 = data[1];
408 let max_segments = decode_max_segments((byte1 >> 4) & 0x07);
409 let max_apdu_length = decode_max_apdu(byte1 & 0x0F)?;
410
411 let invoke_id = data[2];
412 let mut offset = 3;
413
414 let (sequence_number, proposed_window_size) = if segmented {
415 if data.len() < 6 {
416 return Err(Error::decoding(
417 offset,
418 "segmented ConfirmedRequest too short for sequence/window fields",
419 ));
420 }
421 let seq = data[offset];
422 let win = data[offset + 1];
423 offset += 2;
424 (Some(seq), Some(win))
425 } else {
426 (None, None)
427 };
428
429 if offset >= data.len() {
430 return Err(Error::decoding(
431 offset,
432 "ConfirmedRequest missing service choice",
433 ));
434 }
435 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
436 offset += 1;
437
438 let service_request = data.slice(offset..);
439
440 Ok(ConfirmedRequest {
441 segmented,
442 more_follows,
443 segmented_response_accepted,
444 max_segments,
445 max_apdu_length,
446 invoke_id,
447 sequence_number,
448 proposed_window_size,
449 service_choice,
450 service_request,
451 })
452}
453
454fn decode_unconfirmed_request(data: Bytes) -> Result<UnconfirmedRequest, Error> {
455 if data.len() < 2 {
456 return Err(Error::buffer_too_short(2, data.len()));
457 }
458
459 let service_choice = UnconfirmedServiceChoice::from_raw(data[1]);
460 let service_request = data.slice(2..);
461
462 Ok(UnconfirmedRequest {
463 service_choice,
464 service_request,
465 })
466}
467
468fn decode_simple_ack(data: Bytes) -> Result<SimpleAck, Error> {
469 if data.len() < 3 {
470 return Err(Error::buffer_too_short(3, data.len()));
471 }
472
473 Ok(SimpleAck {
474 invoke_id: data[1],
475 service_choice: ConfirmedServiceChoice::from_raw(data[2]),
476 })
477}
478
479fn decode_complex_ack(data: Bytes) -> Result<ComplexAck, Error> {
480 if data.len() < 3 {
481 return Err(Error::buffer_too_short(3, data.len()));
482 }
483
484 let byte0 = data[0];
485 let segmented = byte0 & 0x08 != 0;
486 let more_follows = byte0 & 0x04 != 0;
487
488 let invoke_id = data[1];
489 let mut offset = 2;
490
491 let (sequence_number, proposed_window_size) = if segmented {
492 if data.len() < 5 {
493 return Err(Error::decoding(
494 offset,
495 "segmented ComplexAck too short for sequence/window fields",
496 ));
497 }
498 let seq = data[offset];
499 let win = data[offset + 1];
500 offset += 2;
501 (Some(seq), Some(win))
502 } else {
503 (None, None)
504 };
505
506 if offset >= data.len() {
507 return Err(Error::decoding(offset, "ComplexAck missing service choice"));
508 }
509 let service_choice = ConfirmedServiceChoice::from_raw(data[offset]);
510 offset += 1;
511
512 let service_ack = data.slice(offset..);
513
514 Ok(ComplexAck {
515 segmented,
516 more_follows,
517 invoke_id,
518 sequence_number,
519 proposed_window_size,
520 service_choice,
521 service_ack,
522 })
523}
524
525fn decode_segment_ack(data: Bytes) -> Result<SegmentAck, Error> {
526 if data.len() < 4 {
527 return Err(Error::buffer_too_short(4, data.len()));
528 }
529
530 let byte0 = data[0];
531 let raw_window = data[3];
532 if raw_window == 0 || raw_window > 127 {
533 return Err(Error::decoding(
534 3,
535 format!("SegmentACK actual-window-size {raw_window} outside range 1..=127"),
536 ));
537 }
538 Ok(SegmentAck {
539 negative_ack: byte0 & 0x02 != 0,
540 sent_by_server: byte0 & 0x01 != 0,
541 invoke_id: data[1],
542 sequence_number: data[2],
543 actual_window_size: raw_window,
544 })
545}
546
547fn decode_error(data: Bytes) -> Result<ErrorPdu, Error> {
548 if data.len() < 5 {
549 return Err(Error::buffer_too_short(5, data.len()));
550 }
551
552 let invoke_id = data[1];
553 let service_choice = ConfirmedServiceChoice::from_raw(data[2]);
554
555 let mut offset = 3;
556 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
557 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
558 return Err(Error::decoding(
559 offset,
560 "ErrorPDU error class: expected application-tagged enumerated",
561 ));
562 }
563 let class_end = tag_end
564 .checked_add(tag.length as usize)
565 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error class length overflow"))?;
566 if class_end > data.len() {
567 return Err(Error::decoding(
568 tag_end,
569 "ErrorPDU truncated at error class",
570 ));
571 }
572 let error_class_raw = primitives::decode_unsigned(&data[tag_end..class_end])? as u16;
573 offset = class_end;
574
575 let (tag, tag_end) = tags::decode_tag(&data, offset)?;
576 if tag.class != tags::TagClass::Application || tag.number != tags::app_tag::ENUMERATED {
577 return Err(Error::decoding(
578 offset,
579 "ErrorPDU error code: expected application-tagged enumerated",
580 ));
581 }
582 let code_end = tag_end
583 .checked_add(tag.length as usize)
584 .ok_or_else(|| Error::decoding(tag_end, "ErrorPDU error code length overflow"))?;
585 if code_end > data.len() {
586 return Err(Error::decoding(tag_end, "ErrorPDU truncated at error code"));
587 }
588 let error_code_raw = primitives::decode_unsigned(&data[tag_end..code_end])? as u16;
589 offset = code_end;
590
591 let error_data = if offset < data.len() {
592 data.slice(offset..)
593 } else {
594 Bytes::new()
595 };
596
597 Ok(ErrorPdu {
598 invoke_id,
599 service_choice,
600 error_class: ErrorClass::from_raw(error_class_raw),
601 error_code: ErrorCode::from_raw(error_code_raw),
602 error_data,
603 })
604}
605
606fn decode_reject(data: Bytes) -> Result<RejectPdu, Error> {
607 if data.len() < 3 {
608 return Err(Error::buffer_too_short(3, data.len()));
609 }
610
611 Ok(RejectPdu {
612 invoke_id: data[1],
613 reject_reason: RejectReason::from_raw(data[2]),
614 })
615}
616
617fn decode_abort(data: Bytes) -> Result<AbortPdu, Error> {
618 if data.len() < 3 {
619 return Err(Error::buffer_too_short(3, data.len()));
620 }
621
622 let byte0 = data[0];
623 Ok(AbortPdu {
624 sent_by_server: byte0 & 0x01 != 0,
625 invoke_id: data[1],
626 abort_reason: AbortReason::from_raw(data[2]),
627 })
628}
629
630#[cfg(test)]
635mod tests;