1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/mccs-db/0.2.0")]
3
4use {
28 mccs::{Capabilities, FeatureCode, Value, ValueNames, Version},
29 serde::{Deserialize, Serialize},
30 std::{collections::BTreeMap, io, mem},
31};
32
33#[cfg(test)]
34#[path = "../../caps/src/testdata.rs"]
35mod testdata;
36
37#[rustfmt::skip::macros(named)]
38mod version_req;
39
40#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub enum TableInterpretation {
43 Generic,
45 CodePage,
51}
52
53impl Default for TableInterpretation {
54 fn default() -> Self {
55 TableInterpretation::Generic
56 }
57}
58
59impl TableInterpretation {
60 pub fn format(&self, table: &[u8]) -> Result<String, ()> {
65 Ok(match *self {
66 TableInterpretation::Generic => format!("{:?}", table),
67 TableInterpretation::CodePage =>
68 if let Some(v) = table.get(0) {
69 format!("{v}")
70 } else {
71 return Err(())
72 },
73 })
74 }
75}
76
77#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub enum ValueInterpretation {
80 Continuous,
82 NonContinuous,
84 NonZeroWrite,
86 VcpVersion,
88}
89
90impl ValueInterpretation {
91 pub fn format(&self, value: &Value) -> String {
93 match *self {
94 ValueInterpretation::Continuous => format!("{} / {}", value.value(), value.maximum()),
95 ValueInterpretation::NonContinuous => {
96 let v16 = match value.value() {
97 v16 if v16 > value.maximum() => v16 & 0x00ff,
100 v16 => v16,
101 };
102 format!("{v16}")
103 },
104 ValueInterpretation::NonZeroWrite => if value.sl == 0 { "unset" } else { "set" }.into(),
105 ValueInterpretation::VcpVersion => format!("{}", Version::new(value.sh, value.sl)),
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
112pub enum ValueType {
113 Unknown,
115 Continuous {
117 interpretation: ValueInterpretation,
119 },
120 NonContinuous {
122 values: ValueNames,
125 interpretation: ValueInterpretation,
127 },
128 Table {
130 interpretation: TableInterpretation,
132 },
133}
134
135impl Default for ValueType {
136 fn default() -> Self {
137 ValueType::Unknown
138 }
139}
140
141#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
143pub enum Access {
144 ReadOnly,
147 WriteOnly,
149 ReadWrite,
151}
152
153impl Default for Access {
154 fn default() -> Self {
155 Access::ReadWrite
156 }
157}
158
159#[derive(Debug, Default, Clone)]
161pub struct Descriptor {
162 pub name: Option<String>,
164 pub description: Option<String>,
166 pub group: Option<String>,
168 pub code: FeatureCode,
170 pub ty: ValueType,
172 pub access: Access,
174 pub mandatory: bool,
177 pub interacts_with: Vec<FeatureCode>,
182}
183
184#[derive(Debug, Clone, Default)]
187pub struct Database {
188 entries: BTreeMap<FeatureCode, Descriptor>,
189}
190
191impl Database {
192 fn apply_database(&mut self, db: DatabaseFile, mccs_version: &Version) -> io::Result<()> {
193 for code in db.vcp_features {
194 if !code.version.matches(mccs_version) {
195 continue
196 }
197
198 let entry = self.entries.entry(code.code).or_insert_with(|| Descriptor::default());
199
200 entry.code = code.code;
201 if let Some(name) = code.name {
202 entry.name = Some(name);
203 }
204 if let Some(desc) = code.desc {
205 entry.description = Some(desc);
206 }
207 if let Some(group) = code.group {
208 entry.group = db.groups.iter().find(|g| g.id == group).map(|g| g.name.clone());
209 }
210 if let Some(ty) = code.ty {
211 entry.ty = match (ty, code.interpretation) {
212 (DatabaseType::Table, None) => ValueType::Table {
213 interpretation: TableInterpretation::Generic,
214 },
215 (DatabaseType::Table, Some(DatabaseInterpretation::Values(..))) =>
216 return Err(io::Error::new(
217 io::ErrorKind::InvalidData,
218 "table type cannot have value names",
219 )),
220 (DatabaseType::Table, Some(DatabaseInterpretation::Id(id))) => ValueType::Table {
221 interpretation: match id {
222 DatabaseInterpretationId::CodePage => TableInterpretation::CodePage,
223 id =>
224 return Err(io::Error::new(
225 io::ErrorKind::InvalidData,
226 format!("invalid interpretation {:?} for table", id),
227 )),
228 },
229 },
230 (DatabaseType::Continuous, ..) => ValueType::Continuous {
231 interpretation: ValueInterpretation::Continuous,
232 },
233 (DatabaseType::NonContinuous, None) => ValueType::NonContinuous {
234 values: Default::default(),
235 interpretation: ValueInterpretation::NonContinuous,
236 },
237 (DatabaseType::NonContinuous, Some(DatabaseInterpretation::Values(values))) =>
238 ValueType::NonContinuous {
239 values: values
240 .into_iter()
241 .flat_map(|v| {
242 let mut name = Some(v.name);
243 let dbv = v.value;
244 let (opti, range) = match dbv {
245 DatabaseValue::Value(value) => (Some((value, name.take())), None),
246 DatabaseValue::Range(version_req::Req::Eq(value)) =>
247 (Some((value, name.take())), None),
248 DatabaseValue::Range(range) => (None, Some(range)),
249 };
250 opti.into_iter().chain(
251 range
252 .map(move |range| {
253 (0..=0xff)
254 .filter(move |value| range.matches(value))
255 .map(move |value| (value, name.clone()))
256 })
257 .into_iter()
258 .flat_map(|i| i),
259 )
260 })
261 .collect(),
262 interpretation: ValueInterpretation::NonContinuous,
263 },
264 (DatabaseType::NonContinuous, Some(DatabaseInterpretation::Id(id))) => ValueType::NonContinuous {
265 values: Default::default(),
266 interpretation: match id {
267 DatabaseInterpretationId::NonZeroWrite => ValueInterpretation::NonZeroWrite,
268 DatabaseInterpretationId::VcpVersion => ValueInterpretation::VcpVersion,
269 id =>
270 return Err(io::Error::new(
271 io::ErrorKind::InvalidData,
272 format!("invalid interpretation {:?} for nc", id),
273 )),
274 },
275 },
276 }
277 }
278 entry.mandatory |= code.mandatory;
279 if let Some(access) = code.access {
280 entry.access = match access {
281 DatabaseReadWrite::ReadOnly => Access::ReadOnly,
282 DatabaseReadWrite::WriteOnly => Access::WriteOnly,
283 DatabaseReadWrite::ReadWrite => Access::ReadWrite,
284 };
285 }
286 entry.interacts_with.extend(code.interacts_with);
287 }
288
289 Ok(())
290 }
291
292 fn mccs_database() -> DatabaseFile {
293 let data = include_bytes!("../data/mccs.yml");
294 serde_yaml::from_slice(data).unwrap()
295 }
296
297 pub fn from_version(mccs_version: &Version) -> Self {
299 let mut s = Self::default();
300 s.apply_database(Self::mccs_database(), mccs_version).unwrap();
301 s
302 }
303
304 pub fn from_database<R: io::Read>(database_yaml: R, mccs_version: &Version) -> io::Result<Self> {
309 let db = serde_yaml::from_reader(database_yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
310
311 let mut s = Self::default();
312 s.apply_database(db, mccs_version)?;
313 Ok(s)
314 }
315
316 pub fn apply_capabilities(&mut self, caps: &Capabilities) {
319 let mut entries = mem::replace(&mut self.entries, Default::default());
320 self.entries.extend(
321 caps.vcp_features
322 .iter()
323 .map(|(code, desc)| match (entries.remove(code), *code, desc) {
324 (Some(mut mccs), code, cap) => {
325 if let Some(ref name) = cap.name {
326 mccs.name = Some(name.clone());
327 }
328
329 if let ValueType::NonContinuous { ref mut values, .. } = mccs.ty {
330 let mut full = mem::replace(values, Default::default());
331 values.extend(cap.values.iter().map(|(&value, caps_name)| match full.remove(&value) {
332 Some(name) => (value, caps_name.clone().or(name)),
333 None => (value, caps_name.clone()),
334 }));
335 }
336
337 (code, mccs)
338 },
339 (None, code, cap) => {
340 let desc = Descriptor {
341 name: cap.name.clone(),
342 description: None,
343 group: None,
344 code,
345 ty: if cap.values.is_empty() {
346 ValueType::Continuous {
347 interpretation: ValueInterpretation::Continuous,
348 }
349 } else {
350 ValueType::NonContinuous {
351 interpretation: ValueInterpretation::NonContinuous,
352 values: cap.values.clone(),
353 }
354 },
355 access: Access::ReadWrite,
356 mandatory: false,
357 interacts_with: Vec::new(),
358 };
359
360 (code, desc)
361 },
362 }),
363 );
364 }
365
366 pub fn get(&self, code: FeatureCode) -> Option<&Descriptor> {
368 self.entries.get(&code)
369 }
370}
371
372#[derive(Debug, Serialize, Deserialize)]
373#[serde(rename_all = "snake_case", deny_unknown_fields)]
374struct DatabaseGroup {
375 id: String,
376 name: String,
377}
378
379#[derive(Debug, Serialize, Deserialize)]
380#[serde(rename_all = "snake_case")]
381enum DatabaseType {
382 Table,
383 #[serde(rename = "nc")]
384 NonContinuous,
385 #[serde(rename = "c")]
386 Continuous,
387}
388
389#[derive(Debug, Serialize, Deserialize)]
390enum DatabaseReadWrite {
391 #[serde(rename = "r")]
392 ReadOnly,
393 #[serde(rename = "w")]
394 WriteOnly,
395 #[serde(rename = "rw")]
396 ReadWrite,
397}
398
399#[derive(Debug, Serialize, Deserialize)]
400#[serde(untagged)]
401enum DatabaseValue {
402 Value(u8),
403 Range(version_req::Req<FeatureCode>),
404}
405
406#[derive(Debug, Serialize, Deserialize)]
407#[serde(rename_all = "snake_case", deny_unknown_fields)]
408struct DatabaseValueDesc {
409 value: DatabaseValue,
410 name: String,
411 #[serde(default, skip_serializing_if = "Option::is_none")]
412 desc: Option<String>,
413 #[serde(default, skip_serializing_if = "Option::is_none")]
414 desc_long: Option<String>,
415}
416#[derive(Debug, Deserialize)]
417#[serde(rename_all = "lowercase")]
418enum DatabaseInterpretationId {
419 CodePage,
420 NonZeroWrite,
421 VcpVersion,
422}
423
424#[derive(Debug, Deserialize)]
425#[serde(untagged)]
426enum DatabaseInterpretation {
427 Id(DatabaseInterpretationId),
428 Values(Vec<DatabaseValueDesc>),
429}
430
431#[derive(Debug, Deserialize)]
432#[serde(rename_all = "snake_case", deny_unknown_fields)]
433struct DatabaseFeature {
434 code: FeatureCode,
435 #[serde(default)]
436 version: version_req::VersionReq,
437 name: Option<String>,
438 #[serde(default, skip_serializing_if = "Option::is_none")]
439 desc: Option<String>,
440 #[serde(default, skip_serializing_if = "Option::is_none")]
441 group: Option<String>,
442 #[serde(rename = "type")]
443 ty: Option<DatabaseType>,
444 #[serde(default)]
445 interpretation: Option<DatabaseInterpretation>,
446 #[serde(default)]
447 mandatory: bool,
448 access: Option<DatabaseReadWrite>,
449 #[serde(default, skip_serializing_if = "Option::is_none")]
450 desc_long: Option<String>,
451 #[serde(default, rename = "interacts")]
452 interacts_with: Vec<FeatureCode>,
453}
454
455#[derive(Debug, Deserialize)]
456#[serde(rename_all = "snake_case", deny_unknown_fields)]
457struct DatabaseFile {
458 groups: Vec<DatabaseGroup>,
459 vcp_features: Vec<DatabaseFeature>,
460}
461
462#[test]
463fn load_database() {
464 for version in &[
465 Version::new(2, 0),
466 Version::new(2, 1),
467 Version::new(2, 2),
468 Version::new(3, 0),
469 ] {
470 let db = Database::from_version(version);
471 for sample in testdata::test_data() {
472 let caps = mccs_caps::parse_capabilities(sample).expect("Failed to parse capabilities");
473 let mut db = db.clone();
474 db.apply_capabilities(&caps);
475 println!("Intersected: {:#?}", db);
476 }
477 }
478}