1use bacnet_types::enums::{ObjectType, PropertyIdentifier};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
9use std::borrow::Cow;
10
11use crate::common::{self, read_common_properties};
12use crate::traits::BACnetObject;
13
14pub struct FileObject {
24 oid: ObjectIdentifier,
25 name: String,
26 description: String,
27 file_type: String,
28 file_size: u64,
29 modification_date: (Date, Time),
30 archive: bool,
31 read_only: bool,
32 file_access_method: u32,
34 record_count: Option<u64>,
36 data: Vec<u8>,
38 records: Vec<Vec<u8>>,
40 status_flags: StatusFlags,
41 out_of_service: bool,
42 reliability: u32,
44}
45
46impl FileObject {
47 pub fn new(
52 instance: u32,
53 name: impl Into<String>,
54 file_type: impl Into<String>,
55 ) -> Result<Self, Error> {
56 let oid = ObjectIdentifier::new(ObjectType::FILE, instance)?;
57 Ok(Self {
58 oid,
59 name: name.into(),
60 description: String::new(),
61 file_type: file_type.into(),
62 file_size: 0,
63 modification_date: (
64 Date {
65 year: 0xFF,
66 month: 0xFF,
67 day: 0xFF,
68 day_of_week: 0xFF,
69 },
70 Time {
71 hour: 0xFF,
72 minute: 0xFF,
73 second: 0xFF,
74 hundredths: 0xFF,
75 },
76 ),
77 archive: false,
78 read_only: false,
79 file_access_method: 0,
80 record_count: None,
81 data: Vec::new(),
82 records: Vec::new(),
83 status_flags: StatusFlags::empty(),
84 out_of_service: false,
85 reliability: 0,
86 })
87 }
88
89 pub fn set_description(&mut self, desc: impl Into<String>) {
91 self.description = desc.into();
92 }
93
94 pub fn set_file_type(&mut self, ft: impl Into<String>) {
96 self.file_type = ft.into();
97 }
98
99 pub fn set_data(&mut self, data: Vec<u8>) {
101 self.file_size = data.len() as u64;
102 self.data = data;
103 }
104
105 pub fn data(&self) -> &[u8] {
107 &self.data
108 }
109
110 pub fn set_file_access_method(&mut self, method: u32) {
112 self.file_access_method = method;
113 if method == 1 {
114 self.record_count = Some(self.records.len() as u64);
115 } else {
116 self.record_count = None;
117 }
118 }
119
120 pub fn set_records(&mut self, records: Vec<Vec<u8>>) {
122 let total_size: u64 = records.iter().map(|r| r.len() as u64).sum();
123 self.file_size = total_size;
124 self.record_count = Some(records.len() as u64);
125 self.records = records;
126 }
127
128 pub fn records(&self) -> &[Vec<u8>] {
130 &self.records
131 }
132
133 pub fn set_modification_date(&mut self, date: Date, time: Time) {
135 self.modification_date = (date, time);
136 }
137
138 pub fn set_archive(&mut self, archive: bool) {
140 self.archive = archive;
141 }
142
143 pub fn set_read_only(&mut self, read_only: bool) {
145 self.read_only = read_only;
146 }
147
148 pub fn file_size(&self) -> u64 {
150 self.file_size
151 }
152
153 pub fn archive(&self) -> bool {
155 self.archive
156 }
157
158 pub fn read_only(&self) -> bool {
160 self.read_only
161 }
162}
163
164impl BACnetObject for FileObject {
165 fn object_identifier(&self) -> ObjectIdentifier {
166 self.oid
167 }
168
169 fn object_name(&self) -> &str {
170 &self.name
171 }
172
173 fn read_property(
174 &self,
175 property: PropertyIdentifier,
176 array_index: Option<u32>,
177 ) -> Result<PropertyValue, Error> {
178 if let Some(result) = read_common_properties!(self, property, array_index) {
180 return result;
181 }
182
183 match property {
184 p if p == PropertyIdentifier::OBJECT_TYPE => {
185 Ok(PropertyValue::Enumerated(ObjectType::FILE.to_raw()))
186 }
187 p if p == PropertyIdentifier::FILE_TYPE => {
188 Ok(PropertyValue::CharacterString(self.file_type.clone()))
189 }
190 p if p == PropertyIdentifier::FILE_SIZE => Ok(PropertyValue::Unsigned(self.file_size)),
191 p if p == PropertyIdentifier::MODIFICATION_DATE => Ok(PropertyValue::List(vec![
192 PropertyValue::Date(self.modification_date.0),
193 PropertyValue::Time(self.modification_date.1),
194 ])),
195 p if p == PropertyIdentifier::ARCHIVE => Ok(PropertyValue::Boolean(self.archive)),
196 p if p == PropertyIdentifier::READ_ONLY => Ok(PropertyValue::Boolean(self.read_only)),
197 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
198 Ok(PropertyValue::Enumerated(self.file_access_method))
199 }
200 p if p == PropertyIdentifier::RECORD_COUNT => match self.record_count {
201 Some(count) => Ok(PropertyValue::Unsigned(count)),
202 None => Err(common::unknown_property_error()),
203 },
204 _ => Err(common::unknown_property_error()),
205 }
206 }
207
208 fn write_property(
209 &mut self,
210 property: PropertyIdentifier,
211 _array_index: Option<u32>,
212 value: PropertyValue,
213 _priority: Option<u8>,
214 ) -> Result<(), Error> {
215 if let Some(result) = common::write_description(&mut self.description, property, &value) {
217 return result;
218 }
219
220 if let Some(result) =
222 common::write_out_of_service(&mut self.out_of_service, property, &value)
223 {
224 return result;
225 }
226
227 match property {
228 p if p == PropertyIdentifier::ARCHIVE => {
229 if let PropertyValue::Boolean(v) = value {
230 self.archive = v;
231 Ok(())
232 } else {
233 Err(common::invalid_data_type_error())
234 }
235 }
236 p if p == PropertyIdentifier::FILE_TYPE => {
237 if let PropertyValue::CharacterString(s) = value {
238 self.file_type = s;
239 Ok(())
240 } else {
241 Err(common::invalid_data_type_error())
242 }
243 }
244 p if p == PropertyIdentifier::READ_ONLY => {
245 Err(common::write_access_denied_error())
248 }
249 p if p == PropertyIdentifier::FILE_SIZE => Err(common::write_access_denied_error()),
250 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
251 Err(common::write_access_denied_error())
252 }
253 p if p == PropertyIdentifier::MODIFICATION_DATE => {
254 Err(common::write_access_denied_error())
255 }
256 p if p == PropertyIdentifier::RECORD_COUNT => Err(common::write_access_denied_error()),
257 _ => Err(common::write_access_denied_error()),
258 }
259 }
260
261 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
262 let mut props = vec![
263 PropertyIdentifier::OBJECT_IDENTIFIER,
264 PropertyIdentifier::OBJECT_NAME,
265 PropertyIdentifier::OBJECT_TYPE,
266 PropertyIdentifier::DESCRIPTION,
267 PropertyIdentifier::FILE_TYPE,
268 PropertyIdentifier::FILE_SIZE,
269 PropertyIdentifier::MODIFICATION_DATE,
270 PropertyIdentifier::ARCHIVE,
271 PropertyIdentifier::READ_ONLY,
272 PropertyIdentifier::FILE_ACCESS_METHOD,
273 PropertyIdentifier::STATUS_FLAGS,
274 PropertyIdentifier::OUT_OF_SERVICE,
275 PropertyIdentifier::RELIABILITY,
276 ];
277 if self.record_count.is_some() {
278 props.push(PropertyIdentifier::RECORD_COUNT);
279 }
280 Cow::Owned(props)
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use bacnet_types::enums::ErrorCode;
288
289 #[test]
290 fn file_object_creation() {
291 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
292 assert_eq!(file.object_name(), "FILE-1");
293 assert_eq!(file.object_identifier().instance_number(), 1);
294 }
295
296 #[test]
297 fn file_read_object_type() {
298 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
299 let val = file
300 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
301 .unwrap();
302 assert_eq!(val, PropertyValue::Enumerated(ObjectType::FILE.to_raw()));
303 }
304
305 #[test]
306 fn file_read_object_identifier() {
307 let file = FileObject::new(42, "FILE-42", "application/octet-stream").unwrap();
308 let val = file
309 .read_property(PropertyIdentifier::OBJECT_IDENTIFIER, None)
310 .unwrap();
311 if let PropertyValue::ObjectIdentifier(oid) = val {
312 assert_eq!(oid.instance_number(), 42);
313 } else {
314 panic!("expected ObjectIdentifier");
315 }
316 }
317
318 #[test]
319 fn file_read_object_name() {
320 let file = FileObject::new(1, "MY-FILE", "text/plain").unwrap();
321 let val = file
322 .read_property(PropertyIdentifier::OBJECT_NAME, None)
323 .unwrap();
324 assert_eq!(val, PropertyValue::CharacterString("MY-FILE".into()));
325 }
326
327 #[test]
328 fn file_read_file_type() {
329 let file = FileObject::new(1, "FILE-1", "text/csv").unwrap();
330 let val = file
331 .read_property(PropertyIdentifier::FILE_TYPE, None)
332 .unwrap();
333 assert_eq!(val, PropertyValue::CharacterString("text/csv".into()));
334 }
335
336 #[test]
337 fn file_read_file_size_default_zero() {
338 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
339 let val = file
340 .read_property(PropertyIdentifier::FILE_SIZE, None)
341 .unwrap();
342 assert_eq!(val, PropertyValue::Unsigned(0));
343 }
344
345 #[test]
346 fn file_set_data_updates_file_size() {
347 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
348 file.set_data(vec![0x48, 0x65, 0x6C, 0x6C, 0x6F]); let val = file
350 .read_property(PropertyIdentifier::FILE_SIZE, None)
351 .unwrap();
352 assert_eq!(val, PropertyValue::Unsigned(5));
353 assert_eq!(file.data(), &[0x48, 0x65, 0x6C, 0x6C, 0x6F]);
354 }
355
356 #[test]
357 fn file_read_archive_default_false() {
358 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
359 let val = file
360 .read_property(PropertyIdentifier::ARCHIVE, None)
361 .unwrap();
362 assert_eq!(val, PropertyValue::Boolean(false));
363 }
364
365 #[test]
366 fn file_set_and_read_archive() {
367 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
368 file.set_archive(true);
369 assert!(file.archive());
370 let val = file
371 .read_property(PropertyIdentifier::ARCHIVE, None)
372 .unwrap();
373 assert_eq!(val, PropertyValue::Boolean(true));
374 }
375
376 #[test]
377 fn file_read_read_only_default_false() {
378 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
379 let val = file
380 .read_property(PropertyIdentifier::READ_ONLY, None)
381 .unwrap();
382 assert_eq!(val, PropertyValue::Boolean(false));
383 }
384
385 #[test]
386 fn file_set_and_read_read_only() {
387 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
388 file.set_read_only(true);
389 assert!(file.read_only());
390 let val = file
391 .read_property(PropertyIdentifier::READ_ONLY, None)
392 .unwrap();
393 assert_eq!(val, PropertyValue::Boolean(true));
394 }
395
396 #[test]
397 fn file_read_modification_date_default_unspecified() {
398 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
399 let val = file
400 .read_property(PropertyIdentifier::MODIFICATION_DATE, None)
401 .unwrap();
402 if let PropertyValue::List(items) = val {
403 assert_eq!(items.len(), 2);
404 let unspec_date = Date {
405 year: 0xFF,
406 month: 0xFF,
407 day: 0xFF,
408 day_of_week: 0xFF,
409 };
410 let unspec_time = Time {
411 hour: 0xFF,
412 minute: 0xFF,
413 second: 0xFF,
414 hundredths: 0xFF,
415 };
416 assert_eq!(items[0], PropertyValue::Date(unspec_date));
417 assert_eq!(items[1], PropertyValue::Time(unspec_time));
418 } else {
419 panic!("expected PropertyValue::List");
420 }
421 }
422
423 #[test]
424 fn file_set_and_read_modification_date() {
425 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
426 let d = Date {
427 year: 126,
428 month: 3,
429 day: 1,
430 day_of_week: 7,
431 };
432 let t = Time {
433 hour: 14,
434 minute: 30,
435 second: 0,
436 hundredths: 0,
437 };
438 file.set_modification_date(d, t);
439 let val = file
440 .read_property(PropertyIdentifier::MODIFICATION_DATE, None)
441 .unwrap();
442 if let PropertyValue::List(items) = val {
443 assert_eq!(items[0], PropertyValue::Date(d));
444 assert_eq!(items[1], PropertyValue::Time(t));
445 } else {
446 panic!("expected PropertyValue::List");
447 }
448 }
449
450 #[test]
451 fn file_read_file_access_method_default_stream() {
452 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
453 let val = file
454 .read_property(PropertyIdentifier::FILE_ACCESS_METHOD, None)
455 .unwrap();
456 assert_eq!(val, PropertyValue::Enumerated(0));
457 }
458
459 #[test]
460 fn file_record_count_unavailable_for_stream() {
461 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
462 let result = file.read_property(PropertyIdentifier::RECORD_COUNT, None);
463 assert!(result.is_err());
464 }
465
466 #[test]
467 fn file_set_records_updates_record_count_and_size() {
468 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
469 file.set_file_access_method(1);
470 file.set_records(vec![vec![0x01, 0x02], vec![0x03, 0x04, 0x05]]);
471 let count = file
472 .read_property(PropertyIdentifier::RECORD_COUNT, None)
473 .unwrap();
474 assert_eq!(count, PropertyValue::Unsigned(2));
475 let size = file
476 .read_property(PropertyIdentifier::FILE_SIZE, None)
477 .unwrap();
478 assert_eq!(size, PropertyValue::Unsigned(5)); assert_eq!(file.records().len(), 2);
480 }
481
482 #[test]
483 fn file_read_status_flags_default() {
484 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
485 let val = file
486 .read_property(PropertyIdentifier::STATUS_FLAGS, None)
487 .unwrap();
488 if let PropertyValue::BitString { unused_bits, data } = val {
489 assert_eq!(unused_bits, 4);
490 assert_eq!(data, vec![0x00]);
491 } else {
492 panic!("expected BitString");
493 }
494 }
495
496 #[test]
497 fn file_read_out_of_service_default_false() {
498 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
499 let val = file
500 .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
501 .unwrap();
502 assert_eq!(val, PropertyValue::Boolean(false));
503 }
504
505 #[test]
506 fn file_read_reliability_default() {
507 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
508 let val = file
509 .read_property(PropertyIdentifier::RELIABILITY, None)
510 .unwrap();
511 assert_eq!(val, PropertyValue::Enumerated(0));
512 }
513
514 #[test]
515 fn file_read_description_default_empty() {
516 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
517 let val = file
518 .read_property(PropertyIdentifier::DESCRIPTION, None)
519 .unwrap();
520 assert_eq!(val, PropertyValue::CharacterString(String::new()));
521 }
522
523 #[test]
524 fn file_write_description() {
525 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
526 file.write_property(
527 PropertyIdentifier::DESCRIPTION,
528 None,
529 PropertyValue::CharacterString("A test file".into()),
530 None,
531 )
532 .unwrap();
533 let val = file
534 .read_property(PropertyIdentifier::DESCRIPTION, None)
535 .unwrap();
536 assert_eq!(val, PropertyValue::CharacterString("A test file".into()));
537 }
538
539 #[test]
540 fn file_write_archive() {
541 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
542 file.write_property(
543 PropertyIdentifier::ARCHIVE,
544 None,
545 PropertyValue::Boolean(true),
546 None,
547 )
548 .unwrap();
549 let val = file
550 .read_property(PropertyIdentifier::ARCHIVE, None)
551 .unwrap();
552 assert_eq!(val, PropertyValue::Boolean(true));
553 }
554
555 #[test]
556 fn file_write_archive_invalid_type() {
557 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
558 let result = file.write_property(
559 PropertyIdentifier::ARCHIVE,
560 None,
561 PropertyValue::Unsigned(1),
562 None,
563 );
564 assert!(result.is_err());
565 }
566
567 #[test]
568 fn file_write_file_type() {
569 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
570 file.write_property(
571 PropertyIdentifier::FILE_TYPE,
572 None,
573 PropertyValue::CharacterString("application/json".into()),
574 None,
575 )
576 .unwrap();
577 let val = file
578 .read_property(PropertyIdentifier::FILE_TYPE, None)
579 .unwrap();
580 assert_eq!(
581 val,
582 PropertyValue::CharacterString("application/json".into())
583 );
584 }
585
586 #[test]
587 fn file_write_out_of_service() {
588 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
589 file.write_property(
590 PropertyIdentifier::OUT_OF_SERVICE,
591 None,
592 PropertyValue::Boolean(true),
593 None,
594 )
595 .unwrap();
596 let val = file
597 .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
598 .unwrap();
599 assert_eq!(val, PropertyValue::Boolean(true));
600 }
601
602 #[test]
603 fn file_write_read_only_denied() {
604 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
605 let result = file.write_property(
606 PropertyIdentifier::READ_ONLY,
607 None,
608 PropertyValue::Boolean(true),
609 None,
610 );
611 assert!(result.is_err());
612 }
613
614 #[test]
615 fn file_write_file_size_denied() {
616 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
617 let result = file.write_property(
618 PropertyIdentifier::FILE_SIZE,
619 None,
620 PropertyValue::Unsigned(100),
621 None,
622 );
623 assert!(result.is_err());
624 }
625
626 #[test]
627 fn file_property_list_stream() {
628 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
629 let props = file.property_list();
630 assert!(props.contains(&PropertyIdentifier::OBJECT_IDENTIFIER));
631 assert!(props.contains(&PropertyIdentifier::OBJECT_NAME));
632 assert!(props.contains(&PropertyIdentifier::OBJECT_TYPE));
633 assert!(props.contains(&PropertyIdentifier::FILE_TYPE));
634 assert!(props.contains(&PropertyIdentifier::FILE_SIZE));
635 assert!(props.contains(&PropertyIdentifier::MODIFICATION_DATE));
636 assert!(props.contains(&PropertyIdentifier::ARCHIVE));
637 assert!(props.contains(&PropertyIdentifier::READ_ONLY));
638 assert!(props.contains(&PropertyIdentifier::FILE_ACCESS_METHOD));
639 assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
640 assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
641 assert!(props.contains(&PropertyIdentifier::RELIABILITY));
642 assert!(!props.contains(&PropertyIdentifier::RECORD_COUNT));
644 }
645
646 #[test]
647 fn file_property_list_record_access() {
648 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
649 file.set_file_access_method(1);
650 let props = file.property_list();
651 assert!(props.contains(&PropertyIdentifier::RECORD_COUNT));
652 }
653
654 #[test]
655 fn file_unknown_property_error() {
656 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
657 let result = file.read_property(PropertyIdentifier::PRESENT_VALUE, None);
658 assert!(result.is_err());
659 if let Err(Error::Protocol { code, .. }) = result {
660 assert_eq!(code, ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32);
661 } else {
662 panic!("expected Protocol error");
663 }
664 }
665}