ssh_key/certificate/
options_map.rs

1//! OpenSSH certificate options used by critical options and extensions.
2
3use crate::{Error, Result};
4use alloc::{collections::BTreeMap, string::String, vec::Vec};
5use core::{
6    cmp::Ordering,
7    ops::{Deref, DerefMut},
8};
9use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
10
11/// Key/value map type used for certificate's critical options and extensions.
12#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
13pub struct OptionsMap(pub BTreeMap<String, String>);
14
15impl OptionsMap {
16    /// Create a new [`OptionsMap`].
17    pub fn new() -> Self {
18        Self::default()
19    }
20}
21
22impl Deref for OptionsMap {
23    type Target = BTreeMap<String, String>;
24
25    fn deref(&self) -> &Self::Target {
26        &self.0
27    }
28}
29
30impl DerefMut for OptionsMap {
31    fn deref_mut(&mut self) -> &mut Self::Target {
32        &mut self.0
33    }
34}
35
36impl Decode for OptionsMap {
37    type Error = Error;
38
39    fn decode(reader: &mut impl Reader) -> Result<Self> {
40        reader.read_prefixed(|reader| {
41            let mut entries = Vec::<(String, String)>::new();
42
43            while !reader.is_finished() {
44                let name = String::decode(reader)?;
45                let data = reader.read_prefixed(|reader| {
46                    if reader.remaining_len() > 0 {
47                        String::decode(reader)
48                    } else {
49                        Ok(String::default())
50                    }
51                })?;
52
53                // Options must be lexically ordered by "name" if they appear in
54                // the sequence. Each named option may only appear once in a
55                // certificate.
56                if let Some((prev_name, _)) = entries.last() {
57                    if prev_name.cmp(&name) != Ordering::Less {
58                        return Err(Error::FormatEncoding);
59                    }
60                }
61
62                entries.push((name, data));
63            }
64
65            Ok(OptionsMap::from_iter(entries))
66        })
67    }
68}
69
70impl Encode for OptionsMap {
71    fn encoded_len(&self) -> encoding::Result<usize> {
72        self.iter()
73            .try_fold(4, |acc, (name, data)| {
74                [
75                    acc,
76                    name.encoded_len()?,
77                    if data.is_empty() {
78                        4
79                    } else {
80                        data.encoded_len_prefixed()?
81                    },
82                ]
83                .checked_sum()
84            })
85            .map_err(Into::into)
86    }
87
88    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
89        self.encoded_len()?
90            .checked_sub(4)
91            .ok_or(encoding::Error::Length)?
92            .encode(writer)?;
93
94        for (name, data) in self.iter() {
95            name.encode(writer)?;
96            if data.is_empty() {
97                0usize.encode(writer)?;
98            } else {
99                data.encode_prefixed(writer)?
100            }
101        }
102
103        Ok(())
104    }
105}
106
107impl From<BTreeMap<String, String>> for OptionsMap {
108    fn from(map: BTreeMap<String, String>) -> OptionsMap {
109        OptionsMap(map)
110    }
111}
112
113impl From<OptionsMap> for BTreeMap<String, String> {
114    fn from(map: OptionsMap) -> BTreeMap<String, String> {
115        map.0
116    }
117}
118
119impl FromIterator<(String, String)> for OptionsMap {
120    fn from_iter<T>(iter: T) -> Self
121    where
122        T: IntoIterator<Item = (String, String)>,
123    {
124        BTreeMap::from_iter(iter).into()
125    }
126}