sl4f_lib/weave/
facade.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::common_utils::common::LazyProxy;
6use crate::weave::types::{PairingState, ResetConfig};
7use anyhow::Error;
8use fidl::endpoints::create_proxy;
9use fidl_fuchsia_weave::{
10    ErrorCode, FactoryDataManagerMarker, FactoryDataManagerProxy, PairingStateWatcherMarker,
11    PairingStateWatcherProxy, ResetConfigFlags, StackMarker, StackProxy,
12};
13use serde_json::Value;
14
15/// Perform Weave FIDL operations.
16///
17/// Note this object is shared among all threads created by server.
18#[derive(Debug)]
19pub struct WeaveFacade {
20    factory_data_manager: LazyProxy<FactoryDataManagerMarker>,
21    stack: LazyProxy<StackMarker>,
22}
23
24impl WeaveFacade {
25    pub fn new() -> WeaveFacade {
26        WeaveFacade { factory_data_manager: Default::default(), stack: Default::default() }
27    }
28
29    /// Returns the FactoryDataManager proxy provided on instantiation
30    /// or establishes a new connection.
31    fn factory_data_manager(&self) -> Result<FactoryDataManagerProxy, Error> {
32        self.factory_data_manager.get_or_connect()
33    }
34
35    /// Returns the Stack proxy provided on instantiation or establishes a new connection.
36    fn stack(&self) -> Result<StackProxy, Error> {
37        self.stack.get_or_connect()
38    }
39
40    /// Returns the PairingStateWatcher proxy provided on instantiation.
41    fn pairing_state_watcher(&self) -> Result<PairingStateWatcherProxy, Error> {
42        let (pairing_proxy, pairing_server_end) = create_proxy::<PairingStateWatcherMarker>();
43        self.stack()?.get_pairing_state_watcher(pairing_server_end)?;
44        Ok(pairing_proxy)
45    }
46
47    /// Returns a string mapped from the provided Weave error code.
48    fn map_weave_err(&self, code: ErrorCode) -> anyhow::Error {
49        anyhow!(match code {
50            ErrorCode::FileNotFound => "FileNotFound",
51            ErrorCode::CryptoError => "CryptoError",
52            ErrorCode::InvalidArgument => "InvalidArgument",
53            ErrorCode::InvalidState => "InvalidState",
54            ErrorCode::UnspecifiedError => "UnspecifiedError",
55        })
56    }
57
58    /// Returns the pairing code from the FactoryDataManager proxy service.
59    pub async fn get_pairing_code(&self) -> Result<Vec<u8>, Error> {
60        self.factory_data_manager()?.get_pairing_code().await?.map_err(|e| self.map_weave_err(e))
61    }
62
63    /// Returns the qr code from the StackManager proxy service.
64    pub async fn get_qr_code(&self) -> Result<String, Error> {
65        self.stack()?
66            .get_qr_code()
67            .await?
68            .map(|qr_code| qr_code.data)
69            .map_err(|e| self.map_weave_err(e))
70    }
71
72    /// Returns the pairing state from the PairingStateWatcher service.
73    pub async fn get_pairing_state(&self) -> Result<PairingState, Error> {
74        let watch = self.pairing_state_watcher()?.watch_pairing_state().await;
75        watch.map(|pairing_state| pairing_state.into()).map_err(anyhow::Error::from)
76    }
77
78    /// Resets Weave state by wiping the provided configurations.
79    ///
80    /// # Arguments
81    /// * `args`: The JSON indicating the configurations to reset, in the form of a list of bytes.
82    ///
83    /// # JSON Format
84    /// All fields are optional. Fields set to 'true' reset the corresponding configuration in
85    /// weave, and fields left unset default to 'false'.
86    ///
87    /// {
88    ///   network_config: true,
89    ///   fabric_config: false,
90    ///   service_config: true,
91    ///   operational_credentials: false
92    /// }
93    pub async fn reset_config(&self, args: Value) -> Result<(), Error> {
94        let flags: ResetConfig = serde_json::from_value(args)?;
95        self.stack()?
96            .reset_config(ResetConfigFlags::from(flags))
97            .await?
98            .map_err(|e| self.map_weave_err(e))
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use assert_matches::assert_matches;
106    use fidl::endpoints::create_proxy_and_stream;
107    use fidl_fuchsia_weave::{
108        FactoryDataManagerRequest, PairingStateWatcherRequest, QrCode, StackGetQrCodeResult,
109        StackRequest, StackResetConfigResult,
110    };
111    use fuchsia_async as fasync;
112    use futures::prelude::*;
113    use serde_json::json;
114    use std::sync::LazyLock;
115
116    const PAIRING_CODE: &'static [u8] = b"ABC1234";
117
118    static QR_CODE: LazyLock<QrCode> =
119        LazyLock::new(|| QrCode { data: String::from("qrcodedata") });
120    static PAIRING_STATE: LazyLock<PairingState> = LazyLock::new(|| PairingState {
121        is_wlan_provisioned: Some(true),
122        is_fabric_provisioned: Some(true),
123        is_service_provisioned: Some(false),
124        is_weave_fully_provisioned: Some(false),
125        is_thread_provisioned: Some(true),
126    });
127    static RESET_CONFIG: LazyLock<Value> = LazyLock::new(|| {
128        json!({
129            "network_config": true,
130            // "fabric_config" unset
131            "service_config": true,
132            // "operational_credentials" unset
133        })
134    });
135
136    struct MockStackBuilder {
137        expected_stack: Vec<Box<dyn FnOnce(StackRequest) + Send + 'static>>,
138        expected_pair: Vec<Box<dyn FnOnce(PairingStateWatcherRequest) + Send + 'static>>,
139    }
140
141    impl MockStackBuilder {
142        fn new() -> Self {
143            Self { expected_stack: vec![], expected_pair: vec![] }
144        }
145
146        fn push_stack(mut self, request: impl FnOnce(StackRequest) + Send + 'static) -> Self {
147            self.expected_stack.push(Box::new(request));
148            self
149        }
150
151        fn push_pair(
152            mut self,
153            request: impl FnOnce(PairingStateWatcherRequest) + Send + 'static,
154        ) -> Self {
155            self.expected_pair.push(Box::new(request));
156            self
157        }
158
159        fn expect_get_qr_code(self, result: StackGetQrCodeResult) -> Self {
160            self.push_stack(move |req| match req {
161                StackRequest::GetQrCode { responder } => {
162                    responder.send(result.as_ref().map_err(|e| *e)).unwrap()
163                }
164                req => panic!("unexpected request: {:?}", req),
165            })
166        }
167
168        fn expect_get_pairing_state(self, result: PairingState) -> Self {
169            self.push_pair(move |req| match req {
170                PairingStateWatcherRequest::WatchPairingState { responder } => {
171                    responder.send(&fidl_fuchsia_weave::PairingState::from(result)).unwrap();
172                }
173            })
174        }
175
176        fn expect_reset_config(
177            self,
178            _expected_flags: fidl_fuchsia_weave::ResetConfigFlags,
179            result: StackResetConfigResult,
180        ) -> Self {
181            self.push_stack(move |req| match req {
182                StackRequest::ResetConfig { responder, flags } => {
183                    assert_matches!(flags, _expected_flags);
184                    responder.send(result).unwrap()
185                }
186                req => panic!("unexpected request: {:?}", req),
187            })
188        }
189
190        fn build_stack(self) -> (WeaveFacade, impl Future<Output = ()>) {
191            let (proxy, mut stream) = create_proxy_and_stream::<StackMarker>();
192            let fut = async move {
193                let _ = &self;
194                for expected in self.expected_stack {
195                    expected(stream.next().await.unwrap().unwrap());
196                }
197                assert_matches!(stream.next().await, None);
198            };
199            let facade = WeaveFacade::new();
200            facade.stack.set(proxy).expect("just-created facade should have empty stack");
201            (facade, fut)
202        }
203        fn build_stack_and_pairing_state_watcher(self) -> (WeaveFacade, impl Future<Output = ()>) {
204            let (proxy, mut stream) = create_proxy_and_stream::<StackMarker>();
205            let stream_fut = async move {
206                let _ = &self;
207                match stream.next().await {
208                    Some(Ok(StackRequest::GetPairingStateWatcher {
209                        watcher,
210                        control_handle: _,
211                    })) => {
212                        let mut into_stream = watcher.into_stream();
213                        for expected in self.expected_pair {
214                            expected(into_stream.next().await.unwrap().unwrap());
215                        }
216                        assert_matches!(into_stream.next().await, None);
217                    }
218                    err => panic!("Error in request handler: {:?}", err),
219                }
220            };
221            let facade = WeaveFacade::new();
222            facade.stack.set(proxy).expect("just-created facade should have empty stack");
223            (facade, stream_fut)
224        }
225    }
226
227    struct MockFactoryDataManagerBuilder {
228        expected: Vec<Box<dyn FnOnce(FactoryDataManagerRequest) + Send + 'static>>,
229    }
230
231    impl MockFactoryDataManagerBuilder {
232        fn new() -> Self {
233            Self { expected: vec![] }
234        }
235
236        fn push(
237            mut self,
238            request: impl FnOnce(FactoryDataManagerRequest) + Send + 'static,
239        ) -> Self {
240            self.expected.push(Box::new(request));
241            self
242        }
243
244        fn expect_get_pairing_code(self, result: Result<&'static [u8], ErrorCode>) -> Self {
245            self.push(move |req| match req {
246                FactoryDataManagerRequest::GetPairingCode { responder } => {
247                    responder.send(result).unwrap()
248                }
249                _ => {}
250            })
251        }
252
253        fn build(self) -> (WeaveFacade, impl Future<Output = ()>) {
254            let (proxy, mut stream) =
255                fidl::endpoints::create_proxy_and_stream::<FactoryDataManagerMarker>();
256            let fut = async move {
257                for expected in self.expected {
258                    expected(stream.next().await.unwrap().unwrap());
259                }
260                assert_matches!(stream.next().await, None);
261            };
262            let facade = WeaveFacade::new();
263            facade
264                .factory_data_manager
265                .set(proxy)
266                .expect("just-created facade should have empty fdm");
267            (facade, fut)
268        }
269    }
270
271    #[fasync::run_singlethreaded(test)]
272    async fn test_get_pairing_code() {
273        let (facade, pairing_code_fut) =
274            MockFactoryDataManagerBuilder::new().expect_get_pairing_code(Ok(PAIRING_CODE)).build();
275
276        let facade_fut = async move {
277            assert_eq!(facade.get_pairing_code().await.unwrap(), PAIRING_CODE);
278        };
279
280        future::join(facade_fut, pairing_code_fut).await;
281    }
282
283    #[fasync::run_singlethreaded(test)]
284    async fn test_get_qr_code() {
285        let qr_clone: StackGetQrCodeResult = Ok((*QR_CODE).clone());
286        let (facade, qr_code_fut) =
287            MockStackBuilder::new().expect_get_qr_code(qr_clone).build_stack();
288
289        let facade_fut = async move {
290            assert_eq!(facade.get_qr_code().await.unwrap(), *QR_CODE.data);
291        };
292
293        future::join(facade_fut, qr_code_fut).await;
294    }
295
296    #[fasync::run_singlethreaded(test)]
297    async fn test_get_pairing_state() {
298        let (facade, pairing_state_fut) = MockStackBuilder::new()
299            .expect_get_pairing_state(*PAIRING_STATE)
300            .build_stack_and_pairing_state_watcher();
301
302        let facade_fut = async move {
303            let pairing_state = facade.get_pairing_state().await.unwrap();
304            assert_eq!(pairing_state, *PAIRING_STATE);
305        };
306
307        future::join(facade_fut, pairing_state_fut).await;
308    }
309
310    #[allow(clippy::unit_cmp)] // TODO(https://fxbug.dev/42176996)
311    #[fasync::run_singlethreaded(test)]
312    async fn test_reset_config() {
313        let flags: ResetConfigFlags = fidl_fuchsia_weave::ResetConfigFlags::NETWORK_CONFIG
314            | fidl_fuchsia_weave::ResetConfigFlags::SERVICE_CONFIG;
315        let (facade, reset_config_fut) =
316            MockStackBuilder::new().expect_reset_config(flags, Ok(())).build_stack();
317
318        let facade_fut =
319            async move { assert_eq!(facade.reset_config(RESET_CONFIG.clone()).await.unwrap(), ()) };
320
321        future::join(facade_fut, reset_config_fut).await;
322    }
323}