use bacnet_types::enums::{
ErrorClass, ErrorCode, FileAccessMethod, ObjectType, PropertyIdentifier,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, Time};
use std::borrow::Cow;
use std::sync::Arc;
use crate::clock::ClockReader;
use crate::common::{self, read_identity_properties};
use crate::traits::BACnetObject;
mod metadata;
mod resize;
pub const DEFAULT_MAX_FILE_SIZE: u64 = 1_048_576;
pub const DEFAULT_MAX_RECORD_COUNT: u64 = 10_000;
const MAX_RECORD_CAP: u64 = DEFAULT_MAX_RECORD_COUNT;
#[doc(hidden)]
pub trait FileConfiguration: Send + Sync {
fn set_access_method(&mut self, method: FileAccessMethod);
fn set_stream_data(&mut self, data: Vec<u8>) -> Result<(), Error>;
fn stream_data(&self) -> Result<&[u8], Error>;
fn set_record_data(&mut self, records: Vec<Vec<u8>>) -> Result<(), Error>;
fn record_data(&self) -> Result<&[Vec<u8>], Error>;
fn set_max_file_size(&mut self, max_octets: u64) -> u64;
fn set_max_record_count(&mut self, max_records: u64) -> u64;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileStreamRead {
pub data: Vec<u8>,
pub end_of_file: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileRecordRead {
pub records: Vec<Vec<u8>>,
pub end_of_file: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileWriteStart {
At(u64),
Append,
}
pub trait FileStorage: Send + Sync {
fn read_stream(&self, _start: u64, _count: u64) -> Result<FileStreamRead, Error> {
Err(file_access_denied())
}
fn write_stream(&mut self, _start: FileWriteStart, _data: &[u8]) -> Result<u64, Error> {
Err(file_access_denied())
}
fn read_records(&self, _start: u64, _count: u64) -> Result<FileRecordRead, Error> {
Err(file_access_denied())
}
fn write_records(
&mut self,
_start: FileWriteStart,
_records: &[Vec<u8>],
) -> Result<u64, Error> {
Err(file_access_denied())
}
}
fn invalid_file_access_method() -> Error {
common::protocol_error(ErrorClass::SERVICES, ErrorCode::INVALID_FILE_ACCESS_METHOD)
}
fn file_access_denied() -> Error {
common::protocol_error(ErrorClass::SERVICES, ErrorCode::FILE_ACCESS_DENIED)
}
fn invalid_file_start_position() -> Error {
common::protocol_error(ErrorClass::SERVICES, ErrorCode::INVALID_FILE_START_POSITION)
}
fn file_full() -> Error {
common::protocol_error(ErrorClass::OBJECT, ErrorCode::FILE_FULL)
}
const MAX_REPRESENTABLE: u64 = i32::MAX as u64;
fn octet_total(records: &[Vec<u8>]) -> u64 {
records.iter().map(|r| r.len() as u64).sum()
}
pub struct FileObject {
oid: ObjectIdentifier,
name: String,
description: String,
file_type: String,
file_size: u64,
modification_date: (Date, Time),
archive: bool,
read_only: bool,
file_access_method: u32,
record_count: Option<u64>,
data: Vec<u8>,
records: Vec<Vec<u8>>,
max_file_size: u64,
max_record_count: u64,
clock: Option<Arc<dyn ClockReader>>,
}
impl FileObject {
pub fn new(
instance: u32,
name: impl Into<String>,
file_type: impl Into<String>,
) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::FILE, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
file_type: file_type.into(),
file_size: 0,
modification_date: crate::clock::UNSPECIFIED_DATETIME,
archive: false,
read_only: false,
file_access_method: FileAccessMethod::STREAM_ACCESS.to_raw(),
record_count: None,
data: Vec::new(),
records: Vec::new(),
max_file_size: DEFAULT_MAX_FILE_SIZE,
max_record_count: DEFAULT_MAX_RECORD_COUNT,
clock: None,
})
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
pub fn set_file_type(&mut self, ft: impl Into<String>) {
self.file_type = ft.into();
}
pub fn set_data(&mut self, data: Vec<u8>) {
self.data = data;
if self.file_access_method != FileAccessMethod::RECORD_ACCESS.to_raw() {
self.file_size = self.data.len() as u64;
}
self.mark_modified();
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn set_file_access_method(&mut self, method: u32) {
let old_file_size = self.file_size;
self.file_access_method = method;
if method == FileAccessMethod::RECORD_ACCESS.to_raw() {
self.record_count = Some(self.records.len() as u64);
self.file_size = octet_total(&self.records);
} else {
self.record_count = None;
self.file_size = self.data.len() as u64;
}
if self.file_size != old_file_size {
self.mark_modified();
}
}
pub fn set_records(&mut self, records: Vec<Vec<u8>>) {
self.records = records;
if self.file_access_method == FileAccessMethod::RECORD_ACCESS.to_raw() {
self.record_count = Some(self.records.len() as u64);
self.file_size = octet_total(&self.records);
}
self.mark_modified();
}
pub fn records(&self) -> &[Vec<u8>] {
&self.records
}
pub fn set_modification_date(&mut self, date: Date, time: Time) {
let modification_date = (date, time);
if self.modification_date != modification_date {
self.modification_date = modification_date;
self.archive = false;
}
}
pub fn set_archive(&mut self, archive: bool) {
self.archive = archive;
}
pub fn set_read_only(&mut self, read_only: bool) {
self.read_only = read_only;
}
pub fn file_size(&self) -> u64 {
self.file_size
}
pub fn archive(&self) -> bool {
self.archive
}
pub fn read_only(&self) -> bool {
self.read_only
}
pub fn set_max_file_size(&mut self, max_octets: u64) {
self.max_file_size = max_octets.min(MAX_REPRESENTABLE);
}
pub fn max_file_size(&self) -> u64 {
self.max_file_size
}
pub fn set_max_record_count(&mut self, max_records: u64) {
self.max_record_count = max_records.min(MAX_RECORD_CAP).min(MAX_REPRESENTABLE);
}
pub fn max_record_count(&self) -> u64 {
self.max_record_count
}
fn require_access(&self, method: FileAccessMethod) -> Result<(), Error> {
if self.file_access_method == method.to_raw() {
Ok(())
} else {
Err(invalid_file_access_method())
}
}
fn mark_modified(&mut self) {
self.modification_date = crate::clock::stamp_datetime(self.clock.as_deref());
self.archive = false;
}
}
impl FileStorage for FileObject {
fn read_stream(&self, start: u64, count: u64) -> Result<FileStreamRead, Error> {
self.require_access(FileAccessMethod::STREAM_ACCESS)?;
let len = self.data.len() as u64;
if start > len {
return Err(invalid_file_start_position());
}
let end = start.saturating_add(count).min(len);
Ok(FileStreamRead {
data: self.data[start as usize..end as usize].to_vec(),
end_of_file: len == 0 || (end >= len && end > start),
})
}
fn write_stream(&mut self, start: FileWriteStart, data: &[u8]) -> Result<u64, Error> {
self.require_access(FileAccessMethod::STREAM_ACCESS)?;
let len = self.data.len() as u64;
let start = match start {
FileWriteStart::At(offset) => offset,
FileWriteStart::Append => len,
};
let end = start.checked_add(data.len() as u64).ok_or_else(file_full)?;
if end > self.max_file_size.max(len) {
return Err(file_full());
}
let (start_idx, end_idx) = (
usize::try_from(start).map_err(|_| file_full())?,
usize::try_from(end).map_err(|_| file_full())?,
);
let mut updated = std::mem::take(&mut self.data);
if end_idx > updated.len() {
updated.resize(end_idx, 0);
}
updated[start_idx..end_idx].copy_from_slice(data);
self.set_data(updated);
Ok(start)
}
fn read_records(&self, start: u64, count: u64) -> Result<FileRecordRead, Error> {
self.require_access(FileAccessMethod::RECORD_ACCESS)?;
let len = self.records.len() as u64;
if start > len {
return Err(invalid_file_start_position());
}
let end = start.saturating_add(count).min(len);
Ok(FileRecordRead {
records: self.records[start as usize..end as usize].to_vec(),
end_of_file: len == 0 || (end >= len && end > start),
})
}
fn write_records(&mut self, start: FileWriteStart, records: &[Vec<u8>]) -> Result<u64, Error> {
self.require_access(FileAccessMethod::RECORD_ACCESS)?;
let len = self.records.len() as u64;
let start = match start {
FileWriteStart::At(index) => index,
FileWriteStart::Append => len,
};
let end = start
.checked_add(records.len() as u64)
.ok_or_else(file_full)?;
if end > self.max_record_count.max(len) {
return Err(file_full());
}
let (start_idx, end_idx) = (
usize::try_from(start).map_err(|_| file_full())?,
usize::try_from(end).map_err(|_| file_full())?,
);
let existing = octet_total(&self.records);
let replaced = octet_total(
&self.records[start_idx.min(self.records.len())..end_idx.min(self.records.len())],
);
let projected = existing - replaced + octet_total(records);
if projected > self.max_file_size.max(existing) {
return Err(file_full());
}
let mut updated = std::mem::take(&mut self.records);
if end_idx > updated.len() {
updated.resize(end_idx, Vec::new());
}
updated[start_idx..end_idx].clone_from_slice(records);
self.set_records(updated);
Ok(start)
}
}
impl FileConfiguration for FileObject {
fn set_access_method(&mut self, method: FileAccessMethod) {
FileObject::set_file_access_method(self, method.to_raw());
}
fn set_stream_data(&mut self, data: Vec<u8>) -> Result<(), Error> {
self.require_access(FileAccessMethod::STREAM_ACCESS)?;
FileObject::set_data(self, data);
Ok(())
}
fn stream_data(&self) -> Result<&[u8], Error> {
self.require_access(FileAccessMethod::STREAM_ACCESS)?;
Ok(FileObject::data(self))
}
fn set_record_data(&mut self, records: Vec<Vec<u8>>) -> Result<(), Error> {
self.require_access(FileAccessMethod::RECORD_ACCESS)?;
FileObject::set_records(self, records);
Ok(())
}
fn record_data(&self) -> Result<&[Vec<u8>], Error> {
self.require_access(FileAccessMethod::RECORD_ACCESS)?;
Ok(FileObject::records(self))
}
fn set_max_file_size(&mut self, max_octets: u64) -> u64 {
FileObject::set_max_file_size(self, max_octets);
FileObject::max_file_size(self)
}
fn set_max_record_count(&mut self, max_records: u64) -> u64 {
FileObject::set_max_record_count(self, max_records);
FileObject::max_record_count(self)
}
}
impl BACnetObject for FileObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(result) = read_identity_properties!(self, property, array_index) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => {
Ok(PropertyValue::Enumerated(ObjectType::FILE.to_raw()))
}
p if p == PropertyIdentifier::FILE_TYPE => {
Ok(PropertyValue::CharacterString(self.file_type.clone()))
}
p if p == PropertyIdentifier::FILE_SIZE => Ok(PropertyValue::Unsigned(self.file_size)),
p if p == PropertyIdentifier::MODIFICATION_DATE => Ok(PropertyValue::List(vec![
PropertyValue::Date(self.modification_date.0),
PropertyValue::Time(self.modification_date.1),
])),
p if p == PropertyIdentifier::ARCHIVE => Ok(PropertyValue::Boolean(self.archive)),
p if p == PropertyIdentifier::READ_ONLY => Ok(PropertyValue::Boolean(self.read_only)),
p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
Ok(PropertyValue::Enumerated(self.file_access_method))
}
p if p == PropertyIdentifier::RECORD_COUNT => match self.record_count {
Some(count) => Ok(PropertyValue::Unsigned(count)),
None => Err(common::unknown_property_error()),
},
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
_array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
match property {
p if p == PropertyIdentifier::ARCHIVE => {
if let PropertyValue::Boolean(v) = value {
self.archive = v;
Ok(())
} else {
Err(common::invalid_data_type_error())
}
}
p if p == PropertyIdentifier::FILE_TYPE => {
if let PropertyValue::CharacterString(s) = value {
self.file_type = s;
Ok(())
} else {
Err(common::invalid_data_type_error())
}
}
p if p == PropertyIdentifier::READ_ONLY => {
Err(common::write_access_denied_error())
}
p if p == PropertyIdentifier::FILE_SIZE => resize::write_stream(self, value),
p if p == PropertyIdentifier::FILE_ACCESS_METHOD => {
Err(common::write_access_denied_error())
}
p if p == PropertyIdentifier::MODIFICATION_DATE => {
Err(common::write_access_denied_error())
}
p if p == PropertyIdentifier::RECORD_COUNT => {
if _array_index.is_none() && self.record_count.is_none() {
Err(common::unknown_property_error())
} else {
resize::write_records(self, value)
}
}
_ => Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
_array_index,
)),
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
metadata::for_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
fn file_configuration_internal(&self) -> Option<&dyn FileConfiguration> {
Some(self)
}
fn file_configuration_internal_mut(&mut self) -> Option<&mut dyn FileConfiguration> {
Some(self)
}
fn file_storage_internal(&self) -> Option<&dyn FileStorage> {
Some(self)
}
fn file_storage_internal_mut(&mut self) -> Option<&mut dyn FileStorage> {
Some(self)
}
}
#[cfg(test)]
mod resize_tests;
#[cfg(test)]
mod storage_tests;
#[cfg(test)]
mod tests;