#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/mccs-db/0.2.0")]
use {
mccs::{Capabilities, FeatureCode, Value, ValueNames, Version},
serde::{Deserialize, Serialize},
std::{collections::BTreeMap, io, mem},
};
#[cfg(test)]
#[path = "../../caps/src/testdata.rs"]
mod testdata;
#[rustfmt::skip::macros(named)]
mod version_req;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TableInterpretation {
Generic,
CodePage,
}
impl Default for TableInterpretation {
fn default() -> Self {
TableInterpretation::Generic
}
}
impl TableInterpretation {
pub fn format(&self, table: &[u8]) -> Result<String, ()> {
Ok(match *self {
TableInterpretation::Generic => format!("{:?}", table),
TableInterpretation::CodePage =>
if let Some(v) = table.get(0) {
format!("{v}")
} else {
return Err(())
},
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ValueInterpretation {
Continuous,
NonContinuous,
NonZeroWrite,
VcpVersion,
}
impl ValueInterpretation {
pub fn format(&self, value: &Value) -> String {
match *self {
ValueInterpretation::Continuous => format!("{} / {}", value.value(), value.maximum()),
ValueInterpretation::NonContinuous => {
let v16 = match value.value() {
v16 if v16 > value.maximum() => v16 & 0x00ff,
v16 => v16,
};
format!("{v16}")
},
ValueInterpretation::NonZeroWrite => if value.sl == 0 { "unset" } else { "set" }.into(),
ValueInterpretation::VcpVersion => format!("{}", Version::new(value.sh, value.sl)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ValueType {
Unknown,
Continuous {
interpretation: ValueInterpretation,
},
NonContinuous {
values: ValueNames,
interpretation: ValueInterpretation,
},
Table {
interpretation: TableInterpretation,
},
}
impl Default for ValueType {
fn default() -> Self {
ValueType::Unknown
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Access {
ReadOnly,
WriteOnly,
ReadWrite,
}
impl Default for Access {
fn default() -> Self {
Access::ReadWrite
}
}
#[derive(Debug, Default, Clone)]
pub struct Descriptor {
pub name: Option<String>,
pub description: Option<String>,
pub group: Option<String>,
pub code: FeatureCode,
pub ty: ValueType,
pub access: Access,
pub mandatory: bool,
pub interacts_with: Vec<FeatureCode>,
}
#[derive(Debug, Clone, Default)]
pub struct Database {
entries: BTreeMap<FeatureCode, Descriptor>,
}
impl Database {
fn apply_database(&mut self, db: DatabaseFile, mccs_version: &Version) -> io::Result<()> {
for code in db.vcp_features {
if !code.version.matches(mccs_version) {
continue
}
let entry = self.entries.entry(code.code).or_insert_with(|| Descriptor::default());
entry.code = code.code;
if let Some(name) = code.name {
entry.name = Some(name);
}
if let Some(desc) = code.desc {
entry.description = Some(desc);
}
if let Some(group) = code.group {
entry.group = db.groups.iter().find(|g| g.id == group).map(|g| g.name.clone());
}
if let Some(ty) = code.ty {
entry.ty = match (ty, code.interpretation) {
(DatabaseType::Table, None) => ValueType::Table {
interpretation: TableInterpretation::Generic,
},
(DatabaseType::Table, Some(DatabaseInterpretation::Values(..))) =>
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"table type cannot have value names",
)),
(DatabaseType::Table, Some(DatabaseInterpretation::Id(id))) => ValueType::Table {
interpretation: match id {
DatabaseInterpretationId::CodePage => TableInterpretation::CodePage,
id =>
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid interpretation {:?} for table", id),
)),
},
},
(DatabaseType::Continuous, ..) => ValueType::Continuous {
interpretation: ValueInterpretation::Continuous,
},
(DatabaseType::NonContinuous, None) => ValueType::NonContinuous {
values: Default::default(),
interpretation: ValueInterpretation::NonContinuous,
},
(DatabaseType::NonContinuous, Some(DatabaseInterpretation::Values(values))) =>
ValueType::NonContinuous {
values: values
.into_iter()
.flat_map(|v| {
let mut name = Some(v.name);
let dbv = v.value;
let (opti, range) = match dbv {
DatabaseValue::Value(value) => (Some((value, name.take())), None),
DatabaseValue::Range(version_req::Req::Eq(value)) =>
(Some((value, name.take())), None),
DatabaseValue::Range(range) => (None, Some(range)),
};
opti.into_iter().chain(
range
.map(move |range| {
(0..=0xff)
.filter(move |value| range.matches(value))
.map(move |value| (value, name.clone()))
})
.into_iter()
.flat_map(|i| i),
)
})
.collect(),
interpretation: ValueInterpretation::NonContinuous,
},
(DatabaseType::NonContinuous, Some(DatabaseInterpretation::Id(id))) => ValueType::NonContinuous {
values: Default::default(),
interpretation: match id {
DatabaseInterpretationId::NonZeroWrite => ValueInterpretation::NonZeroWrite,
DatabaseInterpretationId::VcpVersion => ValueInterpretation::VcpVersion,
id =>
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid interpretation {:?} for nc", id),
)),
},
},
}
}
entry.mandatory |= code.mandatory;
if let Some(access) = code.access {
entry.access = match access {
DatabaseReadWrite::ReadOnly => Access::ReadOnly,
DatabaseReadWrite::WriteOnly => Access::WriteOnly,
DatabaseReadWrite::ReadWrite => Access::ReadWrite,
};
}
entry.interacts_with.extend(code.interacts_with);
}
Ok(())
}
fn mccs_database() -> DatabaseFile {
let data = include_bytes!("../data/mccs.yml");
serde_yaml::from_slice(data).unwrap()
}
pub fn from_version(mccs_version: &Version) -> Self {
let mut s = Self::default();
s.apply_database(Self::mccs_database(), mccs_version).unwrap();
s
}
pub fn from_database<R: io::Read>(database_yaml: R, mccs_version: &Version) -> io::Result<Self> {
let db = serde_yaml::from_reader(database_yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let mut s = Self::default();
s.apply_database(db, mccs_version)?;
Ok(s)
}
pub fn apply_capabilities(&mut self, caps: &Capabilities) {
let mut entries = mem::replace(&mut self.entries, Default::default());
self.entries.extend(
caps.vcp_features
.iter()
.map(|(code, desc)| match (entries.remove(code), *code, desc) {
(Some(mut mccs), code, cap) => {
if let Some(ref name) = cap.name {
mccs.name = Some(name.clone());
}
if let ValueType::NonContinuous { ref mut values, .. } = mccs.ty {
let mut full = mem::replace(values, Default::default());
values.extend(cap.values.iter().map(|(&value, caps_name)| match full.remove(&value) {
Some(name) => (value, caps_name.clone().or(name)),
None => (value, caps_name.clone()),
}));
}
(code, mccs)
},
(None, code, cap) => {
let desc = Descriptor {
name: cap.name.clone(),
description: None,
group: None,
code,
ty: if cap.values.is_empty() {
ValueType::Continuous {
interpretation: ValueInterpretation::Continuous,
}
} else {
ValueType::NonContinuous {
interpretation: ValueInterpretation::NonContinuous,
values: cap.values.clone(),
}
},
access: Access::ReadWrite,
mandatory: false,
interacts_with: Vec::new(),
};
(code, desc)
},
}),
);
}
pub fn get(&self, code: FeatureCode) -> Option<&Descriptor> {
self.entries.get(&code)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
struct DatabaseGroup {
id: String,
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum DatabaseType {
Table,
#[serde(rename = "nc")]
NonContinuous,
#[serde(rename = "c")]
Continuous,
}
#[derive(Debug, Serialize, Deserialize)]
enum DatabaseReadWrite {
#[serde(rename = "r")]
ReadOnly,
#[serde(rename = "w")]
WriteOnly,
#[serde(rename = "rw")]
ReadWrite,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum DatabaseValue {
Value(u8),
Range(version_req::Req<FeatureCode>),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
struct DatabaseValueDesc {
value: DatabaseValue,
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
desc: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
desc_long: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum DatabaseInterpretationId {
CodePage,
NonZeroWrite,
VcpVersion,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum DatabaseInterpretation {
Id(DatabaseInterpretationId),
Values(Vec<DatabaseValueDesc>),
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
struct DatabaseFeature {
code: FeatureCode,
#[serde(default)]
version: version_req::VersionReq,
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
desc: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
group: Option<String>,
#[serde(rename = "type")]
ty: Option<DatabaseType>,
#[serde(default)]
interpretation: Option<DatabaseInterpretation>,
#[serde(default)]
mandatory: bool,
access: Option<DatabaseReadWrite>,
#[serde(default, skip_serializing_if = "Option::is_none")]
desc_long: Option<String>,
#[serde(default, rename = "interacts")]
interacts_with: Vec<FeatureCode>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
struct DatabaseFile {
groups: Vec<DatabaseGroup>,
vcp_features: Vec<DatabaseFeature>,
}
#[test]
fn load_database() {
for version in &[
Version::new(2, 0),
Version::new(2, 1),
Version::new(2, 2),
Version::new(3, 0),
] {
let db = Database::from_version(version);
for sample in testdata::test_data() {
let caps = mccs_caps::parse_capabilities(sample).expect("Failed to parse capabilities");
let mut db = db.clone();
db.apply_capabilities(&caps);
println!("Intersected: {:#?}", db);
}
}
}