1use bacnet_encoding::{primitives, tags};
4use bacnet_types::error::Error;
5use bacnet_types::primitives::ObjectIdentifier;
6use bytes::BytesMut;
7
8use crate::common::MAX_DECODED_ITEMS;
9
10fn checked_slice<'a>(
12 content: &'a [u8],
13 offset: usize,
14 context: &str,
15) -> Result<(&'a [u8], usize), Error> {
16 let (t, p) = tags::decode_tag(content, offset)?;
17 let end = p + t.length as usize;
18 if end > content.len() {
19 return Err(Error::decoding(p, format!("{context} truncated")));
20 }
21 Ok((&content[p..end], end))
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct AtomicReadFileRequest {
31 pub file_identifier: ObjectIdentifier,
32 pub access: FileAccessMethod,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct AtomicWriteFileRequest {
38 pub file_identifier: ObjectIdentifier,
39 pub access: FileWriteAccessMethod,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum FileAccessMethod {
45 Stream {
47 file_start_position: i32,
48 requested_octet_count: u32,
49 },
50 Record {
52 file_start_record: i32,
53 requested_record_count: u32,
54 },
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum FileWriteAccessMethod {
60 Stream {
62 file_start_position: i32,
63 file_data: Vec<u8>,
64 },
65 Record {
67 file_start_record: i32,
68 record_count: u32,
69 file_record_data: Vec<Vec<u8>>,
70 },
71}
72
73impl AtomicReadFileRequest {
74 pub fn encode(&self, buf: &mut BytesMut) {
75 primitives::encode_app_object_id(buf, &self.file_identifier);
76 match &self.access {
77 FileAccessMethod::Stream {
78 file_start_position,
79 requested_octet_count,
80 } => {
81 tags::encode_opening_tag(buf, 0);
82 primitives::encode_app_signed(buf, *file_start_position);
83 primitives::encode_app_unsigned(buf, *requested_octet_count as u64);
84 tags::encode_closing_tag(buf, 0);
85 }
86 FileAccessMethod::Record {
87 file_start_record,
88 requested_record_count,
89 } => {
90 tags::encode_opening_tag(buf, 1);
91 primitives::encode_app_signed(buf, *file_start_record);
92 primitives::encode_app_unsigned(buf, *requested_record_count as u64);
93 tags::encode_closing_tag(buf, 1);
94 }
95 }
96 }
97
98 pub fn decode(data: &[u8]) -> Result<Self, Error> {
99 let mut offset = 0;
100
101 let (tag, pos) = tags::decode_tag(data, offset)?;
102 let end = pos + tag.length as usize;
103 if end > data.len() {
104 return Err(Error::buffer_too_short(end, data.len()));
105 }
106 let file_identifier = ObjectIdentifier::decode(&data[pos..end])?;
107 offset = end;
108
109 let (tag, tag_end) = tags::decode_tag(data, offset)?;
110 let access = if tag.is_opening_tag(0) {
111 let (content, _) = tags::extract_context_value(data, tag_end, 0)?;
112 let (slice, inner) =
113 checked_slice(content, 0, "AtomicReadFile stream file-start-position")?;
114 let file_start_position = primitives::decode_signed(slice)?;
115 let (slice, _) = checked_slice(
116 content,
117 inner,
118 "AtomicReadFile stream requested-octet-count",
119 )?;
120 let requested_octet_count = primitives::decode_unsigned(slice)? as u32;
121 FileAccessMethod::Stream {
122 file_start_position,
123 requested_octet_count,
124 }
125 } else if tag.is_opening_tag(1) {
126 let (content, _) = tags::extract_context_value(data, tag_end, 1)?;
127 let (slice, inner) =
128 checked_slice(content, 0, "AtomicReadFile record file-start-record")?;
129 let file_start_record = primitives::decode_signed(slice)?;
130 let (slice, _) = checked_slice(
131 content,
132 inner,
133 "AtomicReadFile record requested-record-count",
134 )?;
135 let requested_record_count = primitives::decode_unsigned(slice)? as u32;
136 FileAccessMethod::Record {
137 file_start_record,
138 requested_record_count,
139 }
140 } else {
141 return Err(Error::decoding(offset, "Unknown file access method"));
142 };
143
144 Ok(Self {
145 file_identifier,
146 access,
147 })
148 }
149}
150
151impl AtomicWriteFileRequest {
152 pub fn encode(&self, buf: &mut BytesMut) {
153 primitives::encode_app_object_id(buf, &self.file_identifier);
154 match &self.access {
155 FileWriteAccessMethod::Stream {
156 file_start_position,
157 file_data,
158 } => {
159 tags::encode_opening_tag(buf, 0);
160 primitives::encode_app_signed(buf, *file_start_position);
161 primitives::encode_app_octet_string(buf, file_data);
162 tags::encode_closing_tag(buf, 0);
163 }
164 FileWriteAccessMethod::Record {
165 file_start_record,
166 record_count,
167 file_record_data,
168 } => {
169 tags::encode_opening_tag(buf, 1);
170 primitives::encode_app_signed(buf, *file_start_record);
171 primitives::encode_app_unsigned(buf, *record_count as u64);
172 for record in file_record_data {
173 primitives::encode_app_octet_string(buf, record);
174 }
175 tags::encode_closing_tag(buf, 1);
176 }
177 }
178 }
179
180 pub fn decode(data: &[u8]) -> Result<Self, Error> {
181 let mut offset = 0;
182
183 let (tag, pos) = tags::decode_tag(data, offset)?;
184 let end = pos + tag.length as usize;
185 if end > data.len() {
186 return Err(Error::buffer_too_short(end, data.len()));
187 }
188 let file_identifier = ObjectIdentifier::decode(&data[pos..end])?;
189 offset = end;
190
191 let (tag, tag_end) = tags::decode_tag(data, offset)?;
192 let access = if tag.is_opening_tag(0) {
193 let (content, _) = tags::extract_context_value(data, tag_end, 0)?;
194 let (slice, inner) =
195 checked_slice(content, 0, "AtomicWriteFile stream file-start-position")?;
196 let file_start_position = primitives::decode_signed(slice)?;
197 let (slice, _) = checked_slice(content, inner, "AtomicWriteFile stream file-data")?;
198 let file_data = slice.to_vec();
199 FileWriteAccessMethod::Stream {
200 file_start_position,
201 file_data,
202 }
203 } else if tag.is_opening_tag(1) {
204 let (content, _) = tags::extract_context_value(data, tag_end, 1)?;
205 let (slice, mut inner) =
206 checked_slice(content, 0, "AtomicWriteFile record file-start-record")?;
207 let file_start_record = primitives::decode_signed(slice)?;
208 let (slice, new_inner) =
209 checked_slice(content, inner, "AtomicWriteFile record record-count")?;
210 let record_count = primitives::decode_unsigned(slice)? as u32;
211 inner = new_inner;
212 if record_count as usize > MAX_DECODED_ITEMS {
213 return Err(Error::decoding(0, "record count exceeds maximum"));
214 }
215 let mut file_record_data = Vec::new();
216 for i in 0..record_count {
217 if inner >= content.len() {
218 break;
219 }
220 let (slice, new_inner) =
221 checked_slice(content, inner, &format!("AtomicWriteFile record data[{i}]"))?;
222 file_record_data.push(slice.to_vec());
223 inner = new_inner;
224 }
225 FileWriteAccessMethod::Record {
226 file_start_record,
227 record_count,
228 file_record_data,
229 }
230 } else {
231 return Err(Error::decoding(offset, "Unknown file write access method"));
232 };
233
234 Ok(Self {
235 file_identifier,
236 access,
237 })
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct AtomicReadFileAck {
248 pub end_of_file: bool,
249 pub access: FileReadAckMethod,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum FileReadAckMethod {
255 Stream {
257 file_start_position: i32,
258 file_data: Vec<u8>,
259 },
260 Record {
262 file_start_record: i32,
263 returned_record_count: u32,
264 file_record_data: Vec<Vec<u8>>,
265 },
266}
267
268impl AtomicReadFileAck {
269 pub fn encode(&self, buf: &mut BytesMut) {
270 primitives::encode_app_boolean(buf, self.end_of_file);
271 match &self.access {
272 FileReadAckMethod::Stream {
273 file_start_position,
274 file_data,
275 } => {
276 tags::encode_opening_tag(buf, 0);
277 primitives::encode_app_signed(buf, *file_start_position);
278 primitives::encode_app_octet_string(buf, file_data);
279 tags::encode_closing_tag(buf, 0);
280 }
281 FileReadAckMethod::Record {
282 file_start_record,
283 returned_record_count,
284 file_record_data,
285 } => {
286 tags::encode_opening_tag(buf, 1);
287 primitives::encode_app_signed(buf, *file_start_record);
288 primitives::encode_app_unsigned(buf, *returned_record_count as u64);
289 for record in file_record_data {
290 primitives::encode_app_octet_string(buf, record);
291 }
292 tags::encode_closing_tag(buf, 1);
293 }
294 }
295 }
296
297 pub fn decode(data: &[u8]) -> Result<Self, Error> {
298 let mut offset = 0;
299
300 let (tag, pos) = tags::decode_tag(data, offset)?;
301 let end_of_file = tag.length != 0;
302 offset = pos;
303
304 let (tag, tag_end) = tags::decode_tag(data, offset)?;
305 let access = if tag.is_opening_tag(0) {
306 let (content, _) = tags::extract_context_value(data, tag_end, 0)?;
307 let (slice, inner) =
308 checked_slice(content, 0, "AtomicReadFileAck stream file-start-position")?;
309 let file_start_position = primitives::decode_signed(slice)?;
310 let (slice, _) = checked_slice(content, inner, "AtomicReadFileAck stream file-data")?;
311 let file_data = slice.to_vec();
312 FileReadAckMethod::Stream {
313 file_start_position,
314 file_data,
315 }
316 } else if tag.is_opening_tag(1) {
317 let (content, _) = tags::extract_context_value(data, tag_end, 1)?;
318 let (slice, mut inner) =
319 checked_slice(content, 0, "AtomicReadFileAck record file-start-record")?;
320 let file_start_record = primitives::decode_signed(slice)?;
321 let (slice, new_inner) = checked_slice(
322 content,
323 inner,
324 "AtomicReadFileAck record returned-record-count",
325 )?;
326 let returned_record_count = primitives::decode_unsigned(slice)? as u32;
327 inner = new_inner;
328 if returned_record_count as usize > MAX_DECODED_ITEMS {
329 return Err(Error::decoding(0, "record count exceeds maximum"));
330 }
331 let mut file_record_data = Vec::new();
332 for i in 0..returned_record_count {
333 if inner >= content.len() {
334 break;
335 }
336 let (slice, new_inner) = checked_slice(
337 content,
338 inner,
339 &format!("AtomicReadFileAck record data[{i}]"),
340 )?;
341 file_record_data.push(slice.to_vec());
342 inner = new_inner;
343 }
344 FileReadAckMethod::Record {
345 file_start_record,
346 returned_record_count,
347 file_record_data,
348 }
349 } else {
350 return Err(Error::decoding(
351 offset,
352 "Unknown read file ACK access method",
353 ));
354 };
355
356 Ok(Self {
357 end_of_file,
358 access,
359 })
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct AtomicWriteFileAck {
370 pub access: FileWriteAckMethod,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq)]
375pub enum FileWriteAckMethod {
376 Stream { file_start_position: i32 },
378 Record { file_start_record: i32 },
380}
381
382impl AtomicWriteFileAck {
383 pub fn encode(&self, buf: &mut BytesMut) {
384 match &self.access {
385 FileWriteAckMethod::Stream {
386 file_start_position,
387 } => {
388 primitives::encode_ctx_signed(buf, 0, *file_start_position);
389 }
390 FileWriteAckMethod::Record { file_start_record } => {
391 primitives::encode_ctx_signed(buf, 1, *file_start_record);
392 }
393 }
394 }
395
396 pub fn decode(data: &[u8]) -> Result<Self, Error> {
397 let (tag, pos) = tags::decode_tag(data, 0)?;
398 let end = pos + tag.length as usize;
399 if end > data.len() {
400 return Err(Error::buffer_too_short(end, data.len()));
401 }
402 let access = if tag.is_context(0) {
403 let file_start_position = primitives::decode_signed(&data[pos..end])?;
404 FileWriteAckMethod::Stream {
405 file_start_position,
406 }
407 } else if tag.is_context(1) {
408 let file_start_record = primitives::decode_signed(&data[pos..end])?;
409 FileWriteAckMethod::Record { file_start_record }
410 } else {
411 return Err(Error::decoding(0, "Unknown write file ACK access method"));
412 };
413
414 Ok(Self { access })
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use bacnet_types::enums::ObjectType;
422
423 fn file_oid() -> ObjectIdentifier {
424 ObjectIdentifier::new(ObjectType::FILE, 1).unwrap()
425 }
426
427 #[test]
428 fn atomic_read_stream_round_trip() {
429 let req = AtomicReadFileRequest {
430 file_identifier: file_oid(),
431 access: FileAccessMethod::Stream {
432 file_start_position: 0,
433 requested_octet_count: 1024,
434 },
435 };
436 let mut buf = BytesMut::new();
437 req.encode(&mut buf);
438 let decoded = AtomicReadFileRequest::decode(&buf).unwrap();
439 assert_eq!(decoded, req);
440 }
441
442 #[test]
443 fn atomic_read_record_round_trip() {
444 let req = AtomicReadFileRequest {
445 file_identifier: file_oid(),
446 access: FileAccessMethod::Record {
447 file_start_record: 5,
448 requested_record_count: 10,
449 },
450 };
451 let mut buf = BytesMut::new();
452 req.encode(&mut buf);
453 let decoded = AtomicReadFileRequest::decode(&buf).unwrap();
454 assert_eq!(decoded, req);
455 }
456
457 #[test]
458 fn atomic_write_stream_round_trip() {
459 let req = AtomicWriteFileRequest {
460 file_identifier: file_oid(),
461 access: FileWriteAccessMethod::Stream {
462 file_start_position: 100,
463 file_data: vec![0x01, 0x02, 0x03, 0x04],
464 },
465 };
466 let mut buf = BytesMut::new();
467 req.encode(&mut buf);
468 let decoded = AtomicWriteFileRequest::decode(&buf).unwrap();
469 assert_eq!(decoded, req);
470 }
471
472 #[test]
473 fn atomic_write_record_round_trip() {
474 let req = AtomicWriteFileRequest {
475 file_identifier: file_oid(),
476 access: FileWriteAccessMethod::Record {
477 file_start_record: 0,
478 record_count: 2,
479 file_record_data: vec![vec![0xAA, 0xBB], vec![0xCC, 0xDD]],
480 },
481 };
482 let mut buf = BytesMut::new();
483 req.encode(&mut buf);
484 let decoded = AtomicWriteFileRequest::decode(&buf).unwrap();
485 assert_eq!(decoded, req);
486 }
487
488 #[test]
493 fn test_decode_atomic_read_file_empty_input() {
494 assert!(AtomicReadFileRequest::decode(&[]).is_err());
495 }
496
497 #[test]
498 fn test_decode_atomic_read_file_truncated_1_byte() {
499 let req = AtomicReadFileRequest {
500 file_identifier: file_oid(),
501 access: FileAccessMethod::Stream {
502 file_start_position: 0,
503 requested_octet_count: 1024,
504 },
505 };
506 let mut buf = BytesMut::new();
507 req.encode(&mut buf);
508 assert!(AtomicReadFileRequest::decode(&buf[..1]).is_err());
509 }
510
511 #[test]
512 fn test_decode_atomic_read_file_truncated_3_bytes() {
513 let req = AtomicReadFileRequest {
514 file_identifier: file_oid(),
515 access: FileAccessMethod::Stream {
516 file_start_position: 0,
517 requested_octet_count: 1024,
518 },
519 };
520 let mut buf = BytesMut::new();
521 req.encode(&mut buf);
522 assert!(AtomicReadFileRequest::decode(&buf[..3]).is_err());
523 }
524
525 #[test]
526 fn test_decode_atomic_read_file_truncated_half() {
527 let req = AtomicReadFileRequest {
528 file_identifier: file_oid(),
529 access: FileAccessMethod::Stream {
530 file_start_position: 0,
531 requested_octet_count: 1024,
532 },
533 };
534 let mut buf = BytesMut::new();
535 req.encode(&mut buf);
536 let half = buf.len() / 2;
537 assert!(AtomicReadFileRequest::decode(&buf[..half]).is_err());
538 }
539
540 #[test]
541 fn test_decode_atomic_read_file_invalid_tag() {
542 assert!(AtomicReadFileRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
543 }
544
545 #[test]
546 fn test_decode_atomic_write_file_empty_input() {
547 assert!(AtomicWriteFileRequest::decode(&[]).is_err());
548 }
549
550 #[test]
551 fn test_decode_atomic_write_file_truncated_1_byte() {
552 let req = AtomicWriteFileRequest {
553 file_identifier: file_oid(),
554 access: FileWriteAccessMethod::Stream {
555 file_start_position: 100,
556 file_data: vec![0x01, 0x02, 0x03, 0x04],
557 },
558 };
559 let mut buf = BytesMut::new();
560 req.encode(&mut buf);
561 assert!(AtomicWriteFileRequest::decode(&buf[..1]).is_err());
562 }
563
564 #[test]
565 fn test_decode_atomic_write_file_truncated_3_bytes() {
566 let req = AtomicWriteFileRequest {
567 file_identifier: file_oid(),
568 access: FileWriteAccessMethod::Stream {
569 file_start_position: 100,
570 file_data: vec![0x01, 0x02, 0x03, 0x04],
571 },
572 };
573 let mut buf = BytesMut::new();
574 req.encode(&mut buf);
575 assert!(AtomicWriteFileRequest::decode(&buf[..3]).is_err());
576 }
577
578 #[test]
579 fn test_decode_atomic_write_file_truncated_half() {
580 let req = AtomicWriteFileRequest {
581 file_identifier: file_oid(),
582 access: FileWriteAccessMethod::Stream {
583 file_start_position: 100,
584 file_data: vec![0x01, 0x02, 0x03, 0x04],
585 },
586 };
587 let mut buf = BytesMut::new();
588 req.encode(&mut buf);
589 let half = buf.len() / 2;
590 assert!(AtomicWriteFileRequest::decode(&buf[..half]).is_err());
591 }
592
593 #[test]
594 fn test_decode_atomic_write_file_invalid_tag() {
595 assert!(AtomicWriteFileRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
596 }
597
598 #[test]
599 fn atomic_read_file_request_truncated_inner_tag() {
600 let data = [
604 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E,
607 0x35, 50, 0x01, 0x02, 0x0F,
611 ];
612 assert!(AtomicReadFileRequest::decode(&data).is_err());
613 }
614
615 #[test]
616 fn atomic_write_file_request_truncated_inner_tag() {
617 let data = [
619 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E, 0x35, 80, 0x01, 0x0F,
625 ];
626 assert!(AtomicWriteFileRequest::decode(&data).is_err());
627 }
628
629 #[test]
634 fn atomic_read_file_ack_stream_round_trip() {
635 let ack = AtomicReadFileAck {
636 end_of_file: false,
637 access: FileReadAckMethod::Stream {
638 file_start_position: 0,
639 file_data: vec![0x48, 0x65, 0x6C, 0x6C, 0x6F],
640 },
641 };
642 let mut buf = BytesMut::new();
643 ack.encode(&mut buf);
644 let decoded = AtomicReadFileAck::decode(&buf).unwrap();
645 assert_eq!(decoded, ack);
646 }
647
648 #[test]
649 fn atomic_read_file_ack_stream_eof_true() {
650 let ack = AtomicReadFileAck {
651 end_of_file: true,
652 access: FileReadAckMethod::Stream {
653 file_start_position: 512,
654 file_data: vec![0xDE, 0xAD],
655 },
656 };
657 let mut buf = BytesMut::new();
658 ack.encode(&mut buf);
659 let decoded = AtomicReadFileAck::decode(&buf).unwrap();
660 assert_eq!(decoded, ack);
661 }
662
663 #[test]
664 fn atomic_read_file_ack_record_round_trip() {
665 let ack = AtomicReadFileAck {
666 end_of_file: true,
667 access: FileReadAckMethod::Record {
668 file_start_record: 5,
669 returned_record_count: 2,
670 file_record_data: vec![vec![0xAA, 0xBB], vec![0xCC, 0xDD, 0xEE]],
671 },
672 };
673 let mut buf = BytesMut::new();
674 ack.encode(&mut buf);
675 let decoded = AtomicReadFileAck::decode(&buf).unwrap();
676 assert_eq!(decoded, ack);
677 }
678
679 #[test]
680 fn atomic_read_file_ack_record_empty() {
681 let ack = AtomicReadFileAck {
682 end_of_file: true,
683 access: FileReadAckMethod::Record {
684 file_start_record: 0,
685 returned_record_count: 0,
686 file_record_data: vec![],
687 },
688 };
689 let mut buf = BytesMut::new();
690 ack.encode(&mut buf);
691 let decoded = AtomicReadFileAck::decode(&buf).unwrap();
692 assert_eq!(decoded, ack);
693 }
694
695 #[test]
700 fn atomic_write_file_ack_stream_round_trip() {
701 let ack = AtomicWriteFileAck {
702 access: FileWriteAckMethod::Stream {
703 file_start_position: 100,
704 },
705 };
706 let mut buf = BytesMut::new();
707 ack.encode(&mut buf);
708 let decoded = AtomicWriteFileAck::decode(&buf).unwrap();
709 assert_eq!(decoded, ack);
710 }
711
712 #[test]
713 fn atomic_write_file_ack_stream_negative_position() {
714 let ack = AtomicWriteFileAck {
715 access: FileWriteAckMethod::Stream {
716 file_start_position: -1,
717 },
718 };
719 let mut buf = BytesMut::new();
720 ack.encode(&mut buf);
721 let decoded = AtomicWriteFileAck::decode(&buf).unwrap();
722 assert_eq!(decoded, ack);
723 }
724
725 #[test]
726 fn atomic_write_file_ack_record_round_trip() {
727 let ack = AtomicWriteFileAck {
728 access: FileWriteAckMethod::Record {
729 file_start_record: 42,
730 },
731 };
732 let mut buf = BytesMut::new();
733 ack.encode(&mut buf);
734 let decoded = AtomicWriteFileAck::decode(&buf).unwrap();
735 assert_eq!(decoded, ack);
736 }
737
738 #[test]
743 fn test_decode_read_file_ack_empty_input() {
744 assert!(AtomicReadFileAck::decode(&[]).is_err());
745 }
746
747 #[test]
748 fn test_decode_read_file_ack_truncated() {
749 let ack = AtomicReadFileAck {
750 end_of_file: false,
751 access: FileReadAckMethod::Stream {
752 file_start_position: 0,
753 file_data: vec![0x01, 0x02, 0x03],
754 },
755 };
756 let mut buf = BytesMut::new();
757 ack.encode(&mut buf);
758 assert!(AtomicReadFileAck::decode(&buf[..1]).is_err());
760 let half = buf.len() / 2;
762 assert!(AtomicReadFileAck::decode(&buf[..half]).is_err());
763 }
764
765 #[test]
766 fn test_decode_write_file_ack_empty_input() {
767 assert!(AtomicWriteFileAck::decode(&[]).is_err());
768 }
769
770 #[test]
771 fn test_decode_write_file_ack_truncated() {
772 let ack = AtomicWriteFileAck {
773 access: FileWriteAckMethod::Stream {
774 file_start_position: 100,
775 },
776 };
777 let mut buf = BytesMut::new();
778 ack.encode(&mut buf);
779 if buf.len() > 1 {
781 assert!(AtomicWriteFileAck::decode(&buf[..1]).is_err());
782 }
783 }
784}