wlan_sme/client/
inspect.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
5use 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
21/// These limits are set to capture roughly 5 to 10 recent connection attempts. An average
22/// successful connection attempt would generate about 5 state events and 7 supplicant events (this
23/// number may be different in error cases).
24const STATE_EVENTS_LIMIT: usize = 50;
25const RSN_EVENTS_LIMIT: usize = 50;
26
27/// Limit set to capture roughly join scans for 10 recent connection attempts.
28const JOIN_SCAN_EVENTS_LIMIT: usize = 10;
29
30/// Display idle status str
31const IDLE_STR: &str = "idle";
32
33/// Wrapper struct SME inspection nodes
34pub struct SmeTree {
35    /// Top level inspector that contains the SmeTree root node.
36    pub inspector: Inspector,
37    /// Base SME inspection node that holds all other nodes in the SmeTree.
38    pub _root_node: Node,
39    /// Inspection node to log recent state transitions, or cases where an event would that would
40    /// normally cause a state transition doesn't due to an error.
41    pub state_events: Mutex<BoundedListNode>,
42    /// Inspection node to log EAPOL frames processed by supplicant and its output.
43    pub rsn_events: Mutex<BoundedListNode>,
44    /// Inspection node to log recent join scan results.
45    // We expect to only write this node
46    #[allow(dead_code)]
47    pub join_scan_events: Mutex<BoundedListNode>,
48    /// Inspect node to log periodic pulse check. For the most part, information logged in this
49    /// node can be derived from (and is therefore redundant with) `state_events` node. This
50    /// is logged  for two reasons:
51    /// 1. To show a quick summary of latest status.
52    /// 2. To show how up-to-date the latest status is (although pulse is logged within SME, it can
53    ///    be thought similarly to an external entity periodically checking SME's status).
54    pub last_pulse: Mutex<PulseNode>,
55
56    /// Number of FIDL BSS we discard in scan because we fail to convert them.
57    pub scan_discard_fidl_bss: UintProperty,
58
59    /// Number of time we decide to merge an IE during scan but it fails.
60    /// This should never occur, but we log the count in case the assumption is violated.
61    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.dfs.supported,
95                }
96            }
97        });
98        Self {
99            inspector,
100            _root_node: node,
101            state_events: Mutex::new(state_events),
102            rsn_events: Mutex::new(rsn_events),
103            join_scan_events: Mutex::new(join_scan_events),
104            last_pulse: Mutex::new(pulse),
105            scan_discard_fidl_bss,
106            scan_merge_ie_failures,
107        }
108    }
109
110    pub fn update_pulse(&self, new_status: ClientSmeStatus) {
111        self.last_pulse.lock().update(new_status)
112    }
113
114    pub fn clone_vmo_data(&self) -> Option<fidl::Vmo> {
115        self.inspector.copy_vmo()
116    }
117}
118
119pub struct PulseNode {
120    node: Node,
121    _started: MonotonicTimeProperty,
122    last_updated: MonotonicTimeProperty,
123    last_link_up: Option<MonotonicTimeProperty>,
124    status_node: Option<ClientSmeStatusNode>,
125
126    // Not part of Inspect node. We use it to compare new status against existing status
127    status: Option<ClientSmeStatus>,
128}
129
130impl PulseNode {
131    fn new(node: Node) -> Self {
132        let now = zx::MonotonicInstant::get();
133        let started = node.create_time_at("started", now);
134        let last_updated = node.create_time_at("last_updated", now);
135        Self {
136            node,
137            _started: started,
138            last_updated,
139            last_link_up: None,
140            status_node: None,
141            status: None,
142        }
143    }
144
145    pub fn update(&mut self, new_status: ClientSmeStatus) {
146        let now = zx::MonotonicInstant::get();
147        self.last_updated.set_at(now);
148
149        // This method is always called when there's a state transition, so even if the client is
150        // no longer connected now, if the client was previously connected, we can conclude
151        // that they were connected until now.
152        if new_status.is_connected()
153            || self.status.as_ref().map(|s| s.is_connected()).unwrap_or(false)
154        {
155            match &self.last_link_up {
156                Some(last_link_up) => last_link_up.set_at(now),
157                None => self.last_link_up = Some(self.node.create_time_at("last_link_up", now)),
158            }
159        }
160
161        // Do not update status_node if status is the same.
162        if let Some(status) = &self.status {
163            if *status == new_status {
164                return;
165            }
166        }
167
168        match self.status_node.as_mut() {
169            Some(status_node) => status_node.update(self.status.as_ref(), &new_status),
170            None => {
171                self.status_node =
172                    Some(ClientSmeStatusNode::new(self.node.create_child("status"), &new_status))
173            }
174        }
175        self.status = Some(new_status);
176    }
177}
178
179pub struct ClientSmeStatusNode {
180    node: Node,
181    status_str: StringProperty,
182    prev_connected_to: Option<ServingApInfoNode>,
183    connected_to: Option<ServingApInfoNode>,
184    connecting_to: Option<ConnectingToNode>,
185}
186
187impl ClientSmeStatusNode {
188    fn new(node: Node, status: &ClientSmeStatus) -> Self {
189        let status_str = node.create_string("status_str", IDLE_STR);
190        let mut status_node = Self {
191            node,
192            status_str,
193            prev_connected_to: None,
194            connected_to: None,
195            connecting_to: None,
196        };
197        status_node.update(None, status);
198        status_node
199    }
200
201    pub fn update(&mut self, old_status: Option<&ClientSmeStatus>, new_status: &ClientSmeStatus) {
202        let status_str = match new_status {
203            ClientSmeStatus::Connected(_) => "connected",
204            ClientSmeStatus::Connecting(_) => "connecting",
205            ClientSmeStatus::Roaming(_) => "roaming",
206            ClientSmeStatus::Idle => IDLE_STR,
207        };
208        self.status_str.set(status_str);
209
210        if status_str == IDLE_STR {
211            if let Some(ClientSmeStatus::Connected(serving_ap_info)) = old_status {
212                match self.prev_connected_to.as_mut() {
213                    Some(prev_connected_to) => prev_connected_to.update(serving_ap_info),
214                    None => {
215                        self.prev_connected_to = Some(ServingApInfoNode::new(
216                            self.node.create_child("prev_connected_to"),
217                            serving_ap_info,
218                        ));
219                    }
220                }
221            }
222        }
223
224        match &new_status {
225            ClientSmeStatus::Connected(serving_ap_info) => match self.connected_to.as_mut() {
226                Some(connected_to) => connected_to.update(serving_ap_info),
227                None => {
228                    self.connected_to = Some(ServingApInfoNode::new(
229                        self.node.create_child("connected_to"),
230                        serving_ap_info,
231                    ));
232                }
233            },
234            ClientSmeStatus::Connecting(_)
235            | ClientSmeStatus::Roaming(_)
236            | ClientSmeStatus::Idle => {
237                self.connected_to = None;
238            }
239        }
240
241        match &new_status {
242            ClientSmeStatus::Connecting(ssid) => match self.connecting_to.as_mut() {
243                Some(connecting_to) => connecting_to.update(ssid),
244                None => {
245                    self.connecting_to =
246                        Some(ConnectingToNode::new(self.node.create_child("connecting_to"), ssid));
247                }
248            },
249            ClientSmeStatus::Connected(_) | ClientSmeStatus::Roaming(_) | ClientSmeStatus::Idle => {
250                self.connecting_to = None;
251            }
252        }
253    }
254}
255
256pub struct ServingApInfoNode {
257    node: Node,
258    bssid: StringProperty,
259    ssid: StringProperty,
260
261    rssi_dbm: IntProperty,
262    snr_db: IntProperty,
263    signal_report_time: MonotonicTimeProperty,
264    channel: ChannelNode,
265    protection: StringProperty,
266    is_wmm_assoc: BoolProperty,
267    wmm_param: Option<BssWmmParamNode>,
268    ht_cap: Option<BytesProperty>,
269    vht_cap: Option<BytesProperty>,
270    wsc: Option<BssWscNode>,
271}
272
273impl ServingApInfoNode {
274    fn new(node: Node, ap: &ServingApInfo) -> Self {
275        let mut serving_ap_info_node = Self {
276            bssid: node.create_string("bssid", ap.bssid.to_string()),
277            ssid: node.create_string("ssid", ap.ssid.to_string()),
278            rssi_dbm: node.create_int("rssi_dbm", ap.rssi_dbm as i64),
279            snr_db: node.create_int("snr_db", ap.snr_db as i64),
280            signal_report_time: node.create_time_at("signal_report_time", ap.signal_report_time),
281            channel: ChannelNode::new(node.create_child("channel"), ap.channel.into()),
282            protection: node.create_string("protection", format!("{}", ap.protection)),
283
284            // Reuse update_* helper functions to fill this fields.
285            ht_cap: None,
286            vht_cap: None,
287            is_wmm_assoc: node.create_bool("is_wmm_assoc", false),
288            wmm_param: None,
289            wsc: None,
290
291            node,
292        };
293        serving_ap_info_node.update_ht_cap_node(ap);
294        serving_ap_info_node.update_vht_cap_node(ap);
295        serving_ap_info_node.update_wmm_node(ap);
296        serving_ap_info_node.update_wsc_node(ap);
297
298        serving_ap_info_node
299    }
300
301    fn update(&mut self, ap: &ServingApInfo) {
302        self.bssid.set(&ap.bssid.to_string());
303        self.ssid.set(&ap.ssid.to_string());
304        self.rssi_dbm.set(ap.rssi_dbm as i64);
305        self.snr_db.set(ap.snr_db as i64);
306        self.signal_report_time.set_at(ap.signal_report_time);
307        self.channel.update(ap.channel.into());
308        self.protection.set(&format!("{}", ap.protection));
309
310        self.update_ht_cap_node(ap);
311        self.update_vht_cap_node(ap);
312        self.update_wmm_node(ap);
313        self.update_wsc_node(ap);
314    }
315
316    fn update_ht_cap_node(&mut self, ap: &ServingApInfo) {
317        match &ap.ht_cap {
318            Some(ht_cap) => match self.ht_cap.as_mut() {
319                Some(ht_cap_prop) => ht_cap_prop.set(&ht_cap.bytes),
320                None => self.ht_cap = Some(self.node.create_bytes("ht_cap", ht_cap.bytes)),
321            },
322            None => {
323                self.ht_cap = None;
324            }
325        }
326    }
327
328    fn update_vht_cap_node(&mut self, ap: &ServingApInfo) {
329        match &ap.vht_cap {
330            Some(vht_cap) => match self.vht_cap.as_mut() {
331                Some(vht_cap_prop) => vht_cap_prop.set(&vht_cap.bytes),
332                None => self.vht_cap = Some(self.node.create_bytes("vht_cap", vht_cap.bytes)),
333            },
334            None => {
335                self.vht_cap = None;
336            }
337        }
338    }
339
340    fn update_wmm_node(&mut self, ap: &ServingApInfo) {
341        match &ap.wmm_param {
342            Some(wmm_param) => {
343                self.is_wmm_assoc.set(true);
344                match self.wmm_param.as_mut() {
345                    Some(wmm_param_node) => wmm_param_node.update(wmm_param),
346                    None => {
347                        self.wmm_param = Some(BssWmmParamNode::new(
348                            self.node.create_child("wmm_param"),
349                            wmm_param,
350                        ))
351                    }
352                }
353            }
354            None => {
355                self.is_wmm_assoc.set(false);
356                self.wmm_param = None;
357            }
358        }
359    }
360
361    fn update_wsc_node(&mut self, ap: &ServingApInfo) {
362        match &ap.probe_resp_wsc {
363            Some(wsc) => match self.wsc.as_mut() {
364                Some(wsc_node) => wsc_node.update(wsc),
365                None => self.wsc = Some(BssWscNode::new(self.node.create_child("wsc"), wsc)),
366            },
367            None => {
368                self.wsc = None;
369            }
370        }
371    }
372}
373
374pub struct ChannelNode {
375    _node: Node,
376    primary: UintProperty,
377    cbw: StringProperty,
378    secondary80: UintProperty,
379}
380
381impl ChannelNode {
382    pub fn new(node: Node, channel: fidl_common::WlanChannel) -> Self {
383        let primary = node.create_uint("primary", channel.primary as u64);
384        let cbw = node.create_string("cbw", format!("{:?}", channel.cbw));
385        let secondary80 = node.create_uint("secondary80", channel.secondary80 as u64);
386        Self { _node: node, primary, cbw, secondary80 }
387    }
388
389    pub fn update(&mut self, channel: fidl_common::WlanChannel) {
390        self.primary.set(channel.primary as u64);
391        self.cbw.set(&format!("{:?}", channel.cbw));
392        self.secondary80.set(channel.secondary80 as u64);
393    }
394}
395
396pub struct BssWmmParamNode {
397    _node: Node,
398    wmm_info: BssWmmInfoNode,
399    ac_be: BssWmmAcParamsNode,
400    ac_bk: BssWmmAcParamsNode,
401    ac_vi: BssWmmAcParamsNode,
402    ac_vo: BssWmmAcParamsNode,
403}
404
405impl BssWmmParamNode {
406    fn new(node: Node, wmm_param: &ie::WmmParam) -> Self {
407        let wmm_info =
408            BssWmmInfoNode::new(node.create_child("wmm_info"), wmm_param.wmm_info.ap_wmm_info());
409        let ac_be = BssWmmAcParamsNode::new(node.create_child("ac_be"), wmm_param.ac_be_params);
410        let ac_bk = BssWmmAcParamsNode::new(node.create_child("ac_bk"), wmm_param.ac_bk_params);
411        let ac_vi = BssWmmAcParamsNode::new(node.create_child("ac_vi"), wmm_param.ac_vi_params);
412        let ac_vo = BssWmmAcParamsNode::new(node.create_child("ac_vo"), wmm_param.ac_vo_params);
413        Self { _node: node, wmm_info, ac_be, ac_bk, ac_vi, ac_vo }
414    }
415
416    fn update(&mut self, wmm_param: &ie::WmmParam) {
417        self.wmm_info.update(&wmm_param.wmm_info.ap_wmm_info());
418        self.ac_be.update(&wmm_param.ac_be_params);
419        self.ac_bk.update(&wmm_param.ac_bk_params);
420        self.ac_vi.update(&wmm_param.ac_vi_params);
421        self.ac_vo.update(&wmm_param.ac_vo_params);
422    }
423}
424
425pub struct BssWmmInfoNode {
426    _node: Node,
427    param_set_count: UintProperty,
428    uapsd: BoolProperty,
429}
430
431impl BssWmmInfoNode {
432    fn new(node: Node, info: ie::ApWmmInfo) -> Self {
433        let param_set_count =
434            node.create_uint("param_set_count", info.parameter_set_count() as u64);
435        let uapsd = node.create_bool("uapsd", info.uapsd());
436        Self { _node: node, param_set_count, uapsd }
437    }
438
439    fn update(&mut self, info: &ie::ApWmmInfo) {
440        self.param_set_count.set(info.parameter_set_count() as u64);
441        self.uapsd.set(info.uapsd());
442    }
443}
444
445pub struct BssWmmAcParamsNode {
446    _node: Node,
447    aifsn: UintProperty,
448    acm: BoolProperty,
449    ecw_min: UintProperty,
450    ecw_max: UintProperty,
451    txop_limit: UintProperty,
452}
453
454impl BssWmmAcParamsNode {
455    fn new(node: Node, ac_params: ie::WmmAcParams) -> Self {
456        let aifsn = node.create_uint("aifsn", ac_params.aci_aifsn.aifsn() as u64);
457        let acm = node.create_bool("acm", ac_params.aci_aifsn.acm());
458        let ecw_min = node.create_uint("ecw_min", ac_params.ecw_min_max.ecw_min() as u64);
459        let ecw_max = node.create_uint("ecw_max", ac_params.ecw_min_max.ecw_max() as u64);
460        let txop_limit = node.create_uint("txop_limit", ac_params.txop_limit as u64);
461        Self { _node: node, aifsn, acm, ecw_min, ecw_max, txop_limit }
462    }
463
464    fn update(&self, ac_params: &ie::WmmAcParams) {
465        self.aifsn.set(ac_params.aci_aifsn.aifsn() as u64);
466        self.acm.set(ac_params.aci_aifsn.acm());
467        self.ecw_min.set(ac_params.ecw_min_max.ecw_min() as u64);
468        self.ecw_max.set(ac_params.ecw_min_max.ecw_max() as u64);
469        self.txop_limit.set(ac_params.txop_limit as u64);
470    }
471}
472
473pub struct BssWscNode {
474    _node: Node,
475    manufacturer: StringProperty,
476    model_name: StringProperty,
477    model_number: StringProperty,
478    device_name: StringProperty,
479}
480
481impl BssWscNode {
482    fn new(node: Node, wsc: &wsc::ProbeRespWsc) -> Self {
483        let manufacturer =
484            node.create_string("manufacturer", String::from_utf8_lossy(&wsc.manufacturer[..]));
485        let model_name =
486            node.create_string("model_name", String::from_utf8_lossy(&wsc.model_name[..]));
487        let model_number =
488            node.create_string("model_number", String::from_utf8_lossy(&wsc.model_number[..]));
489        let device_name =
490            node.create_string("device_name", String::from_utf8_lossy(&wsc.device_name[..]));
491
492        Self { _node: node, manufacturer, model_name, model_number, device_name }
493    }
494
495    fn update(&mut self, wsc: &wsc::ProbeRespWsc) {
496        self.manufacturer.set(&String::from_utf8_lossy(&wsc.manufacturer[..]));
497        self.model_name.set(&String::from_utf8_lossy(&wsc.model_name[..]));
498        self.model_number.set(&String::from_utf8_lossy(&wsc.model_number[..]));
499        self.device_name.set(&String::from_utf8_lossy(&wsc.device_name[..]));
500    }
501}
502
503pub struct ConnectingToNode {
504    _node: Node,
505    ssid: StringProperty,
506}
507
508impl ConnectingToNode {
509    fn new(node: Node, ssid: &Ssid) -> Self {
510        let ssid = node.create_string("ssid", ssid.to_string());
511        Self { _node: node, ssid }
512    }
513
514    fn update(&mut self, ssid: &Ssid) {
515        self.ssid.set(&ssid.to_string());
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522    use crate::client::test_utils;
523    use diagnostics_assertions::{assert_data_tree, AnyProperty};
524    use fuchsia_inspect::Inspector;
525
526    #[test]
527    fn test_inspect_update_pulse_connect_disconnect() {
528        let inspector = Inspector::default();
529        let root = inspector.root();
530        let mut pulse = PulseNode::new(root.create_child("last_pulse"));
531
532        // SME is idle. Pulse node should not have any field except "last_updated" and "status"
533        let status = ClientSmeStatus::Idle;
534        pulse.update(status);
535        assert_data_tree!(inspector, root: {
536            last_pulse: {
537                started: AnyProperty,
538                last_updated: AnyProperty,
539                status: { status_str: "idle" }
540            }
541        });
542
543        // SME is connecting. Check that "connecting_to" field now appears, and that existing
544        // fields are still kept.
545        let status = ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap());
546        pulse.update(status);
547        assert_data_tree!(inspector, root: {
548            last_pulse: {
549                started: AnyProperty,
550                last_updated: AnyProperty,
551                status: {
552                    status_str: "connecting",
553                    connecting_to: { ssid: "<ssid-666f6f>" }
554                },
555            }
556        });
557
558        // SME is connected. Aside from verifying that existing fields are kept, key things we
559        // want to check are that "last_link_up" and "connected_to" are populated, and
560        // "connecting_to" is cleared out.
561        let status = ClientSmeStatus::Connected(test_utils::fake_serving_ap_info());
562        pulse.update(status);
563        assert_data_tree!(inspector, root: {
564            last_pulse: {
565                started: AnyProperty,
566                last_updated: AnyProperty,
567                last_link_up: AnyProperty,
568                status: {
569                    status_str: "connected",
570                    connected_to: contains {
571                        ssid: "<ssid-666f6f>",
572                        bssid: "37:0a:16:03:09:46",
573                    },
574                },
575            }
576        });
577
578        // SME is idle. The "connected_to" field is cleared out.
579        // The "prev_connected_to" field is logged.
580        let status = ClientSmeStatus::Idle;
581        pulse.update(status);
582        assert_data_tree!(inspector, root: {
583            last_pulse: {
584                started: AnyProperty,
585                last_updated: AnyProperty,
586                last_link_up: AnyProperty,
587                status: {
588                    status_str: "idle",
589                    prev_connected_to: contains {
590                        ssid: "<ssid-666f6f>",
591                        bssid: "37:0a:16:03:09:46",
592                    },
593                },
594            }
595        });
596    }
597
598    #[test]
599    fn test_inspect_update_pulse_wmm_status_changed() {
600        let inspector = Inspector::default();
601        let root = inspector.root();
602        let mut pulse = PulseNode::new(root.create_child("last_pulse"));
603
604        let mut serving_ap_info = test_utils::fake_serving_ap_info();
605        serving_ap_info.wmm_param = None;
606        let status = ClientSmeStatus::Connected(serving_ap_info.clone());
607        pulse.update(status);
608        assert_data_tree!(inspector, root: {
609            last_pulse: contains {
610                status: contains {
611                    connected_to: contains {
612                        is_wmm_assoc: false,
613                    },
614                },
615            }
616        });
617
618        let mut wmm_param =
619            *ie::parse_wmm_param(&test_utils::fake_wmm_param().bytes[..]).expect("parse wmm");
620        serving_ap_info.wmm_param = Some(wmm_param);
621        let status = ClientSmeStatus::Connected(serving_ap_info.clone());
622        pulse.update(status);
623        assert_data_tree!(inspector, root: {
624            last_pulse: contains {
625                status: contains {
626                    connected_to: contains {
627                        is_wmm_assoc: true,
628                        wmm_param: contains {
629                            ac_be: {
630                                aifsn: 3u64,
631                                acm: false,
632                                ecw_min: 4u64,
633                                ecw_max: 10u64,
634                                txop_limit: 0u64,
635                            },
636                            ac_bk: {
637                                aifsn: 7u64,
638                                acm: false,
639                                ecw_min: 4u64,
640                                ecw_max: 10u64,
641                                txop_limit: 0u64,
642                            },
643                            ac_vi: {
644                                aifsn: 2u64,
645                                acm: false,
646                                ecw_min: 3u64,
647                                ecw_max: 4u64,
648                                txop_limit: 0x5eu64,
649                            },
650                            ac_vo: {
651                                aifsn: 2u64,
652                                acm: false,
653                                ecw_min: 2u64,
654                                ecw_max: 3u64,
655                                txop_limit: 0x2fu64,
656                            },
657                            wmm_info: contains {
658                                uapsd: true,
659                            },
660                        }
661                    },
662                },
663            }
664        });
665
666        let mut wmm_info = wmm_param.wmm_info.ap_wmm_info();
667        wmm_info.set_uapsd(false);
668        wmm_param.wmm_info.0 = wmm_info.0;
669        wmm_param.ac_be_params.aci_aifsn.set_aifsn(9);
670        wmm_param.ac_bk_params.aci_aifsn.set_acm(true);
671        wmm_param.ac_vi_params.ecw_min_max.set_ecw_min(11);
672        wmm_param.ac_vi_params.ecw_min_max.set_ecw_max(14);
673        wmm_param.ac_vo_params.txop_limit = 0xaa;
674        serving_ap_info.wmm_param = Some(wmm_param);
675        let status = ClientSmeStatus::Connected(serving_ap_info.clone());
676        pulse.update(status);
677        assert_data_tree!(inspector, root: {
678            last_pulse: contains {
679                status: contains {
680                    connected_to: contains {
681                        is_wmm_assoc: true,
682                        wmm_param: contains {
683                            ac_be: {
684                                aifsn: 9u64,
685                                acm: false,
686                                ecw_min: 4u64,
687                                ecw_max: 10u64,
688                                txop_limit: 0u64,
689                            },
690                            ac_bk: {
691                                aifsn: 7u64,
692                                acm: true,
693                                ecw_min: 4u64,
694                                ecw_max: 10u64,
695                                txop_limit: 0u64,
696                            },
697                            ac_vi: {
698                                aifsn: 2u64,
699                                acm: false,
700                                ecw_min: 11u64,
701                                ecw_max: 14u64,
702                                txop_limit: 0x5eu64,
703                            },
704                            ac_vo: {
705                                aifsn: 2u64,
706                                acm: false,
707                                ecw_min: 2u64,
708                                ecw_max: 3u64,
709                                txop_limit: 0xaau64,
710                            },
711                            wmm_info: contains {
712                                uapsd: false,
713                            },
714                        }
715                    },
716                },
717            }
718        });
719
720        serving_ap_info.wmm_param = None;
721        let status = ClientSmeStatus::Connected(serving_ap_info.clone());
722        pulse.update(status);
723        assert_data_tree!(inspector, root: {
724            last_pulse: contains {
725                status: contains {
726                    connected_to: contains {
727                        is_wmm_assoc: false,
728                    },
729                },
730            }
731        });
732    }
733}