1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct InterfaceId(String);
19
20impl InterfaceId {
21 pub fn new(id: impl Into<String>) -> Self {
23 Self(id.into())
24 }
25
26 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32#[non_exhaustive]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum InterfaceType {
38 WiFi,
40 Cellular,
42 Ethernet,
44 Bluetooth,
46 Loopback,
48 Vpn,
50 Unknown,
52}
53
54impl InterfaceType {
55 pub const fn label(&self) -> &'static str {
57 match self {
58 Self::WiFi => "WiFi",
59 Self::Cellular => "Cellular",
60 Self::Ethernet => "Ethernet",
61 Self::Bluetooth => "Bluetooth",
62 Self::Loopback => "Loopback",
63 Self::Vpn => "VPN",
64 Self::Unknown => "Unknown",
65 }
66 }
67
68 pub const fn default_priority(&self) -> u8 {
70 match self {
71 Self::Ethernet => 10,
72 Self::WiFi => 20,
73 Self::Vpn => 30,
74 Self::Cellular => 40,
75 Self::Bluetooth => 50,
76 Self::Loopback => 100,
77 Self::Unknown => 200,
78 }
79 }
80
81 pub const fn is_metered(&self) -> bool {
83 matches!(self, Self::Cellular)
84 }
85}
86
87#[non_exhaustive]
91#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
92pub enum IpAddr {
93 V4([u8; 4]),
95 V6([u8; 16]),
97}
98
99impl IpAddr {
100 pub const fn v4(a: u8, b: u8, c: u8, d: u8) -> Self {
102 Self::V4([a, b, c, d])
103 }
104
105 pub const fn loopback_v4() -> Self {
107 Self::V4([127, 0, 0, 1])
108 }
109
110 pub const fn unspecified_v4() -> Self {
112 Self::V4([0, 0, 0, 0])
113 }
114
115 pub fn is_loopback(&self) -> bool {
117 match self {
118 Self::V4(b) => b[0] == 127,
119 Self::V6(b) => b[..15].iter().all(|&x| x == 0) && b[15] == 1,
120 }
121 }
122
123 pub fn is_private(&self) -> bool {
125 match self {
126 Self::V4(b) => {
127 b[0] == 10
128 || (b[0] == 172 && (16..=31).contains(&b[1]))
129 || (b[0] == 192 && b[1] == 168)
130 }
131 Self::V6(b) => b[0] == 0xfe && (b[1] & 0xc0) == 0x80,
132 }
133 }
134
135 pub fn to_string_repr(&self) -> String {
137 match self {
138 Self::V4(b) => format!("{}.{}.{}.{}", b[0], b[1], b[2], b[3]),
139 Self::V6(b) => {
140 let parts: Vec<String> = (0_u8..8_u8)
142 .map(|i| {
143 let lo = usize::from(i.saturating_mul(2));
144 let hi = usize::from(i.saturating_mul(2).saturating_add(1));
145 let pair = [
146 b.get(lo).copied().unwrap_or(0),
147 b.get(hi).copied().unwrap_or(0),
148 ];
149 format!("{:x}", u16::from_be_bytes(pair))
150 })
151 .collect();
152 parts.join(":")
153 }
154 }
155 }
156}
157
158#[non_exhaustive]
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct Interface {
164 pub id: InterfaceId,
166 pub name: String,
168 pub interface_type: InterfaceType,
170 pub is_up: bool,
172 pub addresses: Vec<IpAddr>,
174 pub mac: Option<[u8; 6]>,
176 pub signal_strength: Option<u8>,
178 pub link_speed_mbps: Option<u32>,
180 pub is_metered: bool,
182}
183
184impl Interface {
185 pub fn new(
187 id: impl Into<String>,
188 name: impl Into<String>,
189 interface_type: InterfaceType,
190 ) -> Self {
191 Self {
192 id: InterfaceId::new(id),
193 name: name.into(),
194 interface_type,
195 is_up: false,
196 addresses: Vec::new(),
197 mac: None,
198 signal_strength: None,
199 link_speed_mbps: None,
200 is_metered: interface_type.is_metered(),
201 }
202 }
203
204 pub fn up(mut self) -> Self {
206 self.is_up = true;
207 self
208 }
209
210 pub fn with_address(mut self, addr: IpAddr) -> Self {
212 self.addresses.push(addr);
213 self
214 }
215
216 pub fn with_mac(mut self, mac: [u8; 6]) -> Self {
218 self.mac = Some(mac);
219 self
220 }
221
222 pub fn with_signal(mut self, strength: u8) -> Self {
224 self.signal_strength = Some(strength.min(100));
225 self
226 }
227
228 pub fn with_speed(mut self, mbps: u32) -> Self {
230 self.link_speed_mbps = Some(mbps);
231 self
232 }
233
234 pub fn has_address(&self) -> bool {
236 !self.addresses.is_empty()
237 }
238
239 pub fn is_ready(&self) -> bool {
241 self.is_up && self.has_address()
242 }
243
244 pub fn primary_address(&self) -> Option<&IpAddr> {
246 self.addresses.first()
247 }
248
249 pub fn effective_priority(&self) -> u16 {
251 let base = u16::from(self.interface_type.default_priority());
252 let signal_penalty: u16 = match self.signal_strength {
253 Some(s) if s < 30 => 50, Some(s) if s < 60 => 20, _ => 0,
256 };
257 base.saturating_add(signal_penalty)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn interface_id() {
267 let id = InterfaceId::new("wlan0");
268 assert_eq!(id.as_str(), "wlan0");
269 }
270
271 #[test]
272 fn interface_type_labels() {
273 assert_eq!(InterfaceType::WiFi.label(), "WiFi");
274 assert_eq!(InterfaceType::Cellular.label(), "Cellular");
275 assert_eq!(InterfaceType::Ethernet.label(), "Ethernet");
276 }
277
278 #[test]
279 fn interface_type_priority() {
280 assert!(
281 InterfaceType::Ethernet.default_priority() < InterfaceType::WiFi.default_priority()
282 );
283 assert!(
284 InterfaceType::WiFi.default_priority() < InterfaceType::Cellular.default_priority()
285 );
286 }
287
288 #[test]
289 fn interface_type_metered() {
290 assert!(InterfaceType::Cellular.is_metered());
291 assert!(!InterfaceType::WiFi.is_metered());
292 assert!(!InterfaceType::Ethernet.is_metered());
293 }
294
295 #[test]
296 fn ip_addr_v4() {
297 let addr = IpAddr::v4(192, 168, 1, 100);
298 assert!(addr.is_private());
299 assert!(!addr.is_loopback());
300 assert_eq!(addr.to_string_repr(), "192.168.1.100");
301 }
302
303 #[test]
304 fn ip_addr_loopback() {
305 let addr = IpAddr::loopback_v4();
306 assert!(addr.is_loopback());
307 assert!(!addr.is_private());
308 }
309
310 #[test]
311 fn ip_addr_private_ranges() {
312 assert!(IpAddr::v4(10, 0, 0, 1).is_private());
313 assert!(IpAddr::v4(172, 16, 0, 1).is_private());
314 assert!(IpAddr::v4(192, 168, 0, 1).is_private());
315 assert!(!IpAddr::v4(8, 8, 8, 8).is_private());
316 }
317
318 #[test]
319 fn interface_builder() {
320 let iface = Interface::new("wlan0", "WiFi Adapter", InterfaceType::WiFi)
321 .up()
322 .with_address(IpAddr::v4(192, 168, 1, 100))
323 .with_signal(75)
324 .with_speed(300)
325 .with_mac([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
326
327 assert!(iface.is_up);
328 assert!(iface.has_address());
329 assert!(iface.is_ready());
330 assert_eq!(iface.signal_strength, Some(75));
331 assert_eq!(iface.link_speed_mbps, Some(300));
332 assert!(iface.mac.is_some());
333 }
334
335 #[test]
336 fn interface_not_ready_when_down() {
337 let iface = Interface::new("eth0", "Ethernet", InterfaceType::Ethernet)
338 .with_address(IpAddr::v4(10, 0, 0, 5));
339 assert!(!iface.is_ready()); }
341
342 #[test]
343 fn interface_not_ready_without_address() {
344 let iface = Interface::new("wlan0", "WiFi", InterfaceType::WiFi).up();
345 assert!(!iface.is_ready()); }
347
348 #[test]
349 fn effective_priority_weak_signal() {
350 let strong = Interface::new("w0", "WiFi", InterfaceType::WiFi).with_signal(80);
351 let weak = Interface::new("w1", "WiFi", InterfaceType::WiFi).with_signal(20);
352 assert!(strong.effective_priority() < weak.effective_priority());
353 }
354
355 #[test]
356 fn interface_metered_from_type() {
357 let cell = Interface::new("rmnet0", "Cellular", InterfaceType::Cellular);
358 assert!(cell.is_metered);
359 let wifi = Interface::new("wlan0", "WiFi", InterfaceType::WiFi);
360 assert!(!wifi.is_metered);
361 }
362
363 #[test]
364 fn signal_strength_clamped() {
365 let iface = Interface::new("w0", "WiFi", InterfaceType::WiFi).with_signal(255); assert_eq!(iface.signal_strength, Some(100));
367 }
368}