use bacnet_encoding::constructed::{
constructed_channel_value, decode_xy_color, ConstructedChannelValue,
};
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberDatatype {
Unknown,
Boolean,
Unsigned,
Integer,
Real,
Double,
OctetString,
CharacterString,
BitString,
Enumerated,
Date,
Time,
ObjectIdentifier,
LightingCommand,
ColorCommand,
XyColor,
}
impl MemberDatatype {
pub fn of(
object_type: ObjectType,
property: PropertyIdentifier,
current: Option<&PropertyValue>,
) -> Self {
use PropertyIdentifier as P;
match property {
P::LIGHTING_COMMAND => return Self::LightingCommand,
P::COLOR_COMMAND => return Self::ColorCommand,
P::DEFAULT_COLOR => return Self::XyColor,
P::PRESENT_VALUE | P::TRACKING_VALUE if object_type == ObjectType::COLOR => {
return Self::XyColor
}
_ => {}
}
match current {
Some(PropertyValue::Boolean(_)) => Self::Boolean,
Some(PropertyValue::Unsigned(_)) => Self::Unsigned,
Some(PropertyValue::Signed(_)) => Self::Integer,
Some(PropertyValue::Real(_)) => Self::Real,
Some(PropertyValue::Double(_)) => Self::Double,
Some(PropertyValue::OctetString(_)) => Self::OctetString,
Some(PropertyValue::CharacterString(_)) => Self::CharacterString,
Some(PropertyValue::BitString { .. }) => Self::BitString,
Some(PropertyValue::Enumerated(_)) => Self::Enumerated,
Some(PropertyValue::Date(_)) => Self::Date,
Some(PropertyValue::Time(_)) => Self::Time,
Some(PropertyValue::ObjectIdentifier(_)) => Self::ObjectIdentifier,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CoercionFailure;
#[derive(Clone, Copy)]
enum Number {
Boolean(bool),
Unsigned(u64),
Integer(i64),
Real(f32),
Double(f64),
}
const FLOAT_TO_INTEGER_LIMIT: f64 = 2_147_483_000.0;
impl Number {
fn of(value: &PropertyValue) -> Option<Self> {
Some(match *value {
PropertyValue::Boolean(value) => Self::Boolean(value),
PropertyValue::Unsigned(value) => Self::Unsigned(value),
PropertyValue::Enumerated(value) => Self::Unsigned(value.into()),
PropertyValue::Signed(value) => Self::Integer(value.into()),
PropertyValue::Real(value) => Self::Real(value),
PropertyValue::Double(value) => Self::Double(value),
_ => return None,
})
}
fn to_boolean(self) -> bool {
match self {
Self::Boolean(value) => value,
Self::Unsigned(value) => value != 0,
Self::Integer(value) => value != 0,
Self::Real(value) => value != 0.0,
Self::Double(value) => value != 0.0,
}
}
fn to_unsigned(self) -> Option<u64> {
match self {
Self::Boolean(value) => Some(value.into()),
Self::Unsigned(value) => Some(value),
Self::Integer(value) => u64::try_from(value).ok(),
Self::Real(value) => float_to_unsigned(value.into()),
Self::Double(value) => float_to_unsigned(value),
}
}
fn to_integer(self) -> Option<i32> {
match self {
Self::Boolean(value) => Some(value.into()),
Self::Unsigned(value) => rule_3_bound(value).map(|value| value as i32),
Self::Integer(value) => i32::try_from(value).ok(),
Self::Real(value) => float_to_integer(value.into()),
Self::Double(value) => float_to_integer(value),
}
}
fn to_real(self) -> Option<f32> {
match self {
Self::Boolean(value) => Some(u8::from(value).into()),
Self::Unsigned(value) => rule_3_bound(value).map(|value| value as f32),
Self::Integer(value) => Some(value as f32),
Self::Real(value) => Some(value),
Self::Double(value) => (value.abs() <= f64::from(f32::MAX)).then_some(value as f32),
}
}
fn to_double(self) -> Option<f64> {
match self {
Self::Boolean(value) => Some(u8::from(value).into()),
Self::Unsigned(value) => rule_3_bound(value).map(f64::from),
Self::Integer(value) => Some(value as f64),
Self::Real(value) => Some(value.into()),
Self::Double(value) => Some(value),
}
}
}
fn rule_3_bound(value: u64) -> Option<u32> {
i32::try_from(value).ok().map(|value| value as u32)
}
fn float_to_unsigned(value: f64) -> Option<u64> {
(0.0..=FLOAT_TO_INTEGER_LIMIT)
.contains(&value)
.then(|| value.trunc() as u64)
}
fn float_to_integer(value: f64) -> Option<i32> {
(-FLOAT_TO_INTEGER_LIMIT..=FLOAT_TO_INTEGER_LIMIT)
.contains(&value)
.then(|| value.trunc() as i32)
}
pub fn coerce_channel_value(
value: &PropertyValue,
target: MemberDatatype,
) -> Result<PropertyValue, CoercionFailure> {
use ConstructedChannelValue as C;
use MemberDatatype as D;
if let PropertyValue::ApplicationData(octets) = value {
let inner = || octets[1..octets.len() - 1].to_vec();
return match (constructed_channel_value(octets), target) {
(Some(C::LightingCommand), D::LightingCommand)
| (Some(C::ColorCommand), D::ColorCommand) => {
Ok(PropertyValue::ApplicationData(inner()))
}
(Some(C::XyColor), D::XyColor) => {
let (color, _) = decode_xy_color(octets, 1).map_err(|_| CoercionFailure)?;
Ok(PropertyValue::List(vec![
PropertyValue::Real(color.x),
PropertyValue::Real(color.y),
]))
}
_ => Err(CoercionFailure),
};
}
match target {
D::LightingCommand | D::ColorCommand | D::XyColor => return Err(CoercionFailure),
D::Unknown => return Ok(value.clone()),
_ => {}
}
if *value == PropertyValue::Null {
return Ok(PropertyValue::Null);
}
if let Some(number) = Number::of(value) {
let coerced = match target {
D::Boolean => Some(PropertyValue::Boolean(number.to_boolean())),
D::Unsigned => number.to_unsigned().map(PropertyValue::Unsigned),
D::Integer => number.to_integer().map(PropertyValue::Signed),
D::Real => number.to_real().map(PropertyValue::Real),
D::Double => number.to_double().map(PropertyValue::Double),
D::Enumerated => number
.to_unsigned()
.and_then(|raw| u32::try_from(raw).ok())
.map(PropertyValue::Enumerated),
D::ObjectIdentifier => match *value {
PropertyValue::Unsigned(raw) => u32::try_from(raw)
.ok()
.and_then(|raw| ObjectIdentifier::decode(&raw.to_be_bytes()).ok())
.map(PropertyValue::ObjectIdentifier),
_ => None,
},
_ => None,
};
return coerced.ok_or(CoercionFailure);
}
let passes = match (value, target) {
(PropertyValue::OctetString(_), D::OctetString)
| (PropertyValue::CharacterString(_), D::CharacterString)
| (PropertyValue::BitString { .. }, D::BitString)
| (PropertyValue::Date(_), D::Date)
| (PropertyValue::Time(_), D::Time)
| (PropertyValue::ObjectIdentifier(_), D::ObjectIdentifier) => true,
(PropertyValue::ObjectIdentifier(oid), D::Unsigned) => {
return Ok(PropertyValue::Unsigned(
u32::from_be_bytes(oid.encode()).into(),
));
}
_ => false,
};
if passes {
Ok(value.clone())
} else {
Err(CoercionFailure)
}
}