wlan_sme/ap/remote_client/
mod.rs

1// Copyright 2021 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
5mod state;
6
7use state::*;
8
9use crate::ap::event::{ClientEvent, Event};
10use crate::ap::{Context, MlmeRequest, RsnCfg, aid};
11use ieee80211::{MacAddr, MacAddrBytes};
12use log::error;
13use wlan_common::ie::SupportedRate;
14use wlan_common::mac::{Aid, CapabilityInfo};
15use wlan_common::timer::EventHandle;
16use wlan_rsn::key::Tk;
17use wlan_rsn::key::exchange::Key;
18use {fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme};
19
20pub struct RemoteClient {
21    pub addr: MacAddr,
22    state: Option<States>,
23}
24
25impl RemoteClient {
26    pub fn new(addr: MacAddr) -> Self {
27        Self { addr, state: Some(States::new_initial()) }
28    }
29
30    pub fn aid(&self) -> Option<Aid> {
31        // Safe: |state| is never None and always replaced with Some(..).
32        #[expect(clippy::unwrap_used)]
33        let aid = self.state.as_ref().unwrap().aid();
34        aid
35    }
36
37    pub fn authenticated(&self) -> bool {
38        // Safe: |state| is never None and always replaced with Some(..).
39        #[expect(clippy::unwrap_used)]
40        let authenticated = self.state.as_ref().unwrap().authenticated();
41        authenticated
42    }
43
44    pub fn associated(&self) -> bool {
45        self.aid().is_some()
46    }
47
48    pub fn handle_auth_ind(
49        &mut self,
50        ctx: &mut Context,
51        auth_type: fidl_mlme::AuthenticationTypes,
52    ) {
53        // Safe: |state| is never None and always replaced with Some(..).
54        self.state = self.state.take().map(|state| state.handle_auth_ind(self, ctx, auth_type));
55    }
56
57    #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
58    pub fn handle_assoc_ind(
59        &mut self,
60        ctx: &mut Context,
61        aid_map: &mut aid::Map,
62        client_capabilities: u16,
63        client_rates: Vec<SupportedRate>,
64        rsn_cfg: &Option<RsnCfg>,
65        s_rsne: Option<Vec<u8>>,
66    ) {
67        // Safe: |state| is never None and always replaced with Some(..).
68        self.state = self.state.take().map(|state| {
69            state.handle_assoc_ind(
70                self,
71                ctx,
72                aid_map,
73                client_capabilities,
74                client_rates,
75                rsn_cfg,
76                s_rsne,
77            )
78        });
79    }
80
81    pub fn handle_disassoc_ind(&mut self, ctx: &mut Context, aid_map: &mut aid::Map) {
82        // Safe: |state| is never None and always replaced with Some(..).
83        self.state = self.state.take().map(|state| state.handle_disassoc_ind(self, ctx, aid_map));
84    }
85
86    pub fn handle_eapol_ind(&mut self, ctx: &mut Context, data: &[u8]) {
87        // Safe: |state| is never None and always replaced with Some(..).
88        self.state = self.state.take().map(|state| state.handle_eapol_ind(self, ctx, data));
89    }
90
91    pub fn handle_eapol_conf(&mut self, ctx: &mut Context, result: fidl_mlme::EapolResultCode) {
92        // Safe: |state| is never None and always replaced with Some(..).
93        self.state = self.state.take().map(|state| state.handle_eapol_conf(self, ctx, result));
94    }
95
96    pub fn handle_timeout(&mut self, ctx: &mut Context, event: ClientEvent) {
97        // Safe: |state| is never None and always replaced with Some(..).
98        self.state = self.state.take().map(|state| state.handle_timeout(self, ctx, event));
99    }
100
101    /// Sends MLME-AUTHENTICATE.response (IEEE Std 802.11-2016, 6.3.5.5) to the MLME.
102    pub fn send_authenticate_resp(
103        &mut self,
104        ctx: &mut Context,
105        result_code: fidl_mlme::AuthenticateResultCode,
106    ) {
107        // TODO(https://fxbug.dev/42172646) - Added to help investigate hw-sim test. Remove later
108        log::info!("Sending fidl_mlme::AuthenticateResponse - result code: {:?}", result_code);
109        ctx.mlme_sink.send(MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
110            peer_sta_address: self.addr.to_array(),
111            result_code,
112        }))
113    }
114
115    /// Sends MLME-DEAUTHENTICATE.request (IEEE Std 802.11-2016, 6.3.6.2) to the MLME.
116    pub fn send_deauthenticate_req(
117        &mut self,
118        ctx: &mut Context,
119        reason_code: fidl_ieee80211::ReasonCode,
120    ) {
121        ctx.mlme_sink.send(MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
122            peer_sta_address: self.addr.to_array(),
123            reason_code,
124        }))
125    }
126
127    /// Sends MLME-ASSOCIATE.response (IEEE Std 802.11-2016, 6.3.7.5) to the MLME.
128    pub fn send_associate_resp(
129        &mut self,
130        ctx: &mut Context,
131        result_code: fidl_mlme::AssociateResultCode,
132        aid: Aid,
133        capabilities: CapabilityInfo,
134        rates: Vec<SupportedRate>,
135    ) {
136        ctx.mlme_sink.send(MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
137            peer_sta_address: self.addr.to_array(),
138            result_code,
139            association_id: aid,
140            capability_info: capabilities.0,
141            rates: rates.into_iter().map(|r| r.0).collect(),
142        }))
143    }
144
145    /// Sends MLME-EAPOL.request (IEEE Std 802.11-2016, 6.3.22.1) to the MLME.
146    pub fn send_eapol_req(&mut self, ctx: &mut Context, frame: eapol::KeyFrameBuf) {
147        ctx.mlme_sink.send(MlmeRequest::Eapol(fidl_mlme::EapolRequest {
148            src_addr: ctx.device_info.sta_addr,
149            dst_addr: self.addr.to_array(),
150            data: frame.into(),
151        }));
152    }
153
154    /// Sends SET_CONTROLLED_PORT.request (fuchsia.wlan.mlme.SetControlledPortRequest) to the MLME.
155    pub fn send_set_controlled_port_req(
156        &mut self,
157        ctx: &mut Context,
158        port_state: fidl_mlme::ControlledPortState,
159    ) {
160        ctx.mlme_sink.send(MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
161            peer_sta_address: self.addr.to_array(),
162            state: port_state,
163        }));
164    }
165
166    pub fn send_key(&mut self, ctx: &mut Context, key: &Key) {
167        let set_key_descriptor = match key {
168            Key::Ptk(ptk) => fidl_mlme::SetKeyDescriptor {
169                key: ptk.tk().to_vec(),
170                key_id: 0,
171                key_type: fidl_mlme::KeyType::Pairwise,
172                address: self.addr.to_array(),
173                rsc: 0,
174                cipher_suite_oui: eapol::to_array(&ptk.cipher.oui[..]),
175                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
176                    ptk.cipher.suite_type.into(),
177                ),
178            },
179            Key::Gtk(gtk) => fidl_mlme::SetKeyDescriptor {
180                key: gtk.tk().to_vec(),
181                key_id: gtk.key_id() as u16,
182                key_type: fidl_mlme::KeyType::Group,
183                address: [0xFFu8; 6],
184                rsc: gtk.key_rsc(),
185                cipher_suite_oui: eapol::to_array(&gtk.cipher().oui[..]),
186                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
187                    gtk.cipher().suite_type.into(),
188                ),
189            },
190            _ => {
191                error!("unsupported key type in UpdateSink");
192                return;
193            }
194        };
195        ctx.mlme_sink.send(MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest {
196            keylist: vec![set_key_descriptor],
197        }));
198    }
199
200    pub fn schedule_at(
201        &mut self,
202        ctx: &mut Context,
203        deadline: zx::MonotonicInstant,
204        event: ClientEvent,
205    ) -> EventHandle {
206        ctx.timer.schedule_at(deadline, Event::Client { addr: self.addr, event })
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::{MlmeSink, MlmeStream, test_utils};
214    use assert_matches::assert_matches;
215    use futures::channel::mpsc;
216    use ieee80211::MacAddr;
217    use std::sync::LazyLock;
218    use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
219    use wlan_common::timer;
220
221    static AP_ADDR: LazyLock<MacAddr> = LazyLock::new(|| [6u8; 6].into());
222    static CLIENT_ADDR: LazyLock<MacAddr> = LazyLock::new(|| [7u8; 6].into());
223
224    fn make_remote_client() -> RemoteClient {
225        RemoteClient::new(*CLIENT_ADDR)
226    }
227
228    fn make_env() -> (Context, MlmeStream, timer::EventStream<Event>) {
229        let device_info = test_utils::fake_device_info(*AP_ADDR);
230        let (mlme_sink, mlme_stream) = mpsc::unbounded();
231        let (timer, time_stream) = timer::create_timer();
232        let ctx = Context {
233            device_info,
234            spectrum_management_support: fake_spectrum_management_support_empty(),
235            mlme_sink: MlmeSink::new(mlme_sink),
236            timer,
237        };
238        (ctx, mlme_stream, time_stream)
239    }
240
241    #[test]
242    fn aid_when_not_associated() {
243        let r_sta = make_remote_client();
244        assert_eq!(r_sta.aid(), None);
245    }
246
247    #[test]
248    fn authenticated_when_not_authenticated() {
249        let r_sta = make_remote_client();
250        assert!(!r_sta.authenticated());
251    }
252
253    #[test]
254    fn authenticated_when_authenticated() {
255        let mut r_sta = make_remote_client();
256        let (mut ctx, _, _) = make_env();
257        r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
258        assert!(r_sta.authenticated());
259    }
260
261    #[test]
262    fn authenticated_when_associated() {
263        let mut r_sta = make_remote_client();
264        let (mut ctx, _, _) = make_env();
265        r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
266        let mut aid_map = aid::Map::default();
267        r_sta.handle_assoc_ind(
268            &mut ctx,
269            &mut aid_map,
270            CapabilityInfo(0).with_short_preamble(true).raw(),
271            vec![SupportedRate(0b11111000)],
272            &None,
273            None,
274        );
275        assert!(r_sta.authenticated());
276    }
277
278    #[test]
279    fn aid_when_associated() {
280        let mut r_sta = make_remote_client();
281        let (mut ctx, _, _) = make_env();
282        r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
283        let mut aid_map = aid::Map::default();
284        r_sta.handle_assoc_ind(
285            &mut ctx,
286            &mut aid_map,
287            CapabilityInfo(0).with_short_preamble(true).raw(),
288            vec![SupportedRate(0b11111000)],
289            &None,
290            None,
291        );
292        assert_eq!(r_sta.aid(), Some(1));
293    }
294
295    #[test]
296    fn aid_after_disassociation() {
297        let mut r_sta = make_remote_client();
298        let (mut ctx, _, _) = make_env();
299        r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
300        assert!(r_sta.authenticated());
301        let mut aid_map = aid::Map::default();
302        r_sta.handle_assoc_ind(
303            &mut ctx,
304            &mut aid_map,
305            CapabilityInfo(0).with_short_preamble(true).raw(),
306            vec![SupportedRate(0b11111000)],
307            &None,
308            None,
309        );
310        assert_matches!(r_sta.aid(), Some(_));
311        r_sta.handle_disassoc_ind(&mut ctx, &mut aid_map);
312        assert_eq!(r_sta.aid(), None);
313    }
314
315    #[test]
316    fn disassociate_does_nothing_when_not_associated() {
317        let mut r_sta = make_remote_client();
318        let (mut ctx, _, _) = make_env();
319        let mut aid_map = aid::Map::default();
320        r_sta.handle_disassoc_ind(&mut ctx, &mut aid_map);
321    }
322
323    #[test]
324    fn send_authenticate_resp() {
325        let mut r_sta = make_remote_client();
326        let (mut ctx, mut mlme_stream, _) = make_env();
327        r_sta.send_authenticate_resp(
328            &mut ctx,
329            fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired,
330        );
331        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
332        assert_matches!(mlme_event, MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
333            peer_sta_address,
334            result_code,
335        }) => {
336            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
337            assert_eq!(result_code, fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired);
338        });
339    }
340
341    #[test]
342    fn association_times_out() {
343        let mut r_sta = make_remote_client();
344        let (mut ctx, _, _) = make_env();
345        r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
346        assert!(r_sta.authenticated());
347        r_sta.handle_timeout(&mut ctx, ClientEvent::AssociationTimeout);
348        assert!(!r_sta.authenticated());
349    }
350
351    #[test]
352    fn send_associate_resp() {
353        let mut r_sta = make_remote_client();
354        let (mut ctx, mut mlme_stream, _) = make_env();
355        r_sta.send_associate_resp(
356            &mut ctx,
357            fidl_mlme::AssociateResultCode::RefusedApOutOfMemory,
358            1,
359            CapabilityInfo(0).with_short_preamble(true),
360            vec![SupportedRate(1), SupportedRate(2), SupportedRate(3)],
361        );
362        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
363        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
364            peer_sta_address,
365            result_code,
366            association_id,
367            capability_info,
368            rates,
369        }) => {
370            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
371            assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedApOutOfMemory);
372            assert_eq!(association_id, 1);
373            assert_eq!(capability_info, CapabilityInfo(0).with_short_preamble(true).raw());
374            assert_eq!(rates, vec![1, 2, 3]);
375        });
376    }
377
378    #[test]
379    fn send_deauthenticate_req() {
380        let mut r_sta = make_remote_client();
381        let (mut ctx, mut mlme_stream, _) = make_env();
382        r_sta.send_deauthenticate_req(&mut ctx, fidl_ieee80211::ReasonCode::NoMoreStas);
383        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
384        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
385            peer_sta_address,
386            reason_code,
387        }) => {
388            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
389            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::NoMoreStas);
390        });
391    }
392
393    #[test]
394    fn send_eapol_req() {
395        let mut r_sta = make_remote_client();
396        let (mut ctx, mut mlme_stream, _) = make_env();
397        r_sta.send_eapol_req(&mut ctx, test_utils::eapol_key_frame());
398        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
399        assert_matches!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest {
400            src_addr,
401            dst_addr,
402            data,
403        }) => {
404            assert_eq!(&src_addr, AP_ADDR.as_array());
405            assert_eq!(&dst_addr, CLIENT_ADDR.as_array());
406            assert_eq!(data, Vec::<u8>::from(test_utils::eapol_key_frame()));
407        });
408    }
409
410    #[test]
411    fn send_key_ptk() {
412        let mut r_sta = make_remote_client();
413        let (mut ctx, mut mlme_stream, _) = make_env();
414        r_sta.send_key(&mut ctx, &Key::Ptk(test_utils::ptk()));
415        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
416        assert_matches!(mlme_event, MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist }) => {
417            assert_eq!(keylist.len(), 1);
418            let k = keylist.first().expect("expect key descriptor");
419            assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap() as usize]);
420            assert_eq!(k.key_id, 0);
421            assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
422            assert_eq!(&k.address, CLIENT_ADDR.as_array());
423            assert_eq!(k.rsc, 0);
424            assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
425            assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
426        });
427    }
428
429    #[test]
430    fn send_key_gtk() {
431        let mut r_sta = make_remote_client();
432        let (mut ctx, mut mlme_stream, _) = make_env();
433        r_sta.send_key(&mut ctx, &Key::Gtk(test_utils::gtk()));
434        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
435        assert_matches!(mlme_event, MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist }) => {
436            assert_eq!(keylist.len(), 1);
437            let k = keylist.first().expect("expect key descriptor");
438            assert_eq!(&k.key[..], &test_utils::gtk_bytes()[..]);
439            assert_eq!(k.key_id, 2);
440            assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
441            assert_eq!(k.address, [0xFFu8; 6]);
442            assert_eq!(k.rsc, 0);
443            assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
444            assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
445        });
446    }
447
448    #[test]
449    fn send_set_controlled_port_req() {
450        let mut r_sta = make_remote_client();
451        let (mut ctx, mut mlme_stream, _) = make_env();
452        r_sta.send_set_controlled_port_req(&mut ctx, fidl_mlme::ControlledPortState::Open);
453        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
454        assert_matches!(mlme_event, MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
455            peer_sta_address,
456            state,
457        }) => {
458            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
459            assert_eq!(state, fidl_mlme::ControlledPortState::Open);
460        });
461    }
462
463    #[test]
464    fn schedule_at() {
465        let mut r_sta = make_remote_client();
466        let (mut ctx, _, mut time_stream) = make_env();
467        let timeout_event = r_sta.schedule_at(
468            &mut ctx,
469            zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(2)),
470            ClientEvent::AssociationTimeout,
471        );
472        let (_, timed_event, _) = time_stream.try_next().unwrap().expect("expected timed event");
473        assert_eq!(timed_event.id, timeout_event.id());
474        assert_matches!(timed_event.event, Event::Client { addr, event } => {
475            assert_eq!(addr, *CLIENT_ADDR);
476            assert_matches!(event, ClientEvent::AssociationTimeout);
477        });
478    }
479}