Skip to main content

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, Hash, PartialEq, PartialOrd, Ord)]
13pub struct OptionsMap(pub BTreeMap<String, String>);
14
15impl OptionsMap {
16    /// Create a new [`OptionsMap`].
17    #[must_use]
18    pub fn new() -> Self {
19        Self::default()
20    }
21}
22
23impl Deref for OptionsMap {
24    type Target = BTreeMap<String, String>;
25
26    fn deref(&self) -> &Self::Target {
27        &self.0
28    }
29}
30
31impl DerefMut for OptionsMap {
32    fn deref_mut(&mut self) -> &mut Self::Target {
33        &mut self.0
34    }
35}
36
37impl Decode for OptionsMap {
38    type Error = Error;
39
40    fn decode(reader: &mut impl Reader) -> Result<Self> {
41        reader.read_prefixed(|reader| {
42            let mut entries = Vec::<(String, String)>::new();
43
44            while !reader.is_finished() {
45                let name = String::decode(reader)?;
46                let data = reader.read_prefixed(|reader| {
47                    if reader.remaining_len() > 0 {
48                        String::decode(reader)
49                    } else {
50                        Ok(String::default())
51                    }
52                })?;
53
54                // Options must be lexically ordered by "name" if they appear in
55                // the sequence. Each named option may only appear once in a
56                // certificate.
57                if let Some((prev_name, _)) = entries.last() {
58                    if prev_name.cmp(&name) != Ordering::Less {
59                        return Err(Error::FormatEncoding);
60                    }
61                }
62
63                entries.push((name, data));
64            }
65
66            Ok(OptionsMap::from_iter(entries))
67        })
68    }
69}
70
71impl Encode for OptionsMap {
72    fn encoded_len(&self) -> encoding::Result<usize> {
73        self.iter().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    }
86
87    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
88        self.encoded_len()?
89            .checked_sub(4)
90            .ok_or(encoding::Error::Length)?
91            .encode(writer)?;
92
93        for (name, data) in self.iter() {
94            name.encode(writer)?;
95            if data.is_empty() {
96                0usize.encode(writer)?;
97            } else {
98                data.encode_prefixed(writer)?;
99            }
100        }
101
102        Ok(())
103    }
104}
105
106impl From<BTreeMap<String, String>> for OptionsMap {
107    fn from(map: BTreeMap<String, String>) -> OptionsMap {
108        OptionsMap(map)
109    }
110}
111
112impl From<OptionsMap> for BTreeMap<String, String> {
113    fn from(map: OptionsMap) -> BTreeMap<String, String> {
114        map.0
115    }
116}
117
118impl FromIterator<(String, String)> for OptionsMap {
119    fn from_iter<T>(iter: T) -> Self
120    where
121        T: IntoIterator<Item = (String, String)>,
122    {
123        BTreeMap::from_iter(iter).into()
124    }
125}