1use crate::util::cobalt_logger::log_cobalt_batch;
6use derivative::Derivative;
7use fidl_fuchsia_metrics::{MetricEvent, MetricEventPayload};
8use fuchsia_inspect::Node as InspectNode;
9use fuchsia_inspect_auto_persist::{self as auto_persist, AutoPersist};
10use fuchsia_inspect_contrib::id_enum::IdEnum;
11use fuchsia_inspect_contrib::nodes::{BoundedListNode, LruCacheNode};
12use fuchsia_inspect_contrib::{inspect_insert, inspect_log};
13use fuchsia_inspect_derive::Unit;
14use fuchsia_sync::Mutex;
15use std::sync::atomic::{AtomicUsize, Ordering};
16use std::sync::Arc;
17use strum_macros::{Display, EnumIter};
18use windowed_stats::experimental::clock::Timed;
19use windowed_stats::experimental::series::interpolation::{Constant, LastSample};
20use windowed_stats::experimental::series::metadata::{BitSetMap, BitSetNode};
21use windowed_stats::experimental::series::statistic::Union;
22use windowed_stats::experimental::series::{SamplingProfile, TimeMatrix};
23use windowed_stats::experimental::serve::{InspectSender, InspectedTimeMatrix};
24use wlan_common::bss::BssDescription;
25use wlan_common::channel::Channel;
26use {
27 fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_sme as fidl_sme,
28 fuchsia_async as fasync, wlan_legacy_metrics_registry as metrics, zx,
29};
30
31const INSPECT_CONNECT_EVENTS_LIMIT: usize = 10;
32const INSPECT_DISCONNECT_EVENTS_LIMIT: usize = 10;
33const INSPECT_CONNECTED_NETWORKS_ID_LIMIT: usize = 16;
34const INSPECT_DISCONNECT_SOURCES_ID_LIMIT: usize = 32;
35const SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_TIMEOUT: zx::BootDuration =
36 zx::BootDuration::from_minutes(2);
37
38#[derive(Debug, Display, EnumIter)]
39enum ConnectionState {
40 Idle(IdleState),
41 Connected(ConnectedState),
42 Disconnected(DisconnectedState),
43}
44
45impl IdEnum for ConnectionState {
46 type Id = u8;
47 fn to_id(&self) -> Self::Id {
48 match self {
49 Self::Idle(_) => 0,
50 Self::Disconnected(_) => 1,
51 Self::Connected(_) => 2,
52 }
53 }
54}
55
56#[derive(Debug, Default)]
57struct IdleState {}
58
59#[derive(Debug, Default)]
60struct ConnectedState {}
61
62#[derive(Debug, Default)]
63struct DisconnectedState {}
64
65#[derive(Derivative, Unit)]
66#[derivative(PartialEq, Eq, Hash)]
67struct InspectConnectedNetwork {
68 bssid: String,
69 ssid: String,
70 protection: String,
71 ht_cap: Option<Vec<u8>>,
72 vht_cap: Option<Vec<u8>>,
73 #[derivative(PartialEq = "ignore")]
74 #[derivative(Hash = "ignore")]
75 wsc: Option<InspectNetworkWsc>,
76 is_wmm_assoc: bool,
77 wmm_param: Option<Vec<u8>>,
78}
79
80impl From<&BssDescription> for InspectConnectedNetwork {
81 fn from(bss_description: &BssDescription) -> Self {
82 Self {
83 bssid: bss_description.bssid.to_string(),
84 ssid: bss_description.ssid.to_string(),
85 protection: format!("{:?}", bss_description.protection()),
86 ht_cap: bss_description.raw_ht_cap().map(|cap| cap.bytes.into()),
87 vht_cap: bss_description.raw_vht_cap().map(|cap| cap.bytes.into()),
88 wsc: bss_description.probe_resp_wsc().as_ref().map(InspectNetworkWsc::from),
89 is_wmm_assoc: bss_description.find_wmm_param().is_some(),
90 wmm_param: bss_description.find_wmm_param().map(|bytes| bytes.into()),
91 }
92 }
93}
94
95#[derive(PartialEq, Unit, Hash)]
96struct InspectNetworkWsc {
97 device_name: String,
98 manufacturer: String,
99 model_name: String,
100 model_number: String,
101}
102
103impl From<&wlan_common::ie::wsc::ProbeRespWsc> for InspectNetworkWsc {
104 fn from(wsc: &wlan_common::ie::wsc::ProbeRespWsc) -> Self {
105 Self {
106 device_name: String::from_utf8_lossy(&wsc.device_name[..]).to_string(),
107 manufacturer: String::from_utf8_lossy(&wsc.manufacturer[..]).to_string(),
108 model_name: String::from_utf8_lossy(&wsc.model_name[..]).to_string(),
109 model_number: String::from_utf8_lossy(&wsc.model_number[..]).to_string(),
110 }
111 }
112}
113
114#[derive(PartialEq, Eq, Unit, Hash)]
115struct InspectDisconnectSource {
116 source: String,
117 reason: String,
118 mlme_event_name: Option<String>,
119}
120
121impl From<&fidl_sme::DisconnectSource> for InspectDisconnectSource {
122 fn from(disconnect_source: &fidl_sme::DisconnectSource) -> Self {
123 match disconnect_source {
124 fidl_sme::DisconnectSource::User(reason) => Self {
125 source: "user".to_string(),
126 reason: format!("{reason:?}"),
127 mlme_event_name: None,
128 },
129 fidl_sme::DisconnectSource::Ap(cause) => Self {
130 source: "ap".to_string(),
131 reason: format!("{:?}", cause.reason_code),
132 mlme_event_name: Some(format!("{:?}", cause.mlme_event_name)),
133 },
134 fidl_sme::DisconnectSource::Mlme(cause) => Self {
135 source: "mlme".to_string(),
136 reason: format!("{:?}", cause.reason_code),
137 mlme_event_name: Some(format!("{:?}", cause.mlme_event_name)),
138 },
139 }
140 }
141}
142
143#[derive(Clone, Debug, PartialEq)]
144pub struct DisconnectInfo {
145 pub iface_id: u16,
146 pub connected_duration: zx::BootDuration,
147 pub is_sme_reconnecting: bool,
148 pub disconnect_source: fidl_sme::DisconnectSource,
149 pub original_bss_desc: Box<BssDescription>,
150 pub current_rssi_dbm: i8,
151 pub current_snr_db: i8,
152 pub current_channel: Channel,
153}
154
155pub struct ConnectDisconnectLogger {
156 connection_state: Arc<Mutex<ConnectionState>>,
157 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
158 connect_events_node: Mutex<AutoPersist<BoundedListNode>>,
159 disconnect_events_node: Mutex<AutoPersist<BoundedListNode>>,
160 inspect_metadata_node: Mutex<InspectMetadataNode>,
161 time_series_stats: ConnectDisconnectTimeSeries,
162 successive_connect_attempt_failures: AtomicUsize,
163 last_connect_failure_at: Arc<Mutex<Option<fasync::BootInstant>>>,
164 last_disconnect_at: Arc<Mutex<Option<fasync::MonotonicInstant>>>,
165}
166
167impl ConnectDisconnectLogger {
168 pub fn new<S: InspectSender>(
169 cobalt_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
170 inspect_node: &InspectNode,
171 inspect_metadata_node: &InspectNode,
172 inspect_metadata_path: &str,
173 persistence_req_sender: auto_persist::PersistenceReqSender,
174 time_matrix_client: &S,
175 ) -> Self {
176 let connect_events = inspect_node.create_child("connect_events");
177 let disconnect_events = inspect_node.create_child("disconnect_events");
178 let this = Self {
179 cobalt_proxy,
180 connection_state: Arc::new(Mutex::new(ConnectionState::Idle(IdleState {}))),
181 connect_events_node: Mutex::new(AutoPersist::new(
182 BoundedListNode::new(connect_events, INSPECT_CONNECT_EVENTS_LIMIT),
183 "wlan-connect-events",
184 persistence_req_sender.clone(),
185 )),
186 disconnect_events_node: Mutex::new(AutoPersist::new(
187 BoundedListNode::new(disconnect_events, INSPECT_DISCONNECT_EVENTS_LIMIT),
188 "wlan-disconnect-events",
189 persistence_req_sender,
190 )),
191 inspect_metadata_node: Mutex::new(InspectMetadataNode::new(inspect_metadata_node)),
192 time_series_stats: ConnectDisconnectTimeSeries::new(
193 time_matrix_client,
194 inspect_metadata_path,
195 ),
196 successive_connect_attempt_failures: AtomicUsize::new(0),
197 last_connect_failure_at: Arc::new(Mutex::new(None)),
198 last_disconnect_at: Arc::new(Mutex::new(None)),
199 };
200 this.log_connection_state();
201 this
202 }
203
204 fn update_connection_state(&self, state: ConnectionState) {
205 *self.connection_state.lock() = state;
206 self.log_connection_state();
207 }
208
209 fn log_connection_state(&self) {
210 let wlan_connectivity_state_id = self.connection_state.lock().to_id() as u64;
211 self.time_series_stats.log_wlan_connectivity_state(1 << wlan_connectivity_state_id);
212 }
213
214 pub async fn handle_connect_attempt(
215 &self,
216 result: fidl_ieee80211::StatusCode,
217 bss: &BssDescription,
218 ) {
219 let mut flushed_successive_failures = None;
220 let mut downtime_duration = None;
221 if result == fidl_ieee80211::StatusCode::Success {
222 self.update_connection_state(ConnectionState::Connected(ConnectedState {}));
223 flushed_successive_failures =
224 Some(self.successive_connect_attempt_failures.swap(0, Ordering::SeqCst));
225 downtime_duration =
226 self.last_disconnect_at.lock().map(|t| fasync::MonotonicInstant::now() - t);
227 } else {
228 self.update_connection_state(ConnectionState::Idle(IdleState {}));
229 let _prev = self.successive_connect_attempt_failures.fetch_add(1, Ordering::SeqCst);
230 let _prev = self.last_connect_failure_at.lock().replace(fasync::BootInstant::now());
231 }
232
233 self.log_connect_attempt_inspect(result, bss);
234 self.log_connect_attempt_cobalt(result, flushed_successive_failures, downtime_duration)
235 .await;
236 }
237
238 fn log_connect_attempt_inspect(
239 &self,
240 result: fidl_ieee80211::StatusCode,
241 bss: &BssDescription,
242 ) {
243 if result == fidl_ieee80211::StatusCode::Success {
244 let mut inspect_metadata_node = self.inspect_metadata_node.lock();
245 let connected_network = InspectConnectedNetwork::from(bss);
246 let connected_network_id =
247 inspect_metadata_node.connected_networks.insert(connected_network) as u64;
248
249 self.time_series_stats.log_connected_networks(1 << connected_network_id);
250
251 inspect_log!(self.connect_events_node.lock().get_mut(), {
252 network_id: connected_network_id,
253 });
254 }
255 }
256
257 #[allow(clippy::vec_init_then_push, reason = "mass allow for https://fxbug.dev/381896734")]
258 async fn log_connect_attempt_cobalt(
259 &self,
260 result: fidl_ieee80211::StatusCode,
261 flushed_successive_failures: Option<usize>,
262 downtime_duration: Option<zx::MonotonicDuration>,
263 ) {
264 let mut metric_events = vec![];
265 metric_events.push(MetricEvent {
266 metric_id: metrics::CONNECT_ATTEMPT_BREAKDOWN_BY_STATUS_CODE_METRIC_ID,
267 event_codes: vec![result as u32],
268 payload: MetricEventPayload::Count(1),
269 });
270
271 if let Some(failures) = flushed_successive_failures {
272 metric_events.push(MetricEvent {
273 metric_id: metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID,
274 event_codes: vec![],
275 payload: MetricEventPayload::IntegerValue(failures as i64),
276 });
277 }
278
279 if let Some(duration) = downtime_duration {
280 metric_events.push(MetricEvent {
281 metric_id: metrics::DOWNTIME_POST_DISCONNECT_METRIC_ID,
282 event_codes: vec![],
283 payload: MetricEventPayload::IntegerValue(duration.into_millis()),
284 });
285 }
286
287 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "log_connect_attempt_cobalt");
288 }
289
290 pub async fn log_disconnect(&self, info: &DisconnectInfo) {
291 self.update_connection_state(ConnectionState::Disconnected(DisconnectedState {}));
292 let _prev = self.last_disconnect_at.lock().replace(fasync::MonotonicInstant::now());
293 self.log_disconnect_inspect(info);
294 self.log_disconnect_cobalt(info).await;
295 }
296
297 fn log_disconnect_inspect(&self, info: &DisconnectInfo) {
298 let mut inspect_metadata_node = self.inspect_metadata_node.lock();
299 let connected_network = InspectConnectedNetwork::from(&*info.original_bss_desc);
300 let connected_network_id =
301 inspect_metadata_node.connected_networks.insert(connected_network) as u64;
302 let disconnect_source = InspectDisconnectSource::from(&info.disconnect_source);
303 let disconnect_source_id =
304 inspect_metadata_node.disconnect_sources.insert(disconnect_source) as u64;
305 inspect_log!(self.disconnect_events_node.lock().get_mut(), {
306 connected_duration: info.connected_duration.into_nanos(),
307 disconnect_source_id: disconnect_source_id,
308 network_id: connected_network_id,
309 rssi_dbm: info.current_rssi_dbm,
310 snr_db: info.current_snr_db,
311 channel: format!("{}", info.current_channel),
312 });
313
314 self.time_series_stats.log_disconnected_networks(1 << connected_network_id);
315 self.time_series_stats.log_disconnect_sources(1 << disconnect_source_id);
316 }
317
318 async fn log_disconnect_cobalt(&self, info: &DisconnectInfo) {
319 let mut metric_events = vec![];
320 metric_events.push(MetricEvent {
321 metric_id: metrics::TOTAL_DISCONNECT_COUNT_METRIC_ID,
322 event_codes: vec![],
323 payload: MetricEventPayload::Count(1),
324 });
325
326 if info.disconnect_source.should_log_for_mobile_device() {
327 metric_events.push(MetricEvent {
328 metric_id: metrics::DISCONNECT_OCCURRENCE_FOR_MOBILE_DEVICE_METRIC_ID,
329 event_codes: vec![],
330 payload: MetricEventPayload::Count(1),
331 });
332 }
333
334 metric_events.push(MetricEvent {
335 metric_id: metrics::CONNECTED_DURATION_ON_DISCONNECT_METRIC_ID,
336 event_codes: vec![],
337 payload: MetricEventPayload::IntegerValue(info.connected_duration.into_millis()),
338 });
339
340 metric_events.push(MetricEvent {
341 metric_id: metrics::DISCONNECT_BREAKDOWN_BY_REASON_CODE_METRIC_ID,
342 event_codes: vec![
343 u32::from(info.disconnect_source.cobalt_reason_code()),
344 info.disconnect_source.as_cobalt_disconnect_source() as u32,
345 ],
346 payload: MetricEventPayload::Count(1),
347 });
348
349 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "log_disconnect_cobalt");
350 }
351
352 pub async fn handle_periodic_telemetry(&self) {
353 let mut metric_events = vec![];
354 let now = fasync::BootInstant::now();
355 if let Some(failed_at) = *self.last_connect_failure_at.lock() {
356 if now - failed_at >= SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_TIMEOUT {
357 let failures = self.successive_connect_attempt_failures.swap(0, Ordering::SeqCst);
358 if failures > 0 {
359 metric_events.push(MetricEvent {
360 metric_id: metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID,
361 event_codes: vec![],
362 payload: MetricEventPayload::IntegerValue(failures as i64),
363 });
364 }
365 }
366 }
367
368 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_periodic_telemetry");
369 }
370
371 pub async fn handle_suspend_imminent(&self) {
372 let mut metric_events = vec![];
373
374 let flushed_successive_failures =
375 self.successive_connect_attempt_failures.swap(0, Ordering::SeqCst);
376 if flushed_successive_failures > 0 {
377 metric_events.push(MetricEvent {
378 metric_id: metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID,
379 event_codes: vec![],
380 payload: MetricEventPayload::IntegerValue(flushed_successive_failures as i64),
381 });
382 }
383
384 log_cobalt_batch!(self.cobalt_proxy, &metric_events, "handle_suspend_imminent");
385 }
386}
387
388struct InspectMetadataNode {
389 connected_networks: LruCacheNode<InspectConnectedNetwork>,
390 disconnect_sources: LruCacheNode<InspectDisconnectSource>,
391}
392
393impl InspectMetadataNode {
394 const CONNECTED_NETWORKS: &'static str = "connected_networks";
395 const DISCONNECT_SOURCES: &'static str = "disconnect_sources";
396
397 fn new(inspect_node: &InspectNode) -> Self {
398 let connected_networks = inspect_node.create_child(Self::CONNECTED_NETWORKS);
399 let disconnect_sources = inspect_node.create_child(Self::DISCONNECT_SOURCES);
400 Self {
401 connected_networks: LruCacheNode::new(
402 connected_networks,
403 INSPECT_CONNECTED_NETWORKS_ID_LIMIT,
404 ),
405 disconnect_sources: LruCacheNode::new(
406 disconnect_sources,
407 INSPECT_DISCONNECT_SOURCES_ID_LIMIT,
408 ),
409 }
410 }
411}
412
413#[derive(Debug, Clone)]
414struct ConnectDisconnectTimeSeries {
415 wlan_connectivity_states: InspectedTimeMatrix<u64>,
416 connected_networks: InspectedTimeMatrix<u64>,
417 disconnected_networks: InspectedTimeMatrix<u64>,
418 disconnect_sources: InspectedTimeMatrix<u64>,
419}
420
421impl ConnectDisconnectTimeSeries {
422 pub fn new<S: InspectSender>(client: &S, inspect_metadata_path: &str) -> Self {
423 let wlan_connectivity_states = client.inspect_time_matrix_with_metadata(
424 "wlan_connectivity_states",
425 TimeMatrix::<Union<u64>, LastSample>::new(
426 SamplingProfile::highly_granular(),
427 LastSample::or(0),
428 ),
429 BitSetMap::from_ordered(["idle", "disconnected", "connected"]),
430 );
431 let connected_networks = client.inspect_time_matrix_with_metadata(
432 "connected_networks",
433 TimeMatrix::<Union<u64>, Constant>::new(
434 SamplingProfile::granular(),
435 Constant::default(),
436 ),
437 BitSetNode::from_path(format!(
438 "{}/{}",
439 inspect_metadata_path,
440 InspectMetadataNode::CONNECTED_NETWORKS
441 )),
442 );
443 let disconnected_networks = client.inspect_time_matrix_with_metadata(
444 "disconnected_networks",
445 TimeMatrix::<Union<u64>, Constant>::new(
446 SamplingProfile::granular(),
447 Constant::default(),
448 ),
449 BitSetNode::from_path(format!(
451 "{}/{}",
452 inspect_metadata_path,
453 InspectMetadataNode::CONNECTED_NETWORKS
454 )),
455 );
456 let disconnect_sources = client.inspect_time_matrix_with_metadata(
457 "disconnect_sources",
458 TimeMatrix::<Union<u64>, Constant>::new(
459 SamplingProfile::granular(),
460 Constant::default(),
461 ),
462 BitSetNode::from_path(format!(
463 "{}/{}",
464 inspect_metadata_path,
465 InspectMetadataNode::DISCONNECT_SOURCES,
466 )),
467 );
468 Self {
469 wlan_connectivity_states,
470 connected_networks,
471 disconnected_networks,
472 disconnect_sources,
473 }
474 }
475
476 fn log_wlan_connectivity_state(&self, data: u64) {
477 self.wlan_connectivity_states.fold_or_log_error(Timed::now(data));
478 }
479 fn log_connected_networks(&self, data: u64) {
480 self.connected_networks.fold_or_log_error(Timed::now(data));
481 }
482 fn log_disconnected_networks(&self, data: u64) {
483 self.disconnected_networks.fold_or_log_error(Timed::now(data));
484 }
485 fn log_disconnect_sources(&self, data: u64) {
486 self.disconnect_sources.fold_or_log_error(Timed::now(data));
487 }
488}
489
490pub trait DisconnectSourceExt {
491 fn should_log_for_mobile_device(&self) -> bool;
492 fn cobalt_reason_code(&self) -> u16;
493 fn as_cobalt_disconnect_source(
494 &self,
495 ) -> metrics::ConnectivityWlanMetricDimensionDisconnectSource;
496}
497
498impl DisconnectSourceExt for fidl_sme::DisconnectSource {
499 fn should_log_for_mobile_device(&self) -> bool {
500 match self {
501 fidl_sme::DisconnectSource::Ap(_) => true,
502 fidl_sme::DisconnectSource::Mlme(cause)
503 if cause.reason_code != fidl_ieee80211::ReasonCode::MlmeLinkFailed =>
504 {
505 true
506 }
507 _ => false,
508 }
509 }
510
511 fn cobalt_reason_code(&self) -> u16 {
512 let cobalt_disconnect_reason_code = match self {
513 fidl_sme::DisconnectSource::Ap(cause) | fidl_sme::DisconnectSource::Mlme(cause) => {
514 cause.reason_code.into_primitive()
515 }
516 fidl_sme::DisconnectSource::User(reason) => *reason as u16,
517 };
518 const REASON_CODE_MAX: u16 = 1000;
521 std::cmp::min(cobalt_disconnect_reason_code, REASON_CODE_MAX)
522 }
523
524 fn as_cobalt_disconnect_source(
525 &self,
526 ) -> metrics::ConnectivityWlanMetricDimensionDisconnectSource {
527 use metrics::ConnectivityWlanMetricDimensionDisconnectSource as DS;
528 match self {
529 fidl_sme::DisconnectSource::Ap(..) => DS::Ap,
530 fidl_sme::DisconnectSource::User(..) => DS::User,
531 fidl_sme::DisconnectSource::Mlme(..) => DS::Mlme,
532 }
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::testing::*;
540 use diagnostics_assertions::{
541 assert_data_tree, AnyBoolProperty, AnyBytesProperty, AnyNumericProperty, AnyStringProperty,
542 };
543
544 use futures::task::Poll;
545 use ieee80211_testutils::{BSSID_REGEX, SSID_REGEX};
546 use rand::Rng;
547 use std::pin::pin;
548 use test_case::test_case;
549 use windowed_stats::experimental::serve;
550 use windowed_stats::experimental::testing::TimeMatrixCall;
551 use wlan_common::channel::{Cbw, Channel};
552 use wlan_common::{fake_bss_description, random_bss_description};
553
554 #[fuchsia::test]
555 fn log_connect_attempt_then_inspect_data_tree_contains_time_matrix_metadata() {
556 let mut harness = setup_test();
557
558 let (client, _server) = serve::serve_time_matrix_inspection(
559 harness.inspect_node.create_child("wlan_connect_disconnect"),
560 );
561 let logger = ConnectDisconnectLogger::new(
562 harness.cobalt_proxy.clone(),
563 &harness.inspect_node,
564 &harness.inspect_metadata_node,
565 &harness.inspect_metadata_path,
566 harness.persistence_sender.clone(),
567 &client,
568 );
569 let bss = random_bss_description!();
570 let mut log_connect_attempt =
571 pin!(logger.handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss));
572 assert!(
573 harness.run_until_stalled_drain_cobalt_events(&mut log_connect_attempt).is_ready(),
574 "`log_connect_attempt` did not complete",
575 );
576
577 let tree = harness.get_inspect_data_tree();
578 assert_data_tree!(
579 @executor harness.exec,
580 tree,
581 root: contains {
582 test_stats: contains {
583 wlan_connect_disconnect: contains {
584 wlan_connectivity_states: {
585 "type": "bitset",
586 "data": AnyBytesProperty,
587 metadata: {
588 index: {
589 "0": "idle",
590 "1": "disconnected",
591 "2": "connected",
592 },
593 },
594 },
595 connected_networks: {
596 "type": "bitset",
597 "data": AnyBytesProperty,
598 metadata: {
599 "index_node_path": "root/test_stats/metadata/connected_networks",
600 },
601 },
602 disconnected_networks: {
603 "type": "bitset",
604 "data": AnyBytesProperty,
605 metadata: {
606 "index_node_path": "root/test_stats/metadata/connected_networks",
607 },
608 },
609 disconnect_sources: {
610 "type": "bitset",
611 "data": AnyBytesProperty,
612 metadata: {
613 "index_node_path": "root/test_stats/metadata/disconnect_sources",
614 },
615 },
616 },
617 },
618 }
619 );
620 }
621
622 #[fuchsia::test]
623 fn test_log_connect_attempt_inspect() {
624 let mut test_helper = setup_test();
625 let logger = ConnectDisconnectLogger::new(
626 test_helper.cobalt_proxy.clone(),
627 &test_helper.inspect_node,
628 &test_helper.inspect_metadata_node,
629 &test_helper.inspect_metadata_path,
630 test_helper.persistence_sender.clone(),
631 &test_helper.mock_time_matrix_client,
632 );
633
634 let bss_description = random_bss_description!();
636 let mut test_fut =
637 pin!(logger
638 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
639 assert_eq!(
640 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
641 Poll::Ready(())
642 );
643
644 let data = test_helper.get_inspect_data_tree();
646 assert_data_tree!(@executor test_helper.exec, data, root: contains {
647 test_stats: contains {
648 metadata: contains {
649 connected_networks: contains {
650 "0": {
651 "@time": AnyNumericProperty,
652 "data": contains {
653 bssid: &*BSSID_REGEX,
654 ssid: &*SSID_REGEX,
655 }
656 }
657 },
658 },
659 connect_events: {
660 "0": {
661 "@time": AnyNumericProperty,
662 network_id: 0u64,
663 }
664 }
665 }
666 });
667
668 let mut time_matrix_calls = test_helper.mock_time_matrix_client.drain_calls();
669 assert_eq!(
670 &time_matrix_calls.drain::<u64>("wlan_connectivity_states")[..],
671 &[TimeMatrixCall::Fold(Timed::now(1 << 0)), TimeMatrixCall::Fold(Timed::now(1 << 2)),]
672 );
673 assert_eq!(
674 &time_matrix_calls.drain::<u64>("connected_networks")[..],
675 &[TimeMatrixCall::Fold(Timed::now(1 << 0))]
676 );
677 }
678
679 #[fuchsia::test]
680 fn test_log_connect_attempt_cobalt() {
681 let mut test_helper = setup_test();
682 let logger = ConnectDisconnectLogger::new(
683 test_helper.cobalt_proxy.clone(),
684 &test_helper.inspect_node,
685 &test_helper.inspect_metadata_node,
686 &test_helper.inspect_metadata_path,
687 test_helper.persistence_sender.clone(),
688 &test_helper.mock_time_matrix_client,
689 );
690
691 let bss_description = random_bss_description!(Wpa2,
693 channel: Channel::new(157, Cbw::Cbw40),
694 bssid: [0x00, 0xf6, 0x20, 0x03, 0x04, 0x05],
695 );
696
697 let mut test_fut =
699 pin!(logger
700 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
701 assert_eq!(
702 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
703 Poll::Ready(())
704 );
705
706 let breakdowns_by_status_code = test_helper
708 .get_logged_metrics(metrics::CONNECT_ATTEMPT_BREAKDOWN_BY_STATUS_CODE_METRIC_ID);
709 assert_eq!(breakdowns_by_status_code.len(), 1);
710 assert_eq!(
711 breakdowns_by_status_code[0].event_codes,
712 vec![fidl_ieee80211::StatusCode::Success as u32]
713 );
714 assert_eq!(breakdowns_by_status_code[0].payload, MetricEventPayload::Count(1));
715 }
716
717 #[fuchsia::test]
718 fn test_successive_connect_attempt_failures_cobalt_zero_failures() {
719 let mut test_helper = setup_test();
720 let logger = ConnectDisconnectLogger::new(
721 test_helper.cobalt_proxy.clone(),
722 &test_helper.inspect_node,
723 &test_helper.inspect_metadata_node,
724 &test_helper.inspect_metadata_path,
725 test_helper.persistence_sender.clone(),
726 &test_helper.mock_time_matrix_client,
727 );
728
729 let bss_description = random_bss_description!(Wpa2);
730 let mut test_fut =
731 pin!(logger
732 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
733 assert_eq!(
734 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
735 Poll::Ready(())
736 );
737
738 let metrics =
739 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
740 assert_eq!(metrics.len(), 1);
741 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(0));
742 }
743
744 #[test_case(1; "one_failure")]
745 #[test_case(2; "two_failures")]
746 #[fuchsia::test(add_test_attr = false)]
747 fn test_successive_connect_attempt_failures_cobalt_one_failure_then_success(n_failures: usize) {
748 let mut test_helper = setup_test();
749 let logger = ConnectDisconnectLogger::new(
750 test_helper.cobalt_proxy.clone(),
751 &test_helper.inspect_node,
752 &test_helper.inspect_metadata_node,
753 &test_helper.inspect_metadata_path,
754 test_helper.persistence_sender.clone(),
755 &test_helper.mock_time_matrix_client,
756 );
757
758 let bss_description = random_bss_description!(Wpa2);
759 for _i in 0..n_failures {
760 let mut test_fut = pin!(logger.handle_connect_attempt(
761 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
762 &bss_description
763 ));
764 assert_eq!(
765 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
766 Poll::Ready(())
767 );
768 }
769
770 let metrics =
771 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
772 assert!(metrics.is_empty());
773
774 let mut test_fut =
775 pin!(logger
776 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
777 assert_eq!(
778 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
779 Poll::Ready(())
780 );
781
782 let metrics =
783 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
784 assert_eq!(metrics.len(), 1);
785 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(n_failures as i64));
786
787 test_helper.clear_cobalt_events();
789 let mut test_fut =
790 pin!(logger
791 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
792 assert_eq!(
793 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
794 Poll::Ready(())
795 );
796 let metrics =
797 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
798 assert_eq!(metrics.len(), 1);
799 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(0));
800 }
801
802 #[test_case(1; "one_failure")]
803 #[test_case(2; "two_failures")]
804 #[fuchsia::test(add_test_attr = false)]
805 fn test_successive_connect_attempt_failures_cobalt_one_failure_then_timeout(n_failures: usize) {
806 let mut test_helper = setup_test();
807 let logger = ConnectDisconnectLogger::new(
808 test_helper.cobalt_proxy.clone(),
809 &test_helper.inspect_node,
810 &test_helper.inspect_metadata_node,
811 &test_helper.inspect_metadata_path,
812 test_helper.persistence_sender.clone(),
813 &test_helper.mock_time_matrix_client,
814 );
815
816 let bss_description = random_bss_description!(Wpa2);
817 for _i in 0..n_failures {
818 let mut test_fut = pin!(logger.handle_connect_attempt(
819 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
820 &bss_description
821 ));
822 assert_eq!(
823 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
824 Poll::Ready(())
825 );
826 }
827
828 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(60_000_000_000));
829 let mut test_fut = pin!(logger.handle_periodic_telemetry());
830 assert_eq!(
831 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
832 Poll::Ready(())
833 );
834
835 let metrics =
837 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
838 assert!(metrics.is_empty());
839
840 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(120_000_000_000));
841 let mut test_fut = pin!(logger.handle_periodic_telemetry());
842 assert_eq!(
843 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
844 Poll::Ready(())
845 );
846
847 let metrics =
848 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
849 assert_eq!(metrics.len(), 1);
850 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(n_failures as i64));
851
852 test_helper.clear_cobalt_events();
854 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(240_000_000_000));
855 let mut test_fut = pin!(logger.handle_periodic_telemetry());
856 assert_eq!(
857 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
858 Poll::Ready(())
859 );
860 let metrics =
861 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
862 assert!(metrics.is_empty());
863 }
864
865 #[fuchsia::test]
866 fn test_zero_successive_connect_attempt_failures_on_suspend() {
867 let mut test_helper = setup_test();
868 let logger = ConnectDisconnectLogger::new(
869 test_helper.cobalt_proxy.clone(),
870 &test_helper.inspect_node,
871 &test_helper.inspect_metadata_node,
872 &test_helper.inspect_metadata_path,
873 test_helper.persistence_sender.clone(),
874 &test_helper.mock_time_matrix_client,
875 );
876
877 let mut test_fut = pin!(logger.handle_suspend_imminent());
878 assert_eq!(
879 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
880 Poll::Ready(())
881 );
882
883 let metrics =
884 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
885 assert!(metrics.is_empty());
886 }
887
888 #[test_case(1; "one_failure")]
889 #[test_case(2; "two_failures")]
890 #[fuchsia::test(add_test_attr = false)]
891 fn test_one_or_more_successive_connect_attempt_failures_on_suspend(n_failures: usize) {
892 let mut test_helper = setup_test();
893 let logger = ConnectDisconnectLogger::new(
894 test_helper.cobalt_proxy.clone(),
895 &test_helper.inspect_node,
896 &test_helper.inspect_metadata_node,
897 &test_helper.inspect_metadata_path,
898 test_helper.persistence_sender.clone(),
899 &test_helper.mock_time_matrix_client,
900 );
901
902 let bss_description = random_bss_description!(Wpa2);
903 for _i in 0..n_failures {
904 let mut test_fut = pin!(logger.handle_connect_attempt(
905 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
906 &bss_description
907 ));
908 assert_eq!(
909 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
910 Poll::Ready(())
911 );
912 }
913
914 let mut test_fut = pin!(logger.handle_suspend_imminent());
915 assert_eq!(
916 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
917 Poll::Ready(())
918 );
919
920 let metrics =
921 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
922 assert_eq!(metrics.len(), 1);
923 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(n_failures as i64));
924
925 test_helper.clear_cobalt_events();
926 let mut test_fut = pin!(logger.handle_suspend_imminent());
927 assert_eq!(
928 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
929 Poll::Ready(())
930 );
931
932 let metrics =
934 test_helper.get_logged_metrics(metrics::SUCCESSIVE_CONNECT_ATTEMPT_FAILURES_METRIC_ID);
935 assert!(metrics.is_empty());
936 }
937
938 #[fuchsia::test]
939 fn test_log_disconnect_inspect() {
940 let mut test_helper = setup_test();
941 let logger = ConnectDisconnectLogger::new(
942 test_helper.cobalt_proxy.clone(),
943 &test_helper.inspect_node,
944 &test_helper.inspect_metadata_node,
945 &test_helper.inspect_metadata_path,
946 test_helper.persistence_sender.clone(),
947 &test_helper.mock_time_matrix_client,
948 );
949
950 let bss_description = fake_bss_description!(Open);
952 let channel = bss_description.channel;
953 let disconnect_info = DisconnectInfo {
954 iface_id: 32,
955 connected_duration: zx::BootDuration::from_seconds(30),
956 is_sme_reconnecting: false,
957 disconnect_source: fidl_sme::DisconnectSource::Ap(fidl_sme::DisconnectCause {
958 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
959 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
960 }),
961 original_bss_desc: Box::new(bss_description),
962 current_rssi_dbm: -30,
963 current_snr_db: 25,
964 current_channel: channel,
965 };
966 let mut test_fut = pin!(logger.log_disconnect(&disconnect_info));
967 assert_eq!(
968 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
969 Poll::Ready(())
970 );
971
972 let data = test_helper.get_inspect_data_tree();
974 assert_data_tree!(@executor test_helper.exec, data, root: contains {
975 test_stats: contains {
976 metadata: {
977 connected_networks: {
978 "0": {
979 "@time": AnyNumericProperty,
980 "data": {
981 bssid: &*BSSID_REGEX,
982 ssid: &*SSID_REGEX,
983 ht_cap: AnyBytesProperty,
984 vht_cap: AnyBytesProperty,
985 protection: "Open",
986 is_wmm_assoc: AnyBoolProperty,
987 wmm_param: AnyBytesProperty,
988 }
989 }
990 },
991 disconnect_sources: {
992 "0": {
993 "@time": AnyNumericProperty,
994 "data": {
995 source: "ap",
996 reason: "UnspecifiedReason",
997 mlme_event_name: "DeauthenticateIndication",
998 }
999 }
1000 },
1001 },
1002 disconnect_events: {
1003 "0": {
1004 "@time": AnyNumericProperty,
1005 connected_duration: zx::BootDuration::from_seconds(30).into_nanos(),
1006 disconnect_source_id: 0u64,
1007 network_id: 0u64,
1008 rssi_dbm: -30i64,
1009 snr_db: 25i64,
1010 channel: AnyStringProperty,
1011 }
1012 }
1013 }
1014 });
1015
1016 let mut time_matrix_calls = test_helper.mock_time_matrix_client.drain_calls();
1017 assert_eq!(
1018 &time_matrix_calls.drain::<u64>("wlan_connectivity_states")[..],
1019 &[TimeMatrixCall::Fold(Timed::now(1 << 0)), TimeMatrixCall::Fold(Timed::now(1 << 1)),]
1020 );
1021 assert_eq!(
1022 &time_matrix_calls.drain::<u64>("disconnected_networks")[..],
1023 &[TimeMatrixCall::Fold(Timed::now(1 << 0))]
1024 );
1025 assert_eq!(
1026 &time_matrix_calls.drain::<u64>("disconnect_sources")[..],
1027 &[TimeMatrixCall::Fold(Timed::now(1 << 0))]
1028 );
1029 }
1030
1031 #[fuchsia::test]
1032 fn test_log_disconnect_cobalt() {
1033 let mut test_helper = setup_test();
1034 let logger = ConnectDisconnectLogger::new(
1035 test_helper.cobalt_proxy.clone(),
1036 &test_helper.inspect_node,
1037 &test_helper.inspect_metadata_node,
1038 &test_helper.inspect_metadata_path,
1039 test_helper.persistence_sender.clone(),
1040 &test_helper.mock_time_matrix_client,
1041 );
1042
1043 let disconnect_info = DisconnectInfo {
1045 connected_duration: zx::BootDuration::from_millis(300_000),
1046 disconnect_source: fidl_sme::DisconnectSource::Ap(fidl_sme::DisconnectCause {
1047 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
1048 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
1049 }),
1050 ..fake_disconnect_info()
1051 };
1052 let mut test_fut = pin!(logger.log_disconnect(&disconnect_info));
1053 assert_eq!(
1054 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
1055 Poll::Ready(())
1056 );
1057
1058 let disconnect_count_metrics =
1059 test_helper.get_logged_metrics(metrics::TOTAL_DISCONNECT_COUNT_METRIC_ID);
1060 assert_eq!(disconnect_count_metrics.len(), 1);
1061 assert_eq!(disconnect_count_metrics[0].payload, MetricEventPayload::Count(1));
1062
1063 let connected_duration_metrics =
1064 test_helper.get_logged_metrics(metrics::CONNECTED_DURATION_ON_DISCONNECT_METRIC_ID);
1065 assert_eq!(connected_duration_metrics.len(), 1);
1066 assert_eq!(
1067 connected_duration_metrics[0].payload,
1068 MetricEventPayload::IntegerValue(300_000)
1069 );
1070
1071 let disconnect_by_reason_metrics =
1072 test_helper.get_logged_metrics(metrics::DISCONNECT_BREAKDOWN_BY_REASON_CODE_METRIC_ID);
1073 assert_eq!(disconnect_by_reason_metrics.len(), 1);
1074 assert_eq!(disconnect_by_reason_metrics[0].payload, MetricEventPayload::Count(1));
1075 assert_eq!(disconnect_by_reason_metrics[0].event_codes.len(), 2);
1076 assert_eq!(
1077 disconnect_by_reason_metrics[0].event_codes[0],
1078 fidl_ieee80211::ReasonCode::ApInitiated.into_primitive() as u32
1079 );
1080 assert_eq!(
1081 disconnect_by_reason_metrics[0].event_codes[1],
1082 metrics::ConnectivityWlanMetricDimensionDisconnectSource::Ap as u32
1083 );
1084 }
1085
1086 #[test_case(
1087 fidl_sme::DisconnectSource::Ap(fidl_sme::DisconnectCause {
1088 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
1089 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1090 }),
1091 true;
1092 "ap_disconnect_source"
1093 )]
1094 #[test_case(
1095 fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1096 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
1097 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1098 }),
1099 true;
1100 "mlme_disconnect_source_not_link_failed"
1101 )]
1102 #[test_case(
1103 fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1104 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
1105 reason_code: fidl_ieee80211::ReasonCode::MlmeLinkFailed,
1106 }),
1107 false;
1108 "mlme_link_failed"
1109 )]
1110 #[test_case(
1111 fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown),
1112 false;
1113 "user_disconnect_source"
1114 )]
1115 #[fuchsia::test(add_test_attr = false)]
1116 fn test_log_disconnect_for_mobile_device_cobalt(
1117 disconnect_source: fidl_sme::DisconnectSource,
1118 should_log: bool,
1119 ) {
1120 let mut test_helper = setup_test();
1121 let logger = ConnectDisconnectLogger::new(
1122 test_helper.cobalt_proxy.clone(),
1123 &test_helper.inspect_node,
1124 &test_helper.inspect_metadata_node,
1125 &test_helper.inspect_metadata_path,
1126 test_helper.persistence_sender.clone(),
1127 &test_helper.mock_time_matrix_client,
1128 );
1129
1130 let disconnect_info = DisconnectInfo { disconnect_source, ..fake_disconnect_info() };
1132 let mut test_fut = pin!(logger.log_disconnect(&disconnect_info));
1133 assert_eq!(
1134 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
1135 Poll::Ready(())
1136 );
1137
1138 let metrics = test_helper
1139 .get_logged_metrics(metrics::DISCONNECT_OCCURRENCE_FOR_MOBILE_DEVICE_METRIC_ID);
1140 if should_log {
1141 assert_eq!(metrics.len(), 1);
1142 assert_eq!(metrics[0].payload, MetricEventPayload::Count(1));
1143 } else {
1144 assert!(metrics.is_empty());
1145 }
1146 }
1147
1148 #[fuchsia::test]
1149 fn test_log_downtime_post_disconnect_on_reconnect() {
1150 let mut test_helper = setup_test();
1151 let logger = ConnectDisconnectLogger::new(
1152 test_helper.cobalt_proxy.clone(),
1153 &test_helper.inspect_node,
1154 &test_helper.inspect_metadata_node,
1155 &test_helper.inspect_metadata_path,
1156 test_helper.persistence_sender.clone(),
1157 &test_helper.mock_time_matrix_client,
1158 );
1159
1160 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(15_000_000_000));
1162 let bss_description = random_bss_description!(Wpa2);
1163 let mut test_fut =
1164 pin!(logger
1165 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
1166 assert_eq!(
1167 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
1168 Poll::Ready(())
1169 );
1170
1171 let metrics = test_helper.get_logged_metrics(metrics::DOWNTIME_POST_DISCONNECT_METRIC_ID);
1173 assert!(metrics.is_empty());
1174
1175 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(25_000_000_000));
1177 let disconnect_info = fake_disconnect_info();
1178 let mut test_fut = pin!(logger.log_disconnect(&disconnect_info));
1179 assert_eq!(
1180 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
1181 Poll::Ready(())
1182 );
1183
1184 test_helper.exec.set_fake_time(fasync::MonotonicInstant::from_nanos(60_000_000_000));
1186 let mut test_fut =
1187 pin!(logger
1188 .handle_connect_attempt(fidl_ieee80211::StatusCode::Success, &bss_description));
1189 assert_eq!(
1190 test_helper.run_until_stalled_drain_cobalt_events(&mut test_fut),
1191 Poll::Ready(())
1192 );
1193
1194 let metrics = test_helper.get_logged_metrics(metrics::DOWNTIME_POST_DISCONNECT_METRIC_ID);
1196 assert_eq!(metrics.len(), 1);
1197 assert_eq!(metrics[0].payload, MetricEventPayload::IntegerValue(35_000));
1198 }
1199
1200 fn fake_disconnect_info() -> DisconnectInfo {
1201 let bss_description = random_bss_description!(Wpa2);
1202 let channel = bss_description.channel;
1203 DisconnectInfo {
1204 iface_id: 1,
1205 connected_duration: zx::BootDuration::from_hours(6),
1206 is_sme_reconnecting: false,
1207 disconnect_source: fidl_sme::DisconnectSource::User(
1208 fidl_sme::UserDisconnectReason::Unknown,
1209 ),
1210 original_bss_desc: bss_description.into(),
1211 current_rssi_dbm: -30,
1212 current_snr_db: 25,
1213 current_channel: channel,
1214 }
1215 }
1216}