Skip to main content

wlancfg_lib/
regulatory_manager.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::mode_management::iface_manager_api::IfaceManagerApi;
6use anyhow::{Context, Error};
7use fidl_fuchsia_location_namedplace::RegulatoryRegionWatcherProxy;
8use futures::channel::oneshot;
9use futures::lock::Mutex;
10use log::{info, warn};
11use std::sync::Arc;
12
13pub struct RegulatoryManager<I: IfaceManagerApi + ?Sized> {
14    regulatory_service: RegulatoryRegionWatcherProxy,
15    iface_manager: Arc<Mutex<I>>,
16    power_manager: Arc<dyn wlan_power_manager::PowerManager>,
17}
18
19mod country_code {
20    use anyhow::{Error, format_err};
21    use std::str::FromStr;
22    const CODE_LENGTH: usize = 2;
23
24    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
25    pub struct CountryCode([u8; CODE_LENGTH]);
26
27    impl FromStr for CountryCode {
28        type Err = Error;
29        fn from_str(region: &str) -> Result<Self, Self::Err> {
30            region
31                .as_bytes()
32                .try_into()
33                .map_err(|_| {
34                    format_err!("Invalid length: {region}, {} != {CODE_LENGTH}", region.len())
35                })
36                .map(CountryCode)
37        }
38    }
39
40    impl From<CountryCode> for [u8; CODE_LENGTH] {
41        fn from(country_code: CountryCode) -> Self {
42            country_code.0
43        }
44    }
45}
46
47pub use country_code::CountryCode;
48
49impl<I: IfaceManagerApi + ?Sized> RegulatoryManager<I> {
50    pub fn new(
51        regulatory_service: RegulatoryRegionWatcherProxy,
52        iface_manager: Arc<Mutex<I>>,
53        power_manager: Arc<dyn wlan_power_manager::PowerManager>,
54    ) -> Self {
55        RegulatoryManager { regulatory_service, iface_manager, power_manager }
56    }
57
58    pub async fn run(&self, policy_notifier: oneshot::Sender<()>) -> Result<(), Error> {
59        let mut policy_notifier = Some(policy_notifier);
60        loop {
61            let region_update = self
62                .regulatory_service
63                .get_region_update()
64                .await
65                .context("failed to get_update()")?;
66            let region_string = match region_update {
67                Some(region_string) => region_string,
68                None => {
69                    info!("No cached regulatory region is available.");
70                    if let Some(notifier) = policy_notifier.take()
71                        && notifier.send(()).is_err()
72                    {
73                        info!("Could not notify policy layer of initial region setting");
74                    };
75
76                    continue;
77                }
78            };
79
80            info!("Received regulatory region code {}", region_string);
81
82            let country_code = match region_string.parse::<CountryCode>() {
83                Err(e) => {
84                    warn!("{:?}", e.context("Failed to parse region string."));
85                    continue;
86                }
87                Ok(country_code) => country_code,
88            };
89
90            let _wake_lease =
91                self.power_manager.take_wake_lease("wlancfg-regulatory-set-country-code").await;
92
93            // Apply the new country code.
94            let mut iface_manager = self.iface_manager.lock().await;
95            if let Err(e) = iface_manager.set_country(Some(country_code)).await {
96                warn!("Failed to set region code: {:?}", e);
97                continue;
98            }
99
100            if let Some(notifier) = policy_notifier.take()
101                && notifier.send(()).is_err()
102            {
103                info!("Could not notify policy layer of initial region setting");
104            };
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::{Arc, IfaceManagerApi, Mutex, RegulatoryManager};
112    use crate::access_point::{state_machine as ap_fsm, types as ap_types};
113    use crate::client::types as client_types;
114    use crate::mode_management::iface_manager_api::{ConnectAttemptRequest, SmeForScan};
115    use anyhow::{Error, format_err};
116    use assert_matches::assert_matches;
117    use async_trait::async_trait;
118    use fidl::endpoints::create_proxy;
119    use fidl_fuchsia_location_namedplace::{
120        RegulatoryRegionWatcherMarker, RegulatoryRegionWatcherRequest,
121        RegulatoryRegionWatcherRequestStream,
122    };
123    use fuchsia_async as fasync;
124    use futures::channel::{mpsc, oneshot};
125    use futures::stream::{self, Stream, StreamExt};
126    use futures::task::Poll;
127    use std::pin::pin;
128    use std::unimplemented;
129
130    /// Holds all of the boilerplate required for testing RegulatoryManager.
131    struct TestContext<S: Stream<Item = Result<(), Error>> + Unpin> {
132        iface_manager: Arc<Mutex<StubIfaceManager<S>>>,
133        regulatory_manager: RegulatoryManager<StubIfaceManager<S>>,
134        regulatory_region_requests: RegulatoryRegionWatcherRequestStream,
135        regulatory_sender: oneshot::Sender<()>,
136        regulatory_receiver: oneshot::Receiver<()>,
137        power_manager: Arc<wlan_power_manager_testing::TestPowerManager>,
138        // Fields are dropped in declaration order. Always drop executor last because we hold other
139        // zircon objects tied to the executor in this struct, and those can't outlive the executor.
140        //
141        // See
142        // - https://fuchsia-docs.firebaseapp.com/rust/fuchsia_async/struct.TestExecutor.html
143        // - https://doc.rust-lang.org/reference/destructors.html.
144        executor: fasync::TestExecutor,
145    }
146
147    impl<S> TestContext<S>
148    where
149        S: Stream<Item = Result<(), Error>> + Unpin,
150    {
151        fn new(
152            iface_manager: StubIfaceManager<S>,
153        ) -> TestContext<impl Stream<Item = Result<(), Error>> + Unpin> {
154            let executor = fasync::TestExecutor::new();
155            let (regulatory_region_proxy, regulatory_region_server_channel) =
156                create_proxy::<RegulatoryRegionWatcherMarker>();
157            let iface_manager = Arc::new(Mutex::new(iface_manager));
158            let power_manager = Arc::new(wlan_power_manager_testing::TestPowerManager::new());
159            let regulatory_manager = RegulatoryManager::new(
160                regulatory_region_proxy,
161                iface_manager.clone(),
162                power_manager.clone(),
163            );
164            let regulatory_region_requests = regulatory_region_server_channel.into_stream();
165
166            let (regulatory_sender, regulatory_receiver) = oneshot::channel();
167            Self {
168                executor,
169                iface_manager,
170                regulatory_manager,
171                regulatory_region_requests,
172                regulatory_sender,
173                regulatory_receiver,
174                power_manager,
175            }
176        }
177    }
178
179    #[fuchsia::test]
180    fn ignore_update_with_short_region_code() {
181        let mut context = TestContext::new(make_default_stub_iface_manager());
182        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
183        let mut regulatory_fut = pin!(regulatory_fut);
184        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
185
186        let region_request_fut = &mut context.regulatory_region_requests.next();
187        let responder = assert_matches!(
188            context.executor.run_until_stalled(region_request_fut),
189            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
190        );
191        responder.send(Some("U")).expect("failed to send response");
192        assert_matches!(context.executor.run_until_stalled(&mut regulatory_fut), Poll::Pending);
193
194        assert_matches!(
195            &context.executor.run_until_stalled(&mut context.regulatory_receiver),
196            Poll::Pending
197        );
198
199        // Verify that there is a new region update request.
200        let region_request_fut = &mut context.regulatory_region_requests.next();
201        assert_matches!(
202            context.executor.run_until_stalled(region_request_fut),
203            Poll::Ready(Some(_))
204        );
205    }
206
207    #[fuchsia::test]
208    fn update_with_long_region_code_fails() {
209        let mut context = TestContext::new(make_default_stub_iface_manager());
210        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
211        let mut regulatory_fut = pin!(regulatory_fut);
212        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
213
214        let region_request_fut = &mut context.regulatory_region_requests.next();
215        let responder = assert_matches!(
216            context.executor.run_until_stalled(region_request_fut),
217            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
218        );
219        assert_matches!(responder.send(Some("USA")), Err(fidl::Error::StringTooLong { .. }));
220        assert_matches!(context.executor.run_until_stalled(&mut regulatory_fut), Poll::Pending);
221    }
222
223    #[fuchsia::test]
224    fn propagates_update_on_region_code_with_valid_length() {
225        let mut context = TestContext::new(make_default_stub_iface_manager());
226        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
227        let mut regulatory_fut = pin!(regulatory_fut);
228        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
229
230        let region_request_fut = &mut context.regulatory_region_requests.next();
231        let region_responder = assert_matches!(
232            context.executor.run_until_stalled(region_request_fut),
233            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
234        );
235        region_responder.send(Some("US")).expect("failed to send region response");
236        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
237
238        let iface_manager_fut = context.iface_manager.lock();
239        let mut iface_manager_fut = pin!(iface_manager_fut);
240        match context.executor.run_until_stalled(&mut iface_manager_fut) {
241            Poll::Ready(iface_manager) => {
242                assert_eq!(iface_manager.country_code, Some("US".parse().unwrap()))
243            }
244            Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
245        };
246    }
247
248    #[fuchsia::test]
249    fn does_not_propagate_invalid_length_region_code() {
250        let mut context = TestContext::new(make_default_stub_iface_manager());
251        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
252        let mut regulatory_fut = pin!(regulatory_fut);
253        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
254
255        let region_request_fut = &mut context.regulatory_region_requests.next();
256        let region_responder = assert_matches!(
257            context.executor.run_until_stalled(region_request_fut),
258            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
259        );
260        region_responder.send(Some("U")).expect("failed to send region response");
261
262        // Drive the RegulatoryManager until stalled, then verify that RegulatoryManager did not
263        // send a request to the IfaceManager.
264        let _ = context.executor.run_until_stalled(&mut regulatory_fut);
265
266        let iface_manager_fut = context.iface_manager.lock();
267        let mut iface_manager_fut = pin!(iface_manager_fut);
268        match context.executor.run_until_stalled(&mut iface_manager_fut) {
269            Poll::Ready(iface_manager) => assert_eq!(iface_manager.country_code, None),
270            Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
271        }
272
273        assert_matches!(
274            &context.executor.run_until_stalled(&mut context.regulatory_receiver),
275            Poll::Pending
276        );
277    }
278
279    #[fuchsia::test]
280    fn does_not_propagate_null_update() {
281        let mut context = TestContext::new(make_default_stub_iface_manager());
282        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
283        let mut regulatory_fut = pin!(regulatory_fut);
284        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
285
286        // Set the regulatory region to be non-None initially.
287        {
288            let iface_manager_fut = context.iface_manager.lock();
289            let mut iface_manager_fut = pin!(iface_manager_fut);
290            match context.executor.run_until_stalled(&mut iface_manager_fut) {
291                Poll::Ready(mut iface_manager) => {
292                    iface_manager.country_code = Some("US".parse().unwrap())
293                }
294                Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
295            }
296        }
297
298        let region_request_fut = &mut context.regulatory_region_requests.next();
299        let region_responder = assert_matches!(
300            context.executor.run_until_stalled(region_request_fut),
301            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
302        );
303        region_responder.send(None).expect("failed to send region response");
304        // Run RegulatoryManager until stalled. Getting a null update should not cause an error.
305        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
306
307        // Verify that no region change is applied to the IfaceManager.
308        {
309            let iface_manager_fut = context.iface_manager.lock();
310            let mut iface_manager_fut = pin!(iface_manager_fut);
311            match context.executor.run_until_stalled(&mut iface_manager_fut) {
312                Poll::Ready(iface_manager) => assert!(iface_manager.country_code.is_some()),
313                Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
314            }
315        }
316
317        // Verify that the policy API is instructed to begin serving.
318        assert_matches!(
319            &context.executor.run_until_stalled(&mut context.regulatory_receiver),
320            Poll::Ready(Ok(()))
321        );
322    }
323
324    #[fuchsia::test]
325    fn absorbs_iface_manager_failure() {
326        let (mut set_country_responder, set_country_response_stream) = mpsc::channel(0);
327        let mut context =
328            TestContext::new(StubIfaceManager { country_code: None, set_country_response_stream });
329        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
330        let mut regulatory_fut = pin!(regulatory_fut);
331        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
332
333        // Drive the RegulatoryManager to request an update from RegulatoryRegionWatcher,
334        // and deliver a RegulatoryRegion update.
335        let region_request_fut = &mut context.regulatory_region_requests.next();
336        let region_responder = assert_matches!(
337            context.executor.run_until_stalled(region_request_fut),
338            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
339        );
340        region_responder.send(Some("US")).expect("failed to send region response");
341        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
342
343        // Setup an error response for when the RegulatoryManager tries to set the country code.
344        set_country_responder
345            .try_send(Err(format_err!("sending a test error")))
346            .expect("internal error: failed to send fake response to StubIfaceManager");
347
348        assert_matches!(&context.executor.run_until_stalled(&mut regulatory_fut), Poll::Pending);
349
350        // Verify that there is a new region update request.
351        let region_request_fut = &mut context.regulatory_region_requests.next();
352        assert_matches!(
353            context.executor.run_until_stalled(region_request_fut),
354            Poll::Ready(Some(_))
355        );
356    }
357
358    #[fuchsia::test]
359    fn propagates_multiple_valid_region_code_updates_to_device_service() {
360        let mut context = TestContext::new(make_default_stub_iface_manager());
361        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
362        let mut regulatory_fut = pin!(regulatory_fut);
363
364        // Receive first `RegulatoryRegionWatcher` update, and propagate it to `IfaceManager`.
365        {
366            assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
367
368            let region_request_fut = &mut context.regulatory_region_requests.next();
369            let region_responder = assert_matches!(
370                context.executor.run_until_stalled(region_request_fut),
371                Poll::Ready(Some(Ok(
372                    RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
373            );
374            region_responder.send(Some("US")).expect("failed to send region response");
375            assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
376
377            let iface_manager_fut = context.iface_manager.lock();
378            let mut iface_manager_fut = pin!(iface_manager_fut);
379            match context.executor.run_until_stalled(&mut iface_manager_fut) {
380                Poll::Ready(iface_manager) => {
381                    assert_eq!(iface_manager.country_code, Some("US".parse().unwrap()))
382                }
383                Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
384            }
385        }
386
387        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
388        assert_matches!(
389            &context.executor.run_until_stalled(&mut context.regulatory_receiver),
390            Poll::Ready(Ok(()))
391        );
392
393        // Receive second `RegulatoryRegionWatcher` update, and propagate it to `IfaceManager`.
394        {
395            assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
396
397            let region_request_fut = &mut context.regulatory_region_requests.next();
398            let region_responder = assert_matches!(
399                context.executor.run_until_stalled(region_request_fut),
400                Poll::Ready(Some(Ok(
401                    RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
402            );
403            region_responder.send(Some("CA")).expect("failed to send region response");
404            assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
405
406            let iface_manager_fut = context.iface_manager.lock();
407            let mut iface_manager_fut = pin!(iface_manager_fut);
408            match context.executor.run_until_stalled(&mut iface_manager_fut) {
409                Poll::Ready(iface_manager) => {
410                    assert_eq!(iface_manager.country_code, Some("CA".parse().unwrap()))
411                }
412                Poll::Pending => panic!("Expected to be able to lock the IfaceManager."),
413            };
414        }
415    }
416
417    struct StubIfaceManager<S: Stream<Item = Result<(), Error>> + Unpin> {
418        country_code: Option<client_types::CountryCode>,
419        set_country_response_stream: S,
420    }
421
422    /// A default StubIfaceManager
423    /// * immediately returns Ok() in response to stop_client_connections(), and
424    /// * immediately returns Ok() in response to start_client_connections()
425    fn make_default_stub_iface_manager()
426    -> StubIfaceManager<impl Stream<Item = Result<(), Error>> + Unpin> {
427        StubIfaceManager {
428            country_code: None,
429            set_country_response_stream: stream::unfold((), |_| async { Some((Ok(()), ())) })
430                .boxed(),
431        }
432    }
433
434    #[async_trait(?Send)]
435    impl<S: Stream<Item = Result<(), Error>> + Unpin> IfaceManagerApi for StubIfaceManager<S> {
436        async fn disconnect(
437            &mut self,
438            _network_id: client_types::NetworkIdentifier,
439            _reason: client_types::DisconnectReason,
440        ) -> Result<(), Error> {
441            unimplemented!();
442        }
443
444        async fn connect(&mut self, _connect_req: ConnectAttemptRequest) -> Result<(), Error> {
445            unimplemented!();
446        }
447
448        async fn record_idle_client(&mut self, _iface_id: u16) -> Result<(), Error> {
449            unimplemented!();
450        }
451
452        async fn has_idle_client(&mut self) -> Result<bool, Error> {
453            unimplemented!();
454        }
455
456        async fn handle_added_iface(&mut self, _iface_id: u16) -> Result<(), Error> {
457            unimplemented!();
458        }
459
460        async fn handle_removed_iface(&mut self, _iface_id: u16) -> Result<(), Error> {
461            unimplemented!();
462        }
463
464        async fn get_sme_proxy_for_scan(&mut self) -> Result<SmeForScan, Error> {
465            unimplemented!()
466        }
467
468        async fn stop_client_connections(
469            &mut self,
470            _reason: client_types::DisconnectReason,
471        ) -> Result<(), Error> {
472            unimplemented!()
473        }
474
475        async fn start_client_connections(&mut self) -> Result<(), Error> {
476            unimplemented!()
477        }
478
479        async fn start_ap(
480            &mut self,
481            _config: ap_fsm::ApConfig,
482        ) -> Result<oneshot::Receiver<()>, Error> {
483            unimplemented!();
484        }
485
486        async fn stop_ap(
487            &mut self,
488            _ssid: ap_types::Ssid,
489            _password: Vec<u8>,
490        ) -> Result<(), Error> {
491            unimplemented!();
492        }
493
494        async fn stop_all_aps(&mut self) -> Result<(), Error> {
495            unimplemented!()
496        }
497
498        async fn set_country(
499            &mut self,
500            country_code: Option<client_types::CountryCode>,
501        ) -> Result<(), Error> {
502            self.country_code = country_code;
503            self.set_country_response_stream
504                .next()
505                .await
506                .expect("internal error: failed to receive fake response from test case")
507        }
508    }
509
510    #[fuchsia::test]
511    fn test_regulatory_wake_lease() {
512        let mut context = TestContext::new(make_default_stub_iface_manager());
513        let regulatory_fut = context.regulatory_manager.run(context.regulatory_sender);
514        let mut regulatory_fut = pin!(regulatory_fut);
515        assert!(context.executor.run_until_stalled(&mut regulatory_fut).is_pending());
516
517        let region_request_fut = &mut context.regulatory_region_requests.next();
518        let region_responder = assert_matches!(
519            context.executor.run_until_stalled(region_request_fut),
520            Poll::Ready(Some(Ok(RegulatoryRegionWatcherRequest::GetRegionUpdate{responder}))) => responder
521        );
522        region_responder.send(Some("US")).expect("failed to send response");
523        assert_matches!(context.executor.run_until_stalled(&mut regulatory_fut), Poll::Pending);
524
525        // Verify that the wake lease was taken.
526        let calls = context.power_manager.calls.lock();
527        assert!(calls.contains(&"wlancfg-regulatory-set-country-code".to_string()));
528    }
529}