ssh_key/certificate/
options_map.rs

1//! OpenSSH certificate options used by critical options and extensions.
2
3use crate::{
4    checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
5    Result,
6};
7use alloc::{string::String, vec::Vec};
8use core::cmp::Ordering;
9
10/// Key/value map type used for certificate's critical options and extensions.
11pub type OptionsMap = alloc::collections::BTreeMap<String, String>;
12
13impl Decode for OptionsMap {
14    fn decode(reader: &mut impl Reader) -> Result<Self> {
15        reader.read_nested(|reader| {
16            let mut entries = Vec::<(String, String)>::new();
17
18            while !reader.is_finished() {
19                let name = String::decode(reader)?;
20                let data = String::decode(reader)?;
21
22                // Options must be lexically ordered by "name" if they appear in
23                // the sequence. Each named option may only appear once in a
24                // certificate.
25                if let Some((prev_name, _)) = entries.last() {
26                    if prev_name.cmp(&name) != Ordering::Less {
27                        return Err(Error::FormatEncoding);
28                    }
29                }
30
31                entries.push((name, data));
32            }
33
34            Ok(OptionsMap::from_iter(entries))
35        })
36    }
37}
38
39impl Encode for OptionsMap {
40    fn encoded_len(&self) -> Result<usize> {
41        self.iter().try_fold(4, |acc, (name, data)| {
42            [acc, 4, name.len(), 4, data.len()].checked_sum()
43        })
44    }
45
46    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
47        self.encoded_len()?
48            .checked_sub(4)
49            .ok_or(Error::Length)?
50            .encode(writer)?;
51
52        for (name, data) in self {
53            name.encode(writer)?;
54            data.encode(writer)?;
55        }
56
57        Ok(())
58    }
59}