mccs_caps/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/mccs-caps/0.2.0")]
3
4//! MCCS compliant displays will report their supported capabilities in a string
5//! retrieved over DDC/CI. The format of this string is specified in the DDC
6//! specification, MCCS, and ACCESS.bus section 7. This crate parses the
7//! capability string into structured data.
8
9pub use self::{
10    caps::{Cap, Vcp, VcpName, VcpValue},
11    entries::ValueParser,
12};
13use {
14    mccs::{Capabilities, UnknownData, UnknownTag, VcpDescriptor, Version},
15    nom::Finish,
16    std::{fmt, io, str},
17};
18
19#[cfg(test)]
20mod testdata;
21
22#[allow(missing_docs)]
23mod caps;
24#[allow(missing_docs)]
25mod entries;
26
27/// Parses a MCCS capability string.
28pub fn parse_capabilities<C: AsRef<[u8]>>(capability_string: C) -> io::Result<Capabilities> {
29    let capability_string = capability_string.as_ref();
30    let entries = Value::parse_capabilities(capability_string);
31
32    // TODO: check for multiple tags of anything only allowed once?
33
34    let mut caps = Capabilities::default();
35    let mut vcpnames = Vec::new();
36    for cap in Cap::parse_entries(entries) {
37        match cap? {
38            Cap::Protocol(protocol) => caps.protocol = Some(protocol.into()),
39            Cap::Type(ty) => caps.ty = Some(ty.into()),
40            Cap::Model(model) => caps.model = Some(model.into()),
41            Cap::Commands(ref cmds) => caps.commands = cmds.clone(),
42            Cap::Whql(whql) => caps.ms_whql = Some(whql),
43            Cap::MccsVersion(major, minor) => caps.mccs_version = Some(Version::new(major, minor)),
44            Cap::Vcp(ref vcp) =>
45                for Vcp {
46                    feature: code,
47                    ref values,
48                } in vcp
49                {
50                    caps.vcp_features
51                        .entry(*code)
52                        .or_insert_with(|| VcpDescriptor::default())
53                        .values
54                        .extend(values.iter().flat_map(|i| i).map(|v| (v.value, None)))
55                },
56            Cap::VcpNames(v) => vcpnames.extend(v), // wait until after processing vcp() section
57            Cap::Unknown(value) => caps.unknown_tags.push(UnknownTag {
58                name: value.tag().into(),
59                data: match value {
60                    Value::String { value, .. } => match str::from_utf8(value) {
61                        Ok(value) => UnknownData::String(value.into()),
62                        Err(..) => UnknownData::StringBytes(value.into()),
63                    },
64                    Value::Binary { data, .. } => UnknownData::Binary(data.into()),
65                },
66            }),
67            Cap::Edid(edid) => caps.edid = Some(edid.into()),
68            Cap::Vdif(vdif) => caps.vdif.push(vdif.into()),
69        }
70    }
71
72    for VcpName {
73        feature: code,
74        name,
75        value_names,
76    } in vcpnames
77    {
78        if let Some(vcp) = caps.vcp_features.get_mut(&code) {
79            if let Some(name) = name {
80                vcp.name = Some(name.into())
81            }
82
83            if let Some(value_names) = value_names {
84                for ((_, dest), name) in vcp.values.iter_mut().zip(value_names) {
85                    *dest = Some(name.into())
86                }
87            }
88        } else {
89            // TODO: should this be an error if it wasn't specified in vcp()?
90        }
91    }
92
93    Ok(caps)
94}
95
96/// An entry from a capability string
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98pub enum Value<'i> {
99    /// A normal string
100    String {
101        /// The value name
102        tag: &'i str,
103        /// String contents
104        value: &'i [u8],
105    },
106    /// Raw binary data
107    Binary {
108        /// The value name
109        tag: &'i str,
110        /// Data contents
111        data: &'i [u8],
112    },
113}
114
115impl<'i> Value<'i> {
116    /// Create a new iterator over the values in a capability string
117    pub fn parse_capabilities(capability_string: &'i [u8]) -> ValueParser<'i> {
118        ValueParser::new(capability_string)
119    }
120
121    /// Parse a single capability string entry
122    pub fn parse(data: &'i str) -> io::Result<Self> {
123        Self::parse_bytes(data.as_bytes())
124    }
125
126    /// Parse a single capability string entry
127    pub fn parse_bytes(data: &'i [u8]) -> io::Result<Self> {
128        Self::parse_nom(data, None).finish().map(|(_, v)| v).map_err(map_err)
129    }
130
131    /// The value name
132    pub fn tag(&self) -> &'i str {
133        match *self {
134            Value::String { tag, .. } => tag,
135            Value::Binary { tag, .. } => tag,
136        }
137    }
138}
139
140impl From<Value<'_>> for UnknownTag {
141    fn from(v: Value) -> Self {
142        UnknownTag {
143            name: v.tag().into(),
144            data: match v {
145                Value::Binary { data, .. } => UnknownData::Binary(data.into()),
146                Value::String { value, .. } => match str::from_utf8(value) {
147                    Ok(value) => UnknownData::String(value.into()),
148                    Err(_) => UnknownData::StringBytes(value.into()),
149                },
150            },
151        }
152    }
153}
154
155impl<'a> From<&'a UnknownTag> for Value<'a> {
156    fn from(v: &'a UnknownTag) -> Self {
157        let tag = &v.name;
158        match &v.data {
159            UnknownData::Binary(data) => Value::Binary { tag, data },
160            UnknownData::StringBytes(value) => Value::String { tag, value },
161            UnknownData::String(value) => Value::String {
162                tag,
163                value: value.as_bytes(),
164            },
165        }
166    }
167}
168
169impl<'i> fmt::Display for Value<'i> {
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        match self {
172            Value::String { tag, value } => write!(f, "{tag}({})", value.escape_ascii()),
173            Value::Binary { tag, data } => write!(f, "{tag} bin({}({}))", data.len(), data.escape_ascii()),
174        }
175    }
176}
177
178pub(crate) type OResult<'i, O> = Result<O, nom::error::Error<&'i [u8]>>;
179pub(crate) type OResultI<'i, O> = Result<O, nom::Err<nom::error::Error<&'i [u8]>>>;
180
181pub(crate) fn map_err(e: nom::error::Error<&[u8]>) -> io::Error {
182    use nom::error::{Error, ErrorKind};
183
184    io::Error::new(
185        match e.code {
186            ErrorKind::Eof | ErrorKind::Complete => io::ErrorKind::UnexpectedEof,
187            _ => io::ErrorKind::InvalidData,
188        },
189        Error {
190            input: e.input.escape_ascii().to_string(),
191            code: e.code,
192        },
193    )
194}
195
196pub(crate) fn trim_spaces<I, O, E, P>(parser: P) -> impl FnMut(I) -> nom::IResult<I, O, E>
197where
198    P: nom::Parser<I, O, E>,
199    E: nom::error::ParseError<I>,
200    I: Clone + nom::InputTakeAtPosition,
201    <I as nom::InputTakeAtPosition>::Item: nom::AsChar + Clone,
202{
203    use nom::{character::complete::space0, sequence::delimited};
204
205    delimited(space0, parser, space0)
206}
207
208pub(crate) fn bracketed<I, O, E, P>(parser: P) -> impl FnMut(I) -> nom::IResult<I, O, E>
209where
210    P: nom::Parser<I, O, E>,
211    E: nom::error::ParseError<I>,
212    I: Clone + nom::Slice<std::ops::RangeFrom<usize>> + nom::InputIter,
213    <I as nom::InputIter>::Item: nom::AsChar,
214{
215    use nom::{character::complete::char, sequence::delimited};
216
217    delimited(char('('), parser, char(')'))
218}
219
220#[test]
221fn samples_entries() {
222    for sample in testdata::test_data() {
223        println!("Parsing caps: {}", String::from_utf8_lossy(sample));
224        for cap in Value::parse_capabilities(sample).nom_iter() {
225            println!("entry: {:?}", cap.unwrap());
226        }
227    }
228}
229
230#[test]
231fn samples_caps() {
232    for sample in testdata::test_data() {
233        println!("Parsing caps: {}", String::from_utf8_lossy(sample));
234        let ent = Value::parse_capabilities(sample);
235        for (cap, end) in Cap::parse_entries(ent.clone()).zip(ent) {
236            println!("{}", end.unwrap());
237            println!("{:?}", cap.unwrap());
238        }
239    }
240}
241
242#[test]
243fn samples_high() {
244    for sample in testdata::test_data() {
245        let caps = parse_capabilities(sample).expect("Failed to parse capabilities");
246        println!("Caps: {:#?}", caps);
247    }
248}