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