1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use bacnet_encoding::constructed::tagged::{
7 decode_app_object_id, decode_app_primitive, decode_app_unsigned, decode_ctx_constructed,
8 decode_ctx_primitive, expect_end, next_is_context, next_is_opening,
9};
10use bacnet_encoding::{primitives, tags};
11use bacnet_types::error::{DecodeError, DecodingKind, Error};
12use bacnet_types::primitives::ObjectIdentifier;
13use bytes::BytesMut;
14
15use crate::common::MAX_DECODED_ITEMS;
16
17fn indexed_record(error: Error, index: u32) -> Error {
21 match (error, u16::try_from(index)) {
22 (Error::Decoding(refused), Ok(index)) => refused.at_item(index).into(),
23 (other, _) => other,
24 }
25}
26
27fn decode_start(data: &[u8], offset: usize, what: &'static str) -> Result<(i32, usize), Error> {
30 let (octets, end) = decode_app_primitive(data, offset, tags::app_tag::SIGNED, what)?;
31 Ok((primitives::decode_signed(octets)?, end))
32}
33
34fn unknown_access_method(data: &[u8], offset: usize, what: &'static str) -> Error {
38 let refused = if offset >= data.len() {
39 DecodeError::new(DecodingKind::Missing, offset, "access method missing")
40 } else {
41 DecodeError::new(DecodingKind::InvalidTag, offset, "unknown access method")
42 };
43 refused.within(what).into()
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct AtomicReadFileRequest {
53 pub file_identifier: ObjectIdentifier,
55 pub access: FileAccessMethod,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct AtomicWriteFileRequest {
62 pub file_identifier: ObjectIdentifier,
64 pub access: FileWriteAccessMethod,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum FileAccessMethod {
71 Stream {
73 file_start_position: i32,
75 requested_octet_count: u32,
77 },
78 Record {
80 file_start_record: i32,
82 requested_record_count: u32,
84 },
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum FileWriteAccessMethod {
90 Stream {
93 file_start_position: i32,
95 file_data: Vec<u8>,
97 },
98 Record {
102 file_start_record: i32,
104 record_count: u32,
107 file_record_data: Vec<Vec<u8>>,
109 },
110}
111
112impl AtomicReadFileRequest {
113 pub fn encode(&self, buf: &mut BytesMut) {
115 primitives::encode_app_object_id(buf, &self.file_identifier);
116 match &self.access {
117 FileAccessMethod::Stream {
118 file_start_position,
119 requested_octet_count,
120 } => {
121 tags::encode_opening_tag(buf, 0);
122 primitives::encode_app_signed(buf, *file_start_position);
123 primitives::encode_app_unsigned(buf, *requested_octet_count as u64);
124 tags::encode_closing_tag(buf, 0);
125 }
126 FileAccessMethod::Record {
127 file_start_record,
128 requested_record_count,
129 } => {
130 tags::encode_opening_tag(buf, 1);
131 primitives::encode_app_signed(buf, *file_start_record);
132 primitives::encode_app_unsigned(buf, *requested_record_count as u64);
133 tags::encode_closing_tag(buf, 1);
134 }
135 }
136 }
137
138 pub fn decode(data: &[u8]) -> Result<Self, Error> {
142 let (file_identifier, offset) =
143 decode_app_object_id(data, 0, "AtomicReadFile file-identifier")?;
144
145 let (access, end) = if next_is_opening(data, offset, 0)? {
146 const WHAT: &str = "AtomicReadFile stream";
147 let (content, end) = decode_ctx_constructed(data, offset, 0, WHAT)?;
148 let (file_start_position, inner) =
149 decode_start(content, 0, "AtomicReadFile stream file-start-position")?;
150 let (requested_octet_count, inner) = decode_app_unsigned::<u32>(
151 content,
152 inner,
153 "AtomicReadFile stream requested-octet-count",
154 )?;
155 expect_end(content, inner, offset, WHAT)?;
156 let access = FileAccessMethod::Stream {
157 file_start_position,
158 requested_octet_count,
159 };
160 (access, end)
161 } else if next_is_opening(data, offset, 1)? {
162 const WHAT: &str = "AtomicReadFile record";
163 let (content, end) = decode_ctx_constructed(data, offset, 1, WHAT)?;
164 let (file_start_record, inner) =
165 decode_start(content, 0, "AtomicReadFile record file-start-record")?;
166 let (requested_record_count, inner) = decode_app_unsigned::<u32>(
167 content,
168 inner,
169 "AtomicReadFile record requested-record-count",
170 )?;
171 expect_end(content, inner, offset, WHAT)?;
172 let access = FileAccessMethod::Record {
173 file_start_record,
174 requested_record_count,
175 };
176 (access, end)
177 } else {
178 return Err(unknown_access_method(data, offset, "AtomicReadFile"));
179 };
180 expect_end(data, end, end, "AtomicReadFile")?;
181
182 Ok(Self {
183 file_identifier,
184 access,
185 })
186 }
187}
188
189impl AtomicWriteFileRequest {
190 pub fn encode(&self, buf: &mut BytesMut) {
192 primitives::encode_app_object_id(buf, &self.file_identifier);
193 match &self.access {
194 FileWriteAccessMethod::Stream {
195 file_start_position,
196 file_data,
197 } => {
198 tags::encode_opening_tag(buf, 0);
199 primitives::encode_app_signed(buf, *file_start_position);
200 primitives::encode_app_octet_string(buf, file_data);
201 tags::encode_closing_tag(buf, 0);
202 }
203 FileWriteAccessMethod::Record {
204 file_start_record,
205 record_count,
206 file_record_data,
207 } => {
208 tags::encode_opening_tag(buf, 1);
209 primitives::encode_app_signed(buf, *file_start_record);
210 primitives::encode_app_unsigned(buf, *record_count as u64);
211 for record in file_record_data {
212 primitives::encode_app_octet_string(buf, record);
213 }
214 tags::encode_closing_tag(buf, 1);
215 }
216 }
217 }
218
219 pub fn decode(data: &[u8]) -> Result<Self, Error> {
225 let (file_identifier, offset) =
226 decode_app_object_id(data, 0, "AtomicWriteFile file-identifier")?;
227
228 let (access, end) = if next_is_opening(data, offset, 0)? {
229 const WHAT: &str = "AtomicWriteFile stream";
230 let (content, end) = decode_ctx_constructed(data, offset, 0, WHAT)?;
231 let (file_start_position, inner) =
232 decode_start(content, 0, "AtomicWriteFile stream file-start-position")?;
233 let (file_data, inner) = decode_app_primitive(
234 content,
235 inner,
236 tags::app_tag::OCTET_STRING,
237 "AtomicWriteFile stream file-data",
238 )?;
239 expect_end(content, inner, offset, WHAT)?;
240 let access = FileWriteAccessMethod::Stream {
241 file_start_position,
242 file_data: file_data.to_vec(),
243 };
244 (access, end)
245 } else if next_is_opening(data, offset, 1)? {
246 let (content, end) = decode_ctx_constructed(data, offset, 1, "AtomicWriteFile record")?;
247 let (file_start_record, mut inner) =
248 decode_start(content, 0, "AtomicWriteFile record file-start-record")?;
249 let (record_count, new_inner) =
250 decode_app_unsigned::<u32>(content, inner, "AtomicWriteFile record record-count")?;
251 inner = new_inner;
252 if record_count as usize > MAX_DECODED_ITEMS {
253 return Err(Error::overflow(0, "record count exceeds maximum"));
254 }
255 let mut file_record_data = Vec::new();
256 for i in 0..record_count {
257 if inner >= content.len() {
258 return Err(DecodeError::new(
259 DecodingKind::Missing,
260 inner,
261 "AtomicWriteFile record: record {} of the count is missing",
262 )
263 .with_value(i.into())
264 .into());
265 }
266 let (record, new_inner) = decode_app_primitive(
267 content,
268 inner,
269 tags::app_tag::OCTET_STRING,
270 "AtomicWriteFile record data",
271 )
272 .map_err(|error| indexed_record(error, i))?;
273 file_record_data.push(record.to_vec());
274 inner = new_inner;
275 }
276 expect_end(content, inner, inner, "AtomicWriteFile record")?;
277 let access = FileWriteAccessMethod::Record {
278 file_start_record,
279 record_count,
280 file_record_data,
281 };
282 (access, end)
283 } else {
284 return Err(unknown_access_method(data, offset, "AtomicWriteFile"));
285 };
286 expect_end(data, end, end, "AtomicWriteFile")?;
287
288 Ok(Self {
289 file_identifier,
290 access,
291 })
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct AtomicReadFileAck {
302 pub end_of_file: bool,
304 pub access: FileReadAckMethod,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
310pub enum FileReadAckMethod {
311 Stream {
313 file_start_position: i32,
315 file_data: Vec<u8>,
317 },
318 Record {
320 file_start_record: i32,
322 returned_record_count: u32,
324 file_record_data: Vec<Vec<u8>>,
326 },
327}
328
329impl AtomicReadFileAck {
330 pub fn encode(&self, buf: &mut BytesMut) {
332 primitives::encode_app_boolean(buf, self.end_of_file);
333 match &self.access {
334 FileReadAckMethod::Stream {
335 file_start_position,
336 file_data,
337 } => {
338 tags::encode_opening_tag(buf, 0);
339 primitives::encode_app_signed(buf, *file_start_position);
340 primitives::encode_app_octet_string(buf, file_data);
341 tags::encode_closing_tag(buf, 0);
342 }
343 FileReadAckMethod::Record {
344 file_start_record,
345 returned_record_count,
346 file_record_data,
347 } => {
348 tags::encode_opening_tag(buf, 1);
349 primitives::encode_app_signed(buf, *file_start_record);
350 primitives::encode_app_unsigned(buf, *returned_record_count as u64);
351 for record in file_record_data {
352 primitives::encode_app_octet_string(buf, record);
353 }
354 tags::encode_closing_tag(buf, 1);
355 }
356 }
357 }
358
359 pub fn decode(data: &[u8]) -> Result<Self, Error> {
362 let mut offset = 0;
363
364 let (tag, pos) = tags::decode_tag(data, offset)?;
365 if tag.class != tags::TagClass::Application
366 || tag.number != tags::app_tag::BOOLEAN
367 || tag.is_opening
368 || tag.is_closing
369 || data[offset] & 0x07 > 1
370 {
371 return Err(Error::decoding(
372 offset,
373 "AtomicReadFileAck expected application Boolean end-of-file",
374 ));
375 }
376 let end_of_file = tag.length != 0;
377 offset = pos;
378
379 let (tag, tag_end) = tags::decode_tag(data, offset)?;
380 let (access, access_end) = if tag.is_opening_tag(0) {
381 let (content, access_end) = tags::extract_context_value(data, tag_end, 0)?;
382 let (file_start_position, inner) =
383 decode_start(content, 0, "AtomicReadFileAck stream file-start-position")?;
384 let (slice, inner) = decode_app_primitive(
385 content,
386 inner,
387 tags::app_tag::OCTET_STRING,
388 "AtomicReadFileAck stream file-data",
389 )?;
390 expect_end(content, inner, inner, "AtomicReadFileAck stream")?;
391 let file_data = slice.to_vec();
392 (
393 FileReadAckMethod::Stream {
394 file_start_position,
395 file_data,
396 },
397 access_end,
398 )
399 } else if tag.is_opening_tag(1) {
400 let (content, access_end) = tags::extract_context_value(data, tag_end, 1)?;
401 let (file_start_record, mut inner) =
402 decode_start(content, 0, "AtomicReadFileAck record file-start-record")?;
403 let (returned_record_count, new_inner) = decode_app_unsigned::<u32>(
404 content,
405 inner,
406 "AtomicReadFileAck record returned-record-count",
407 )?;
408 inner = new_inner;
409 if returned_record_count as usize > MAX_DECODED_ITEMS {
410 return Err(Error::overflow(0, "record count exceeds maximum"));
411 }
412 let mut file_record_data = Vec::new();
413 for i in 0..returned_record_count {
414 if inner >= content.len() {
415 return Err(DecodeError::new(
416 DecodingKind::Missing,
417 inner,
418 "AtomicReadFileAck record: record {} of the count is missing",
419 )
420 .with_value(i.into())
421 .into());
422 }
423 let (slice, new_inner) = decode_app_primitive(
424 content,
425 inner,
426 tags::app_tag::OCTET_STRING,
427 "AtomicReadFileAck record data",
428 )
429 .map_err(|error| indexed_record(error, i))?;
430 file_record_data.push(slice.to_vec());
431 inner = new_inner;
432 }
433 expect_end(content, inner, inner, "AtomicReadFileAck record")?;
434 (
435 FileReadAckMethod::Record {
436 file_start_record,
437 returned_record_count,
438 file_record_data,
439 },
440 access_end,
441 )
442 } else {
443 return Err(Error::decoding(
444 offset,
445 "Unknown read file ACK access method",
446 ));
447 };
448 expect_end(data, access_end, access_end, "AtomicReadFileAck")?;
449
450 Ok(Self {
451 end_of_file,
452 access,
453 })
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Eq)]
463pub struct AtomicWriteFileAck {
464 pub access: FileWriteAckMethod,
466}
467
468#[derive(Debug, Clone, PartialEq, Eq)]
470pub enum FileWriteAckMethod {
471 Stream {
473 file_start_position: i32,
475 },
476 Record {
478 file_start_record: i32,
480 },
481}
482
483impl AtomicWriteFileAck {
484 pub fn encode(&self, buf: &mut BytesMut) {
486 match &self.access {
487 FileWriteAckMethod::Stream {
488 file_start_position,
489 } => {
490 primitives::encode_ctx_signed(buf, 0, *file_start_position);
491 }
492 FileWriteAckMethod::Record { file_start_record } => {
493 primitives::encode_ctx_signed(buf, 1, *file_start_record);
494 }
495 }
496 }
497
498 pub fn decode(data: &[u8]) -> Result<Self, Error> {
501 let (access, end) = if next_is_context(data, 0, 0)? {
502 let (octets, end) =
503 decode_ctx_primitive(data, 0, 0, "AtomicWriteFileAck file-start-position")?;
504 let access = FileWriteAckMethod::Stream {
505 file_start_position: primitives::decode_signed(octets)?,
506 };
507 (access, end)
508 } else if next_is_context(data, 0, 1)? {
509 let (octets, end) =
510 decode_ctx_primitive(data, 0, 1, "AtomicWriteFileAck file-start-record")?;
511 let access = FileWriteAckMethod::Record {
512 file_start_record: primitives::decode_signed(octets)?,
513 };
514 (access, end)
515 } else {
516 return Err(Error::decoding(0, "Unknown write file ACK access method"));
517 };
518 expect_end(data, end, end, "AtomicWriteFileAck")?;
519
520 Ok(Self { access })
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use bacnet_types::enums::ObjectType;
528
529 fn file_oid() -> ObjectIdentifier {
530 ObjectIdentifier::new(ObjectType::FILE, 1).unwrap()
531 }
532
533 #[test]
534 fn atomic_read_stream_round_trip() {
535 let req = AtomicReadFileRequest {
536 file_identifier: file_oid(),
537 access: FileAccessMethod::Stream {
538 file_start_position: 0,
539 requested_octet_count: 1024,
540 },
541 };
542 let mut buf = BytesMut::new();
543 req.encode(&mut buf);
544 let decoded = AtomicReadFileRequest::decode(&buf).unwrap();
545 assert_eq!(decoded, req);
546 }
547
548 #[test]
549 fn atomic_read_record_round_trip() {
550 let req = AtomicReadFileRequest {
551 file_identifier: file_oid(),
552 access: FileAccessMethod::Record {
553 file_start_record: 5,
554 requested_record_count: 10,
555 },
556 };
557 let mut buf = BytesMut::new();
558 req.encode(&mut buf);
559 let decoded = AtomicReadFileRequest::decode(&buf).unwrap();
560 assert_eq!(decoded, req);
561 }
562
563 #[test]
564 fn atomic_write_stream_round_trip() {
565 let req = AtomicWriteFileRequest {
566 file_identifier: file_oid(),
567 access: FileWriteAccessMethod::Stream {
568 file_start_position: 100,
569 file_data: vec![0x01, 0x02, 0x03, 0x04],
570 },
571 };
572 let mut buf = BytesMut::new();
573 req.encode(&mut buf);
574 let decoded = AtomicWriteFileRequest::decode(&buf).unwrap();
575 assert_eq!(decoded, req);
576 }
577
578 #[test]
579 fn atomic_write_record_round_trip() {
580 let req = AtomicWriteFileRequest {
581 file_identifier: file_oid(),
582 access: FileWriteAccessMethod::Record {
583 file_start_record: 0,
584 record_count: 2,
585 file_record_data: vec![vec![0xAA, 0xBB], vec![0xCC, 0xDD]],
586 },
587 };
588 let mut buf = BytesMut::new();
589 req.encode(&mut buf);
590 let decoded = AtomicWriteFileRequest::decode(&buf).unwrap();
591 assert_eq!(decoded, req);
592 }
593
594 #[test]
599 fn test_decode_atomic_read_file_empty_input() {
600 assert!(AtomicReadFileRequest::decode(&[]).is_err());
601 }
602
603 #[test]
604 fn test_decode_atomic_read_file_truncated_1_byte() {
605 let req = AtomicReadFileRequest {
606 file_identifier: file_oid(),
607 access: FileAccessMethod::Stream {
608 file_start_position: 0,
609 requested_octet_count: 1024,
610 },
611 };
612 let mut buf = BytesMut::new();
613 req.encode(&mut buf);
614 assert!(AtomicReadFileRequest::decode(&buf[..1]).is_err());
615 }
616
617 #[test]
618 fn test_decode_atomic_read_file_truncated_3_bytes() {
619 let req = AtomicReadFileRequest {
620 file_identifier: file_oid(),
621 access: FileAccessMethod::Stream {
622 file_start_position: 0,
623 requested_octet_count: 1024,
624 },
625 };
626 let mut buf = BytesMut::new();
627 req.encode(&mut buf);
628 assert!(AtomicReadFileRequest::decode(&buf[..3]).is_err());
629 }
630
631 #[test]
632 fn test_decode_atomic_read_file_truncated_half() {
633 let req = AtomicReadFileRequest {
634 file_identifier: file_oid(),
635 access: FileAccessMethod::Stream {
636 file_start_position: 0,
637 requested_octet_count: 1024,
638 },
639 };
640 let mut buf = BytesMut::new();
641 req.encode(&mut buf);
642 let half = buf.len() / 2;
643 assert!(AtomicReadFileRequest::decode(&buf[..half]).is_err());
644 }
645
646 #[test]
647 fn test_decode_atomic_read_file_invalid_tag() {
648 assert!(AtomicReadFileRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
649 }
650
651 #[test]
652 fn test_decode_atomic_write_file_empty_input() {
653 assert!(AtomicWriteFileRequest::decode(&[]).is_err());
654 }
655
656 #[test]
657 fn test_decode_atomic_write_file_truncated_1_byte() {
658 let req = AtomicWriteFileRequest {
659 file_identifier: file_oid(),
660 access: FileWriteAccessMethod::Stream {
661 file_start_position: 100,
662 file_data: vec![0x01, 0x02, 0x03, 0x04],
663 },
664 };
665 let mut buf = BytesMut::new();
666 req.encode(&mut buf);
667 assert!(AtomicWriteFileRequest::decode(&buf[..1]).is_err());
668 }
669
670 #[test]
671 fn test_decode_atomic_write_file_truncated_3_bytes() {
672 let req = AtomicWriteFileRequest {
673 file_identifier: file_oid(),
674 access: FileWriteAccessMethod::Stream {
675 file_start_position: 100,
676 file_data: vec![0x01, 0x02, 0x03, 0x04],
677 },
678 };
679 let mut buf = BytesMut::new();
680 req.encode(&mut buf);
681 assert!(AtomicWriteFileRequest::decode(&buf[..3]).is_err());
682 }
683
684 #[test]
685 fn test_decode_atomic_write_file_truncated_half() {
686 let req = AtomicWriteFileRequest {
687 file_identifier: file_oid(),
688 access: FileWriteAccessMethod::Stream {
689 file_start_position: 100,
690 file_data: vec![0x01, 0x02, 0x03, 0x04],
691 },
692 };
693 let mut buf = BytesMut::new();
694 req.encode(&mut buf);
695 let half = buf.len() / 2;
696 assert!(AtomicWriteFileRequest::decode(&buf[..half]).is_err());
697 }
698
699 #[test]
700 fn test_decode_atomic_write_file_invalid_tag() {
701 assert!(AtomicWriteFileRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
702 }
703
704 #[test]
705 fn atomic_read_file_request_truncated_inner_tag() {
706 let data = [
710 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E,
713 0x35, 50, 0x01, 0x02, 0x0F,
717 ];
718 assert!(AtomicReadFileRequest::decode(&data).is_err());
719 }
720
721 #[test]
722 fn atomic_write_file_request_truncated_inner_tag() {
723 let data = [
725 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E, 0x35, 80, 0x01, 0x0F,
731 ];
732 assert!(AtomicWriteFileRequest::decode(&data).is_err());
733 }
734}
735
736#[cfg(test)]
737#[path = "file_width_tests.rs"]
738mod width_tests;
739
740#[cfg(test)]
741#[path = "file_ack_strict_tests.rs"]
742mod ack_strict_tests;
743
744#[cfg(test)]
745#[path = "file_ack_roundtrip_tests.rs"]
746mod ack_roundtrip_tests;
747#[path = "file_ack_size.rs"]
748mod ack_size;