Skip to main content

recovery_util/
regulatory.rs

1// Copyright 2022 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 anyhow::{Context as _, Error, format_err};
6use fidl_fuchsia_hwinfo as hwinfo;
7use fidl_fuchsia_intl::RegulatoryDomain;
8use fidl_fuchsia_location_namedplace::{
9    RegulatoryRegionConfiguratorMarker, RegulatoryRegionConfiguratorProxy,
10};
11use fuchsia_component::client::connect_to_protocol;
12
13/// Read region code using fuchsia.hwinfo API, then set it using the fuchsia.location.namedplace API.
14/// Caller must have access to fuchsia.hwinfo.Product and fuchsia.location.namedplace.RegulatoryRegionConfigurator
15/// APIs before calling this function.
16pub async fn set_region_code_from_factory() -> Result<(), Error> {
17    let hwinfo_proxy = connect_to_protocol::<hwinfo::ProductMarker>()
18        .context("Failed to connect to hwinfo protocol")?;
19
20    let configurator_proxy = connect_to_protocol::<RegulatoryRegionConfiguratorMarker>()
21        .context("Failed to connect to Configurator protocol")?;
22
23    let region_code = read_region_code_from_factory(&hwinfo_proxy).await?;
24    set_region_code(&region_code, &configurator_proxy)
25}
26
27// Note: the hwinfo service refers to the 2-character region code as country_code, while RegulatoryRegionConfigurator
28// uses the terminology RegionCode (or region_code). These refer to the same value for the intended purpose here.
29async fn read_region_code_from_factory(proxy: &hwinfo::ProductProxy) -> Result<String, Error> {
30    let product_info = proxy.get_info().await.context("Failed to get_info from ProductProxy")?;
31
32    if let Some(RegulatoryDomain { country_code: Some(country_code), .. }) =
33        product_info.regulatory_domain
34    {
35        return Ok(country_code);
36    }
37
38    Err(format_err!("No region code found, defaulting to worldwide mode (2.4GHz networks only)"))
39}
40
41fn set_region_code(
42    region_code: &str,
43    proxy: &RegulatoryRegionConfiguratorProxy,
44) -> Result<(), Error> {
45    validate_region_code(&region_code).context("Failed to validate region code")?;
46
47    println!("Set region code: {:?}", region_code);
48    proxy.set_region(&region_code).context("Set region code")?;
49    Ok(())
50}
51
52fn validate_region_code(region_code: &str) -> Result<(), Error> {
53    // sdk/fidl/fuchsia.location.namedplace/namedplace.fidl requires region codes to be of length 2.
54    if region_code.len() != 2 {
55        return Err(format_err!(
56            "Invalid region code requested to set_region_code: {:?}",
57            region_code
58        ));
59    }
60
61    Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use fidl_fuchsia_hwinfo as hwinfo;
68    use fidl_fuchsia_location_namedplace as regulatory;
69    use fuchsia_async as fasync;
70    use fuchsia_async::TimeoutExt;
71    use futures::channel::mpsc;
72    use futures::{StreamExt, TryStreamExt};
73    use zx::MonotonicDuration;
74
75    fn create_mock_hwinfo_server(
76        mock_info: hwinfo::ProductInfo,
77    ) -> Result<hwinfo::ProductProxy, Error> {
78        let (proxy, mut request_stream) =
79            fidl::endpoints::create_proxy_and_stream::<hwinfo::ProductMarker>();
80
81        fasync::Task::local(async move {
82            while let Some(request) =
83                request_stream.try_next().await.expect("failed to read mock request")
84            {
85                match request {
86                    hwinfo::ProductRequest::GetInfo { responder } => {
87                        responder.send(&mock_info).ok();
88                    }
89                }
90            }
91        })
92        .detach();
93
94        Ok(proxy)
95    }
96
97    fn create_mock_regulatory_configurator_server()
98    -> Result<(regulatory::RegulatoryRegionConfiguratorProxy, mpsc::Receiver<String>), Error> {
99        let (mut sender, receiver) = mpsc::channel(1);
100        let (proxy, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
101            regulatory::RegulatoryRegionConfiguratorMarker,
102        >();
103
104        fasync::Task::local(async move {
105            while let Some(request) =
106                request_stream.try_next().await.expect("failed to read mock request")
107            {
108                match request {
109                    regulatory::RegulatoryRegionConfiguratorRequest::SetRegion {
110                        region,
111                        control_handle: _,
112                    } => {
113                        sender.start_send(region).unwrap();
114                    }
115                }
116            }
117        })
118        .detach();
119
120        Ok((proxy, receiver))
121    }
122
123    #[fuchsia::test]
124    async fn test_read_from_hwinfo_success() {
125        // We need both the regulatory_domain and country_code fields to be populated for success.
126        let expected_region_code = "AA".to_string();
127        let mut regulatory_domain = RegulatoryDomain::default();
128        regulatory_domain.country_code = Some(expected_region_code.clone());
129
130        let mut product_info = hwinfo::ProductInfo::default();
131        product_info.regulatory_domain = Some(regulatory_domain);
132
133        let proxy = create_mock_hwinfo_server(product_info).unwrap();
134
135        let region_code = read_region_code_from_factory(&proxy).await.unwrap();
136
137        assert_eq!(region_code, expected_region_code);
138    }
139
140    #[fuchsia::test]
141    async fn test_read_from_hwinfo_no_regulatory_domain_returns_error() {
142        let product_info = hwinfo::ProductInfo::default();
143        let proxy = create_mock_hwinfo_server(product_info).unwrap();
144
145        let result = read_region_code_from_factory(&proxy).await;
146
147        assert!(result.is_err());
148        assert_eq!(
149            format!("{}", result.unwrap_err()),
150            "No region code found, defaulting to worldwide mode (2.4GHz networks only)"
151        );
152    }
153
154    #[fuchsia::test]
155    async fn test_read_from_hwinfo_no_country_code_returns_error() {
156        let regulatory_domain = RegulatoryDomain::default();
157
158        let mut product_info = hwinfo::ProductInfo::default();
159        product_info.regulatory_domain = Some(regulatory_domain);
160        let proxy = create_mock_hwinfo_server(product_info).unwrap();
161
162        let result = read_region_code_from_factory(&proxy).await;
163
164        assert!(result.is_err());
165        assert_eq!(
166            format!("{}", result.unwrap_err()),
167            "No region code found, defaulting to worldwide mode (2.4GHz networks only)"
168        );
169    }
170
171    #[fuchsia::test]
172    async fn test_set_region_code_success() {
173        let valid_region_code = "AA".to_string();
174
175        let (proxy, mut receiver) = create_mock_regulatory_configurator_server().unwrap();
176
177        set_region_code(&valid_region_code, &proxy).unwrap();
178
179        let region_code_received =
180            receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
181        assert_eq!(region_code_received, valid_region_code);
182    }
183
184    #[fuchsia::test]
185    async fn test_set_invalid_region_code_returns_error() {
186        // This will fail the validation. The proxy shouldn't see any calls coming through with the invalid code.
187        let invalid_region_code = "a".to_string();
188
189        let (proxy, mut receiver) = create_mock_regulatory_configurator_server().unwrap();
190
191        let result = set_region_code(&invalid_region_code, &proxy);
192
193        assert!(result.is_err());
194        // try_next will return error if there are no messages waiting, and the channel is closed.
195        assert!(receiver.try_next().is_err());
196    }
197
198    #[fuchsia::test]
199    async fn test_valid_region_codes() {
200        let valid_codes = vec!["AA", "ZZ"];
201
202        for code in valid_codes {
203            validate_region_code(code).unwrap();
204        }
205    }
206
207    #[fuchsia::test]
208    async fn test_invalid_region_codes() {
209        let invalid_codes = vec!["", "a", "test"];
210
211        for code in invalid_codes {
212            validate_region_code(code).unwrap_err();
213        }
214    }
215}