wifidirect_legacy_ap/
lib.rs

1use std::sync::mpsc::Sender;
2use std::sync::Mutex;
3
4use windows::core::{IInspectable, Result, HSTRING};
5use windows::Devices::WiFiDirect::{
6    WiFiDirectAdvertisementPublisher, WiFiDirectAdvertisementPublisherStatus,
7    WiFiDirectAdvertisementPublisherStatusChangedEventArgs, WiFiDirectConnectionListener,
8    WiFiDirectConnectionRequestedEventArgs, WiFiDirectConnectionStatus, WiFiDirectDevice,
9    WiFiDirectError,
10};
11use windows::Foundation::{AsyncOperationCompletedHandler, AsyncStatus, TypedEventHandler};
12use windows::Security::Credentials::PasswordCredential;
13
14pub struct WlanHostedNetworkHelper {
15    publisher: Mutex<WiFiDirectAdvertisementPublisher>,
16    message_tx: Mutex<Sender<String>>, // mutex necessary for integration with tokio
17}
18
19impl WlanHostedNetworkHelper {
20    pub fn new(ssid: &str, password: &str, message_tx: Sender<String>, success_tx: Sender<bool>) -> Result<Self> {
21        let publisher = start(ssid, password, message_tx.clone(), success_tx.clone())?;
22        Ok(WlanHostedNetworkHelper {
23            publisher: Mutex::new(publisher),
24            message_tx: Mutex::new(message_tx),
25        })
26    }
27
28    pub fn stop(&self) -> Result<()> {
29        let publisher = self
30            .publisher
31            .lock()
32            .expect("Couldn't lock publisher mutex.");
33        let status = publisher.Status()?;
34        if status == WiFiDirectAdvertisementPublisherStatus::Started {
35            publisher.Stop()?;
36            // self.tx
37            //     .lock()
38            //     .expect("Couldn't lock sender mutex.")
39            //     .send("Hosted network stopped".to_string())
40            //     .expect("Could not send on channel.");
41        } else {
42            self.message_tx
43                .lock()
44                .expect("Couldn't lock sender mutex.")
45                .send("Stop called but WiFiDirectAdvertisementPublisher is not running".to_string())
46                .expect("Could not send on channel.");
47        }
48        Ok(())
49    }
50}
51
52fn start_listener(tx: Sender<String>) -> Result<()> {
53    let listener = WiFiDirectConnectionListener::new()?;
54    let connection_requested_callback = TypedEventHandler::<
55        WiFiDirectConnectionListener,
56        WiFiDirectConnectionRequestedEventArgs,
57    >::new(move |_sender, args| {
58        tx.send("Connection requested...".to_string())
59            .expect("Couldn't send on tx");
60        let request = args
61            .as_ref()
62            .expect("args == None in connection requested callback")
63            .GetConnectionRequest()?;
64        let device_info = request.DeviceInformation()?;
65        let device_id = device_info.Id()?;
66        let wifi_direct_device = WiFiDirectDevice::FromIdAsync(&device_id)?;
67        let async_operation_completed_callback =
68            AsyncOperationCompletedHandler::<WiFiDirectDevice>::new(|async_operation, status| {
69                if status == AsyncStatus::Completed {
70                    let wfd_device = async_operation
71                        .as_ref()
72                        .expect("No device in WiFiDirectDevice AsyncOperation callback")
73                        .GetResults()?;
74                    let endpoint_pairs = wfd_device.GetConnectionEndpointPairs()?;
75                    let endpoint_pair = endpoint_pairs.GetAt(0)?;
76                    let remote_hostname = endpoint_pair.RemoteHostName()?;
77                    let _display_name = remote_hostname.DisplayName();
78                    let connection_status_changed_callback = TypedEventHandler::<
79                        WiFiDirectDevice,
80                        IInspectable,
81                    >::new(
82                        |sender, _inspectable| {
83                            let status = sender
84                                .as_ref()
85                                .expect("No sender in connection status changed handler")
86                                .ConnectionStatus()?;
87                            // TODO: do we need to do anything here? We don't need to keep track of multiple clients.
88                            // C++ seems to store them in a map but not use them? It does call remove_ConnectionStatusChanged() on the tokens when this disconnected branch hits...
89                            // So I'd like to replicate, but don't know how to reference a map of device IDs and tokens. Arc?
90                            match status {
91                                WiFiDirectConnectionStatus::Disconnected => {
92                                    let _device_id = sender
93                                        .as_ref()
94                                        .expect("No sender in connection status changed handler")
95                                        .DeviceId()?;
96                                }
97                                _ => (),
98                            }
99                            Ok(())
100                        },
101                    );
102                    // In https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/WiFiDirectLegacyAP/cpp/WlanHostedNetworkWinRT.cpp,
103                    // they store this token and the device ID in maps to keep track of connected clients. they don't seem to do anything with them though.
104                    // skipping now as it's not necessary for our purposes.
105                    let _event_registration_token =
106                        wfd_device.ConnectionStatusChanged(&connection_status_changed_callback);
107                }
108                Ok(())
109            });
110        wifi_direct_device.SetCompleted(&async_operation_completed_callback)?;
111        Ok(())
112    });
113    listener.ConnectionRequested(&connection_requested_callback)?;
114    Ok(())
115}
116
117fn start(
118    ssid: &str,
119    password: &str,
120    message_tx: Sender<String>,
121    success_tx: Sender<bool>,
122) -> Result<WiFiDirectAdvertisementPublisher> {
123    let publisher = WiFiDirectAdvertisementPublisher::new()?;
124
125    // add status changed handler
126    let _ssid = ssid.to_string();
127    let publisher_status_changed_callback = TypedEventHandler::<
128        WiFiDirectAdvertisementPublisher,
129        WiFiDirectAdvertisementPublisherStatusChangedEventArgs,
130    >::new(move |_sender, args| {
131        let status = args
132            .as_ref()
133            .expect("args == None in status change callback")
134            .Status()?;
135        match status {
136            WiFiDirectAdvertisementPublisherStatus::Created => message_tx
137                .send("Hosted network created".to_string())
138                .expect("Couldn't send on tx"),
139            WiFiDirectAdvertisementPublisherStatus::Stopped => message_tx
140                .send("Hosted network stopped".to_string())
141                .expect("Couldn't send on tx"),
142            WiFiDirectAdvertisementPublisherStatus::Started => {
143                start_listener(message_tx.clone())?;
144                message_tx.send(format!("Hosted network {} has started", _ssid))
145                    .expect("Couldn't send on tx");
146                // tell caller we started hotspot
147                success_tx.send(true).expect("Couldn't send hotspot creation success");
148            }
149            WiFiDirectAdvertisementPublisherStatus::Aborted => {
150                let err = match args
151                    .as_ref()
152                    .expect("args == None in status change callback")
153                    .Error()
154                    .expect("Couldn't get error")
155                {
156                    WiFiDirectError::RadioNotAvailable => "Radio not available",
157                    WiFiDirectError::ResourceInUse => "Resource in use",
158                    WiFiDirectError::Success => "No WiFi Direct-capable card or other error",
159                    _ => panic!("got bad WiFiDirectError"),
160                };
161                message_tx.send(format!("Hosted network aborted: {}", err))
162                    .expect("Couldn't send on tx");
163                // tell caller we failed to start hotspot
164                success_tx.send(false).expect("Couldn't send hotspot creation failure");
165            }
166            _ => panic!("Bad status received in callback."),
167        }
168        Ok(())
169    });
170    publisher.StatusChanged(&publisher_status_changed_callback)?;
171
172    // set advertisement required settings
173    let advertisement = publisher
174        .Advertisement()
175        .expect("Error getting advertisement");
176    advertisement.SetIsAutonomousGroupOwnerEnabled(true)?;
177
178    // set ssid and password
179    let legacy_settings = advertisement.LegacySettings()?;
180    legacy_settings.SetIsEnabled(true)?;
181    let _ssid = HSTRING::from(ssid);
182    legacy_settings.SetSsid(&_ssid)?;
183    let password_credential = PasswordCredential::new()?;
184    password_credential.SetPassword(&HSTRING::from(password))?;
185    legacy_settings.SetPassphrase(&password_credential)?;
186
187    // Start the advertisement, which will create an access point that other peers can connect to
188    publisher.Start()?;
189
190    Ok(publisher)
191}
192
193#[cfg(test)]
194mod tests {
195    use crate::WlanHostedNetworkHelper;
196    use std::sync::mpsc;
197    use std::thread::spawn;
198
199    // run with `cargo test -- --nocapture` to see output
200    #[test]
201    fn run_hosted_network() {
202        // Make channels to receive messages from Windows Runtime
203        let (message_tx, message_rx) = mpsc::channel::<String>();
204        let (success_tx, success_rx) = mpsc::channel::<bool>();
205        let wlan_hosted_network_helper =
206            WlanHostedNetworkHelper::new("WiFiDirectTestNetwork", "TestingThisLibrary", message_tx, success_tx)
207                .unwrap();
208
209        // Listen for messages in a thread that will exit when the hotspot is done and the sender closes
210        spawn(move || loop {
211            let msg = match message_rx.recv() {
212                Ok(m) => m,
213                Err(e) => {
214                    println!("WiFiDirect thread exiting: {}", e);
215                    break;
216                }
217            };
218            println!("{}", msg);
219        });
220
221        // Wait to see whether we were able to start the hotspot
222        let started = success_rx.recv().unwrap();
223        if !started {
224            panic!("Failed to start hotspot");
225        }
226
227        // Use the hosted network
228        std::thread::sleep(std::time::Duration::from_secs(10));
229
230        // Stop it when done
231        wlan_hosted_network_helper.stop().expect("Error in stop()");
232    }
233}