ssh_key/
encode.rs

1//! Encoder-side implementation of the SSH protocol's data type representations
2//! as described in [RFC4251 § 5].
3//!
4//! [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
5
6use crate::{checked::CheckedSum, writer::Writer, Result};
7
8#[cfg(feature = "alloc")]
9use {
10    crate::Error,
11    alloc::{string::String, vec::Vec},
12};
13
14/// Encoding trait.
15///
16/// This trait describes how to encode a given type.
17pub(crate) trait Encode {
18    /// Get the length of this type encoded in bytes, prior to Base64 encoding.
19    fn encoded_len(&self) -> Result<usize>;
20
21    /// Encode this value using the provided [`Encoder`].
22    fn encode(&self, writer: &mut impl Writer) -> Result<()>;
23
24    /// Encode this value, first prepending a `uint32` length prefix
25    /// set to [`Encode::encoded_len`].
26    fn encode_nested(&self, writer: &mut impl Writer) -> Result<()> {
27        self.encoded_len()?.encode(writer)?;
28        self.encode(writer)
29    }
30}
31
32/// Encode a single `byte` to the writer.
33impl Encode for u8 {
34    fn encoded_len(&self) -> Result<usize> {
35        Ok(1)
36    }
37
38    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
39        writer.write(&[*self])
40    }
41}
42
43/// Encode a `uint32` as described in [RFC4251 § 5]:
44///
45/// > Represents a 32-bit unsigned integer.  Stored as four bytes in the
46/// > order of decreasing significance (network byte order).
47/// > For example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4 aa.
48///
49/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
50impl Encode for u32 {
51    fn encoded_len(&self) -> Result<usize> {
52        Ok(4)
53    }
54
55    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
56        writer.write(&self.to_be_bytes())
57    }
58}
59
60/// Encode a `uint64` as described in [RFC4251 § 5]:
61///
62/// > Represents a 64-bit unsigned integer.  Stored as eight bytes in
63/// > the order of decreasing significance (network byte order).
64///
65/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
66impl Encode for u64 {
67    fn encoded_len(&self) -> Result<usize> {
68        Ok(8)
69    }
70
71    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
72        writer.write(&self.to_be_bytes())
73    }
74}
75
76/// Encode a `usize` as a `uint32` as described in [RFC4251 § 5].
77///
78/// Uses [`Encode`] impl on `u32` after converting from a `usize`, handling
79/// potential overflow if `usize` is bigger than `u32`.
80///
81/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
82impl Encode for usize {
83    fn encoded_len(&self) -> Result<usize> {
84        Ok(4)
85    }
86
87    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
88        u32::try_from(*self)?.encode(writer)
89    }
90}
91
92/// Encodes `[u8]` into `byte[n]` as described in [RFC4251 § 5]:
93///
94/// > A byte represents an arbitrary 8-bit value (octet).  Fixed length
95/// > data is sometimes represented as an array of bytes, written
96/// > byte[n], where n is the number of bytes in the array.
97///
98/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
99impl Encode for [u8] {
100    fn encoded_len(&self) -> Result<usize> {
101        [4, self.len()].checked_sum()
102    }
103
104    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
105        self.len().encode(writer)?;
106        writer.write(self)
107    }
108}
109
110/// Encodes `[u8; N]` into `byte[n]` as described in [RFC4251 § 5]:
111///
112/// > A byte represents an arbitrary 8-bit value (octet).  Fixed length
113/// > data is sometimes represented as an array of bytes, written
114/// > byte[n], where n is the number of bytes in the array.
115///
116/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
117impl<const N: usize> Encode for [u8; N] {
118    fn encoded_len(&self) -> Result<usize> {
119        self.as_slice().encoded_len()
120    }
121
122    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
123        self.as_slice().encode(writer)
124    }
125}
126
127/// Encode a `string` as described in [RFC4251 § 5]:
128///
129/// > Arbitrary length binary string.  Strings are allowed to contain
130/// > arbitrary binary data, including null characters and 8-bit
131/// > characters.  They are stored as a uint32 containing its length
132/// > (number of bytes that follow) and zero (= empty string) or more
133/// > bytes that are the value of the string.  Terminating null
134/// > characters are not used.
135/// >
136/// > Strings are also used to store text.  In that case, US-ASCII is
137/// > used for internal names, and ISO-10646 UTF-8 for text that might
138/// > be displayed to the user.  The terminating null character SHOULD
139/// > NOT normally be stored in the string.  For example: the US-ASCII
140/// > string "testing" is represented as 00 00 00 07 t e s t i n g.  The
141/// > UTF-8 mapping does not alter the encoding of US-ASCII characters.
142///
143/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
144impl Encode for &str {
145    fn encoded_len(&self) -> Result<usize> {
146        self.as_bytes().encoded_len()
147    }
148
149    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
150        self.as_bytes().encode(writer)
151    }
152}
153
154#[cfg(feature = "alloc")]
155#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
156impl Encode for Vec<u8> {
157    fn encoded_len(&self) -> Result<usize> {
158        self.as_slice().encoded_len()
159    }
160
161    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
162        self.as_slice().encode(writer)
163    }
164}
165
166#[cfg(feature = "alloc")]
167#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
168impl Encode for String {
169    fn encoded_len(&self) -> Result<usize> {
170        self.as_str().encoded_len()
171    }
172
173    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
174        self.as_str().encode(writer)
175    }
176}
177
178#[cfg(feature = "alloc")]
179#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
180impl Encode for Vec<String> {
181    fn encoded_len(&self) -> Result<usize> {
182        self.iter().try_fold(4usize, |acc, string| {
183            acc.checked_add(string.encoded_len()?).ok_or(Error::Length)
184        })
185    }
186
187    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
188        self.encoded_len()?
189            .checked_sub(4)
190            .ok_or(Error::Length)?
191            .encode(writer)?;
192
193        for entry in self {
194            entry.encode(writer)?;
195        }
196
197        Ok(())
198    }
199}