use std::collections::BTreeMap;
use log::debug;
use log::warn;
use shvproto::MetaMap;
use shvproto::RpcValue;
use shvproto::Map as RpcMap;
use crate::metamethod::AccessLevel;
use crate::metamethod::DirAttribute;
use crate::util::join_path;
pub const KEY_DEVICE_TYPE: &str = "deviceType";
pub const KEY_TYPE_NAME: &str = "typeName";
pub const KEY_LABEL: &str = "label";
pub const KEY_DESCRIPTION: &str = "description";
pub const KEY_UNIT: &str = "unit";
pub const KEY_NAME: &str = "name";
pub const KEY_TYPE: &str = "type";
pub const KEY_VALUE: &str = "value";
pub const KEY_FIELDS: &str = "fields";
pub const KEY_SAMPLE_TYPE: &str = "sampleType";
pub const KEY_RESTRICTION_OF_DEVICE: &str = "restrictionOfDevice";
pub const KEY_RESTRICTION_OF_TYPE: &str = "restrictionOfType";
pub const KEY_SITE_SPECIFIC_LOCALIZATION: &str = "siteSpecificLocalization";
pub const KEY_TAGS: &str = "tags";
pub const KEY_METHODS: &str = "methods";
pub const KEY_BLACKLIST: &str = "blacklist";
pub const KEY_DEC_PLACES: &str = "decPlaces";
pub const KEY_VISUAL_STYLE: &str = "visualStyle";
pub const KEY_ALARM: &str = "alarm";
pub const KEY_STATE_ALARM: &str = "stateAlarm";
pub const KEY_ALARM_LEVEL: &str = "alarmLevel";
const METH_LS: &str = "ls";
const METH_DIR: &str = "dir";
fn merge_tags(mut map: RpcMap) -> RpcMap {
if let Some(rv) = map.remove(KEY_TAGS)
&& let shvproto::Value::Map(tags) = rv.value {
map.extend(*tags);
}
map
}
fn set_data_value(descr: &mut (impl TypeDescriptionMethods + ?Sized), key: impl Into<String>, value: impl Into<RpcValue>) {
descr.data_mut().insert(key.into(), value.into());
}
pub trait TypeDescriptionMethods {
fn data(&self) -> &RpcMap;
fn data_mut(&mut self) -> &mut RpcMap;
fn data_value(&self, key: impl AsRef<str>) -> Option<&RpcValue> {
self.data().get(key.as_ref())
}
fn data_value_into<'a, T>(&'a self, key: impl AsRef<str>) -> Option<T>
where
T: TryFrom<&'a RpcValue>,
{
<T>::try_from(self.data_value(key)?).ok()
}
fn is_valid(&self) -> bool {
!self.data().is_empty()
}
fn type_id(&self) -> Option<Type> {
self.data_value_into::<i32>(KEY_TYPE).and_then(Type::try_from_i32)
}
fn set_type_id(&mut self, type_id: Type) {
set_data_value(self, KEY_TYPE, type_id as i32);
}
fn type_name(&self) -> Option<&str> {
if let Some(tn) = self.data_value_into::<&str>(KEY_TYPE_NAME) && !tn.is_empty() {
Some(tn)
} else {
self.type_id().as_ref().map(Type::as_str)
}
}
fn set_type_name(&mut self, type_name: impl AsRef<str>) {
set_data_value(self, KEY_TYPE_NAME, type_name.as_ref());
if self.type_id().is_none() && let Some(new_type_id) = Type::try_from_str(type_name.as_ref()) {
set_data_value(self, KEY_TYPE, new_type_id as i32);
}
}
fn sample_type(&self) -> Option<SampleType> {
self.data_value_into::<i32>(KEY_SAMPLE_TYPE).and_then(SampleType::try_from_i32)
}
fn set_sample_type(&mut self, sample_type_id: SampleType) {
set_data_value(self, KEY_SAMPLE_TYPE, sample_type_id as i32);
}
fn fields(&self) -> Vec<FieldDescription> {
self.data_value_into::<Vec<FieldDescription>>(KEY_FIELDS).unwrap_or_default()
}
fn set_fields(&mut self, fields: Vec<FieldDescription>) {
set_data_value(self, KEY_FIELDS, fields);
}
fn bit_range(&self) -> Option<&RpcValue> {
self.data_value(KEY_VALUE)
}
fn bit_range_pair(&self) -> (u64, u64) {
let Some(rv) = self.bit_range() else {
return (0, 0);
};
if rv.is_list() {
let mut it = Vec::<u64>::try_from(rv).unwrap_or_default().into_iter();
let (b1, b2) = (it.next().unwrap_or_default(), it.next().unwrap_or_default());
if b2 < b1 {
warn!("Invalid bit specification: {val}", val = rv.to_cpon());
(b1, b1)
} else {
(b1, b2)
}
} else {
let val = rv.as_u64();
(val, val)
}
}
fn bitfield_value(&self, val: u64) -> u64 {
if !self.is_valid() {
return 0;
}
let (b1, b2) = self.bit_range_pair();
let mask = if b2 >= 63 {
u64::MAX
} else {
(1u64 << (b2 + 1)) - 1
};
debug!("bits: {b1} {b2} val: {val:#x} mask: {mask:#x}");
let new_val = if b1 > 63 {
0
} else {
(val & mask) >> b1
};
debug!("val masked and rotated right by: {b1} bits, new_val: {new_val:#x}");
if b1 == b2 {
(new_val != 0).into()
} else {
new_val
}
}
fn set_bitfield_value(&self, bitfield: u64, val: u64) -> u64 {
if !self.is_valid() {
return val;
}
let (b1, b2) = self.bit_range_pair();
let width = b2.saturating_sub(b1) + 1;
let mask = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
let val = val & mask;
let shifted_mask = if b1 >= 64 {
0 } else {
mask << b1
};
let shifted_val = if b1 >= 64 {
0
} else {
val << b1
};
let mut bitfield = bitfield & !shifted_mask;
bitfield |= shifted_val;
bitfield
}
fn decimal_places(&self) -> Option<i64> {
self.data_value_into(KEY_DEC_PLACES)
}
fn set_decimal_places(&mut self, n: i64) {
set_data_value(self, KEY_DEC_PLACES, n);
}
fn unit(&self) -> Option<&str> {
self.data_value_into(KEY_UNIT)
}
fn set_unit(&mut self, unit: impl AsRef<str>) {
set_data_value(self, KEY_UNIT, unit.as_ref());
}
fn label(&self) -> Option<&str> {
self.data_value_into(KEY_LABEL)
}
fn set_label(&mut self, label: impl AsRef<str>) {
set_data_value(self, KEY_LABEL, label.as_ref());
}
fn description(&self) -> Option<&str> {
self.data_value_into(KEY_DESCRIPTION)
}
fn set_description(&mut self, description: impl AsRef<str>) {
set_data_value(self, KEY_DESCRIPTION, description.as_ref());
}
fn visual_style_name(&self) -> Option<&str> {
self.data_value_into(KEY_VISUAL_STYLE)
}
fn set_visual_style_name(&mut self, visual_style_name: impl AsRef<str>) {
set_data_value(self, KEY_VISUAL_STYLE, visual_style_name.as_ref());
}
fn restriction_of_type(&self) -> Option<&str> {
self.data_value_into(KEY_RESTRICTION_OF_TYPE)
}
fn is_site_specific_localization(&self) -> bool {
self.data_value_into(KEY_SITE_SPECIFIC_LOCALIZATION).unwrap_or_default()
}
fn field(&self, field_name: impl AsRef<str>) -> Option<FieldDescription> {
self.data_value_into::<shvproto::List>(KEY_FIELDS)
.unwrap_or_default()
.into_iter()
.filter_map(|rpc_value| FieldDescription::try_from(rpc_value).ok())
.find(|descr| descr.name() == field_name.as_ref())
}
fn field_value(&self, val: impl Into<RpcValue>, field_name: impl AsRef<str>) -> Option<RpcValue> {
let val = val.into();
match self.type_id()? {
Type::BitField => {
self.field(field_name).map(|descr| descr.bitfield_value(val.as_u64()).into())
}
Type::Map => {
if let shvproto::Value::Map(map) = &val.value {
map.get(field_name.as_ref()).cloned()
} else {
None
}
}
Type::Enum => {
let descr = self.field(field_name)?;
let v = descr.bit_range()?;
<i64>::try_from(v).is_ok().then(|| v.clone())
}
_ => None,
}
}
}
pub trait FieldDescriptionMethods: TypeDescriptionMethods {
fn name(&self) -> &str {
self.data_value(KEY_NAME).map_or("", RpcValue::as_str)
}
fn set_name(&mut self, name: impl AsRef<str>) {
set_data_value(self, KEY_NAME, name.as_ref());
}
fn alarm(&self) -> Option<&str> {
self.data_value(KEY_ALARM).map(RpcValue::as_str)
}
fn set_alarm(&mut self, alarm: impl AsRef<str>) {
set_data_value(self, KEY_ALARM, alarm.as_ref());
}
fn state_alarm(&self) -> Option<&str> {
self.data_value(KEY_STATE_ALARM).map(RpcValue::as_str)
}
fn set_state_alarm(&mut self, alarm: impl AsRef<str>) {
set_data_value(self, KEY_STATE_ALARM, alarm.as_ref());
}
fn alarm_level(&self) -> Option<i32> {
self.data_value(KEY_ALARM_LEVEL).map(RpcValue::as_i32)
}
}
#[derive(PartialEq)]
pub enum Type {
BitField = 1,
Enum,
Bool,
UInt,
Int,
Decimal,
Double,
String,
DateTime,
List,
Map,
IMap,
}
impl Type {
pub fn as_str(&self) -> &'static str {
match self {
Type::BitField => "BitField",
Type::Enum => "Enum",
Type::Bool => "Bool",
Type::UInt => "UInt",
Type::Int => "Int",
Type::Decimal => "Decimal",
Type::Double => "Double",
Type::String => "String",
Type::DateTime => "DateTime",
Type::List => "List",
Type::Map => "Map",
Type::IMap => "IMap",
}
}
pub fn try_from_i32(value: i32) -> Option<Self> {
match value {
v if v == Self::BitField as _ => Some(Self::BitField),
v if v == Self::Enum as _ => Some(Self::Enum),
v if v == Self::Bool as _ => Some(Self::Bool),
v if v == Self::UInt as _ => Some(Self::UInt),
v if v == Self::Int as _ => Some(Self::Int),
v if v == Self::Decimal as _ => Some(Self::Decimal),
v if v == Self::Double as _ => Some(Self::Double),
v if v == Self::String as _ => Some(Self::String),
v if v == Self::DateTime as _ => Some(Self::DateTime),
v if v == Self::List as _ => Some(Self::List),
v if v == Self::Map as _ => Some(Self::Map),
v if v == Self::IMap as _ => Some(Self::IMap),
_ => None,
}
}
pub fn try_from_str(s: &str) -> Option<Self> {
match s {
"BitField" => Some(Self::BitField),
"Enum" => Some(Self::Enum),
"Bool" => Some(Self::Bool),
"UInt" => Some(Self::UInt),
"Int" => Some(Self::Int),
"Decimal" => Some(Self::Decimal),
"Double" => Some(Self::Double),
"String" => Some(Self::String),
"DateTime" => Some(Self::DateTime),
"List" => Some(Self::List),
"Map" => Some(Self::Map),
"IMap" => Some(Self::IMap),
_ => None,
}
}
}
pub enum SampleType {
Continuous = 1,
Discrete
}
impl SampleType {
pub fn as_str(&self) -> &'static str {
match self {
SampleType::Continuous => "Continuos",
SampleType::Discrete => "Discrete",
}
}
pub fn try_from_i32(value: i32) -> Option<Self> {
match value {
v if v == Self::Continuous as _ => Some(Self::Continuous),
v if v == Self::Discrete as _ => Some(Self::Discrete),
_ => None,
}
}
pub fn try_from_str(s: &str) -> Option<Self> {
match s {
"Continuous" => Some(Self::Continuous),
"Discrete" | "discrete" | "D" | "2" => Some(Self::Discrete),
_ => None,
}
}
}
#[derive(Debug)]
pub struct TypeDescription {
data: RpcMap,
}
impl TypeDescription {
pub fn from_rpc_map(mut map: RpcMap) -> Self {
let src_fields: Vec<RpcValue> = map.remove("fields")
.and_then(|v| v.try_into().ok())
.unwrap_or_default();
let fields = src_fields
.into_iter()
.map(|v| merge_tags(RpcMap::try_from(v).unwrap_or_default()))
.collect::<Vec<_>>();
let mut map = merge_tags(map);
map.insert(KEY_FIELDS.into(),fields.into());
let mut ret = Self { data: map };
if let Some(s) = ret.data_value_into::<String>(KEY_TYPE_NAME) {
ret.set_type_name(s);
}
if ret.type_id().is_none()
&& let Some(s) = ret.data_value_into::<String>(KEY_NAME)
{
ret.set_type_name(s);
}
if ret.type_id().is_none()
&& let Some(s) = ret.data_value_into::<String>(KEY_TYPE)
{
ret.set_type_name(s);
}
if let Some(s) = ret.data_value_into::<&str>(KEY_SAMPLE_TYPE)
&& let Some(sample_type) = SampleType::try_from_str(s) {
ret.set_sample_type(sample_type);
}
if ret.is_valid() && ret.sample_type().is_none() {
ret.set_sample_type(SampleType::Continuous);
}
ret
}
pub fn new(t: Type, fields: Vec<FieldDescription>, sample_type: SampleType, tags: RpcMap) -> Self {
let mut ret = Self { data: tags };
ret.set_type_id(t);
ret.set_fields(fields);
ret.set_sample_type(sample_type);
ret
}
}
impl TypeDescriptionMethods for TypeDescription {
fn data(&self) -> &RpcMap {
&self.data
}
fn data_mut(&mut self) -> &mut RpcMap {
&mut self.data
}
}
impl TryFrom<RpcValue> for TypeDescription {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
let map: RpcMap = value.try_into()?;
Ok(Self::from_rpc_map(map))
}
}
impl TryFrom<&RpcValue> for TypeDescription {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
value.clone().try_into()
}
}
#[derive(Clone, Default, Debug, PartialEq)]
pub struct FieldDescription {
data: RpcMap,
}
impl FieldDescription {
pub fn from_rpc_map(map: RpcMap) -> Self {
Self { data: merge_tags(map) }
}
pub fn into_rpc_map(self) -> RpcMap {
let is_descr_empty = self.description().is_none_or(str::is_empty);
let mut ret = self.data;
if is_descr_empty {
ret.remove(KEY_DESCRIPTION);
}
ret
}
}
impl From<FieldDescription> for RpcValue {
fn from(value: FieldDescription) -> Self {
value.into_rpc_map().into()
}
}
impl TryFrom<RpcValue> for FieldDescription {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
value.try_into().map(Self::from_rpc_map)
}
}
impl TryFrom<&RpcValue> for FieldDescription {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
value.to_owned().try_into()
}
}
impl TypeDescriptionMethods for FieldDescription {
fn data(&self) -> &RpcMap {
&self.data
}
fn data_mut(&mut self) -> &mut RpcMap {
&mut self.data
}
}
impl FieldDescriptionMethods for FieldDescription { }
#[derive(Clone, Default, Debug, PartialEq)]
pub struct PropertyDescription {
base: FieldDescription,
}
impl PropertyDescription {
pub fn from_rpc_map(mut map: RpcMap) -> (Self, RpcMap) {
const KNOWN_TAGS: &[&str] = &[
KEY_DEVICE_TYPE,
KEY_NAME,
KEY_TYPE_NAME,
KEY_LABEL,
KEY_DESCRIPTION,
KEY_UNIT,
KEY_METHODS,
"autoload",
"autorefresh",
"monitored",
"monitorOptions",
KEY_SAMPLE_TYPE,
KEY_ALARM,
KEY_STATE_ALARM,
KEY_ALARM_LEVEL,
];
let node_map = KNOWN_TAGS
.iter()
.filter_map(|tag| map.remove(*tag).map(|val| (tag.to_string(), val)))
.filter(|(tag, _val)| tag != KEY_DEVICE_TYPE)
.collect::<RpcMap>();
(Self { base: FieldDescription { data: merge_tags(node_map) }}, map)
}
pub fn into_rpc_map(self) -> RpcMap {
let methods = self.methods();
let mut res = self.base.into_rpc_map();
if !methods.is_empty() {
res.insert(KEY_METHODS.into(), methods.into());
}
res
}
pub fn methods(&self) -> Vec<MethodDescription> {
self.data_value_into(KEY_METHODS).unwrap_or_default()
}
pub fn method(&self, method_name: impl AsRef<str>) -> Option<MethodDescription> {
self.data_value_into::<shvproto::List>(KEY_METHODS)
.unwrap_or_default()
.into_iter()
.filter_map(|rpc_value| MethodDescription::try_from(rpc_value).ok())
.find(|descr| descr.name == method_name.as_ref())
}
}
impl TryFrom<RpcValue> for PropertyDescription {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
let map: RpcMap = value.try_into()?;
let (res, _) = Self::from_rpc_map(map);
Ok(res)
}
}
impl TryFrom<&RpcValue> for PropertyDescription {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
value.clone().try_into()
}
}
impl From<PropertyDescription> for RpcValue {
fn from(value: PropertyDescription) -> Self {
value.into_rpc_map().into()
}
}
impl TypeDescriptionMethods for PropertyDescription {
fn data(&self) -> &RpcMap {
self.base.data()
}
fn data_mut(&mut self) -> &mut RpcMap {
self.base.data_mut()
}
}
impl FieldDescriptionMethods for PropertyDescription { }
#[derive(Clone, Debug)]
pub struct MethodDescription {
pub name: String,
pub flags: u32,
pub access: AccessLevel,
pub param: String,
pub result: String,
pub signals: Vec::<(String, Option<String>)>,
pub description: String,
pub extra: Option<RpcMap>,
}
impl Default for MethodDescription {
fn default() -> Self {
Self {
name: String::default(),
flags: u32::default(),
access: AccessLevel::Browse,
param: String::default(),
result: String::default(),
signals: Vec::default(),
description: String::default(),
extra: None,
}
}
}
const IKEY_EXTRA: i32 = (DirAttribute::Signals as i32) + 1;
impl TryFrom<RpcValue> for MethodDescription {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
match value.value {
shvproto::Value::Map(map) => {
Ok(Self {
name: map.get(DirAttribute::Name.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
flags: map.get(DirAttribute::Flags.into()).map(shvproto::RpcValue::as_u32).unwrap_or_default(),
param: map.get(DirAttribute::Param.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
result: map.get(DirAttribute::Result.into()).map(|rv| rv.as_str().to_owned()).unwrap_or_default(),
access: AccessLevel::try_from(
map.get(DirAttribute::AccessLevel.into())
.map(shvproto::RpcValue::as_i32)
.unwrap_or_default()
)
.unwrap_or(AccessLevel::Browse),
signals: {
let signals_map: RpcMap = map.get(DirAttribute::Signals.into()).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
signals_map.into_iter()
.map(|(k,v)| {
(k, v.try_into().ok())
})
.collect()
},
description: map.get(KEY_DESCRIPTION)
.map(|d| d.as_str().to_string())
.unwrap_or_default(),
extra: map.get(KEY_TAGS)
.map(RpcValue::as_map)
.cloned(),
})
}
shvproto::Value::IMap(imap) => {
let extra = imap.get(&IKEY_EXTRA);
Ok(Self {
name: imap.get(&(DirAttribute::Name as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
flags: imap.get(&(DirAttribute::Flags as _)).map(RpcValue::as_u32).unwrap_or_default(),
param: imap.get(&(DirAttribute::Param as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
result: imap.get(&(DirAttribute::Result as _)).map(|rv| rv.as_str().to_string()).unwrap_or_default(),
access: AccessLevel::try_from(
imap.get(&(DirAttribute::AccessLevel as _))
.map(RpcValue::as_i32)
.unwrap_or_default()
)
.unwrap_or(AccessLevel::Browse),
signals: {
let signals_map: RpcMap = imap.get(&(DirAttribute::Signals as _)).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
signals_map.into_iter()
.map(|(k,v)| {
(k, v.try_into().ok())
})
.collect()
},
description: extra
.and_then(|extra| extra
.get("description")?.try_into().ok()
).unwrap_or_default(),
extra: extra.map(RpcValue::as_map).cloned(),
})
}
_ => Err(format!("Unexpected type: {type}", type = value.type_name())),
}
}
}
impl TryFrom<&RpcValue> for MethodDescription {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
value.clone().try_into()
}
}
impl From<MethodDescription> for RpcValue {
fn from(value: MethodDescription) -> Self {
let mut res = shvproto::IMap::new();
res.insert(DirAttribute::Name.into(), value.name.into());
res.insert(DirAttribute::Flags.into(), value.flags.into());
res.insert(DirAttribute::Param.into(), value.param.into());
res.insert(DirAttribute::Result.into(), value.result.into());
res.insert(DirAttribute::AccessLevel.into(), (value.access as i32).into());
res.insert(DirAttribute::Signals.into(), value.signals
.into_iter()
.map(|(name, value)| (name, value.map_or_else(RpcValue::null, RpcValue::from)))
.collect::<BTreeMap<_,_>>()
.into()
);
let mut extra = shvproto::make_map!("description" => value.description);
if let Some(extra_map) = value.extra {
for (k, v) in extra_map {
extra.entry(k).or_insert(v);
}
}
res.insert(IKEY_EXTRA, extra.into());
res.into()
}
}
#[derive(Default, Debug, Clone)]
pub struct DeviceDescription {
properties: Vec<PropertyDescription>,
}
impl DeviceDescription {
pub fn from_rpc_map(map: &RpcMap) -> Self {
Self {
properties: map.get("properties").and_then(|v| v.try_into().ok()).unwrap_or_default()
}
}
pub fn into_rpc_map(self) -> RpcMap {
shvproto::make_map!("properties" => RpcValue::from(self.properties))
}
pub fn find_property(&self, prop_name: impl AsRef<str>) -> Option<&PropertyDescription> {
self.properties.iter().find(|prop| prop.name() == prop_name.as_ref())
}
pub fn find_longest_property_prefix(&self, path: impl AsRef<str>) -> Option<&PropertyDescription> {
let path = path.as_ref();
self.properties
.iter()
.filter(|prop| crate::util::starts_with_path(path, prop.name()))
.max_by_key(|prop| prop.name().len())
}
pub fn set_property_description(&mut self, descr: PropertyDescription) {
let prop_name = descr.name();
if let Some(prop) = self.properties.iter_mut().find(|p| p.name() == prop_name) {
*prop = descr;
} else {
self.properties.push(descr);
}
}
pub fn remove_property_description(&mut self, prop_name: impl AsRef<str>) {
let prop_name = prop_name.as_ref();
self.properties.retain(|prop| prop.name() != prop_name);
}
pub fn properties(&self) -> &[PropertyDescription] {
&self.properties
}
}
impl TryFrom<RpcValue> for DeviceDescription {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
let map: RpcMap = value.try_into()?;
Ok(Self::from_rpc_map(&map))
}
}
impl TryFrom<&RpcValue> for DeviceDescription {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
let shvproto::Value::Map(map) = &value.value else {
return Err(format!("Wrong type, expected a map, have {ty}", ty = value.type_name()))
};
Ok(Self::from_rpc_map(map))
}
}
#[derive(Default, Debug)]
pub struct TypeInfo {
types: BTreeMap<String, TypeDescription>,
device_paths: BTreeMap<String, String>,
device_descriptions: BTreeMap<String, DeviceDescription>,
extra_tags: BTreeMap<String, RpcValue>,
system_paths_roots: BTreeMap<String, String>,
blacklisted_paths: BTreeMap<String, RpcValue>,
property_deviations: BTreeMap<String, PropertyDescription>,
}
#[derive(Default)]
pub struct PathInfo {
pub device_path: String,
pub device_type: String,
pub property_description: PropertyDescription,
pub field_path: String,
}
#[derive(Default)]
pub struct DeviceTypeInfo {
pub device_path: String,
pub device_type: String,
pub property_path: String,
}
fn find_longest_prefix_entry<'a, V>(
map: &'a BTreeMap<String, V>,
path: &'a str,
) -> Option<(&'a str, &'a V)> {
let mut path = path;
loop {
if let Some(val) = map.get(path) {
return Some((path, val));
}
if path.is_empty() {
break;
}
#[expect(clippy::string_slice, reason = "Assuming the path is an ASCII string")]
if let Some(slash_ix) = path.rfind('/') {
path = &path[..slash_ix];
} else {
path = "";
}
}
None
}
impl TypeInfo {
const VERSION: &str = "version";
const PATHS: &str = "paths"; const TYPES: &str = "types"; const DEVICE_PATHS: &str = "devicePaths";
const DEVICE_PROPERTIES: &str = "deviceProperties";
const DEVICE_DESCRIPTIONS: &str = "deviceDescriptions";
const PROPERTY_DEVIATIONS: &str = "propertyDeviations";
const EXTRA_TAGS: &str = "extraTags";
const SYSTEM_PATHS_ROOTS: &str = "systemPathsRoots";
const BLACKLISTED_PATHS: &str = "blacklistedPaths";
pub fn find_type_description(&self, type_name: impl AsRef<str>) -> Option<&TypeDescription> {
self.types.get(type_name.as_ref())
}
pub fn find_property_description(
&self,
device_type: impl AsRef<str>,
property_path: impl AsRef<str>,
) -> Option<(PropertyDescription, String)>
{
if let Some(dev_descr) = self.device_descriptions.get(device_type.as_ref()) {
let property_path = property_path.as_ref();
if let Some(prop_descr) = dev_descr.find_longest_property_prefix(property_path) {
let own_property_path = prop_descr.name();
let field_path = property_path.strip_prefix(own_property_path).unwrap_or_default();
return Some((prop_descr.clone(), field_path.into()));
}
}
None
}
pub fn find_device_type(&self, shv_path: impl AsRef<str>) -> Option<DeviceTypeInfo> {
let shv_path = shv_path.as_ref();
if let Some((device_path, device_type)) = find_longest_prefix_entry(&self.device_paths, shv_path) {
let property_path = if device_path.is_empty() {
shv_path.to_string()
} else {
shv_path.get(device_path.len() + 1 ..).unwrap_or("").to_string()
};
Some(DeviceTypeInfo {
device_path: device_path.into(),
device_type: device_type.into(),
property_path
})
} else {
None
}
}
pub fn path_info(&self, shv_path: impl AsRef<str>) -> PathInfo {
let mut ret = PathInfo::default();
let mut deviation_found = false;
let shv_path = shv_path.as_ref();
if let Some((own_property_path, prop_descr)) = find_longest_prefix_entry(&self.property_deviations, shv_path) {
deviation_found = true;
ret.field_path = shv_path.strip_prefix(own_property_path).unwrap_or_default().into();
ret.property_description = prop_descr.clone();
}
let DeviceTypeInfo { device_path, device_type, property_path } = self.find_device_type(shv_path).unwrap_or_default();
ret.device_path = device_path;
ret.device_type.clone_from(&device_type);
if deviation_found {
if ret.property_description.is_valid() {
let name = shv_path
.strip_prefix(&ret.device_path)
.and_then(|s| s.strip_suffix(&ret.field_path))
.unwrap_or_default();
ret.property_description.set_name(name);
}
} else {
let (property_descr, field_path) = self.find_property_description(&device_type, &property_path).unwrap_or_default();
if property_descr.is_valid() {
ret.field_path = field_path;
ret.property_description = property_descr;
}
}
ret
}
pub fn type_description_for_path(&self, shv_path: impl AsRef<str>) -> Option<&TypeDescription> {
let PathInfo { property_description, .. } = self.path_info(shv_path);
self.find_type_description(property_description.type_name()?)
}
pub fn set_blacklist(&mut self, shv_path: impl AsRef<str>, blacklist: &RpcValue) {
let shv_path = shv_path.as_ref();
match &blacklist.value {
shvproto::Value::List(list) => {
for path in list.as_ref() {
self.blacklisted_paths.insert(join_path(shv_path, path.as_str()), RpcValue::null());
}
}
shvproto::Value::Map(map) => {
for (path, val) in map.as_ref() {
self.blacklisted_paths.insert(join_path(shv_path, path), val.into());
}
}
_ => { }
}
}
pub fn types(&self) -> &BTreeMap<String, TypeDescription> {
&self.types
}
pub fn device_paths(&self) -> &BTreeMap<String, String> {
&self.device_paths
}
pub fn device_descriptions(&self) -> &BTreeMap<String, DeviceDescription> {
&self.device_descriptions
}
pub fn extra_tags(&self) -> &BTreeMap<String, RpcValue> {
&self.extra_tags
}
pub fn system_paths_roots(&self) -> &BTreeMap<String, String> {
&self.system_paths_roots
}
pub fn blacklisted_paths(&self) -> &BTreeMap<String, RpcValue> {
&self.blacklisted_paths
}
pub fn property_deviations(&self) -> &BTreeMap<String, PropertyDescription> {
&self.property_deviations
}
}
impl TryFrom<&RpcValue> for TypeInfo {
type Error = String;
fn try_from(value: &RpcValue) -> Result<Self, Self::Error> {
let version = value.meta
.as_ref()
.and_then(|m| m.get(Self::VERSION).map(RpcValue::as_i64))
.unwrap_or_default();
let map: RpcMap = value.try_into()?;
let mut res = Self::default();
if version == 3 || version == 4 {
res.types = map.get(Self::TYPES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
res.device_paths = map.get(Self::DEVICE_PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
if version == 3 {
let m: RpcMap = map.get(Self::DEVICE_PROPERTIES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
for(device_type, prop_map) in m {
let pm = prop_map.as_map();
let dev_descr = res.device_descriptions.entry(device_type).or_insert_with(DeviceDescription::default);
for (property_path, property_descr_rv) in pm {
let mut property_descr: PropertyDescription = property_descr_rv.try_into().unwrap_or_default();
property_descr.set_name(property_path);
dev_descr.properties.push(property_descr);
}
}
}
else {
res.device_descriptions = map.get(Self::DEVICE_DESCRIPTIONS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
}
res.extra_tags = map.get(Self::EXTRA_TAGS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
res.system_paths_roots = map.get(Self::SYSTEM_PATHS_ROOTS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
res.blacklisted_paths = map.get(Self::BLACKLISTED_PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
res.property_deviations = map.get(Self::PROPERTY_DEVIATIONS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
} else if map.contains_key(Self::PATHS) && map.contains_key(Self::TYPES) {
res.types = map.get(Self::TYPES).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
let dev_descr = res.device_descriptions.entry("".into()).or_insert_with(DeviceDescription::default);
let m: RpcMap = map.get(Self::PATHS).and_then(|rv| rv.try_into().ok()).unwrap_or_default();
for (path, val) in m {
let val_map: RpcMap = val.try_into().unwrap_or_default();
let (mut descr, _) = PropertyDescription::from_rpc_map(val_map.clone());
descr.set_type_name(val_map.get("type").map(RpcValue::as_str).unwrap_or_default());
descr.set_name(path);
dev_descr.properties.push(descr);
}
}
else {
let types: RpcMap = value.meta
.as_ref()
.and_then(|m| m.get("typeInfo"))
.and_then(|ty| ty.as_map().get(Self::TYPES))
.and_then(|rv| rv.try_into().ok())
.unwrap_or_default();
for (name, descr) in types {
let mut descr = TypeDescription::from_rpc_map(descr.try_into().unwrap_or_default());
if descr.type_name().is_none_or(str::is_empty) {
descr.set_type_name(&name);
}
res.types.insert(name, descr);
}
let node_types: RpcMap = value.meta
.as_ref()
.and_then(|m| m.get("nodeTypes"))
.and_then(|rv| rv.try_into().ok())
.unwrap_or_default();
from_nodes_tree_helper(&node_types, value, Default::default(), Default::default(), Default::default(), None, &mut res);
}
if res.system_paths_roots.is_empty() {
res.system_paths_roots.insert("".into(), "system".into());
}
Ok(res)
}
}
impl TryFrom<RpcValue> for TypeInfo {
type Error = String;
fn try_from(value: RpcValue) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
fn from_nodes_tree_helper(
node_types: &RpcMap,
node: &RpcValue,
device_type: &str,
device_path: &str,
property_path: &str,
device_description: Option<&mut DeviceDescription>,
out: &mut TypeInfo,
) {
let node_map: RpcMap = node.try_into().unwrap_or_default();
let meta_map_opt = node.meta.as_ref().map(Box::as_ref);
if node_map.is_empty() && meta_map_opt.is_none_or(MetaMap::is_empty) {
return;
}
if let Some(ref_str) = meta_map_opt.and_then(|m| m.get("nodeTypeRef").map(RpcValue::as_str))
&& !ref_str.is_empty()
&& let Some(ref_node) = node_types.get(ref_str)
{
from_nodes_tree_helper(
node_types,
ref_node,
device_type,
device_path,
property_path,
device_description,
out,
);
return;
}
let mut current_device_type = device_type.to_string();
let mut current_device_path = device_path.to_string();
let mut current_property_path = property_path.to_string();
let mut new_device_type_entered = device_description.is_none();
let mut new_device_description = DeviceDescription::default();
let mut current_device_description = device_description.unwrap_or(&mut new_device_description);
let mut property_descr_map = RpcMap::new();
use shvproto::rpcvalue::List as RpcList;
let mut property_methods = RpcList::new();
if let Some(methods) = meta_map_opt.and_then(|m| m.get(KEY_METHODS).map(RpcValue::as_list)) {
for mm in methods {
let Ok(mm) = MethodDescription::try_from(mm) else {
continue
};
if mm.name == METH_LS || mm.name == METH_DIR {
continue
}
property_methods.push(RpcValue::from(mm.clone()));
if let Some(extra) = mm.extra && !extra.is_empty() {
let key = crate::join_path!(property_path, "method", mm.name);
out.extra_tags.insert(key, extra.into());
}
}
}
let node_tags = meta_map_opt.and_then(|m| m.get(KEY_TAGS).map(RpcValue::as_map).cloned()).unwrap_or_default();
if !node_tags.is_empty() {
if let Some(device_type) = node_tags.get(KEY_DEVICE_TYPE).map(RpcValue::as_str) && !device_type.is_empty() {
current_device_type = device_type.into();
current_device_path = join_path(device_path, property_path);
current_property_path = "".into();
current_device_description = &mut new_device_description;
new_device_type_entered = true;
}
for (k, v) in &node_tags {
property_descr_map.entry(k.into()).or_insert_with(|| v.into());
}
}
property_descr_map.remove("createFromTypeName");
if !property_methods.is_empty() {
property_descr_map.insert(KEY_METHODS.to_string(), property_methods.into());
}
let mut property_descr = PropertyDescription::default();
if !property_descr_map.is_empty() {
let (descr, mut extra_tags) = PropertyDescription::from_rpc_map(property_descr_map);
property_descr = descr;
if property_descr.is_valid() {
property_descr.set_name(¤t_property_path);
current_device_description.properties.push(property_descr.clone());
}
let system_path = extra_tags.remove("systemPath").and_then(|v| String::try_from(v).ok());
if let Some(system_path) = system_path && !system_path.is_empty() {
let shv_path = join_path(¤t_device_path, ¤t_property_path);
out.system_paths_roots.insert(shv_path, system_path);
}
let blacklist = extra_tags.remove(KEY_BLACKLIST);
if let Some(blacklist) = blacklist {
let shv_path = join_path(¤t_device_path, ¤t_property_path);
out.set_blacklist(shv_path, &blacklist);
}
if !extra_tags.is_empty() {
let shv_path = join_path(¤t_device_path, ¤t_property_path);
out.extra_tags.insert(shv_path, extra_tags.into());
}
}
if property_descr.type_name().is_none_or(str::is_empty) && !node_tags.contains_key("createFromTypeName") {
for (child_name, child_node) in &node_map {
if child_name.is_empty() {
continue;
}
let child_property_path = join_path(¤t_property_path, child_name);
from_nodes_tree_helper(node_types, child_node, ¤t_device_type, ¤t_device_path, &child_property_path, Some(current_device_description), out);
}
}
if new_device_type_entered {
out.device_paths.insert(current_device_path.clone(), current_device_type.clone());
if let Some(existing_device_descr) = out.device_descriptions.get_mut(¤t_device_type) {
for existing_property_descr in &existing_device_descr.properties {
if let Some(new_property_descr) = new_device_description.find_property(existing_property_descr.name()) {
if new_property_descr != existing_property_descr {
let shv_path = join_path(¤t_device_path, existing_property_descr.name());
out.property_deviations.insert(shv_path, new_property_descr.clone());
}
let new_property_descr = new_property_descr.clone();
new_device_description.properties.retain(|descr| new_property_descr != *descr);
} else {
let shv_path = join_path(¤t_device_path, existing_property_descr.name());
out.property_deviations.insert(shv_path, PropertyDescription::default());
}
}
for new_property_descr in &new_device_description.properties {
let shv_path = join_path(¤t_device_path, new_property_descr.name());
out.property_deviations.insert(shv_path, new_property_descr.clone());
}
} else {
out.device_descriptions.insert(current_device_type, new_device_description);
}
}
}
#[cfg(test)]
mod tests {
use shvproto::RpcValue;
use crate::typeinfo::{FieldDescriptionMethods, SampleType, Type, TypeDescriptionMethods, TypeInfo};
use std::collections::BTreeMap;
const TYPE_INFO: &str = r#"
<"version":4>{
"deviceDescriptions":{
"device1":{
"properties":[
{
"name":"status1",
"typeName":"BitField"
},
{
"name":"status2",
"typeName":"Map"
},
{
"name":"status3",
"typeName":"Enum"
},
]
},
},
"devicePaths":{
"foo/bar":"device1",
},
"types":{
"BitField":{
"fields":[
{"alarm":"warning", "description":"Alarm 1", "label":"Alarm 1 label", "name":"field1", "value": [0,7] },
{"alarm":"error", "description":"Alarm 2", "label":"Alarm 2 label", "name":"field2", "value": 24 },
{"name":"field3", "value": [25, 26] },
{"stateAlarm":"error", "description":"State alarm 1", "label":"State alarm 1 label", "name":"field4", "value": 27 },
],
"typeName":"BitField"
},
"Map":{
"fields":[
{"description":"Description 1", "label":"Label 1", "name":"mapField1", "typeName":"Int"},
{"description":"Description 2", "label":"Label 2", "name":"mapField2", "typeName":"String"},
],
"typeName":"Map",
"sampleType":"Discrete"
},
"Enum":{
"fields":[
{"description":"", "label":"", "name":"Unknown", "value":0},
{"description":"", "label":"", "name":"Normal", "value":1},
{
"alarm":"warning",
"alarmLevel":50,
"description":"",
"label":"",
"name":"Warning",
"value":2
},
{
"alarm":"error",
"stateAlarm":"error",
"alarmLevel":100,
"description":"",
"label":"",
"name":"Error",
"value":3
}
],
"typeName":"Enum"
},
}
}
"#;
const NODES_TREE: &str = r#"
<
"nodeTypes":{
"Device1@0":<
"tags": {"deviceType": "device1"}
>{
"status1":<
"tags":{"description":"A bitfield", "typeName": "BitField"},
"methods":[
{"access":"rd", "name":"chng", "signature":"VoidParam"},
{"access":"rd", "name":"get", "signature":"RetParam"}
]
>{},
"status2":<
"tags":{"description":"An enum", "typeName": "Enum"},
"methods":[
{"access":"rd", "name":"chng", "signature":"VoidParam"},
{"access":"rd", "name":"get", "signature":"RetParam"}
]
>{},
}
},
"tags":{"systemPath":"system/xyz"},
"typeInfo":{
"types":{
"BitField":{
"fields":[
{"tags":{"alarm":"warning", "description":"Alarm 1", "label":"Alarm 1 label"}, "name":"field1", "value": [0,7] },
{"tags":{"alarm":"error", "description":"Alarm 2", "label":"Alarm 2 label"}, "name":"field2", "value": 24 },
{"name":"field3", "value": [25, 26] },
],
"type":"BitField"
},
"Map":{
"fields":[
{"description":"Description 1", "label":"Label 1", "name":"mapField1", "typeName":"Int"},
{"description":"Description 2", "label":"Label 2", "name":"mapField2", "typeName":"String"},
],
"type":"Map",
},
"Enum":{
"fields":[
{"tags":{"description":"", "label":""}, "name":"Unknown", "value":0},
{"tags":{"description":"", "label":""}, "name":"Normal", "value":1},
{
"tags":{
"alarm":"warning",
"alarmLevel":50,
"description":"",
"label":""
},
"name":"Warning",
"value":2
},
{
"tags":{
"alarm":"error",
"alarmLevel":100,
"description":"",
"label":""
},
"name":"Error",
"value":3
}
],
"type":"Enum"
},
}
}
>{
"foo":{
"bar":<"nodeTypeRef":"Device1@0">{},
"baz":<"nodeTypeRef":"Device1@0">{},
//"bar":<"tags":{"deviceType":"device1"}>{},
//"baz":<"tags":{"deviceType":"device1"}>{},
}
}
"#;
#[test]
fn parse_type_info() {
let rv = RpcValue::from_cpon(TYPE_INFO).unwrap_or_else(|e| panic!("Cannot parse typeInfo: {e}"));
let type_info = TypeInfo::try_from(&rv).unwrap_or_else(|e| panic!("Cannot convert RpcValue to TypeInfo: {e}"));
println!("type info: {type_info:#?}");
let type_descr = type_info.type_description_for_path("foo/bar/status1").unwrap();
assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));
let bitfield_type_descr = type_info.find_type_description("BitField").unwrap();
assert!(bitfield_type_descr.is_valid());
assert!(matches!(bitfield_type_descr.type_id(), Some(Type::BitField)));
assert!(bitfield_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
assert_eq!(bitfield_type_descr.field_value(0xfffa, "field1").unwrap().as_u32(), 0xfa);
assert_eq!(bitfield_type_descr.field_value(0x7fff_ffff, "field2").unwrap().as_u32(), 1);
assert_eq!(bitfield_type_descr.field_value(0x7eff_ffff, "field2").unwrap().as_u32(), 0);
assert_eq!(bitfield_type_descr.field_value(0x1cff_ffff, "field3").unwrap().as_u32(), 2);
assert_eq!(bitfield_type_descr.field("field4").as_ref().and_then(FieldDescriptionMethods::state_alarm), Some("error"));
let map_type_descr = type_info.find_type_description("Map").unwrap();
assert!(matches!(map_type_descr.type_id(), Some(Type::Map)));
assert!(map_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Discrete)));
let vehicle_data: RpcValue = shvproto::make_map!("mapField1" => 123).into();
assert_eq!(map_type_descr.field_value(&vehicle_data, "mapField1").unwrap().as_int(), 123);
assert!(map_type_descr.field_value(&vehicle_data, "noMapField").is_none());
let enum_type_descr = type_info.find_type_description("Enum").unwrap();
assert!(matches!(enum_type_descr.type_id(), Some(Type::Enum)));
assert!(enum_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
assert_eq!(enum_type_descr.field_value((), "Warning").unwrap().as_u32(), 2);
let field_alarms = BTreeMap::from([
("Unknown", (None, None)),
("Normal", (None, None)),
("Warning", (Some("warning"), Some(50))),
("Error", (Some("error"), Some(100))),
]);
for fld in &enum_type_descr.fields() {
assert!(field_alarms.get(fld.name()).is_some_and(|(alarm, level)| alarm == &fld.alarm() && level == &fld.alarm_level()));
}
assert_eq!(enum_type_descr.field("Error").as_ref().and_then(FieldDescriptionMethods::state_alarm), Some("error"));
}
#[test]
fn parse_nodes_tree() {
let rv = RpcValue::from_cpon(NODES_TREE).unwrap_or_else(|e| panic!("Cannot parse typeInfo: {e}"));
let type_info = TypeInfo::try_from(&rv).unwrap_or_else(|e| panic!("Cannot convert RpcValue to TypeInfo: {e}"));
println!("type info from nodes tree: {type_info:#?}");
let type_descr = type_info.type_description_for_path("foo/bar/status1").unwrap();
assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));
let type_descr = type_info.type_description_for_path("foo/baz/status1").unwrap();
assert!(matches!(type_descr.type_id(), Some(Type::BitField)));
assert!(type_descr.field_value(0x1234, "field1").is_some_and(|v| v.as_u32() == 0x34));
let enum_type_descr = type_info.type_description_for_path("foo/baz/status2").unwrap();
assert!(matches!(enum_type_descr.type_id(), Some(Type::Enum)));
assert!(enum_type_descr.sample_type().is_some_and(|st| matches!(st, SampleType::Continuous)));
assert_eq!(enum_type_descr.field_value((), "Warning").unwrap().as_u32(), 2);
let field_alarms = BTreeMap::from([
("Unknown", (None, None)),
("Normal", (None, None)),
("Warning", (Some("warning"), Some(50))),
("Error", (Some("error"), Some(100))),
]);
for fld in &enum_type_descr.fields() {
assert!(field_alarms.get(fld.name()).is_some_and(|(alarm, level)| alarm == &fld.alarm() && level == &fld.alarm_level()));
}
}
}