1use bacnet_types::enums::{
7 ErrorClass, ErrorCode, FileAccessMethod, ObjectType, PropertyIdentifier,
8};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
11use std::borrow::Cow;
12use std::sync::Arc;
13
14use crate::clock::ClockReader;
15use crate::common::{self, read_common_properties};
16use crate::traits::{BACnetObject, WritePropertyRollback};
17
18mod resize;
19
20pub const DEFAULT_MAX_FILE_SIZE: u64 = 1_048_576;
33
34pub const DEFAULT_MAX_RECORD_COUNT: u64 = 10_000;
45
46const MAX_RECORD_CAP: u64 = DEFAULT_MAX_RECORD_COUNT;
49
50#[doc(hidden)]
58pub trait FileConfiguration: Send + Sync {
59 fn set_access_method(&mut self, method: FileAccessMethod);
61
62 fn set_stream_data(&mut self, data: Vec<u8>) -> Result<(), Error>;
64
65 fn stream_data(&self) -> Result<&[u8], Error>;
67
68 fn set_record_data(&mut self, records: Vec<Vec<u8>>) -> Result<(), Error>;
70
71 fn record_data(&self) -> Result<&[Vec<u8>], Error>;
73
74 fn set_max_file_size(&mut self, max_octets: u64) -> u64;
76
77 fn set_max_record_count(&mut self, max_records: u64) -> u64;
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct FileStreamRead {
84 pub data: Vec<u8>,
87 pub end_of_file: bool,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct FileRecordRead {
95 pub records: Vec<Vec<u8>>,
98 pub end_of_file: bool,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum FileWriteStart {
110 At(u64),
113 Append,
115}
116
117pub trait FileStorage: Send + Sync {
151 fn read_stream(&self, _start: u64, _count: u64) -> Result<FileStreamRead, Error> {
153 Err(file_access_denied())
154 }
155
156 fn write_stream(&mut self, _start: FileWriteStart, _data: &[u8]) -> Result<u64, Error> {
161 Err(file_access_denied())
162 }
163
164 fn read_records(&self, _start: u64, _count: u64) -> Result<FileRecordRead, Error> {
166 Err(file_access_denied())
167 }
168
169 fn write_records(
173 &mut self,
174 _start: FileWriteStart,
175 _records: &[Vec<u8>],
176 ) -> Result<u64, Error> {
177 Err(file_access_denied())
178 }
179}
180
181fn invalid_file_access_method() -> Error {
182 common::protocol_error(ErrorClass::SERVICES, ErrorCode::INVALID_FILE_ACCESS_METHOD)
183}
184
185fn file_access_denied() -> Error {
186 common::protocol_error(ErrorClass::SERVICES, ErrorCode::FILE_ACCESS_DENIED)
187}
188
189fn invalid_file_start_position() -> Error {
190 common::protocol_error(ErrorClass::SERVICES, ErrorCode::INVALID_FILE_START_POSITION)
191}
192
193fn file_full() -> Error {
194 common::protocol_error(ErrorClass::OBJECT, ErrorCode::FILE_FULL)
195}
196
197const MAX_REPRESENTABLE: u64 = i32::MAX as u64;
200
201fn octet_total(records: &[Vec<u8>]) -> u64 {
202 records.iter().map(|r| r.len() as u64).sum()
203}
204
205pub struct FileObject {
216 oid: ObjectIdentifier,
217 name: String,
218 description: String,
219 file_type: String,
220 file_size: u64,
221 modification_date: (Date, Time),
222 archive: bool,
223 read_only: bool,
224 file_access_method: u32,
227 record_count: Option<u64>,
229 data: Vec<u8>,
231 records: Vec<Vec<u8>>,
233 status_flags: StatusFlags,
234 out_of_service: bool,
235 reliability: u32,
237 max_file_size: u64,
239 max_record_count: u64,
241 clock: Option<Arc<dyn ClockReader>>,
243}
244
245impl FileObject {
246 pub fn new(
251 instance: u32,
252 name: impl Into<String>,
253 file_type: impl Into<String>,
254 ) -> Result<Self, Error> {
255 let oid = ObjectIdentifier::new(ObjectType::FILE, instance)?;
256 Ok(Self {
257 oid,
258 name: name.into(),
259 description: String::new(),
260 file_type: file_type.into(),
261 file_size: 0,
262 modification_date: (
263 Date {
264 year: Date::UNSPECIFIED,
265 month: Date::UNSPECIFIED,
266 day: Date::UNSPECIFIED,
267 day_of_week: Date::UNSPECIFIED,
268 },
269 Time {
270 hour: Time::UNSPECIFIED,
271 minute: Time::UNSPECIFIED,
272 second: Time::UNSPECIFIED,
273 hundredths: Time::UNSPECIFIED,
274 },
275 ),
276 archive: false,
277 read_only: false,
278 file_access_method: FileAccessMethod::STREAM_ACCESS.to_raw(),
279 record_count: None,
280 data: Vec::new(),
281 records: Vec::new(),
282 status_flags: StatusFlags::empty(),
283 out_of_service: false,
284 reliability: 0,
285 max_file_size: DEFAULT_MAX_FILE_SIZE,
286 max_record_count: DEFAULT_MAX_RECORD_COUNT,
287 clock: None,
288 })
289 }
290
291 pub fn set_description(&mut self, desc: impl Into<String>) {
293 self.description = desc.into();
294 }
295
296 pub fn set_file_type(&mut self, ft: impl Into<String>) {
298 self.file_type = ft.into();
299 }
300
301 pub fn set_data(&mut self, data: Vec<u8>) {
306 self.data = data;
307 if self.file_access_method != FileAccessMethod::RECORD_ACCESS.to_raw() {
308 self.file_size = self.data.len() as u64;
309 }
310 self.mark_modified();
311 }
312
313 pub fn data(&self) -> &[u8] {
315 &self.data
316 }
317
318 pub fn set_file_access_method(&mut self, method: u32) {
326 let old_file_size = self.file_size;
327 self.file_access_method = method;
328 if method == FileAccessMethod::RECORD_ACCESS.to_raw() {
329 self.record_count = Some(self.records.len() as u64);
330 self.file_size = octet_total(&self.records);
331 } else {
332 self.record_count = None;
333 self.file_size = self.data.len() as u64;
334 }
335 if self.file_size != old_file_size {
336 self.mark_modified();
337 }
338 }
339
340 pub fn set_records(&mut self, records: Vec<Vec<u8>>) {
343 self.records = records;
344 if self.file_access_method == FileAccessMethod::RECORD_ACCESS.to_raw() {
345 self.record_count = Some(self.records.len() as u64);
346 self.file_size = octet_total(&self.records);
347 }
348 self.mark_modified();
349 }
350
351 pub fn records(&self) -> &[Vec<u8>] {
353 &self.records
354 }
355
356 pub fn set_modification_date(&mut self, date: Date, time: Time) {
358 let modification_date = (date, time);
359 if self.modification_date != modification_date {
360 self.modification_date = modification_date;
361 self.archive = false;
362 }
363 }
364
365 pub fn set_archive(&mut self, archive: bool) {
367 self.archive = archive;
368 }
369
370 pub fn set_read_only(&mut self, read_only: bool) {
372 self.read_only = read_only;
373 }
374
375 pub fn file_size(&self) -> u64 {
377 self.file_size
378 }
379
380 pub fn archive(&self) -> bool {
382 self.archive
383 }
384
385 pub fn read_only(&self) -> bool {
387 self.read_only
388 }
389
390 pub fn set_max_file_size(&mut self, max_octets: u64) {
397 self.max_file_size = max_octets.min(MAX_REPRESENTABLE);
398 }
399
400 pub fn max_file_size(&self) -> u64 {
402 self.max_file_size
403 }
404
405 pub fn set_max_record_count(&mut self, max_records: u64) {
415 self.max_record_count = max_records.min(MAX_RECORD_CAP).min(MAX_REPRESENTABLE);
416 }
417
418 pub fn max_record_count(&self) -> u64 {
420 self.max_record_count
421 }
422
423 fn require_access(&self, method: FileAccessMethod) -> Result<(), Error> {
426 if self.file_access_method == method.to_raw() {
427 Ok(())
428 } else {
429 Err(invalid_file_access_method())
430 }
431 }
432
433 fn modification_datetime(&self) -> (Date, Time) {
434 let frame = self.clock.as_ref().and_then(|clock| clock.read_clock());
435 match frame {
436 Some(frame) if frame.is_valid_actual_datetime() => (frame.local_date, frame.local_time),
437 _ => (
438 Date {
439 year: Date::UNSPECIFIED,
440 month: Date::UNSPECIFIED,
441 day: Date::UNSPECIFIED,
442 day_of_week: Date::UNSPECIFIED,
443 },
444 Time {
445 hour: Time::UNSPECIFIED,
446 minute: Time::UNSPECIFIED,
447 second: Time::UNSPECIFIED,
448 hundredths: Time::UNSPECIFIED,
449 },
450 ),
451 }
452 }
453
454 fn mark_modified(&mut self) {
455 self.modification_date = self.modification_datetime();
456 self.archive = false;
457 }
458}
459
460impl FileStorage for FileObject {
461 fn read_stream(&self, start: u64, count: u64) -> Result<FileStreamRead, Error> {
462 self.require_access(FileAccessMethod::STREAM_ACCESS)?;
463 let len = self.data.len() as u64;
464 if start > len {
465 return Err(invalid_file_start_position());
466 }
467 let end = start.saturating_add(count).min(len);
468 Ok(FileStreamRead {
469 data: self.data[start as usize..end as usize].to_vec(),
470 end_of_file: len == 0 || (end >= len && end > start),
471 })
472 }
473
474 fn write_stream(&mut self, start: FileWriteStart, data: &[u8]) -> Result<u64, Error> {
475 self.require_access(FileAccessMethod::STREAM_ACCESS)?;
476 let len = self.data.len() as u64;
477 let start = match start {
478 FileWriteStart::At(offset) => offset,
479 FileWriteStart::Append => len,
480 };
481 let end = start.checked_add(data.len() as u64).ok_or_else(file_full)?;
482 if end > self.max_file_size.max(len) {
483 return Err(file_full());
484 }
485 let (start_idx, end_idx) = (
486 usize::try_from(start).map_err(|_| file_full())?,
487 usize::try_from(end).map_err(|_| file_full())?,
488 );
489 let mut updated = std::mem::take(&mut self.data);
490 if end_idx > updated.len() {
491 updated.resize(end_idx, 0);
492 }
493 updated[start_idx..end_idx].copy_from_slice(data);
494 self.set_data(updated);
495 Ok(start)
496 }
497
498 fn read_records(&self, start: u64, count: u64) -> Result<FileRecordRead, Error> {
499 self.require_access(FileAccessMethod::RECORD_ACCESS)?;
500 let len = self.records.len() as u64;
501 if start > len {
502 return Err(invalid_file_start_position());
503 }
504 let end = start.saturating_add(count).min(len);
505 Ok(FileRecordRead {
506 records: self.records[start as usize..end as usize].to_vec(),
507 end_of_file: len == 0 || (end >= len && end > start),
508 })
509 }
510
511 fn write_records(&mut self, start: FileWriteStart, records: &[Vec<u8>]) -> Result<u64, Error> {
512 self.require_access(FileAccessMethod::RECORD_ACCESS)?;
513 let len = self.records.len() as u64;
514 let start = match start {
515 FileWriteStart::At(index) => index,
516 FileWriteStart::Append => len,
517 };
518 let end = start
519 .checked_add(records.len() as u64)
520 .ok_or_else(file_full)?;
521 if end > self.max_record_count.max(len) {
522 return Err(file_full());
523 }
524 let (start_idx, end_idx) = (
525 usize::try_from(start).map_err(|_| file_full())?,
526 usize::try_from(end).map_err(|_| file_full())?,
527 );
528 let existing = octet_total(&self.records);
531 let replaced = octet_total(
532 &self.records[start_idx.min(self.records.len())..end_idx.min(self.records.len())],
533 );
534 let projected = existing - replaced + octet_total(records);
535 if projected > self.max_file_size.max(existing) {
536 return Err(file_full());
537 }
538 let mut updated = std::mem::take(&mut self.records);
539 if end_idx > updated.len() {
540 updated.resize(end_idx, Vec::new());
541 }
542 updated[start_idx..end_idx].clone_from_slice(records);
543 self.set_records(updated);
544 Ok(start)
545 }
546}
547
548impl FileConfiguration for FileObject {
549 fn set_access_method(&mut self, method: FileAccessMethod) {
550 FileObject::set_file_access_method(self, method.to_raw());
551 }
552
553 fn set_stream_data(&mut self, data: Vec<u8>) -> Result<(), Error> {
554 self.require_access(FileAccessMethod::STREAM_ACCESS)?;
555 FileObject::set_data(self, data);
556 Ok(())
557 }
558
559 fn stream_data(&self) -> Result<&[u8], Error> {
560 self.require_access(FileAccessMethod::STREAM_ACCESS)?;
561 Ok(FileObject::data(self))
562 }
563
564 fn set_record_data(&mut self, records: Vec<Vec<u8>>) -> Result<(), Error> {
565 self.require_access(FileAccessMethod::RECORD_ACCESS)?;
566 FileObject::set_records(self, records);
567 Ok(())
568 }
569
570 fn record_data(&self) -> Result<&[Vec<u8>], Error> {
571 self.require_access(FileAccessMethod::RECORD_ACCESS)?;
572 Ok(FileObject::records(self))
573 }
574
575 fn set_max_file_size(&mut self, max_octets: u64) -> u64 {
576 FileObject::set_max_file_size(self, max_octets);
577 FileObject::max_file_size(self)
578 }
579
580 fn set_max_record_count(&mut self, max_records: u64) -> u64 {
581 FileObject::set_max_record_count(self, max_records);
582 FileObject::max_record_count(self)
583 }
584}
585
586impl BACnetObject for FileObject {
587 fn object_identifier(&self) -> ObjectIdentifier {
588 self.oid
589 }
590
591 fn object_name(&self) -> &str {
592 &self.name
593 }
594
595 fn read_property(
596 &self,
597 property: PropertyIdentifier,
598 array_index: Option<u32>,
599 ) -> Result<PropertyValue, Error> {
600 if let Some(result) = read_common_properties!(self, property, array_index) {
601 return result;
602 }
603
604 match property {
605 p if p == PropertyIdentifier::OBJECT_TYPE => {
606 Ok(PropertyValue::Enumerated(ObjectType::FILE.to_raw()))
607 }
608 p if p == PropertyIdentifier::FILE_TYPE => {
609 Ok(PropertyValue::CharacterString(self.file_type.clone()))
610 }
611 p if p == PropertyIdentifier::FILE_SIZE => Ok(PropertyValue::Unsigned(self.file_size)),
612 p if p == PropertyIdentifier::MODIFICATION_DATE => Ok(PropertyValue::List(vec![
613 PropertyValue::Date(self.modification_date.0),
614 PropertyValue::Time(self.modification_date.1),
615 ])),
616 p if p == PropertyIdentifier::ARCHIVE => Ok(PropertyValue::Boolean(self.archive)),
617 p if p == PropertyIdentifier::READ_ONLY => Ok(PropertyValue::Boolean(self.read_only)),
618 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
619 Ok(PropertyValue::Enumerated(self.file_access_method))
620 }
621 p if p == PropertyIdentifier::RECORD_COUNT => match self.record_count {
622 Some(count) => Ok(PropertyValue::Unsigned(count)),
623 None => Err(common::unknown_property_error()),
624 },
625 _ => Err(common::unknown_property_error()),
626 }
627 }
628
629 fn write_property(
630 &mut self,
631 property: PropertyIdentifier,
632 _array_index: Option<u32>,
633 value: PropertyValue,
634 _priority: Option<u8>,
635 ) -> Result<(), Error> {
636 if let Some(result) = common::write_description(&mut self.description, property, &value) {
637 return result;
638 }
639 if let Some(result) =
640 common::write_out_of_service(&mut self.out_of_service, property, &value)
641 {
642 return result;
643 }
644
645 match property {
646 p if p == PropertyIdentifier::ARCHIVE => {
647 if let PropertyValue::Boolean(v) = value {
648 self.archive = v;
649 Ok(())
650 } else {
651 Err(common::invalid_data_type_error())
652 }
653 }
654 p if p == PropertyIdentifier::FILE_TYPE => {
655 if let PropertyValue::CharacterString(s) = value {
656 self.file_type = s;
657 Ok(())
658 } else {
659 Err(common::invalid_data_type_error())
660 }
661 }
662 p if p == PropertyIdentifier::READ_ONLY => {
663 Err(common::write_access_denied_error())
666 }
667 p if p == PropertyIdentifier::FILE_SIZE => resize::write_stream(self, value),
668 p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
669 Err(common::write_access_denied_error())
670 }
671 p if p == PropertyIdentifier::MODIFICATION_DATE => {
672 Err(common::write_access_denied_error())
673 }
674 p if p == PropertyIdentifier::RECORD_COUNT => resize::write_records(self, value),
675 _ => Err(common::write_access_denied_error()),
676 }
677 }
678
679 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
680 let mut props = vec![
681 PropertyIdentifier::OBJECT_IDENTIFIER,
682 PropertyIdentifier::OBJECT_NAME,
683 PropertyIdentifier::OBJECT_TYPE,
684 PropertyIdentifier::DESCRIPTION,
685 PropertyIdentifier::FILE_TYPE,
686 PropertyIdentifier::FILE_SIZE,
687 PropertyIdentifier::MODIFICATION_DATE,
688 PropertyIdentifier::ARCHIVE,
689 PropertyIdentifier::READ_ONLY,
690 PropertyIdentifier::FILE_ACCESS_METHOD,
691 PropertyIdentifier::STATUS_FLAGS,
692 PropertyIdentifier::OUT_OF_SERVICE,
693 PropertyIdentifier::RELIABILITY,
694 ];
695 if self.record_count.is_some() {
696 props.push(PropertyIdentifier::RECORD_COUNT);
697 }
698 Cow::Owned(props)
699 }
700
701 fn is_writable_property(&self, property: PropertyIdentifier) -> bool {
702 resize::is_writable(self, property)
703 }
704
705 fn capture_write_property_rollback(
706 &mut self,
707 property: PropertyIdentifier,
708 value: &PropertyValue,
709 ) -> Option<WritePropertyRollback> {
710 resize::capture(self, property, value)
711 }
712
713 fn restore_write_property_rollback(
714 &mut self,
715 rollback: WritePropertyRollback,
716 ) -> Result<(), Error> {
717 resize::restore(self, rollback)
718 }
719
720 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
721 self.clock = clock;
722 }
723
724 fn file_configuration_internal(&self) -> Option<&dyn FileConfiguration> {
725 Some(self)
726 }
727
728 fn file_configuration_internal_mut(&mut self) -> Option<&mut dyn FileConfiguration> {
729 Some(self)
730 }
731
732 fn file_storage_internal(&self) -> Option<&dyn FileStorage> {
733 Some(self)
734 }
735
736 fn file_storage_internal_mut(&mut self) -> Option<&mut dyn FileStorage> {
737 Some(self)
738 }
739}
740
741#[cfg(test)]
746mod resize_tests;
747#[cfg(test)]
748mod storage_tests;
749#[cfg(test)]
750mod tests;