use bacnet_encoding::constructed::tagged::{
decode_app_enumerated, decode_app_object_id, decode_app_unsigned, decode_ctx_unsigned,
decode_optional_ctx, expect_end,
};
use bacnet_encoding::primitives;
use bacnet_encoding::sink::{EncodeSink, SinkFull};
use bacnet_types::enums::Segmentation;
use bacnet_types::error::{DecodeError, DecodingKind, Error, RangeError};
use bacnet_types::primitives::ObjectIdentifier;
#[cfg(feature = "alloc")]
use bytes::BytesMut;
pub(crate) fn staged<S: EncodeSink + ?Sized>(
sink: &mut S,
staging: &mut [u8],
encode: impl FnOnce(&mut &mut [u8]) -> Result<usize, SinkFull>,
) -> Result<usize, SinkFull> {
let mut tail = &mut *staging;
let written = encode(&mut tail).expect("the staging buffer holds the largest value");
sink.write(&staging[..written])?;
Ok(written)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DeviceInstanceRange {
low: u32,
high: u32,
}
impl DeviceInstanceRange {
pub fn new(low: u32, high: u32) -> Result<Self, Error> {
let range = Self::decoded(low, high)?;
if high > ObjectIdentifier::MAX_INSTANCE {
return Err(RangeError::new(
"device instance range high limit {} is above the highest instance, 4194303",
)
.with_value(high.into())
.into());
}
Ok(range)
}
fn decoded(low: u32, high: u32) -> Result<Self, Error> {
if low > high {
return Err(RangeError::new(
"device instance range low limit {} is above its high limit {}, \
so it would match no device",
)
.with_values(low.into(), high.into())
.into());
}
Ok(Self { low, high })
}
pub fn single(instance: u32) -> Result<Self, Error> {
Self::new(instance, instance)
}
pub fn device(device: ObjectIdentifier) -> Self {
let instance = device.instance_number();
Self {
low: instance,
high: instance,
}
}
pub fn from_limits(low: Option<u32>, high: Option<u32>) -> Result<Option<Self>, Error> {
let alone = |label, limit: u32| Err(RangeError::new(label).with_value(limit.into()).into());
match (low, high) {
(None, None) => Ok(None),
(Some(low), Some(high)) => Self::new(low, high).map(Some),
(Some(low), None) => alone(
"a device instance range needs both limits or neither: \
low limit {} was given without a high limit",
low,
),
(None, Some(high)) => alone(
"a device instance range needs both limits or neither: \
high limit {} was given without a low limit",
high,
),
}
}
pub const fn low(self) -> u32 {
self.low
}
pub const fn high(self) -> u32 {
self.high
}
pub const fn contains(self, instance: u32) -> bool {
self.low <= instance && instance <= self.high
}
pub(crate) fn encode_to_sink<S: EncodeSink + ?Sized>(
self,
sink: &mut S,
) -> Result<usize, SinkFull> {
staged(sink, &mut [0; 10], |tail| {
Ok(
primitives::encode_ctx_unsigned_to_sink(tail, 0, self.low.into())?
+ primitives::encode_ctx_unsigned_to_sink(tail, 1, self.high.into())?,
)
})
}
#[cfg(feature = "alloc")]
pub(crate) fn encode(self, buf: &mut BytesMut) {
self.encode_to_sink(buf)
.expect("the BytesMut sink cannot return SinkFull");
}
}
pub(crate) struct LimitLabels {
low: &'static str,
high: &'static str,
inverted: &'static str,
low_alone: &'static str,
high_alone: &'static str,
}
macro_rules! limit_labels {
($service:literal) => {
LimitLabels {
low: concat!($service, " low limit"),
high: concat!($service, " high limit"),
inverted: concat!($service, " low limit exceeds high limit"),
low_alone: concat!($service, " low limit needs the high limit [1] with it"),
high_alone: concat!($service, " high limit needs the low limit [0] before it"),
}
};
}
impl LimitLabels {
pub(crate) const WHO_IS: Self = limit_labels!("WhoIs");
#[cfg(feature = "alloc")]
pub(crate) const WHO_HAS: Self = limit_labels!("WhoHas");
}
pub(crate) struct WireLimits {
low: Option<u32>,
high: Option<u32>,
after_low: usize,
pub(crate) end: usize,
labels: &'static LimitLabels,
}
impl WireLimits {
pub(crate) fn decode(data: &[u8], labels: &'static LimitLabels) -> Result<Self, Error> {
let (low, after_low) =
decode_optional_ctx(data, 0, 0, labels.low, decode_ctx_unsigned::<u32>)?;
let (high, end) =
decode_optional_ctx(data, after_low, 1, labels.high, decode_ctx_unsigned::<u32>)?;
Ok(Self {
low,
high,
after_low,
end,
labels,
})
}
pub(crate) fn range(&self) -> Result<Option<DeviceInstanceRange>, Error> {
let labels = self.labels;
match (self.low, self.high) {
(None, None) => Ok(None),
(Some(low), Some(high)) => DeviceInstanceRange::decoded(low, high)
.map(Some)
.map_err(|_| DecodeError::new(DecodingKind::OutOfRange, 0, labels.inverted).into()),
(Some(_), None) => {
Err(
DecodeError::new(DecodingKind::Missing, self.after_low, labels.low_alone)
.into(),
)
}
(None, Some(_)) => {
Err(DecodeError::new(DecodingKind::Missing, 0, labels.high_alone).into())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WhoIsRequest {
pub range: Option<DeviceInstanceRange>,
}
impl WhoIsRequest {
pub fn all() -> Self {
Self { range: None }
}
pub fn encode_to_sink<S: EncodeSink + ?Sized>(&self, sink: &mut S) -> Result<usize, SinkFull> {
match self.range {
Some(range) => range.encode_to_sink(sink),
None => Ok(0),
}
}
#[cfg(feature = "alloc")]
pub fn encode(&self, buf: &mut BytesMut) {
self.encode_to_sink(buf)
.expect("the BytesMut sink cannot return SinkFull");
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let limits = WireLimits::decode(data, &LimitLabels::WHO_IS)?;
expect_end(data, limits.end, limits.end, "WhoIs")?;
Ok(Self {
range: limits.range()?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IAmRequest {
pub object_identifier: ObjectIdentifier,
pub max_apdu_length: u32,
pub segmentation_supported: Segmentation,
pub vendor_id: u16,
}
impl IAmRequest {
pub fn encode_to_sink<S: EncodeSink + ?Sized>(&self, sink: &mut S) -> Result<usize, SinkFull> {
staged(sink, &mut [0; 15], |tail| {
Ok(
primitives::encode_app_object_id_to_sink(tail, &self.object_identifier)?
+ primitives::encode_app_unsigned_to_sink(tail, self.max_apdu_length.into())?
+ primitives::encode_app_enumerated_to_sink(
tail,
self.segmentation_supported.to_raw().into(),
)?
+ primitives::encode_app_unsigned_to_sink(tail, self.vendor_id.into())?,
)
})
}
#[cfg(feature = "alloc")]
pub fn encode(&self, buf: &mut BytesMut) {
self.encode_to_sink(buf)
.expect("the BytesMut sink cannot return SinkFull");
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let (object_identifier, offset) = decode_app_object_id(data, 0, "IAm object identifier")?;
let (max_apdu_length, offset) =
decode_app_unsigned::<u32>(data, offset, "IAm max APDU length")?;
let (segmentation, offset) = decode_app_enumerated::<u8>(data, offset, "IAm segmentation")?;
let segmentation_supported = Segmentation::from_raw(segmentation);
let (vendor_id, end) = decode_app_unsigned::<u16>(data, offset, "IAm vendor ID")?;
expect_end(data, end, end, "IAm")?;
Ok(Self {
object_identifier,
max_apdu_length,
segmentation_supported,
vendor_id,
})
}
}
#[cfg(test)]
#[path = "who_is_sink_tests.rs"]
mod sink_tests;
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
use bacnet_encoding::tags;
use bacnet_types::enums::ObjectType;
#[test]
fn who_is_all_round_trip() {
let req = WhoIsRequest::all();
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(buf.is_empty());
let decoded = WhoIsRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn who_is_range_round_trip() {
let req = WhoIsRequest {
range: Some(DeviceInstanceRange::new(1000, 2000).unwrap()),
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(!buf.is_empty());
let decoded = WhoIsRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn i_am_round_trip() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 999,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let decoded = IAmRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn i_am_wire_format() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 42,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert_eq!(buf[0], 0xC4);
}
#[test]
fn test_decode_who_is_truncated() {
let req = WhoIsRequest {
range: Some(DeviceInstanceRange::new(1000, 2000).unwrap()),
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
for cut in 1..buf.len() {
assert!(WhoIsRequest::decode(&buf[..cut]).is_err(), "cut at {cut}");
}
}
#[test]
fn who_is_with_one_limit_is_refused() {
for data in [&[0x09, 0x01][..], &[0x19, 0x0A]] {
let error = WhoIsRequest::decode(data).unwrap_err();
assert!(matches!(error, Error::Decoding(_)), "{error:?}");
}
}
#[test]
fn test_decode_who_is_invalid_tag() {
let error = WhoIsRequest::decode(&[0x29, 0]).unwrap_err();
assert!(matches!(error, Error::Decoding(_)), "{error:?}");
}
#[test]
fn who_is_low_exceeds_high_is_error() {
let data = [0x0A, 0x07, 0xD0, 0x1A, 0x03, 0xE8];
let err = WhoIsRequest::decode(&data).unwrap_err();
assert!(
err.to_string()
.contains("WhoIs low limit exceeds high limit"),
"expected a low limit above the high limit to fail, got: {err:?}"
);
}
#[test]
fn who_is_equal_limits_is_valid() {
let req = WhoIsRequest {
range: Some(DeviceInstanceRange::single(1500).unwrap()),
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert_eq!(buf[..], [0x0A, 0x05, 0xDC, 0x1A, 0x05, 0xDC]);
let decoded = WhoIsRequest::decode(&buf).unwrap();
assert_eq!(decoded, req);
}
#[test]
fn who_is_limits_must_fit_u32() {
let encode_range = |low, high| {
let mut buf = BytesMut::new();
primitives::encode_ctx_unsigned(&mut buf, 0, low);
primitives::encode_ctx_unsigned(&mut buf, 1, high);
buf
};
for (low, high, field, value) in [
(
4_294_967_297,
4_294_967_297,
"low limit: [0]",
4_294_967_297_u64,
),
(1, 4_294_967_297, "high limit: [1]", 4_294_967_297),
] {
let encoded = encode_range(low, high);
let error = WhoIsRequest::decode(&encoded).unwrap_err();
assert!(
error
.to_string()
.contains(&format!("WhoIs {field} value {value} exceeds u32")),
"unexpected error for {field} {value}: {error}"
);
}
let mut leading_zero = BytesMut::new();
for tag_number in [0, 1] {
tags::encode_tag(&mut leading_zero, tag_number, tags::TagClass::Context, 5);
leading_zero.extend_from_slice(&[0, 0xff, 0xff, 0xff, 0xff]);
}
let decoded = WhoIsRequest::decode(&leading_zero).unwrap();
let range = decoded.range.unwrap();
assert_eq!((range.low(), range.high()), (u32::MAX, u32::MAX));
}
#[test]
fn i_am_values_must_fit_field_widths() {
let object_identifier = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
let encode_request = |max_apdu_length, segmentation, vendor_id| {
let mut buf = BytesMut::new();
primitives::encode_app_object_id(&mut buf, &object_identifier);
primitives::encode_app_unsigned(&mut buf, max_apdu_length);
primitives::encode_app_enumerated(&mut buf, segmentation);
primitives::encode_app_unsigned(&mut buf, vendor_id);
buf
};
for (max_apdu_length, segmentation, vendor_id, refusal) in [
(4_294_967_296, 0, 0, "max APDU length: Unsigned exceeds u32"),
(1, 256, 0, "segmentation: ENUMERATED exceeds u8"),
(1, 0, 65_536, "vendor ID: Unsigned exceeds u16"),
] {
let encoded = encode_request(max_apdu_length, segmentation, vendor_id);
let error = IAmRequest::decode(&encoded).unwrap_err();
assert!(
error.to_string().contains(&format!("IAm {refusal}")),
"unexpected error for {refusal}: {error}"
);
}
let mut leading_zero = BytesMut::new();
primitives::encode_app_object_id(&mut leading_zero, &object_identifier);
for (tag_number, content) in [
(tags::app_tag::UNSIGNED, &[0, 0xff, 0xff, 0xff, 0xff][..]),
(tags::app_tag::ENUMERATED, &[0, 0xff][..]),
(tags::app_tag::UNSIGNED, &[0, 0xff, 0xff][..]),
] {
tags::encode_tag(
&mut leading_zero,
tag_number,
tags::TagClass::Application,
content.len() as u32,
);
leading_zero.extend_from_slice(content);
}
let decoded = IAmRequest::decode(&leading_zero).unwrap();
assert_eq!(decoded.max_apdu_length, u32::MAX);
assert_eq!(decoded.segmentation_supported.to_raw(), u8::MAX);
assert_eq!(decoded.vendor_id, u16::MAX);
let encode_with_tags = |object_tag, max_apdu_tag, segmentation_tag, vendor_tag| {
let mut buf = BytesMut::new();
for (tag_number, content) in [
(object_tag, &object_identifier.encode()[..]),
(max_apdu_tag, &1476_u16.to_be_bytes()[..]),
(segmentation_tag, &[0][..]),
(vendor_tag, &999_u16.to_be_bytes()[..]),
] {
tags::encode_tag(
&mut buf,
tag_number,
tags::TagClass::Application,
content.len() as u32,
);
buf.extend_from_slice(content);
}
buf
};
for (object_tag, max_apdu_tag, segmentation_tag, vendor_tag, field) in [
(
tags::app_tag::UNSIGNED,
tags::app_tag::UNSIGNED,
tags::app_tag::ENUMERATED,
tags::app_tag::UNSIGNED,
"object identifier",
),
(
tags::app_tag::OBJECT_IDENTIFIER,
tags::app_tag::ENUMERATED,
tags::app_tag::ENUMERATED,
tags::app_tag::UNSIGNED,
"max APDU length",
),
(
tags::app_tag::OBJECT_IDENTIFIER,
tags::app_tag::UNSIGNED,
tags::app_tag::UNSIGNED,
tags::app_tag::UNSIGNED,
"segmentation",
),
(
tags::app_tag::OBJECT_IDENTIFIER,
tags::app_tag::UNSIGNED,
tags::app_tag::ENUMERATED,
tags::app_tag::SIGNED,
"vendor ID",
),
] {
let encoded = encode_with_tags(object_tag, max_apdu_tag, segmentation_tag, vendor_tag);
let error = IAmRequest::decode(&encoded).unwrap_err();
assert!(
error
.to_string()
.contains(&format!("IAm {field}: expected application-tagged")),
"unexpected error for {field} tag: {error}"
);
}
let valid = encode_request(1476, 0, 999);
let mut reserved_lvt = BytesMut::from(&[0xc6, 4][..]);
reserved_lvt.extend_from_slice(&valid[1..]);
assert!(IAmRequest::decode(&reserved_lvt).is_err());
}
#[test]
fn test_decode_i_am_empty_input() {
assert!(IAmRequest::decode(&[]).is_err());
}
#[test]
fn test_decode_i_am_truncated_1_byte() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 999,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(IAmRequest::decode(&buf[..1]).is_err());
}
#[test]
fn test_decode_i_am_truncated_2_bytes() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 999,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(IAmRequest::decode(&buf[..2]).is_err());
}
#[test]
fn test_decode_i_am_truncated_3_bytes() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 999,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(IAmRequest::decode(&buf[..3]).is_err());
}
#[test]
fn test_decode_i_am_truncated_half() {
let req = IAmRequest {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap(),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
vendor_id: 999,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let half = buf.len() / 2;
assert!(IAmRequest::decode(&buf[..half]).is_err());
}
#[test]
fn test_decode_i_am_invalid_tag() {
assert!(IAmRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
}
}
#[cfg(all(test, feature = "alloc"))]
#[path = "device_range_tests.rs"]
mod device_range_tests;