1mod 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 #[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: &[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::{test_utils, MlmeSink, MlmeStream};
214 use futures::channel::mpsc;
215 use lazy_static::lazy_static;
216 use wlan_common::{assert_variant, timer};
217
218 lazy_static! {
219 static ref AP_ADDR: MacAddr = [6u8; 6].into();
220 static ref CLIENT_ADDR: MacAddr = [7u8; 6].into();
221 }
222
223 fn make_remote_client() -> RemoteClient {
224 RemoteClient::new(*CLIENT_ADDR)
225 }
226
227 fn make_env() -> (Context, MlmeStream, timer::EventStream<Event>) {
228 let device_info = test_utils::fake_device_info(*AP_ADDR);
229 let (mlme_sink, mlme_stream) = mpsc::unbounded();
230 let (timer, time_stream) = timer::create_timer();
231 let ctx = Context { device_info, mlme_sink: MlmeSink::new(mlme_sink), timer };
232 (ctx, mlme_stream, time_stream)
233 }
234
235 #[test]
236 fn aid_when_not_associated() {
237 let r_sta = make_remote_client();
238 assert_eq!(r_sta.aid(), None);
239 }
240
241 #[test]
242 fn authenticated_when_not_authenticated() {
243 let r_sta = make_remote_client();
244 assert!(!r_sta.authenticated());
245 }
246
247 #[test]
248 fn authenticated_when_authenticated() {
249 let mut r_sta = make_remote_client();
250 let (mut ctx, _, _) = make_env();
251 r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
252 assert!(r_sta.authenticated());
253 }
254
255 #[test]
256 fn authenticated_when_associated() {
257 let mut r_sta = make_remote_client();
258 let (mut ctx, _, _) = make_env();
259 r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
260 let mut aid_map = aid::Map::default();
261 r_sta.handle_assoc_ind(
262 &mut ctx,
263 &mut aid_map,
264 CapabilityInfo(0).with_short_preamble(true).raw(),
265 &[SupportedRate(0b11111000)][..],
266 &None,
267 None,
268 );
269 assert!(r_sta.authenticated());
270 }
271
272 #[test]
273 fn aid_when_associated() {
274 let mut r_sta = make_remote_client();
275 let (mut ctx, _, _) = make_env();
276 r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
277 let mut aid_map = aid::Map::default();
278 r_sta.handle_assoc_ind(
279 &mut ctx,
280 &mut aid_map,
281 CapabilityInfo(0).with_short_preamble(true).raw(),
282 &[SupportedRate(0b11111000)][..],
283 &None,
284 None,
285 );
286 assert_eq!(r_sta.aid(), Some(1));
287 }
288
289 #[test]
290 fn aid_after_disassociation() {
291 let mut r_sta = make_remote_client();
292 let (mut ctx, _, _) = make_env();
293 r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
294 assert!(r_sta.authenticated());
295 let mut aid_map = aid::Map::default();
296 r_sta.handle_assoc_ind(
297 &mut ctx,
298 &mut aid_map,
299 CapabilityInfo(0).with_short_preamble(true).raw(),
300 &[SupportedRate(0b11111000)][..],
301 &None,
302 None,
303 );
304 assert_variant!(r_sta.aid(), Some(_));
305 r_sta.handle_disassoc_ind(&mut ctx, &mut aid_map);
306 assert_eq!(r_sta.aid(), None);
307 }
308
309 #[test]
310 fn disassociate_does_nothing_when_not_associated() {
311 let mut r_sta = make_remote_client();
312 let (mut ctx, _, _) = make_env();
313 let mut aid_map = aid::Map::default();
314 r_sta.handle_disassoc_ind(&mut ctx, &mut aid_map);
315 }
316
317 #[test]
318 fn send_authenticate_resp() {
319 let mut r_sta = make_remote_client();
320 let (mut ctx, mut mlme_stream, _) = make_env();
321 r_sta.send_authenticate_resp(
322 &mut ctx,
323 fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired,
324 );
325 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
326 assert_variant!(mlme_event, MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
327 peer_sta_address,
328 result_code,
329 }) => {
330 assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
331 assert_eq!(result_code, fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired);
332 });
333 }
334
335 #[test]
336 fn association_times_out() {
337 let mut r_sta = make_remote_client();
338 let (mut ctx, _, _) = make_env();
339 r_sta.handle_auth_ind(&mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
340 assert!(r_sta.authenticated());
341 r_sta.handle_timeout(&mut ctx, ClientEvent::AssociationTimeout);
342 assert!(!r_sta.authenticated());
343 }
344
345 #[test]
346 fn send_associate_resp() {
347 let mut r_sta = make_remote_client();
348 let (mut ctx, mut mlme_stream, _) = make_env();
349 r_sta.send_associate_resp(
350 &mut ctx,
351 fidl_mlme::AssociateResultCode::RefusedApOutOfMemory,
352 1,
353 CapabilityInfo(0).with_short_preamble(true),
354 vec![SupportedRate(1), SupportedRate(2), SupportedRate(3)],
355 );
356 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
357 assert_variant!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
358 peer_sta_address,
359 result_code,
360 association_id,
361 capability_info,
362 rates,
363 }) => {
364 assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
365 assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedApOutOfMemory);
366 assert_eq!(association_id, 1);
367 assert_eq!(capability_info, CapabilityInfo(0).with_short_preamble(true).raw());
368 assert_eq!(rates, vec![1, 2, 3]);
369 });
370 }
371
372 #[test]
373 fn send_deauthenticate_req() {
374 let mut r_sta = make_remote_client();
375 let (mut ctx, mut mlme_stream, _) = make_env();
376 r_sta.send_deauthenticate_req(&mut ctx, fidl_ieee80211::ReasonCode::NoMoreStas);
377 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
378 assert_variant!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
379 peer_sta_address,
380 reason_code,
381 }) => {
382 assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
383 assert_eq!(reason_code, fidl_ieee80211::ReasonCode::NoMoreStas);
384 });
385 }
386
387 #[test]
388 fn send_eapol_req() {
389 let mut r_sta = make_remote_client();
390 let (mut ctx, mut mlme_stream, _) = make_env();
391 r_sta.send_eapol_req(&mut ctx, test_utils::eapol_key_frame());
392 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
393 assert_variant!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest {
394 src_addr,
395 dst_addr,
396 data,
397 }) => {
398 assert_eq!(&src_addr, AP_ADDR.as_array());
399 assert_eq!(&dst_addr, CLIENT_ADDR.as_array());
400 assert_eq!(data, Vec::<u8>::from(test_utils::eapol_key_frame()));
401 });
402 }
403
404 #[test]
405 fn send_key_ptk() {
406 let mut r_sta = make_remote_client();
407 let (mut ctx, mut mlme_stream, _) = make_env();
408 r_sta.send_key(&mut ctx, &Key::Ptk(test_utils::ptk()));
409 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
410 assert_variant!(mlme_event, MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist }) => {
411 assert_eq!(keylist.len(), 1);
412 let k = keylist.first().expect("expect key descriptor");
413 assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap() as usize]);
414 assert_eq!(k.key_id, 0);
415 assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
416 assert_eq!(&k.address, CLIENT_ADDR.as_array());
417 assert_eq!(k.rsc, 0);
418 assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
419 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
420 });
421 }
422
423 #[test]
424 fn send_key_gtk() {
425 let mut r_sta = make_remote_client();
426 let (mut ctx, mut mlme_stream, _) = make_env();
427 r_sta.send_key(&mut ctx, &Key::Gtk(test_utils::gtk()));
428 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
429 assert_variant!(mlme_event, MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist }) => {
430 assert_eq!(keylist.len(), 1);
431 let k = keylist.first().expect("expect key descriptor");
432 assert_eq!(&k.key[..], &test_utils::gtk_bytes()[..]);
433 assert_eq!(k.key_id, 2);
434 assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
435 assert_eq!(k.address, [0xFFu8; 6]);
436 assert_eq!(k.rsc, 0);
437 assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
438 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
439 });
440 }
441
442 #[test]
443 fn send_set_controlled_port_req() {
444 let mut r_sta = make_remote_client();
445 let (mut ctx, mut mlme_stream, _) = make_env();
446 r_sta.send_set_controlled_port_req(&mut ctx, fidl_mlme::ControlledPortState::Open);
447 let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
448 assert_variant!(mlme_event, MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
449 peer_sta_address,
450 state,
451 }) => {
452 assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
453 assert_eq!(state, fidl_mlme::ControlledPortState::Open);
454 });
455 }
456
457 #[test]
458 fn schedule_at() {
459 let mut r_sta = make_remote_client();
460 let (mut ctx, _, mut time_stream) = make_env();
461 let timeout_event = r_sta.schedule_at(
462 &mut ctx,
463 zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(2)),
464 ClientEvent::AssociationTimeout,
465 );
466 let (_, timed_event, _) = time_stream.try_next().unwrap().expect("expected timed event");
467 assert_eq!(timed_event.id, timeout_event.id());
468 assert_variant!(timed_event.event, Event::Client { addr, event } => {
469 assert_eq!(addr, *CLIENT_ADDR);
470 assert_variant!(event, ClientEvent::AssociationTimeout);
471 });
472 }
473}