#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use bacnet_encoding::constructed::{check_decoded_mac_len, check_encoded_mac_len};
use bacnet_encoding::primitives;
use bacnet_encoding::tags;
use bacnet_types::enums::ObjectType;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use bacnet_encoding::constructed::tagged::{
decode_app_character_string, decode_app_object_id, decode_app_primitive, decode_app_unsigned,
next_is_application,
};
struct IdentityLabels {
vendor_id: &'static str,
model_name: &'static str,
serial_number: &'static str,
}
const WHO_AM_I: IdentityLabels = IdentityLabels {
vendor_id: "WhoAmI vendor-id",
model_name: "WhoAmI model-name",
serial_number: "WhoAmI serial-number",
};
const YOU_ARE: IdentityLabels = IdentityLabels {
vendor_id: "YouAre vendor-id",
model_name: "YouAre model-name",
serial_number: "YouAre serial-number",
};
fn decode_identity(
data: &[u8],
labels: &IdentityLabels,
) -> Result<(u16, String, String, usize), Error> {
let (vendor_id, offset) = decode_app_unsigned::<u16>(data, 0, labels.vendor_id)?;
let (model_name, offset) = decode_app_character_string(data, offset, labels.model_name)?;
let (serial_number, offset) = decode_app_character_string(data, offset, labels.serial_number)?;
Ok((vendor_id, model_name, serial_number, offset))
}
fn encode_identity(
buf: &mut BytesMut,
vendor_id: u16,
model_name: &str,
serial_number: &str,
) -> Result<(), Error> {
primitives::encode_app_unsigned(buf, u64::from(vendor_id));
primitives::encode_app_character_string(buf, model_name)?;
primitives::encode_app_character_string(buf, serial_number)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WhoAmIRequest {
pub vendor_id: u16,
pub model_name: String,
pub serial_number: String,
}
impl WhoAmIRequest {
pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
let mut scratch = BytesMut::new();
encode_identity(
&mut scratch,
self.vendor_id,
&self.model_name,
&self.serial_number,
)?;
buf.extend_from_slice(&scratch);
Ok(())
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let (vendor_id, model_name, serial_number, offset) = decode_identity(data, &WHO_AM_I)?;
if offset != data.len() {
return Err(Error::decoding(offset, "WhoAmI has trailing data"));
}
Ok(Self {
vendor_id,
model_name,
serial_number,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct YouAreRequest {
pub vendor_id: u16,
pub model_name: String,
pub serial_number: String,
pub device_identifier: Option<ObjectIdentifier>,
pub device_mac_address: Option<Vec<u8>>,
}
impl YouAreRequest {
pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
if self.device_identifier.is_none() && self.device_mac_address.is_none() {
return Err(Error::encode("YouAre needs a device identifier, a device MAC address, or both"));
}
if let Some(oid) = &self.device_identifier {
if oid.object_type() != ObjectType::DEVICE {
return Err(Error::encode("YouAre device identifier must name a Device object"));
}
}
if let Some(mac) = &self.device_mac_address {
check_encoded_mac_len(mac, "YouAre device-mac-address")?;
}
let mut scratch = BytesMut::new();
encode_identity(
&mut scratch,
self.vendor_id,
&self.model_name,
&self.serial_number,
)?;
if let Some(oid) = &self.device_identifier {
primitives::encode_app_object_id(&mut scratch, oid);
}
if let Some(mac) = &self.device_mac_address {
primitives::encode_app_octet_string(&mut scratch, mac);
}
buf.extend_from_slice(&scratch);
Ok(())
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let (vendor_id, model_name, serial_number, mut offset) = decode_identity(data, &YOU_ARE)?;
let mut device_identifier = None;
if next_is_application(data, offset, tags::app_tag::OBJECT_IDENTIFIER)? {
let (oid, end) = decode_app_object_id(data, offset, "YouAre device-identifier")?;
if oid.object_type() != ObjectType::DEVICE {
return Err(Error::decoding(
offset,
"YouAre device identifier must name a Device object",
));
}
device_identifier = Some(oid);
offset = end;
}
let mut device_mac_address = None;
if next_is_application(data, offset, tags::app_tag::OCTET_STRING)? {
let what = "YouAre device-mac-address";
let (content, end) =
decode_app_primitive(data, offset, tags::app_tag::OCTET_STRING, what)?;
check_decoded_mac_len(content.len(), offset, what)?;
device_mac_address = Some(content.to_vec());
offset = end;
}
if offset != data.len() {
return Err(Error::decoding(
offset,
"YouAre has trailing or unexpected data",
));
}
if device_identifier.is_none() && device_mac_address.is_none() {
return Err(Error::decoding(
offset,
"YouAre needs a device identifier, a device MAC address, or both",
));
}
Ok(Self {
vendor_id,
model_name,
serial_number,
device_identifier,
device_mac_address,
})
}
}
#[cfg(test)]
#[path = "who_am_i_tests.rs"]
mod tests;