use std::ops::Range;
use super::group_present_value::GroupMembers;
use super::*;
use bacnet_objects::log_buffer::{LogRecordIdentity, TimestampKey};
use bacnet_objects::traits::BACnetObject;
use bacnet_services::read_range::{RangeSpec, ReadRangeAck, ReadRangeRequest};
use bacnet_types::primitives::{Date, Time};
#[path = "read_range_logs.rs"]
mod logs;
pub(crate) use logs::{LogIdentities, RangeItems};
#[path = "read_range_items.rs"]
mod items;
#[path = "read_range_page.rs"]
mod page;
pub(crate) use page::ReadRangeFailure;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct SignedRangeSelection {
pub(super) range: Range<usize>,
pub(super) result_flags: (bool, bool, bool),
}
impl SignedRangeSelection {
fn empty() -> Self {
Self {
range: 0..0,
result_flags: (false, false, false),
}
}
fn from_range(total: usize, range: Range<usize>) -> Self {
if range.is_empty() {
return Self::empty();
}
Self {
result_flags: (range.start == 0, range.end == total, false),
range,
}
}
}
pub(super) fn select_signed_range(
total: usize,
reference: Option<usize>,
count: i32,
) -> SignedRangeSelection {
let Some(reference) = reference.filter(|reference| *reference < total) else {
return SignedRangeSelection::empty();
};
if count == 0 {
return SignedRangeSelection::empty();
}
let range = if count > 0 {
let count = usize::try_from(count).unwrap_or(usize::MAX);
reference..reference.saturating_add(count).min(total)
} else {
let count = usize::try_from(count.unsigned_abs()).unwrap_or(usize::MAX);
let end = reference + 1;
end.saturating_sub(count)..end
};
SignedRangeSelection::from_range(total, range)
}
fn list_item_not_numbered() -> Error {
Error::protocol(ErrorClass::PROPERTY, ErrorCode::LIST_ITEM_NOT_NUMBERED)
}
fn list_item_not_timestamped() -> Error {
Error::protocol(ErrorClass::PROPERTY, ErrorCode::LIST_ITEM_NOT_TIMESTAMPED)
}
pub(super) fn select_time_range(
item_count: usize,
identities: Option<&LogIdentities<'_>>,
reference_time: (Date, Time),
count: i32,
) -> Result<(SignedRangeSelection, Option<u64>), Error> {
let identities = identities.ok_or_else(list_item_not_timestamped)?;
if identities.len() != item_count {
return Err(list_item_not_timestamped());
}
let reference = TimestampKey::of(reference_time.0, reference_time.1)
.ok_or_else(list_item_not_timestamped)?;
let anchor = identities.time_anchor(reference, count)?;
let selection = select_signed_range(item_count, anchor, count);
let first_sequence_number =
(!selection.range.is_empty()).then(|| identities.sequence_number(selection.range.start));
Ok((selection, first_sequence_number))
}
pub(super) fn append_read_range_ack_with<F>(
request: &ReadRangeRequest,
items: &RangeItems<'_>,
selection: &SignedRangeSelection,
first_sequence_number: Option<u64>,
response: &mut BytesMut,
mut encode_item: F,
) -> Result<(), Error>
where
F: FnMut(&mut BytesMut, &PropertyValue) -> Result<(), Error>,
{
let mut item_data = BytesMut::new();
for index in selection.range.clone() {
items.encode_with(index, &mut item_data, &mut encode_item)?;
}
let ack = ReadRangeAck {
object_identifier: request.object_identifier,
property_identifier: request.property_identifier,
property_array_index: request.property_array_index,
result_flags: selection.result_flags,
item_count: selection.range.len() as u32,
item_data: item_data.to_vec(),
first_sequence_number,
};
let mut encoded_ack = BytesMut::new();
ack.encode(&mut encoded_ack);
response.extend_from_slice(&encoded_ack);
Ok(())
}
pub fn handle_read_range(
db: &ObjectDatabase,
service_data: &[u8],
response: &mut BytesMut,
) -> Result<(), Error> {
let unlimited = |failure| match failure {
ReadRangeFailure::Service(error) => error,
ReadRangeFailure::Bytes | ReadRangeFailure::Work => {
unreachable!("a read with no limit ran past one")
}
};
let request = ReadRangeRequest::decode(service_data).map_err(Error::into_request_reject)?;
let plan = plan_read_range(db, None, &request).map_err(unlimited)?;
let selected = prepare_read_range(db, None, request, plan.into_members()).map_err(unlimited)?;
append_read_range_ack_with(
&selected.request,
&selected.items,
&selected.selection,
selected.first_sequence_number,
response,
encode_property_value,
)
}
#[cfg(test)]
pub(crate) fn handle_read_range_budgeted(
db: &ObjectDatabase,
service_data: &[u8],
response: &mut BytesMut,
budget: crate::server::ReadRangeBudget,
) -> Result<(), ReadRangeFailure> {
let request = ReadRangeRequest::decode(service_data)
.map_err(|error| ReadRangeFailure::Service(error.into_request_reject()))?;
let plan = plan_read_range(db, None, &request)?;
read_range_request_observed(db, None, request, plan, response, budget, |_, _, _, _| {})
}
pub(crate) fn plan_read_range(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
request: &ReadRangeRequest,
) -> Result<PropertyPlan, ReadRangeFailure> {
Ok(PropertyPlan::new(
db,
view,
request.object_identifier,
db.get(&request.object_identifier),
request.property_identifier,
request.property_array_index,
)?)
}
pub(crate) fn read_range_request_observed(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
request: ReadRangeRequest,
plan: PropertyPlan,
response: &mut BytesMut,
budget: crate::server::ReadRangeBudget,
completed: impl FnOnce(ObjectIdentifier, PropertyIdentifier, Option<u32>, &Result<(), Error>),
) -> Result<(), ReadRangeFailure> {
let target = request.object_identifier;
let property = request.property_identifier;
let index = request.property_array_index;
let result = prepare_read_range(db, view, request, plan.into_members()).and_then(|selected| {
page::append_page_with(&selected, response, budget, encode_property_value)
});
let result = match result {
Ok(()) => Ok(()),
Err(ReadRangeFailure::Service(error)) => Err(error),
Err(failure) => return Err(failure),
};
completed(target, property, index, &result);
result.map_err(ReadRangeFailure::Service)
}
struct PreparedReadRange<'a> {
request: ReadRangeRequest,
items: RangeItems<'a>,
selection: SignedRangeSelection,
first_sequence_number: Option<u64>,
identities: Option<LogIdentities<'a>>,
}
fn read_range_items(
db: &ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
object: &dyn BACnetObject,
request: &ReadRangeRequest,
members: Option<GroupMembers>,
) -> Result<Vec<PropertyValue>, ReadRangeFailure> {
let property = request.property_identifier;
let index = request.property_array_index;
if index.is_some() && !object.is_array_property(property) {
object.read_property(property, None)?;
return Err(ReadRangeFailure::Service(Error::protocol(
ErrorClass::PROPERTY,
ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
)));
}
let value =
group_present_value::read_served_property(db, view, object, property, index, members)?;
if index.is_some() || !object.is_list_property(property) {
return Err(ReadRangeFailure::Service(Error::protocol(
ErrorClass::SERVICES,
ErrorCode::PROPERTY_IS_NOT_A_LIST,
)));
}
Ok(items::list_items(
request.object_identifier.object_type(),
property,
value,
)?)
}
fn prepare_read_range<'a>(
db: &'a ObjectDatabase,
view: Option<&DeviceReadContext<'_>>,
request: ReadRangeRequest,
members: Option<GroupMembers>,
) -> Result<PreparedReadRange<'a>, ReadRangeFailure> {
let stored = db.get(&request.object_identifier).ok_or(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::UNKNOWN_OBJECT,
))?;
let served = view.map(|view| view.object(stored));
let object: &dyn BACnetObject = served.as_ref().map_or(stored, |served| served);
let (items, mut buffer_identities) = match logs::log_buffer(stored, &request)? {
Some((items, identities)) => (items, Some(identities)),
None => (
RangeItems::Values(read_range_items(db, view, object, &request, members)?),
None,
),
};
let mut log_identities = || {
buffer_identities.take().or_else(|| {
object
.log_record_identities_internal()
.map(LogIdentities::Listed)
})
};
let mut resident_identities = None;
let (selection, first_sequence_number) = match &request.range {
None => (
SignedRangeSelection::from_range(items.len(), 0..items.len()),
None,
),
Some(RangeSpec::ByPosition {
reference_index,
count,
}) => {
let reference = reference_index
.checked_sub(1)
.and_then(|reference| usize::try_from(reference).ok());
(select_signed_range(items.len(), reference, *count), None)
}
Some(RangeSpec::BySequenceNumber {
reference_seq,
count,
}) => {
if request.property_identifier != PropertyIdentifier::LOG_BUFFER {
return Err(list_item_not_numbered().into());
}
let identities = log_identities().ok_or_else(list_item_not_numbered)?;
if identities.len() != items.len() {
return Err(list_item_not_numbered().into());
}
let reference = identities.position(*reference_seq)?;
let selection = select_signed_range(items.len(), reference, *count);
let first_sequence_number = (!selection.range.is_empty())
.then(|| identities.sequence_number(selection.range.start));
resident_identities = Some(identities);
(selection, first_sequence_number)
}
Some(RangeSpec::ByTime {
reference_time,
count,
}) => {
if request.property_identifier != PropertyIdentifier::LOG_BUFFER {
return Err(list_item_not_timestamped().into());
}
resident_identities = log_identities();
select_time_range(
items.len(),
resident_identities.as_ref(),
*reference_time,
*count,
)?
}
};
Ok(PreparedReadRange {
request,
items,
selection,
first_sequence_number,
identities: resident_identities,
})
}