corewlan_sys/
hli.rs

1#![allow(non_snake_case)]
2
3use crate::raw::{ICWWiFiClient, INSString, ICWInterface, INSArray, NSString, INSError, INSSet, ICWNetwork, ICWChannel};
4
5/// "A wrapper around the entire Wi-Fi subsystem that you use to access interfaces and set up event
6/// notifications."
7pub struct CWWiFiClient {
8    raw: crate::raw::CWWiFiClient
9}
10
11impl CWWiFiClient {
12    /// "The shared Wi-Fi client object."
13    pub fn sharedWiFiClient() -> CWWiFiClient {
14        unsafe { CWWiFiClient { raw: crate::raw::CWWiFiClient::sharedWiFiClient() } }
15    }
16    /// "Initializes a Wi-Fi client object."
17    /// You probably want to use [CWWiFiClient::sharedWiFiClient].
18    pub fn init() -> CWWiFiClient {
19        unsafe {
20            CWWiFiClient {
21                raw: crate::raw::CWWiFiClient(
22                    crate::raw::CWWiFiClient::init(
23                        &crate::raw::CWWiFiClient::alloc()
24                    )
25                )
26            }
27        }
28    }
29    /// "Returns the default Wi-Fi interface."
30    pub fn interface(&self) -> CWInterface {
31        unsafe { CWInterface { raw: self.raw.interface() } }
32    }
33    /// "Returns the Wi-Fi interface with the given name."
34    /// TODO: Test what happens when an invalid interface name is passed.
35    pub fn interface_with_name(&self, name: &str) -> CWInterface {
36        let out_name = &format!("{}\0", name);
37        unsafe { CWInterface { raw: self.raw.interfaceWithName_(crate::raw::NSString(crate::raw::NSString::stringWithUTF8String(out_name.as_bytes()))) } }
38    }
39    /// "Returns all available Wi-Fi interfaces."
40    pub fn interfaces(&self) -> Vec<CWInterface> {
41        let mut final_interfaces = vec![];
42        unsafe {
43            let interfaces = self.raw.interfaces();
44            let arr_len = <crate::raw::NSArray as INSArray<crate::raw::CWInterface>>::count(&interfaces);
45            for i in 0..arr_len {
46                final_interfaces.push(CWInterface { raw: crate::raw::CWInterface(<crate::raw::NSArray as INSArray<crate::raw::CWInterface>>::objectAtIndex_(&interfaces, i)) } );
47            }
48        }
49        return final_interfaces;
50    }
51    /// "Returns the list of the names of available Wi-Fi interfaces."
52    pub fn interfaceNames(&self) -> Vec<String> {
53        let mut final_interface_names = vec![];
54        unsafe {
55            let interface_names = crate::raw::CWWiFiClient::interfaceNames();
56            let arr_len = <crate::raw::NSArray as INSArray<crate::raw::NSString>>::count(&interface_names);
57            for i in 0..arr_len {
58                let nsstring = crate::raw::NSString(<crate::raw::NSArray as INSArray<crate::raw::NSString>>::objectAtIndex_(&interface_names, i));
59                let cstring = std::ffi::CStr::from_ptr(nsstring.UTF8String());
60                let new_utf8 = cstring.to_str().unwrap();
61                let safe_utf8 = String::from(new_utf8);
62                final_interface_names.push(safe_utf8);
63            }
64        }
65        return final_interface_names;
66    }
67}
68
69/// "Encapsulates an IEEE 802.11 interface."
70pub struct CWInterface {
71    raw: crate::raw::CWInterface
72}
73
74impl CWInterface {
75    /// "Scans for networks."
76    /// 
77    /// Scanning more than once every 10 seconds leads to an error.
78    pub fn scanForNetworksWithName(&self, name: Option<String>) -> Result<Vec<CWNetwork>, ()> {
79        let mut final_networks = vec![];
80        unsafe {
81            if let Some(name) = name {
82                let modified_name = &format!("{}\0", name);
83                let network_name = crate::raw::NSString(crate::raw::NSString::stringWithUTF8String(modified_name.as_bytes()));
84                let potential_error = &mut crate::raw::NSError::alloc() as *mut crate::raw::NSError;
85                let networks = self.raw.scanForNetworksWithName_error_(network_name, potential_error);
86                if potential_error.as_ref().unwrap().code() != 0 {
87                    // TODO: proper error codes!
88                    println!("ERROR CODE #{}", potential_error.as_ref().unwrap().code());
89                    return Err(());
90                }
91                let networks_nsarr = <crate::raw::NSSet as INSSet<crate::raw::CWNetwork>>::allObjects(&networks);
92                let arr_len = <crate::raw::NSArray as INSArray<crate::raw::CWNetwork>>::count(&networks_nsarr);
93                for i in 0..arr_len {
94                    let instance = crate::raw::CWNetwork(<crate::raw::NSArray as INSArray<crate::raw::CWNetwork>>::objectAtIndex_(&networks_nsarr, i));
95                    final_networks.push(CWNetwork { raw: instance });
96                }
97            }
98            else {
99                let potential_error = &mut crate::raw::NSError::alloc() as *mut crate::raw::NSError;
100                let networks = self.raw.scanForNetworks_error_(potential_error);
101                if potential_error.as_ref().unwrap().code() != 0 {
102                    // TODO: proper error codes!
103                    println!("ERROR CODE #{}", potential_error.as_ref().unwrap().code());
104                    return Err(());
105                }
106                let networks_nsarr = <crate::raw::NSSet as INSSet<crate::raw::CWNetwork>>::allObjects(&networks);
107                let arr_len = <crate::raw::NSArray as INSArray<crate::raw::CWNetwork>>::count(&networks_nsarr);
108                for i in 0..arr_len {
109                    let instance = crate::raw::CWNetwork(<crate::raw::NSArray as INSArray<crate::raw::CWNetwork>>::objectAtIndex_(&networks_nsarr, i));
110                    final_networks.push(CWNetwork { raw: instance });
111                }
112            }
113        }
114        return Ok(final_networks);
115    }
116    /// "Disassociates from the current network."
117    pub fn disassociate(&self) {
118        unsafe { self.raw.disassociate() }
119    }
120}
121
122/// "Encapsulates an IEEE 802.11 network, providing read-only accessors to various properties of the
123/// network."
124pub struct CWNetwork {
125    raw: crate::raw::CWNetwork
126}
127
128impl CWNetwork {
129    /// "Method for determining which security types a network supports."
130    pub fn supportsSecurity(&self, security: CWSecurity) -> bool {
131        unsafe { return self.raw.supportsSecurity_(security as i64); }
132    }
133    /// "Method for determining which PHY modes a network supports."
134    pub fn supportsPHYMode(&self, mode: CWPHYMode) -> bool {
135        unsafe { return self.raw.supportsPHYMode_(mode as i64); }
136    }
137    /// "The beacon interval (ms) for the network."
138    pub fn beaconInterval(&self) -> i64 {
139        unsafe { return self.raw.beaconInterval(); }
140    }
141    /// "The basic service set identifier (BSSID) for the network."
142    /// 
143    /// This value is not typically returned. Getting it to work is finicky. Try googling 'bssid
144    /// CoreWLAN macOS' and pray.
145    /// 
146    /// Further notes:
147    /// 
148    /// Afaik if the following are true this should return a valid value:
149    /// - CoreLocation::CLLocationManager::requestAlwaysAuthorization()
150    /// - Executable is signed
151    /// 
152    /// I've been unable to test/reproduce this.
153    pub fn bssid(&self) -> Option<String> {
154        unsafe {
155            // SAFTEY: This block checks if a string or null pointer was returned manually. It's not
156            // clean, it's not the best soulution. It works. This shouldn't cause issues, but if it
157            // can, or you have a better soulution, please open an issue immediately.
158            // attempt to grab the bssid string
159            let nsstring = self.raw.bssid();
160            // get a pointer to our theoretical string
161            let raw_val = &nsstring as *const NSString;
162            // convert it to a pointer to a u8 (this is the danger)
163            let to_u8 = raw_val as *const u8;
164            // check if the value stored there = 0
165            if to_u8.as_ref().unwrap() == &0 {
166                // then there's not a string!
167                return None;
168            }
169            // otherwise there is a string, ignore the pointer nonsense and carry on
170            let cstring = std::ffi::CStr::from_ptr(nsstring.UTF8String());
171            return Some(cstring.to_str().unwrap().clone().to_string());
172        }
173    }
174    /// "The country code (ISO/IEC 3166-1:1997) for the network."
175    /// 
176    /// Requesting this information also requires location services permissions. See
177    /// [CWNetwork::bssid] for how you might get this information.
178    pub fn countryCode(&self) -> Option<String> {
179        unsafe {
180            // SAFTEY: This block checks if a string or null pointer was returned manually. It's not
181            // clean, it's not the best soulution. It works. This shouldn't cause issues, but if it
182            // can, or you have a better soulution, please open an issue immediately.
183            let nsstring = self.raw.countryCode();
184            // get a pointer to our theoretical string
185            let raw_val = &nsstring as *const NSString;
186            // convert it to a pointer to a u8 (this is the danger)
187            let to_u8 = raw_val as *const u8;
188            // check if the value stored there = 0
189            if to_u8.as_ref().unwrap() == &0 {
190                // then there's not a string!
191                return None;
192            }
193            let cstring = std::ffi::CStr::from_ptr(nsstring.UTF8String());
194            let new_utf8 = cstring.to_str().unwrap();
195            return Some(String::from(new_utf8));
196        }
197    }
198    /// "The network is an IBSS network."
199    /// 
200    /// IBSS networks are essentially peer-to-peer networks.
201    pub fn ibss(&self) -> bool {
202        unsafe { return self.raw.ibss(); }
203    }
204    /// "The aggregate noise measurement (dBm) for the network."
205    pub fn noiseMeasurement(&self) -> i64 {
206        unsafe { return self.raw.noiseMeasurement(); }
207    }
208    /// "The aggregate received signal strength indication (RSSI) measurement (dBm) for the
209    /// network."
210    pub fn rssiValue(&self) -> i64 {
211        unsafe { return self.raw.rssiValue(); }
212    }
213    /// "The service set identifier (SSID) for the network."
214    pub fn ssid(&self) -> String {
215        unsafe {
216            let nsstring = self.raw.ssid();
217            let cstring = std::ffi::CStr::from_ptr(nsstring.UTF8String());
218            let new_utf8 = cstring.to_str().unwrap();
219            // SAFTEY: There is no promise on the lifetime of the returned NSString, so we create a
220            // new string using the data while we know it's good.
221            return String::from(new_utf8);
222        }
223    }
224    /// "The channel for the network."
225    pub fn wlanChannel(&self) -> CWChannel {
226        let (number, width, band);
227        unsafe {
228            let raw_channel = self.raw.wlanChannel();
229            number = raw_channel.channelNumber();
230            // The OS has "Unknown" values for both these enums, and so should never return an
231            // invalid value to us.
232            width = CWChannelWidth::try_from_i64(raw_channel.channelWidth()).unwrap();
233            band = CWChannelBand::try_from_i64(raw_channel.channelBand()).unwrap();
234        }
235        return CWChannel { number, width, band };
236    }
237
238}
239
240#[repr(i64)]
241#[derive(Clone, Copy, PartialEq, Eq, Debug)]
242/// "CoreWLAN security types."
243pub enum CWSecurity {
244    /// "Open System authentication."
245    None = 0,
246    /// "WEP security."
247    WEP = 1,
248    /// "WPA Personal authentication."
249    WPAPersonal = 2,
250    /// "WPA/WPA2 Personal authentication."
251    WPAPersonalMixed = 3,
252    /// "WPA2 Personal authentication."
253    WPA2Personal = 4,
254    /// "Personal authentication."
255    Personal = 5,
256    /// "Dynamic WEP security."
257    DynamicWEP = 6,
258    /// "WPA Enterprise authentication."
259    WPAEnterprise = 7,
260    /// "WPA/WPA2 Enterprise authentication."
261    WPAEnterpriseMixed = 8,
262    /// "WPA2 Enterprise authentication."
263    WPA2Enterprise = 9,
264    /// "Enterprise authentication."
265    Enterprise = 10,
266    /// "WPA3 Personal authentication."
267    WPA3Personal = 11,
268    /// "WPA3 Enterprise authentication."
269    WPA3Enterprise = 12,
270    /// "WPA3 Transition (WPA3/WPA2 Personal) authentication."
271    WPA3Transition = 13,
272    /// "Unknown security type."
273    Unknown = 9223372036854775807,
274}
275
276#[repr(i64)]
277#[derive(Clone, Copy, PartialEq, Eq, Debug)]
278/// "CoreWLAN physical layer modes."
279pub enum CWPHYMode {
280    /// "No specified mode."
281    None = 0,
282    /// "IEEE 802.11a PHY."
283    M11a = 1,
284    /// "IEEE 802.11b PHY."
285    M11b = 2,
286    /// "IEEE 802.11g PHY."
287    M11g = 3,
288    /// "IEEE 802.11n PHY."
289    M11n = 4,
290    /// "IEEE 802.11ac PHY."
291    M11ac = 5,
292    // Labeled weird in Apple's documentation, maybe normal, maybe not?
293    M11ax = 6,
294}
295
296pub struct CWChannel {
297    // ???
298    pub number: i64,
299    /// Specifies the width of this channel in MHz
300    pub width: CWChannelWidth,
301    /// Specifies the 2.4 or 5GHz band
302    pub band: CWChannelBand,
303}
304
305#[repr(i64)]
306#[derive(Clone, Copy, PartialEq, Eq, Debug)]
307/// "The channel width."
308pub enum CWChannelWidth {
309    /// "Unknown channel width."
310    Unknown = 0,
311    /// "20MHz channel width."
312    W20MHz = 1,
313    /// "40MHz channel width."
314    W40MHz = 2,
315    /// "80MHz channel width."
316    W80MHz = 3,
317    /// "160MHz channel width."
318    W160MHz = 4,
319}
320
321impl CWChannelWidth {
322    pub fn try_from_i64(data: i64) -> Option<CWChannelWidth> {
323        match data {
324            x if x == Self::Unknown as i64 => Some(Self::Unknown),
325            x if x == Self::W20MHz as i64 => Some(Self::W20MHz),
326            x if x == Self::W40MHz as i64 => Some(Self::W40MHz),
327            x if x == Self::W80MHz as i64 => Some(Self::W80MHz),
328            x if x == Self::W160MHz as i64 => Some(Self::W160MHz),
329            _ => None
330        }
331    }
332}
333
334#[repr(i64)]
335#[derive(Clone, Copy, PartialEq, Eq, Debug)]
336/// "The channel band."
337pub enum CWChannelBand {
338    /// "Unknown channel band."
339    Unknown = 0,
340    /// "2.4GHz channel band."
341    B2GHz = 1,
342    /// "5GHz channel band."
343    B5GHz = 2,
344}
345
346impl CWChannelBand {
347    pub fn try_from_i64(data: i64) -> Option<CWChannelBand> {
348        match data {
349            x if x == Self::Unknown as i64 => Some(Self::Unknown),
350            x if x == Self::B2GHz as i64 => Some(Self::B2GHz),
351            x if x == Self::B5GHz as i64 => Some(Self::B5GHz),
352            _ => None
353        }
354    }
355}