ssh_packet/arch/
ascii.rs

1use std::ops::Deref;
2
3use binrw::binrw;
4
5use super::Bytes;
6
7/// Create an [`Ascii`] string from a literal in _const_-context.
8#[doc(hidden)]
9#[macro_export]
10macro_rules! __ascii__ {
11    ($string:literal) => {
12        if $string.is_ascii() {
13            #[allow(deprecated)]
14            $crate::arch::Ascii::borrowed_unchecked($string)
15        } else {
16            panic!("the literal wasn't ASCII-formatted")
17        }
18    };
19}
20
21pub use __ascii__ as ascii;
22
23/// Errors which can occur when attempting to interpret a string as a ASCII characters.
24#[derive(Debug)]
25pub struct AsciiError {}
26
27impl std::fmt::Display for AsciiError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        f.write_str("the input data wasn't ASCII-formatted")
30    }
31}
32
33impl std::error::Error for AsciiError {}
34
35/// A `string` as defined in the SSH protocol, restricted to valid **UTF-8**.
36///
37/// see <https://datatracker.ietf.org/doc/html/rfc4251#section-5>.
38#[binrw]
39#[derive(Default, Clone, PartialEq, Eq)]
40#[br(assert(self_0.as_borrow().is_ascii()))]
41pub struct Ascii<'b>(Bytes<'b>);
42
43impl<'b> Ascii<'b> {
44    /// Create an [`Ascii`] string from a [`String`].
45    pub fn owned(value: String) -> Result<Self, AsciiError> {
46        if value.is_ascii() {
47            Ok(Self(Bytes::owned(value.into_bytes())))
48        } else {
49            Err(AsciiError {})
50        }
51    }
52
53    /// Create an [`Ascii`] string from a [`&str`].
54    pub const fn borrowed(value: &'b str) -> Result<Self, AsciiError> {
55        if value.is_ascii() {
56            Ok(Self(Bytes::borrowed(value.as_bytes())))
57        } else {
58            Err(AsciiError {})
59        }
60    }
61
62    // TODO: (safety) Remove this method when compiler feature `const_precise_live_drops`
63    // and directly use `Self::borrowed` in the `ascii!` macro.
64    #[doc(hidden)]
65    #[deprecated(
66        since = "0.0.0",
67        note = "This is an internal function, and is not safe to work with"
68    )]
69    pub const fn borrowed_unchecked(value: &'b str) -> Self {
70        Self(Bytes::borrowed(value.as_bytes()))
71    }
72
73    /// Obtain an [`Ascii`] string from a reference by borrowing the internal buffer.
74    pub fn as_borrow<'a: 'b>(&'a self) -> Ascii<'a> {
75        Self(self.0.as_borrow())
76    }
77
78    /// Extract the buffer as a [`String`].
79    pub fn into_string(self) -> String {
80        String::from_utf8(self.0.into_vec()).expect("The inner buffer contained non UTF-8 data")
81    }
82}
83
84impl std::fmt::Debug for Ascii<'_> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        f.debug_tuple("Ascii").field(&&**self).finish()
87    }
88}
89
90impl std::fmt::Display for Ascii<'_> {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.write_str(self)
93    }
94}
95
96impl Deref for Ascii<'_> {
97    type Target = str;
98
99    fn deref(&self) -> &Self::Target {
100        std::str::from_utf8(&self.0).expect("The inner buffer contained non UTF-8 data")
101    }
102}
103
104impl AsRef<str> for Ascii<'_> {
105    fn as_ref(&self) -> &str {
106        self
107    }
108}
109
110impl TryFrom<String> for Ascii<'_> {
111    type Error = AsciiError;
112
113    fn try_from(value: String) -> Result<Self, AsciiError> {
114        Self::owned(value)
115    }
116}
117
118impl<'b> TryFrom<&'b str> for Ascii<'b> {
119    type Error = AsciiError;
120
121    fn try_from(value: &'b str) -> Result<Self, Self::Error> {
122        Self::borrowed(value)
123    }
124}