Skip to main content

rama_net/socket/
device_name.rs

1use std::{fmt, str::FromStr};
2
3use rama_core::error::BoxErrorExt as _;
4use rama_core::error::{BoxError, ErrorContext as _};
5use rama_utils::str::smol_str::SmolStr;
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8/// Name of a (network) interface device name, e.g. `eth0`.
9pub struct DeviceName(SmolStr);
10
11impl DeviceName {
12    /// Create a new [`DeviceName`].
13    #[must_use]
14    #[expect(
15        clippy::panic,
16        reason = "static-str invariant: panic at compile time when the static is not a valid device name"
17    )]
18    pub const fn new(name: &'static str) -> Self {
19        if !is_valid(name.as_bytes()) {
20            panic!("static str is not a valid (interface) device name");
21        }
22        Self(SmolStr::new_static(name))
23    }
24
25    /// Return a reference to `self` as a byte slice.
26    #[must_use]
27    pub fn as_bytes(&self) -> &[u8] {
28        self.0.as_bytes()
29    }
30
31    /// Return a reference to `self` as a string slice.
32    #[must_use]
33    pub fn as_str(&self) -> &str {
34        self.0.as_str()
35    }
36}
37
38impl fmt::Display for DeviceName {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{}", self.0)
41    }
42}
43
44impl FromStr for DeviceName {
45    type Err = BoxError;
46
47    #[inline]
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        Self::try_from(s)
50    }
51}
52
53impl TryFrom<String> for DeviceName {
54    type Error = BoxError;
55
56    #[inline]
57    fn try_from(s: String) -> Result<Self, Self::Error> {
58        s.as_str().try_into()
59    }
60}
61
62impl TryFrom<&String> for DeviceName {
63    type Error = BoxError;
64
65    #[inline]
66    fn try_from(value: &String) -> Result<Self, Self::Error> {
67        value.as_str().try_into()
68    }
69}
70
71impl TryFrom<&str> for DeviceName {
72    type Error = BoxError;
73
74    fn try_from(s: &str) -> Result<Self, Self::Error> {
75        use rama_core::error::ErrorExt as _;
76
77        if is_valid(s.as_bytes()) {
78            return Ok(Self(SmolStr::from(s)));
79        }
80
81        Err(BoxError::from_static_str("invalid (interface) device name")
82            .context_str_field("str", s))
83    }
84}
85
86impl TryFrom<Vec<u8>> for DeviceName {
87    type Error = BoxError;
88
89    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
90        Self::try_from(bytes.as_slice())
91    }
92}
93
94impl TryFrom<&[u8]> for DeviceName {
95    type Error = BoxError;
96
97    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
98        let s = core::str::from_utf8(bytes).context("parse (interface) device name from bytes")?;
99        s.try_into()
100    }
101}
102
103use rama_utils::macros::serde_str::impl_serde_str;
104
105impl_serde_str!(as_str DeviceName);
106
107pub(super) const fn is_valid(s: &[u8]) -> bool {
108    if s.is_empty() || s.len() > DEVICE_MAX_LEN {
109        false
110    } else {
111        let mut i = 0;
112        if DEVICE_FIRST_CHARS[s[0] as usize] == 0 {
113            return false;
114        }
115        while i < s.len() {
116            if DEVICE_CHARS[s[i] as usize] == 0 {
117                return false;
118            }
119            i += 1;
120        }
121        true
122    }
123}
124
125/// The maximum length of a device name.
126const DEVICE_MAX_LEN: usize = 15;
127
128#[rustfmt::skip]
129/// Valid byte values for a device name.
130const DEVICE_CHARS: [u8; 256] = [
131    //  0      1      2      3      4      5      6      7      8      9
132        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //   x
133        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  1x
134        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  2x
135        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  3x
136        0,     0,     0,     0,     0,  b'-',  b'.',     0,  b'0',  b'1', //  4x
137        b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9',  b':',     0, //  5x
138        0,     0,     0,     0,     0,  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
139        b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
140        b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
141        b'Z',     0,     0,     0,     0,  b'_',     0,  b'a',  b'b',  b'c', //  9x
142        b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
143        b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
144        b'x',  b'y',  b'z',     0,     0,     0,     0,     0,     0,     0, // 12x
145        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 13x
146        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 14x
147        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 15x
148        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 16x
149        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 17x
150        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 18x
151        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 19x
152        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 20x
153        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 21x
154        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 22x
155        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 23x
156        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 24x
157        0,     0,     0,     0,     0,     0                              // 25x
158];
159
160#[rustfmt::skip]
161const DEVICE_FIRST_CHARS: [u8; 256] = [
162    //  0      1      2      3      4      5      6      7      8      9
163        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //   x
164        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //  1x
165        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //  2x
166        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //  3x
167        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //  4x
168        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    //  5x
169        0,     0,     0,     0,     0,     b'A',  b'B',  b'C',  b'D',  b'E', //  6x
170        b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
171        b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
172        b'Z',     0,     0,     0,     0,     0,     0,  b'a',  b'b',  b'c', //  9x
173        b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
174        b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
175        b'x',  b'y',  b'z',     0,     0,     0,     0,     0,     0,  0,    // 12x
176        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 13x
177        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 14x
178        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 15x
179        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 16x
180        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 17x
181        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 18x
182        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 19x
183        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 20x
184        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 21x
185        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 22x
186        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 23x
187        0,     0,     0,     0,     0,     0,     0,     0,     0,     0,    // 24x
188        0,     0,     0,     0,     0,     0                                 // 25x
189];
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
196    #[test]
197    fn test_parse_valid_device_name() {
198        for s in [
199            "eth0",
200            "eth0.100",
201            "br-lan",
202            "ens192",
203            "veth_abcd1234",
204            "lo",
205        ] {
206            let msg = format!("parsing '{s}'");
207
208            assert_eq!(s, s.parse::<DeviceName>().expect(&msg).as_str());
209        }
210    }
211
212    #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
213    #[test]
214    fn test_parse_display_device_name() {
215        for s in [
216            "eth0",
217            "eth0.100",
218            "br-lan",
219            "ens192",
220            "veth_abcd1234",
221            "lo",
222        ] {
223            let msg = format!("parsing '{s}'");
224            let name: DeviceName = s.parse().expect(&msg);
225            assert_eq!(name.to_string(), s, "{msg}");
226        }
227    }
228}