1use crate::client::{ClientSmeStatus, ServingApInfo};
6use fuchsia_inspect::{
7 BoolProperty, BytesProperty, Inspector, IntProperty, Node, Property, StringProperty,
8 UintProperty,
9};
10use fuchsia_inspect_contrib::inspect_insert;
11use fuchsia_inspect_contrib::log::{InspectListClosure, InspectUintArray};
12use fuchsia_inspect_contrib::nodes::{BoundedListNode, MonotonicTimeProperty, NodeTimeExt};
13use fuchsia_sync::Mutex;
14use ieee80211::Ssid;
15use wlan_common::ie::{self, wsc};
16use {
17 fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
18 fidl_fuchsia_wlan_mlme as fidl_mlme,
19};
20
21const STATE_EVENTS_LIMIT: usize = 50;
25const RSN_EVENTS_LIMIT: usize = 50;
26
27const JOIN_SCAN_EVENTS_LIMIT: usize = 10;
29
30const IDLE_STR: &str = "idle";
32
33pub struct SmeTree {
35 pub inspector: Inspector,
37 pub _root_node: Node,
39 pub state_events: Mutex<BoundedListNode>,
42 pub rsn_events: Mutex<BoundedListNode>,
44 #[allow(dead_code)]
47 pub join_scan_events: Mutex<BoundedListNode>,
48 pub last_pulse: Mutex<PulseNode>,
55
56 pub scan_discard_fidl_bss: UintProperty,
58
59 pub scan_merge_ie_failures: UintProperty,
62}
63
64impl SmeTree {
65 pub fn new(
66 inspector: Inspector,
67 node: Node,
68 device_info: &fidl_mlme::DeviceInfo,
69 spectrum_management_support: &fidl_common::SpectrumManagementSupport,
70 ) -> Self {
71 let state_events =
72 BoundedListNode::new(node.create_child("state_events"), STATE_EVENTS_LIMIT);
73 let rsn_events = BoundedListNode::new(node.create_child("rsn_events"), RSN_EVENTS_LIMIT);
74 let join_scan_events =
75 BoundedListNode::new(node.create_child("join_scan_events"), JOIN_SCAN_EVENTS_LIMIT);
76 let pulse = PulseNode::new(node.create_child("last_pulse"));
77 let scan_discard_fidl_bss = node.create_uint("scan_discard_fidl_bss", 0);
78 let scan_merge_ie_failures = node.create_uint("scan_merge_ie_failures", 0);
79 inspect_insert!(node, device_support: {
80 device_info: {
81 bands: InspectListClosure(&device_info.bands, |node, key, band| {
82 inspect_insert!(node, var key: {
83 band: match band.band {
84 fidl_ieee80211::WlanBand::TwoGhz => "2.4Ghz",
85 fidl_ieee80211::WlanBand::FiveGhz => "5Ghz",
86 _ => "Unknown",
87 },
88 operating_channels: InspectUintArray::new(&band.operating_channels),
89 });
90 }),
91 },
92 spectrum_management_support: {
93 dfs: {
94 supported: spectrum_management_support
95 .dfs
96 .as_ref()
97 .is_some_and(|dfs| dfs.supported.unwrap_or(false)),
98 }
99 }
100 });
101 Self {
102 inspector,
103 _root_node: node,
104 state_events: Mutex::new(state_events),
105 rsn_events: Mutex::new(rsn_events),
106 join_scan_events: Mutex::new(join_scan_events),
107 last_pulse: Mutex::new(pulse),
108 scan_discard_fidl_bss,
109 scan_merge_ie_failures,
110 }
111 }
112
113 pub fn update_pulse(&self, new_status: ClientSmeStatus) {
114 self.last_pulse.lock().update(new_status)
115 }
116
117 pub fn clone_vmo_data(&self) -> Option<fidl::Vmo> {
118 self.inspector.copy_vmo()
119 }
120}
121
122pub struct PulseNode {
123 node: Node,
124 _started: MonotonicTimeProperty,
125 last_updated: MonotonicTimeProperty,
126 last_link_up: Option<MonotonicTimeProperty>,
127 status_node: Option<ClientSmeStatusNode>,
128
129 status: Option<ClientSmeStatus>,
131}
132
133impl PulseNode {
134 fn new(node: Node) -> Self {
135 let now = zx::MonotonicInstant::get();
136 let started = node.create_time_at("started", now);
137 let last_updated = node.create_time_at("last_updated", now);
138 Self {
139 node,
140 _started: started,
141 last_updated,
142 last_link_up: None,
143 status_node: None,
144 status: None,
145 }
146 }
147
148 pub fn update(&mut self, new_status: ClientSmeStatus) {
149 let now = zx::MonotonicInstant::get();
150 self.last_updated.set_at(now);
151
152 if new_status.is_connected()
156 || self.status.as_ref().map(|s| s.is_connected()).unwrap_or(false)
157 {
158 match &self.last_link_up {
159 Some(last_link_up) => last_link_up.set_at(now),
160 None => self.last_link_up = Some(self.node.create_time_at("last_link_up", now)),
161 }
162 }
163
164 if let Some(status) = &self.status
166 && *status == new_status
167 {
168 return;
169 }
170
171 match self.status_node.as_mut() {
172 Some(status_node) => status_node.update(self.status.as_ref(), &new_status),
173 None => {
174 self.status_node =
175 Some(ClientSmeStatusNode::new(self.node.create_child("status"), &new_status))
176 }
177 }
178 self.status = Some(new_status);
179 }
180}
181
182pub struct ClientSmeStatusNode {
183 node: Node,
184 status_str: StringProperty,
185 prev_connected_to: Option<ServingApInfoNode>,
186 connected_to: Option<ServingApInfoNode>,
187 connecting_to: Option<ConnectingToNode>,
188}
189
190impl ClientSmeStatusNode {
191 fn new(node: Node, status: &ClientSmeStatus) -> Self {
192 let status_str = node.create_string("status_str", IDLE_STR);
193 let mut status_node = Self {
194 node,
195 status_str,
196 prev_connected_to: None,
197 connected_to: None,
198 connecting_to: None,
199 };
200 status_node.update(None, status);
201 status_node
202 }
203
204 pub fn update(&mut self, old_status: Option<&ClientSmeStatus>, new_status: &ClientSmeStatus) {
205 let status_str = match new_status {
206 ClientSmeStatus::Connected(_) => "connected",
207 ClientSmeStatus::Connecting(_) => "connecting",
208 ClientSmeStatus::Roaming(_) => "roaming",
209 ClientSmeStatus::Idle => IDLE_STR,
210 };
211 self.status_str.set(status_str);
212
213 if status_str == IDLE_STR
214 && let Some(ClientSmeStatus::Connected(serving_ap_info)) = old_status
215 {
216 match self.prev_connected_to.as_mut() {
217 Some(prev_connected_to) => prev_connected_to.update(serving_ap_info),
218 None => {
219 self.prev_connected_to = Some(ServingApInfoNode::new(
220 self.node.create_child("prev_connected_to"),
221 serving_ap_info,
222 ));
223 }
224 }
225 }
226
227 match &new_status {
228 ClientSmeStatus::Connected(serving_ap_info) => match self.connected_to.as_mut() {
229 Some(connected_to) => connected_to.update(serving_ap_info),
230 None => {
231 self.connected_to = Some(ServingApInfoNode::new(
232 self.node.create_child("connected_to"),
233 serving_ap_info,
234 ));
235 }
236 },
237 ClientSmeStatus::Connecting(_)
238 | ClientSmeStatus::Roaming(_)
239 | ClientSmeStatus::Idle => {
240 self.connected_to = None;
241 }
242 }
243
244 match &new_status {
245 ClientSmeStatus::Connecting(ssid) => match self.connecting_to.as_mut() {
246 Some(connecting_to) => connecting_to.update(ssid),
247 None => {
248 self.connecting_to =
249 Some(ConnectingToNode::new(self.node.create_child("connecting_to"), ssid));
250 }
251 },
252 ClientSmeStatus::Connected(_) | ClientSmeStatus::Roaming(_) | ClientSmeStatus::Idle => {
253 self.connecting_to = None;
254 }
255 }
256 }
257}
258
259pub struct ServingApInfoNode {
260 node: Node,
261 bssid: StringProperty,
262 ssid: StringProperty,
263
264 rssi_dbm: IntProperty,
265 snr_db: IntProperty,
266 signal_report_time: MonotonicTimeProperty,
267 channel: ChannelNode,
268 protection: StringProperty,
269 is_wmm_assoc: BoolProperty,
270 wmm_param: Option<BssWmmParamNode>,
271 ht_cap: Option<BytesProperty>,
272 vht_cap: Option<BytesProperty>,
273 wsc: Option<BssWscNode>,
274}
275
276impl ServingApInfoNode {
277 fn new(node: Node, ap: &ServingApInfo) -> Self {
278 let mut serving_ap_info_node = Self {
279 bssid: node.create_string("bssid", ap.bssid.to_string()),
280 ssid: node.create_string("ssid", ap.ssid.to_string()),
281 rssi_dbm: node.create_int("rssi_dbm", ap.rssi_dbm as i64),
282 snr_db: node.create_int("snr_db", ap.snr_db as i64),
283 signal_report_time: node.create_time_at("signal_report_time", ap.signal_report_time),
284 channel: ChannelNode::new(node.create_child("channel"), ap.channel.into()),
285 protection: node.create_string("protection", format!("{}", ap.protection)),
286
287 ht_cap: None,
289 vht_cap: None,
290 is_wmm_assoc: node.create_bool("is_wmm_assoc", false),
291 wmm_param: None,
292 wsc: None,
293
294 node,
295 };
296 serving_ap_info_node.update_ht_cap_node(ap);
297 serving_ap_info_node.update_vht_cap_node(ap);
298 serving_ap_info_node.update_wmm_node(ap);
299 serving_ap_info_node.update_wsc_node(ap);
300
301 serving_ap_info_node
302 }
303
304 fn update(&mut self, ap: &ServingApInfo) {
305 self.bssid.set(&ap.bssid.to_string());
306 self.ssid.set(&ap.ssid.to_string());
307 self.rssi_dbm.set(ap.rssi_dbm as i64);
308 self.snr_db.set(ap.snr_db as i64);
309 self.signal_report_time.set_at(ap.signal_report_time);
310 self.channel.update(ap.channel.into());
311 self.protection.set(&format!("{}", ap.protection));
312
313 self.update_ht_cap_node(ap);
314 self.update_vht_cap_node(ap);
315 self.update_wmm_node(ap);
316 self.update_wsc_node(ap);
317 }
318
319 fn update_ht_cap_node(&mut self, ap: &ServingApInfo) {
320 match &ap.ht_cap {
321 Some(ht_cap) => match self.ht_cap.as_mut() {
322 Some(ht_cap_prop) => ht_cap_prop.set(&ht_cap.bytes),
323 None => self.ht_cap = Some(self.node.create_bytes("ht_cap", ht_cap.bytes)),
324 },
325 None => {
326 self.ht_cap = None;
327 }
328 }
329 }
330
331 fn update_vht_cap_node(&mut self, ap: &ServingApInfo) {
332 match &ap.vht_cap {
333 Some(vht_cap) => match self.vht_cap.as_mut() {
334 Some(vht_cap_prop) => vht_cap_prop.set(&vht_cap.bytes),
335 None => self.vht_cap = Some(self.node.create_bytes("vht_cap", vht_cap.bytes)),
336 },
337 None => {
338 self.vht_cap = None;
339 }
340 }
341 }
342
343 fn update_wmm_node(&mut self, ap: &ServingApInfo) {
344 match &ap.wmm_param {
345 Some(wmm_param) => {
346 self.is_wmm_assoc.set(true);
347 match self.wmm_param.as_mut() {
348 Some(wmm_param_node) => wmm_param_node.update(wmm_param),
349 None => {
350 self.wmm_param = Some(BssWmmParamNode::new(
351 self.node.create_child("wmm_param"),
352 wmm_param,
353 ))
354 }
355 }
356 }
357 None => {
358 self.is_wmm_assoc.set(false);
359 self.wmm_param = None;
360 }
361 }
362 }
363
364 fn update_wsc_node(&mut self, ap: &ServingApInfo) {
365 match &ap.probe_resp_wsc {
366 Some(wsc) => match self.wsc.as_mut() {
367 Some(wsc_node) => wsc_node.update(wsc),
368 None => self.wsc = Some(BssWscNode::new(self.node.create_child("wsc"), wsc)),
369 },
370 None => {
371 self.wsc = None;
372 }
373 }
374 }
375}
376
377pub struct ChannelNode {
378 _node: Node,
379 primary: UintProperty,
380 cbw: StringProperty,
381 secondary80: UintProperty,
382}
383
384impl ChannelNode {
385 pub fn new(node: Node, channel: fidl_ieee80211::WlanChannel) -> Self {
386 let primary = node.create_uint("primary", channel.primary as u64);
387 let cbw = node.create_string("cbw", format!("{:?}", channel.cbw));
388 let secondary80 = node.create_uint("secondary80", channel.secondary80 as u64);
389 Self { _node: node, primary, cbw, secondary80 }
390 }
391
392 pub fn update(&mut self, channel: fidl_ieee80211::WlanChannel) {
393 self.primary.set(channel.primary as u64);
394 self.cbw.set(&format!("{:?}", channel.cbw));
395 self.secondary80.set(channel.secondary80 as u64);
396 }
397}
398
399pub struct BssWmmParamNode {
400 _node: Node,
401 wmm_info: BssWmmInfoNode,
402 ac_be: BssWmmAcParamsNode,
403 ac_bk: BssWmmAcParamsNode,
404 ac_vi: BssWmmAcParamsNode,
405 ac_vo: BssWmmAcParamsNode,
406}
407
408impl BssWmmParamNode {
409 fn new(node: Node, wmm_param: &ie::WmmParam) -> Self {
410 let wmm_info =
411 BssWmmInfoNode::new(node.create_child("wmm_info"), wmm_param.wmm_info.ap_wmm_info());
412 let ac_be = BssWmmAcParamsNode::new(node.create_child("ac_be"), wmm_param.ac_be_params);
413 let ac_bk = BssWmmAcParamsNode::new(node.create_child("ac_bk"), wmm_param.ac_bk_params);
414 let ac_vi = BssWmmAcParamsNode::new(node.create_child("ac_vi"), wmm_param.ac_vi_params);
415 let ac_vo = BssWmmAcParamsNode::new(node.create_child("ac_vo"), wmm_param.ac_vo_params);
416 Self { _node: node, wmm_info, ac_be, ac_bk, ac_vi, ac_vo }
417 }
418
419 fn update(&mut self, wmm_param: &ie::WmmParam) {
420 self.wmm_info.update(&wmm_param.wmm_info.ap_wmm_info());
421 self.ac_be.update(&wmm_param.ac_be_params);
422 self.ac_bk.update(&wmm_param.ac_bk_params);
423 self.ac_vi.update(&wmm_param.ac_vi_params);
424 self.ac_vo.update(&wmm_param.ac_vo_params);
425 }
426}
427
428pub struct BssWmmInfoNode {
429 _node: Node,
430 param_set_count: UintProperty,
431 uapsd: BoolProperty,
432}
433
434impl BssWmmInfoNode {
435 fn new(node: Node, info: ie::ApWmmInfo) -> Self {
436 let param_set_count =
437 node.create_uint("param_set_count", info.parameter_set_count() as u64);
438 let uapsd = node.create_bool("uapsd", info.uapsd());
439 Self { _node: node, param_set_count, uapsd }
440 }
441
442 fn update(&mut self, info: &ie::ApWmmInfo) {
443 self.param_set_count.set(info.parameter_set_count() as u64);
444 self.uapsd.set(info.uapsd());
445 }
446}
447
448pub struct BssWmmAcParamsNode {
449 _node: Node,
450 aifsn: UintProperty,
451 acm: BoolProperty,
452 ecw_min: UintProperty,
453 ecw_max: UintProperty,
454 txop_limit: UintProperty,
455}
456
457impl BssWmmAcParamsNode {
458 fn new(node: Node, ac_params: ie::WmmAcParams) -> Self {
459 let aifsn = node.create_uint("aifsn", ac_params.aci_aifsn.aifsn() as u64);
460 let acm = node.create_bool("acm", ac_params.aci_aifsn.acm());
461 let ecw_min = node.create_uint("ecw_min", ac_params.ecw_min_max.ecw_min() as u64);
462 let ecw_max = node.create_uint("ecw_max", ac_params.ecw_min_max.ecw_max() as u64);
463 let txop_limit = node.create_uint("txop_limit", ac_params.txop_limit as u64);
464 Self { _node: node, aifsn, acm, ecw_min, ecw_max, txop_limit }
465 }
466
467 fn update(&self, ac_params: &ie::WmmAcParams) {
468 self.aifsn.set(ac_params.aci_aifsn.aifsn() as u64);
469 self.acm.set(ac_params.aci_aifsn.acm());
470 self.ecw_min.set(ac_params.ecw_min_max.ecw_min() as u64);
471 self.ecw_max.set(ac_params.ecw_min_max.ecw_max() as u64);
472 self.txop_limit.set(ac_params.txop_limit as u64);
473 }
474}
475
476pub struct BssWscNode {
477 _node: Node,
478 manufacturer: StringProperty,
479 model_name: StringProperty,
480 model_number: StringProperty,
481 device_name: StringProperty,
482}
483
484impl BssWscNode {
485 fn new(node: Node, wsc: &wsc::ProbeRespWsc) -> Self {
486 let manufacturer =
487 node.create_string("manufacturer", String::from_utf8_lossy(&wsc.manufacturer[..]));
488 let model_name =
489 node.create_string("model_name", String::from_utf8_lossy(&wsc.model_name[..]));
490 let model_number =
491 node.create_string("model_number", String::from_utf8_lossy(&wsc.model_number[..]));
492 let device_name =
493 node.create_string("device_name", String::from_utf8_lossy(&wsc.device_name[..]));
494
495 Self { _node: node, manufacturer, model_name, model_number, device_name }
496 }
497
498 fn update(&mut self, wsc: &wsc::ProbeRespWsc) {
499 self.manufacturer.set(&String::from_utf8_lossy(&wsc.manufacturer[..]));
500 self.model_name.set(&String::from_utf8_lossy(&wsc.model_name[..]));
501 self.model_number.set(&String::from_utf8_lossy(&wsc.model_number[..]));
502 self.device_name.set(&String::from_utf8_lossy(&wsc.device_name[..]));
503 }
504}
505
506pub struct ConnectingToNode {
507 _node: Node,
508 ssid: StringProperty,
509}
510
511impl ConnectingToNode {
512 fn new(node: Node, ssid: &Ssid) -> Self {
513 let ssid = node.create_string("ssid", ssid.to_string());
514 Self { _node: node, ssid }
515 }
516
517 fn update(&mut self, ssid: &Ssid) {
518 self.ssid.set(&ssid.to_string());
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use crate::client::test_utils;
526 use diagnostics_assertions::{AnyProperty, assert_data_tree};
527 use fuchsia_inspect::Inspector;
528
529 #[fuchsia::test]
530 async fn test_inspect_update_pulse_connect_disconnect() {
531 let inspector = Inspector::default();
532 let root = inspector.root();
533 let mut pulse = PulseNode::new(root.create_child("last_pulse"));
534
535 let status = ClientSmeStatus::Idle;
537 pulse.update(status);
538 assert_data_tree!(inspector, root: {
539 last_pulse: {
540 started: AnyProperty,
541 last_updated: AnyProperty,
542 status: { status_str: "idle" }
543 }
544 });
545
546 let status = ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap());
549 pulse.update(status);
550 assert_data_tree!(inspector, root: {
551 last_pulse: {
552 started: AnyProperty,
553 last_updated: AnyProperty,
554 status: {
555 status_str: "connecting",
556 connecting_to: { ssid: "<ssid-666f6f>" }
557 },
558 }
559 });
560
561 let status = ClientSmeStatus::Connected(test_utils::fake_serving_ap_info());
565 pulse.update(status);
566 assert_data_tree!(inspector, root: {
567 last_pulse: {
568 started: AnyProperty,
569 last_updated: AnyProperty,
570 last_link_up: AnyProperty,
571 status: {
572 status_str: "connected",
573 connected_to: contains {
574 ssid: "<ssid-666f6f>",
575 bssid: "37:0a:16:03:09:46",
576 },
577 },
578 }
579 });
580
581 let status = ClientSmeStatus::Idle;
584 pulse.update(status);
585 assert_data_tree!(inspector, root: {
586 last_pulse: {
587 started: AnyProperty,
588 last_updated: AnyProperty,
589 last_link_up: AnyProperty,
590 status: {
591 status_str: "idle",
592 prev_connected_to: contains {
593 ssid: "<ssid-666f6f>",
594 bssid: "37:0a:16:03:09:46",
595 },
596 },
597 }
598 });
599 }
600
601 #[fuchsia::test]
602 async fn test_inspect_update_pulse_wmm_status_changed() {
603 let inspector = Inspector::default();
604 let root = inspector.root();
605 let mut pulse = PulseNode::new(root.create_child("last_pulse"));
606
607 let mut serving_ap_info = test_utils::fake_serving_ap_info();
608 serving_ap_info.wmm_param = None;
609 let status = ClientSmeStatus::Connected(serving_ap_info.clone());
610 pulse.update(status);
611 assert_data_tree!(inspector, root: {
612 last_pulse: contains {
613 status: contains {
614 connected_to: contains {
615 is_wmm_assoc: false,
616 },
617 },
618 }
619 });
620
621 let mut wmm_param =
622 *ie::parse_wmm_param(&test_utils::fake_wmm_param().bytes[..]).expect("parse wmm");
623 serving_ap_info.wmm_param = Some(wmm_param);
624 let status = ClientSmeStatus::Connected(serving_ap_info.clone());
625 pulse.update(status);
626 assert_data_tree!(inspector, root: {
627 last_pulse: contains {
628 status: contains {
629 connected_to: contains {
630 is_wmm_assoc: true,
631 wmm_param: contains {
632 ac_be: {
633 aifsn: 3u64,
634 acm: false,
635 ecw_min: 4u64,
636 ecw_max: 10u64,
637 txop_limit: 0u64,
638 },
639 ac_bk: {
640 aifsn: 7u64,
641 acm: false,
642 ecw_min: 4u64,
643 ecw_max: 10u64,
644 txop_limit: 0u64,
645 },
646 ac_vi: {
647 aifsn: 2u64,
648 acm: false,
649 ecw_min: 3u64,
650 ecw_max: 4u64,
651 txop_limit: 0x5eu64,
652 },
653 ac_vo: {
654 aifsn: 2u64,
655 acm: false,
656 ecw_min: 2u64,
657 ecw_max: 3u64,
658 txop_limit: 0x2fu64,
659 },
660 wmm_info: contains {
661 uapsd: true,
662 },
663 }
664 },
665 },
666 }
667 });
668
669 let mut wmm_info = wmm_param.wmm_info.ap_wmm_info();
670 wmm_info.set_uapsd(false);
671 wmm_param.wmm_info.0 = wmm_info.0;
672 wmm_param.ac_be_params.aci_aifsn.set_aifsn(9);
673 wmm_param.ac_bk_params.aci_aifsn.set_acm(true);
674 wmm_param.ac_vi_params.ecw_min_max.set_ecw_min(11);
675 wmm_param.ac_vi_params.ecw_min_max.set_ecw_max(14);
676 wmm_param.ac_vo_params.txop_limit = 0xaa;
677 serving_ap_info.wmm_param = Some(wmm_param);
678 let status = ClientSmeStatus::Connected(serving_ap_info.clone());
679 pulse.update(status);
680 assert_data_tree!(inspector, root: {
681 last_pulse: contains {
682 status: contains {
683 connected_to: contains {
684 is_wmm_assoc: true,
685 wmm_param: contains {
686 ac_be: {
687 aifsn: 9u64,
688 acm: false,
689 ecw_min: 4u64,
690 ecw_max: 10u64,
691 txop_limit: 0u64,
692 },
693 ac_bk: {
694 aifsn: 7u64,
695 acm: true,
696 ecw_min: 4u64,
697 ecw_max: 10u64,
698 txop_limit: 0u64,
699 },
700 ac_vi: {
701 aifsn: 2u64,
702 acm: false,
703 ecw_min: 11u64,
704 ecw_max: 14u64,
705 txop_limit: 0x5eu64,
706 },
707 ac_vo: {
708 aifsn: 2u64,
709 acm: false,
710 ecw_min: 2u64,
711 ecw_max: 3u64,
712 txop_limit: 0xaau64,
713 },
714 wmm_info: contains {
715 uapsd: false,
716 },
717 }
718 },
719 },
720 }
721 });
722
723 serving_ap_info.wmm_param = None;
724 let status = ClientSmeStatus::Connected(serving_ap_info.clone());
725 pulse.update(status);
726 assert_data_tree!(inspector, root: {
727 last_pulse: contains {
728 status: contains {
729 connected_to: contains {
730 is_wmm_assoc: false,
731 },
732 },
733 }
734 });
735 }
736}