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