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::{format_err, Context as _, Error};
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 fuchsia_async::TimeoutExt;
68    use futures::channel::mpsc;
69    use futures::{StreamExt, TryStreamExt};
70    use zx::MonotonicDuration;
71    use {
72        fidl_fuchsia_hwinfo as hwinfo, fidl_fuchsia_location_namedplace as regulatory,
73        fuchsia_async as fasync,
74    };
75
76    fn create_mock_hwinfo_server(
77        mock_info: hwinfo::ProductInfo,
78    ) -> Result<hwinfo::ProductProxy, Error> {
79        let (proxy, mut request_stream) =
80            fidl::endpoints::create_proxy_and_stream::<hwinfo::ProductMarker>();
81
82        fasync::Task::local(async move {
83            while let Some(request) =
84                request_stream.try_next().await.expect("failed to read mock request")
85            {
86                match request {
87                    hwinfo::ProductRequest::GetInfo { responder } => {
88                        responder.send(&mock_info).ok();
89                    }
90                }
91            }
92        })
93        .detach();
94
95        Ok(proxy)
96    }
97
98    fn create_mock_regulatory_configurator_server(
99    ) -> Result<(regulatory::RegulatoryRegionConfiguratorProxy, mpsc::Receiver<String>), Error>
100    {
101        let (mut sender, receiver) = mpsc::channel(1);
102        let (proxy, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
103            regulatory::RegulatoryRegionConfiguratorMarker,
104        >();
105
106        fasync::Task::local(async move {
107            while let Some(request) =
108                request_stream.try_next().await.expect("failed to read mock request")
109            {
110                match request {
111                    regulatory::RegulatoryRegionConfiguratorRequest::SetRegion {
112                        region,
113                        control_handle: _,
114                    } => {
115                        sender.start_send(region).unwrap();
116                    }
117                }
118            }
119        })
120        .detach();
121
122        Ok((proxy, receiver))
123    }
124
125    #[fuchsia::test]
126    async fn test_read_from_hwinfo_success() {
127        // We need both the regulatory_domain and country_code fields to be populated for success.
128        let expected_region_code = "AA".to_string();
129        let mut regulatory_domain = RegulatoryDomain::default();
130        regulatory_domain.country_code = Some(expected_region_code.clone());
131
132        let mut product_info = hwinfo::ProductInfo::default();
133        product_info.regulatory_domain = Some(regulatory_domain);
134
135        let proxy = create_mock_hwinfo_server(product_info).unwrap();
136
137        let region_code = read_region_code_from_factory(&proxy).await.unwrap();
138
139        assert_eq!(region_code, expected_region_code);
140    }
141
142    #[fuchsia::test]
143    async fn test_read_from_hwinfo_no_regulatory_domain_returns_error() {
144        let product_info = hwinfo::ProductInfo::default();
145        let proxy = create_mock_hwinfo_server(product_info).unwrap();
146
147        let result = read_region_code_from_factory(&proxy).await;
148
149        assert!(result.is_err());
150        assert_eq!(
151            format!("{}", result.unwrap_err()),
152            "No region code found, defaulting to worldwide mode (2.4GHz networks only)"
153        );
154    }
155
156    #[fuchsia::test]
157    async fn test_read_from_hwinfo_no_country_code_returns_error() {
158        let regulatory_domain = RegulatoryDomain::default();
159
160        let mut product_info = hwinfo::ProductInfo::default();
161        product_info.regulatory_domain = Some(regulatory_domain);
162        let proxy = create_mock_hwinfo_server(product_info).unwrap();
163
164        let result = read_region_code_from_factory(&proxy).await;
165
166        assert!(result.is_err());
167        assert_eq!(
168            format!("{}", result.unwrap_err()),
169            "No region code found, defaulting to worldwide mode (2.4GHz networks only)"
170        );
171    }
172
173    #[fuchsia::test]
174    async fn test_set_region_code_success() {
175        let valid_region_code = "AA".to_string();
176
177        let (proxy, mut receiver) = create_mock_regulatory_configurator_server().unwrap();
178
179        set_region_code(&valid_region_code, &proxy).unwrap();
180
181        let region_code_received =
182            receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
183        assert_eq!(region_code_received, valid_region_code);
184    }
185
186    #[fuchsia::test]
187    async fn test_set_invalid_region_code_returns_error() {
188        // This will fail the validation. The proxy shouldn't see any calls coming through with the invalid code.
189        let invalid_region_code = "a".to_string();
190
191        let (proxy, mut receiver) = create_mock_regulatory_configurator_server().unwrap();
192
193        let result = set_region_code(&invalid_region_code, &proxy);
194
195        assert!(result.is_err());
196        // try_next will return error if there are no messages waiting, and the channel is closed.
197        assert!(receiver.try_next().is_err());
198    }
199
200    #[fasync::run_singlethreaded(test)]
201    async fn test_valid_region_codes() {
202        let valid_codes = vec!["AA", "ZZ"];
203
204        for code in valid_codes {
205            validate_region_code(code).unwrap();
206        }
207    }
208
209    #[fasync::run_singlethreaded(test)]
210    async fn test_invalid_region_codes() {
211        let invalid_codes = vec!["", "a", "test"];
212
213        for code in invalid_codes {
214            validate_region_code(code).unwrap_err();
215        }
216    }
217}