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
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    // Not part of Inspect node. We use it to compare new status against existing status
130    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        // This method is always called when there's a state transition, so even if the client is
153        // no longer connected now, if the client was previously connected, we can conclude
154        // that they were connected until now.
155        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        // Do not update status_node if status is the same.
165        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            // Reuse update_* helper functions to fill this fields.
288            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        // SME is idle. Pulse node should not have any field except "last_updated" and "status"
536        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        // SME is connecting. Check that "connecting_to" field now appears, and that existing
547        // fields are still kept.
548        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        // SME is connected. Aside from verifying that existing fields are kept, key things we
562        // want to check are that "last_link_up" and "connected_to" are populated, and
563        // "connecting_to" is cleared out.
564        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        // SME is idle. The "connected_to" field is cleared out.
582        // The "prev_connected_to" field is logged.
583        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}