use std::sync::Arc;
use crate::apdu::encoding::{ApduDecoder, ApduEncoder};
use crate::apdu::types::{ConfirmedService, ErrorClass, ErrorCode};
use crate::object::property::{BACnetValue, PropertyError, PropertyId};
use crate::object::types::ObjectId;
use crate::object::ObjectRegistry;
use super::handler::{ConfirmedServiceHandler, ServiceContext, ServiceResult};
#[derive(Debug, Clone)]
pub struct PropertyReference {
pub property_id: PropertyId,
pub array_index: Option<u32>,
}
impl PropertyReference {
pub fn new(property_id: PropertyId) -> Self {
Self {
property_id,
array_index: None,
}
}
pub fn with_index(property_id: PropertyId, index: u32) -> Self {
Self {
property_id,
array_index: Some(index),
}
}
pub fn is_all(&self) -> bool {
self.property_id == PropertyId::All
}
pub fn is_required(&self) -> bool {
self.property_id == PropertyId::Required
}
pub fn is_optional(&self) -> bool {
self.property_id == PropertyId::Optional
}
}
#[derive(Debug, Clone)]
pub enum PropertyAccessResult {
Value(BACnetValue),
Error {
error_class: ErrorClass,
error_code: ErrorCode,
},
}
impl PropertyAccessResult {
pub fn success(value: BACnetValue) -> Self {
Self::Value(value)
}
pub fn error(error_class: ErrorClass, error_code: ErrorCode) -> Self {
Self::Error {
error_class,
error_code,
}
}
pub fn from_property_error(error: PropertyError) -> Self {
let (error_class, error_code) = property_error_to_bacnet(error);
Self::Error {
error_class,
error_code,
}
}
pub fn is_success(&self) -> bool {
matches!(self, Self::Value(_))
}
}
#[derive(Debug, Clone)]
pub struct ReadAccessResultItem {
pub property_ref: PropertyReference,
pub result: PropertyAccessResult,
}
#[derive(Debug, Clone)]
pub struct ReadAccessResult {
pub object_id: ObjectId,
pub results: Vec<ReadAccessResultItem>,
}
#[derive(Debug, Clone)]
pub struct ReadAccessSpecification {
pub object_id: ObjectId,
pub property_references: Vec<PropertyReference>,
}
#[derive(Debug, Clone)]
pub struct ReadPropertyMultipleRequest {
pub read_access_specifications: Vec<ReadAccessSpecification>,
}
pub struct ReadPropertyMultipleHandler;
impl ReadPropertyMultipleHandler {
pub fn new() -> Self {
Self
}
pub fn decode_request(data: &[u8]) -> Result<ReadPropertyMultipleRequest, ServiceError> {
let mut decoder = ApduDecoder::new(data);
let mut specifications = Vec::new();
while !decoder.is_empty() {
let (tag, is_context, len) = decoder.decode_tag_info()?;
if !is_context || tag != 0 || len != 4 {
return Err(ServiceError::InvalidRequest);
}
let object_id = decoder.decode_object_identifier()?;
if !decoder.is_opening_tag(1) {
return Err(ServiceError::InvalidRequest);
}
decoder.read_u8()?;
let mut property_references = Vec::new();
while !decoder.is_closing_tag(1) {
let (tag, is_context, len) = decoder.decode_tag_info()?;
if !is_context || tag != 0 {
return Err(ServiceError::InvalidRequest);
}
let prop_id_value = decoder.decode_unsigned(len)?;
let property_id = PropertyId::from_u32(prop_id_value)
.ok_or(ServiceError::UnknownProperty(prop_id_value))?;
let array_index = if !decoder.is_empty()
&& !decoder.is_closing_tag(1)
&& decoder.peek() == Some(0x19)
{
let (_, _, len) = decoder.decode_tag_info()?;
Some(decoder.decode_unsigned(len)?)
} else {
None
};
property_references.push(PropertyReference {
property_id,
array_index,
});
}
decoder.read_u8()?;
specifications.push(ReadAccessSpecification {
object_id,
property_references,
});
}
Ok(ReadPropertyMultipleRequest {
read_access_specifications: specifications,
})
}
pub fn execute(
request: &ReadPropertyMultipleRequest,
objects: &ObjectRegistry,
) -> Vec<ReadAccessResult> {
let mut results = Vec::with_capacity(request.read_access_specifications.len());
for spec in &request.read_access_specifications {
let access_result = Self::read_object_properties(spec, objects);
results.push(access_result);
}
results
}
fn read_object_properties(
spec: &ReadAccessSpecification,
objects: &ObjectRegistry,
) -> ReadAccessResult {
let object = match objects.get(&spec.object_id) {
Some(obj) => obj,
None => {
let results = spec
.property_references
.iter()
.map(|prop_ref| ReadAccessResultItem {
property_ref: prop_ref.clone(),
result: PropertyAccessResult::error(
ErrorClass::Object,
ErrorCode::UnknownObject,
),
})
.collect();
return ReadAccessResult {
object_id: spec.object_id,
results,
};
}
};
let mut results = Vec::with_capacity(spec.property_references.len());
for prop_ref in &spec.property_references {
let result = if prop_ref.is_all() {
Self::read_all_properties(&object, objects)
} else if prop_ref.is_required() {
Self::read_required_properties(&object, objects)
} else if prop_ref.is_optional() {
Self::read_optional_properties(&object, objects)
} else {
vec![Self::read_single_property(&object, prop_ref)]
};
results.extend(result);
}
ReadAccessResult {
object_id: spec.object_id,
results,
}
}
fn read_single_property(
object: &Arc<dyn crate::object::traits::BACnetObject>,
prop_ref: &PropertyReference,
) -> ReadAccessResultItem {
let result = match object.read_property_at(prop_ref.property_id, prop_ref.array_index) {
Ok(value) => PropertyAccessResult::success(value),
Err(e) => PropertyAccessResult::from_property_error(e),
};
ReadAccessResultItem {
property_ref: prop_ref.clone(),
result,
}
}
fn read_all_properties(
object: &Arc<dyn crate::object::traits::BACnetObject>,
_objects: &ObjectRegistry,
) -> Vec<ReadAccessResultItem> {
object
.list_properties()
.into_iter()
.map(|prop_id| {
let prop_ref = PropertyReference::new(prop_id);
Self::read_single_property(object, &prop_ref)
})
.collect()
}
fn read_required_properties(
object: &Arc<dyn crate::object::traits::BACnetObject>,
_objects: &ObjectRegistry,
) -> Vec<ReadAccessResultItem> {
let required = [
PropertyId::ObjectIdentifier,
PropertyId::ObjectName,
PropertyId::ObjectType,
];
required
.iter()
.filter(|&&prop_id| object.has_property(prop_id))
.map(|&prop_id| {
let prop_ref = PropertyReference::new(prop_id);
Self::read_single_property(object, &prop_ref)
})
.collect()
}
fn read_optional_properties(
object: &Arc<dyn crate::object::traits::BACnetObject>,
_objects: &ObjectRegistry,
) -> Vec<ReadAccessResultItem> {
let required = [
PropertyId::ObjectIdentifier,
PropertyId::ObjectName,
PropertyId::ObjectType,
];
object
.list_properties()
.into_iter()
.filter(|prop_id| !required.contains(prop_id))
.map(|prop_id| {
let prop_ref = PropertyReference::new(prop_id);
Self::read_single_property(object, &prop_ref)
})
.collect()
}
pub fn encode_response(results: &[ReadAccessResult]) -> Vec<u8> {
let mut encoder = ApduEncoder::new();
for result in results {
encoder.encode_context_object_identifier(0, result.object_id);
encoder.encode_opening_tag(1);
for item in &result.results {
encoder.encode_context_enumerated(2, item.property_ref.property_id as u32);
if let Some(index) = item.property_ref.array_index {
encoder.encode_context_unsigned(3, index);
}
match &item.result {
PropertyAccessResult::Value(value) => {
encoder.encode_opening_tag(4);
encoder.encode_value(value);
encoder.encode_closing_tag(4);
}
PropertyAccessResult::Error {
error_class,
error_code,
} => {
encoder.encode_opening_tag(5);
encoder.encode_enumerated(*error_class as u32);
encoder.encode_enumerated(*error_code as u32);
encoder.encode_closing_tag(5);
}
}
}
encoder.encode_closing_tag(1);
}
encoder.into_bytes()
}
}
impl Default for ReadPropertyMultipleHandler {
fn default() -> Self {
Self::new()
}
}
impl ConfirmedServiceHandler for ReadPropertyMultipleHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::ReadPropertyMultiple
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
match Self::decode_request(data) {
Ok(request) => {
let results = Self::execute(&request, &ctx.objects);
let response = Self::encode_response(&results);
ServiceResult::ComplexAck(response)
}
Err(e) => ServiceResult::Error {
error_class: ErrorClass::Services,
error_code: match e {
ServiceError::InvalidRequest => ErrorCode::InvalidParameterDataType,
ServiceError::UnknownProperty(_) => ErrorCode::UnknownProperty,
_ => ErrorCode::Other,
},
},
}
}
fn name(&self) -> &'static str {
"ReadPropertyMultiple"
}
fn min_data_length(&self) -> usize {
7 }
}
#[derive(Debug, Clone)]
pub struct PropertyValueWrite {
pub property_ref: PropertyReference,
pub value: BACnetValue,
pub priority: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct WriteAccessSpecification {
pub object_id: ObjectId,
pub property_values: Vec<PropertyValueWrite>,
}
#[derive(Debug, Clone)]
pub struct WritePropertyMultipleRequest {
pub write_access_specifications: Vec<WriteAccessSpecification>,
}
#[derive(Debug, Clone)]
pub struct WriteAccessResultItem {
pub property_ref: PropertyReference,
pub error: Option<(ErrorClass, ErrorCode)>,
}
#[derive(Debug, Clone)]
pub struct WriteAccessResult {
pub object_id: ObjectId,
pub errors: Vec<WriteAccessResultItem>,
}
pub struct WritePropertyMultipleHandler;
impl WritePropertyMultipleHandler {
pub fn new() -> Self {
Self
}
pub fn decode_request(data: &[u8]) -> Result<WritePropertyMultipleRequest, ServiceError> {
let mut decoder = ApduDecoder::new(data);
let mut specifications = Vec::new();
while !decoder.is_empty() {
let (tag, is_context, len) = decoder.decode_tag_info()?;
if !is_context || tag != 0 || len != 4 {
return Err(ServiceError::InvalidRequest);
}
let object_id = decoder.decode_object_identifier()?;
if !decoder.is_opening_tag(1) {
return Err(ServiceError::InvalidRequest);
}
decoder.read_u8()?;
let mut property_values = Vec::new();
while !decoder.is_closing_tag(1) {
let (tag, is_context, len) = decoder.decode_tag_info()?;
if !is_context || tag != 0 {
return Err(ServiceError::InvalidRequest);
}
let prop_id_value = decoder.decode_unsigned(len)?;
let property_id = PropertyId::from_u32(prop_id_value)
.ok_or(ServiceError::UnknownProperty(prop_id_value))?;
let array_index = if !decoder.is_empty() && decoder.peek() == Some(0x19) {
let (_, _, len) = decoder.decode_tag_info()?;
Some(decoder.decode_unsigned(len)?)
} else {
None
};
let value = decode_property_value_from_context(&mut decoder, 2)?;
let priority = if !decoder.is_empty() && decoder.peek() == Some(0x39) {
let (_, _, len) = decoder.decode_tag_info()?;
Some(decoder.decode_unsigned(len)? as u8)
} else {
None
};
property_values.push(PropertyValueWrite {
property_ref: PropertyReference {
property_id,
array_index,
},
value,
priority,
});
}
decoder.read_u8()?;
specifications.push(WriteAccessSpecification {
object_id,
property_values,
});
}
Ok(WritePropertyMultipleRequest {
write_access_specifications: specifications,
})
}
pub fn execute(
request: &WritePropertyMultipleRequest,
objects: &ObjectRegistry,
) -> Vec<WriteAccessResult> {
let mut results = Vec::with_capacity(request.write_access_specifications.len());
for spec in &request.write_access_specifications {
let write_result = Self::write_object_properties(spec, objects);
results.push(write_result);
}
results
}
fn write_object_properties(
spec: &WriteAccessSpecification,
objects: &ObjectRegistry,
) -> WriteAccessResult {
let object = match objects.get(&spec.object_id) {
Some(obj) => obj,
None => {
let errors = spec
.property_values
.iter()
.map(|pv| WriteAccessResultItem {
property_ref: pv.property_ref.clone(),
error: Some((ErrorClass::Object, ErrorCode::UnknownObject)),
})
.collect();
return WriteAccessResult {
object_id: spec.object_id,
errors,
};
}
};
let mut errors = Vec::new();
for pv in &spec.property_values {
let result = object.write_property_at(
pv.property_ref.property_id,
pv.value.clone(),
pv.property_ref.array_index,
pv.priority,
);
if let Err(e) = result {
let (error_class, error_code) = property_error_to_bacnet(e);
errors.push(WriteAccessResultItem {
property_ref: pv.property_ref.clone(),
error: Some((error_class, error_code)),
});
}
}
WriteAccessResult {
object_id: spec.object_id,
errors,
}
}
pub fn all_successful(results: &[WriteAccessResult]) -> bool {
results.iter().all(|r| r.errors.is_empty())
}
pub fn encode_error_response(results: &[WriteAccessResult]) -> Vec<u8> {
let mut encoder = ApduEncoder::new();
for result in results {
if result.errors.is_empty() {
continue;
}
encoder.encode_context_object_identifier(0, result.object_id);
encoder.encode_opening_tag(1);
for item in &result.errors {
if let Some((error_class, error_code)) = &item.error {
encoder.encode_context_enumerated(0, item.property_ref.property_id as u32);
if let Some(index) = item.property_ref.array_index {
encoder.encode_context_unsigned(1, index);
}
encoder.encode_opening_tag(2);
encoder.encode_enumerated(*error_class as u32);
encoder.encode_enumerated(*error_code as u32);
encoder.encode_closing_tag(2);
}
}
encoder.encode_closing_tag(1);
}
encoder.into_bytes()
}
}
impl Default for WritePropertyMultipleHandler {
fn default() -> Self {
Self::new()
}
}
impl ConfirmedServiceHandler for WritePropertyMultipleHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::WritePropertyMultiple
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
match Self::decode_request(data) {
Ok(request) => {
let results = Self::execute(&request, &ctx.objects);
if Self::all_successful(&results) {
ServiceResult::SimpleAck
} else {
let first_error = results
.iter()
.flat_map(|r| r.errors.iter())
.find_map(|e| e.error)
.unwrap_or((ErrorClass::Property, ErrorCode::WriteAccessDenied));
ServiceResult::Error {
error_class: first_error.0,
error_code: first_error.1,
}
}
}
Err(e) => ServiceResult::Error {
error_class: ErrorClass::Services,
error_code: match e {
ServiceError::InvalidRequest => ErrorCode::InvalidParameterDataType,
ServiceError::UnknownProperty(_) => ErrorCode::UnknownProperty,
_ => ErrorCode::Other,
},
},
}
}
fn name(&self) -> &'static str {
"WritePropertyMultiple"
}
fn min_data_length(&self) -> usize {
10 }
}
fn decode_property_value_from_context(
decoder: &mut ApduDecoder,
expected_tag: u8,
) -> Result<BACnetValue, ServiceError> {
if decoder.is_opening_tag(expected_tag) {
decoder.read_u8()?; }
let (tag, is_context, len) = decoder.decode_tag_info()?;
let value = if is_context {
BACnetValue::Unsigned(decoder.decode_unsigned(len)?)
} else {
match tag {
0 => BACnetValue::Null,
1 => BACnetValue::Boolean(len != 0),
2 => BACnetValue::Unsigned(decoder.decode_unsigned(len)?),
3 => BACnetValue::Signed(decoder.decode_signed(len)?),
4 => BACnetValue::Real(decoder.decode_real()?),
5 => BACnetValue::Double(decoder.decode_double()?),
6 => {
let bytes = decoder.read_bytes(len)?;
BACnetValue::OctetString(bytes.to_vec())
}
7 => {
let s = decoder.decode_character_string(len)?;
BACnetValue::CharacterString(s)
}
8 => {
let bits = decoder.decode_bit_string(len)?;
BACnetValue::BitString(bits)
}
9 => BACnetValue::Enumerated(decoder.decode_unsigned(len)?),
12 => BACnetValue::ObjectIdentifier(decoder.decode_object_identifier()?),
_ => return Err(ServiceError::UnsupportedDataType(tag)),
}
};
if !decoder.is_empty() && decoder.is_closing_tag(expected_tag) {
decoder.read_u8()?; }
Ok(value)
}
fn property_error_to_bacnet(error: PropertyError) -> (ErrorClass, ErrorCode) {
match error {
PropertyError::NotFound(_) => (ErrorClass::Property, ErrorCode::UnknownProperty),
PropertyError::WriteAccessDenied(_) => (ErrorClass::Property, ErrorCode::WriteAccessDenied),
PropertyError::InvalidDataType(_) => (ErrorClass::Property, ErrorCode::InvalidDataType),
PropertyError::ValueOutOfRange(_) => (ErrorClass::Property, ErrorCode::ValueOutOfRange),
PropertyError::ReadOnly(_) => (ErrorClass::Property, ErrorCode::WriteAccessDenied),
PropertyError::InvalidArrayIndex(_) => (ErrorClass::Property, ErrorCode::InvalidArrayIndex),
}
}
#[derive(Debug, thiserror::Error)]
pub enum ServiceError {
#[error("Invalid request format")]
InvalidRequest,
#[error("Unknown property: {0}")]
UnknownProperty(u32),
#[error("Unsupported data type: {0}")]
UnsupportedDataType(u8),
#[error("APDU error: {0}")]
Apdu(#[from] crate::apdu::types::ApduError),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::standard::AnalogInput;
use crate::object::types::ObjectType;
#[test]
fn test_property_reference() {
let prop_ref = PropertyReference::new(PropertyId::PresentValue);
assert!(!prop_ref.is_all());
assert!(!prop_ref.is_required());
assert!(!prop_ref.is_optional());
let all_ref = PropertyReference::new(PropertyId::All);
assert!(all_ref.is_all());
}
#[test]
fn test_property_access_result() {
let success = PropertyAccessResult::success(BACnetValue::Real(25.0));
assert!(success.is_success());
let error = PropertyAccessResult::error(ErrorClass::Property, ErrorCode::UnknownProperty);
assert!(!error.is_success());
}
#[test]
fn test_read_property_multiple_handler() {
let registry = ObjectRegistry::new();
let ai1 = Arc::new(AnalogInput::new(1, "Temperature"));
ai1.set_value(25.0);
registry.register(ai1);
let ai2 = Arc::new(AnalogInput::new(2, "Humidity"));
ai2.set_value(50.0);
registry.register(ai2);
let request = ReadPropertyMultipleRequest {
read_access_specifications: vec![
ReadAccessSpecification {
object_id: ObjectId::new(ObjectType::AnalogInput, 1),
property_references: vec![PropertyReference::new(PropertyId::PresentValue)],
},
ReadAccessSpecification {
object_id: ObjectId::new(ObjectType::AnalogInput, 2),
property_references: vec![PropertyReference::new(PropertyId::PresentValue)],
},
],
};
let results = ReadPropertyMultipleHandler::execute(&request, ®istry);
assert_eq!(results.len(), 2);
assert_eq!(results[0].results.len(), 1);
assert!(results[0].results[0].result.is_success());
assert_eq!(results[1].results.len(), 1);
assert!(results[1].results[0].result.is_success());
}
#[test]
fn test_read_all_properties() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Temperature"));
ai.set_value(25.0);
registry.register(ai);
let request = ReadPropertyMultipleRequest {
read_access_specifications: vec![ReadAccessSpecification {
object_id: ObjectId::new(ObjectType::AnalogInput, 1),
property_references: vec![PropertyReference::new(PropertyId::All)],
}],
};
let results = ReadPropertyMultipleHandler::execute(&request, ®istry);
assert_eq!(results.len(), 1);
assert!(results[0].results.len() >= 4);
}
#[test]
fn test_write_property_multiple_handler() {
let registry = ObjectRegistry::new();
let ai = Arc::new(AnalogInput::new(1, "Temperature"));
registry.register(ai);
let request = WritePropertyMultipleRequest {
write_access_specifications: vec![WriteAccessSpecification {
object_id: ObjectId::new(ObjectType::AnalogInput, 1),
property_values: vec![PropertyValueWrite {
property_ref: PropertyReference::new(PropertyId::ObjectIdentifier),
value: BACnetValue::ObjectIdentifier(ObjectId::new(
ObjectType::AnalogInput,
999,
)),
priority: None,
}],
}],
};
let results = WritePropertyMultipleHandler::execute(&request, ®istry);
assert_eq!(results.len(), 1);
assert!(!WritePropertyMultipleHandler::all_successful(&results));
}
#[test]
fn test_unknown_object_error() {
let registry = ObjectRegistry::new();
let request = ReadPropertyMultipleRequest {
read_access_specifications: vec![ReadAccessSpecification {
object_id: ObjectId::new(ObjectType::AnalogInput, 999),
property_references: vec![PropertyReference::new(PropertyId::PresentValue)],
}],
};
let results = ReadPropertyMultipleHandler::execute(&request, ®istry);
assert_eq!(results.len(), 1);
assert_eq!(results[0].results.len(), 1);
assert!(!results[0].results[0].result.is_success());
if let PropertyAccessResult::Error {
error_class,
error_code,
} = &results[0].results[0].result
{
assert_eq!(*error_class, ErrorClass::Object);
assert_eq!(*error_code, ErrorCode::UnknownObject);
} else {
panic!("Expected error result");
}
}
}