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,
};
fn decode_identity(data: &[u8], context: &str) -> Result<(u16, String, String, usize), Error> {
let (vendor_id, offset) = decode_app_unsigned::<u16>(data, 0, &format!("{context} vendor-id"))?;
let (model_name, offset) =
decode_app_character_string(data, offset, &format!("{context} model-name"))?;
let (serial_number, offset) =
decode_app_character_string(data, offset, &format!("{context} 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, "WhoAmI")?;
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::Encoding(
"YouAre needs a device identifier, a device MAC address, or both".into(),
));
}
if let Some(oid) = &self.device_identifier {
if oid.object_type() != ObjectType::DEVICE {
return Err(Error::Encoding(
"YouAre device identifier must name a Device object".into(),
));
}
}
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, "YouAre")?;
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;