1use std::sync::mpsc::{self, Receiver, Sender};
40
41use quick_protobuf::{MessageWrite, Writer};
42use serde_json::json;
43pub use uuid::Uuid;
44
45mod base;
46mod ble;
47mod softap;
48
49use crate::{
50 error::Error,
51 persistent_storage::{Nvs, NvsPartition},
52 protocomm::{ProtocommCallbackType, ProtocommSecurity},
53 utils::{wrap_in_arc_mutex, WrappedInArcMutex},
54 wifi::{WifiApInfo, WifiClientConfig, WifiMgr},
55};
56
57pub use base::WiFiProvTransportTrait;
58
59use ble::WiFiProvTransportBle;
60pub use ble::WifiProvBleConfig;
61
62pub use softap::WiFiProvSoftApConfig;
63use softap::WiFiProvTransportSoftAp;
64
65pub type WiFiProvMgrBle = WifiProvMgr<WiFiProvTransportBle>;
79
80pub type WiFiProvMgrSoftAp = WifiProvMgr<WiFiProvTransportSoftAp>;
91
92const WIFI_NAMESPACE: &str = "net80211";
93const WIFI_SSID_KEY: &str = "sta.ssid";
94const WIFI_PASS_KEY: &str = "sta.pswd";
95
96struct ProvisioningSharedData {
98 wifi: WrappedInArcMutex<WifiMgr<'static>>,
99 scan_results: Option<Vec<WifiApInfo>>,
100 nvs_partition: NvsPartition,
101 msg_sender: Sender<()>,
102}
103
104pub struct WifiProvMgr<T>
105where
106 T: WiFiProvTransportTrait,
107{
108 sec_ver: u8,
109 pop: Option<String>,
110 shared: WrappedInArcMutex<ProvisioningSharedData>,
111 transport: T,
112 msg_receiver: Receiver<()>,
113}
114
115impl WiFiProvMgrSoftAp {
116 pub fn new(
117 wifi: WrappedInArcMutex<WifiMgr<'static>>,
118 config: WiFiProvSoftApConfig,
119 nvs_partition: NvsPartition,
120 sec: ProtocommSecurity,
121 ) -> Result<WiFiProvMgrSoftAp, Error> {
122 let (sec_ver, pop) = Self::get_sec_ver_and_pop(&sec);
123 let transport = WiFiProvTransportSoftAp::new(config, sec, wifi.clone());
124 Self::new_with_transport(wifi, nvs_partition, sec_ver, pop, transport)
125 }
126}
127
128impl WiFiProvMgrBle {
129 pub fn new(
130 wifi: WrappedInArcMutex<WifiMgr<'static>>,
131 config: WifiProvBleConfig,
132 nvs_partition: NvsPartition,
133 sec: ProtocommSecurity,
134 ) -> Result<WiFiProvMgrBle, Error> {
135 let (sec_ver, pop) = Self::get_sec_ver_and_pop(&sec);
136 let transport = WiFiProvTransportBle::new(config, sec);
137 Self::new_with_transport(wifi, nvs_partition, sec_ver, pop, transport)
138 }
139}
140
141impl<T: WiFiProvTransportTrait> WifiProvMgr<T> {
142 pub fn wait_for_provisioning(&self) {
145 self.msg_receiver.recv().unwrap()
146 }
147
148 pub fn add_endpoint(&mut self, ep_name: &str, cb: ProtocommCallbackType) {
151 self.transport.add_endpoint(ep_name, cb);
152 }
153
154 pub fn start(&mut self) -> Result<(), Error> {
156 self.register_protocomm_endpoints();
157
158 self.transport.start()?;
159 let data = self.shared.lock().unwrap();
160 let mut wifi = data.wifi.lock().unwrap();
161 wifi.set_client_config(WifiClientConfig::default()).unwrap();
162
163 wifi.start().unwrap();
165
166 Ok(())
167 }
168
169 pub fn is_provisioned(&self) -> Option<(String, String)> {
172 let partition = self.shared.lock().unwrap().nvs_partition.clone();
173 let nvs = Nvs::new(partition, WIFI_NAMESPACE).expect("Unable to open NVS partition");
174 let mut buff = [0; 64];
175 let ssid = nvs
176 .get_string(WIFI_SSID_KEY, &mut buff)
177 .expect("Unable to get SSID");
178 let password = nvs
179 .get_string(WIFI_PASS_KEY, &mut buff)
180 .expect("Unable to get Password");
181
182 if let (Some(ssid), Some(password)) = (ssid, password) {
183 return Some((ssid, password));
184 }
185 None
186 }
187
188 fn new_with_transport(
189 wifi: WrappedInArcMutex<WifiMgr<'static>>,
190 nvs_partition: NvsPartition,
191 sec_ver: u8,
192 pop: Option<String>,
193 transport: T,
194 ) -> Result<WifiProvMgr<T>, Error> {
195 let (sender, receiver) = mpsc::channel::<()>();
196
197 let shared = wrap_in_arc_mutex(ProvisioningSharedData {
198 nvs_partition,
199 wifi,
200 scan_results: None,
201 msg_sender: sender,
202 });
203
204 Ok(WifiProvMgr {
205 sec_ver,
206 pop,
207 shared,
208 transport,
209 msg_receiver: receiver,
210 })
211 }
212
213 fn get_sec_ver_and_pop(sec: &ProtocommSecurity) -> (u8, Option<String>) {
214 let sec_ver;
215 let pop;
216
217 match &sec {
218 ProtocommSecurity::Sec0(_sec0) => {
219 sec_ver = 0;
220 pop = None;
221 }
222 ProtocommSecurity::Sec1(sec1) => {
223 sec_ver = 1;
224 pop = sec1.pop.clone();
225 }
226 }
227
228 (sec_ver, pop)
229 }
230
231 fn get_version_info(&self) -> String {
232 let mut cap = vec!["wifi_scan"];
233
234 if self.pop.is_none() {
235 cap.push("no_pop");
236 }
237
238 if self.sec_ver == 0 {
239 cap.push("no_sec");
240 }
241
242 json! ({
243 "prov": {
244 "ver": "v1.1",
245 "sec_ver": self.sec_ver,
246 "cap": cap
247 }
248 })
249 .to_string()
250 }
251
252 fn register_protocomm_endpoints(&mut self) {
253 let version_info = self.get_version_info();
254 let shared_1 = self.shared.clone();
255 let shared_2 = self.shared.clone();
256
257 self.transport.set_version_info("proto-ver", version_info);
258 self.transport.set_security_ep("prov-session");
259 self.add_endpoint(
260 "prov-scan",
261 Box::new(move |ep, data| ep_prov_scan::prov_scan_callbak(ep, data, shared_1.clone())),
262 );
263 self.add_endpoint(
264 "prov-config",
265 Box::new(move |ep, data| {
266 ep_prov_config::prov_config_callback(ep, data, shared_2.clone())
267 }),
268 );
269 }
270}
271
272mod ep_prov_scan {
273 use super::*;
274
275 use crate::proto::{
276 constants::Status,
277 wifi_prov::{
278 wifi_constants::WifiAuthMode,
279 wifi_scan::{mod_WiFiScanPayload::OneOfpayload, *},
280 },
281 };
282
283 impl From<WifiApInfo> for WiFiScanResult {
284 fn from(value: WifiApInfo) -> Self {
285 let auth = match value.auth {
286 crate::wifi::WifiAuthMode::None => WifiAuthMode::Open,
287 crate::wifi::WifiAuthMode::WEP => WifiAuthMode::WEP,
288 crate::wifi::WifiAuthMode::WPA => WifiAuthMode::WPA_PSK,
289 crate::wifi::WifiAuthMode::WPA2Personal => WifiAuthMode::WPA2_PSK,
290 crate::wifi::WifiAuthMode::WPAWPA2Personal => WifiAuthMode::WPA_WPA2_PSK,
291 crate::wifi::WifiAuthMode::WPA2Enterprise => WifiAuthMode::WPA2_ENTERPRISE,
292 crate::wifi::WifiAuthMode::WPA3Personal => WifiAuthMode::WPA3_PSK,
293 crate::wifi::WifiAuthMode::WPA2WPA3Personal => WifiAuthMode::WPA2_WPA3_PSK,
294 _ => panic!("Unknown WiFi auth type"),
295 };
296
297 Self {
298 ssid: value.ssid.into(),
299 channel: value.channel as u32,
300 bssid: value.bssid,
301 rssi: value.signal_strength as i32,
302 auth,
303 }
304 }
305 }
306
307 #[inline(always)]
308 pub fn prov_scan_callbak(
309 _ep: &str,
310 inp: &[u8],
311 shared: WrappedInArcMutex<ProvisioningSharedData>,
312 ) -> Vec<u8> {
313 let mut out_payload: Vec<u8> = Default::default();
314 let mut writer = Writer::new(&mut out_payload);
315 let mut resp = WiFiScanPayload::default();
316
317 let inp_data = match WiFiScanPayload::try_from(inp) {
318 Ok(payload) => payload,
319 Err(_) => {
320 resp.status = Status::InvalidProto;
321 resp.write_message(&mut writer).unwrap();
322 return out_payload;
323 }
324 };
325
326 let resp_msg;
327 let resp_payload = match inp_data.payload {
328 OneOfpayload::cmd_scan_start(cmd_scan_start) => {
329 resp_msg = WiFiScanMsgType::TypeRespScanStart;
330 handle_scan_start(cmd_scan_start, shared)
331 }
332 OneOfpayload::cmd_scan_status(cmd_scan_status) => {
333 resp_msg = WiFiScanMsgType::TypeRespScanStatus;
334 handle_scan_status(cmd_scan_status, shared)
335 }
336 OneOfpayload::cmd_scan_result(cmd_scan_result) => {
337 resp_msg = WiFiScanMsgType::TypeRespScanResult;
338 handle_scan_result(cmd_scan_result, shared)
339 }
340 other => {
341 log::error!("Invalid payload type {:?}", other);
342 return vec![];
343 }
344 };
345
346 resp.status = Status::Success;
347 resp.msg = resp_msg;
348 resp.payload = resp_payload;
349
350 if resp.write_message(&mut writer).is_err() {
351 log::error!("Failed to write message");
352 return vec![];
353 };
354
355 out_payload
356 }
357
358 fn handle_scan_start(
359 _cmd: CmdScanStart,
360 _shared: WrappedInArcMutex<ProvisioningSharedData>,
361 ) -> OneOfpayload {
362 let resp = RespScanStart::default();
363 OneOfpayload::resp_scan_start(resp)
364 }
365
366 fn handle_scan_status(
367 _cmd: CmdScanStatus,
368 shared: WrappedInArcMutex<ProvisioningSharedData>,
369 ) -> OneOfpayload {
370 let mut resp = RespScanStatus::default();
371
372 let mut data = shared.lock().unwrap();
373
374 let networks = data.wifi.lock().unwrap().scan().unwrap();
375
376 resp.scan_finished = true;
377 resp.result_count = networks.len() as u32;
378 log::info!("Found {} WiFi network(s)", networks.len());
379
380 data.scan_results = Some(networks);
381
382 OneOfpayload::resp_scan_status(resp)
383 }
384
385 fn handle_scan_result(
386 cmd: CmdScanResult,
387 shared: WrappedInArcMutex<ProvisioningSharedData>,
388 ) -> OneOfpayload {
389 log::info!("Sending WiFi scan results");
390
391 let mut resp = RespScanResult::default();
392
393 let data = shared.lock().unwrap();
394 let networks = data
395 .scan_results
396 .as_ref()
397 .expect("WiFi scan results not found");
398
399 let start_index = cmd.start_index as usize;
400 let count = cmd.count as usize;
401 let end_index = start_index + count;
402
403 let entries = networks
404 .clone()
405 .drain(start_index..end_index)
406 .map(|x| x.into())
407 .collect();
408
409 resp.entries = entries;
410 OneOfpayload::resp_scan_result(resp)
411 }
412}
413
414mod ep_prov_config {
415 use mod_RespGetStatus::OneOfstate;
416 use mod_WiFiConfigPayload::OneOfpayload;
417 use quick_protobuf::{MessageWrite, Writer};
418
419 use super::*;
420 use crate::{
421 proto::wifi_prov::{
422 constants::*,
423 wifi_config::*,
424 wifi_constants::{WifiConnectFailedReason, WifiConnectedState, WifiStationState},
425 },
426 wifi::WifiClientConfig,
427 };
428
429 use super::ProvisioningSharedData;
430
431 #[inline(always)]
432 pub fn prov_config_callback(
433 _ep: &str,
434 inp: &[u8],
435 shared: WrappedInArcMutex<ProvisioningSharedData>,
436 ) -> Vec<u8> {
437 let mut resp = WiFiConfigPayload::default();
438 let mut out_vec = Vec::<u8>::new();
439 let mut writer = Writer::new(&mut out_vec);
440
441 let inp_payload = WiFiConfigPayload::try_from(inp).unwrap();
442
443 let resp_payload = match inp_payload.payload {
444 mod_WiFiConfigPayload::OneOfpayload::cmd_get_status(cmd_get_status) => {
445 resp.msg = WiFiConfigMsgType::TypeRespGetStatus;
446 handle_get_status(cmd_get_status, shared)
447 }
448 mod_WiFiConfigPayload::OneOfpayload::cmd_set_config(cmd_set_config) => {
449 resp.msg = WiFiConfigMsgType::TypeRespSetConfig;
450 handle_set_config(cmd_set_config, shared)
451 }
452 mod_WiFiConfigPayload::OneOfpayload::cmd_apply_config(cmd_apply_config) => {
453 resp.msg = WiFiConfigMsgType::TypeRespApplyConfig;
454 handle_apply_config(cmd_apply_config, shared)
455 }
456 _ => unreachable!(),
457 };
458
459 resp.payload = resp_payload;
460
461 if resp.write_message(&mut writer).is_err() {
462 log::error!("Failed to write wifi_config response");
463 return vec![];
464 };
465
466 out_vec
467 }
468
469 fn handle_set_config(
470 cmd: CmdSetConfig,
471 shared: WrappedInArcMutex<ProvisioningSharedData>,
472 ) -> OneOfpayload {
473 let mut resp = RespSetConfig::default();
474
475 let ssid = String::from_utf8(cmd.ssid).expect("Failed to decode WiFi SSID");
476 let password = String::from_utf8(cmd.passphrase).expect("Failed to decode WiFi passphrase");
477 let bssid = cmd.bssid;
478 let channel = cmd.channel;
479
480 log::info!("Received SSID={} PASSWORD={}", ssid, password);
481
482 let wifi_config = WifiClientConfig {
483 ssid,
484 password,
485 bssid,
486 channel: channel as u8,
487 ..Default::default()
488 };
489
490 let data = shared.lock().unwrap();
493 data.wifi
494 .lock()
495 .unwrap()
496 .set_client_config(wifi_config)
497 .unwrap();
498
499 resp.status = Status::Success;
500
501 OneOfpayload::resp_set_config(resp)
502 }
503
504 fn handle_apply_config(
505 _cmd: CmdApplyConfig,
506 shared: WrappedInArcMutex<ProvisioningSharedData>,
507 ) -> OneOfpayload {
508 log::info!("Connecting to WiFi");
509 let mut resp = RespApplyConfig::default();
510
511 let data = shared.lock().unwrap();
512 let mut wifi = data.wifi.lock().unwrap();
513
514 if wifi.connect().is_err() {
515 log::error!("Failed connecting to provided WiFi network");
516 } else {
517 let (client_config, _) = wifi.get_wifi_config();
518 if let Some(config) = client_config {
519 let ssid = config.ssid;
520 let password = config.password;
521 let nvs_partition = data.nvs_partition.clone();
522 let nvs = Nvs::new(nvs_partition, WIFI_NAMESPACE);
523 match nvs {
524 Err(_) => log::error!("Failed to open nvs for saving WiFi credentials"),
525 Ok(mut nvs) => {
526 nvs.set_str(WIFI_SSID_KEY, &ssid)
527 .expect("Failed to save SSID");
528 nvs.set_str(WIFI_PASS_KEY, &password)
529 .expect("Failed to save Password");
530 }
531 }
532 }
533 }
534 resp.status = Status::Success;
535
536 OneOfpayload::resp_apply_config(resp)
537 }
538
539 fn handle_get_status(
540 _cmd: CmdGetStatus,
541 shared: WrappedInArcMutex<ProvisioningSharedData>,
542 ) -> OneOfpayload {
543 let mut resp = RespGetStatus::default();
544
545 let data = shared.lock().unwrap();
546
547 let wifi = data.wifi.lock().unwrap();
549 let ip_addr = wifi.get_ip_addr();
550
551 resp.status = Status::Success;
552 if wifi.is_connected() {
553 resp.sta_state = WifiStationState::Connected;
554 resp.state = OneOfstate::connected(WifiConnectedState {
555 ip4_addr: ip_addr.to_string(),
556 ..Default::default()
557 });
558 } else {
559 resp.sta_state = WifiStationState::ConnectionFailed;
560 resp.state = OneOfstate::fail_reason(WifiConnectFailedReason::AuthError);
561 }
562
563 data.msg_sender.send(()).unwrap();
564
565 OneOfpayload::resp_get_status(resp)
566 }
567}