ssh_encoding/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::{Error, checked::CheckedSum, writer::Writer};
7use core::str;
8
9#[cfg(feature = "alloc")]
10use alloc::{string::String, vec::Vec};
11
12#[cfg(feature = "bytes")]
13use bytes::{Bytes, BytesMut};
14
15/// Encoding trait.
16///
17/// This trait describes how to encode a given type.
18pub trait Encode {
19 /// Get the length of this type encoded in bytes, prior to Base64 encoding.
20 ///
21 /// # Errors
22 /// Returns errors specific to the concrete implementation of this trait.
23 fn encoded_len(&self) -> Result<usize, Error>;
24
25 /// Encode this value using the provided [`Writer`].
26 ///
27 /// # Errors
28 /// Returns errors specific to the concrete implementation of this trait.
29 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error>;
30
31 /// Return the length of this type after encoding when prepended with a `uint32` length prefix.
32 ///
33 /// # Errors
34 /// Returns errors specific to the concrete implementation of this trait.
35 fn encoded_len_prefixed(&self) -> Result<usize, Error> {
36 [4, self.encoded_len()?].checked_sum()
37 }
38
39 /// Encode this value, first prepending a `uint32` length prefix set to [`Encode::encoded_len`].
40 ///
41 /// # Errors
42 /// Returns errors specific to the concrete implementation of this trait.
43 fn encode_prefixed(&self, writer: &mut impl Writer) -> Result<(), Error> {
44 self.encoded_len()?.encode(writer)?;
45 self.encode(writer)
46 }
47
48 /// Encode this value, returning a `Vec<u8>` containing the encoded message.
49 ///
50 /// # Errors
51 /// Returns errors specific to the concrete implementation of this trait.
52 #[cfg(feature = "alloc")]
53 fn encode_vec(&self) -> Result<Vec<u8>, Error> {
54 let mut ret = Vec::with_capacity(self.encoded_len()?);
55 self.encode(&mut ret)?;
56 Ok(ret)
57 }
58
59 /// Encode this value, returning a [`BytesMut`] containing the encoded message.
60 ///
61 /// # Errors
62 /// Returns errors specific to the concrete implementation of this trait.
63 #[cfg(feature = "bytes")]
64 fn encode_bytes(&self) -> Result<BytesMut, Error> {
65 let mut ret = BytesMut::with_capacity(self.encoded_len()?);
66 self.encode(&mut ret)?;
67 Ok(ret)
68 }
69}
70
71/// Encode a single `byte` to the writer.
72impl Encode for u8 {
73 fn encoded_len(&self) -> Result<usize, Error> {
74 Ok(1)
75 }
76
77 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
78 writer.write(&[*self])
79 }
80}
81
82/// Encode a `boolean` as described in [RFC4251 § 5]:
83///
84/// > A boolean value is stored as a single byte. The value 0
85/// > represents FALSE, and the value 1 represents TRUE. All non-zero
86/// > values MUST be interpreted as TRUE; however, applications MUST NOT
87/// > store values other than 0 and 1.
88///
89/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
90impl Encode for bool {
91 fn encoded_len(&self) -> Result<usize, Error> {
92 Ok(1)
93 }
94
95 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
96 if *self {
97 1u8.encode(writer)
98 } else {
99 0u8.encode(writer)
100 }
101 }
102}
103
104/// Encode a `uint32` as described in [RFC4251 § 5]:
105///
106/// > Represents a 32-bit unsigned integer. Stored as four bytes in the
107/// > order of decreasing significance (network byte order).
108/// > For example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4 aa.
109///
110/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
111impl Encode for u32 {
112 fn encoded_len(&self) -> Result<usize, Error> {
113 Ok(4)
114 }
115
116 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
117 writer.write(&self.to_be_bytes())
118 }
119}
120
121/// Encode a `uint64` as described in [RFC4251 § 5]:
122///
123/// > Represents a 64-bit unsigned integer. Stored as eight bytes in
124/// > the order of decreasing significance (network byte order).
125///
126/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
127impl Encode for u64 {
128 fn encoded_len(&self) -> Result<usize, Error> {
129 Ok(8)
130 }
131
132 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
133 writer.write(&self.to_be_bytes())
134 }
135}
136
137/// Encode a `usize` as a `uint32` as described in [RFC4251 § 5].
138///
139/// Uses [`Encode`] impl on `u32` after converting from a `usize`, handling
140/// potential overflow if `usize` is bigger than `u32`.
141///
142/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
143impl Encode for usize {
144 fn encoded_len(&self) -> Result<usize, Error> {
145 Ok(4)
146 }
147
148 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
149 u32::try_from(*self)?.encode(writer)
150 }
151}
152
153/// Encodes `[u8]` into `string` as described in [RFC4251 § 5]:
154///
155/// > Arbitrary length binary string. Strings are allowed to contain
156/// > arbitrary binary data, including null characters and 8-bit
157/// > characters. They are stored as a uint32 containing its length
158/// > (number of bytes that follow) and zero (= empty string) or more
159/// > bytes that are the value of the string. Terminating null
160/// > characters are not used.
161///
162/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
163impl Encode for [u8] {
164 fn encoded_len(&self) -> Result<usize, Error> {
165 [4, self.len()].checked_sum()
166 }
167
168 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
169 self.len().encode(writer)?;
170 writer.write(self)
171 }
172}
173
174/// Encodes byte array using `byte[n]` encoding as described in [RFC4251 § 5]:
175///
176/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
177/// > data is sometimes represented as an array of bytes, written
178/// > `byte[n]`, where n is the number of bytes in the array.
179///
180/// Note that unlike `string`, this type is encoded without a length prefix,
181/// but instead implicitly obtains its length as `N`.
182///
183/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
184impl<const N: usize> Encode for [u8; N] {
185 fn encoded_len(&self) -> Result<usize, Error> {
186 Ok(N)
187 }
188
189 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
190 writer.write(self)
191 }
192}
193
194/// A macro to implement `Encode` for a type by delegating to some transformed version of `self`.
195macro_rules! impl_by_delegation {
196 (
197 $(
198 $(#[$attr:meta])*
199 impl $( ($($generics:tt)+) )? Encode for $type:ty where $self:ident -> $delegate:expr;
200 )+
201 ) => {
202 $(
203 $(#[$attr])*
204 impl $(< $($generics)* >)? Encode for $type {
205 fn encoded_len(&$self) -> Result<usize, Error> {
206 $delegate.encoded_len()
207 }
208
209 fn encode(&$self, writer: &mut impl Writer) -> Result<(), Error> {
210 $delegate.encode(writer)
211 }
212 }
213 )+
214 };
215}
216
217impl_by_delegation!(
218 /// Encode a `string` as described in [RFC4251 § 5]:
219 ///
220 /// > Arbitrary length binary string. Strings are allowed to contain
221 /// > arbitrary binary data, including null characters and 8-bit
222 /// > characters. They are stored as a uint32 containing its length
223 /// > (number of bytes that follow) and zero (= empty string) or more
224 /// > bytes that are the value of the string. Terminating null
225 /// > characters are not used.
226 /// >
227 /// > Strings are also used to store text. In that case, US-ASCII is
228 /// > used for internal names, and ISO-10646 UTF-8 for text that might
229 /// > be displayed to the user. The terminating null character SHOULD
230 /// > NOT normally be stored in the string. For example: the US-ASCII
231 /// > string "testing" is represented as 00 00 00 07 t e s t i n g. The
232 /// > UTF-8 mapping does not alter the encoding of US-ASCII characters.
233 ///
234 /// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
235 impl Encode for str where self -> self.as_bytes();
236
237 #[cfg(feature = "alloc")]
238 impl Encode for Vec<u8> where self -> self.as_slice();
239 #[cfg(feature = "alloc")]
240 impl Encode for String where self -> self.as_bytes();
241 #[cfg(feature = "bytes")]
242 impl Encode for Bytes where self -> self.as_ref();
243
244 // While deref coercion ensures that `&E` can use the `Encode` trait methods, it will not be
245 // allowd in trait bounds, as `&E` does not implement `Encode` itself just because `E: Encode`.
246 // A blanket impl for `&E` would be the most generic, but that collides with the `Label` trait's
247 // blanket impl. Instead, we can do it explicitly for the immediatley relevant base types.
248 impl Encode for &str where self -> **self;
249 impl Encode for &[u8] where self -> **self;
250 #[cfg(feature = "alloc")]
251 impl Encode for &Vec<u8> where self -> **self;
252 #[cfg(feature = "alloc")]
253 impl Encode for &String where self -> **self;
254 #[cfg(feature = "bytes")]
255 impl Encode for &Bytes where self -> **self;
256
257);
258
259/// A trait indicating that the type is encoded like an RFC4251 string.
260///
261/// Implementing this trait allows encoding sequences of the type as a string of strings.
262///
263/// A `string` is described in [RFC4251 § 5]:
264///
265/// > Arbitrary length binary string. Strings are allowed to contain
266/// > arbitrary binary data, including null characters and 8-bit
267/// > characters. They are stored as a uint32 containing its length
268/// > (number of bytes that follow) and zero (= empty string) or more
269/// > bytes that are the value of the string. Terminating null
270/// > characters are not used.
271/// >
272/// > Strings are also used to store text. In that case, US-ASCII is
273/// > used for internal names, and ISO-10646 UTF-8 for text that might
274/// > be displayed to the user. The terminating null character SHOULD
275/// > NOT normally be stored in the string. For example: the US-ASCII
276/// > string "testing" is represented as 00 00 00 07 t e s t i n g. The
277/// > UTF-8 mapping does not alter the encoding of US-ASCII characters.
278///
279/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
280pub trait Rfc4251String: Encode {}
281
282impl Rfc4251String for str {}
283impl Rfc4251String for [u8] {}
284#[cfg(feature = "alloc")]
285impl Rfc4251String for String {}
286#[cfg(feature = "alloc")]
287impl Rfc4251String for Vec<u8> {}
288#[cfg(feature = "bytes")]
289impl Rfc4251String for Bytes {}
290
291/// Any reference to [`Rfc4251String`] is itself [`Rfc4251String`] if `&T: Encode`.
292impl<'a, T> Rfc4251String for &'a T
293where
294 T: Rfc4251String + ?Sized,
295 &'a T: Encode,
296{
297}
298
299/// Encode a slice of string-like types as a string wrapping all the entries.
300impl<T: Rfc4251String> Encode for [T] {
301 fn encoded_len(&self) -> Result<usize, Error> {
302 self.iter().try_fold(4usize, |acc, string| {
303 acc.checked_add(string.encoded_len()?).ok_or(Error::Length)
304 })
305 }
306
307 fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
308 self.encoded_len()?
309 .checked_sub(4)
310 .ok_or(Error::Length)?
311 .encode(writer)?;
312 self.iter().try_fold((), |(), entry| entry.encode(writer))
313 }
314}
315
316impl_by_delegation!(
317 #[cfg(feature = "alloc")]
318 impl (T: Rfc4251String) Encode for Vec<T> where self -> self.as_slice();
319);