#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bacnet_encoding::{primitives, tags};
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::{DecodeError, DecodingKind, Error};
use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
use bytes::BytesMut;
use bacnet_encoding::constructed::tagged::{
decode_app_primitive, decode_app_unsigned, decode_ctx_object_id, decode_ctx_primitive,
decode_ctx_unsigned, expect_end, misplaced_tag, next_is_context,
};
fn decode_count(data: &[u8], offset: usize, field: &'static str) -> Result<(i32, usize), Error> {
let (content, end) = decode_app_primitive(data, offset, tags::app_tag::SIGNED, field)?;
let value = primitives::decode_signed(content)?;
let value = i16::try_from(value).map_err(|_| {
DecodeError::new(DecodingKind::OutOfRange, offset, "exceeds INTEGER16").within(field)
})?;
if value == 0 {
return Err(
DecodeError::new(DecodingKind::OutOfRange, offset, "may not be zero")
.within(field)
.into(),
);
}
Ok((i32::from(value), end))
}
fn specific_datetime(date: &Date, time: &Time) -> bool {
date.year != Date::UNSPECIFIED
&& (1..=12).contains(&date.month)
&& (1..=31).contains(&date.day)
&& (1..=7).contains(&date.day_of_week)
&& time.hour <= 23
&& time.minute <= 59
&& time.second <= 59
&& time.hundredths <= 99
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadRangeRequest {
pub object_identifier: ObjectIdentifier,
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
pub range: Option<RangeSpec>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RangeSpec {
ByPosition {
reference_index: u64,
count: i32,
},
BySequenceNumber {
reference_seq: u64,
count: i32,
},
ByTime {
reference_time: (Date, Time),
count: i32,
},
}
impl ReadRangeRequest {
pub fn validate(&self) -> Result<(), Error> {
if matches!(
self.property_identifier,
PropertyIdentifier::ALL | PropertyIdentifier::REQUIRED | PropertyIdentifier::OPTIONAL
) {
return Err(Error::encode("ReadRange property may not be ALL, REQUIRED, or OPTIONAL"));
}
if self.property_array_index == Some(0) {
return Err(Error::encode("ReadRange array index may not be zero"));
}
if let Some(range) = &self.range {
let count = match range {
RangeSpec::ByPosition { count, .. } | RangeSpec::BySequenceNumber { count, .. } => {
*count
}
RangeSpec::ByTime {
reference_time: (date, time),
count,
} => {
if !specific_datetime(date, time) {
return Err(Error::encode("ReadRange byTime requires a specific datetime"));
}
*count
}
};
if count == 0 || i16::try_from(count).is_err() {
return Err(Error::encode("ReadRange count must be a nonzero INTEGER16"));
}
}
Ok(())
}
pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
self.validate()?;
primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
if let Some(idx) = self.property_array_index {
primitives::encode_ctx_unsigned(buf, 2, idx as u64);
}
if let Some(ref range) = self.range {
match range {
RangeSpec::ByPosition {
reference_index,
count,
} => {
tags::encode_opening_tag(buf, 3);
primitives::encode_app_unsigned(buf, *reference_index);
primitives::encode_app_signed(buf, *count);
tags::encode_closing_tag(buf, 3);
}
RangeSpec::BySequenceNumber {
reference_seq,
count,
} => {
tags::encode_opening_tag(buf, 6);
primitives::encode_app_unsigned(buf, *reference_seq);
primitives::encode_app_signed(buf, *count);
tags::encode_closing_tag(buf, 6);
}
RangeSpec::ByTime {
reference_time,
count,
} => {
tags::encode_opening_tag(buf, 7);
primitives::encode_app_date(buf, &reference_time.0);
primitives::encode_app_time(buf, &reference_time.1);
primitives::encode_app_signed(buf, *count);
tags::encode_closing_tag(buf, 7);
}
}
}
Ok(())
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let (object_identifier, end) =
decode_ctx_object_id(data, offset, 0, "ReadRange request object-id")?;
offset = end;
let (property_identifier, end) =
decode_ctx_unsigned::<u32>(data, offset, 1, "ReadRange request property-id")?;
let property_identifier = PropertyIdentifier::from_raw(property_identifier);
if matches!(
property_identifier,
PropertyIdentifier::ALL | PropertyIdentifier::REQUIRED | PropertyIdentifier::OPTIONAL
) {
return Err(Error::decoding(
offset,
"ReadRange request property-id may not be ALL, REQUIRED, or OPTIONAL",
));
}
offset = end;
let mut property_array_index = None;
if next_is_context(data, offset, 2)? {
let (index, end) =
decode_ctx_unsigned::<u32>(data, offset, 2, "ReadRange request array-index")?;
if index == 0 {
return Err(Error::out_of_range(
offset,
"ReadRange request array-index may not be zero",
));
}
property_array_index = Some(index);
offset = end;
}
let mut range = None;
if offset < data.len() {
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if tag.is_opening_tag(3) {
let (content, new_offset) = tags::extract_context_value(data, tag_end, 3)?;
let (reference_index, inner_offset) =
decode_app_unsigned::<u64>(content, 0, "ReadRange byPosition reference-index")?;
let (count, inner_offset) =
decode_count(content, inner_offset, "ReadRange byPosition count")?;
expect_end(content, inner_offset, inner_offset, "ReadRange byPosition")?;
range = Some(RangeSpec::ByPosition {
reference_index,
count,
});
offset = new_offset;
} else if tag.is_opening_tag(6) {
let (content, new_offset) = tags::extract_context_value(data, tag_end, 6)?;
let (reference_seq, inner_offset) = decode_app_unsigned::<u64>(
content,
0,
"ReadRange bySequenceNumber reference-seq",
)?;
let (count, inner_offset) =
decode_count(content, inner_offset, "ReadRange bySequenceNumber count")?;
expect_end(
content,
inner_offset,
inner_offset,
"ReadRange bySequenceNumber",
)?;
range = Some(RangeSpec::BySequenceNumber {
reference_seq,
count,
});
offset = new_offset;
} else if tag.is_opening_tag(7) {
let (content, new_offset) = tags::extract_context_value(data, tag_end, 7)?;
let (date, inner_offset) =
decode_app_primitive(content, 0, tags::app_tag::DATE, "ReadRange byTime date")?;
let date = Date::decode(date)?;
let (time, inner_offset) = decode_app_primitive(
content,
inner_offset,
tags::app_tag::TIME,
"ReadRange byTime time",
)?;
let time = Time::decode(time)?;
if !specific_datetime(&date, &time) {
return Err(Error::decoding(
inner_offset,
"ReadRange byTime requires a specific datetime",
));
}
let (count, inner_offset) =
decode_count(content, inner_offset, "ReadRange byTime count")?;
expect_end(content, inner_offset, inner_offset, "ReadRange byTime")?;
range = Some(RangeSpec::ByTime {
reference_time: (date, time),
count,
});
offset = new_offset;
} else {
return Err(misplaced_tag(
data,
&tag,
None,
offset,
"ReadRange request has invalid range choice",
));
}
}
expect_end(data, offset, offset, "ReadRange request")?;
Ok(Self {
object_identifier,
property_identifier,
property_array_index,
range,
})
}
}
#[derive(Debug, Clone)]
pub struct ReadRangeAck {
pub object_identifier: ObjectIdentifier,
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
pub result_flags: (bool, bool, bool),
pub item_count: u32,
pub item_data: Vec<u8>,
pub first_sequence_number: Option<u64>,
}
impl ReadRangeAck {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
if let Some(idx) = self.property_array_index {
primitives::encode_ctx_unsigned(buf, 2, idx as u64);
}
let mut flags: u8 = 0;
if self.result_flags.0 {
flags |= 0x80;
}
if self.result_flags.1 {
flags |= 0x40;
}
if self.result_flags.2 {
flags |= 0x20;
}
primitives::encode_ctx_bit_string(buf, 3, 5, &[flags]);
primitives::encode_ctx_unsigned(buf, 4, self.item_count as u64);
tags::encode_opening_tag(buf, 5);
buf.extend_from_slice(&self.item_data);
tags::encode_closing_tag(buf, 5);
if let Some(seq) = self.first_sequence_number {
primitives::encode_ctx_unsigned(buf, 6, seq);
}
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let (object_identifier, end) =
decode_ctx_object_id(data, offset, 0, "ReadRange ACK object-id")?;
offset = end;
let (property_identifier, end) =
decode_ctx_unsigned::<u32>(data, offset, 1, "ReadRange ACK property-id")?;
let property_identifier = PropertyIdentifier::from_raw(property_identifier);
offset = end;
let mut property_array_index = None;
if next_is_context(data, offset, 2)? {
let (index, end) =
decode_ctx_unsigned::<u32>(data, offset, 2, "ReadRange ACK array-index")?;
property_array_index = Some(index);
offset = end;
}
let (content, end) = decode_ctx_primitive(data, offset, 3, "ReadRange ACK result-flags")?;
let (unused_bits, bits) = primitives::decode_bit_string(content)?;
if unused_bits != 5 || bits.len() != 1 || bits[0] & 0x1f != 0 {
return Err(Error::decoding(
offset,
"ReadRange ACK result-flags must contain three bits with zero padding",
));
}
let b = bits[0];
let result_flags = (b & 0x80 != 0, b & 0x40 != 0, b & 0x20 != 0);
offset = end;
let (item_count, end) =
decode_ctx_unsigned::<u32>(data, offset, 4, "ReadRange ACK item-count")?;
offset = end;
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if !tag.is_opening_tag(5) {
return Err(misplaced_tag(
data,
&tag,
Some(5),
offset,
"ReadRange ACK item-data expected opening tag 5",
));
}
let (content, new_offset) = tags::extract_context_value(data, tag_end, 5)?;
if (item_count == 0) != content.is_empty() {
return Err(Error::decoding(
offset,
"ReadRange ACK item-count contradicts empty item-data",
));
}
let item_data = content.to_vec();
offset = new_offset;
let mut first_sequence_number = None;
if offset < data.len() {
let (tag, _) = tags::decode_tag(data, offset)?;
if !tag.is_context(6) {
return Err(misplaced_tag(
data,
&tag,
Some(6),
offset,
"ReadRange ACK expected context tag 6 for first-sequence-number",
));
}
if item_count == 0 {
return Err(Error::decoding(
offset,
"ReadRange ACK first-sequence-number requires a nonzero item-count",
));
}
let (sequence_number, end) =
decode_ctx_unsigned::<u64>(data, offset, 6, "ReadRange ACK first-sequence-number")?;
first_sequence_number = Some(sequence_number);
offset = end;
}
expect_end(data, offset, offset, "ReadRange ACK")?;
Ok(Self {
object_identifier,
property_identifier,
property_array_index,
result_flags,
item_count,
item_data,
first_sequence_number,
})
}
}
#[path = "read_range_records.rs"]
mod records;
pub use records::{LogBufferRecord, LogRecords, LogRecordsError};
#[path = "read_range_checks.rs"]
mod checks;
pub use bacnet_types::error::ReadRangeViolation;
pub use checks::{ReadRangeReply, ReadRangeValidation};
#[cfg(test)]
#[path = "read_range_width_tests.rs"]
mod width_tests;
#[cfg(test)]
#[path = "read_range_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "read_range_validation_tests.rs"]
mod validation_tests;