1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//! OpenSSH certificate options used by critical options and extensions.

use crate::{
    checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
    Result,
};
use alloc::{string::String, vec::Vec};
use core::cmp::Ordering;

/// Key/value map type used for certificate's critical options and extensions.
pub type OptionsMap = alloc::collections::BTreeMap<String, String>;

impl Decode for OptionsMap {
    fn decode(reader: &mut impl Reader) -> Result<Self> {
        reader.read_nested(|reader| {
            let mut entries = Vec::<(String, String)>::new();

            while !reader.is_finished() {
                let name = String::decode(reader)?;
                let data = String::decode(reader)?;

                // Options must be lexically ordered by "name" if they appear in
                // the sequence. Each named option may only appear once in a
                // certificate.
                if let Some((prev_name, _)) = entries.last() {
                    if prev_name.cmp(&name) != Ordering::Less {
                        return Err(Error::FormatEncoding);
                    }
                }

                entries.push((name, data));
            }

            Ok(OptionsMap::from_iter(entries))
        })
    }
}

impl Encode for OptionsMap {
    fn encoded_len(&self) -> Result<usize> {
        self.iter().try_fold(4, |acc, (name, data)| {
            [acc, 4, name.len(), 4, data.len()].checked_sum()
        })
    }

    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
        self.encoded_len()?
            .checked_sub(4)
            .ok_or(Error::Length)?
            .encode(writer)?;

        for (name, data) in self {
            name.encode(writer)?;
            data.encode(writer)?;
        }

        Ok(())
    }
}