wpa_ctrl/
lib.rs

1//!WPA controller
2//!
3//!## Usage
4//!
5//!```rust,no_run
6//!use wpa_ctrl::{WpaControlReq, WpaControllerBuilder};
7//!
8//!const WPA_CTRL_BUILD: WpaControllerBuilder<'static> = WpaControllerBuilder::new();
9//!
10//!let mut ctrl = match WPA_CTRL_BUILD.open("wlan0") {
11//!    Ok(ctrl) => ctrl,
12//!    Err(error) => panic!("Cannot open wlan0"),
13//!};
14//!
15//!ctrl.request(WpaControlReq::status()).expect("Successful command");
16//!while let Some(resp) = ctrl.recv().expect("To read message") {
17//!    //Skip messages that are not intended as responses
18//!    if resp.is_unsolicited() {
19//!        continue;
20//!    }
21//!
22//!    if let Some(status) = resp.as_status() {
23//!        println!("Network status={:?}", status);
24//!        break;
25//!    }
26//!}
27//!```
28//!
29//!## Usage scenarios
30//!
31//!### Add new network
32//!
33//!- Optionally `scan` and check list of networks using `scan_results`
34//!- `add_network` which returns returns `id` of network
35//!- `set_network <id> ssid "network name"` which specifies network's name to associate with
36//!- `set_network <id> psk "WAP password"` which specifies WPA password, only usable when network
37//!requires WPA security
38//!- `set_network <id> key_mgmt NONE` which specifies no security, required to connect to networks
39//!without password
40//!- `select_network <id>` - Select network for use.
41//!- `save_config` - Optionally to save configuration.
42//!
43//!### Reconnect
44//!
45//!- Optionally `disconnect`;
46//!- Run `reassociate` to start process of connecting to currently selected network
47
48#![cfg(unix)]
49#![warn(missing_docs)]
50#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
51
52#[cfg(feature = "serde")]
53use serde::{Serialize, Deserialize};
54
55mod utils;
56
57const BUF_SIZE: usize = 512;
58const DEFAULT_ROOT: &str = "/var/run/wpa_supplicant/";
59
60#[cfg(not(unix))]
61compile_error!("Supports only unix targets");
62
63#[cfg(target_os = "android")]
64const LOCAL_SOCKET_DIR: &str = "/data/misc/wifi/sockets";
65#[cfg(not(target_os = "android"))]
66const LOCAL_SOCKET_DIR: &str = "/tmp";
67const LOCAL_SOCKET_PREFIX: &str = "wpa_crtl_";
68const UNSOLICITED_PREFIX: char = '<';
69type LocalSocketName = str_buf::StrBuf<23>;
70
71use std::os::unix::net::UnixDatagram;
72use std::{fs, io, path, net};
73use core::{str, time};
74use core::sync::atomic::{AtomicU32, Ordering};
75use core::fmt::{self, Write};
76
77///Surrounds value with quotes, useful when setting `ssid` or `psk`
78pub struct QuotedValue<T: fmt::Display>(pub T);
79
80impl<T: fmt::Display> fmt::Display for QuotedValue<T> {
81    #[inline]
82    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
83        fmt.write_fmt(format_args!("\"{}\"", self.0))
84    }
85}
86
87fn local_socket_name() -> LocalSocketName {
88    static COUNTER: AtomicU32 = AtomicU32::new(1);
89
90    let mut name = LocalSocketName::new();
91    let _ = write!(&mut name, "{}{}", LOCAL_SOCKET_PREFIX, COUNTER.fetch_add(1, Ordering::SeqCst));
92    name
93}
94
95///Client builder
96#[derive(Copy, Clone, Debug)]
97pub struct WpaControllerBuilder<'a> {
98    ///Folder where to look up wpa_supplicant's interfaces.
99    pub root: &'a str,
100    ///Read timeout for responses.
101    pub read_timeout: Option<time::Duration>,
102}
103
104impl WpaControllerBuilder<'static> {
105    ///Creates default instance.
106    ///
107    ///- `root` - `/var/run/wpa_supplicant/`
108    ///- `read_timeout` - `Some(Duration::from_secs(10))`
109    pub const fn new() -> Self {
110        Self {
111            root: DEFAULT_ROOT,
112            read_timeout: Some(time::Duration::from_secs(10)),
113        }
114    }
115}
116
117impl<'a> WpaControllerBuilder<'a> {
118    #[inline]
119    #[allow(clippy::needless_lifetimes)]
120    ///Changes root folder
121    pub const fn set_root<'b>(self, new: &'b str) -> WpaControllerBuilder<'b> {
122        WpaControllerBuilder {
123            root: new,
124            read_timeout: self.read_timeout,
125        }
126    }
127
128    #[inline]
129    ///Changes read_timeout
130    ///
131    ///If None, then block indefinitely, otherwise return error on timeout.
132    ///This library handles timeout error, returning `None` response
133    pub const fn set_read_timeout(mut self, read_timeout: Option<time::Duration>) -> Self {
134        self.read_timeout = read_timeout;
135        self
136    }
137
138    #[inline]
139    ///Attempts to open socket.
140    pub fn open(self, interface: &str) -> Result<WpaController, io::Error> {
141        let path = path::Path::new(self.root).join(interface);
142        WpaController::open_path(&path)
143    }
144}
145
146///Request type
147///
148///Max message size 127 bytes
149pub struct WpaControlReq {
150    buf: str_buf::StrBuf<127>,
151}
152
153impl WpaControlReq {
154    #[inline]
155    ///Creates raw request, 127 bytes maximum
156    ///
157    ///Panics on overflow.
158    pub const fn raw(text: &str) -> Self {
159        Self {
160            buf: str_buf::StrBuf::from_str(text)
161        }
162    }
163
164    #[inline]
165    ///Creates PING request
166    pub const fn ping() -> Self {
167        Self::raw("PING")
168    }
169
170    #[inline]
171    ///Creates STATUS request
172    pub const fn status() -> Self {
173        Self::raw("STATUS")
174    }
175
176    #[inline]
177    ///Creates SCAN request
178    pub const fn scan() -> Self {
179        Self::raw("SCAN")
180    }
181
182    #[inline]
183    ///Creates SCAN_RESULTS request
184    pub const fn scan_results() -> Self {
185        Self::raw("SCAN_RESULTS")
186    }
187
188    #[inline]
189    ///Creates DISCONNECT request
190    pub const fn disconnect() -> Self {
191        Self::raw("DISCONNECT")
192    }
193
194    #[inline]
195    ///Creates REASSOCIATE request
196    pub const fn reassociate() -> Self {
197        Self::raw("REASSOCIATE")
198    }
199
200    #[inline]
201    ///Creates LIST_NETWORKS request
202    pub const fn list_networks() -> Self {
203        Self::raw("LIST_NETWORKS")
204    }
205
206    #[inline]
207    ///Creates ENABLE_NETWORK request
208    pub fn add_network() -> Self {
209        Self::raw("ADD_NETWORK")
210    }
211
212    #[inline]
213    ///Creates GET_NETWORK request
214    pub fn get_network(id: Id, var: &str) -> Self {
215        let mut this = Self::raw("SET_NETWORK");
216        let _ = write!(&mut this.buf, " {}", id.0);
217        this.buf.push_str(" ");
218        this.buf.push_str(var);
219        this
220    }
221
222    #[inline]
223    ///Creates SET_NETWORK request
224    pub fn set_network(id: Id, var: &str, value: impl fmt::Display) -> Self {
225        let mut this = Self::raw("SET_NETWORK");
226        let _ = write!(&mut this.buf, " {}", id.0);
227        this.buf.push_str(" ");
228        this.buf.push_str(var);
229        let _ = write!(&mut this.buf, " {}", value);
230        this
231    }
232
233    #[inline]
234    ///Creates SELECT_NETWORK request
235    pub fn select_network(id: Id) -> Self {
236        let mut this = Self::raw("SELECT_NETWORK");
237        let _ = write!(&mut this.buf, " {}", id.0);
238        this
239    }
240
241    #[inline]
242    ///Creates ENABLE_NETWORK request
243    pub fn enable_network(id: Id) -> Self {
244        let mut this = Self::raw("ENABLE_NETWORK");
245        let _ = write!(&mut this.buf, " {}", id.0);
246        this
247    }
248
249    #[inline]
250    ///Creates DISABLE_NETWORK request
251    pub fn disable_network(id: Id) -> Self {
252        let mut this = Self::raw("DISABLE_NETWORK");
253        let _ = write!(&mut this.buf, " {}", id.0);
254        this
255    }
256
257    #[inline]
258    ///Creates REMOVE_NETWORK request
259    pub fn remove_network(id: Id) -> Self {
260        let mut this = Self::raw("REMOVE_NETWORK");
261        let _ = write!(&mut this.buf, " {}", id.0);
262        this
263    }
264
265    #[inline]
266    ///Creates REMOVE_NETWORK all request
267    pub fn remove_network_all() -> Self {
268        Self::raw("REMOVE_NETWORK all")
269    }
270
271
272    #[inline]
273    ///Creates SAVE_CONFIG request
274    pub fn save_config() -> Self {
275        Self::raw("SAVE_CONFIG")
276    }
277}
278
279impl fmt::Debug for WpaControlReq {
280    #[inline(always)]
281    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
282        fmt::Debug::fmt(&self.buf, fmt)
283    }
284}
285
286
287#[derive(Copy, Clone, Debug)]
288///Indicates success of command
289pub struct Success;
290
291#[derive(Copy, Clone, Debug)]
292///Indicates failure of command
293pub struct Fail;
294
295#[derive(Copy, Clone, Debug)]
296///Pong Message
297pub struct Pong;
298
299#[derive(Copy, Clone, Debug)]
300#[repr(transparent)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302#[cfg_attr(feature = "serde", serde(transparent))]
303///Network id
304pub struct Id(pub u32);
305
306#[derive(Copy, Clone, Debug)]
307///Interface state
308pub enum WpaState {
309    ///Not recognized state
310    Unknown,
311    ///Disconnected state.
312    ///
313    ///This state indicates that client is not associated, but is likely to start looking for an
314    ///access point. This state is entered when a connection is lost.
315    Disconnected,
316    ///Interface disabled.
317    ///
318    ///This state is entered if the network interface is disabled, e.g., due to rfkill.
319    ///wpa_supplicant refuses any new operations that would use the radio until the interface has
320    ///been enabled.
321    InterfaceDisabled,
322    ///Inactive state (wpa_supplicant disabled)
323    ///
324    ///This state is entered if there are no enabled networks in the configuration. wpa_supplicant
325    ///is not trying to associate with a new network and external interaction (e.g., ctrl_iface
326    ///call to add or enable a network) is needed to start association.
327    Inactive,
328    ///Scanning for a network.
329    ///
330    ///This state is entered when wpa_supplicant starts scanning for a network.
331    Scanning,
332    ///Trying to authenticate with a BSS/SSID.
333    ///
334    ///This state is entered when wpa_supplicant has found a suitable BSS to authenticate with and
335    ///the driver is configured to try to authenticate with this BSS. This state is used only with
336    ///drivers that use wpa_supplicant as the SME.
337    Authenticating,
338    ///Trying to associate with a BSS/SSID.
339    ///
340    ///This state is entered when wpa_supplicant has found a suitable BSS to associate with and the
341    ///driver is configured to try to associate with this BSS in ap_scan=1 mode. When using
342    ///ap_scan=2 mode, this state is entered when the driver is configured to try to associate with
343    ///a network using the configured SSID and security policy.
344    Associating,
345    ///Association completed.
346    ///
347    ///This state is entered when the driver reports that association has been successfully
348    ///completed with an AP. If IEEE 802.1X is used (with or without WPA/WPA2), wpa_supplicant
349    ///remains in this state until the IEEE 802.1X/EAPOL authentication has been completed.
350    Associated,
351    ///WPA 4-Way Key Handshake in progress.
352    ///
353    ///This state is entered when WPA/WPA2 4-Way Handshake is started. In case of WPA-PSK, this
354    ///happens when receiving the first EAPOL-Key frame after association. In case of WPA-EAP, this
355    ///state is entered when the IEEE 802.1X/EAPOL authentication has been completed.
356    Handshake,
357    ///This state is entered when 4-Way Key Handshake has been completed (i.e., when the supplicant
358    ///sends out message 4/4) and when Group Key rekeying is started by the AP (i.e., when
359    ///supplicant receives message 1/2).
360    GroupHandshake,
361    ///Connected and authenticated.
362    Completed
363}
364
365impl WpaState {
366    ///Parses status from its textual representation
367    pub fn from_str(text: &str) -> Self {
368        if text.eq_ignore_ascii_case("COMPLETED") {
369            Self::Completed
370        } else if text.eq_ignore_ascii_case("GROUP_HANDSHAKE") {
371            Self::GroupHandshake
372        } else if text.eq_ignore_ascii_case("4WAY_HANDSHAKE") {
373            Self::Handshake
374        } else if text.eq_ignore_ascii_case("ASSOCIATED") {
375            Self::Associated
376        } else if text.eq_ignore_ascii_case("ASSOCIATING") {
377            Self::Associating
378        } else if text.eq_ignore_ascii_case("AUTHENTICATING") {
379            Self::Authenticating
380        } else if text.eq_ignore_ascii_case("SCANNING") {
381            Self::Scanning
382        } else if text.eq_ignore_ascii_case("INACTIVE") {
383            Self::Inactive
384        } else if text.eq_ignore_ascii_case("INTERFACE_DISABLED") {
385            Self::InterfaceDisabled
386        } else if text.eq_ignore_ascii_case("DISCONNECTED") {
387            Self::Disconnected
388        } else {
389            Self::Unknown
390        }
391    }
392}
393
394#[derive(Clone, Debug)]
395///Interface status
396pub struct WpaStatus {
397    ///Interface state
398    pub state: WpaState,
399    ///Interface IP address, available if connected
400    pub ip: Option<net::IpAddr>,
401    ///SSID used by interface on successful connection.
402    pub ssid: Option<String>
403}
404
405impl WpaStatus {
406    ///Attempts to parse WPA Status from string.
407    ///
408    ///Returning `None` if it is invalid format (not lines in format `<var>=<value>`) or missing `wpa_status`
409    pub fn from_str(text: &str) -> Option<Self> {
410        let mut state = None;
411        let mut ip = None;
412        let mut ssid = None;
413
414        for line in text.lines() {
415            if line.is_empty() {
416                continue;
417            }
418
419            let mut split = line.splitn(2, '=');
420            let var = split.next().unwrap();
421            if let Some(value) = split.next() {
422                if var.eq_ignore_ascii_case("wpa_state") {
423                    state = Some(WpaState::from_str(value));
424                } else if var.eq_ignore_ascii_case("ip_address") {
425                    ip = value.parse().ok();
426                } else if var.eq_ignore_ascii_case("ssid") {
427                    ssid = Some(value.to_owned());
428                } else {
429                    //Not interested to us, so skip
430                    continue;
431                }
432            } else {
433                //STATUS output is always <var>=<value>
434                return None;
435            }
436        }
437
438        state.map(|state| Self {
439            state,
440            ip,
441            ssid,
442        })
443    }
444}
445
446
447///Network's flag, describing its current state.
448#[derive(Clone, Copy, Debug, Default)]
449#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
450pub struct WpaNetworkFlags {
451    ///Indicates that network is currently set to be used.
452    pub current: bool,
453    ///Indicates that network is disabled.
454    pub disabled: bool,
455    ///Network is part of p2p persistent group. Google what it means.
456    pub p2p_persistent: bool
457}
458
459impl WpaNetworkFlags {
460    #[inline(always)]
461    ///Parses network flags from flags string
462    pub fn from_str(mut text: &str) -> Self {
463        let mut result = WpaNetworkFlags {
464            current: false,
465            disabled: false,
466            p2p_persistent: false,
467        };
468
469        if !text.is_empty() {
470            while let Some(start_flag) = text.strip_prefix('[') {
471                if let Some(end) = start_flag.find(']') {
472                    let flag = &start_flag[..end];
473                    if flag.eq_ignore_ascii_case("CURRENT") {
474                        result.current = true;
475                    } else if flag.eq_ignore_ascii_case("DISABLED") {
476                        result.disabled = true;
477                    } else if flag.eq_ignore_ascii_case("P2P-PERSISTENT") {
478                        result.p2p_persistent = true;
479                    }
480                    text = &start_flag[end..];
481                } else {
482                    break;
483                }
484            }
485        }
486
487        result
488    }
489}
490
491///Network description
492#[derive(Clone, Debug)]
493#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
494pub struct WpaNetwork {
495    ///Network id
496    pub id: Id,
497    ///Network's SSID. Can be empty string, when not set.
498    pub ssid: String,
499    ///Network's BSSID. Can be empty string, when not set.
500    pub bssid: String,
501    ///Network's flag
502    pub flags: WpaNetworkFlags,
503}
504
505///Iterator over list of networks
506pub struct WpaNetworkList<'a> {
507    lines: str::Lines<'a>,
508}
509
510impl<'a> Iterator for WpaNetworkList<'a> {
511    type Item = WpaNetwork;
512
513    #[inline]
514    fn next(&mut self) -> Option<Self::Item> {
515        let line = self.lines.next()?;
516        let mut parts = line.splitn(4, '\t');
517        let network = WpaNetwork {
518            id: Id(parts.next().unwrap().parse().ok()?),
519            ssid: parts.next()?.to_owned(),
520            bssid: parts.next()?.to_owned(),
521            flags: WpaNetworkFlags::from_str(parts.next().unwrap_or("")),
522        };
523        Some(network)
524    }
525}
526
527///Message.
528pub struct WpaControlMessage<'a> {
529    ///Raw content of message
530    pub raw: &'a str,
531}
532
533impl<'a> WpaControlMessage<'a> {
534    #[inline(always)]
535    ///Returns whether message is unsolicited response, namely it means it is not reply to request.
536    pub const fn is_unsolicited(&self) -> bool {
537        !self.raw.is_empty() && self.raw.as_bytes()[0] == UNSOLICITED_PREFIX as u8
538    }
539
540    ///Attempts to reinterpret message as pong
541    pub fn as_pong(&self) -> Option<Pong> {
542        if self.raw.eq_ignore_ascii_case("pong") {
543            Some(Pong)
544        } else {
545            None
546        }
547    }
548
549    ///Attempts to reinterpret message as success of request
550    pub fn as_success(&self) -> Option<Success> {
551        if self.raw.eq_ignore_ascii_case("ok") {
552            Some(Success)
553        } else {
554            None
555        }
556    }
557
558    ///Attempts to reinterpret message as failure of request
559    pub fn as_fail(&self) -> Option<Fail> {
560        if self.raw.eq_ignore_ascii_case("fail") {
561            Some(Fail)
562        } else {
563            None
564        }
565    }
566
567    ///Attempts to reinterpret message as status
568    pub fn as_status(&self) -> Option<WpaStatus> {
569        WpaStatus::from_str(self.raw)
570    }
571
572    ///Attempts to reinterpret message as network id
573    pub fn as_network_id(&self) -> Option<Id> {
574        self.raw.parse().map(|id| Id(id)).ok()
575    }
576
577    ///Attempts to reinterpret message as network id
578    pub fn as_network_list(&self) -> Option<WpaNetworkList<'_>> {
579        let mut lines = self.raw.lines();
580        let line = lines.next().unwrap();
581        let header = utils::split::<4>(line, '/')?;
582        if header[0] == "network id" && header[1] == "ssid" && header[2] == "bssid" && header[3] == "flags" {
583            Some(WpaNetworkList {
584                lines
585            })
586        } else {
587            None
588        }
589    }
590}
591
592impl<'a> fmt::Debug for WpaControlMessage<'a> {
593    #[inline(always)]
594    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
595        fmt::Debug::fmt(self.raw, fmt)
596    }
597}
598
599///WPA controller
600pub struct WpaController {
601    buffer: [u8; BUF_SIZE],
602    socket: UnixDatagram,
603    local: path::PathBuf,
604}
605
606impl WpaController {
607    #[inline(always)]
608    ///Attempts to connect to WPA controller at specified `path`
609    pub fn open<P: AsRef<path::Path>>(path: P) -> Result<Self, io::Error> {
610        Self::open_path(path.as_ref())
611    }
612
613    ///Attempts to connect to WPA controller at specified `path`
614    pub fn open_path(path: &path::Path) -> Result<Self, io::Error> {
615        let local_name = local_socket_name();
616        let local = path::Path::new(LOCAL_SOCKET_DIR).join(local_name.as_str());
617        let socket = UnixDatagram::bind(&local)?;
618        let this = Self {
619            buffer: [0; BUF_SIZE],
620            socket,
621            local,
622        };
623        this.socket.connect(path)?;
624        Ok(this)
625    }
626
627    #[inline]
628    ///Sends request, returning number of bytes written.
629    pub fn request(&mut self, req: WpaControlReq) -> Result<usize, io::Error> {
630        self.socket.send(req.buf.as_bytes())
631    }
632
633    ///Attempts to receive message.
634    pub fn recv(&mut self) -> Result<Option<WpaControlMessage<'_>>, io::Error> {
635        loop {
636            match self.socket.recv(&mut self.buffer) {
637                Ok(len) => {
638                    let msg = match std::str::from_utf8(&self.buffer[..len]) {
639                        Ok(msg) => msg.trim(),
640                        Err(error) => break Err(io::Error::new(io::ErrorKind::InvalidData, error))
641                    };
642
643                    break Ok(Some(WpaControlMessage {
644                        raw: msg,
645                    }))
646                },
647                Err(error) => match error.kind() {
648                    io::ErrorKind::Interrupted => continue,
649                    io::ErrorKind::TimedOut => break Ok(None),
650                    _ => break Err(error),
651                }
652            }
653        }
654    }
655
656    ///Attempts to receive reply for result of command.
657    ///
658    ///This method will continuously `recv` skipping `unsolicited` messages
659    ///
660    ///# Result
661    ///- Returns `None` if neither success or fail are present among replies.
662    ///
663    ///- `Ok(())` indicates success.
664    ///
665    ///- `Err(())` indicates failure.
666    pub fn recv_req_result(&mut self) -> Option<Result<Result<(), ()>, io::Error>> {
667        loop {
668            match self.recv() {
669                Ok(Some(msg)) => {
670                    if msg.as_success().is_some() {
671                        break Some(Ok(Ok(())));
672                    } else if msg.as_fail().is_some() {
673                        break Some(Ok(Err(())));
674                    } else {
675                        continue
676                    }
677                },
678                Ok(None) => break None,
679                Err(error) => return Some(Err(error)),
680            }
681        }
682    }
683
684    ///Performs network add sequence
685    ///
686    ///# Arguments
687    ///
688    ///- `ssid` - Network identifier;
689    ///- `wpa_pass` - Passkey for WPA auth, if `None` sets `key_mgmt` to `None`
690    ///- `hidden` - Specifies whether you want to scan for SSID to connect to the network.
691    ///
692    ///# Result
693    ///
694    ///- `Ok(id)` - Newly created network id
695    pub fn add_network(&mut self, ssid: &str, wpa_pass: Option<&str>, hidden: bool) -> Result<Id, io::Error> {
696        self.request(WpaControlReq::add_network())?;
697        let id = loop {
698            match self.recv()? {
699                Some(msg) => match msg.as_network_id() {
700                    Some(id) => break id,
701                    None => continue,
702                },
703                None => return Err(io::Error::new(io::ErrorKind::TimedOut, "no response to add_network")),
704            }
705        };
706
707        self.request(WpaControlReq::set_network(id, "ssid", QuotedValue(ssid)))?;
708        match self.recv_req_result() {
709            Some(Ok(Ok(()))) => (),
710            Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} failed", id.0, QuotedValue(ssid)))),
711            Some(Err(error)) => return Err(error),
712            None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} had no ok/fail reply", id.0, QuotedValue(ssid)))),
713        }
714        match wpa_pass {
715            Some(wpa_pass) => {
716                let wpa_pass = QuotedValue(wpa_pass);
717                self.request(WpaControlReq::set_network(id, "psk", &wpa_pass))?;
718                match self.recv_req_result() {
719                    Some(Ok(Ok(()))) => (),
720                    Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} failed", id.0, wpa_pass))),
721                    Some(Err(error)) => return Err(error),
722                    None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} had no ok/fail reply", id.0, wpa_pass))),
723                }
724            },
725            None => {
726                self.request(WpaControlReq::set_network(id, "key_mgmt", "NONE"))?;
727                match self.recv_req_result() {
728                    Some(Ok(Ok(()))) => (),
729                    Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE failed", id.0))),
730                    Some(Err(error)) => return Err(error),
731                    None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE had no ok/fail reply", id.0))),
732                }
733            },
734        }
735
736        if hidden {
737            self.request(WpaControlReq::set_network(id, "scan_ssid", 1))?;
738            match self.recv_req_result() {
739                Some(Ok(Ok(()))) => (),
740                Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 failed", id.0))),
741                Some(Err(error)) => return Err(error),
742                None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 had no ok/fail reply", id.0))),
743            }
744        }
745
746        Ok(id)
747    }
748
749    ///Performs removal of known network by `id`.
750    pub fn remove_network(&mut self, id: Id) -> Result<(), io::Error> {
751        self.request(WpaControlReq::remove_network(id))?;
752        match self.recv_req_result() {
753            Some(Ok(Ok(()))) => Ok(()),
754            Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={}", id.0))),
755            Some(Err(error)) => return Err(error),
756            None => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={} has no reply", id.0))),
757        }
758    }
759
760    ///Select a network for use by `id`.
761    pub fn select_network(&mut self, id: Id) -> Result<(), io::Error> {
762        self.request(WpaControlReq::select_network(id))?;
763        match self.recv_req_result() {
764            Some(Ok(Ok(()))) => Ok(()),
765            Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={}", id.0))),
766            Some(Err(error)) => return Err(error),
767            None => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={} has no reply", id.0))),
768        }
769    }
770
771    ///Reconfigure wpa, i.e. reload wpasupplicant from saved config.
772    pub fn reconfigure(&mut self) -> Result<(), io::Error> {
773        self.request(WpaControlReq::raw("RECONFIGURE"))?;
774        match self.recv_req_result() {
775            Some(Ok(Ok(()))) => Ok(()),
776            Some(Ok(Err(r))) => return Err(io::Error::new(io::ErrorKind::Other, format!("reconfigure ret={:?}", r))),
777            Some(Err(error)) => return Err(error),
778            None => return Err(io::Error::new(io::ErrorKind::Other, "reconfigure has no reply".to_owned())),
779        }
780    }
781}
782
783impl Drop for WpaController {
784    #[inline]
785    fn drop(&mut self) {
786        let _ = self.socket.shutdown(net::Shutdown::Both);
787        let _ = fs::remove_file(&self.local);
788    }
789}