#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "std")]
use std::fmt;
#[cfg(not(feature = "std"))]
use core::fmt;
#[cfg(not(feature = "std"))]
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "std")]
pub type Result<T> = std::result::Result<T, ServiceError>;
#[cfg(not(feature = "std"))]
pub type Result<T> = core::result::Result<T, ServiceError>;
#[derive(Debug)]
pub enum ServiceError {
UnsupportedService,
InvalidParameters(String),
Timeout,
Rejected(RejectReason),
Aborted(AbortReason),
EncodingError(String),
UnsupportedServiceChoice(u8),
}
impl fmt::Display for ServiceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ServiceError::UnsupportedService => write!(f, "Service not supported"),
ServiceError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg),
ServiceError::Timeout => write!(f, "Service timeout"),
ServiceError::Rejected(reason) => write!(f, "Service rejected: {:?}", reason),
ServiceError::Aborted(reason) => write!(f, "Service aborted: {:?}", reason),
ServiceError::EncodingError(msg) => write!(f, "Encoding error: {}", msg),
ServiceError::UnsupportedServiceChoice(choice) => {
write!(f, "Unsupported service choice: {}", choice)
}
}
}
}
#[cfg(feature = "std")]
impl Error for ServiceError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ConfirmedServiceChoice {
AcknowledgeAlarm = 0,
ConfirmedEventNotification = 2,
GetAlarmSummary = 3,
GetEnrollmentSummary = 4,
GetEventInformation = 29,
AtomicReadFile = 6,
AtomicWriteFile = 7,
AddListElement = 8,
RemoveListElement = 9,
CreateObject = 10,
DeleteObject = 11,
ReadProperty = 12,
ReadPropertyMultiple = 14,
WriteProperty = 15,
WritePropertyMultiple = 16,
DeviceCommunicationControl = 17,
ReinitializeDevice = 20,
VtOpen = 21,
VtClose = 22,
VtData = 23,
Authenticate = 24,
RequestKey = 25,
ReadRange = 26,
SubscribeCOV = 5,
SubscribeCOVProperty = 28,
AuthRequest = 34,
}
impl TryFrom<u8> for ConfirmedServiceChoice {
type Error = ServiceError;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::AcknowledgeAlarm),
2 => Ok(Self::ConfirmedEventNotification),
3 => Ok(Self::GetAlarmSummary),
4 => Ok(Self::GetEnrollmentSummary),
29 => Ok(Self::GetEventInformation),
6 => Ok(Self::AtomicReadFile),
7 => Ok(Self::AtomicWriteFile),
8 => Ok(Self::AddListElement),
9 => Ok(Self::RemoveListElement),
10 => Ok(Self::CreateObject),
11 => Ok(Self::DeleteObject),
12 => Ok(Self::ReadProperty),
14 => Ok(Self::ReadPropertyMultiple),
15 => Ok(Self::WriteProperty),
16 => Ok(Self::WritePropertyMultiple),
17 => Ok(Self::DeviceCommunicationControl),
20 => Ok(Self::ReinitializeDevice),
21 => Ok(Self::VtOpen),
22 => Ok(Self::VtClose),
23 => Ok(Self::VtData),
24 => Ok(Self::Authenticate),
25 => Ok(Self::RequestKey),
26 => Ok(Self::ReadRange),
5 => Ok(Self::SubscribeCOV),
28 => Ok(Self::SubscribeCOVProperty),
34 => Ok(Self::AuthRequest),
_ => Err(ServiceError::UnsupportedServiceChoice(value)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum UnconfirmedServiceChoice {
IAm = 0,
IHave = 1,
UnconfirmedCOVNotification = 2,
UnconfirmedEventNotification = 3,
UnconfirmedPrivateTransfer = 4,
UnconfirmedTextMessage = 5,
TimeSynchronization = 6,
WhoHas = 7,
WhoIs = 8,
UtcTimeSynchronization = 9,
WriteGroup = 10,
UnconfirmedCOVNotificationMultiple = 11,
UnconfirmedAuditNotification = 12,
WhoAmI = 13,
YouAre = 14,
}
impl TryFrom<u8> for UnconfirmedServiceChoice {
type Error = ServiceError;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::IAm),
1 => Ok(Self::IHave),
2 => Ok(Self::UnconfirmedCOVNotification),
3 => Ok(Self::UnconfirmedEventNotification),
4 => Ok(Self::UnconfirmedPrivateTransfer),
5 => Ok(Self::UnconfirmedTextMessage),
6 => Ok(Self::TimeSynchronization),
7 => Ok(Self::WhoHas),
8 => Ok(Self::WhoIs),
9 => Ok(Self::UtcTimeSynchronization),
10 => Ok(Self::WriteGroup),
11 => Ok(Self::UnconfirmedCOVNotificationMultiple),
12 => Ok(Self::UnconfirmedAuditNotification),
13 => Ok(Self::WhoAmI),
14 => Ok(Self::YouAre),
_ => Err(ServiceError::UnsupportedServiceChoice(value)),
}
}
}
generate_custom_enum!(
RejectReason{
Other = 0,
BufferOverflow = 1,
InconsistentParameters = 2,
InvalidParameterDataType = 3,
InvalidTag = 4,
MissingRequiredParameter = 5,
ParameterOutOfRange = 6,
TooManyArguments = 7,
UndefinedEnumeration = 8,
UnrecognizedService = 9,
InvalidDataEncoding = 10,
}, u8, 64..=255);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AbortReason {
Other = 0,
BufferOverflow = 1,
InvalidApduInThisState = 2,
PreemptedByHigherPriorityTask = 3,
SegmentationNotSupported = 4,
}
use crate::encoding::{
decode_context_enumerated, decode_context_object_id, decode_context_tag,
decode_context_unsigned, decode_enumerated, decode_object_identifier, decode_tag,
decode_unsigned, encode_context_enumerated, encode_context_object_id, encode_context_unsigned,
encode_enumerated, encode_object_identifier, encode_unsigned, BACnetTag,
Result as EncodingResult,
};
use crate::object::{
ObjectError, ObjectIdentifier, PropertyIdentifier, PropertyValue, Segmentation,
};
use crate::property::{self, decode_property_value, encode_property_value};
use crate::{generate_custom_enum, EncodingError};
pub const BACNET_ARRAY_ALL: u32 = 0xFFFFFFFF;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WhoIsRequest {
pub device_instance_range_low_limit: Option<u32>,
pub device_instance_range_high_limit: Option<u32>,
}
impl WhoIsRequest {
pub fn new() -> Self {
Self {
device_instance_range_low_limit: None,
device_instance_range_high_limit: None,
}
}
pub fn for_device(device_instance: u32) -> Self {
Self {
device_instance_range_low_limit: Some(device_instance),
device_instance_range_high_limit: Some(device_instance),
}
}
pub fn for_range(low: u32, high: u32) -> Self {
Self {
device_instance_range_low_limit: Some(low),
device_instance_range_high_limit: Some(high),
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
if let (Some(low), Some(high)) = (
self.device_instance_range_low_limit,
self.device_instance_range_high_limit,
) {
let low_bytes = encode_context_unsigned(low, 0)?;
buffer.extend_from_slice(&low_bytes);
let high_bytes = encode_context_unsigned(high, 1)?;
buffer.extend_from_slice(&high_bytes);
}
Ok(())
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let mut request = WhoIsRequest::new();
let mut pos = 0;
if pos < data.len() {
match decode_context_unsigned(&data[pos..], 0) {
Ok((low, consumed)) => {
request.device_instance_range_low_limit = Some(low);
pos += consumed;
if pos < data.len() {
match decode_context_unsigned(&data[pos..], 1) {
Ok((high, _consumed)) => {
request.device_instance_range_high_limit = Some(high);
}
Err(_) => {
return Err(crate::encoding::EncodingError::InvalidFormat(
"Who-Is request has low limit without high limit".to_string(),
));
}
}
}
}
Err(_) => {
}
}
}
Ok(request)
}
pub fn matches(&self, device_instance: u32) -> bool {
match (
self.device_instance_range_low_limit,
self.device_instance_range_high_limit,
) {
(None, None) => true, (Some(low), Some(high)) => device_instance >= low && device_instance <= high,
(Some(low), None) => device_instance >= low,
(None, Some(high)) => device_instance <= high,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IAmRequest {
pub device_identifier: ObjectIdentifier,
pub max_apdu_length_accepted: u32,
pub segmentation_supported: Segmentation,
pub vendor_identifier: u16,
}
impl IAmRequest {
pub fn new(
device_identifier: ObjectIdentifier,
max_apdu_length_accepted: u32,
segmentation_supported: Segmentation,
vendor_identifier: u16,
) -> Self {
Self {
device_identifier,
max_apdu_length_accepted,
segmentation_supported,
vendor_identifier,
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
encode_object_identifier(buffer, self.device_identifier)?;
encode_unsigned(buffer, self.max_apdu_length_accepted)?;
encode_enumerated(buffer, self.segmentation_supported as u32);
encode_unsigned(buffer, self.vendor_identifier as u32)?;
Ok(())
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let mut pos = 0;
let (device_identifier, consumed) = decode_object_identifier(&data[pos..])?;
pos += consumed;
let (max_apdu_length_accepted, consumed) = decode_unsigned(&data[pos..])?;
pos += consumed;
let (segmentation_supported, consumed) = decode_enumerated(&data[pos..])?;
pos += consumed;
let (vendor_identifier, _consumed) = decode_unsigned(&data[pos..])?;
Ok(IAmRequest::new(
device_identifier,
max_apdu_length_accepted,
segmentation_supported
.try_into()
.map_err(|e: ObjectError| EncodingError::InvalidFormat(e.to_string()))?,
vendor_identifier as u16,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadPropertyRequest {
pub object_identifier: ObjectIdentifier,
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
}
impl ReadPropertyRequest {
pub fn new(
object_identifier: ObjectIdentifier,
property_identifier: PropertyIdentifier,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: None,
}
}
pub fn with_array_index(
object_identifier: ObjectIdentifier,
property_identifier: PropertyIdentifier,
array_index: u32,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: Some(array_index),
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let obj_id_bytes = encode_context_object_id(self.object_identifier, 0)?;
buffer.extend_from_slice(&obj_id_bytes);
let prop_id_bytes = encode_context_enumerated(self.property_identifier.into(), 1)?;
buffer.extend_from_slice(&prop_id_bytes);
if let Some(array_index) = self.property_array_index {
let array_bytes = encode_context_unsigned(array_index, 2)?;
buffer.extend_from_slice(&array_bytes);
}
Ok(())
}
pub fn decode(buffer: &[u8]) -> EncodingResult<Self> {
let mut offset = 0;
let (object_identifier, consumed) = decode_context_object_id(&buffer[offset..], 0)?;
offset += consumed;
let (property_identifier, consumed) = decode_context_enumerated(&buffer[offset..], 1)?;
offset += consumed;
let property_array_index = if offset < buffer.len() {
let (array_index, _) = decode_context_unsigned(&buffer[offset..], 2)?;
Some(array_index)
} else {
None
};
Ok(Self {
object_identifier,
property_identifier: property_identifier.into(),
property_array_index,
})
}
}
#[derive(Debug, Clone)]
pub struct ReadPropertyResponse {
pub object_identifier: ObjectIdentifier,
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
pub property_values: Vec<property::PropertyValue>, }
impl ReadPropertyResponse {
pub fn new(
object_identifier: ObjectIdentifier,
property_identifier: PropertyIdentifier,
property_values: Vec<property::PropertyValue>,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: None,
property_values,
}
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let mut pos = 0;
let (object_identifier, consumed) = decode_context_object_id(&data[pos..], 0)?;
pos += consumed;
let (property_identifier, consumed) = decode_context_enumerated(&data[pos..], 1)?;
pos += consumed;
let property_array_index = match decode_context_unsigned(&data[pos..], 2) {
Ok((array_index, consumed)) => {
pos += consumed;
if array_index == BACNET_ARRAY_ALL {
None
} else {
Some(array_index)
}
}
Err(_) => None,
};
let (tag, _, consumed) = decode_tag(&data[pos..])?;
pos += consumed;
let property_values = if let BACnetTag::Context(3) = tag {
let (tag, _, _) = decode_tag(&data[pos..])?;
let mut current_tag = tag;
let mut values = Vec::new();
while let BACnetTag::Application(_) = current_tag {
let (value, consumed) = decode_property_value(&data[pos..])?;
values.push(value);
pos += consumed;
let (tag, _, _) = decode_tag(&data[pos..])?;
current_tag = tag;
}
values
} else {
return Err(EncodingError::InvalidTag);
};
let (tag, _, _) = decode_tag(&data[pos..])?;
if let BACnetTag::Context(tag) = tag {
if tag != 3 {
return Err(EncodingError::InvalidTag);
}
}
Ok(ReadPropertyResponse {
object_identifier,
property_identifier: property_identifier.into(),
property_array_index,
property_values,
})
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let object_id = encode_context_object_id(self.object_identifier, 0)?;
buffer.extend_from_slice(&object_id);
let prop_id = encode_context_enumerated(self.property_identifier.into(), 1)?;
buffer.extend_from_slice(&prop_id);
if let Some(array_index) = self.property_array_index {
let array_bytes = encode_context_unsigned(array_index, 2)?;
buffer.extend_from_slice(&array_bytes);
}
buffer.push(0x3E); for property_value in &self.property_values {
encode_property_value(property_value, buffer)?;
}
buffer.push(0x3F);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct WritePropertyRequest {
pub object_identifier: ObjectIdentifier,
pub property_identifier: u32,
pub property_array_index: Option<u32>,
pub property_value: Vec<u8>, pub priority: Option<u8>,
}
impl WritePropertyRequest {
pub fn new(
object_identifier: ObjectIdentifier,
property_identifier: u32,
property_value: Vec<u8>,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: None,
property_value,
priority: None,
}
}
pub fn with_priority(
object_identifier: ObjectIdentifier,
property_identifier: u32,
property_value: Vec<u8>,
priority: u8,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: None,
property_value,
priority: Some(priority),
}
}
pub fn with_array_index(
object_identifier: ObjectIdentifier,
property_identifier: u32,
array_index: u32,
property_value: Vec<u8>,
) -> Self {
Self {
object_identifier,
property_identifier,
property_array_index: Some(array_index),
property_value,
priority: None,
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let object_id: u32 = self.object_identifier.try_into()?;
buffer.push(0x0C); buffer.extend_from_slice(&object_id.to_be_bytes());
buffer.push(0x19); buffer.push(self.property_identifier as u8);
if let Some(array_index) = self.property_array_index {
buffer.push(0x29); buffer.push(array_index as u8);
}
buffer.push(0x3E); buffer.extend_from_slice(&self.property_value);
buffer.push(0x3F);
if let Some(priority) = self.priority {
buffer.push(0x49); buffer.push(priority);
}
Ok(())
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let mut pos = 0;
if pos + 5 > data.len() || data[pos] != 0x0C {
return Err(crate::encoding::EncodingError::InvalidTag);
}
pos += 1;
let object_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
let object_id = u32::from_be_bytes(object_id_bytes);
let object_identifier = object_id.into();
pos += 4;
if pos + 2 > data.len() || data[pos] != 0x19 {
return Err(crate::encoding::EncodingError::InvalidTag);
}
pos += 1;
let property_identifier = data[pos] as u32;
pos += 1;
let property_array_index = if pos < data.len() && data[pos] == 0x29 {
pos += 1;
let array_index = data[pos] as u32;
pos += 1;
Some(array_index)
} else {
None
};
if pos >= data.len() || data[pos] != 0x3E {
return Err(crate::encoding::EncodingError::InvalidTag);
}
pos += 1;
let value_start = pos;
let mut value_end = pos;
while value_end < data.len() {
if data[value_end] == 0x3F {
break;
}
value_end += 1;
}
if value_end >= data.len() {
return Err(crate::encoding::EncodingError::InvalidTag);
}
let property_value = data[value_start..value_end].to_vec();
pos = value_end + 1;
let priority = if pos < data.len() && data[pos] == 0x49 {
pos += 1;
if pos < data.len() {
Some(data[pos])
} else {
None
}
} else {
None
};
Ok(WritePropertyRequest {
object_identifier,
property_identifier,
property_array_index,
property_value,
priority,
})
}
}
#[derive(Debug, Clone)]
pub struct ReadPropertyMultipleRequest {
pub read_access_specifications: Vec<ReadAccessSpecification>,
}
#[derive(Debug, Clone)]
pub struct ReadAccessSpecification {
pub object_identifier: ObjectIdentifier,
pub property_references: Vec<PropertyReference>,
}
#[derive(Debug, Clone)]
pub struct PropertyReference {
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
}
impl ReadPropertyMultipleRequest {
pub fn new(read_access_specifications: Vec<ReadAccessSpecification>) -> Self {
Self {
read_access_specifications,
}
}
pub fn add_specification(&mut self, spec: ReadAccessSpecification) {
self.read_access_specifications.push(spec);
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
for spec in &self.read_access_specifications {
spec.encode(buffer)?;
}
Ok(())
}
}
impl ReadAccessSpecification {
pub fn new(
object_identifier: ObjectIdentifier,
property_references: Vec<PropertyReference>,
) -> Self {
Self {
object_identifier,
property_references,
}
}
pub fn add_property(&mut self, property_reference: PropertyReference) {
self.property_references.push(property_reference);
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let object_id_bytes = encode_context_object_id(self.object_identifier, 0)?;
buffer.extend_from_slice(&object_id_bytes);
buffer.push(0x1E);
for property_ref in &self.property_references {
property_ref.encode(buffer)?;
}
buffer.push(0x1F);
Ok(())
}
}
impl PropertyReference {
pub fn new(property_identifier: PropertyIdentifier) -> Self {
Self {
property_identifier,
property_array_index: None,
}
}
pub fn with_array_index(property_identifier: PropertyIdentifier, array_index: u32) -> Self {
Self {
property_identifier,
property_array_index: Some(array_index),
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let prop_id_bytes = encode_context_enumerated(self.property_identifier.into(), 0)?;
buffer.extend_from_slice(&prop_id_bytes);
if let Some(array_index) = self.property_array_index {
let array_bytes = encode_context_unsigned(array_index, 1)?;
buffer.extend_from_slice(&array_bytes);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ReadPropertyMultipleResponse {
pub read_access_results: Vec<ReadAccessResult>,
}
impl ReadPropertyMultipleResponse {
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let mut read_access_results = Vec::new();
let mut total_consumed = 0;
while total_consumed < data.len() {
let (result, consumed) = ReadAccessResult::decode(&data[total_consumed..])?;
total_consumed += consumed;
read_access_results.push(result);
}
Ok(Self {
read_access_results,
})
}
}
#[derive(Debug, Clone)]
pub struct ReadAccessResult {
pub object_identifier: ObjectIdentifier,
pub results: Vec<PropertyResult>,
}
impl ReadAccessResult {
pub fn decode(data: &[u8]) -> EncodingResult<(Self, usize)> {
let mut total_consumed = 0;
let mut results = Vec::new();
let (object_id, consumed) = decode_context_object_id(data, 0)?;
total_consumed += consumed;
let (context_id, context_size, consumed) = decode_context_tag(&data[total_consumed..])?;
total_consumed += consumed;
if context_id == 1 && context_size == 6 {
let (mut context_id, _, _) = decode_context_tag(&data[total_consumed..])?;
while context_id != 1 {
let (result, consumed) = PropertyResult::decode(&data[total_consumed..])?;
total_consumed += consumed;
results.push(result);
let (id, _, _) = decode_context_tag(&data[total_consumed..])?;
context_id = id;
}
let (_, _, consumed) = decode_context_tag(&data[total_consumed..])?;
total_consumed += consumed;
if context_id != 1 {
return Err(EncodingError::InvalidTag);
}
} else {
return Err(EncodingError::InvalidTag);
}
Ok((
Self {
object_identifier: object_id,
results,
},
total_consumed,
))
}
}
#[derive(Debug, Clone)]
pub struct PropertyResult {
pub property_identifier: PropertyIdentifier,
pub array_index: Option<u32>,
pub value: PropertyResultValue,
}
impl PropertyResult {
pub fn decode(bytes: &[u8]) -> EncodingResult<(Self, usize)> {
let (property_identifier, consumed) = decode_context_enumerated(bytes, 2)?;
let mut total_consumed = consumed;
let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
let array_index = if let BACnetTag::Context(3) = tag {
let (index, consumed) = decode_context_unsigned(&bytes[total_consumed..], 3)?;
total_consumed += consumed;
Some(index)
} else {
None
};
let (tag, _, consumed) = decode_tag(&bytes[total_consumed..])?;
total_consumed += consumed;
let value = if let BACnetTag::Context(4) = tag {
let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
let mut current_tag = tag;
let mut values = Vec::new();
while let BACnetTag::Application(_) = current_tag {
let (value, consumed) = decode_property_value(&bytes[total_consumed..])?;
values.push(value);
total_consumed += consumed;
let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
current_tag = tag;
}
PropertyResultValue::Value(values)
} else if let BACnetTag::Context(5) = tag {
let (error_class, consumed) = decode_enumerated(&bytes[total_consumed..])?;
total_consumed += consumed;
let (error_code, consumed) = decode_enumerated(&bytes[total_consumed..])?;
total_consumed += consumed;
PropertyResultValue::Error(error_class, error_code)
} else {
return Err(EncodingError::InvalidTag);
};
let (tag, _, consumed) = decode_tag(&bytes[total_consumed..])?;
total_consumed += consumed;
if let BACnetTag::Context(tag) = tag {
if tag != 4 && tag != 5 {
return Err(EncodingError::InvalidTag);
}
} else {
return Err(EncodingError::InvalidTag);
}
Ok((
Self {
property_identifier: property_identifier.into(),
array_index,
value,
},
total_consumed,
))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyResultValue {
Value(Vec<property::PropertyValue>),
Error(u32, u32),
}
#[derive(Debug, Clone)]
pub struct SubscribeCovRequest {
pub subscriber_process_identifier: u32,
pub monitored_object_identifier: ObjectIdentifier,
pub issue_confirmed_notifications: Option<bool>,
pub lifetime: Option<u32>,
}
impl SubscribeCovRequest {
pub fn new(
subscriber_process_identifier: u32,
monitored_object_identifier: ObjectIdentifier,
) -> Self {
Self {
subscriber_process_identifier,
monitored_object_identifier,
issue_confirmed_notifications: None,
lifetime: None,
}
}
pub fn with_confirmation(
subscriber_process_identifier: u32,
monitored_object_identifier: ObjectIdentifier,
issue_confirmed_notifications: bool,
) -> Self {
Self {
subscriber_process_identifier,
monitored_object_identifier,
issue_confirmed_notifications: Some(issue_confirmed_notifications),
lifetime: None,
}
}
pub fn with_lifetime(
subscriber_process_identifier: u32,
monitored_object_identifier: ObjectIdentifier,
lifetime: u32,
) -> Self {
Self {
subscriber_process_identifier,
monitored_object_identifier,
issue_confirmed_notifications: None,
lifetime: Some(lifetime),
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
buffer.push(0x09); buffer.push(self.subscriber_process_identifier as u8);
let object_id: u32 = self.monitored_object_identifier.try_into()?;
buffer.push(0x1C); buffer.extend_from_slice(&object_id.to_be_bytes());
if let Some(confirmed) = self.issue_confirmed_notifications {
buffer.push(0x22); buffer.push(if confirmed { 1 } else { 0 });
}
if let Some(lifetime) = self.lifetime {
buffer.push(0x39); buffer.push(lifetime as u8);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SubscribeCovPropertyRequest {
pub subscriber_process_identifier: u32,
pub monitored_object_identifier: ObjectIdentifier,
pub issue_confirmed_notifications: Option<bool>,
pub lifetime: Option<u32>,
pub monitored_property: PropertyReference,
pub cov_increment: Option<f32>,
}
impl SubscribeCovPropertyRequest {
pub fn new(
subscriber_process_identifier: u32,
monitored_object_identifier: ObjectIdentifier,
monitored_property: PropertyReference,
) -> Self {
Self {
subscriber_process_identifier,
monitored_object_identifier,
issue_confirmed_notifications: None,
lifetime: None,
monitored_property,
cov_increment: None,
}
}
pub fn with_cov_increment(mut self, increment: f32) -> Self {
self.cov_increment = Some(increment);
self
}
}
#[derive(Debug, Clone)]
pub struct CovNotificationRequest {
pub subscriber_process_identifier: u32,
pub initiating_device_identifier: ObjectIdentifier,
pub monitored_object_identifier: ObjectIdentifier,
pub time_remaining: u32,
pub list_of_values: Vec<PropertyValue>,
}
impl CovNotificationRequest {
pub fn new(
subscriber_process_identifier: u32,
initiating_device_identifier: ObjectIdentifier,
monitored_object_identifier: ObjectIdentifier,
time_remaining: u32,
list_of_values: Vec<PropertyValue>,
) -> Self {
Self {
subscriber_process_identifier,
initiating_device_identifier,
monitored_object_identifier,
time_remaining,
list_of_values,
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
buffer.push(0x09); buffer.push(self.subscriber_process_identifier as u8);
let device_id: u32 = self.initiating_device_identifier.try_into()?;
buffer.push(0x1C); buffer.extend_from_slice(&device_id.to_be_bytes());
let object_id: u32 = self.monitored_object_identifier.try_into()?;
buffer.push(0x2C); buffer.extend_from_slice(&object_id.to_be_bytes());
buffer.push(0x39); buffer.push(self.time_remaining as u8);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CovSubscription {
pub subscriber_process_identifier: u32,
pub subscriber_device_identifier: ObjectIdentifier,
pub monitored_object_identifier: ObjectIdentifier,
pub monitored_property: Option<PropertyReference>,
pub issue_confirmed_notifications: bool,
pub lifetime: u32,
pub time_remaining: u32,
pub cov_increment: Option<f32>,
}
impl CovSubscription {
pub fn new(
subscriber_process_identifier: u32,
subscriber_device_identifier: ObjectIdentifier,
monitored_object_identifier: ObjectIdentifier,
lifetime: u32,
) -> Self {
Self {
subscriber_process_identifier,
subscriber_device_identifier,
monitored_object_identifier,
monitored_property: None,
issue_confirmed_notifications: false,
lifetime,
time_remaining: lifetime,
cov_increment: None,
}
}
pub fn is_expired(&self) -> bool {
self.lifetime > 0 && self.time_remaining == 0
}
pub fn update_time(&mut self, elapsed_seconds: u32) {
if self.lifetime > 0 {
self.time_remaining = self.time_remaining.saturating_sub(elapsed_seconds);
}
}
}
#[derive(Debug, Default)]
pub struct CovSubscriptionManager {
subscriptions: Vec<CovSubscription>,
}
impl CovSubscriptionManager {
pub fn new() -> Self {
Self {
subscriptions: Vec::new(),
}
}
pub fn add_subscription(&mut self, subscription: CovSubscription) {
self.subscriptions.retain(|s| {
!(s.subscriber_device_identifier == subscription.subscriber_device_identifier
&& s.subscriber_process_identifier == subscription.subscriber_process_identifier
&& s.monitored_object_identifier == subscription.monitored_object_identifier)
});
self.subscriptions.push(subscription);
}
pub fn remove_subscription(
&mut self,
subscriber_device: ObjectIdentifier,
subscriber_process: u32,
monitored_object: ObjectIdentifier,
) {
self.subscriptions.retain(|s| {
!(s.subscriber_device_identifier == subscriber_device
&& s.subscriber_process_identifier == subscriber_process
&& s.monitored_object_identifier == monitored_object)
});
}
pub fn get_subscriptions_for_object(
&self,
object_id: ObjectIdentifier,
) -> Vec<&CovSubscription> {
self.subscriptions
.iter()
.filter(|s| s.monitored_object_identifier == object_id && !s.is_expired())
.collect()
}
pub fn cleanup_expired(&mut self) {
self.subscriptions.retain(|s| !s.is_expired());
}
pub fn update_timers(&mut self, elapsed_seconds: u32) {
for subscription in &mut self.subscriptions {
subscription.update_time(elapsed_seconds);
}
}
pub fn active_count(&self) -> usize {
self.subscriptions
.iter()
.filter(|s| !s.is_expired())
.count()
}
}
#[derive(Debug, Clone)]
pub struct AtomicReadFileRequest {
pub file_identifier: ObjectIdentifier,
pub access_method: FileAccessMethod,
}
#[derive(Debug, Clone)]
pub enum FileAccessMethod {
StreamAccess {
file_start_position: i32,
requested_octet_count: u32,
},
RecordAccess {
file_start_record: i32,
requested_record_count: u32,
},
}
impl AtomicReadFileRequest {
pub fn new_stream_access(
file_identifier: ObjectIdentifier,
start_position: i32,
octet_count: u32,
) -> Self {
Self {
file_identifier,
access_method: FileAccessMethod::StreamAccess {
file_start_position: start_position,
requested_octet_count: octet_count,
},
}
}
pub fn new_record_access(
file_identifier: ObjectIdentifier,
start_record: i32,
record_count: u32,
) -> Self {
Self {
file_identifier,
access_method: FileAccessMethod::RecordAccess {
file_start_record: start_record,
requested_record_count: record_count,
},
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let file_id: u32 = self.file_identifier.try_into()?;
buffer.push(0x0C); buffer.extend_from_slice(&file_id.to_be_bytes());
buffer.push(0x1E);
match &self.access_method {
FileAccessMethod::StreamAccess {
file_start_position,
requested_octet_count,
} => {
buffer.push(0x0E);
buffer.push(0x09); buffer.extend_from_slice(&file_start_position.to_be_bytes());
buffer.push(0x19); buffer.extend_from_slice(&requested_octet_count.to_be_bytes());
buffer.push(0x0F); }
FileAccessMethod::RecordAccess {
file_start_record,
requested_record_count,
} => {
buffer.push(0x1E);
buffer.push(0x09); buffer.extend_from_slice(&file_start_record.to_be_bytes());
buffer.push(0x19); buffer.extend_from_slice(&requested_record_count.to_be_bytes());
buffer.push(0x1F); }
}
buffer.push(0x1F);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AtomicReadFileResponse {
pub end_of_file: bool,
pub access_method_result: FileAccessMethodResult,
}
#[derive(Debug, Clone)]
pub enum FileAccessMethodResult {
StreamAccess {
file_start_position: i32,
file_data: Vec<u8>,
},
RecordAccess {
file_start_record: i32,
record_count: u32,
file_record_data: Vec<Vec<u8>>,
},
}
impl AtomicReadFileResponse {
pub fn new_stream_access(end_of_file: bool, start_position: i32, data: Vec<u8>) -> Self {
Self {
end_of_file,
access_method_result: FileAccessMethodResult::StreamAccess {
file_start_position: start_position,
file_data: data,
},
}
}
pub fn new_record_access(end_of_file: bool, start_record: i32, records: Vec<Vec<u8>>) -> Self {
let record_count = records.len() as u32;
Self {
end_of_file,
access_method_result: FileAccessMethodResult::RecordAccess {
file_start_record: start_record,
record_count,
file_record_data: records,
},
}
}
}
#[derive(Debug, Clone)]
pub struct AtomicWriteFileRequest {
pub file_identifier: ObjectIdentifier,
pub access_method: FileWriteAccessMethod,
}
#[derive(Debug, Clone)]
pub enum FileWriteAccessMethod {
StreamAccess {
file_start_position: i32,
file_data: Vec<u8>,
},
RecordAccess {
file_start_record: i32,
record_count: u32,
file_record_data: Vec<Vec<u8>>,
},
}
impl AtomicWriteFileRequest {
pub fn new_stream_access(
file_identifier: ObjectIdentifier,
start_position: i32,
data: Vec<u8>,
) -> Self {
Self {
file_identifier,
access_method: FileWriteAccessMethod::StreamAccess {
file_start_position: start_position,
file_data: data,
},
}
}
pub fn new_record_access(
file_identifier: ObjectIdentifier,
start_record: i32,
records: Vec<Vec<u8>>,
) -> Self {
let record_count = records.len() as u32;
Self {
file_identifier,
access_method: FileWriteAccessMethod::RecordAccess {
file_start_record: start_record,
record_count,
file_record_data: records,
},
}
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
let file_id: u32 = self.file_identifier.try_into()?;
buffer.push(0x0C); buffer.extend_from_slice(&file_id.to_be_bytes());
buffer.push(0x1E);
match &self.access_method {
FileWriteAccessMethod::StreamAccess {
file_start_position,
file_data,
} => {
buffer.push(0x0E);
buffer.push(0x09); buffer.extend_from_slice(&file_start_position.to_be_bytes());
buffer.push(0x1E); buffer.extend_from_slice(file_data);
buffer.push(0x1F);
buffer.push(0x0F); }
FileWriteAccessMethod::RecordAccess {
file_start_record,
record_count: _,
file_record_data,
} => {
buffer.push(0x1E);
buffer.push(0x09); buffer.extend_from_slice(&file_start_record.to_be_bytes());
let record_count = file_record_data.len() as u32;
buffer.push(0x19); buffer.extend_from_slice(&record_count.to_be_bytes());
buffer.push(0x2E); for record in file_record_data {
buffer.push(0x65); buffer.push(record.len() as u8);
buffer.extend_from_slice(record);
}
buffer.push(0x2F);
buffer.push(0x1F); }
}
buffer.push(0x1F);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AtomicWriteFileResponse {
pub file_start_position: i32,
}
#[derive(Debug, Clone)]
pub struct TimeSynchronizationRequest {
pub date_time: BacnetDateTime,
}
#[derive(Debug, Clone)]
pub struct UtcTimeSynchronizationRequest {
pub utc_date_time: BacnetDateTime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BacnetDateTime {
pub date: crate::object::Date,
pub time: crate::object::Time,
}
impl BacnetDateTime {
pub fn new(date: crate::object::Date, time: crate::object::Time) -> Self {
Self { date, time }
}
#[cfg(feature = "std")]
pub fn now() -> Self {
use chrono::{Datelike, Local, Timelike};
let now = Local::now();
let year = now.year() as u16;
let month = now.month() as u8;
let day = now.day() as u8;
let weekday = now.weekday().number_from_monday() as u8; let hour = now.hour() as u8;
let minute = now.minute() as u8;
let second = now.second() as u8;
let hundredths = (now.nanosecond() / 10_000_000) as u8;
let date = crate::object::Date {
year,
month,
day,
weekday,
};
let time = crate::object::Time {
hour,
minute,
second,
hundredths,
};
Self::new(date, time)
}
pub fn unspecified() -> Self {
Self {
date: crate::object::Date {
year: 255,
month: 255,
day: 255,
weekday: 255,
},
time: crate::object::Time {
hour: 255,
minute: 255,
second: 255,
hundredths: 255,
},
}
}
pub fn is_unspecified(&self) -> bool {
self.date.year == 255
&& self.date.month == 255
&& self.date.day == 255
&& self.date.weekday == 255
&& self.time.hour == 255
&& self.time.minute == 255
&& self.time.second == 255
&& self.time.hundredths == 255
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
use crate::encoding::{encode_date, encode_time};
encode_date(
buffer,
self.date.year,
self.date.month,
self.date.day,
self.date.weekday,
)?;
encode_time(
buffer,
self.time.hour,
self.time.minute,
self.time.second,
self.time.hundredths,
)?;
Ok(())
}
pub fn decode(data: &[u8]) -> EncodingResult<(Self, usize)> {
use crate::encoding::{decode_date, decode_time};
let ((year, month, day, weekday), consumed_date) = decode_date(data)?;
let ((hour, minute, second, hundredths), consumed_time) =
decode_time(&data[consumed_date..])?;
let datetime = BacnetDateTime {
date: crate::object::Date {
year,
month,
day,
weekday,
},
time: crate::object::Time {
hour,
minute,
second,
hundredths,
},
};
Ok((datetime, consumed_date + consumed_time))
}
}
impl TimeSynchronizationRequest {
pub fn new(date_time: BacnetDateTime) -> Self {
Self { date_time }
}
#[cfg(feature = "std")]
pub fn now() -> Self {
Self::new(BacnetDateTime::now())
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
self.date_time.encode(buffer)
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let (date_time, _consumed) = BacnetDateTime::decode(data)?;
Ok(Self::new(date_time))
}
}
impl UtcTimeSynchronizationRequest {
pub fn new(utc_date_time: BacnetDateTime) -> Self {
Self { utc_date_time }
}
#[cfg(feature = "std")]
pub fn now() -> Self {
use chrono::{Datelike, Timelike, Utc};
let now = Utc::now();
let year = now.year() as u16;
let month = now.month() as u8;
let day = now.day() as u8;
let weekday = now.weekday().number_from_monday() as u8;
let hour = now.hour() as u8;
let minute = now.minute() as u8;
let second = now.second() as u8;
let hundredths = (now.nanosecond() / 10_000_000) as u8;
let date = crate::object::Date {
year,
month,
day,
weekday,
};
let time = crate::object::Time {
hour,
minute,
second,
hundredths,
};
let utc_date_time = BacnetDateTime::new(date, time);
Self::new(utc_date_time)
}
pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
self.utc_date_time.encode(buffer)
}
pub fn decode(data: &[u8]) -> EncodingResult<Self> {
let (utc_date_time, _consumed) = BacnetDateTime::decode(data)?;
Ok(Self::new(utc_date_time))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::{ObjectIdentifier, ObjectType};
#[test]
fn test_whois_request() {
let whois_all = WhoIsRequest::new();
assert!(whois_all.matches(123));
assert!(whois_all.matches(456));
let whois_specific = WhoIsRequest::for_device(123);
assert!(whois_specific.matches(123));
assert!(!whois_specific.matches(124));
let whois_range = WhoIsRequest::for_range(100, 200);
assert!(whois_range.matches(150));
assert!(!whois_range.matches(50));
assert!(!whois_range.matches(250));
}
#[test]
fn test_whois_encoding() {
let mut buffer = Vec::new();
let whois_all = WhoIsRequest::new();
whois_all.encode(&mut buffer).unwrap();
assert_eq!(buffer.len(), 0);
buffer.clear();
let whois_specific = WhoIsRequest::for_device(123);
whois_specific.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
let decoded = WhoIsRequest::decode(&buffer).unwrap();
assert_eq!(decoded, whois_specific);
}
#[test]
fn test_iam_request() {
let device_id = ObjectIdentifier::new(ObjectType::Device, 123);
let iam = IAmRequest::new(device_id, 1476, Segmentation::Both, 999);
assert_eq!(iam.device_identifier.instance, 123);
assert_eq!(iam.max_apdu_length_accepted, 1476);
assert_eq!(iam.vendor_identifier, 999);
}
#[test]
fn test_read_property_request() {
let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
let read_prop = ReadPropertyRequest::new(object_id, PropertyIdentifier::PresentValue);
assert_eq!(read_prop.object_identifier.instance, 1);
assert_eq!(
read_prop.property_identifier,
PropertyIdentifier::PresentValue
);
assert_eq!(read_prop.property_array_index, None);
let read_prop_array =
ReadPropertyRequest::with_array_index(object_id, PropertyIdentifier::PresentValue, 0);
assert_eq!(read_prop_array.property_array_index, Some(0));
}
#[test]
fn test_write_property_request() {
let object_id = ObjectIdentifier::new(ObjectType::AnalogOutput, 1);
let property_value = vec![0x44, 0x42, 0x20, 0x00, 0x00]; let write_prop = WritePropertyRequest::new(object_id, 85, property_value.clone());
assert_eq!(write_prop.object_identifier.instance, 1);
assert_eq!(write_prop.property_identifier, 85);
assert_eq!(write_prop.property_value, property_value);
assert_eq!(write_prop.priority, None);
let write_prop_priority =
WritePropertyRequest::with_priority(object_id, 85, property_value.clone(), 8);
assert_eq!(write_prop_priority.priority, Some(8));
let mut buffer = Vec::new();
write_prop.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
let decoded = WritePropertyRequest::decode(&buffer).unwrap();
assert_eq!(decoded.object_identifier.instance, 1);
assert_eq!(decoded.property_identifier, 85);
assert_eq!(decoded.property_value, property_value);
}
#[test]
fn test_read_property_multiple_request() {
let object_id1 = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
let object_id2 = ObjectIdentifier::new(ObjectType::BinaryInput, 2);
let prop_ref1 = PropertyReference::new(PropertyIdentifier::PresentValue); let prop_ref2 = PropertyReference::new(PropertyIdentifier::ObjectName); let prop_ref3 = PropertyReference::with_array_index(PropertyIdentifier::PriorityArray, 8);
let spec1 = ReadAccessSpecification::new(object_id1, vec![prop_ref1, prop_ref2]);
let spec2 = ReadAccessSpecification::new(object_id2, vec![prop_ref3]);
let rpm_request = ReadPropertyMultipleRequest::new(vec![spec1, spec2]);
assert_eq!(rpm_request.read_access_specifications.len(), 2);
assert_eq!(
rpm_request.read_access_specifications[0]
.property_references
.len(),
2
);
assert_eq!(
rpm_request.read_access_specifications[1]
.property_references
.len(),
1
);
assert_eq!(
rpm_request.read_access_specifications[1].property_references[0].property_array_index,
Some(8)
);
}
#[test]
fn test_subscribe_cov_request() {
let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
let cov_req = SubscribeCovRequest::new(123, object_id);
assert_eq!(cov_req.subscriber_process_identifier, 123);
assert_eq!(cov_req.monitored_object_identifier.instance, 1);
assert_eq!(cov_req.issue_confirmed_notifications, None);
assert_eq!(cov_req.lifetime, None);
let cov_confirmed = SubscribeCovRequest::with_confirmation(123, object_id, true);
assert_eq!(cov_confirmed.issue_confirmed_notifications, Some(true));
let cov_lifetime = SubscribeCovRequest::with_lifetime(123, object_id, 3600);
assert_eq!(cov_lifetime.lifetime, Some(3600));
let mut buffer = Vec::new();
cov_req.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
}
#[test]
fn test_cov_subscription_manager() {
let mut manager = CovSubscriptionManager::new();
let device_id = ObjectIdentifier::new(ObjectType::Device, 1);
let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
let subscription = CovSubscription::new(123, device_id, object_id, 3600);
manager.add_subscription(subscription);
assert_eq!(manager.active_count(), 1);
let subscriptions = manager.get_subscriptions_for_object(object_id);
assert_eq!(subscriptions.len(), 1);
assert_eq!(subscriptions[0].subscriber_process_identifier, 123);
manager.update_timers(1800); let subscriptions = manager.get_subscriptions_for_object(object_id);
assert_eq!(subscriptions[0].time_remaining, 1800);
manager.update_timers(1800); assert_eq!(manager.active_count(), 0);
manager.cleanup_expired();
assert_eq!(manager.subscriptions.len(), 0);
}
#[test]
fn test_cov_notification_request() {
let device_id = ObjectIdentifier::new(ObjectType::Device, 1);
let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
let values = vec![
crate::object::PropertyValue::Real(25.5), crate::object::PropertyValue::Boolean(false), ];
let notification = CovNotificationRequest::new(123, device_id, object_id, 3600, values);
assert_eq!(notification.subscriber_process_identifier, 123);
assert_eq!(notification.initiating_device_identifier, device_id);
assert_eq!(notification.monitored_object_identifier, object_id);
assert_eq!(notification.time_remaining, 3600);
assert_eq!(notification.list_of_values.len(), 2);
let mut buffer = Vec::new();
notification.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
}
#[test]
fn test_atomic_read_file_request() {
let file_id = ObjectIdentifier::new(ObjectType::File, 1);
let read_stream = AtomicReadFileRequest::new_stream_access(file_id, 0, 1024);
match &read_stream.access_method {
FileAccessMethod::StreamAccess {
file_start_position,
requested_octet_count,
} => {
assert_eq!(*file_start_position, 0);
assert_eq!(*requested_octet_count, 1024);
}
_ => panic!("Expected StreamAccess"),
}
let read_record = AtomicReadFileRequest::new_record_access(file_id, 5, 10);
match &read_record.access_method {
FileAccessMethod::RecordAccess {
file_start_record,
requested_record_count,
} => {
assert_eq!(*file_start_record, 5);
assert_eq!(*requested_record_count, 10);
}
_ => panic!("Expected RecordAccess"),
}
let mut buffer = Vec::new();
read_stream.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
}
#[test]
fn test_atomic_read_file_response() {
let data = vec![1, 2, 3, 4, 5];
let response_stream = AtomicReadFileResponse::new_stream_access(false, 0, data.clone());
assert!(!response_stream.end_of_file);
match &response_stream.access_method_result {
FileAccessMethodResult::StreamAccess {
file_start_position,
file_data,
} => {
assert_eq!(*file_start_position, 0);
assert_eq!(*file_data, data);
}
_ => panic!("Expected StreamAccess result"),
}
let records = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
let response_record = AtomicReadFileResponse::new_record_access(true, 10, records.clone());
assert!(response_record.end_of_file);
match &response_record.access_method_result {
FileAccessMethodResult::RecordAccess {
file_start_record,
record_count,
file_record_data,
} => {
assert_eq!(*file_start_record, 10);
assert_eq!(*record_count, 3);
assert_eq!(*file_record_data, records);
}
_ => panic!("Expected RecordAccess result"),
}
}
#[test]
fn test_atomic_write_file_request() {
let file_id = ObjectIdentifier::new(ObjectType::File, 1);
let data = vec![65, 66, 67, 68]; let write_stream = AtomicWriteFileRequest::new_stream_access(file_id, 100, data.clone());
match &write_stream.access_method {
FileWriteAccessMethod::StreamAccess {
file_start_position,
file_data,
} => {
assert_eq!(*file_start_position, 100);
assert_eq!(*file_data, data);
}
_ => panic!("Expected StreamAccess"),
}
let records = vec![
b"Record 1".to_vec(),
b"Record 2".to_vec(),
b"Record 3".to_vec(),
];
let write_record = AtomicWriteFileRequest::new_record_access(file_id, 5, records.clone());
match &write_record.access_method {
FileWriteAccessMethod::RecordAccess {
file_start_record,
record_count,
file_record_data,
} => {
assert_eq!(*file_start_record, 5);
assert_eq!(*record_count, 3);
assert_eq!(*file_record_data, records);
}
_ => panic!("Expected RecordAccess"),
}
let mut buffer = Vec::new();
write_stream.encode(&mut buffer).unwrap();
assert!(!buffer.is_empty());
}
#[test]
fn test_atomic_write_file_response() {
let response = AtomicWriteFileResponse {
file_start_position: 150,
};
assert_eq!(response.file_start_position, 150);
}
#[test]
fn test_bacnet_datetime() {
let date = crate::object::Date {
year: 2024,
month: 3,
day: 15,
weekday: 5,
};
let time = crate::object::Time {
hour: 14,
minute: 30,
second: 45,
hundredths: 50,
};
let datetime = BacnetDateTime::new(date, time);
assert_eq!(datetime.date.year, 2024);
assert_eq!(datetime.date.month, 3);
assert_eq!(datetime.date.day, 15);
assert_eq!(datetime.date.weekday, 5); assert_eq!(datetime.time.hour, 14);
assert_eq!(datetime.time.minute, 30);
assert_eq!(datetime.time.second, 45);
assert_eq!(datetime.time.hundredths, 50);
let unspecified = BacnetDateTime::unspecified();
assert!(unspecified.is_unspecified());
assert_eq!(unspecified.date.year, 255);
assert_eq!(unspecified.time.hour, 255);
let mut buffer = Vec::new();
datetime.encode(&mut buffer).unwrap();
assert_eq!(buffer.len(), 10);
let (decoded, consumed) = BacnetDateTime::decode(&buffer).unwrap();
assert_eq!(consumed, 10);
assert_eq!(decoded, datetime);
}
#[test]
fn test_time_synchronization_request() {
let date = crate::object::Date {
year: 2024,
month: 6,
day: 20,
weekday: 4,
};
let time = crate::object::Time {
hour: 10,
minute: 15,
second: 30,
hundredths: 25,
};
let datetime = BacnetDateTime::new(date, time);
let time_sync = TimeSynchronizationRequest::new(datetime);
assert_eq!(time_sync.date_time, datetime);
let mut buffer = Vec::new();
time_sync.encode(&mut buffer).unwrap();
assert_eq!(buffer.len(), 10);
let decoded = TimeSynchronizationRequest::decode(&buffer).unwrap();
assert_eq!(decoded.date_time, datetime);
}
#[test]
fn test_utc_time_synchronization_request() {
let date = crate::object::Date {
year: 2024,
month: 6,
day: 20,
weekday: 4,
};
let time = crate::object::Time {
hour: 18,
minute: 45,
second: 15,
hundredths: 75,
};
let utc_datetime = BacnetDateTime::new(date, time);
let utc_sync = UtcTimeSynchronizationRequest::new(utc_datetime);
assert_eq!(utc_sync.utc_date_time, utc_datetime);
let mut buffer = Vec::new();
utc_sync.encode(&mut buffer).unwrap();
assert_eq!(buffer.len(), 10);
let decoded = UtcTimeSynchronizationRequest::decode(&buffer).unwrap();
assert_eq!(decoded.utc_date_time, utc_datetime);
}
#[cfg(feature = "std")]
#[test]
fn test_time_synchronization_now() {
let now_sync = TimeSynchronizationRequest::now();
assert!(!now_sync.date_time.is_unspecified());
let utc_now_sync = UtcTimeSynchronizationRequest::now();
assert!(!utc_now_sync.utc_date_time.is_unspecified());
assert!(now_sync.date_time.date.year >= 2024);
assert!(now_sync.date_time.date.month >= 1 && now_sync.date_time.date.month <= 12);
assert!(now_sync.date_time.date.day >= 1 && now_sync.date_time.date.day <= 31);
assert!(now_sync.date_time.time.hour <= 23);
assert!(now_sync.date_time.time.minute <= 59);
assert!(now_sync.date_time.time.second <= 59);
assert!(now_sync.date_time.time.hundredths <= 99);
}
#[test]
fn test_read_property_multiple_response() {
let data = [
0xc, 0x0, 0x80, 0x75, 0x3b, 0x1e, 0x29, 0x4b, 0x4e, 0xc4, 0x0, 0x80, 0x75, 0x3b, 0x4f,
0x29, 0x4d, 0x4e, 0x75, 0xc, 0x0, 0x48, 0x53, 0x31, 0x43, 0x75, 0x72, 0x76, 0x65, 0x5f,
0x59, 0x33, 0x4f, 0x29, 0x4f, 0x4e, 0x91, 0x2, 0x4f, 0x29, 0x55, 0x4e, 0x44, 0x42,
0x6c, 0x0, 0x0, 0x4f, 0x29, 0x6f, 0x4e, 0x82, 0x4, 0x0, 0x4f, 0x29, 0x24, 0x4e, 0x91,
0x0, 0x4f, 0x29, 0x51, 0x4e, 0x10, 0x4f, 0x29, 0x75, 0x4e, 0x91, 0x3e, 0x4f, 0x29,
0x1c, 0x4e, 0x75, 0x43, 0x0, 0x53, 0x65, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20,
0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0x20, 0x63, 0x75, 0x72, 0x76,
0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6f, 0x75, 0x74,
0x64, 0x6f, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x6e, 0x73, 0x61, 0x74,
0x65, 0x64, 0x20, 0x73, 0x65, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x48, 0x53,
0x31, 0x4f, 0x29, 0x67, 0x4e, 0x91, 0x0, 0x4f, 0x1f,
];
let response = ReadPropertyMultipleResponse::decode(&data).unwrap();
assert_eq!(response.read_access_results.len(), 1);
let result = &response.read_access_results[0];
assert_eq!(
result.object_identifier,
ObjectIdentifier::new(ObjectType::AnalogValue, 30011)
);
assert_eq!(result.results.len(), 10);
assert_eq!(
result.results[0].property_identifier,
PropertyIdentifier::ObjectIdentifier
);
assert_eq!(
result.results[0].value,
PropertyResultValue::Value(vec![property::PropertyValue::ObjectIdentifier(
ObjectIdentifier::new(ObjectType::AnalogValue, 30011)
)])
);
assert_eq!(
result.results[1].property_identifier,
PropertyIdentifier::ObjectName
);
assert_eq!(
result.results[1].value,
PropertyResultValue::Value(vec![property::PropertyValue::CharacterString(
"HS1Curve_Y3".to_string()
)])
);
assert_eq!(
result.results[2].property_identifier,
PropertyIdentifier::ObjectType
);
assert_eq!(
result.results[2].value,
PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(
ObjectType::AnalogValue.into()
)])
);
assert_eq!(
result.results[3].property_identifier,
PropertyIdentifier::PresentValue
);
assert_eq!(
result.results[3].value,
PropertyResultValue::Value(vec![property::PropertyValue::Real(59.0)])
);
assert_eq!(
result.results[4].property_identifier,
PropertyIdentifier::StatusFlags
);
assert_eq!(
result.results[4].value,
PropertyResultValue::Value(vec![property::PropertyValue::BitString(vec![
false, false, false, false
])])
);
assert_eq!(
result.results[5].property_identifier,
PropertyIdentifier::EventState
);
assert_eq!(
result.results[5].value,
PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(0)])
);
assert_eq!(
result.results[6].property_identifier,
PropertyIdentifier::OutOfService
);
assert_eq!(
result.results[6].value,
PropertyResultValue::Value(vec![property::PropertyValue::Boolean(false)])
);
assert_eq!(
result.results[7].property_identifier,
PropertyIdentifier::Units
);
assert_eq!(
result.results[7].value,
PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(62)])
);
assert_eq!(
result.results[8].property_identifier,
PropertyIdentifier::Description
);
assert_eq!(
result.results[8].value,
PropertyResultValue::Value(vec![property::PropertyValue::CharacterString(
"Setpoint for third curvepoint for outdoor compensated setpoint HS1".to_string()
)])
);
assert_eq!(
result.results[9].property_identifier,
PropertyIdentifier::Reliability
);
assert_eq!(
result.results[9].value,
PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(0)])
);
}
#[test]
fn test_read_property_response() {
let data = [
0xc, 0x2, 0x0, 0xa, 0x50, 0x19, 0x4d, 0x3e, 0x75, 0xf, 0x0, 0x43, 0x6f, 0x72, 0x72,
0x69, 0x67, 0x6f, 0x48, 0x65, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x3f,
];
let response = ReadPropertyResponse::decode(&data).unwrap();
assert_eq!(
response.object_identifier,
ObjectIdentifier::new(ObjectType::Device, 2640)
);
assert_eq!(response.property_identifier, PropertyIdentifier::ObjectName);
assert_eq!(response.property_array_index, None);
assert_eq!(response.property_values.len(), 1);
assert_eq!(
response.property_values[0],
property::PropertyValue::CharacterString("CorrigoHeating".to_string())
);
let mut encoded = Vec::new();
response.encode(&mut encoded).unwrap();
assert_eq!(encoded.len(), data.len());
assert_eq!(encoded, data);
}
}