1mod misc;
35mod sys;
36
37use std::fmt;
38
39use crate::misc::yes_or_no;
40
41type Result<T> = std::result::Result<T, Error>;
42
43#[derive(Debug, PartialEq, Eq)]
45pub enum Error {
46 InterfaceError(String),
47 SocketError(String),
48 ScanFailed(String),
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum WifiSecurity {
55 Open,
56 Wep,
57 Wpa2PersonalPsk,
58 Wpa3PersonalSae,
59 Wpa2EnterpriseEap,
60 Wpa2EnterpriseEap256,
61 Wpa3EnterpriseEap256,
62 Wpa3EnterpriseSuiteBEap256,
63 Wpa3EnterpriseEap,
64 Wpa2EnterpriseEapFt,
65 Wpa2PersonalPskFt,
66 Wpa2PersonalPsk256,
67 Wpa3PersonalSaeFt,
68 WpaPersonalPsk,
69 WpaEnterpriseEap,
70 TunneledDirectLinkSetup,
71 Unknown,
72 Other(String),
73}
74
75#[derive(Debug, PartialEq, Eq, Default, Clone)]
77pub struct Wifi {
78 pub mac: String,
80 pub ssid: String,
82 pub channel: u32,
84 pub signal_level: i32,
86 pub security: Vec<WifiSecurity>,
88}
89
90pub enum SignalStrength {
92 Unknown,
93 Weak,
94 Fair,
95 Good,
96 Excellent,
97}
98
99impl fmt::Display for Error {
100 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 match self {
102 Error::SocketError(detail) => {
103 write!(f, "Error while creating socket: {}", detail)
104 }
105 Error::InterfaceError(detail) => {
106 write!(f, "Interface error: {}", detail)
107 }
108 Error::ScanFailed(detail) => {
109 write!(f, "Scan Failed: {}", detail)
110 }
111 }
112 }
113}
114
115impl fmt::Display for WifiSecurity {
116 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117 match self {
118 WifiSecurity::Open => write!(f, "Open"),
119 WifiSecurity::Other(sec) => write!(f, "{}", sec),
120 WifiSecurity::TunneledDirectLinkSetup => write!(f, "Tunneled Direct Link Setup"),
121 WifiSecurity::Unknown => write!(f, "Unknown"),
122 WifiSecurity::Wep => write!(f, "WEP"),
123 WifiSecurity::Wpa2EnterpriseEap => write!(f, "WPA2-Enterprise (EAP)"),
124 WifiSecurity::Wpa2EnterpriseEapFt => write!(f, "WPA2-Enterprise (EAP-FT)"),
125 WifiSecurity::Wpa2PersonalPsk => write!(f, "WPA2-Personal (PSK)"),
126 WifiSecurity::Wpa2PersonalPskFt => write!(f, "WPA2-Personal (PSK-FT)"),
127 WifiSecurity::Wpa3EnterpriseEap256 => write!(f, "WPA3-Enterprise (EAP-256)"),
128 WifiSecurity::Wpa3EnterpriseSuiteBEap256 => {
129 write!(f, "WPA3-Enterprise (Suite B EAP-256)")
130 }
131 WifiSecurity::Wpa2PersonalPsk256 => write!(f, "WPA2-Personal (PSK-256)"),
132 WifiSecurity::Wpa3PersonalSae => write!(f, "WPA3-Personal (SAE)"),
133 WifiSecurity::Wpa3PersonalSaeFt => write!(f, "WPA3-Personal (SAE-FT)"),
134 WifiSecurity::WpaEnterpriseEap => write!(f, "WPA-Enterprise"),
135 WifiSecurity::WpaPersonalPsk => write!(f, "WPA-Personal"),
136 WifiSecurity::Wpa2EnterpriseEap256 => write!(f, "WPA2-Enterprise (EPA-256)"),
137 WifiSecurity::Wpa3EnterpriseEap => write!(f, "WPA3-Enterprise (EAP)"),
138 }
139 }
140}
141
142impl fmt::Display for Wifi {
143 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144 write!(
145 f,
146 "BSSID: {} | SSID: {} | Channel: {} | RSSI: {} dBm | Security: {} | Hidden: {}",
147 self.mac,
148 self.ssid,
149 self.channel,
150 self.signal_level,
151 self.security
152 .iter()
153 .map(|s| s.to_string())
154 .collect::<Vec<_>>()
155 .join(", "),
156 yes_or_no(self.is_hidden())
157 )
158 }
159}
160
161impl Wifi {
162 pub fn is_open(&self) -> bool {
164 self.security.len() == 1 && self.security[0] == WifiSecurity::Open
165 }
166
167 pub fn is_wpa3(&self) -> bool {
169 self.security.iter().any(|s| {
170 matches!(
171 s,
172 WifiSecurity::Wpa3EnterpriseEap256
173 | WifiSecurity::Wpa3EnterpriseSuiteBEap256
174 | WifiSecurity::Wpa3PersonalSae
175 | WifiSecurity::Wpa3PersonalSaeFt
176 | WifiSecurity::Wpa3EnterpriseEap
177 )
178 })
179 }
180
181 pub fn is_wpa2(&self) -> bool {
183 self.security.iter().any(|s| {
184 matches!(
185 s,
186 WifiSecurity::Wpa2EnterpriseEap
187 | WifiSecurity::Wpa2EnterpriseEapFt
188 | WifiSecurity::Wpa2PersonalPsk
189 | WifiSecurity::Wpa2PersonalPskFt
190 | WifiSecurity::Wpa2PersonalPsk256
191 | WifiSecurity::Wpa2EnterpriseEap256
192 )
193 })
194 }
195
196 pub fn is_enterprise(&self) -> bool {
198 self.security.iter().any(|s| {
199 matches!(
200 s,
201 WifiSecurity::WpaEnterpriseEap
202 | WifiSecurity::Wpa2EnterpriseEap
203 | WifiSecurity::Wpa2EnterpriseEapFt
204 | WifiSecurity::Wpa3EnterpriseEap256
205 | WifiSecurity::Wpa3EnterpriseSuiteBEap256
206 | WifiSecurity::Wpa3EnterpriseEap
207 | WifiSecurity::Wpa2EnterpriseEap256
208 )
209 })
210 }
211
212 pub fn is_personal(&self) -> bool {
214 self.security.iter().any(|s| {
215 matches!(
216 s,
217 WifiSecurity::WpaPersonalPsk
218 | WifiSecurity::Wpa2PersonalPsk
219 | WifiSecurity::Wpa2PersonalPskFt
220 | WifiSecurity::Wpa3PersonalSae
221 | WifiSecurity::Wpa3PersonalSaeFt
222 | WifiSecurity::Wpa2PersonalPsk256
223 )
224 })
225 }
226
227 pub fn readable_signal(&self) -> SignalStrength {
229 match self.signal_level {
230 0 => SignalStrength::Unknown,
231 -50..=0 => SignalStrength::Excellent,
232 -70..=-61 => SignalStrength::Good,
233 -80..=-71 => SignalStrength::Fair,
234 _ => SignalStrength::Weak,
235 }
236 }
237
238 pub fn is_hidden(&self) -> bool {
240 self.ssid.is_empty()
241 }
242
243 pub fn get_frequency(&self) -> u32 {
245 match self.channel {
246 1..=13 => 2407 + self.channel * 5, 14 => 2484, 36..=165 => 5000 + self.channel * 5, 167..=233 => 5950 + (self.channel - 1) * 5, _ => 0, }
252 }
253}
254
255impl std::error::Error for Error {}
256
257pub trait WlanScanner {
258 fn scan(&mut self) -> Result<Vec<Wifi>>;
259}
260
261pub fn scan() -> Result<Vec<Wifi>> {
272 #[cfg(target_os = "macos")]
273 let mut scanner = sys::macos::ScanMac;
274
275 #[cfg(target_os = "linux")]
276 let mut scanner = sys::linux::ScanLinux;
277
278 #[cfg(target_os = "windows")]
279 let mut scanner = sys::windows::ScanWindows;
280
281 #[cfg(target_os = "openbsd")]
282 let mut scanner = sys::openbsd::ScanOpenBsd;
283
284 #[cfg(target_os = "freebsd")]
285 let mut scanner = sys::freebsd::ScanFreeBsd;
286
287 #[cfg(target_os = "netbsd")]
288 let mut scanner = sys::netbsd::ScanNetBsd;
289
290 #[cfg(not(any(
291 target_os = "macos",
292 target_os = "linux",
293 target_os = "windows",
294 target_os = "openbsd",
295 target_os = "freebsd",
296 target_os = "netbsd"
297 )))]
298 compile_error!("wifi_scan does not support this platform");
299
300 scanner.scan()
301}