1mod 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 #[expect(clippy::unwrap_used)]
33 let aid = self.state.as_ref().unwrap().aid();
34 aid
35 }
36
37 pub fn authenticated(&self) -> bool {
38 #[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 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 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 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 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 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 self.state = self.state.take().map(|state| state.handle_timeout(self, ctx, event));
99 }
100
101 pub fn send_authenticate_resp(
103 &mut self,
104 ctx: &mut Context,
105 result_code: fidl_mlme::AuthenticateResultCode,
106 ) {
107 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 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 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 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 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(>k.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}