drogue_client/registry/v1/data/device/
mod.rs

1use crate::{
2    attribute, dialect,
3    meta::v1::{CommonMetadata, CommonMetadataMut, ScopedMetadata},
4    serde::{is_default, Base64Standard},
5    translator, Dialect, Section, Translator,
6};
7use chrono::{DateTime, Utc};
8use core::fmt::{self, Formatter};
9use serde::{de::MapAccess, Deserialize, Deserializer, Serialize};
10use serde_json::{Map, Value};
11use std::{cmp::Ordering, collections::HashMap};
12
13/// A device.
14#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
15pub struct Device {
16    pub metadata: ScopedMetadata,
17    #[serde(default)]
18    #[serde(skip_serializing_if = "Map::is_empty")]
19    pub spec: Map<String, Value>,
20    #[serde(default)]
21    #[serde(skip_serializing_if = "Map::is_empty")]
22    pub status: Map<String, Value>,
23}
24
25translator!(Device);
26
27impl AsRef<dyn CommonMetadata> for Device {
28    fn as_ref(&self) -> &(dyn CommonMetadata + 'static) {
29        &self.metadata
30    }
31}
32
33impl AsMut<dyn CommonMetadataMut> for Device {
34    fn as_mut(&mut self) -> &mut (dyn CommonMetadataMut + 'static) {
35        &mut self.metadata
36    }
37}
38
39impl Device {
40    /// Validate if a device is enabled
41    pub fn validate_device(&self) -> bool {
42        match self.section::<DeviceSpecCore>() {
43            // found "core", decoded successfully -> check
44            Some(Ok(core)) => {
45                if core.disabled {
46                    return false;
47                }
48            }
49            // found "core", but could not decode -> fail
50            Some(Err(_)) => {
51                return false;
52            }
53            // no "core" section
54            _ => {}
55        };
56
57        // done
58        true
59    }
60
61    /// Create an minimal device object from the an application name and a device name
62    pub fn new<A, D>(application: A, device: D) -> Self
63    where
64        A: AsRef<str>,
65        D: AsRef<str>,
66    {
67        Device {
68            metadata: ScopedMetadata {
69                application: application.as_ref().into(),
70                name: device.as_ref().into(),
71                ..Default::default()
72            },
73            ..Default::default()
74        }
75    }
76
77    /// Insert a credential entry to the credentials of a device.
78    /// If there are no credentials already existing an array is created
79    /// if there is an error deserializing the existing data an error is returned
80    pub fn add_credential(&mut self, credential: Credential) -> Result<(), serde_json::Error> {
81        // TODO: Remove before drg version 0.12.x
82        self.update_section::<DeviceSpecCredentials, _>(|mut auth| {
83            auth.credentials.push(credential.clone());
84            auth
85        })?;
86        self.update_section::<DeviceSpecAuthentication, _>(|mut auth| {
87            auth.credentials.push(credential);
88            auth
89        })
90    }
91
92    /// Retrieve the credentials of this device
93    pub fn get_credentials(&self) -> Option<Vec<Credential>> {
94        let credentials = match self.section::<DeviceSpecAuthentication>() {
95            Some(Ok(auth)) => auth.credentials,
96            _ => match self.section::<DeviceSpecCredentials>() {
97                Some(Ok(credentials)) => credentials.credentials,
98                _ => {
99                    return None;
100                }
101            },
102        };
103        Some(credentials)
104    }
105}
106
107#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
108pub struct DeviceSpecCore {
109    #[serde(default)]
110    #[serde(skip_serializing_if = "is_default")]
111    pub disabled: bool,
112}
113
114attribute!(pub DeviceSpecCore[DeviceEnabled:bool] => |core| match core {
115    Some(Ok(core)) => core.disabled,
116    // failed to decode
117    Some(Err(_)) => false,
118    // no "core" section
119    None => true,
120});
121attribute!(pub DeviceSpecCommands[Commands:Vec<Command>] => |commands| match commands {
122    Some(Ok(commands)) => commands.commands,
123    _ => vec![],
124});
125attribute!(pub DeviceSpecCommands[FirstCommand:Option<Command>] => |commands| match commands {
126    Some(Ok(commands)) => commands.commands.get(0).cloned(),
127    _ => None,
128});
129
130impl Dialect for DeviceSpecCore {
131    fn key() -> &'static str {
132        "core"
133    }
134    fn section() -> Section {
135        Section::Spec
136    }
137}
138
139/// Configured device credentials.
140#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
141pub struct DeviceSpecCredentials {
142    #[serde(default)]
143    #[serde(skip_serializing_if = "Vec::is_empty")]
144    pub credentials: Vec<Credential>,
145}
146
147impl Dialect for DeviceSpecCredentials {
148    fn key() -> &'static str {
149        "credentials"
150    }
151    fn section() -> Section {
152        Section::Spec
153    }
154}
155
156/// A single credential entry.
157#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
158pub enum Credential {
159    #[serde(rename = "user")]
160    UsernamePassword {
161        username: String,
162        password: Password,
163        #[serde(default)]
164        unique: bool,
165    },
166    #[serde(rename = "pass")]
167    Password(Password),
168    #[serde(rename = "cert")]
169    Certificate(String),
170    #[serde(rename = "psk")]
171    PreSharedKey(PreSharedKey),
172}
173
174#[derive(Clone, Serialize, PartialEq, Eq)]
175pub enum Password {
176    #[serde(rename = "plain")]
177    Plain(String),
178    #[serde(rename = "bcrypt")]
179    BCrypt(String),
180    #[serde(rename = "sha512")]
181    Sha512(String),
182}
183
184/// Configured device credentials.
185#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
186pub struct DeviceSpecAuthentication {
187    #[serde(default)]
188    #[serde(skip_serializing_if = "Vec::is_empty")]
189    pub credentials: Vec<Credential>,
190}
191
192impl Dialect for DeviceSpecAuthentication {
193    fn key() -> &'static str {
194        "authentication"
195    }
196    fn section() -> Section {
197        Section::Spec
198    }
199}
200
201#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
202pub struct PreSharedKey {
203    #[serde(with = "Base64Standard")]
204    pub key: Vec<u8>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub validity: Option<Validity>,
207}
208
209impl fmt::Debug for PreSharedKey {
210    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211        write!(f, "key=..., validity: {:?}", self.validity)
212    }
213}
214
215impl PartialOrd for PreSharedKey {
216    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
217        if self.validity.is_none() && other.validity.is_none() {
218            Some(Ordering::Equal)
219        } else if self.validity.is_none() {
220            Some(Ordering::Less)
221        } else if other.validity.is_none() {
222            Some(Ordering::Equal)
223        } else {
224            self.validity.partial_cmp(&other.validity)
225        }
226    }
227}
228
229impl Ord for PreSharedKey {
230    fn cmp(&self, other: &Self) -> Ordering {
231        // We know it never returns None
232        self.partial_cmp(other).unwrap()
233    }
234}
235
236#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
237pub struct Validity {
238    #[serde(rename = "notBefore")]
239    pub not_before: DateTime<Utc>,
240    #[serde(rename = "notAfter")]
241    pub not_after: DateTime<Utc>,
242}
243
244impl Validity {
245    pub fn is_valid(&self, now: DateTime<Utc>) -> bool {
246        self.not_before <= now && self.not_after >= now
247    }
248}
249
250impl PartialOrd for Validity {
251    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
252        Some(if self.not_before < other.not_before {
253            Ordering::Less
254        } else if self.not_before > other.not_before {
255            Ordering::Greater
256        } else if self.not_after > other.not_after {
257            Ordering::Less
258        } else if self.not_after < other.not_after {
259            Ordering::Greater
260        } else {
261            Ordering::Equal
262        })
263    }
264}
265
266impl Ord for Validity {
267    fn cmp(&self, other: &Self) -> Ordering {
268        // We know it never returns None
269        self.partial_cmp(other).unwrap()
270    }
271}
272
273impl<'de> Deserialize<'de> for Password {
274    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
275    where
276        D: Deserializer<'de>,
277    {
278        deserializer.deserialize_any(PasswordVisitor)
279    }
280}
281
282struct PasswordVisitor;
283
284impl<'de> serde::de::Visitor<'de> for PasswordVisitor {
285    type Value = Password;
286
287    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
288        formatter.write_str("A password, by string or map")
289    }
290
291    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
292        Ok(Password::Plain(value.to_owned()))
293    }
294
295    fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
296        Ok(Password::Plain(value))
297    }
298
299    fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
300    where
301        V: MapAccess<'de>,
302    {
303        if let Some(key) = map.next_key::<String>()? {
304            match key.as_str() {
305                "plain" => Ok(Password::Plain(map.next_value()?)),
306                "bcrypt" => Ok(Password::BCrypt(map.next_value()?)),
307                "sha512" => Ok(Password::Sha512(map.next_value()?)),
308                key => Err(serde::de::Error::unknown_field(
309                    key,
310                    &["plain", "bcrypt", "sha512"],
311                )),
312            }
313        } else {
314            Err(serde::de::Error::invalid_length(
315                0,
316                &"Expected exactly one field",
317            ))
318        }
319    }
320}
321
322impl fmt::Debug for Password {
323    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
324        f.write_str("...")
325    }
326}
327
328#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
329#[serde(rename_all = "camelCase")]
330pub struct DeviceSpecGatewaySelector {
331    #[serde(default)]
332    #[serde(skip_serializing_if = "Vec::is_empty")]
333    pub match_names: Vec<String>,
334}
335
336impl Dialect for DeviceSpecGatewaySelector {
337    fn key() -> &'static str {
338        "gatewaySelector"
339    }
340    fn section() -> Section {
341        Section::Spec
342    }
343}
344
345#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
346pub struct DeviceSpecCommands {
347    #[serde(default)]
348    #[serde(skip_serializing_if = "Vec::is_empty")]
349    pub commands: Vec<Command>,
350}
351
352impl Dialect for DeviceSpecCommands {
353    fn key() -> &'static str {
354        "commands"
355    }
356    fn section() -> Section {
357        Section::Spec
358    }
359}
360
361#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
362pub enum Command {
363    #[serde(rename = "external")]
364    External(ExternalCommandEndpoint),
365}
366
367#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
368pub struct ExternalCommandEndpoint {
369    pub r#type: Option<String>,
370    pub url: String,
371    #[serde(default, skip_serializing_if = "String::is_empty")]
372    pub method: String,
373    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
374    pub headers: HashMap<String, String>,
375}
376
377#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
378pub struct DeviceSpecAliases(
379    #[serde(default)]
380    #[serde(skip_serializing_if = "Vec::is_empty")]
381    pub Vec<String>,
382);
383
384dialect!(DeviceSpecAliases [Section::Spec => "alias"]);
385
386#[cfg(test)]
387mod test;