use super::*;
use bacnet_objects::file::FileWriteStart;
use bacnet_objects::traits::BACnetObject;
use bacnet_types::enums::FileAccessMethod as ObjectFileAccessMethod;
fn invalid_file_access_method() -> Error {
Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::INVALID_FILE_ACCESS_METHOD.to_raw() as u32,
}
}
fn inconsistent_object_type() -> Error {
Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::INCONSISTENT_OBJECT_TYPE.to_raw() as u32,
}
}
fn validate_file_access_method(
object: &dyn BACnetObject,
expected: ObjectFileAccessMethod,
) -> Result<(), Error> {
let actual = match object.read_property(PropertyIdentifier::FILE_ACCESS_METHOD, None) {
Ok(PropertyValue::Enumerated(raw)) => raw,
_ => return Err(invalid_file_access_method()),
};
if actual != expected.to_raw() {
return Err(invalid_file_access_method());
}
Ok(())
}
fn invalid_file_start_position() -> Error {
Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::INVALID_FILE_START_POSITION.to_raw() as u32,
}
}
fn file_access_denied() -> Error {
Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::FILE_ACCESS_DENIED.to_raw() as u32,
}
}
fn unknown_object() -> Error {
Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
}
}
fn ack_position(actual: u64) -> Result<i32, Error> {
i32::try_from(actual).map_err(|_| Error::Protocol {
class: ErrorClass::DEVICE.to_raw() as u32,
code: ErrorCode::INTERNAL_ERROR.to_raw() as u32,
})
}
pub fn handle_atomic_read_file(
db: &ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
use bacnet_services::common::MAX_DECODED_ITEMS;
use bacnet_services::file::{
AtomicReadFileAck, AtomicReadFileRequest, FileAccessMethod, FileReadAckMethod,
};
let request = AtomicReadFileRequest::decode(service_data)?;
if request.file_identifier.object_type() != ObjectType::FILE {
return Err(inconsistent_object_type());
}
let object = db
.get(&request.file_identifier)
.ok_or_else(unknown_object)?;
if object.file_storage_internal().is_none() {
return Err(file_access_denied());
}
let expected = match &request.access {
FileAccessMethod::Stream { .. } => ObjectFileAccessMethod::STREAM_ACCESS,
FileAccessMethod::Record { .. } => ObjectFileAccessMethod::RECORD_ACCESS,
};
validate_file_access_method(object, expected)?;
let storage = object
.file_storage_internal()
.ok_or_else(file_access_denied)?;
match request.access {
FileAccessMethod::Stream {
file_start_position,
requested_octet_count,
} => {
let start =
u64::try_from(file_start_position).map_err(|_| invalid_file_start_position())?;
let read = storage.read_stream(start, u64::from(requested_octet_count))?;
let ack = AtomicReadFileAck {
end_of_file: read.end_of_file,
access: FileReadAckMethod::Stream {
file_start_position,
file_data: read.data,
},
};
ack.encode(buf);
Ok(())
}
FileAccessMethod::Record {
file_start_record,
requested_record_count,
} => {
let start =
u64::try_from(file_start_record).map_err(|_| invalid_file_start_position())?;
let count = u64::from(requested_record_count).min(MAX_DECODED_ITEMS as u64);
let read = storage.read_records(start, count)?;
let ack = AtomicReadFileAck {
end_of_file: read.end_of_file,
access: FileReadAckMethod::Record {
file_start_record,
returned_record_count: read.records.len() as u32,
file_record_data: read.records,
},
};
ack.encode(buf);
Ok(())
}
}
}
pub fn handle_atomic_write_file(
db: &mut ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
use bacnet_services::file::{
AtomicWriteFileAck, AtomicWriteFileRequest, FileWriteAccessMethod, FileWriteAckMethod,
};
let request = AtomicWriteFileRequest::decode(service_data)?;
if let FileWriteAccessMethod::Record {
record_count,
file_record_data,
..
} = &request.access
{
if *record_count as usize != file_record_data.len() {
return Err(Error::Reject {
reason: RejectReason::MISSING_REQUIRED_PARAMETER.to_raw(),
});
}
}
if request.file_identifier.object_type() != ObjectType::FILE {
return Err(inconsistent_object_type());
}
let object = db
.get_mut(&request.file_identifier)
.ok_or_else(unknown_object)?;
if object.file_storage_internal().is_none() {
return Err(file_access_denied());
}
match object.read_property(PropertyIdentifier::READ_ONLY, None) {
Ok(PropertyValue::Boolean(false)) => {}
_ => return Err(file_access_denied()),
}
let expected = match &request.access {
FileWriteAccessMethod::Stream { .. } => ObjectFileAccessMethod::STREAM_ACCESS,
FileWriteAccessMethod::Record { .. } => ObjectFileAccessMethod::RECORD_ACCESS,
};
validate_file_access_method(&**object, expected)?;
let storage = object
.file_storage_internal_mut()
.ok_or_else(file_access_denied)?;
match request.access {
FileWriteAccessMethod::Stream {
file_start_position,
file_data,
} => {
let start = write_start(file_start_position)?;
let actual = storage.write_stream(start, &file_data)?;
let ack = AtomicWriteFileAck {
access: FileWriteAckMethod::Stream {
file_start_position: ack_position(actual)?,
},
};
ack.encode(buf);
Ok(())
}
FileWriteAccessMethod::Record {
file_start_record,
file_record_data,
..
} => {
let start = write_start(file_start_record)?;
let actual = storage.write_records(start, &file_record_data)?;
let ack = AtomicWriteFileAck {
access: FileWriteAckMethod::Record {
file_start_record: ack_position(actual)?,
},
};
ack.encode(buf);
Ok(())
}
}
}
fn write_start(requested: i32) -> Result<FileWriteStart, Error> {
match requested {
-1 => Ok(FileWriteStart::Append),
position => u64::try_from(position)
.map(FileWriteStart::At)
.map_err(|_| invalid_file_start_position()),
}
}