dbus_message_parser/value/
interface.rs

1use crate::value::MAXIMUM_NAME_LENGTH;
2use std::cmp::{Eq, PartialEq};
3use std::convert::{From, TryFrom};
4use std::fmt::{Display, Formatter, Result as FmtResult};
5use thiserror::Error;
6
7enum Input {
8    /// [A-Z][a-z]_
9    AlphabeticAndUnderscore,
10    /// [0-9]
11    Digit,
12    /// .
13    Dot,
14}
15
16impl TryFrom<u8> for Input {
17    type Error = InterfaceError;
18
19    fn try_from(c: u8) -> Result<Self, Self::Error> {
20        if c.is_ascii_alphabetic() || c == b'_' {
21            Ok(Input::AlphabeticAndUnderscore)
22        } else if c.is_ascii_digit() {
23            Ok(Input::Digit)
24        } else if c == b'.' {
25            Ok(Input::Dot)
26        } else {
27            Err(InterfaceError::InvalidChar(c))
28        }
29    }
30}
31
32enum State {
33    /// The beginning of the first element.
34    FirstElementBegin,
35    /// The second or subsequent character of the first element.
36    FirstElement,
37    /// The beginning of the second or subsequent element.
38    ElementBegin,
39    /// The second or subsequent character of the second or subsequent element.
40    Element,
41}
42
43impl State {
44    #[inline]
45    fn consume(self, i: Input) -> Result<State, InterfaceError> {
46        match self {
47            State::FirstElementBegin => match i {
48                Input::AlphabeticAndUnderscore => Ok(State::FirstElement),
49                Input::Digit => Err(InterfaceError::ElementBeginDigit),
50                Input::Dot => Err(InterfaceError::ElementBeginDot),
51            },
52            State::FirstElement => match i {
53                Input::AlphabeticAndUnderscore => Ok(State::FirstElement),
54                Input::Digit => Ok(State::FirstElement),
55                Input::Dot => Ok(State::ElementBegin),
56            },
57            State::ElementBegin => match i {
58                Input::AlphabeticAndUnderscore => Ok(State::Element),
59                Input::Digit => Err(InterfaceError::ElementBeginDigit),
60                Input::Dot => Err(InterfaceError::ElementBeginDot),
61            },
62            State::Element => match i {
63                Input::AlphabeticAndUnderscore => Ok(State::Element),
64                Input::Digit => Ok(State::Element),
65                Input::Dot => Ok(State::ElementBegin),
66            },
67        }
68    }
69}
70
71/// Check if the given bytes is a valid [interface name].
72///
73/// [interface name]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
74fn check(interface: &[u8]) -> Result<(), InterfaceError> {
75    let interface_len = interface.len();
76    if MAXIMUM_NAME_LENGTH < interface_len {
77        return Err(InterfaceError::ExceedMaximum(interface_len));
78    }
79
80    let mut state = State::FirstElementBegin;
81    for c in interface {
82        let i = Input::try_from(*c)?;
83        state = state.consume(i)?;
84    }
85
86    match state {
87        State::FirstElementBegin => Err(InterfaceError::Empty),
88        State::FirstElement => Err(InterfaceError::Elements),
89        State::ElementBegin => Err(InterfaceError::EndDot),
90        State::Element => Ok(()),
91    }
92}
93
94/// This represents an [interface name].
95///
96/// [interface name]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
97#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
98pub struct Interface(String);
99
100/// An enum representing all errors, which can occur during the handling of a [`Interface`].
101#[derive(Debug, PartialEq, Eq, Error)]
102pub enum InterfaceError {
103    #[error("Interface element must not begin with a digit")]
104    ElementBeginDigit,
105    #[error("Interface element must not beign with a '.'")]
106    ElementBeginDot,
107    #[error("Interface must not end with '.'")]
108    EndDot,
109    #[error("Interface must not be empty")]
110    Empty,
111    #[error("Interface have to be composed of 2 or more elements")]
112    Elements,
113    #[error("Interface must not exceed the maximum length: {MAXIMUM_NAME_LENGTH} < {0}")]
114    ExceedMaximum(usize),
115    #[error("Interface must only contain '[A-Z][a-z][0-9]_.': {0}")]
116    InvalidChar(u8),
117}
118
119impl From<Interface> for String {
120    fn from(interface: Interface) -> Self {
121        interface.0
122    }
123}
124
125impl TryFrom<String> for Interface {
126    type Error = InterfaceError;
127
128    fn try_from(interface: String) -> Result<Self, Self::Error> {
129        check(interface.as_bytes())?;
130        Ok(Interface(interface))
131    }
132}
133
134impl TryFrom<&str> for Interface {
135    type Error = InterfaceError;
136
137    fn try_from(interface: &str) -> Result<Self, Self::Error> {
138        check(interface.as_bytes())?;
139        Ok(Interface(interface.to_owned()))
140    }
141}
142
143impl TryFrom<&[u8]> for Interface {
144    type Error = InterfaceError;
145
146    fn try_from(interface: &[u8]) -> Result<Self, Self::Error> {
147        check(interface)?;
148        let interface = interface.to_vec();
149        //  The vector only contains valid UTF-8 (ASCII) characters because it was already
150        //  checked by the `check` function above
151        unsafe { Ok(Interface(String::from_utf8_unchecked(interface))) }
152    }
153}
154
155impl Display for Interface {
156    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
157        write!(f, "{}", self.0)
158    }
159}
160
161impl AsRef<str> for Interface {
162    fn as_ref(&self) -> &str {
163        &self.0
164    }
165}
166
167impl PartialEq<str> for Interface {
168    fn eq(&self, other: &str) -> bool {
169        self.as_ref() == other
170    }
171}