wlancfg_lib/util/listener/
client.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 super::generic::{CurrentStateCache, Listener, Message};
6use crate::client::types as client_types;
7use fidl_fuchsia_wlan_policy as fidl_policy;
8use futures::channel::mpsc;
9use futures::future::LocalBoxFuture;
10use futures::prelude::*;
11
12#[derive(Clone, PartialEq)]
13#[cfg_attr(test, derive(Debug))]
14pub struct ClientNetworkState {
15    pub id: client_types::NetworkIdentifier,
16    pub state: client_types::ConnectionState,
17    pub status: Option<client_types::DisconnectStatus>,
18}
19
20impl From<ClientNetworkState> for fidl_policy::NetworkState {
21    fn from(client_network_state: ClientNetworkState) -> Self {
22        fidl_policy::NetworkState {
23            id: Some(client_network_state.id.into()),
24            state: Some(client_network_state.state),
25            status: client_network_state.status,
26            ..Default::default()
27        }
28    }
29}
30
31#[cfg_attr(test, derive(Debug))]
32#[derive(Clone, PartialEq)]
33pub struct ClientStateUpdate {
34    pub state: fidl_policy::WlanClientState,
35    pub networks: Vec<ClientNetworkState>,
36}
37
38impl From<ClientStateUpdate> for fidl_policy::ClientStateSummary {
39    fn from(update: ClientStateUpdate) -> Self {
40        fidl_policy::ClientStateSummary {
41            state: Some(update.state),
42            networks: Some(update.networks.iter().map(|n| n.clone().into()).collect()),
43            ..Default::default()
44        }
45    }
46}
47
48impl CurrentStateCache for ClientStateUpdate {
49    fn default() -> ClientStateUpdate {
50        // The default client state is disabled until a later update explicitly sets the state to
51        // enabled.
52        ClientStateUpdate {
53            state: fidl_policy::WlanClientState::ConnectionsDisabled,
54            networks: vec![],
55        }
56    }
57
58    fn merge_in_update(&mut self, update: Self) {
59        self.state = update.state;
60        // Remove networks that are present in the update
61        self.networks = self
62            .networks
63            .iter()
64            .filter(|n| !update.networks.iter().any(|u| u.id == n.id))
65            .cloned()
66            .collect();
67        // Push in all the networks from the update
68        for network in update.networks.iter() {
69            self.networks.push(network.clone());
70        }
71    }
72
73    fn purge(&mut self) {
74        // Remove networks with disconnected/failed state from cache
75        self.networks = self
76            .networks
77            .iter()
78            .filter(|n| match n.state {
79                fidl_policy::ConnectionState::Failed => false,
80                fidl_policy::ConnectionState::Disconnected => false,
81                fidl_policy::ConnectionState::Connecting => true,
82                fidl_policy::ConnectionState::Connected => true,
83            })
84            .cloned()
85            .collect();
86    }
87}
88impl Listener<fidl_policy::ClientStateSummary> for fidl_policy::ClientStateUpdatesProxy {
89    fn notify_listener(
90        self,
91        update: fidl_policy::ClientStateSummary,
92    ) -> LocalBoxFuture<'static, Option<Box<Self>>> {
93        let fut =
94            async move { self.on_client_state_update(&update).await.ok().map(|()| Box::new(self)) };
95        fut.boxed()
96    }
97}
98
99// Helpful aliases for servicing client updates
100pub type ClientListenerMessage = Message<fidl_policy::ClientStateUpdatesProxy, ClientStateUpdate>;
101pub type ClientListenerMessageSender = mpsc::UnboundedSender<ClientListenerMessage>;
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use crate::client::types::Ssid;
107    use fidl_fuchsia_wlan_policy as fidl_policy;
108
109    #[fuchsia::test]
110    fn merge_update_none_to_one_active() {
111        let mut current_state_cache = ClientStateUpdate::default();
112        assert_eq!(
113            current_state_cache,
114            ClientStateUpdate {
115                state: fidl_policy::WlanClientState::ConnectionsDisabled,
116                networks: vec![]
117            }
118        );
119
120        // Merge an update with one connected network.
121        let update = ClientStateUpdate {
122            state: fidl_policy::WlanClientState::ConnectionsEnabled,
123            networks: vec![ClientNetworkState {
124                id: client_types::NetworkIdentifier {
125                    ssid: Ssid::try_from("ssid 1").unwrap(),
126                    security_type: client_types::SecurityType::Wpa2,
127                },
128                state: fidl_policy::ConnectionState::Connected,
129                status: None,
130            }],
131        };
132        current_state_cache.merge_in_update(update);
133
134        assert_eq!(
135            current_state_cache,
136            ClientStateUpdate {
137                state: fidl_policy::WlanClientState::ConnectionsEnabled,
138                networks: vec![ClientNetworkState {
139                    id: client_types::NetworkIdentifier {
140                        ssid: Ssid::try_from("ssid 1").unwrap(),
141                        security_type: client_types::SecurityType::Wpa2,
142                    },
143                    state: fidl_policy::ConnectionState::Connected,
144                    status: None,
145                }],
146            }
147        );
148    }
149
150    #[fuchsia::test]
151    fn merge_update_one_to_two_active() {
152        // Start with a connected network
153        let mut current_state_cache = ClientStateUpdate {
154            state: fidl_policy::WlanClientState::ConnectionsEnabled,
155            networks: vec![ClientNetworkState {
156                id: client_types::NetworkIdentifier {
157                    ssid: Ssid::try_from("ssid 1").unwrap(),
158                    security_type: client_types::SecurityType::Wpa2,
159                },
160                state: fidl_policy::ConnectionState::Connected,
161                status: None,
162            }],
163        };
164
165        // Merge an update with a different connecting network.
166        let update = ClientStateUpdate {
167            state: fidl_policy::WlanClientState::ConnectionsEnabled,
168            networks: vec![ClientNetworkState {
169                id: client_types::NetworkIdentifier {
170                    ssid: Ssid::try_from("ssid 2").unwrap(),
171                    security_type: client_types::SecurityType::Wpa2,
172                },
173                state: fidl_policy::ConnectionState::Connecting,
174                status: None,
175            }],
176        };
177        current_state_cache.merge_in_update(update);
178
179        // Both networks are "active" and should be present
180        assert_eq!(
181            current_state_cache,
182            ClientStateUpdate {
183                state: fidl_policy::WlanClientState::ConnectionsEnabled,
184                networks: vec![
185                    ClientNetworkState {
186                        id: client_types::NetworkIdentifier {
187                            ssid: Ssid::try_from("ssid 1").unwrap(),
188                            security_type: client_types::SecurityType::Wpa2,
189                        },
190                        state: fidl_policy::ConnectionState::Connected,
191                        status: None,
192                    },
193                    ClientNetworkState {
194                        id: client_types::NetworkIdentifier {
195                            ssid: Ssid::try_from("ssid 2").unwrap(),
196                            security_type: client_types::SecurityType::Wpa2,
197                        },
198                        state: fidl_policy::ConnectionState::Connecting,
199                        status: None,
200                    }
201                ],
202            }
203        );
204    }
205
206    #[fuchsia::test]
207    fn purge_inactive_network_states() {
208        let mut current_state_cache = ClientStateUpdate {
209            state: fidl_policy::WlanClientState::ConnectionsEnabled,
210            networks: vec![
211                ClientNetworkState {
212                    id: client_types::NetworkIdentifier {
213                        ssid: Ssid::try_from("ssid 1").unwrap(),
214                        security_type: client_types::SecurityType::Wpa2,
215                    },
216                    state: fidl_policy::ConnectionState::Connected,
217                    status: None,
218                },
219                ClientNetworkState {
220                    id: client_types::NetworkIdentifier {
221                        ssid: Ssid::try_from("ssid 2").unwrap(),
222                        security_type: client_types::SecurityType::Wpa2,
223                    },
224                    state: fidl_policy::ConnectionState::Disconnected,
225                    status: None,
226                },
227                ClientNetworkState {
228                    id: client_types::NetworkIdentifier {
229                        ssid: Ssid::try_from("ssid 3").unwrap(),
230                        security_type: client_types::SecurityType::Wpa2,
231                    },
232                    state: fidl_policy::ConnectionState::Failed,
233                    status: Some(client_types::DisconnectStatus::ConnectionStopped),
234                },
235            ],
236        };
237
238        // Should remove both inactive network states
239        current_state_cache.purge();
240
241        assert_eq!(
242            current_state_cache,
243            ClientStateUpdate {
244                state: fidl_policy::WlanClientState::ConnectionsEnabled,
245                networks: vec![ClientNetworkState {
246                    id: client_types::NetworkIdentifier {
247                        ssid: Ssid::try_from("ssid 1").unwrap(),
248                        security_type: client_types::SecurityType::Wpa2,
249                    },
250                    state: fidl_policy::ConnectionState::Connected,
251                    status: None,
252                }],
253            }
254        )
255    }
256
257    #[fuchsia::test]
258    fn into_fidl() {
259        let single_network_state = ClientStateUpdate {
260            state: fidl_policy::WlanClientState::ConnectionsEnabled,
261            networks: vec![ClientNetworkState {
262                id: client_types::NetworkIdentifier {
263                    ssid: Ssid::try_from("ssid 1").unwrap(),
264                    security_type: client_types::SecurityType::Wpa2,
265                },
266                state: fidl_policy::ConnectionState::Connected,
267                status: None,
268            }],
269        };
270        let fidl_state: fidl_policy::ClientStateSummary = single_network_state.into();
271        assert_eq!(
272            fidl_state,
273            fidl_policy::ClientStateSummary {
274                state: Some(fidl_policy::WlanClientState::ConnectionsEnabled),
275                networks: Some(vec![fidl_policy::NetworkState {
276                    id: Some(fidl_policy::NetworkIdentifier {
277                        ssid: Ssid::try_from("ssid 1").unwrap().to_vec(),
278                        type_: fidl_policy::SecurityType::Wpa2,
279                    }),
280                    state: Some(fidl_policy::ConnectionState::Connected),
281                    status: None,
282                    ..Default::default()
283                }]),
284                ..Default::default()
285            }
286        );
287
288        let multi_network_state = ClientStateUpdate {
289            state: fidl_policy::WlanClientState::ConnectionsEnabled,
290            networks: vec![
291                ClientNetworkState {
292                    id: client_types::NetworkIdentifier {
293                        ssid: Ssid::try_from("ssid 2").unwrap(),
294                        security_type: client_types::SecurityType::Wpa2,
295                    },
296                    state: fidl_policy::ConnectionState::Connecting,
297                    status: None,
298                },
299                ClientNetworkState {
300                    id: client_types::NetworkIdentifier {
301                        ssid: Ssid::try_from("ssid 1").unwrap(),
302                        security_type: client_types::SecurityType::Wpa2,
303                    },
304                    state: fidl_policy::ConnectionState::Disconnected,
305                    status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
306                },
307            ],
308        };
309        let fidl_state: fidl_policy::ClientStateSummary = multi_network_state.into();
310        assert_eq!(
311            fidl_state,
312            fidl_policy::ClientStateSummary {
313                state: Some(fidl_policy::WlanClientState::ConnectionsEnabled),
314                networks: Some(vec![
315                    fidl_policy::NetworkState {
316                        id: Some(fidl_policy::NetworkIdentifier {
317                            ssid: Ssid::try_from("ssid 2").unwrap().to_vec(),
318                            type_: fidl_policy::SecurityType::Wpa2,
319                        }),
320                        state: Some(fidl_policy::ConnectionState::Connecting),
321                        status: None,
322                        ..Default::default()
323                    },
324                    fidl_policy::NetworkState {
325                        id: Some(fidl_policy::NetworkIdentifier {
326                            ssid: Ssid::try_from("ssid 1").unwrap().to_vec(),
327                            type_: fidl_policy::SecurityType::Wpa2,
328                        }),
329                        state: Some(fidl_policy::ConnectionState::Disconnected),
330                        status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
331                        ..Default::default()
332                    },
333                ]),
334                ..Default::default()
335            }
336        );
337    }
338}