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) {
179 return result;
180 }
181
182 match property {
183 p if p == PropertyIdentifier::OBJECT_TYPE => {
184 Ok(PropertyValue::Enumerated(ObjectType::FILE.to_raw()))
185 }
186 p if p == PropertyIdentifier::FILE_TYPE => {
187 Ok(PropertyValue::CharacterString(self.file_type.clone()))
188 }
189 p if p == PropertyIdentifier::FILE_SIZE => Ok(PropertyValue::Unsigned(self.file_size)),
190 p if p == PropertyIdentifier::MODIFICATION_DATE => Ok(PropertyValue::List(vec![
191 PropertyValue::Date(self.modification_date.0),
192 PropertyValue::Time(self.modification_date.1),
193 ])),
194 p if p == PropertyIdentifier::ARCHIVE => Ok(PropertyValue::Boolean(self.archive)),
195 p if p == PropertyIdentifier::READ_ONLY => Ok(PropertyValue::Boolean(self.read_only)),
196 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
197 Ok(PropertyValue::Enumerated(self.file_access_method))
198 }
199 p if p == PropertyIdentifier::RECORD_COUNT => match self.record_count {
200 Some(count) => Ok(PropertyValue::Unsigned(count)),
201 None => Err(common::unknown_property_error()),
202 },
203 _ => Err(common::unknown_property_error()),
204 }
205 }
206
207 fn write_property(
208 &mut self,
209 property: PropertyIdentifier,
210 _array_index: Option<u32>,
211 value: PropertyValue,
212 _priority: Option<u8>,
213 ) -> Result<(), Error> {
214 if let Some(result) = common::write_description(&mut self.description, property, &value) {
215 return result;
216 }
217 if let Some(result) =
218 common::write_out_of_service(&mut self.out_of_service, property, &value)
219 {
220 return result;
221 }
222
223 match property {
224 p if p == PropertyIdentifier::ARCHIVE => {
225 if let PropertyValue::Boolean(v) = value {
226 self.archive = v;
227 Ok(())
228 } else {
229 Err(common::invalid_data_type_error())
230 }
231 }
232 p if p == PropertyIdentifier::FILE_TYPE => {
233 if let PropertyValue::CharacterString(s) = value {
234 self.file_type = s;
235 Ok(())
236 } else {
237 Err(common::invalid_data_type_error())
238 }
239 }
240 p if p == PropertyIdentifier::READ_ONLY => {
241 Err(common::write_access_denied_error())
244 }
245 p if p == PropertyIdentifier::FILE_SIZE => Err(common::write_access_denied_error()),
246 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
247 Err(common::write_access_denied_error())
248 }
249 p if p == PropertyIdentifier::MODIFICATION_DATE => {
250 Err(common::write_access_denied_error())
251 }
252 p if p == PropertyIdentifier::RECORD_COUNT => Err(common::write_access_denied_error()),
253 _ => Err(common::write_access_denied_error()),
254 }
255 }
256
257 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
258 let mut props = vec![
259 PropertyIdentifier::OBJECT_IDENTIFIER,
260 PropertyIdentifier::OBJECT_NAME,
261 PropertyIdentifier::OBJECT_TYPE,
262 PropertyIdentifier::DESCRIPTION,
263 PropertyIdentifier::FILE_TYPE,
264 PropertyIdentifier::FILE_SIZE,
265 PropertyIdentifier::MODIFICATION_DATE,
266 PropertyIdentifier::ARCHIVE,
267 PropertyIdentifier::READ_ONLY,
268 PropertyIdentifier::FILE_ACCESS_METHOD,
269 PropertyIdentifier::STATUS_FLAGS,
270 PropertyIdentifier::OUT_OF_SERVICE,
271 PropertyIdentifier::RELIABILITY,
272 ];
273 if self.record_count.is_some() {
274 props.push(PropertyIdentifier::RECORD_COUNT);
275 }
276 Cow::Owned(props)
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use bacnet_types::enums::ErrorCode;
284
285 #[test]
286 fn file_object_creation() {
287 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
288 assert_eq!(file.object_name(), "FILE-1");
289 assert_eq!(file.object_identifier().instance_number(), 1);
290 }
291
292 #[test]
293 fn file_read_object_type() {
294 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
295 let val = file
296 .read_property(PropertyIdentifier::OBJECT_TYPE, None)
297 .unwrap();
298 assert_eq!(val, PropertyValue::Enumerated(ObjectType::FILE.to_raw()));
299 }
300
301 #[test]
302 fn file_read_object_identifier() {
303 let file = FileObject::new(42, "FILE-42", "application/octet-stream").unwrap();
304 let val = file
305 .read_property(PropertyIdentifier::OBJECT_IDENTIFIER, None)
306 .unwrap();
307 if let PropertyValue::ObjectIdentifier(oid) = val {
308 assert_eq!(oid.instance_number(), 42);
309 } else {
310 panic!("expected ObjectIdentifier");
311 }
312 }
313
314 #[test]
315 fn file_read_object_name() {
316 let file = FileObject::new(1, "MY-FILE", "text/plain").unwrap();
317 let val = file
318 .read_property(PropertyIdentifier::OBJECT_NAME, None)
319 .unwrap();
320 assert_eq!(val, PropertyValue::CharacterString("MY-FILE".into()));
321 }
322
323 #[test]
324 fn file_read_file_type() {
325 let file = FileObject::new(1, "FILE-1", "text/csv").unwrap();
326 let val = file
327 .read_property(PropertyIdentifier::FILE_TYPE, None)
328 .unwrap();
329 assert_eq!(val, PropertyValue::CharacterString("text/csv".into()));
330 }
331
332 #[test]
333 fn file_read_file_size_default_zero() {
334 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
335 let val = file
336 .read_property(PropertyIdentifier::FILE_SIZE, None)
337 .unwrap();
338 assert_eq!(val, PropertyValue::Unsigned(0));
339 }
340
341 #[test]
342 fn file_set_data_updates_file_size() {
343 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
344 file.set_data(vec![0x48, 0x65, 0x6C, 0x6C, 0x6F]); let val = file
346 .read_property(PropertyIdentifier::FILE_SIZE, None)
347 .unwrap();
348 assert_eq!(val, PropertyValue::Unsigned(5));
349 assert_eq!(file.data(), &[0x48, 0x65, 0x6C, 0x6C, 0x6F]);
350 }
351
352 #[test]
353 fn file_read_archive_default_false() {
354 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
355 let val = file
356 .read_property(PropertyIdentifier::ARCHIVE, None)
357 .unwrap();
358 assert_eq!(val, PropertyValue::Boolean(false));
359 }
360
361 #[test]
362 fn file_set_and_read_archive() {
363 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
364 file.set_archive(true);
365 assert!(file.archive());
366 let val = file
367 .read_property(PropertyIdentifier::ARCHIVE, None)
368 .unwrap();
369 assert_eq!(val, PropertyValue::Boolean(true));
370 }
371
372 #[test]
373 fn file_read_read_only_default_false() {
374 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
375 let val = file
376 .read_property(PropertyIdentifier::READ_ONLY, None)
377 .unwrap();
378 assert_eq!(val, PropertyValue::Boolean(false));
379 }
380
381 #[test]
382 fn file_set_and_read_read_only() {
383 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
384 file.set_read_only(true);
385 assert!(file.read_only());
386 let val = file
387 .read_property(PropertyIdentifier::READ_ONLY, None)
388 .unwrap();
389 assert_eq!(val, PropertyValue::Boolean(true));
390 }
391
392 #[test]
393 fn file_read_modification_date_default_unspecified() {
394 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
395 let val = file
396 .read_property(PropertyIdentifier::MODIFICATION_DATE, None)
397 .unwrap();
398 if let PropertyValue::List(items) = val {
399 assert_eq!(items.len(), 2);
400 let unspec_date = Date {
401 year: 0xFF,
402 month: 0xFF,
403 day: 0xFF,
404 day_of_week: 0xFF,
405 };
406 let unspec_time = Time {
407 hour: 0xFF,
408 minute: 0xFF,
409 second: 0xFF,
410 hundredths: 0xFF,
411 };
412 assert_eq!(items[0], PropertyValue::Date(unspec_date));
413 assert_eq!(items[1], PropertyValue::Time(unspec_time));
414 } else {
415 panic!("expected PropertyValue::List");
416 }
417 }
418
419 #[test]
420 fn file_set_and_read_modification_date() {
421 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
422 let d = Date {
423 year: 126,
424 month: 3,
425 day: 1,
426 day_of_week: 7,
427 };
428 let t = Time {
429 hour: 14,
430 minute: 30,
431 second: 0,
432 hundredths: 0,
433 };
434 file.set_modification_date(d, t);
435 let val = file
436 .read_property(PropertyIdentifier::MODIFICATION_DATE, None)
437 .unwrap();
438 if let PropertyValue::List(items) = val {
439 assert_eq!(items[0], PropertyValue::Date(d));
440 assert_eq!(items[1], PropertyValue::Time(t));
441 } else {
442 panic!("expected PropertyValue::List");
443 }
444 }
445
446 #[test]
447 fn file_read_file_access_method_default_stream() {
448 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
449 let val = file
450 .read_property(PropertyIdentifier::FILE_ACCESS_METHOD, None)
451 .unwrap();
452 assert_eq!(val, PropertyValue::Enumerated(0));
453 }
454
455 #[test]
456 fn file_record_count_unavailable_for_stream() {
457 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
458 let result = file.read_property(PropertyIdentifier::RECORD_COUNT, None);
459 assert!(result.is_err());
460 }
461
462 #[test]
463 fn file_set_records_updates_record_count_and_size() {
464 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
465 file.set_file_access_method(1);
466 file.set_records(vec![vec![0x01, 0x02], vec![0x03, 0x04, 0x05]]);
467 let count = file
468 .read_property(PropertyIdentifier::RECORD_COUNT, None)
469 .unwrap();
470 assert_eq!(count, PropertyValue::Unsigned(2));
471 let size = file
472 .read_property(PropertyIdentifier::FILE_SIZE, None)
473 .unwrap();
474 assert_eq!(size, PropertyValue::Unsigned(5)); assert_eq!(file.records().len(), 2);
476 }
477
478 #[test]
479 fn file_read_status_flags_default() {
480 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
481 let val = file
482 .read_property(PropertyIdentifier::STATUS_FLAGS, None)
483 .unwrap();
484 if let PropertyValue::BitString { unused_bits, data } = val {
485 assert_eq!(unused_bits, 4);
486 assert_eq!(data, vec![0x00]);
487 } else {
488 panic!("expected BitString");
489 }
490 }
491
492 #[test]
493 fn file_read_out_of_service_default_false() {
494 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
495 let val = file
496 .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
497 .unwrap();
498 assert_eq!(val, PropertyValue::Boolean(false));
499 }
500
501 #[test]
502 fn file_read_reliability_default() {
503 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
504 let val = file
505 .read_property(PropertyIdentifier::RELIABILITY, None)
506 .unwrap();
507 assert_eq!(val, PropertyValue::Enumerated(0));
508 }
509
510 #[test]
511 fn file_read_description_default_empty() {
512 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
513 let val = file
514 .read_property(PropertyIdentifier::DESCRIPTION, None)
515 .unwrap();
516 assert_eq!(val, PropertyValue::CharacterString(String::new()));
517 }
518
519 #[test]
520 fn file_write_description() {
521 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
522 file.write_property(
523 PropertyIdentifier::DESCRIPTION,
524 None,
525 PropertyValue::CharacterString("A test file".into()),
526 None,
527 )
528 .unwrap();
529 let val = file
530 .read_property(PropertyIdentifier::DESCRIPTION, None)
531 .unwrap();
532 assert_eq!(val, PropertyValue::CharacterString("A test file".into()));
533 }
534
535 #[test]
536 fn file_write_archive() {
537 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
538 file.write_property(
539 PropertyIdentifier::ARCHIVE,
540 None,
541 PropertyValue::Boolean(true),
542 None,
543 )
544 .unwrap();
545 let val = file
546 .read_property(PropertyIdentifier::ARCHIVE, None)
547 .unwrap();
548 assert_eq!(val, PropertyValue::Boolean(true));
549 }
550
551 #[test]
552 fn file_write_archive_invalid_type() {
553 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
554 let result = file.write_property(
555 PropertyIdentifier::ARCHIVE,
556 None,
557 PropertyValue::Unsigned(1),
558 None,
559 );
560 assert!(result.is_err());
561 }
562
563 #[test]
564 fn file_write_file_type() {
565 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
566 file.write_property(
567 PropertyIdentifier::FILE_TYPE,
568 None,
569 PropertyValue::CharacterString("application/json".into()),
570 None,
571 )
572 .unwrap();
573 let val = file
574 .read_property(PropertyIdentifier::FILE_TYPE, None)
575 .unwrap();
576 assert_eq!(
577 val,
578 PropertyValue::CharacterString("application/json".into())
579 );
580 }
581
582 #[test]
583 fn file_write_out_of_service() {
584 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
585 file.write_property(
586 PropertyIdentifier::OUT_OF_SERVICE,
587 None,
588 PropertyValue::Boolean(true),
589 None,
590 )
591 .unwrap();
592 let val = file
593 .read_property(PropertyIdentifier::OUT_OF_SERVICE, None)
594 .unwrap();
595 assert_eq!(val, PropertyValue::Boolean(true));
596 }
597
598 #[test]
599 fn file_write_read_only_denied() {
600 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
601 let result = file.write_property(
602 PropertyIdentifier::READ_ONLY,
603 None,
604 PropertyValue::Boolean(true),
605 None,
606 );
607 assert!(result.is_err());
608 }
609
610 #[test]
611 fn file_write_file_size_denied() {
612 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
613 let result = file.write_property(
614 PropertyIdentifier::FILE_SIZE,
615 None,
616 PropertyValue::Unsigned(100),
617 None,
618 );
619 assert!(result.is_err());
620 }
621
622 #[test]
623 fn file_property_list_stream() {
624 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
625 let props = file.property_list();
626 assert!(props.contains(&PropertyIdentifier::OBJECT_IDENTIFIER));
627 assert!(props.contains(&PropertyIdentifier::OBJECT_NAME));
628 assert!(props.contains(&PropertyIdentifier::OBJECT_TYPE));
629 assert!(props.contains(&PropertyIdentifier::FILE_TYPE));
630 assert!(props.contains(&PropertyIdentifier::FILE_SIZE));
631 assert!(props.contains(&PropertyIdentifier::MODIFICATION_DATE));
632 assert!(props.contains(&PropertyIdentifier::ARCHIVE));
633 assert!(props.contains(&PropertyIdentifier::READ_ONLY));
634 assert!(props.contains(&PropertyIdentifier::FILE_ACCESS_METHOD));
635 assert!(props.contains(&PropertyIdentifier::STATUS_FLAGS));
636 assert!(props.contains(&PropertyIdentifier::OUT_OF_SERVICE));
637 assert!(props.contains(&PropertyIdentifier::RELIABILITY));
638 assert!(!props.contains(&PropertyIdentifier::RECORD_COUNT));
640 }
641
642 #[test]
643 fn file_property_list_record_access() {
644 let mut file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
645 file.set_file_access_method(1);
646 let props = file.property_list();
647 assert!(props.contains(&PropertyIdentifier::RECORD_COUNT));
648 }
649
650 #[test]
651 fn file_unknown_property_error() {
652 let file = FileObject::new(1, "FILE-1", "text/plain").unwrap();
653 let result = file.read_property(PropertyIdentifier::PRESENT_VALUE, None);
654 assert!(result.is_err());
655 if let Err(Error::Protocol { code, .. }) = result {
656 assert_eq!(code, ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32);
657 } else {
658 panic!("expected Protocol error");
659 }
660 }
661}