wlancfg_lib/mode_management/
phy_manager.rs

1// Copyright 2020 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::types as client_types;
6use crate::mode_management::recovery::{
7    self, IfaceRecoveryOperation, PhyRecoveryOperation, RecoveryAction,
8};
9use crate::mode_management::{Defect, EventHistory, IfaceFailure, PhyFailure};
10use crate::telemetry::{TelemetryEvent, TelemetrySender};
11use anyhow::{Error, format_err};
12use async_trait::async_trait;
13use fidl::endpoints::create_proxy;
14use fuchsia_inspect::{self as inspect, NumericProperty};
15use ieee80211::{MacAddr, MacAddrBytes, NULL_ADDR};
16use log::{error, info, warn};
17use std::collections::{HashMap, HashSet};
18use std::iter::Iterator;
19use thiserror::Error;
20use {
21    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_device_service as fidl_service,
22    fidl_fuchsia_wlan_sme as fidl_sme,
23};
24
25// Number of seconds that recoverable event histories should be stored.  Store past events for 24
26// hours (86400s).
27const DEFECT_RETENTION_SECONDS: u32 = 86400;
28
29/// Errors raised while attempting to query information about or configure PHYs and ifaces.
30#[derive(Clone, Debug, Error, PartialEq, Eq)]
31pub enum PhyManagerError {
32    #[error("the requested operation is not supported")]
33    Unsupported,
34    #[error("unable to query phy information")]
35    PhyQueryFailure,
36    #[error("failed to set country for new PHY")]
37    PhySetCountryFailure,
38    #[error("unable to reset PHY")]
39    PhyResetFailure,
40    #[error("unable to query iface information")]
41    IfaceQueryFailure,
42    #[error("unable to create iface")]
43    IfaceCreateFailure,
44    #[error("unable to destroy iface")]
45    IfaceDestroyFailure,
46    #[error("internal state has become inconsistent")]
47    InternalError,
48}
49
50/// There are a variety of reasons why the calling code may want to create client interfaces.  The
51/// main logic to do so is identical, but there are different intents for making the call.  This
52/// enum allows callers to express their intent when making the call to ensure that internal
53/// PhyManager state remains consistent with the current desired mode of operation.
54#[derive(PartialEq)]
55pub enum CreateClientIfacesReason {
56    StartClientConnections,
57    RecoverClientIfaces,
58}
59
60/// Stores information about a WLAN PHY and any interfaces that belong to it.
61pub(crate) struct PhyContainer {
62    supported_mac_roles: HashSet<fidl_common::WlanMacRole>,
63    client_ifaces: HashSet<u16>,
64    ap_ifaces: HashSet<u16>,
65    // It is possible for interface destruction and defect reporting to race.  Keeping a set of
66    // past interface IDs ensures that defects can be associated with the appropriate PHY.
67    destroyed_ifaces: HashSet<u16>,
68    defects: EventHistory<Defect>,
69    recoveries: EventHistory<recovery::RecoveryAction>,
70}
71
72#[async_trait(?Send)]
73pub trait PhyManagerApi {
74    /// Checks to see if this PHY is already accounted for.  If it is not, queries its PHY
75    /// attributes and places it in the hash map.
76    async fn add_phy(&mut self, phy_id: u16) -> Result<(), PhyManagerError>;
77
78    /// If the PHY is accounted for, removes the associated `PhyContainer` from the hash map.
79    fn remove_phy(&mut self, phy_id: u16);
80
81    /// Queries the interface properties to get the PHY ID.  If the `PhyContainer`
82    /// representing the interface's parent PHY is already present and its
83    /// interface information is obsolete, updates it.  The PhyManager will track ifaces
84    /// as it creates and deletes them, but it is possible that another caller circumvents the
85    /// policy layer and creates an interface.  If no `PhyContainer` exists
86    /// for the new interface, creates one and adds the newly discovered interface
87    /// ID to it.
88    async fn on_iface_added(&mut self, iface_id: u16) -> Result<(), PhyManagerError>;
89
90    /// Ensures that the `iface_id` is not present in any of the `PhyContainer` interface lists.
91    fn on_iface_removed(&mut self, iface_id: u16);
92
93    /// Creates client interfaces for all PHYs that are capable of acting as clients.  For newly
94    /// discovered PHYs, create client interfaces if the PHY can support them.  This method returns
95    /// a containing all newly-created client interface IDs along with a representation of
96    /// any errors encountered along the way.
97    async fn create_all_client_ifaces(
98        &mut self,
99        reason: CreateClientIfacesReason,
100    ) -> HashMap<u16, Result<Vec<u16>, PhyManagerError>>;
101
102    /// The PhyManager is the authoritative source of whether or not the policy layer is allowed to
103    /// create client interfaces.  This method allows other parts of the policy layer to determine
104    /// whether the API client has allowed client interfaces to be created.
105    fn client_connections_enabled(&self) -> bool;
106
107    /// Destroys all client interfaces.  Do not allow the creation of client interfaces for newly
108    /// discovered PHYs.
109    async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError>;
110
111    /// Finds a PHY with a client interface and returns the interface's ID to the caller.
112    fn get_client(&mut self) -> Option<u16>;
113
114    /// Finds a PHY that is capable of functioning as an AP.  PHYs that do not yet have AP ifaces
115    /// associated with them are searched first.  If one is found, an AP iface is created and its
116    /// ID is returned.  If all AP-capable PHYs already have AP ifaces associated with them, one of
117    /// the existing AP iface IDs is returned.  If there are no AP-capable PHYs, None is returned.
118    async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError>;
119
120    /// Destroys the interface associated with the given interface ID.
121    async fn destroy_ap_iface(&mut self, iface_id: u16) -> Result<(), PhyManagerError>;
122
123    /// Destroys all AP interfaces.
124    async fn destroy_all_ap_ifaces(&mut self) -> Result<(), PhyManagerError>;
125
126    /// Sets a suggested MAC address to be used by new AP interfaces.
127    fn suggest_ap_mac(&mut self, mac: MacAddr);
128
129    /// Returns the IDs for all currently known PHYs.
130    fn get_phy_ids(&self) -> Vec<u16>;
131
132    /// Logs phy add failure inspect metrics.
133    fn log_phy_add_failure(&mut self);
134
135    /// Sets the country code on all known PHYs and stores the country code to be applied to
136    /// newly-discovered PHYs.
137    async fn set_country_code(
138        &mut self,
139        country_code: Option<client_types::CountryCode>,
140    ) -> Result<(), PhyManagerError>;
141
142    /// Store a record for the provided defect.
143    fn record_defect(&mut self, defect: Defect);
144
145    /// Take the recovery action proposed by the recovery summary.
146    async fn perform_recovery(&mut self, summary: recovery::RecoverySummary);
147}
148
149/// Maintains a record of all PHYs that are present and their associated interfaces.
150pub struct PhyManager {
151    phys: HashMap<u16, PhyContainer>,
152    recovery_profile: recovery::RecoveryProfile,
153    recovery_enabled: bool,
154    device_monitor: fidl_service::DeviceMonitorProxy,
155    client_connections_enabled: bool,
156    suggested_ap_mac: Option<MacAddr>,
157    saved_country_code: Option<client_types::CountryCode>,
158    _node: inspect::Node,
159    telemetry_sender: TelemetrySender,
160    recovery_action_sender: recovery::RecoveryActionSender,
161    phy_add_fail_count: inspect::UintProperty,
162}
163
164impl PhyContainer {
165    /// Stores the PhyInfo associated with a newly discovered PHY and creates empty vectors to hold
166    /// interface IDs that belong to this PHY.
167    pub fn new(supported_mac_roles: Vec<fidl_common::WlanMacRole>) -> Self {
168        PhyContainer {
169            supported_mac_roles: supported_mac_roles.into_iter().collect(),
170            client_ifaces: HashSet::new(),
171            ap_ifaces: HashSet::new(),
172            destroyed_ifaces: HashSet::new(),
173            defects: EventHistory::<Defect>::new(DEFECT_RETENTION_SECONDS),
174            recoveries: EventHistory::<RecoveryAction>::new(DEFECT_RETENTION_SECONDS),
175        }
176    }
177}
178
179// TODO(https://fxbug.dev/42126575): PhyManager makes the assumption that WLAN PHYs that support client and AP modes can
180// can operate as clients and APs simultaneously.  For PHYs where this is not the case, the
181// existing interface should be destroyed before the new interface is created.
182impl PhyManager {
183    /// Internally stores a DeviceMonitorProxy to query PHY and interface properties and create and
184    /// destroy interfaces as requested.
185    pub fn new(
186        device_monitor: fidl_service::DeviceMonitorProxy,
187        recovery_profile: recovery::RecoveryProfile,
188        recovery_enabled: bool,
189        node: inspect::Node,
190        telemetry_sender: TelemetrySender,
191        recovery_action_sender: recovery::RecoveryActionSender,
192    ) -> Self {
193        let phy_add_fail_count = node.create_uint("phy_add_fail_count", 0);
194        PhyManager {
195            phys: HashMap::new(),
196            recovery_profile,
197            recovery_enabled,
198            device_monitor,
199            client_connections_enabled: false,
200            suggested_ap_mac: None,
201            saved_country_code: None,
202            _node: node,
203            telemetry_sender,
204            recovery_action_sender,
205            phy_add_fail_count,
206        }
207    }
208    /// Verifies that a given PHY ID is accounted for and, if not, adds a new entry for it.
209    async fn ensure_phy(&mut self, phy_id: u16) -> Result<&mut PhyContainer, PhyManagerError> {
210        if !self.phys.contains_key(&phy_id) {
211            self.add_phy(phy_id).await?;
212        }
213
214        // The phy_id is guaranteed to exist at this point because it was either previously
215        // accounted for or was just added above.
216        Ok(self.phys.get_mut(&phy_id).ok_or_else(|| {
217            error!("Phy ID did not exist in self.phys");
218            PhyManagerError::InternalError
219        }))?
220    }
221
222    /// Queries the information associated with the given iface ID.
223    async fn query_iface(
224        &self,
225        iface_id: u16,
226    ) -> Result<Option<fidl_service::QueryIfaceResponse>, PhyManagerError> {
227        match self.device_monitor.query_iface(iface_id).await {
228            Ok(Ok(response)) => Ok(Some(response)),
229            Ok(Err(zx::sys::ZX_ERR_NOT_FOUND)) => Ok(None),
230            _ => Err(PhyManagerError::IfaceQueryFailure),
231        }
232    }
233
234    /// Returns a list of PHY IDs that can have interfaces of the requested MAC role.
235    fn phys_for_role(&self, role: fidl_common::WlanMacRole) -> Vec<u16> {
236        self.phys
237            .iter()
238            .filter_map(|(k, v)| {
239                if v.supported_mac_roles.contains(&role) {
240                    return Some(*k);
241                }
242                None
243            })
244            .collect()
245    }
246
247    /// Log the provided recovery summary.
248    fn log_recovery_action(&mut self, summary: recovery::RecoverySummary) {
249        let affected_phy_id = match summary.action {
250            RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id }) => {
251                if let Some(container) = self.phys.get_mut(&phy_id) {
252                    container.recoveries.add_event(summary.action);
253                    Some(phy_id)
254                } else {
255                    None
256                }
257            }
258            RecoveryAction::PhyRecovery(PhyRecoveryOperation::DestroyIface { iface_id })
259            | RecoveryAction::IfaceRecovery(IfaceRecoveryOperation::Disconnect { iface_id })
260            | RecoveryAction::IfaceRecovery(IfaceRecoveryOperation::StopAp { iface_id }) => {
261                let mut affected_phy_id = None;
262                for (phy_id, phy_info) in self.phys.iter_mut() {
263                    if phy_info.ap_ifaces.contains(&iface_id)
264                        || phy_info.client_ifaces.contains(&iface_id)
265                    {
266                        phy_info.recoveries.add_event(summary.action);
267                        affected_phy_id = Some(*phy_id);
268                    }
269                }
270
271                affected_phy_id
272            }
273        };
274
275        if let Some(phy_id) = affected_phy_id {
276            warn!("Recovery has been recommended for PHY {}: {:?}", phy_id, summary.action);
277        }
278
279        if let Some(recovery_summary) = summary.as_recovery_reason() {
280            self.telemetry_sender.send(TelemetryEvent::RecoveryEvent { reason: recovery_summary });
281        }
282    }
283
284    /// Creates an interface of the requested role for the requested PHY ID.  Returns either the
285    /// ID of the created interface or an error.
286    async fn create_iface(
287        &mut self,
288        phy_id: u16,
289        role: fidl_common::WlanMacRole,
290        sta_addr: MacAddr,
291    ) -> Result<u16, PhyManagerError> {
292        let request = fidl_service::DeviceMonitorCreateIfaceRequest {
293            phy_id: Some(phy_id),
294            role: Some(role),
295            sta_address: Some(sta_addr.to_array()),
296            ..Default::default()
297        };
298
299        let response = self.device_monitor.create_iface(&request).await;
300        let result = match response {
301            Err(e) => {
302                warn!("Failed to create iface: phy {}, FIDL error {:?}", phy_id, e);
303                Err(PhyManagerError::IfaceCreateFailure)
304            }
305            Ok(Err(e)) => {
306                warn!("Failed to create iface: phy {}, error {:?}", phy_id, e);
307                Err(PhyManagerError::IfaceCreateFailure)
308            }
309            Ok(Ok(fidl_service::DeviceMonitorCreateIfaceResponse { iface_id: None, .. })) => {
310                warn!("Failed to create iface. No iface ID received: phy {}", phy_id);
311                Err(PhyManagerError::IfaceCreateFailure)
312            }
313            Ok(Ok(fidl_service::DeviceMonitorCreateIfaceResponse {
314                iface_id: Some(iface_id),
315                ..
316            })) => Ok(iface_id),
317        };
318
319        if result.is_err() {
320            self.record_defect(Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id }));
321            return result;
322        }
323        self.telemetry_sender.send(TelemetryEvent::IfaceCreationResult(Ok(())));
324        result
325    }
326}
327
328#[async_trait(?Send)]
329impl PhyManagerApi for PhyManager {
330    async fn add_phy(&mut self, phy_id: u16) -> Result<(), PhyManagerError> {
331        let supported_mac_roles = self
332            .device_monitor
333            .get_supported_mac_roles(phy_id)
334            .await
335            .map_err(|e| {
336                warn!("Failed to communicate with monitor service: {:?}", e);
337                PhyManagerError::PhyQueryFailure
338            })?
339            .map_err(|e| {
340                warn!("Unable to get supported MAC roles: {:?}", e);
341                PhyManagerError::PhyQueryFailure
342            })?;
343
344        // Create a new container to store the PHY's information.
345        info!("adding PHY ID #{}", phy_id);
346        let mut phy_container = PhyContainer::new(supported_mac_roles);
347
348        // Attempt to set the country for the newly-discovered PHY.
349        let set_country_result = match self.saved_country_code {
350            Some(country_code) => {
351                set_phy_country_code(&self.device_monitor, phy_id, country_code).await
352            }
353            None => Ok(()),
354        };
355
356        // If setting the country code fails, clear the PHY's country code so that it is in WW
357        // and can continue to operate.  If this process fails, return early and do not use this
358        // PHY.
359        if set_country_result.is_err() {
360            clear_phy_country_code(&self.device_monitor, phy_id).await?;
361        }
362
363        if self.client_connections_enabled
364            && phy_container.supported_mac_roles.contains(&fidl_common::WlanMacRole::Client)
365        {
366            let iface_id =
367                self.create_iface(phy_id, fidl_common::WlanMacRole::Client, NULL_ADDR).await?;
368            let _ = phy_container.client_ifaces.insert(iface_id);
369        }
370
371        if self.phys.insert(phy_id, phy_container).is_some() {
372            warn!("Unexpectedly replaced existing phy information for id {}", phy_id);
373        };
374
375        Ok(())
376    }
377
378    fn remove_phy(&mut self, phy_id: u16) {
379        if self.phys.remove(&phy_id).is_none() {
380            warn!("Attempted to remove non-existed phy {}", phy_id);
381        };
382    }
383
384    async fn on_iface_added(&mut self, iface_id: u16) -> Result<(), PhyManagerError> {
385        if let Some(query_iface_response) = self.query_iface(iface_id).await? {
386            let iface_id = query_iface_response.id;
387            let phy = self.ensure_phy(query_iface_response.phy_id).await?;
388
389            match query_iface_response.role {
390                fidl_common::WlanMacRole::Client => {
391                    if phy.client_ifaces.insert(iface_id) {
392                        // The iface wasn't in the hashset, so it was created by someone else
393                        warn!(
394                            "Detected an unexpected client iface id {} created outside of PhyManager",
395                            iface_id
396                        );
397                    }
398                }
399                fidl_common::WlanMacRole::Ap => {
400                    if phy.ap_ifaces.insert(iface_id) {
401                        // `.insert()` returns true if the value was not already present
402                        warn!("Detected an unexpected AP iface created outside of PhyManager");
403                    }
404                }
405                fidl_common::WlanMacRole::Mesh => {
406                    return Err(PhyManagerError::Unsupported);
407                }
408                fidl_common::WlanMacRoleUnknown!() => {
409                    error!("Unknown WlanMacRole type {:?}", query_iface_response.role);
410                    return Err(PhyManagerError::Unsupported);
411                }
412            }
413        }
414        Ok(())
415    }
416
417    fn on_iface_removed(&mut self, iface_id: u16) {
418        for (_, phy_info) in self.phys.iter_mut() {
419            // The presence or absence of the interface in the PhyManager internal records is
420            // irrelevant.  Simply remove any reference to the removed interface ID to ensure that
421            // it is not used for future operations.
422            let _ = phy_info.client_ifaces.remove(&iface_id);
423            let _ = phy_info.ap_ifaces.remove(&iface_id);
424        }
425    }
426
427    async fn create_all_client_ifaces(
428        &mut self,
429        reason: CreateClientIfacesReason,
430    ) -> HashMap<u16, Result<Vec<u16>, PhyManagerError>> {
431        if reason == CreateClientIfacesReason::StartClientConnections {
432            self.client_connections_enabled = true;
433        }
434
435        let mut available_iface_ids = HashMap::new();
436        if self.client_connections_enabled {
437            let client_capable_phy_ids = self.phys_for_role(fidl_common::WlanMacRole::Client);
438
439            for phy_id in client_capable_phy_ids.iter().copied() {
440                let phy_container = match self.phys.get_mut(&phy_id) {
441                    Some(phy_container) => phy_container,
442                    None => {
443                        let _ = available_iface_ids
444                            .insert(phy_id, Err(PhyManagerError::PhyQueryFailure));
445                        continue;
446                    }
447                };
448
449                // If a PHY should be able to have a client interface and it does not, create a new
450                // client interface for the PHY.
451                if phy_container.client_ifaces.is_empty() {
452                    let iface_id = match self
453                        .create_iface(phy_id, fidl_common::WlanMacRole::Client, NULL_ADDR)
454                        .await
455                    {
456                        Ok(iface_id) => iface_id,
457                        Err(e) => {
458                            warn!("Failed to recover iface for PHY {}: {:?}", phy_id, e);
459                            let _ = available_iface_ids.insert(phy_id, Err(e));
460                            continue;
461                        }
462                    };
463
464                    // Safe to unwrap here: this phy_id was just used create an interface. If we
465                    // can't find it now, it's reasonable to panic
466                    #[expect(clippy::unwrap_used)]
467                    let phy_container = self.phys.get_mut(&phy_id).unwrap();
468                    let _ = phy_container.client_ifaces.insert(iface_id);
469
470                    // There is only one client iface because this branch only runs when
471                    // phy_container.client_ifaces is initially empty.
472                    let _ = available_iface_ids.insert(phy_id, Ok(vec![iface_id]));
473                } else {
474                    let _ = available_iface_ids
475                        .insert(phy_id, Ok(phy_container.client_ifaces.iter().copied().collect()));
476                }
477            }
478        }
479
480        available_iface_ids
481    }
482
483    fn client_connections_enabled(&self) -> bool {
484        self.client_connections_enabled
485    }
486
487    async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError> {
488        self.client_connections_enabled = false;
489
490        let client_capable_phys = self.phys_for_role(fidl_common::WlanMacRole::Client);
491        let mut result = Ok(());
492        let mut failing_phys = Vec::new();
493
494        for client_phy in client_capable_phys.iter() {
495            let phy_container =
496                self.phys.get_mut(client_phy).ok_or(PhyManagerError::PhyQueryFailure)?;
497
498            // Continue tracking interface IDs for which deletion fails.
499            let mut lingering_ifaces = HashSet::new();
500
501            for iface_id in phy_container.client_ifaces.drain() {
502                match destroy_iface(&self.device_monitor, iface_id, &self.telemetry_sender).await {
503                    Ok(()) => {
504                        let _ = phy_container.destroyed_ifaces.insert(iface_id);
505                    }
506                    Err(e) => {
507                        result = Err(e);
508                        failing_phys.push(*client_phy);
509                        if !lingering_ifaces.insert(iface_id) {
510                            warn!("Unexpected duplicate lingering iface for id {}", iface_id);
511                        };
512                    }
513                }
514            }
515            phy_container.client_ifaces = lingering_ifaces;
516        }
517
518        if result.is_err() {
519            for phy_id in failing_phys {
520                self.record_defect(Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id }))
521            }
522        }
523
524        result
525    }
526
527    fn get_client(&mut self) -> Option<u16> {
528        if !self.client_connections_enabled {
529            return None;
530        }
531
532        let client_capable_phys = self.phys_for_role(fidl_common::WlanMacRole::Client);
533
534        // Find the first PHY with any client interfaces and return its first client interface.
535        let first_client_capable_phy = client_capable_phys.first()?;
536        let phy = self.phys.get_mut(first_client_capable_phy)?;
537        phy.client_ifaces.iter().next().copied()
538    }
539
540    async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError> {
541        let ap_capable_phy_ids = self.phys_for_role(fidl_common::WlanMacRole::Ap);
542
543        // First check for any PHYs that can have AP interfaces but do not yet
544        for ap_phy_id in ap_capable_phy_ids.iter() {
545            let phy_container =
546                self.phys.get_mut(ap_phy_id).ok_or(PhyManagerError::PhyQueryFailure)?;
547            if phy_container.ap_ifaces.is_empty() {
548                let mac = match self.suggested_ap_mac {
549                    Some(mac) => mac,
550                    None => NULL_ADDR,
551                };
552                let iface_id =
553                    self.create_iface(*ap_phy_id, fidl_common::WlanMacRole::Ap, mac).await?;
554
555                // Need to reborrow from self here, since self.create_iface also borrows self
556                // mutably. It's ok to unwrap here, since we just got this same interface a few
557                // lines above, and would be appropriate to panic if we can't get it.
558                #[expect(clippy::unwrap_used)]
559                let phy_container = self.phys.get_mut(ap_phy_id).unwrap();
560                let _ = phy_container.ap_ifaces.insert(iface_id);
561                return Ok(Some(iface_id));
562            }
563        }
564
565        // If all of the AP-capable PHYs have created AP interfaces already, return the
566        // first observed existing AP interface
567        // TODO(https://fxbug.dev/42126856): Figure out a better method of interface selection.
568        let Some(first_ap_capable_phy) = ap_capable_phy_ids.first() else {
569            return Ok(None);
570        };
571        let phy = match self.phys.get_mut(first_ap_capable_phy) {
572            Some(phy_container) => phy_container,
573            None => return Ok(None),
574        };
575        match phy.ap_ifaces.iter().next() {
576            Some(iface_id) => Ok(Some(*iface_id)),
577            None => Ok(None),
578        }
579    }
580
581    async fn destroy_ap_iface(&mut self, iface_id: u16) -> Result<(), PhyManagerError> {
582        let mut result = Ok(());
583        let mut failing_phy = None;
584
585        // If the interface has already been destroyed, return Ok.  Only error out in the case that
586        // the request to destroy the interface results in a failure.
587        for (phy_id, phy_container) in self.phys.iter_mut() {
588            if phy_container.ap_ifaces.remove(&iface_id) {
589                match destroy_iface(&self.device_monitor, iface_id, &self.telemetry_sender).await {
590                    Ok(()) => {
591                        let _ = phy_container.destroyed_ifaces.insert(iface_id);
592                    }
593                    Err(e) => {
594                        let _ = phy_container.ap_ifaces.insert(iface_id);
595                        result = Err(e);
596                        failing_phy = Some(*phy_id);
597                    }
598                }
599                break;
600            }
601        }
602
603        if let (Err(_), Some(phy_id)) = (result.as_ref(), failing_phy) {
604            self.record_defect(Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id }))
605        }
606
607        result
608    }
609
610    async fn destroy_all_ap_ifaces(&mut self) -> Result<(), PhyManagerError> {
611        let ap_capable_phys = self.phys_for_role(fidl_common::WlanMacRole::Ap);
612        let mut result = Ok(());
613        let mut failing_phys = Vec::new();
614
615        for ap_phy in ap_capable_phys.iter() {
616            let phy_container =
617                self.phys.get_mut(ap_phy).ok_or(PhyManagerError::PhyQueryFailure)?;
618
619            // Continue tracking interface IDs for which deletion fails.
620            let mut lingering_ifaces = HashSet::new();
621            for iface_id in phy_container.ap_ifaces.drain() {
622                match destroy_iface(&self.device_monitor, iface_id, &self.telemetry_sender).await {
623                    Ok(()) => {
624                        let _ = phy_container.destroyed_ifaces.insert(iface_id);
625                    }
626                    Err(e) => {
627                        result = Err(e);
628                        failing_phys.push(ap_phy);
629                        let _ = lingering_ifaces.insert(iface_id);
630                    }
631                }
632            }
633            phy_container.ap_ifaces = lingering_ifaces;
634        }
635
636        if result.is_err() {
637            for phy_id in failing_phys {
638                self.record_defect(Defect::Phy(PhyFailure::IfaceDestructionFailure {
639                    phy_id: *phy_id,
640                }))
641            }
642        }
643
644        result
645    }
646
647    fn suggest_ap_mac(&mut self, mac: MacAddr) {
648        self.suggested_ap_mac = Some(mac);
649    }
650
651    fn get_phy_ids(&self) -> Vec<u16> {
652        self.phys.keys().cloned().collect()
653    }
654
655    fn log_phy_add_failure(&mut self) {
656        let _ = self.phy_add_fail_count.add(1);
657    }
658
659    async fn set_country_code(
660        &mut self,
661        country_code: Option<client_types::CountryCode>,
662    ) -> Result<(), PhyManagerError> {
663        self.saved_country_code = country_code;
664
665        match country_code {
666            Some(country_code) => {
667                for phy_id in self.phys.keys() {
668                    set_phy_country_code(&self.device_monitor, *phy_id, country_code).await?;
669                }
670            }
671            None => {
672                for phy_id in self.phys.keys() {
673                    clear_phy_country_code(&self.device_monitor, *phy_id).await?;
674                }
675            }
676        }
677
678        Ok(())
679    }
680
681    fn record_defect(&mut self, defect: Defect) {
682        let mut recovery_action = None;
683
684        match defect {
685            Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id }) => {
686                self.telemetry_sender.send(TelemetryEvent::IfaceCreationResult(Err(())));
687                if let Some(container) = self.phys.get_mut(&phy_id) {
688                    container.defects.add_event(defect);
689                    recovery_action = (self.recovery_profile)(
690                        phy_id,
691                        &mut container.defects,
692                        &mut container.recoveries,
693                        defect,
694                    )
695                }
696            }
697            Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id }) => {
698                if let Some(container) = self.phys.get_mut(&phy_id) {
699                    container.defects.add_event(defect);
700                    recovery_action = (self.recovery_profile)(
701                        phy_id,
702                        &mut container.defects,
703                        &mut container.recoveries,
704                        defect,
705                    )
706                }
707            }
708            Defect::Iface(IfaceFailure::CanceledScan { iface_id })
709            | Defect::Iface(IfaceFailure::FailedScan { iface_id })
710            | Defect::Iface(IfaceFailure::EmptyScanResults { iface_id })
711            | Defect::Iface(IfaceFailure::ConnectionFailure { iface_id }) => {
712                for (phy_id, phy_info) in self.phys.iter_mut() {
713                    if phy_info.client_ifaces.contains(&iface_id)
714                        || phy_info.destroyed_ifaces.contains(&iface_id)
715                    {
716                        phy_info.defects.add_event(defect);
717
718                        recovery_action = (self.recovery_profile)(
719                            *phy_id,
720                            &mut phy_info.defects,
721                            &mut phy_info.recoveries,
722                            defect,
723                        );
724
725                        break;
726                    }
727                }
728            }
729            Defect::Iface(IfaceFailure::ApStartFailure { iface_id }) => {
730                for (phy_id, phy_info) in self.phys.iter_mut() {
731                    if phy_info.ap_ifaces.contains(&iface_id)
732                        || phy_info.destroyed_ifaces.contains(&iface_id)
733                    {
734                        phy_info.defects.add_event(defect);
735
736                        recovery_action = (self.recovery_profile)(
737                            *phy_id,
738                            &mut phy_info.defects,
739                            &mut phy_info.recoveries,
740                            defect,
741                        );
742
743                        break;
744                    }
745                }
746            }
747            Defect::Iface(IfaceFailure::Timeout { iface_id, source }) => {
748                self.telemetry_sender.send(TelemetryEvent::SmeTimeout { source });
749
750                for (phy_id, phy_info) in self.phys.iter_mut() {
751                    if phy_info.ap_ifaces.contains(&iface_id)
752                        || phy_info.client_ifaces.contains(&iface_id)
753                        || phy_info.destroyed_ifaces.contains(&iface_id)
754                    {
755                        phy_info.defects.add_event(defect);
756
757                        recovery_action = (self.recovery_profile)(
758                            *phy_id,
759                            &mut phy_info.defects,
760                            &mut phy_info.recoveries,
761                            defect,
762                        );
763
764                        break;
765                    }
766                }
767            }
768        }
769
770        if let Some(recovery_action) = recovery_action.take()
771            && let Err(e) = self
772                .recovery_action_sender
773                .try_send(recovery::RecoverySummary::new(defect, recovery_action))
774        {
775            warn!("Unable to suggest recovery action {:?}: {:?}", recovery_action, e);
776        }
777    }
778
779    async fn perform_recovery(&mut self, summary: recovery::RecoverySummary) {
780        self.log_recovery_action(summary);
781
782        if self.recovery_enabled {
783            match summary.action {
784                RecoveryAction::PhyRecovery(PhyRecoveryOperation::DestroyIface { iface_id }) => {
785                    for (_, phy_container) in self.phys.iter_mut() {
786                        if phy_container.ap_ifaces.remove(&iface_id) {
787                            #[allow(
788                                clippy::redundant_pattern_matching,
789                                reason = "mass allow for https://fxbug.dev/381896734"
790                            )]
791                            if let Err(_) = destroy_iface(
792                                &self.device_monitor,
793                                iface_id,
794                                &self.telemetry_sender,
795                            )
796                            .await
797                            {
798                                let _ = phy_container.ap_ifaces.insert(iface_id);
799                            } else {
800                                let _ = phy_container.destroyed_ifaces.insert(iface_id);
801                            }
802
803                            return;
804                        }
805
806                        if phy_container.client_ifaces.remove(&iface_id) {
807                            if destroy_iface(&self.device_monitor, iface_id, &self.telemetry_sender)
808                                .await
809                                .is_err()
810                            {
811                                let _ = phy_container.client_ifaces.insert(iface_id);
812                            } else {
813                                let _ = phy_container.destroyed_ifaces.insert(iface_id);
814                            }
815
816                            return;
817                        }
818                    }
819
820                    warn!(
821                        "Recovery suggested destroying iface {}, but no record was found.",
822                        iface_id
823                    );
824                }
825                RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id }) => {
826                    for recorded_phy_id in self.phys.keys() {
827                        if phy_id == *recorded_phy_id {
828                            if let Err(e) = reset_phy(&self.device_monitor, phy_id).await {
829                                warn!("Resetting PHY {} failed: {:?}", phy_id, e);
830                            }
831
832                            // The phy reset may clear its country code. Re-set it now if we have one.
833                            if let Some(country_code) = self.saved_country_code {
834                                info!("Setting country code after phy reset");
835                                if let Err(e) =
836                                    set_phy_country_code(&self.device_monitor, phy_id, country_code)
837                                        .await
838                                {
839                                    warn!(
840                                        "Proceeding with default country code because we failed to set the cached one: {}",
841                                        e
842                                    );
843                                }
844                            };
845
846                            return;
847                        }
848                    }
849                }
850                RecoveryAction::IfaceRecovery(IfaceRecoveryOperation::Disconnect { iface_id }) => {
851                    if let Err(e) = disconnect(&self.device_monitor, iface_id).await {
852                        warn!("Disconnecting client {} failed: {:?}", iface_id, e);
853                    }
854                }
855                RecoveryAction::IfaceRecovery(IfaceRecoveryOperation::StopAp { iface_id }) => {
856                    if let Err(e) = stop_ap(&self.device_monitor, iface_id).await {
857                        warn!("Stopping AP {} failed: {:?}", iface_id, e);
858                    }
859                }
860            }
861        }
862    }
863}
864
865/// Destroys the specified interface.
866async fn destroy_iface(
867    proxy: &fidl_service::DeviceMonitorProxy,
868    iface_id: u16,
869    telemetry_sender: &TelemetrySender,
870) -> Result<(), PhyManagerError> {
871    let request = fidl_service::DestroyIfaceRequest { iface_id };
872    let (destroy_iface_response, metric) = match proxy.destroy_iface(&request).await {
873        Ok(status) => match status {
874            zx::sys::ZX_OK => (Ok(()), Some(Ok(()))),
875            zx::sys::ZX_ERR_NOT_FOUND => {
876                info!("Interface not found, assuming it is already destroyed");
877                // Don't return a metric here, we neither succeeded nor failed to destroy
878                (Ok(()), None)
879            }
880            e => {
881                warn!("failed to destroy iface {}: {}", iface_id, e);
882                (Err(PhyManagerError::IfaceDestroyFailure), Some(Err(())))
883            }
884        },
885        Err(e) => {
886            warn!("failed to send destroy iface {}: {}", iface_id, e);
887            (Err(PhyManagerError::IfaceDestroyFailure), Some(Err(())))
888        }
889    };
890
891    if let Some(metric) = metric {
892        telemetry_sender.send(TelemetryEvent::IfaceDestructionResult(metric));
893    }
894
895    destroy_iface_response
896}
897
898async fn reset_phy(
899    proxy: &fidl_service::DeviceMonitorProxy,
900    phy_id: u16,
901) -> Result<(), PhyManagerError> {
902    let result = proxy.reset(phy_id).await.map_err(|e| {
903        warn!("Request to reset PHY {} failed: {:?}", phy_id, e);
904        PhyManagerError::InternalError
905    })?;
906
907    result.map_err(|e| {
908        warn!("Failed to reset PHY {}: {:?}", phy_id, e);
909        PhyManagerError::PhyResetFailure
910    })
911}
912
913async fn set_phy_country_code(
914    proxy: &fidl_service::DeviceMonitorProxy,
915    phy_id: u16,
916    country_code: client_types::CountryCode,
917) -> Result<(), PhyManagerError> {
918    let status = proxy
919        .set_country(&fidl_service::SetCountryRequest { phy_id, alpha2: country_code.into() })
920        .await
921        .map_err(|e| {
922            error!("Failed to set country code for PHY {}: {:?}", phy_id, e);
923            PhyManagerError::PhySetCountryFailure
924        })?;
925
926    zx::ok(status).map_err(|e| {
927        error!("Received bad status when setting country code for PHY {}: {}", phy_id, e);
928        PhyManagerError::PhySetCountryFailure
929    })
930}
931
932async fn clear_phy_country_code(
933    proxy: &fidl_service::DeviceMonitorProxy,
934    phy_id: u16,
935) -> Result<(), PhyManagerError> {
936    let status =
937        proxy.clear_country(&fidl_service::ClearCountryRequest { phy_id }).await.map_err(|e| {
938            error!("Failed to clear country code for PHY {}: {:?}", phy_id, e);
939            PhyManagerError::PhySetCountryFailure
940        })?;
941
942    zx::ok(status).map_err(|e| {
943        error!("Received bad status when clearing country code for PHY {}: {}", phy_id, e);
944        PhyManagerError::PhySetCountryFailure
945    })
946}
947
948async fn disconnect(
949    dev_monitor_proxy: &fidl_service::DeviceMonitorProxy,
950    iface_id: u16,
951) -> Result<(), Error> {
952    let (sme_proxy, remote) = create_proxy();
953    dev_monitor_proxy.get_client_sme(iface_id, remote).await?.map_err(zx::Status::from_raw)?;
954
955    sme_proxy
956        .disconnect(fidl_sme::UserDisconnectReason::Recovery)
957        .await
958        .map_err(|e| format_err!("Disconnect failed: {:?}", e))
959}
960
961async fn stop_ap(
962    dev_monitor_proxy: &fidl_service::DeviceMonitorProxy,
963    iface_id: u16,
964) -> Result<(), Error> {
965    let (sme_proxy, remote) = create_proxy();
966    dev_monitor_proxy.get_ap_sme(iface_id, remote).await?.map_err(zx::Status::from_raw)?;
967
968    match sme_proxy.stop().await {
969        Ok(result) => match result {
970            fidl_sme::StopApResultCode::Success => Ok(()),
971            err => Err(format_err!("Stop AP failed: {:?}", err)),
972        },
973        Err(e) => Err(format_err!("Stop AP request failed: {:?}", e)),
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980    use crate::telemetry;
981    use crate::util::testing::{poll_ap_sme_req, poll_sme_req};
982    use assert_matches::assert_matches;
983    use diagnostics_assertions::assert_data_tree;
984    use fidl::endpoints;
985    use fuchsia_async::{TestExecutor, run_singlethreaded};
986    use futures::channel::mpsc;
987    use futures::stream::StreamExt;
988    use futures::task::Poll;
989    use std::pin::pin;
990    use test_case::test_case;
991    use zx::sys::{ZX_ERR_NOT_FOUND, ZX_OK};
992    use {
993        fidl_fuchsia_wlan_device_service as fidl_service, fidl_fuchsia_wlan_sme as fidl_sme,
994        fuchsia_inspect as inspect,
995    };
996
997    /// Hold the client and service ends for DeviceMonitor to allow mocking DeviceMonitor responses
998    /// for unit tests.
999    struct TestValues {
1000        monitor_proxy: fidl_service::DeviceMonitorProxy,
1001        monitor_stream: fidl_service::DeviceMonitorRequestStream,
1002        inspector: inspect::Inspector,
1003        node: inspect::Node,
1004        telemetry_sender: TelemetrySender,
1005        telemetry_receiver: mpsc::Receiver<TelemetryEvent>,
1006        recovery_sender: recovery::RecoveryActionSender,
1007        recovery_receiver: recovery::RecoveryActionReceiver,
1008    }
1009
1010    /// Create a TestValues for a unit test.
1011    fn test_setup() -> TestValues {
1012        let (monitor_proxy, monitor_requests) =
1013            endpoints::create_proxy::<fidl_service::DeviceMonitorMarker>();
1014        let monitor_stream = monitor_requests.into_stream();
1015
1016        let inspector = inspect::Inspector::default();
1017        let node = inspector.root().create_child("phy_manager");
1018        let (sender, telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1019        let telemetry_sender = TelemetrySender::new(sender);
1020        let (recovery_sender, recovery_receiver) =
1021            mpsc::channel::<recovery::RecoverySummary>(recovery::RECOVERY_SUMMARY_CHANNEL_CAPACITY);
1022
1023        TestValues {
1024            monitor_proxy,
1025            monitor_stream,
1026            inspector,
1027            node,
1028            telemetry_sender,
1029            telemetry_receiver,
1030            recovery_sender,
1031            recovery_receiver,
1032        }
1033    }
1034
1035    /// Take in the service side of a DeviceMonitor::GetSupportedMacRoles request and respond with
1036    /// the given WlanMacRoles responst.
1037    fn send_get_supported_mac_roles_response(
1038        exec: &mut TestExecutor,
1039        server: &mut fidl_service::DeviceMonitorRequestStream,
1040        supported_mac_roles: Result<&[fidl_common::WlanMacRole], zx::sys::zx_status_t>,
1041    ) {
1042        let _ = assert_matches!(
1043            exec.run_until_stalled(&mut server.next()),
1044            Poll::Ready(Some(Ok(
1045                fidl_service::DeviceMonitorRequest::GetSupportedMacRoles {
1046                    responder, ..
1047                }
1048            ))) => {
1049                responder.send(supported_mac_roles)
1050            }
1051        );
1052    }
1053
1054    /// Create a PhyInfo object for unit testing.
1055    #[track_caller]
1056    fn send_query_iface_response(
1057        exec: &mut TestExecutor,
1058        server: &mut fidl_service::DeviceMonitorRequestStream,
1059        iface_info: Option<fidl_service::QueryIfaceResponse>,
1060    ) {
1061        let response = iface_info.as_ref().ok_or(ZX_ERR_NOT_FOUND);
1062        assert_matches!(
1063            exec.run_until_stalled(&mut server.next()),
1064            Poll::Ready(Some(Ok(
1065                fidl_service::DeviceMonitorRequest::QueryIface {
1066                    iface_id: _,
1067                    responder,
1068                }
1069            ))) => {
1070                responder.send(response).expect("sending fake iface info");
1071            }
1072        );
1073    }
1074
1075    /// Handles the service side of a DeviceMonitor::CreateIface request by replying with the
1076    /// provided optional iface ID.
1077    #[track_caller]
1078    fn send_create_iface_response(
1079        exec: &mut TestExecutor,
1080        server: &mut fidl_service::DeviceMonitorRequestStream,
1081        iface_id: Option<u16>,
1082    ) {
1083        assert_matches!(
1084            exec.run_until_stalled(&mut server.next()),
1085            Poll::Ready(Some(Ok(
1086                fidl_service::DeviceMonitorRequest::CreateIface {
1087                    responder,
1088                    ..
1089                }
1090            ))) => {
1091                match iface_id {
1092                    Some(iface_id) => responder.send(
1093                        Ok(&fidl_service::DeviceMonitorCreateIfaceResponse {
1094                            iface_id: Some(iface_id),
1095                            ..Default::default()
1096                        })
1097                    )
1098                    .expect("sending fake iface id"),
1099                    None => responder.send(Err(fidl_service::DeviceMonitorError::unknown())).expect("sending fake response with none")
1100                }
1101            }
1102        );
1103    }
1104
1105    /// Handles the service side of a DeviceMonitor::DestroyIface request by replying with the
1106    /// provided zx_status_t.
1107    fn send_destroy_iface_response(
1108        exec: &mut TestExecutor,
1109        server: &mut fidl_service::DeviceMonitorRequestStream,
1110        return_status: zx::sys::zx_status_t,
1111    ) {
1112        assert_matches!(
1113            exec.run_until_stalled(&mut server.next()),
1114            Poll::Ready(Some(Ok(
1115                fidl_service::DeviceMonitorRequest::DestroyIface {
1116                    req: _,
1117                    responder,
1118                }
1119            ))) => {
1120                responder
1121                    .send(return_status)
1122                    .unwrap_or_else(|e| panic!("sending fake response: {return_status}: {e:?}"));
1123            }
1124        );
1125    }
1126
1127    /// Creates a QueryIfaceResponse from the arguments provided by the caller.
1128    fn create_iface_response(
1129        role: fidl_common::WlanMacRole,
1130        id: u16,
1131        phy_id: u16,
1132        phy_assigned_id: u16,
1133        sta_addr: [u8; 6],
1134        factory_addr: [u8; 6],
1135    ) -> fidl_service::QueryIfaceResponse {
1136        fidl_service::QueryIfaceResponse {
1137            role,
1138            id,
1139            phy_id,
1140            phy_assigned_id,
1141            sta_addr,
1142            factory_addr,
1143        }
1144    }
1145
1146    /// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
1147    /// calling add_phy on PhyManager for a PHY that exists.  The expectation is that the
1148    /// PhyManager initially does not have any PHYs available.  After the call to add_phy, the
1149    /// PhyManager should have a new PhyContainer.
1150    #[fuchsia::test]
1151    fn add_valid_phy() {
1152        let mut exec = TestExecutor::new();
1153        let mut test_values = test_setup();
1154
1155        let fake_phy_id = 0;
1156        let fake_mac_roles = vec![];
1157
1158        let mut phy_manager = PhyManager::new(
1159            test_values.monitor_proxy,
1160            recovery::lookup_recovery_profile(""),
1161            false,
1162            test_values.node,
1163            test_values.telemetry_sender,
1164            test_values.recovery_sender,
1165        );
1166        {
1167            let add_phy_fut = phy_manager.add_phy(0);
1168            let mut add_phy_fut = pin!(add_phy_fut);
1169            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1170
1171            send_get_supported_mac_roles_response(
1172                &mut exec,
1173                &mut test_values.monitor_stream,
1174                Ok(&fake_mac_roles),
1175            );
1176
1177            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1178        }
1179
1180        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1181        assert_eq!(
1182            phy_manager.phys.get(&fake_phy_id).unwrap().supported_mac_roles,
1183            fake_mac_roles.into_iter().collect()
1184        );
1185    }
1186
1187    /// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
1188    /// calling add_phy on PhyManager for a PHY that does not exist.  The PhyManager in this case
1189    /// should not create and store a new PhyContainer.
1190    #[fuchsia::test]
1191    fn add_invalid_phy() {
1192        let mut exec = TestExecutor::new();
1193        let mut test_values = test_setup();
1194        let mut phy_manager = PhyManager::new(
1195            test_values.monitor_proxy,
1196            recovery::lookup_recovery_profile(""),
1197            false,
1198            test_values.node,
1199            test_values.telemetry_sender,
1200            test_values.recovery_sender,
1201        );
1202
1203        {
1204            let add_phy_fut = phy_manager.add_phy(1);
1205            let mut add_phy_fut = pin!(add_phy_fut);
1206            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1207
1208            send_get_supported_mac_roles_response(
1209                &mut exec,
1210                &mut test_values.monitor_stream,
1211                Err(zx::sys::ZX_ERR_NOT_FOUND),
1212            );
1213
1214            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1215        }
1216        assert!(phy_manager.phys.is_empty());
1217    }
1218
1219    /// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
1220    /// calling add_phy on PhyManager for a PHY that has already been accounted for, but whose
1221    /// properties have changed.  The PhyManager in this case should update the associated PhyInfo.
1222    #[fuchsia::test]
1223    fn add_duplicate_phy() {
1224        let mut exec = TestExecutor::new();
1225        let mut test_values = test_setup();
1226        let mut phy_manager = PhyManager::new(
1227            test_values.monitor_proxy,
1228            recovery::lookup_recovery_profile(""),
1229            false,
1230            test_values.node,
1231            test_values.telemetry_sender,
1232            test_values.recovery_sender,
1233        );
1234
1235        let fake_phy_id = 0;
1236        let fake_mac_roles = vec![];
1237
1238        {
1239            let add_phy_fut = phy_manager.add_phy(fake_phy_id);
1240            let mut add_phy_fut = pin!(add_phy_fut);
1241            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1242
1243            send_get_supported_mac_roles_response(
1244                &mut exec,
1245                &mut test_values.monitor_stream,
1246                Ok(&fake_mac_roles),
1247            );
1248
1249            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1250        }
1251
1252        {
1253            assert!(phy_manager.phys.contains_key(&fake_phy_id));
1254            assert_eq!(
1255                phy_manager.phys.get(&fake_phy_id).unwrap().supported_mac_roles,
1256                fake_mac_roles.clone().into_iter().collect()
1257            );
1258        }
1259
1260        // Send an update for the same PHY ID and ensure that the PHY info is updated.
1261        {
1262            let add_phy_fut = phy_manager.add_phy(fake_phy_id);
1263            let mut add_phy_fut = pin!(add_phy_fut);
1264            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1265
1266            send_get_supported_mac_roles_response(
1267                &mut exec,
1268                &mut test_values.monitor_stream,
1269                Ok(&fake_mac_roles),
1270            );
1271
1272            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1273        }
1274
1275        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1276        assert_eq!(
1277            phy_manager.phys.get(&fake_phy_id).unwrap().supported_mac_roles,
1278            fake_mac_roles.into_iter().collect()
1279        );
1280    }
1281
1282    #[fuchsia::test]
1283    fn create_all_client_ifaces_after_phys_added() {
1284        let mut exec = TestExecutor::new();
1285        let mut test_values = test_setup();
1286        let mut phy_manager = PhyManager::new(
1287            test_values.monitor_proxy,
1288            recovery::lookup_recovery_profile(""),
1289            false,
1290            test_values.node,
1291            test_values.telemetry_sender,
1292            test_values.recovery_sender,
1293        );
1294
1295        for phy_id in 0..2 {
1296            {
1297                let add_phy_fut = phy_manager.add_phy(phy_id);
1298                let mut add_phy_fut = pin!(add_phy_fut);
1299
1300                assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1301
1302                send_get_supported_mac_roles_response(
1303                    &mut exec,
1304                    &mut test_values.monitor_stream,
1305                    Ok(&[fidl_common::WlanMacRole::Client]),
1306                );
1307
1308                assert_matches!(exec.run_until_stalled(&mut add_phy_fut), Poll::Ready(Ok(())));
1309            }
1310            assert!(phy_manager.phys.contains_key(&phy_id));
1311        }
1312
1313        {
1314            let start_connections_fut = phy_manager
1315                .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
1316            let mut start_connections_fut = pin!(start_connections_fut);
1317
1318            // This is a little fragile since it may not be guaranteed that the create iface calls
1319            // come in on the phys in the same order they're added.
1320            for iface_id in [10, 20] {
1321                assert!(exec.run_until_stalled(&mut start_connections_fut).is_pending());
1322
1323                send_create_iface_response(
1324                    &mut exec,
1325                    &mut test_values.monitor_stream,
1326                    Some(iface_id),
1327                );
1328            }
1329
1330            assert_matches!(exec.run_until_stalled(&mut start_connections_fut),
1331                Poll::Ready(iface_ids) => {
1332                    assert!(iface_ids.values().all(Result::is_ok));
1333                    assert!(iface_ids.contains_key(&0));
1334                    assert!(iface_ids.contains_key(&1));
1335                    let iface_ids: HashSet<_> = iface_ids.into_values().flat_map(Result::unwrap).collect();
1336                    assert_eq!(iface_ids, HashSet::from([10, 20]));
1337                }
1338            );
1339        }
1340
1341        let mut iface_ids = HashSet::new();
1342        for phy_id in 0..2 {
1343            let phy_container = phy_manager.phys.get(&phy_id).unwrap();
1344            // Because of how this test is mocked, the iface ids could be assigned in
1345            // either order.
1346            assert_eq!(phy_container.client_ifaces.len(), 1);
1347            phy_container.client_ifaces.iter().for_each(|iface_id| {
1348                assert!(iface_ids.insert(*iface_id));
1349            });
1350            assert!(phy_container.defects.events.is_empty());
1351        }
1352        assert_eq!(iface_ids, HashSet::from([10, 20]));
1353    }
1354
1355    /// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyRemoved event and
1356    /// calling remove_phy on PhyManager for a PHY that not longer exists.  The PhyManager in this
1357    /// case should remove the PhyContainer associated with the removed PHY ID.
1358    #[fuchsia::test]
1359    fn add_phy_after_create_all_client_ifaces() {
1360        let mut exec = TestExecutor::new();
1361        let mut test_values = test_setup();
1362        let mut phy_manager = PhyManager::new(
1363            test_values.monitor_proxy,
1364            recovery::lookup_recovery_profile(""),
1365            false,
1366            test_values.node,
1367            test_values.telemetry_sender,
1368            test_values.recovery_sender,
1369        );
1370
1371        let fake_iface_id = 1;
1372        let fake_phy_id = 1;
1373        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
1374
1375        {
1376            let start_connections_fut = phy_manager
1377                .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
1378            let mut start_connections_fut = pin!(start_connections_fut);
1379            assert!(exec.run_until_stalled(&mut start_connections_fut).is_ready());
1380        }
1381
1382        // Add a new phy.  Since client connections have been started, it should also create a
1383        // client iface.
1384        {
1385            let add_phy_fut = phy_manager.add_phy(fake_phy_id);
1386            let mut add_phy_fut = pin!(add_phy_fut);
1387            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1388
1389            send_get_supported_mac_roles_response(
1390                &mut exec,
1391                &mut test_values.monitor_stream,
1392                Ok(&fake_mac_roles),
1393            );
1394
1395            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1396
1397            send_create_iface_response(
1398                &mut exec,
1399                &mut test_values.monitor_stream,
1400                Some(fake_iface_id),
1401            );
1402
1403            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1404        }
1405
1406        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1407        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1408        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
1409        assert!(phy_container.defects.events.is_empty());
1410    }
1411
1412    /// Tests the case where a PHY is added after client connections have been enabled but creating
1413    /// an interface for the new PHY fails.  In this case, the PHY is not added.
1414    ///
1415    /// If this behavior changes, defect accounting needs to be updated and tested here.
1416    #[fuchsia::test]
1417    fn add_phy_with_iface_creation_failure() {
1418        let mut exec = TestExecutor::new();
1419        let mut test_values = test_setup();
1420        let mut phy_manager = PhyManager::new(
1421            test_values.monitor_proxy,
1422            recovery::lookup_recovery_profile(""),
1423            false,
1424            test_values.node,
1425            test_values.telemetry_sender,
1426            test_values.recovery_sender,
1427        );
1428
1429        let fake_phy_id = 1;
1430        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
1431
1432        {
1433            let start_connections_fut = phy_manager
1434                .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
1435            let mut start_connections_fut = pin!(start_connections_fut);
1436            assert!(exec.run_until_stalled(&mut start_connections_fut).is_ready());
1437        }
1438
1439        // Add a new phy.  Since client connections have been started, it should also create a
1440        // client iface.
1441        {
1442            let add_phy_fut = phy_manager.add_phy(fake_phy_id);
1443            let mut add_phy_fut = pin!(add_phy_fut);
1444            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1445
1446            send_get_supported_mac_roles_response(
1447                &mut exec,
1448                &mut test_values.monitor_stream,
1449                Ok(&fake_mac_roles),
1450            );
1451
1452            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1453
1454            // Send back an error to mimic a failure to create an interface.
1455            send_create_iface_response(&mut exec, &mut test_values.monitor_stream, None);
1456
1457            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1458        }
1459
1460        assert!(!phy_manager.phys.contains_key(&fake_phy_id));
1461    }
1462
1463    /// Tests the case where a new PHY is discovered after the country code has been set.
1464    #[fuchsia::test]
1465    fn test_add_phy_after_setting_country_code() {
1466        let mut exec = TestExecutor::new();
1467        let mut test_values = test_setup();
1468
1469        let fake_phy_id = 1;
1470        let fake_mac_roles = vec![];
1471
1472        let mut phy_manager = PhyManager::new(
1473            test_values.monitor_proxy,
1474            recovery::lookup_recovery_profile(""),
1475            false,
1476            test_values.node,
1477            test_values.telemetry_sender,
1478            test_values.recovery_sender,
1479        );
1480
1481        {
1482            let set_country_fut = phy_manager.set_country_code(Some("US".parse().unwrap()));
1483            let mut set_country_fut = pin!(set_country_fut);
1484            assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
1485        }
1486
1487        {
1488            let add_phy_fut = phy_manager.add_phy(fake_phy_id);
1489            let mut add_phy_fut = pin!(add_phy_fut);
1490            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1491
1492            send_get_supported_mac_roles_response(
1493                &mut exec,
1494                &mut test_values.monitor_stream,
1495                Ok(&fake_mac_roles),
1496            );
1497
1498            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
1499
1500            assert_matches!(
1501                exec.run_until_stalled(&mut test_values.monitor_stream.next()),
1502                Poll::Ready(Some(Ok(
1503                    fidl_service::DeviceMonitorRequest::SetCountry {
1504                        req: fidl_service::SetCountryRequest {
1505                            phy_id: 1,
1506                            alpha2: [b'U', b'S'],
1507                        },
1508                        responder,
1509                    }
1510                ))) => {
1511                    responder.send(ZX_OK).expect("sending fake set country response");
1512                }
1513            );
1514
1515            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
1516        }
1517
1518        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1519        assert_eq!(
1520            phy_manager.phys.get(&fake_phy_id).unwrap().supported_mac_roles,
1521            fake_mac_roles.into_iter().collect()
1522        );
1523    }
1524
1525    #[run_singlethreaded(test)]
1526    async fn remove_valid_phy() {
1527        let test_values = test_setup();
1528        let mut phy_manager = PhyManager::new(
1529            test_values.monitor_proxy,
1530            recovery::lookup_recovery_profile(""),
1531            false,
1532            test_values.node,
1533            test_values.telemetry_sender,
1534            test_values.recovery_sender,
1535        );
1536
1537        let fake_phy_id = 1;
1538        let fake_mac_roles = vec![];
1539
1540        let phy_container = PhyContainer::new(fake_mac_roles);
1541        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1542        phy_manager.remove_phy(fake_phy_id);
1543        assert!(phy_manager.phys.is_empty());
1544    }
1545
1546    /// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyRemoved event and
1547    /// calling remove_phy on PhyManager for a PHY ID that is not accounted for by the PhyManager.
1548    /// The PhyManager should realize that it is unaware of this PHY ID and leave its PhyContainers
1549    /// unchanged.
1550    #[run_singlethreaded(test)]
1551    async fn remove_nonexistent_phy() {
1552        let test_values = test_setup();
1553        let mut phy_manager = PhyManager::new(
1554            test_values.monitor_proxy,
1555            recovery::lookup_recovery_profile(""),
1556            false,
1557            test_values.node,
1558            test_values.telemetry_sender,
1559            test_values.recovery_sender,
1560        );
1561
1562        let fake_phy_id = 1;
1563        let fake_mac_roles = vec![];
1564
1565        let phy_container = PhyContainer::new(fake_mac_roles);
1566        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1567        phy_manager.remove_phy(2);
1568        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1569    }
1570
1571    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
1572    /// an iface that belongs to a PHY that has been accounted for.  The PhyManager should add the
1573    /// newly discovered iface to the existing PHY's list of client ifaces.
1574    #[fuchsia::test]
1575    fn on_iface_added() {
1576        let mut exec = TestExecutor::new();
1577        let mut test_values = test_setup();
1578        let mut phy_manager = PhyManager::new(
1579            test_values.monitor_proxy,
1580            recovery::lookup_recovery_profile(""),
1581            false,
1582            test_values.node,
1583            test_values.telemetry_sender,
1584            test_values.recovery_sender,
1585        );
1586
1587        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1588        // iface is added.
1589        let fake_phy_id = 1;
1590        let fake_mac_roles = vec![];
1591
1592        let phy_container = PhyContainer::new(fake_mac_roles);
1593
1594        // Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
1595        let fake_role = fidl_common::WlanMacRole::Client;
1596        let fake_iface_id = 1;
1597        let fake_phy_assigned_id = 1;
1598        let fake_sta_addr = [0, 1, 2, 3, 4, 5];
1599        let fake_factory_addr = [0, 1, 2, 3, 4, 5];
1600        let iface_response = create_iface_response(
1601            fake_role,
1602            fake_iface_id,
1603            fake_phy_id,
1604            fake_phy_assigned_id,
1605            fake_sta_addr,
1606            fake_factory_addr,
1607        );
1608
1609        {
1610            // Inject the fake PHY information
1611            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1612
1613            // Add the fake iface
1614            let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
1615            let mut on_iface_added_fut = pin!(on_iface_added_fut);
1616            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1617
1618            send_query_iface_response(
1619                &mut exec,
1620                &mut test_values.monitor_stream,
1621                Some(iface_response),
1622            );
1623
1624            // Wait for the PhyManager to finish processing the received iface information
1625            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
1626        }
1627
1628        // Expect that the PhyContainer associated with the fake PHY has been updated with the
1629        // fake client
1630        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1631        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
1632    }
1633
1634    #[fuchsia::test]
1635    fn on_iface_added_unknown_role_is_unsupported() {
1636        let mut exec = TestExecutor::new();
1637        let mut test_values = test_setup();
1638        let mut phy_manager = PhyManager::new(
1639            test_values.monitor_proxy,
1640            recovery::lookup_recovery_profile(""),
1641            false,
1642            test_values.node,
1643            test_values.telemetry_sender,
1644            test_values.recovery_sender,
1645        );
1646
1647        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1648        // iface is added.
1649        let fake_phy_id = 1;
1650        let fake_mac_roles = vec![];
1651
1652        let phy_container = PhyContainer::new(fake_mac_roles);
1653
1654        // Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
1655        let fake_role = fidl_common::WlanMacRole::unknown();
1656        let fake_iface_id = 1;
1657        let fake_phy_assigned_id = 1;
1658        let fake_sta_addr = [0, 1, 2, 3, 4, 5];
1659        let fake_factory_addr = [0, 1, 2, 3, 4, 5];
1660        let iface_response = create_iface_response(
1661            fake_role,
1662            fake_iface_id,
1663            fake_phy_id,
1664            fake_phy_assigned_id,
1665            fake_sta_addr,
1666            fake_factory_addr,
1667        );
1668
1669        {
1670            // Inject the fake PHY information
1671            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1672
1673            // Add the fake iface
1674            let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
1675            let mut on_iface_added_fut = pin!(on_iface_added_fut);
1676            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1677
1678            send_query_iface_response(
1679                &mut exec,
1680                &mut test_values.monitor_stream,
1681                Some(iface_response),
1682            );
1683
1684            // Show that on_iface_added results in an error since unknown WlanMacRole is unsupported
1685            assert_matches!(
1686                exec.run_until_stalled(&mut on_iface_added_fut),
1687                Poll::Ready(Err(PhyManagerError::Unsupported))
1688            );
1689        }
1690
1691        // Expect that the PhyContainer associated with the fake PHY has been updated with the
1692        // fake client
1693        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1694        assert!(!phy_container.client_ifaces.contains(&fake_iface_id));
1695    }
1696
1697    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
1698    /// an iface that belongs to a PHY that has not been accounted for.  The PhyManager should
1699    /// query the PHY's information, create a new PhyContainer, and insert the new iface ID into
1700    /// the PHY's list of client ifaces.
1701    #[fuchsia::test]
1702    fn on_iface_added_missing_phy() {
1703        let mut exec = TestExecutor::new();
1704        let mut test_values = test_setup();
1705        let mut phy_manager = PhyManager::new(
1706            test_values.monitor_proxy,
1707            recovery::lookup_recovery_profile(""),
1708            false,
1709            test_values.node,
1710            test_values.telemetry_sender,
1711            test_values.recovery_sender,
1712        );
1713
1714        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1715        // iface is added.
1716        let fake_phy_id = 1;
1717        let fake_mac_roles = vec![];
1718
1719        // Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
1720        let fake_role = fidl_common::WlanMacRole::Client;
1721        let fake_iface_id = 1;
1722        let fake_phy_assigned_id = 1;
1723        let fake_sta_addr = [0, 1, 2, 3, 4, 5];
1724        let fake_factory_addr = [0, 1, 2, 3, 4, 5];
1725        let iface_response = create_iface_response(
1726            fake_role,
1727            fake_iface_id,
1728            fake_phy_id,
1729            fake_phy_assigned_id,
1730            fake_sta_addr,
1731            fake_factory_addr,
1732        );
1733
1734        {
1735            // Add the fake iface
1736            let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
1737            let mut on_iface_added_fut = pin!(on_iface_added_fut);
1738
1739            // Since the PhyManager has not accounted for any PHYs, it will get the iface
1740            // information first and then query for the iface's PHY's information.
1741
1742            // The iface query goes out first
1743            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1744
1745            send_query_iface_response(
1746                &mut exec,
1747                &mut test_values.monitor_stream,
1748                Some(iface_response),
1749            );
1750
1751            // And then the PHY information is queried.
1752            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1753
1754            send_get_supported_mac_roles_response(
1755                &mut exec,
1756                &mut test_values.monitor_stream,
1757                Ok(&fake_mac_roles),
1758            );
1759
1760            // Wait for the PhyManager to finish processing the received iface information
1761            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
1762        }
1763
1764        // Expect that the PhyContainer associated with the fake PHY has been updated with the
1765        // fake client
1766        assert!(phy_manager.phys.contains_key(&fake_phy_id));
1767
1768        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1769        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
1770    }
1771
1772    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
1773    /// an iface that was created by PhyManager and has already been accounted for.  The PhyManager
1774    /// should simply ignore the duplicate iface ID and not append it to its list of clients.
1775    #[fuchsia::test]
1776    fn add_duplicate_iface() {
1777        let mut exec = TestExecutor::new();
1778        let mut test_values = test_setup();
1779        let mut phy_manager = PhyManager::new(
1780            test_values.monitor_proxy,
1781            recovery::lookup_recovery_profile(""),
1782            false,
1783            test_values.node,
1784            test_values.telemetry_sender,
1785            test_values.recovery_sender,
1786        );
1787
1788        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1789        // iface is added.
1790        let fake_phy_id = 1;
1791        let fake_mac_roles = vec![];
1792
1793        // Inject the fake PHY information
1794        let phy_container = PhyContainer::new(fake_mac_roles);
1795        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1796
1797        // Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
1798        let fake_role = fidl_common::WlanMacRole::Client;
1799        let fake_iface_id = 1;
1800        let fake_phy_assigned_id = 1;
1801        let fake_sta_addr = [0, 1, 2, 3, 4, 5];
1802        let fake_factory_addr = [0, 1, 2, 3, 4, 5];
1803        let iface_response = create_iface_response(
1804            fake_role,
1805            fake_iface_id,
1806            fake_phy_id,
1807            fake_phy_assigned_id,
1808            fake_sta_addr,
1809            fake_factory_addr,
1810        );
1811
1812        // Add the same iface ID twice
1813        for _ in 0..2 {
1814            // Add the fake iface
1815            let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
1816            let mut on_iface_added_fut = pin!(on_iface_added_fut);
1817            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1818
1819            send_query_iface_response(
1820                &mut exec,
1821                &mut test_values.monitor_stream,
1822                Some(iface_response),
1823            );
1824
1825            // Wait for the PhyManager to finish processing the received iface information
1826            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
1827        }
1828
1829        // Expect that the PhyContainer associated with the fake PHY has been updated with only one
1830        // reference to the fake client
1831        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1832        assert_eq!(phy_container.client_ifaces.len(), 1);
1833        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
1834    }
1835
1836    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
1837    /// an iface that has already been removed.  The PhyManager should fail to query the iface info
1838    /// and not account for the iface ID.
1839    #[fuchsia::test]
1840    fn add_nonexistent_iface() {
1841        let mut exec = TestExecutor::new();
1842        let mut test_values = test_setup();
1843        let mut phy_manager = PhyManager::new(
1844            test_values.monitor_proxy,
1845            recovery::lookup_recovery_profile(""),
1846            false,
1847            test_values.node,
1848            test_values.telemetry_sender,
1849            test_values.recovery_sender,
1850        );
1851
1852        {
1853            // Add the non-existent iface
1854            let on_iface_added_fut = phy_manager.on_iface_added(1);
1855            let mut on_iface_added_fut = pin!(on_iface_added_fut);
1856            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
1857
1858            send_query_iface_response(&mut exec, &mut test_values.monitor_stream, None);
1859
1860            // Wait for the PhyManager to finish processing the received iface information
1861            assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
1862        }
1863
1864        // Expect that the PhyContainer associated with the fake PHY has been updated with the
1865        // fake client
1866        assert!(phy_manager.phys.is_empty());
1867    }
1868
1869    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceRemoved event
1870    /// for an iface that has been accounted for by the PhyManager.  The PhyManager should remove
1871    /// the iface ID from the PHY's list of client ifaces.
1872    #[run_singlethreaded(test)]
1873    async fn test_on_iface_removed() {
1874        let test_values = test_setup();
1875        let mut phy_manager = PhyManager::new(
1876            test_values.monitor_proxy,
1877            recovery::lookup_recovery_profile(""),
1878            false,
1879            test_values.node,
1880            test_values.telemetry_sender,
1881            test_values.recovery_sender,
1882        );
1883
1884        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1885        // iface is added.
1886        let fake_phy_id = 1;
1887        let fake_mac_roles = vec![];
1888
1889        // Inject the fake PHY information
1890        let mut phy_container = PhyContainer::new(fake_mac_roles);
1891        let fake_iface_id = 1;
1892        let _ = phy_container.client_ifaces.insert(fake_iface_id);
1893
1894        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1895
1896        phy_manager.on_iface_removed(fake_iface_id);
1897
1898        // Expect that the iface ID has been removed from the PhyContainer
1899        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1900        assert!(phy_container.client_ifaces.is_empty());
1901    }
1902
1903    /// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceRemoved event
1904    /// for an iface that has not been accounted for.  The PhyManager should simply ignore the
1905    /// request and leave its list of client iface IDs unchanged.
1906    #[run_singlethreaded(test)]
1907    async fn remove_missing_iface() {
1908        let test_values = test_setup();
1909        let mut phy_manager = PhyManager::new(
1910            test_values.monitor_proxy,
1911            recovery::lookup_recovery_profile(""),
1912            false,
1913            test_values.node,
1914            test_values.telemetry_sender,
1915            test_values.recovery_sender,
1916        );
1917
1918        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1919        // iface is added.
1920        let fake_phy_id = 1;
1921        let fake_mac_roles = vec![];
1922
1923        let present_iface_id = 1;
1924        let removed_iface_id = 2;
1925
1926        // Inject the fake PHY information
1927        let mut phy_container = PhyContainer::new(fake_mac_roles);
1928        let _ = phy_container.client_ifaces.insert(present_iface_id);
1929        let _ = phy_container.client_ifaces.insert(removed_iface_id);
1930        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1931        phy_manager.on_iface_removed(removed_iface_id);
1932
1933        // Expect that the iface ID has been removed from the PhyContainer
1934        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
1935        assert_eq!(phy_container.client_ifaces.len(), 1);
1936        assert!(phy_container.client_ifaces.contains(&present_iface_id));
1937    }
1938
1939    /// Tests the response of the PhyManager when a client iface is requested, but no PHYs are
1940    /// present.  The expectation is that the PhyManager returns None.
1941    #[run_singlethreaded(test)]
1942    async fn get_client_no_phys() {
1943        let test_values = test_setup();
1944        let mut phy_manager = PhyManager::new(
1945            test_values.monitor_proxy,
1946            recovery::lookup_recovery_profile(""),
1947            false,
1948            test_values.node,
1949            test_values.telemetry_sender,
1950            test_values.recovery_sender,
1951        );
1952
1953        let client = phy_manager.get_client();
1954        assert!(client.is_none());
1955    }
1956
1957    /// Tests the response of the PhyManager when a client iface is requested, a client-capable PHY
1958    /// has been discovered, but client connections have not been started.  The expectation is that
1959    /// the PhyManager returns None.
1960    #[run_singlethreaded(test)]
1961    async fn get_unconfigured_client() {
1962        let test_values = test_setup();
1963        let mut phy_manager = PhyManager::new(
1964            test_values.monitor_proxy,
1965            recovery::lookup_recovery_profile(""),
1966            false,
1967            test_values.node,
1968            test_values.telemetry_sender,
1969            test_values.recovery_sender,
1970        );
1971
1972        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
1973        // iface is added.
1974        let fake_phy_id = 1;
1975        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
1976        let phy_container = PhyContainer::new(fake_mac_roles);
1977
1978        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
1979
1980        // Retrieve the client ID
1981        let client = phy_manager.get_client();
1982        assert!(client.is_none());
1983    }
1984
1985    /// Tests the response of the PhyManager when a client iface is requested and a client iface is
1986    /// present.  The expectation is that the PhyManager should reply with the iface ID of the
1987    /// client iface.
1988    #[run_singlethreaded(test)]
1989    async fn get_configured_client() {
1990        let test_values = test_setup();
1991        let mut phy_manager = PhyManager::new(
1992            test_values.monitor_proxy,
1993            recovery::lookup_recovery_profile(""),
1994            false,
1995            test_values.node,
1996            test_values.telemetry_sender,
1997            test_values.recovery_sender,
1998        );
1999        phy_manager.client_connections_enabled = true;
2000
2001        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2002        // iface is added.
2003        let fake_phy_id = 1;
2004        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2005        let phy_container = PhyContainer::new(fake_mac_roles);
2006
2007        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2008
2009        // Insert the fake iface
2010        let fake_iface_id = 1;
2011        let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2012        let _ = phy_container.client_ifaces.insert(fake_iface_id);
2013
2014        // Retrieve the client ID
2015        let client = phy_manager.get_client();
2016        assert_eq!(client.unwrap(), fake_iface_id)
2017    }
2018
2019    /// Tests the response of the PhyManager when a client iface is requested and the only PHY
2020    /// that is present does not support client ifaces and has an AP iface present.  The
2021    /// expectation is that the PhyManager returns None.
2022    #[run_singlethreaded(test)]
2023    async fn get_client_no_compatible_phys() {
2024        let test_values = test_setup();
2025        let mut phy_manager = PhyManager::new(
2026            test_values.monitor_proxy,
2027            recovery::lookup_recovery_profile(""),
2028            false,
2029            test_values.node,
2030            test_values.telemetry_sender,
2031            test_values.recovery_sender,
2032        );
2033
2034        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2035        // iface is added.
2036        let fake_iface_id = 1;
2037        let fake_phy_id = 1;
2038        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2039        let mut phy_container = PhyContainer::new(fake_mac_roles);
2040        let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2041        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2042
2043        // Retrieve the client ID
2044        let client = phy_manager.get_client();
2045        assert!(client.is_none());
2046    }
2047
2048    /// Tests that PhyManager will not return a client interface when client connections are not
2049    /// enabled.
2050    #[fuchsia::test]
2051    fn get_client_while_stopped() {
2052        let _exec = TestExecutor::new();
2053        let test_values = test_setup();
2054
2055        // Create a new PhyManager.  On construction, client connections are disabled.
2056        let mut phy_manager = PhyManager::new(
2057            test_values.monitor_proxy,
2058            recovery::lookup_recovery_profile(""),
2059            false,
2060            test_values.node,
2061            test_values.telemetry_sender,
2062            test_values.recovery_sender,
2063        );
2064        assert!(!phy_manager.client_connections_enabled);
2065
2066        // Add a PHY with a lingering client interface.
2067        let fake_phy_id = 1;
2068        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2069        let mut phy_container = PhyContainer::new(fake_mac_roles);
2070        let _ = phy_container.client_ifaces.insert(1);
2071        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2072
2073        // Try to get a client interface.  No interface should be returned since client connections
2074        // are disabled.
2075        assert_eq!(phy_manager.get_client(), None);
2076    }
2077
2078    /// Tests the PhyManager's response to stop_client_connection when there is an existing client
2079    /// iface.  The expectation is that the client iface is destroyed and there is no remaining
2080    /// record of the iface ID in the PhyManager.
2081    #[fuchsia::test]
2082    fn destroy_all_client_ifaces() {
2083        let mut exec = TestExecutor::new();
2084        let mut test_values = test_setup();
2085        let mut phy_manager = PhyManager::new(
2086            test_values.monitor_proxy,
2087            recovery::lookup_recovery_profile(""),
2088            false,
2089            test_values.node,
2090            test_values.telemetry_sender,
2091            test_values.recovery_sender,
2092        );
2093
2094        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2095        // iface is added.
2096        let fake_iface_id = 1;
2097        let fake_phy_id = 1;
2098        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2099        let phy_container = PhyContainer::new(fake_mac_roles);
2100
2101        {
2102            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2103
2104            // Insert the fake iface
2105            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2106            let _ = phy_container.client_ifaces.insert(fake_iface_id);
2107
2108            // Stop client connections
2109            let stop_clients_future = phy_manager.destroy_all_client_ifaces();
2110            let mut stop_clients_future = pin!(stop_clients_future);
2111
2112            assert!(exec.run_until_stalled(&mut stop_clients_future).is_pending());
2113
2114            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
2115
2116            assert!(exec.run_until_stalled(&mut stop_clients_future).is_ready());
2117        }
2118
2119        // Ensure that the client interface that was added has been removed.
2120        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2121
2122        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2123        assert!(!phy_container.client_ifaces.contains(&fake_iface_id));
2124
2125        // Verify that the client_connections_enabled has been set to false.
2126        assert!(!phy_manager.client_connections_enabled);
2127
2128        // Verify that the destroyed interface ID was recorded.
2129        assert!(phy_container.destroyed_ifaces.contains(&fake_iface_id));
2130    }
2131
2132    /// Tests the PhyManager's response to destroy_all_client_ifaces when no client ifaces are
2133    /// present but an AP iface is present.  The expectation is that the AP iface is left intact.
2134    #[fuchsia::test]
2135    fn destroy_all_client_ifaces_no_clients() {
2136        let mut exec = TestExecutor::new();
2137        let test_values = test_setup();
2138        let mut phy_manager = PhyManager::new(
2139            test_values.monitor_proxy,
2140            recovery::lookup_recovery_profile(""),
2141            false,
2142            test_values.node,
2143            test_values.telemetry_sender,
2144            test_values.recovery_sender,
2145        );
2146
2147        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2148        // iface is added.
2149        let fake_iface_id = 1;
2150        let fake_phy_id = 1;
2151        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2152        let phy_container = PhyContainer::new(fake_mac_roles);
2153
2154        // Insert the fake AP iface and then stop clients
2155        {
2156            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2157
2158            // Insert the fake AP iface
2159            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2160            let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2161
2162            // Stop client connections
2163            let stop_clients_future = phy_manager.destroy_all_client_ifaces();
2164            let mut stop_clients_future = pin!(stop_clients_future);
2165
2166            assert!(exec.run_until_stalled(&mut stop_clients_future).is_ready());
2167        }
2168
2169        // Ensure that the fake PHY and AP interface are still present.
2170        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2171
2172        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2173        assert!(phy_container.ap_ifaces.contains(&fake_iface_id));
2174    }
2175
2176    /// This test validates the behavior when stopping client connections fails.
2177    #[fuchsia::test]
2178    fn destroy_all_client_ifaces_fails() {
2179        let mut exec = TestExecutor::new();
2180        let test_values = test_setup();
2181        let mut phy_manager = PhyManager::new(
2182            test_values.monitor_proxy,
2183            recovery::lookup_recovery_profile(""),
2184            false,
2185            test_values.node,
2186            test_values.telemetry_sender,
2187            test_values.recovery_sender,
2188        );
2189
2190        // Drop the monitor stream so that the request to destroy the interface fails.
2191        drop(test_values.monitor_stream);
2192
2193        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2194        // iface is added.
2195        let fake_iface_id = 1;
2196        let fake_phy_id = 1;
2197        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2198        let mut phy_container = PhyContainer::new(fake_mac_roles);
2199
2200        // For the sake of this test, force the retention period to be indefinite to make sure
2201        // that an event is logged.
2202        phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2203
2204        {
2205            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2206
2207            // Insert the fake iface
2208            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2209            let _ = phy_container.client_ifaces.insert(fake_iface_id);
2210
2211            // Stop client connections and expect the future to fail immediately.
2212            let stop_clients_future = phy_manager.destroy_all_client_ifaces();
2213            let mut stop_clients_future = pin!(stop_clients_future);
2214            assert!(exec.run_until_stalled(&mut stop_clients_future).is_ready());
2215        }
2216
2217        // Ensure that the client interface is still present
2218        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2219
2220        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2221        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
2222        assert_eq!(phy_container.defects.events.len(), 1);
2223        assert_eq!(
2224            phy_container.defects.events[0].value,
2225            Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 1 })
2226        );
2227    }
2228
2229    /// Tests the PhyManager's response to a request for an AP when no PHYs are present.  The
2230    /// expectation is that the PhyManager will return None in this case.
2231    #[fuchsia::test]
2232    fn get_ap_no_phys() {
2233        let mut exec = TestExecutor::new();
2234        let test_values = test_setup();
2235        let mut phy_manager = PhyManager::new(
2236            test_values.monitor_proxy,
2237            recovery::lookup_recovery_profile(""),
2238            false,
2239            test_values.node,
2240            test_values.telemetry_sender,
2241            test_values.recovery_sender,
2242        );
2243
2244        let get_ap_future = phy_manager.create_or_get_ap_iface();
2245
2246        let mut get_ap_future = pin!(get_ap_future);
2247        assert_matches!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(Ok(None)));
2248    }
2249
2250    /// Tests the PhyManager's response when the PhyManager holds a PHY that can have an AP iface
2251    /// but the AP iface has not been created yet.  The expectation is that the PhyManager creates
2252    /// a new AP iface and returns its ID to the caller.
2253    #[fuchsia::test]
2254    fn get_unconfigured_ap() {
2255        let mut exec = TestExecutor::new();
2256        let mut test_values = test_setup();
2257        let mut phy_manager = PhyManager::new(
2258            test_values.monitor_proxy,
2259            recovery::lookup_recovery_profile(""),
2260            false,
2261            test_values.node,
2262            test_values.telemetry_sender,
2263            test_values.recovery_sender,
2264        );
2265
2266        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2267        // iface is added.
2268        let fake_phy_id = 1;
2269        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2270        let phy_container = PhyContainer::new(fake_mac_roles.clone());
2271
2272        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2273
2274        // Retrieve the AP interface ID
2275        let fake_iface_id = 1;
2276        {
2277            let get_ap_future = phy_manager.create_or_get_ap_iface();
2278
2279            let mut get_ap_future = pin!(get_ap_future);
2280            assert!(exec.run_until_stalled(&mut get_ap_future).is_pending());
2281
2282            send_create_iface_response(
2283                &mut exec,
2284                &mut test_values.monitor_stream,
2285                Some(fake_iface_id),
2286            );
2287            assert_matches!(
2288                exec.run_until_stalled(&mut get_ap_future),
2289                Poll::Ready(Ok(Some(iface_id))) => assert_eq!(iface_id, fake_iface_id)
2290            );
2291        }
2292
2293        assert!(phy_manager.phys[&fake_phy_id].ap_ifaces.contains(&fake_iface_id));
2294    }
2295
2296    /// Tests the case where an AP interface is requested but interface creation fails.
2297    #[fuchsia::test]
2298    fn get_ap_iface_creation_fails() {
2299        let mut exec = TestExecutor::new();
2300        let test_values = test_setup();
2301        let mut phy_manager = PhyManager::new(
2302            test_values.monitor_proxy,
2303            recovery::lookup_recovery_profile(""),
2304            false,
2305            test_values.node,
2306            test_values.telemetry_sender,
2307            test_values.recovery_sender,
2308        );
2309
2310        // Drop the monitor stream so that the request to destroy the interface fails.
2311        drop(test_values.monitor_stream);
2312
2313        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2314        // iface is added.
2315        let fake_phy_id = 1;
2316        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2317        let mut phy_container = PhyContainer::new(fake_mac_roles.clone());
2318
2319        // For the sake of this test, force the retention period to be indefinite to make sure
2320        // that an event is logged.
2321        phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2322
2323        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2324
2325        {
2326            let get_ap_future = phy_manager.create_or_get_ap_iface();
2327
2328            let mut get_ap_future = pin!(get_ap_future);
2329            assert!(exec.run_until_stalled(&mut get_ap_future).is_ready());
2330        }
2331
2332        assert!(phy_manager.phys[&fake_phy_id].ap_ifaces.is_empty());
2333        assert_eq!(phy_manager.phys[&fake_phy_id].defects.events.len(), 1);
2334        assert_eq!(
2335            phy_manager.phys[&fake_phy_id].defects.events[0].value,
2336            Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 1 })
2337        );
2338    }
2339
2340    /// Tests the PhyManager's response to a create_or_get_ap_iface call when there is a PHY with an AP iface
2341    /// that has already been created.  The expectation is that the PhyManager should return the
2342    /// iface ID of the existing AP iface.
2343    #[fuchsia::test]
2344    fn get_configured_ap() {
2345        let mut exec = TestExecutor::new();
2346        let test_values = test_setup();
2347        let mut phy_manager = PhyManager::new(
2348            test_values.monitor_proxy,
2349            recovery::lookup_recovery_profile(""),
2350            false,
2351            test_values.node,
2352            test_values.telemetry_sender,
2353            test_values.recovery_sender,
2354        );
2355
2356        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2357        // iface is added.
2358        let fake_phy_id = 1;
2359        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2360        let phy_container = PhyContainer::new(fake_mac_roles);
2361
2362        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2363
2364        // Insert the fake iface
2365        let fake_iface_id = 1;
2366        let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2367        let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2368
2369        // Retrieve the AP iface ID
2370        let get_ap_future = phy_manager.create_or_get_ap_iface();
2371        let mut get_ap_future = pin!(get_ap_future);
2372        assert_matches!(
2373            exec.run_until_stalled(&mut get_ap_future),
2374            Poll::Ready(Ok(Some(iface_id))) => assert_eq!(iface_id, fake_iface_id)
2375        );
2376    }
2377
2378    /// This test attempts to get an AP iface from a PhyManager that has a PHY that can only have
2379    /// a client interface.  The PhyManager should return None.
2380    #[fuchsia::test]
2381    fn get_ap_no_compatible_phys() {
2382        let mut exec = TestExecutor::new();
2383        let test_values = test_setup();
2384        let mut phy_manager = PhyManager::new(
2385            test_values.monitor_proxy,
2386            recovery::lookup_recovery_profile(""),
2387            false,
2388            test_values.node,
2389            test_values.telemetry_sender,
2390            test_values.recovery_sender,
2391        );
2392
2393        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2394        // iface is added.
2395        let fake_phy_id = 1;
2396        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2397        let phy_container = PhyContainer::new(fake_mac_roles);
2398
2399        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2400
2401        // Retrieve the client ID
2402        let get_ap_future = phy_manager.create_or_get_ap_iface();
2403        let mut get_ap_future = pin!(get_ap_future);
2404        assert_matches!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(Ok(None)));
2405    }
2406
2407    /// This test stops a valid AP iface on a PhyManager.  The expectation is that the PhyManager
2408    /// should retain the record of the PHY, but the AP iface ID should be removed.
2409    #[fuchsia::test]
2410    fn stop_valid_ap_iface() {
2411        let mut exec = TestExecutor::new();
2412        let mut test_values = test_setup();
2413        let mut phy_manager = PhyManager::new(
2414            test_values.monitor_proxy,
2415            recovery::lookup_recovery_profile(""),
2416            false,
2417            test_values.node,
2418            test_values.telemetry_sender,
2419            test_values.recovery_sender,
2420        );
2421
2422        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2423        // iface is added.
2424        let fake_iface_id = 1;
2425        let fake_phy_id = 1;
2426        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2427
2428        {
2429            let phy_container = PhyContainer::new(fake_mac_roles.clone());
2430
2431            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2432
2433            // Insert the fake iface
2434            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2435            let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2436
2437            // Remove the AP iface ID
2438            let destroy_ap_iface_future = phy_manager.destroy_ap_iface(fake_iface_id);
2439            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2440            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
2441            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
2442
2443            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
2444        }
2445
2446        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2447
2448        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2449        assert!(!phy_container.ap_ifaces.contains(&fake_iface_id));
2450        assert!(phy_container.defects.events.is_empty());
2451        assert!(phy_container.destroyed_ifaces.contains(&fake_iface_id));
2452    }
2453
2454    /// This test attempts to stop an invalid AP iface ID.  The expectation is that a valid iface
2455    /// ID is unaffected.
2456    #[fuchsia::test]
2457    fn stop_invalid_ap_iface() {
2458        let mut exec = TestExecutor::new();
2459        let test_values = test_setup();
2460        let mut phy_manager = PhyManager::new(
2461            test_values.monitor_proxy,
2462            recovery::lookup_recovery_profile(""),
2463            false,
2464            test_values.node,
2465            test_values.telemetry_sender,
2466            test_values.recovery_sender,
2467        );
2468
2469        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2470        // iface is added.
2471        let fake_iface_id = 1;
2472        let fake_phy_id = 1;
2473        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2474
2475        {
2476            let phy_container = PhyContainer::new(fake_mac_roles);
2477
2478            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2479
2480            // Insert the fake iface
2481            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2482            let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2483
2484            // Remove a non-existent AP iface ID
2485            let destroy_ap_iface_future = phy_manager.destroy_ap_iface(2);
2486            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2487            assert_matches!(
2488                exec.run_until_stalled(&mut destroy_ap_iface_future),
2489                Poll::Ready(Ok(()))
2490            );
2491        }
2492
2493        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2494
2495        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2496        assert!(phy_container.ap_ifaces.contains(&fake_iface_id));
2497        assert!(phy_container.defects.events.is_empty());
2498    }
2499
2500    /// This test fails to stop a valid AP iface on a PhyManager.  The expectation is that the
2501    /// PhyManager should retain the AP interface and log a defect.
2502    #[fuchsia::test]
2503    fn stop_ap_iface_fails() {
2504        let mut exec = TestExecutor::new();
2505        let test_values = test_setup();
2506        let mut phy_manager = PhyManager::new(
2507            test_values.monitor_proxy,
2508            recovery::lookup_recovery_profile(""),
2509            false,
2510            test_values.node,
2511            test_values.telemetry_sender,
2512            test_values.recovery_sender,
2513        );
2514
2515        // Drop the monitor stream so that the request to destroy the interface fails.
2516        drop(test_values.monitor_stream);
2517
2518        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2519        // iface is added.
2520        let fake_iface_id = 1;
2521        let fake_phy_id = 1;
2522        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2523
2524        {
2525            let mut phy_container = PhyContainer::new(fake_mac_roles.clone());
2526
2527            // For the sake of this test, force the retention period to be indefinite to make sure
2528            // that an event is logged.
2529            phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2530
2531            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2532
2533            // Insert the fake iface
2534            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2535            let _ = phy_container.ap_ifaces.insert(fake_iface_id);
2536
2537            // Remove the AP iface ID
2538            let destroy_ap_iface_future = phy_manager.destroy_ap_iface(fake_iface_id);
2539            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2540            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
2541        }
2542
2543        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2544
2545        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2546        assert!(phy_container.ap_ifaces.contains(&fake_iface_id));
2547        assert_eq!(phy_container.defects.events.len(), 1);
2548        assert_eq!(
2549            phy_container.defects.events[0].value,
2550            Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 1 })
2551        );
2552    }
2553
2554    /// This test attempts to stop an invalid AP iface ID.  The expectation is that a valid iface
2555    /// This test creates two AP ifaces for a PHY that supports AP ifaces.  destroy_all_ap_ifaces is then
2556    /// called on the PhyManager.  The expectation is that both AP ifaces should be destroyed and
2557    /// the records of the iface IDs should be removed from the PhyContainer.
2558    #[fuchsia::test]
2559    fn stop_all_ap_ifaces() {
2560        let mut exec = TestExecutor::new();
2561        let mut test_values = test_setup();
2562        let mut phy_manager = PhyManager::new(
2563            test_values.monitor_proxy,
2564            recovery::lookup_recovery_profile(""),
2565            false,
2566            test_values.node,
2567            test_values.telemetry_sender,
2568            test_values.recovery_sender,
2569        );
2570
2571        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2572        // ifaces are added.
2573        let fake_phy_id = 1;
2574        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2575
2576        {
2577            let phy_container = PhyContainer::new(fake_mac_roles.clone());
2578
2579            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2580
2581            // Insert the fake iface
2582            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2583            let _ = phy_container.ap_ifaces.insert(0);
2584            let _ = phy_container.ap_ifaces.insert(1);
2585
2586            // Expect two interface destruction requests
2587            let destroy_ap_iface_future = phy_manager.destroy_all_ap_ifaces();
2588            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2589
2590            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
2591            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
2592
2593            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
2594            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
2595
2596            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
2597        }
2598
2599        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2600
2601        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2602        assert!(phy_container.ap_ifaces.is_empty());
2603        assert!(phy_container.destroyed_ifaces.contains(&0));
2604        assert!(phy_container.destroyed_ifaces.contains(&1));
2605        assert!(phy_container.defects.events.is_empty());
2606    }
2607
2608    /// This test calls destroy_all_ap_ifaces on a PhyManager that only has a client iface.  The expectation
2609    /// is that no interfaces should be destroyed and the client iface ID should remain in the
2610    /// PhyManager
2611    #[fuchsia::test]
2612    fn stop_all_ap_ifaces_with_client() {
2613        let mut exec = TestExecutor::new();
2614        let test_values = test_setup();
2615        let mut phy_manager = PhyManager::new(
2616            test_values.monitor_proxy,
2617            recovery::lookup_recovery_profile(""),
2618            false,
2619            test_values.node,
2620            test_values.telemetry_sender,
2621            test_values.recovery_sender,
2622        );
2623
2624        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2625        // iface is added.
2626        let fake_iface_id = 1;
2627        let fake_phy_id = 1;
2628        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2629
2630        {
2631            let phy_container = PhyContainer::new(fake_mac_roles);
2632
2633            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2634
2635            // Insert the fake iface
2636            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2637            let _ = phy_container.client_ifaces.insert(fake_iface_id);
2638
2639            // Stop all AP ifaces
2640            let destroy_ap_iface_future = phy_manager.destroy_all_ap_ifaces();
2641            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2642            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
2643        }
2644
2645        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2646
2647        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2648        assert!(phy_container.client_ifaces.contains(&fake_iface_id));
2649        assert!(phy_container.defects.events.is_empty());
2650    }
2651
2652    /// This test validates the behavior when destroying all AP interfaces fails.
2653    #[fuchsia::test]
2654    fn stop_all_ap_ifaces_fails() {
2655        let mut exec = TestExecutor::new();
2656        let test_values = test_setup();
2657        let mut phy_manager = PhyManager::new(
2658            test_values.monitor_proxy,
2659            recovery::lookup_recovery_profile(""),
2660            false,
2661            test_values.node,
2662            test_values.telemetry_sender,
2663            test_values.recovery_sender,
2664        );
2665
2666        // Drop the monitor stream so that the request to destroy the interface fails.
2667        drop(test_values.monitor_stream);
2668
2669        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2670        // ifaces are added.
2671        let fake_phy_id = 1;
2672        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2673
2674        {
2675            let mut phy_container = PhyContainer::new(fake_mac_roles.clone());
2676
2677            // For the sake of this test, force the retention period to be indefinite to make sure
2678            // that an event is logged.
2679            phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2680
2681            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2682
2683            // Insert the fake iface
2684            let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2685            let _ = phy_container.ap_ifaces.insert(0);
2686            let _ = phy_container.ap_ifaces.insert(1);
2687
2688            // Expect interface destruction to finish immediately.
2689            let destroy_ap_iface_future = phy_manager.destroy_all_ap_ifaces();
2690            let mut destroy_ap_iface_future = pin!(destroy_ap_iface_future);
2691            assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
2692        }
2693
2694        assert!(phy_manager.phys.contains_key(&fake_phy_id));
2695
2696        let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
2697        assert_eq!(phy_container.ap_ifaces.len(), 2);
2698        assert_eq!(phy_container.defects.events.len(), 2);
2699        assert_eq!(
2700            phy_container.defects.events[0].value,
2701            Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 1 })
2702        );
2703        assert_eq!(
2704            phy_container.defects.events[1].value,
2705            Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 1 })
2706        );
2707    }
2708
2709    /// Verifies that setting a suggested AP MAC address results in that MAC address being used as
2710    /// a part of the request to create an AP interface.  Ensures that this does not affect client
2711    /// interface requests.
2712    #[fuchsia::test]
2713    fn test_suggest_ap_mac() {
2714        let mut exec = TestExecutor::new();
2715        let mut test_values = test_setup();
2716        let mut phy_manager = PhyManager::new(
2717            test_values.monitor_proxy,
2718            recovery::lookup_recovery_profile(""),
2719            false,
2720            test_values.node,
2721            test_values.telemetry_sender,
2722            test_values.recovery_sender,
2723        );
2724
2725        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2726        // iface is added.
2727        let fake_iface_id = 1;
2728        let fake_phy_id = 1;
2729        let fake_mac_roles = vec![fidl_common::WlanMacRole::Ap];
2730        let phy_container = PhyContainer::new(fake_mac_roles.clone());
2731
2732        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2733
2734        // Insert the fake iface
2735        let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
2736        let _ = phy_container.client_ifaces.insert(fake_iface_id);
2737
2738        // Suggest an AP MAC
2739        let mac: MacAddr = [1, 2, 3, 4, 5, 6].into();
2740        phy_manager.suggest_ap_mac(mac);
2741
2742        let get_ap_future = phy_manager.create_or_get_ap_iface();
2743        let mut get_ap_future = pin!(get_ap_future);
2744        assert_matches!(exec.run_until_stalled(&mut get_ap_future), Poll::Pending);
2745
2746        // Verify that the suggested MAC is included in the request
2747        assert_matches!(
2748            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
2749            Poll::Ready(Some(Ok(
2750                fidl_service::DeviceMonitorRequest::CreateIface {
2751                    payload,
2752                    responder,
2753                }
2754            ))) => {
2755                let requested_mac: MacAddr = payload.sta_address.unwrap().into();
2756                assert_eq!(requested_mac, mac);
2757                let response = fidl_service::DeviceMonitorCreateIfaceResponse {
2758                    iface_id: Some(fake_iface_id),
2759                    ..Default::default()
2760                };
2761                responder.send(Ok(&response)).expect("sending fake iface id");
2762            }
2763        );
2764        assert_matches!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(_));
2765    }
2766
2767    #[fuchsia::test]
2768    fn test_suggested_mac_does_not_apply_to_client() {
2769        let mut exec = TestExecutor::new();
2770        let mut test_values = test_setup();
2771        let mut phy_manager = PhyManager::new(
2772            test_values.monitor_proxy,
2773            recovery::lookup_recovery_profile(""),
2774            false,
2775            test_values.node,
2776            test_values.telemetry_sender,
2777            test_values.recovery_sender,
2778        );
2779
2780        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2781        // iface is added.
2782        let fake_iface_id = 1;
2783        let fake_phy_id = 1;
2784        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2785        let phy_container = PhyContainer::new(fake_mac_roles.clone());
2786
2787        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2788
2789        // Suggest an AP MAC
2790        let mac: MacAddr = [1, 2, 3, 4, 5, 6].into();
2791        phy_manager.suggest_ap_mac(mac);
2792
2793        // Start client connections so that an IfaceRequest is issued for the client.
2794        let start_client_future =
2795            phy_manager.create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
2796        let mut start_client_future = pin!(start_client_future);
2797        assert_matches!(exec.run_until_stalled(&mut start_client_future), Poll::Pending);
2798
2799        // Verify that the suggested MAC is NOT included in the request
2800        assert_matches!(
2801            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
2802            Poll::Ready(Some(Ok(
2803                fidl_service::DeviceMonitorRequest::CreateIface {
2804                    payload,
2805                    responder,
2806                }
2807            ))) => {
2808                assert_eq!(payload.sta_address, Some(ieee80211::NULL_ADDR.to_array()));
2809                let response = fidl_service::DeviceMonitorCreateIfaceResponse {
2810                    iface_id: Some(fake_iface_id),
2811                    ..Default::default()
2812                };
2813                responder.send(Ok(&response)).expect("sending fake iface id");
2814            }
2815        );
2816        assert_matches!(exec.run_until_stalled(&mut start_client_future), Poll::Ready(_));
2817    }
2818
2819    /// Tests the case where creating a client interface fails while starting client connections.
2820    #[fuchsia::test]
2821    fn test_iface_creation_fails_during_start_client_connections() {
2822        let mut exec = TestExecutor::new();
2823        let test_values = test_setup();
2824        let mut phy_manager = PhyManager::new(
2825            test_values.monitor_proxy,
2826            recovery::lookup_recovery_profile(""),
2827            false,
2828            test_values.node,
2829            test_values.telemetry_sender,
2830            test_values.recovery_sender,
2831        );
2832
2833        // Drop the monitor stream so that the request to create the interface fails.
2834        drop(test_values.monitor_stream);
2835
2836        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2837        // iface is added.
2838        let fake_phy_id = 1;
2839        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2840        let mut phy_container = PhyContainer::new(fake_mac_roles.clone());
2841
2842        // For the sake of this test, force the retention period to be indefinite to make sure
2843        // that an event is logged.
2844        phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2845
2846        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2847
2848        {
2849            // Start client connections so that an IfaceRequest is issued for the client.
2850            let start_client_future = phy_manager
2851                .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
2852            let mut start_client_future = pin!(start_client_future);
2853            assert!(exec.run_until_stalled(&mut start_client_future).is_ready());
2854        }
2855
2856        // Verify that a defect has been logged.
2857        assert_eq!(phy_manager.phys[&fake_phy_id].defects.events.len(), 1);
2858        assert_eq!(
2859            phy_manager.phys[&fake_phy_id].defects.events[0].value,
2860            Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 1 })
2861        );
2862    }
2863
2864    #[fuchsia::test]
2865    fn test_all_iface_creation_failures_retained_across_multiple_phys() {
2866        let mut exec = TestExecutor::new();
2867        let test_values = test_setup();
2868        let mut phy_manager = PhyManager::new(
2869            test_values.monitor_proxy,
2870            recovery::lookup_recovery_profile(""),
2871            false,
2872            test_values.node,
2873            test_values.telemetry_sender,
2874            test_values.recovery_sender,
2875        );
2876
2877        // Drop the monitor stream so that the request to create the interface fails.
2878        drop(test_values.monitor_stream);
2879
2880        // Create an initial PhyContainer to be inserted into the test PhyManager before the fake
2881        // iface is added.
2882        for fake_phy_id in 0..2 {
2883            let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
2884            let mut phy_container = PhyContainer::new(fake_mac_roles.clone());
2885
2886            // For the sake of this test, force the retention period to be indefinite to make sure
2887            // that an event is logged.
2888            phy_container.defects = EventHistory::<Defect>::new(u32::MAX);
2889
2890            let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
2891        }
2892
2893        let start_client_future =
2894            phy_manager.create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
2895        let mut start_client_future = pin!(start_client_future);
2896        assert_matches!(exec.run_until_stalled(&mut start_client_future),
2897            Poll::Ready(iface_ids) => {
2898                assert_eq!(iface_ids.len(), 2);
2899                assert_eq!(iface_ids[&0], Err(PhyManagerError::IfaceCreateFailure));
2900                assert_eq!(iface_ids[&1], Err(PhyManagerError::IfaceCreateFailure));
2901            }
2902        );
2903    }
2904
2905    /// Tests get_phy_ids() when no PHYs are present. The expectation is that the PhyManager will
2906    /// Tests get_phy_ids() when no PHYs are present. The expectation is that the PhyManager will
2907    /// return an empty `Vec` in this case.
2908    #[run_singlethreaded(test)]
2909    async fn get_phy_ids_no_phys() {
2910        let test_values = test_setup();
2911        let phy_manager = PhyManager::new(
2912            test_values.monitor_proxy,
2913            recovery::lookup_recovery_profile(""),
2914            false,
2915            test_values.node,
2916            test_values.telemetry_sender,
2917            test_values.recovery_sender,
2918        );
2919        assert_eq!(phy_manager.get_phy_ids(), Vec::<u16>::new());
2920    }
2921
2922    /// Tests get_phy_ids() when a single PHY is present. The expectation is that the PhyManager will
2923    /// return a single element `Vec`, with the appropriate ID.
2924    #[fuchsia::test]
2925    fn get_phy_ids_single_phy() {
2926        let mut exec = TestExecutor::new();
2927        let mut test_values = test_setup();
2928        let mut phy_manager = PhyManager::new(
2929            test_values.monitor_proxy,
2930            recovery::lookup_recovery_profile(""),
2931            false,
2932            test_values.node,
2933            test_values.telemetry_sender,
2934            test_values.recovery_sender,
2935        );
2936
2937        {
2938            let add_phy_fut = phy_manager.add_phy(1);
2939            let mut add_phy_fut = pin!(add_phy_fut);
2940            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
2941            send_get_supported_mac_roles_response(
2942                &mut exec,
2943                &mut test_values.monitor_stream,
2944                Ok(&[]),
2945            );
2946            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
2947        }
2948
2949        assert_eq!(phy_manager.get_phy_ids(), vec![1]);
2950    }
2951
2952    /// Tests get_phy_ids() when two PHYs are present. The expectation is that the PhyManager will
2953    /// return a two-element `Vec`, containing the appropriate IDs. Ordering is not guaranteed.
2954    #[fuchsia::test]
2955    fn get_phy_ids_two_phys() {
2956        let mut exec = TestExecutor::new();
2957        let mut test_values = test_setup();
2958        let mut phy_manager = PhyManager::new(
2959            test_values.monitor_proxy,
2960            recovery::lookup_recovery_profile(""),
2961            false,
2962            test_values.node,
2963            test_values.telemetry_sender,
2964            test_values.recovery_sender,
2965        );
2966
2967        {
2968            let add_phy_fut = phy_manager.add_phy(1);
2969            let mut add_phy_fut = pin!(add_phy_fut);
2970            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
2971            send_get_supported_mac_roles_response(
2972                &mut exec,
2973                &mut test_values.monitor_stream,
2974                Ok(&[]),
2975            );
2976            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
2977        }
2978
2979        {
2980            let add_phy_fut = phy_manager.add_phy(2);
2981            let mut add_phy_fut = pin!(add_phy_fut);
2982            assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
2983            send_get_supported_mac_roles_response(
2984                &mut exec,
2985                &mut test_values.monitor_stream,
2986                Ok(&[]),
2987            );
2988            assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
2989        }
2990
2991        let phy_ids = phy_manager.get_phy_ids();
2992        assert!(phy_ids.contains(&1), "expected phy_ids to contain `1`, but phy_ids={phy_ids:?}");
2993        assert!(phy_ids.contains(&2), "expected phy_ids to contain `2`, but phy_ids={phy_ids:?}");
2994    }
2995
2996    /// Tests log_phy_add_failure() to ensure the appropriate inspect count is incremented by 1.
2997    #[run_singlethreaded(test)]
2998    async fn log_phy_add_failure() {
2999        let test_values = test_setup();
3000        let mut phy_manager = PhyManager::new(
3001            test_values.monitor_proxy,
3002            recovery::lookup_recovery_profile(""),
3003            false,
3004            test_values.node,
3005            test_values.telemetry_sender,
3006            test_values.recovery_sender,
3007        );
3008
3009        assert_data_tree!(test_values.inspector, root: {
3010            phy_manager: {
3011                phy_add_fail_count: 0u64,
3012            },
3013        });
3014
3015        phy_manager.log_phy_add_failure();
3016        assert_data_tree!(test_values.inspector, root: {
3017            phy_manager: {
3018                phy_add_fail_count: 1u64,
3019            },
3020        });
3021    }
3022
3023    /// Tests the initialization of the country code and the ability of the PhyManager to cache a
3024    /// country code update.
3025    #[fuchsia::test]
3026    fn test_set_country_code() {
3027        let mut exec = TestExecutor::new();
3028        let mut test_values = test_setup();
3029        let mut phy_manager = PhyManager::new(
3030            test_values.monitor_proxy,
3031            recovery::lookup_recovery_profile(""),
3032            false,
3033            test_values.node,
3034            test_values.telemetry_sender,
3035            test_values.recovery_sender,
3036        );
3037
3038        // Insert a couple fake PHYs.
3039        let _ = phy_manager.phys.insert(
3040            0,
3041            PhyContainer {
3042                supported_mac_roles: HashSet::new(),
3043                client_ifaces: HashSet::new(),
3044                ap_ifaces: HashSet::new(),
3045                destroyed_ifaces: HashSet::new(),
3046                defects: EventHistory::new(DEFECT_RETENTION_SECONDS),
3047                recoveries: EventHistory::new(DEFECT_RETENTION_SECONDS),
3048            },
3049        );
3050        let _ = phy_manager.phys.insert(
3051            1,
3052            PhyContainer {
3053                supported_mac_roles: HashSet::new(),
3054                client_ifaces: HashSet::new(),
3055                ap_ifaces: HashSet::new(),
3056                destroyed_ifaces: HashSet::new(),
3057                defects: EventHistory::new(DEFECT_RETENTION_SECONDS),
3058                recoveries: EventHistory::new(DEFECT_RETENTION_SECONDS),
3059            },
3060        );
3061
3062        // Initially the country code should be unset.
3063        assert!(phy_manager.saved_country_code.is_none());
3064
3065        // Apply a country code and ensure that it is propagated to the device service.
3066        {
3067            let set_country_fut = phy_manager.set_country_code(Some("US".parse().unwrap()));
3068            let mut set_country_fut = pin!(set_country_fut);
3069
3070            // Ensure that both PHYs have their country codes set.
3071            for _ in 0..2 {
3072                assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
3073                assert_matches!(
3074                    exec.run_until_stalled(&mut test_values.monitor_stream.next()),
3075                    Poll::Ready(Some(Ok(
3076                        fidl_service::DeviceMonitorRequest::SetCountry {
3077                            req: fidl_service::SetCountryRequest {
3078                                phy_id: _,
3079                                alpha2: [b'U', b'S'],
3080                            },
3081                            responder,
3082                        }
3083                    ))) => {
3084                        responder.send(ZX_OK).expect("sending fake set country response");
3085                    }
3086                );
3087            }
3088
3089            assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
3090        }
3091        assert_eq!(phy_manager.saved_country_code, Some("US".parse().unwrap()));
3092
3093        // Unset the country code and ensure that the clear country code message is sent to the
3094        // device service.
3095        {
3096            let set_country_fut = phy_manager.set_country_code(None);
3097            let mut set_country_fut = pin!(set_country_fut);
3098
3099            // Ensure that both PHYs have their country codes cleared.
3100            for _ in 0..2 {
3101                assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
3102                assert_matches!(
3103                    exec.run_until_stalled(&mut test_values.monitor_stream.next()),
3104                    Poll::Ready(Some(Ok(
3105                        fidl_service::DeviceMonitorRequest::ClearCountry {
3106                            req: fidl_service::ClearCountryRequest {
3107                                phy_id: _,
3108                            },
3109                            responder,
3110                        }
3111                    ))) => {
3112                        responder.send(ZX_OK).expect("sending fake clear country response");
3113                    }
3114                );
3115            }
3116
3117            assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
3118        }
3119        assert_eq!(phy_manager.saved_country_code, None);
3120    }
3121
3122    // Tests the case where setting the country code is unsuccessful.
3123    #[fuchsia::test]
3124    fn test_setting_country_code_fails() {
3125        let mut exec = TestExecutor::new();
3126        let mut test_values = test_setup();
3127        let mut phy_manager = PhyManager::new(
3128            test_values.monitor_proxy,
3129            recovery::lookup_recovery_profile(""),
3130            false,
3131            test_values.node,
3132            test_values.telemetry_sender,
3133            test_values.recovery_sender,
3134        );
3135
3136        // Insert a fake PHY.
3137        let _ = phy_manager.phys.insert(
3138            0,
3139            PhyContainer {
3140                supported_mac_roles: HashSet::new(),
3141                client_ifaces: HashSet::new(),
3142                ap_ifaces: HashSet::new(),
3143                destroyed_ifaces: HashSet::new(),
3144                defects: EventHistory::new(DEFECT_RETENTION_SECONDS),
3145                recoveries: EventHistory::new(DEFECT_RETENTION_SECONDS),
3146            },
3147        );
3148
3149        // Initially the country code should be unset.
3150        assert!(phy_manager.saved_country_code.is_none());
3151
3152        // Apply a country code and ensure that it is propagated to the device service.
3153        {
3154            let set_country_fut = phy_manager.set_country_code(Some("US".parse().unwrap()));
3155            let mut set_country_fut = pin!(set_country_fut);
3156
3157            assert_matches!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
3158            assert_matches!(
3159                exec.run_until_stalled(&mut test_values.monitor_stream.next()),
3160                Poll::Ready(Some(Ok(
3161                    fidl_service::DeviceMonitorRequest::SetCountry {
3162                        req: fidl_service::SetCountryRequest {
3163                            phy_id: 0,
3164                            alpha2: [b'U', b'S'],
3165                        },
3166                        responder,
3167                    }
3168                ))) => {
3169                    // Send back a failure.
3170                    responder
3171                        .send(zx::sys::ZX_ERR_NOT_SUPPORTED)
3172                        .expect("sending fake set country response");
3173                }
3174            );
3175
3176            assert_matches!(
3177                exec.run_until_stalled(&mut set_country_fut),
3178                Poll::Ready(Err(PhyManagerError::PhySetCountryFailure))
3179            );
3180        }
3181        assert_eq!(phy_manager.saved_country_code, Some("US".parse().unwrap()));
3182    }
3183
3184    /// Tests the case where multiple client interfaces need to be recovered.
3185    #[fuchsia::test]
3186    fn test_recover_client_interfaces_succeeds() {
3187        let mut exec = TestExecutor::new();
3188        let mut test_values = test_setup();
3189        let mut phy_manager = PhyManager::new(
3190            test_values.monitor_proxy,
3191            recovery::lookup_recovery_profile(""),
3192            false,
3193            test_values.node,
3194            test_values.telemetry_sender,
3195            test_values.recovery_sender,
3196        );
3197        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
3198
3199        // Make it look like client connections have been enabled.
3200        phy_manager.client_connections_enabled = true;
3201
3202        // Create four fake PHY entries.  For the sake of this test, each PHY will eventually
3203        // receive and interface ID equal to its PHY ID.
3204        for phy_id in 0..4 {
3205            let fake_mac_roles = fake_mac_roles.clone();
3206            let _ = phy_manager.phys.insert(phy_id, PhyContainer::new(fake_mac_roles.clone()));
3207
3208            // Give the 0th and 2nd PHYs have client interfaces.
3209            if phy_id.is_multiple_of(2) {
3210                let phy_container = phy_manager.phys.get_mut(&phy_id).expect("missing PHY");
3211                let _ = phy_container.client_ifaces.insert(phy_id);
3212            }
3213        }
3214
3215        // There are now two PHYs with client interfaces and two without.  This looks like two
3216        // interfaces have undergone recovery.  Run recover_client_ifaces and ensure that the two
3217        // PHYs that are missing client interfaces have interfaces created for them.
3218        {
3219            let recovery_fut =
3220                phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
3221            let mut recovery_fut = pin!(recovery_fut);
3222            assert_matches!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
3223
3224            loop {
3225                // The recovery future will only stall out when either
3226                // 1. It needs to create a client interface for a PHY that does not have one.
3227                // 2. The futures completes and has recovered all possible interfaces.
3228                match exec.run_until_stalled(&mut recovery_fut) {
3229                    Poll::Pending => {}
3230                    Poll::Ready(iface_ids) => {
3231                        if iface_ids.values().any(Result::is_err) {
3232                            panic!("recovery failed unexpectedly");
3233                        }
3234                        let iface_ids: Vec<_> =
3235                            iface_ids.into_values().flat_map(Result::unwrap).collect();
3236                        assert!(iface_ids.contains(&1));
3237                        assert!(iface_ids.contains(&3));
3238                        break;
3239                    }
3240                }
3241
3242                // Make sure that the stalled future has made a FIDL request to create a client
3243                // interface.  Send back a response assigning an interface ID equal to the PHY ID.
3244                assert_matches!(
3245                exec.run_until_stalled(&mut test_values.monitor_stream.next()),
3246                Poll::Ready(Some(Ok(
3247                    fidl_service::DeviceMonitorRequest::CreateIface {
3248                        payload,
3249                        responder,
3250                    }
3251                ))) => {
3252                    let response = fidl_service::DeviceMonitorCreateIfaceResponse {
3253                        iface_id: Some(payload.phy_id.unwrap()),
3254                        ..Default::default()
3255                    };
3256                    responder.send(Ok(&response)).expect("sending fake iface id");
3257                });
3258            }
3259        }
3260
3261        // Make sure all of the PHYs have interface IDs and that the IDs match the PHY IDs,
3262        // indicating that they were assigned correctly.
3263        for phy_id in phy_manager.phys.keys() {
3264            assert_eq!(phy_manager.phys[phy_id].client_ifaces.len(), 1);
3265            assert!(phy_manager.phys[phy_id].client_ifaces.contains(phy_id));
3266        }
3267    }
3268
3269    /// Tests the case where a client interface needs to be recovered and recovery fails.
3270    #[fuchsia::test]
3271    fn test_recover_client_interfaces_fails() {
3272        let mut exec = TestExecutor::new();
3273        let mut test_values = test_setup();
3274        let mut phy_manager = PhyManager::new(
3275            test_values.monitor_proxy,
3276            recovery::lookup_recovery_profile(""),
3277            false,
3278            test_values.node,
3279            test_values.telemetry_sender,
3280            test_values.recovery_sender,
3281        );
3282        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
3283
3284        // Make it look like client connections have been enabled.
3285        phy_manager.client_connections_enabled = true;
3286
3287        // For this test, use three PHYs (0, 1, and 2).  Let recovery fail for PHYs 0 and 2 and
3288        // succeed for PHY 1.  Verify that a create interface request is sent for each PHY and at
3289        // the end, verify that only one recovered interface is listed and that PHY 1 has been
3290        // assigned that interface.
3291        for phy_id in 0..3 {
3292            let _ = phy_manager.phys.insert(phy_id, PhyContainer::new(fake_mac_roles.clone()));
3293        }
3294
3295        // Run recovery.
3296        {
3297            let recovery_fut =
3298                phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
3299            let mut recovery_fut = pin!(recovery_fut);
3300            assert_matches!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
3301
3302            loop {
3303                match exec.run_until_stalled(&mut recovery_fut) {
3304                    Poll::Pending => {}
3305                    Poll::Ready(iface_ids) => {
3306                        assert!(iface_ids.values().any(Result::is_err));
3307                        let iface_ids: Vec<_> =
3308                            iface_ids.into_values().filter_map(Result::ok).flatten().collect();
3309                        assert_eq!(iface_ids, vec![1]);
3310                        break;
3311                    }
3312                }
3313
3314                // Make sure that the stalled future has made a FIDL request to create a client
3315                // interface.  Send back a response assigning an interface ID equal to the PHY ID.
3316                assert_matches!(
3317                    exec.run_until_stalled(&mut test_values.monitor_stream.next()),
3318                    Poll::Ready(Some(Ok(
3319                        fidl_service::DeviceMonitorRequest::CreateIface {
3320                            payload,
3321                            responder,
3322                        }
3323                    ))) => {
3324                        let iface_id = payload.phy_id.unwrap();
3325                        let response = fidl_service::DeviceMonitorCreateIfaceResponse {
3326                            iface_id: Some(iface_id),
3327                            ..Default::default()
3328                        };
3329
3330                        // As noted above, let the requests for 0 and 2 "fail" and let the request
3331                        // for PHY 1 succeed.
3332                        match payload.phy_id.unwrap() {
3333                            1 => {
3334                                responder.send(Ok(&response)).expect("sending fake iface id")
3335                            },
3336                            _ => responder.send(Err(fidl_service::DeviceMonitorError::unknown())).expect("sending fake iface id"),
3337                        };
3338                    }
3339                );
3340            }
3341        }
3342
3343        // Make sure PHYs 0 and 2 do not have interfaces and that PHY 1 does.
3344        for phy_id in phy_manager.phys.keys() {
3345            match phy_id {
3346                1 => {
3347                    assert_eq!(phy_manager.phys[phy_id].client_ifaces.len(), 1);
3348                    assert!(phy_manager.phys[phy_id].client_ifaces.contains(phy_id));
3349                }
3350                _ => assert!(phy_manager.phys[phy_id].client_ifaces.is_empty()),
3351            }
3352        }
3353    }
3354
3355    /// Tests the case where a PHY is client-capable, but client connections are disabled and a
3356    /// caller requests attempts to recover client interfaces.
3357    #[fuchsia::test]
3358    fn test_recover_client_interfaces_while_disabled() {
3359        let mut exec = TestExecutor::new();
3360        let test_values = test_setup();
3361        let mut phy_manager = PhyManager::new(
3362            test_values.monitor_proxy,
3363            recovery::lookup_recovery_profile(""),
3364            false,
3365            test_values.node,
3366            test_values.telemetry_sender,
3367            test_values.recovery_sender,
3368        );
3369
3370        // Create a fake PHY entry without client interfaces.  Note that client connections have
3371        // not been set to enabled.
3372        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
3373        let _ = phy_manager.phys.insert(0, PhyContainer::new(fake_mac_roles));
3374
3375        // Run recovery and ensure that it completes immediately and does not recover any
3376        // interfaces.
3377        {
3378            let recovery_fut =
3379                phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
3380            let mut recovery_fut = pin!(recovery_fut);
3381            assert_matches!(
3382                exec.run_until_stalled(&mut recovery_fut),
3383                Poll::Ready(recovered_ifaces) => {
3384                    assert!(recovered_ifaces.is_empty());
3385                }
3386            );
3387        }
3388
3389        // Verify that there are no client interfaces.
3390        for (_, phy_container) in phy_manager.phys {
3391            assert!(phy_container.client_ifaces.is_empty());
3392        }
3393    }
3394
3395    /// Tests the case where client connections are re-started following an unsuccessful stop
3396    /// client connections request.
3397    #[fuchsia::test]
3398    fn test_start_after_unsuccessful_stop() {
3399        let mut exec = TestExecutor::new();
3400        let test_values = test_setup();
3401        let mut phy_manager = PhyManager::new(
3402            test_values.monitor_proxy,
3403            recovery::lookup_recovery_profile(""),
3404            false,
3405            test_values.node,
3406            test_values.telemetry_sender,
3407            test_values.recovery_sender,
3408        );
3409
3410        // Verify that client connections are initially stopped.
3411        assert!(!phy_manager.client_connections_enabled);
3412
3413        // Create a PHY with a lingering client interface.
3414        let fake_phy_id = 1;
3415        let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
3416        let mut phy_container = PhyContainer::new(fake_mac_roles);
3417        // Insert the fake iface
3418        let fake_iface_id = 1;
3419        let _ = phy_container.client_ifaces.insert(fake_iface_id);
3420        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
3421
3422        // Try creating all client interfaces due to recovery and ensure that no interfaces are
3423        // returned.
3424        {
3425            let start_client_future =
3426                phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
3427            let mut start_client_future = pin!(start_client_future);
3428            assert_matches!(
3429                exec.run_until_stalled(&mut start_client_future),
3430                Poll::Ready(v) => {
3431                assert!(v.is_empty())
3432            });
3433        }
3434
3435        // Create all client interfaces with the reason set to StartClientConnections and verify
3436        // that the existing interface is returned.
3437        {
3438            let start_client_future = phy_manager
3439                .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
3440            let mut start_client_future = pin!(start_client_future);
3441            assert_matches!(
3442                exec.run_until_stalled(&mut start_client_future),
3443                Poll::Ready(iface_ids) => {
3444                    assert_eq!(iface_ids.into_values().collect::<Vec<_>>(), vec![Ok(vec![1])]);
3445                }
3446            );
3447        }
3448    }
3449
3450    /// Tests reporting of client connections status when client connections are enabled.
3451    #[fuchsia::test]
3452    fn test_client_connections_enabled_when_enabled() {
3453        let _exec = TestExecutor::new();
3454        let test_values = test_setup();
3455        let mut phy_manager = PhyManager::new(
3456            test_values.monitor_proxy,
3457            recovery::lookup_recovery_profile(""),
3458            false,
3459            test_values.node,
3460            test_values.telemetry_sender,
3461            test_values.recovery_sender,
3462        );
3463
3464        phy_manager.client_connections_enabled = true;
3465        assert!(phy_manager.client_connections_enabled());
3466    }
3467
3468    /// Tests reporting of client connections status when client connections are disabled.
3469    #[fuchsia::test]
3470    fn test_client_connections_enabled_when_disabled() {
3471        let _exec = TestExecutor::new();
3472        let test_values = test_setup();
3473        let mut phy_manager = PhyManager::new(
3474            test_values.monitor_proxy,
3475            recovery::lookup_recovery_profile(""),
3476            false,
3477            test_values.node,
3478            test_values.telemetry_sender,
3479            test_values.recovery_sender,
3480        );
3481
3482        phy_manager.client_connections_enabled = false;
3483        assert!(!phy_manager.client_connections_enabled());
3484    }
3485
3486    #[fuchsia::test]
3487    fn test_create_iface_succeeds() {
3488        let mut exec = TestExecutor::new();
3489        let mut test_values = test_setup();
3490        let mut phy_manager = PhyManager::new(
3491            test_values.monitor_proxy,
3492            recovery::lookup_recovery_profile(""),
3493            false,
3494            test_values.node,
3495            test_values.telemetry_sender,
3496            test_values.recovery_sender,
3497        );
3498
3499        // Issue a create iface request
3500        let fut = phy_manager.create_iface(0, fidl_common::WlanMacRole::Client, NULL_ADDR);
3501        let mut fut = pin!(fut);
3502
3503        // Wait for the request to stall out waiting for DeviceMonitor.
3504        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3505
3506        // Send back a positive response from DeviceMonitor.
3507        send_create_iface_response(&mut exec, &mut test_values.monitor_stream, Some(0));
3508
3509        // The future should complete.
3510        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(0)));
3511
3512        // Verify that there is nothing waiting on the telemetry receiver.
3513        assert_matches!(
3514            test_values.telemetry_receiver.try_next(),
3515            Ok(Some(TelemetryEvent::IfaceCreationResult(Ok(()))))
3516        )
3517    }
3518
3519    #[fuchsia::test]
3520    fn test_create_iface_fails() {
3521        let mut exec = TestExecutor::new();
3522        let mut test_values = test_setup();
3523        let mut phy_manager = PhyManager::new(
3524            test_values.monitor_proxy,
3525            recovery::lookup_recovery_profile(""),
3526            false,
3527            test_values.node,
3528            test_values.telemetry_sender,
3529            test_values.recovery_sender,
3530        );
3531        let mut phy_container = PhyContainer::new(vec![]);
3532        let _ = phy_container.client_ifaces.insert(0);
3533        let _ = phy_manager.phys.insert(0, phy_container);
3534
3535        {
3536            // Issue a create iface request
3537            let fut = phy_manager.create_iface(0, fidl_common::WlanMacRole::Client, NULL_ADDR);
3538            let mut fut = pin!(fut);
3539
3540            // Wait for the request to stall out waiting for DeviceMonitor.
3541            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3542
3543            // Send back a failure from DeviceMonitor.
3544            send_create_iface_response(&mut exec, &mut test_values.monitor_stream, None);
3545
3546            // The future should complete.
3547            assert_matches!(
3548                exec.run_until_stalled(&mut fut),
3549                Poll::Ready(Err(PhyManagerError::IfaceCreateFailure))
3550            );
3551
3552            // Verify that a metric has been logged.
3553            assert_matches!(
3554                test_values.telemetry_receiver.try_next(),
3555                Ok(Some(TelemetryEvent::IfaceCreationResult(Err(()))))
3556            );
3557        }
3558
3559        // Verify the defect was recorded.
3560        assert_eq!(phy_manager.phys[&0].defects.events.len(), 1);
3561        assert_eq!(
3562            phy_manager.phys[&0].defects.events[0].value,
3563            Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 0 })
3564        );
3565    }
3566
3567    #[fuchsia::test]
3568    fn test_create_iface_request_fails() {
3569        let mut exec = TestExecutor::new();
3570        let mut test_values = test_setup();
3571        let mut phy_manager = PhyManager::new(
3572            test_values.monitor_proxy,
3573            recovery::lookup_recovery_profile(""),
3574            false,
3575            test_values.node,
3576            test_values.telemetry_sender,
3577            test_values.recovery_sender,
3578        );
3579        let mut phy_container = PhyContainer::new(vec![]);
3580        let _ = phy_container.client_ifaces.insert(0);
3581        let _ = phy_manager.phys.insert(0, phy_container);
3582
3583        drop(test_values.monitor_stream);
3584
3585        {
3586            // Issue a create iface request
3587            let fut = phy_manager.create_iface(0, fidl_common::WlanMacRole::Client, NULL_ADDR);
3588            let mut fut = pin!(fut);
3589
3590            // The request should immediately fail.
3591            assert_matches!(
3592                exec.run_until_stalled(&mut fut),
3593                Poll::Ready(Err(PhyManagerError::IfaceCreateFailure))
3594            );
3595
3596            // Verify that a metric has been logged.
3597            assert_matches!(
3598                test_values.telemetry_receiver.try_next(),
3599                Ok(Some(TelemetryEvent::IfaceCreationResult(Err(()))))
3600            );
3601        }
3602
3603        // Verify the defect was recorded.
3604        assert_eq!(phy_manager.phys[&0].defects.events.len(), 1);
3605        assert_eq!(
3606            phy_manager.phys[&0].defects.events[0].value,
3607            Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 0 })
3608        );
3609    }
3610
3611    #[fuchsia::test]
3612    fn test_destroy_iface_succeeds() {
3613        let mut exec = TestExecutor::new();
3614        let mut test_values = test_setup();
3615
3616        // Issue a destroy iface request
3617        let fut = destroy_iface(&test_values.monitor_proxy, 0, &test_values.telemetry_sender);
3618        let mut fut = pin!(fut);
3619
3620        // Wait for the request to stall out waiting for DeviceMonitor.
3621        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3622
3623        // Send back a positive response from DeviceMonitor.
3624        send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
3625
3626        // The future should complete.
3627        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
3628
3629        // Verify that there is nothing waiting on the telemetry receiver.
3630        assert_matches!(
3631            test_values.telemetry_receiver.try_next(),
3632            Ok(Some(TelemetryEvent::IfaceDestructionResult(Ok(()))))
3633        )
3634    }
3635
3636    #[fuchsia::test]
3637    fn test_destroy_iface_not_found() {
3638        let mut exec = TestExecutor::new();
3639        let mut test_values = test_setup();
3640
3641        // Issue a destroy iface request
3642        let fut = destroy_iface(&test_values.monitor_proxy, 0, &test_values.telemetry_sender);
3643        let mut fut = pin!(fut);
3644
3645        // Wait for the request to stall out waiting for DeviceMonitor.
3646        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3647
3648        // Send back NOT_FOUND from DeviceMonitor.
3649        send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_ERR_NOT_FOUND);
3650
3651        // The future should complete.
3652        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
3653
3654        // Verify that no metric has been logged.
3655        assert_matches!(test_values.telemetry_receiver.try_next(), Err(_))
3656    }
3657
3658    #[fuchsia::test]
3659    fn test_destroy_iface_fails() {
3660        let mut exec = TestExecutor::new();
3661        let mut test_values = test_setup();
3662
3663        // Issue a destroy iface request
3664        let fut = destroy_iface(&test_values.monitor_proxy, 0, &test_values.telemetry_sender);
3665        let mut fut = pin!(fut);
3666
3667        // Wait for the request to stall out waiting for DeviceMonitor.
3668        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3669
3670        // Send back a non-NOT_FOUND failure from DeviceMonitor.
3671        send_destroy_iface_response(
3672            &mut exec,
3673            &mut test_values.monitor_stream,
3674            zx::sys::ZX_ERR_NO_RESOURCES,
3675        );
3676
3677        // The future should complete.
3678        assert_matches!(
3679            exec.run_until_stalled(&mut fut),
3680            Poll::Ready(Err(PhyManagerError::IfaceDestroyFailure))
3681        );
3682
3683        // Verify that a metric has been logged.
3684        assert_matches!(
3685            test_values.telemetry_receiver.try_next(),
3686            Ok(Some(TelemetryEvent::IfaceDestructionResult(Err(()))))
3687        )
3688    }
3689
3690    #[fuchsia::test]
3691    fn test_destroy_iface_request_fails() {
3692        let mut exec = TestExecutor::new();
3693        let mut test_values = test_setup();
3694
3695        drop(test_values.monitor_stream);
3696
3697        // Issue a destroy iface request
3698        let fut = destroy_iface(&test_values.monitor_proxy, 0, &test_values.telemetry_sender);
3699        let mut fut = pin!(fut);
3700
3701        // The request should immediately fail.
3702        assert_matches!(
3703            exec.run_until_stalled(&mut fut),
3704            Poll::Ready(Err(PhyManagerError::IfaceDestroyFailure))
3705        );
3706
3707        // Verify that a metric has been logged.
3708        assert_matches!(
3709            test_values.telemetry_receiver.try_next(),
3710            Ok(Some(TelemetryEvent::IfaceDestructionResult(Err(()))))
3711        )
3712    }
3713
3714    /// Verify that client iface failures are added properly.
3715    #[fuchsia::test]
3716    fn test_record_iface_event() {
3717        let _exec = TestExecutor::new();
3718        let test_values = test_setup();
3719
3720        let mut phy_manager = PhyManager::new(
3721            test_values.monitor_proxy,
3722            recovery::lookup_recovery_profile(""),
3723            false,
3724            test_values.node,
3725            test_values.telemetry_sender,
3726            test_values.recovery_sender,
3727        );
3728
3729        // Add some PHYs with interfaces.
3730        let _ = phy_manager.phys.insert(0, PhyContainer::new(vec![]));
3731        let _ = phy_manager.phys.insert(1, PhyContainer::new(vec![]));
3732        let _ = phy_manager.phys.insert(2, PhyContainer::new(vec![]));
3733        let _ = phy_manager.phys.insert(3, PhyContainer::new(vec![]));
3734
3735        // Add some PHYs with interfaces.
3736        let _ = phy_manager.phys.get_mut(&0).expect("missing PHY").client_ifaces.insert(123);
3737        let _ = phy_manager.phys.get_mut(&1).expect("missing PHY").client_ifaces.insert(456);
3738        let _ = phy_manager.phys.get_mut(&2).expect("missing PHY").client_ifaces.insert(789);
3739        let _ = phy_manager.phys.get_mut(&3).expect("missing PHY").ap_ifaces.insert(246);
3740
3741        // Allow defects to be retained indefinitely.
3742        phy_manager.phys.get_mut(&0).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3743        phy_manager.phys.get_mut(&1).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3744        phy_manager.phys.get_mut(&2).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3745        phy_manager.phys.get_mut(&3).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3746
3747        // Log some client interface failures.
3748        phy_manager.record_defect(Defect::Iface(IfaceFailure::CanceledScan { iface_id: 123 }));
3749        phy_manager.record_defect(Defect::Iface(IfaceFailure::FailedScan { iface_id: 456 }));
3750        phy_manager.record_defect(Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 789 }));
3751        phy_manager.record_defect(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 123 }));
3752
3753        // Log an AP interface failure.
3754        phy_manager.record_defect(Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 246 }));
3755
3756        // Verify that the defects have been logged.
3757        assert_eq!(phy_manager.phys[&0].defects.events.len(), 2);
3758        assert_eq!(
3759            phy_manager.phys[&0].defects.events[0].value,
3760            Defect::Iface(IfaceFailure::CanceledScan { iface_id: 123 })
3761        );
3762        assert_eq!(
3763            phy_manager.phys[&0].defects.events[1].value,
3764            Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 123 })
3765        );
3766        assert_eq!(phy_manager.phys[&1].defects.events.len(), 1);
3767        assert_eq!(
3768            phy_manager.phys[&1].defects.events[0].value,
3769            Defect::Iface(IfaceFailure::FailedScan { iface_id: 456 })
3770        );
3771        assert_eq!(phy_manager.phys[&2].defects.events.len(), 1);
3772        assert_eq!(
3773            phy_manager.phys[&2].defects.events[0].value,
3774            Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 789 })
3775        );
3776        assert_eq!(phy_manager.phys[&3].defects.events.len(), 1);
3777        assert_eq!(
3778            phy_manager.phys[&3].defects.events[0].value,
3779            Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 246 })
3780        );
3781    }
3782
3783    /// Verify that AP ifaces do not receive client failures..
3784    #[fuchsia::test]
3785    fn test_aps_do_not_record_client_defects() {
3786        let _exec = TestExecutor::new();
3787        let test_values = test_setup();
3788
3789        let mut phy_manager = PhyManager::new(
3790            test_values.monitor_proxy,
3791            recovery::lookup_recovery_profile(""),
3792            false,
3793            test_values.node,
3794            test_values.telemetry_sender,
3795            test_values.recovery_sender,
3796        );
3797
3798        // Add some PHYs with interfaces.
3799        let _ = phy_manager.phys.insert(0, PhyContainer::new(vec![]));
3800
3801        // Add some PHYs with interfaces.
3802        let _ = phy_manager.phys.get_mut(&0).expect("missing PHY").ap_ifaces.insert(123);
3803
3804        // Allow defects to be retained indefinitely.
3805        phy_manager.phys.get_mut(&0).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3806
3807        // Log some client interface failures.
3808        phy_manager.record_defect(Defect::Iface(IfaceFailure::CanceledScan { iface_id: 123 }));
3809        phy_manager.record_defect(Defect::Iface(IfaceFailure::FailedScan { iface_id: 123 }));
3810        phy_manager.record_defect(Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 123 }));
3811        phy_manager.record_defect(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 123 }));
3812
3813        // Verify that the defects have been logged.
3814        assert_eq!(phy_manager.phys[&0].defects.events.len(), 0);
3815    }
3816
3817    /// Verify that client ifaces do not receive AP defects.
3818    #[fuchsia::test]
3819    fn test_clients_do_not_record_ap_defects() {
3820        let _exec = TestExecutor::new();
3821        let test_values = test_setup();
3822
3823        let mut phy_manager = PhyManager::new(
3824            test_values.monitor_proxy,
3825            recovery::lookup_recovery_profile(""),
3826            false,
3827            test_values.node,
3828            test_values.telemetry_sender,
3829            test_values.recovery_sender,
3830        );
3831
3832        // Add some PHYs with interfaces.
3833        let _ = phy_manager.phys.insert(0, PhyContainer::new(vec![]));
3834
3835        // Add a PHY with a client interface.
3836        let _ = phy_manager.phys.get_mut(&0).expect("missing PHY").client_ifaces.insert(123);
3837
3838        // Allow defects to be retained indefinitely.
3839        phy_manager.phys.get_mut(&0).expect("missing PHY").defects = EventHistory::new(u32::MAX);
3840
3841        // Log an AP interface failure.
3842        phy_manager.record_defect(Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 123 }));
3843
3844        // Verify that the defects have been not logged.
3845        assert_eq!(phy_manager.phys[&0].defects.events.len(), 0);
3846    }
3847
3848    fn aggressive_test_recovery_profile(
3849        _phy_id: u16,
3850        _defect_history: &mut EventHistory<Defect>,
3851        _recovery_history: &mut EventHistory<RecoveryAction>,
3852        _latest_defect: Defect,
3853    ) -> Option<RecoveryAction> {
3854        Some(RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 }))
3855    }
3856
3857    #[test_case(
3858        Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 123 }) ;
3859        "recommend AP start recovery"
3860    )]
3861    #[test_case(
3862        Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 456 }) ;
3863        "recommend connection failure recovery"
3864    )]
3865    #[test_case(
3866        Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 456 }) ;
3867        "recommend empty scan recovery"
3868    )]
3869    #[test_case(
3870        Defect::Iface(IfaceFailure::FailedScan { iface_id: 456 }) ;
3871        "recommend failed scan recovery"
3872    )]
3873    #[test_case(
3874        Defect::Iface(IfaceFailure::CanceledScan { iface_id: 456 }) ;
3875        "recommend canceled scan recovery"
3876    )]
3877    #[test_case(
3878        Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 0 }) ;
3879        "recommend iface destruction recovery"
3880    )]
3881    #[test_case(
3882        Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 0 }) ;
3883        "recommend iface creation recovery"
3884    )]
3885    #[fuchsia::test(add_test_attr = false)]
3886    fn test_recovery_action_sent_from_record_defect(defect: Defect) {
3887        let _exec = TestExecutor::new();
3888        let mut test_values = test_setup();
3889        let mut phy_manager = PhyManager::new(
3890            test_values.monitor_proxy,
3891            recovery::lookup_recovery_profile(""),
3892            false,
3893            test_values.node,
3894            test_values.telemetry_sender,
3895            test_values.recovery_sender,
3896        );
3897
3898        // Insert a fake PHY, client interface, and AP interface.
3899        let mut phy_container = PhyContainer::new(vec![]);
3900        let _ = phy_container.ap_ifaces.insert(123);
3901        let _ = phy_container.client_ifaces.insert(456);
3902        let _ = phy_manager.phys.insert(0, phy_container);
3903
3904        // Swap the recovery profile with one that always suggests recovery.
3905        phy_manager.recovery_profile = aggressive_test_recovery_profile;
3906
3907        // Record the defect.
3908        phy_manager.record_defect(defect);
3909
3910        // Verify that a recovery event was sent.
3911        let recovery_action = test_values.recovery_receiver.try_next().unwrap().unwrap();
3912        assert_eq!(recovery_action.defect, defect);
3913        assert_eq!(
3914            recovery_action.action,
3915            RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
3916        );
3917    }
3918
3919    #[test_case(
3920        Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 123 }) ;
3921        "do not recommend AP start recovery"
3922    )]
3923    #[test_case(
3924        Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 456 }) ;
3925        "do not recommend connection failure recovery"
3926    )]
3927    #[test_case(
3928        Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 456 }) ;
3929        "do not recommend empty scan recovery"
3930    )]
3931    #[test_case(
3932        Defect::Iface(IfaceFailure::FailedScan { iface_id: 456 }) ;
3933        "do not recommend failed scan recovery"
3934    )]
3935    #[test_case(
3936        Defect::Iface(IfaceFailure::CanceledScan { iface_id: 456 }) ;
3937        "do not recommend canceled scan recovery"
3938    )]
3939    #[test_case(
3940        Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 0 }) ;
3941        "do not recommend iface destruction recovery"
3942    )]
3943    #[test_case(
3944        Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 0 }) ;
3945        "do not recommend iface creation recovery"
3946    )]
3947    #[fuchsia::test(add_test_attr = false)]
3948    fn test_no_recovery_when_defect_contains_bad_ids(defect: Defect) {
3949        let _exec = TestExecutor::new();
3950        let mut test_values = test_setup();
3951
3952        // This PhyManager doesn't have any PHYs or interfaces.
3953        let mut phy_manager = PhyManager::new(
3954            test_values.monitor_proxy,
3955            recovery::lookup_recovery_profile(""),
3956            false,
3957            test_values.node,
3958            test_values.telemetry_sender,
3959            test_values.recovery_sender,
3960        );
3961
3962        // Swap the recovery profile with one that always suggests recovery.
3963        phy_manager.recovery_profile = aggressive_test_recovery_profile;
3964
3965        // Record the defect.
3966        phy_manager.record_defect(defect);
3967
3968        // Verify that a recovery event was sent.
3969        assert!(test_values.recovery_receiver.try_next().is_err());
3970    }
3971
3972    #[test_case(
3973        recovery::RecoverySummary {
3974            defect: Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 0 }),
3975            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
3976        },
3977        telemetry::RecoveryReason::ScanResultsEmpty(
3978            telemetry::ClientRecoveryMechanism::PhyReset
3979        ) ;
3980        "PHY reset for empty scan results"
3981    )]
3982    #[test_case(
3983        recovery::RecoverySummary {
3984            defect: Defect::Iface(IfaceFailure::CanceledScan { iface_id: 0 }),
3985            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
3986        },
3987        telemetry::RecoveryReason::ScanCancellation(
3988            telemetry::ClientRecoveryMechanism::PhyReset
3989        ) ;
3990        "PHY reset for scan cancellation"
3991    )]
3992    #[test_case(
3993        recovery::RecoverySummary {
3994            defect: Defect::Iface(IfaceFailure::FailedScan { iface_id: 0 }),
3995            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
3996        },
3997        telemetry::RecoveryReason::ScanFailure(
3998            telemetry::ClientRecoveryMechanism::PhyReset
3999        ) ;
4000        "PHY reset for scan failure"
4001    )]
4002    #[test_case(
4003        recovery::RecoverySummary {
4004            defect: Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 0 }),
4005            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
4006        },
4007        telemetry::RecoveryReason::StartApFailure(
4008            telemetry::ApRecoveryMechanism::ResetPhy
4009        ) ;
4010        "PHY reset for start AP failure"
4011    )]
4012    #[test_case(
4013        recovery::RecoverySummary {
4014            defect: Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 0 }),
4015            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
4016        },
4017        telemetry::RecoveryReason::ConnectFailure(
4018            telemetry::ClientRecoveryMechanism::PhyReset
4019        ) ;
4020        "PHY reset for connection failure"
4021    )]
4022    #[test_case(
4023        recovery::RecoverySummary {
4024            defect: Defect::Phy(PhyFailure::IfaceDestructionFailure { phy_id: 0 }),
4025            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
4026        },
4027        telemetry::RecoveryReason::DestroyIfaceFailure(
4028            telemetry::PhyRecoveryMechanism::PhyReset
4029        ) ;
4030        "PHY reset for iface destruction failure"
4031    )]
4032    #[test_case(
4033        recovery::RecoverySummary {
4034            defect: Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 0 }),
4035            action: RecoveryAction::PhyRecovery(PhyRecoveryOperation::ResetPhy { phy_id: 0 })
4036        },
4037        telemetry::RecoveryReason::CreateIfaceFailure(
4038            telemetry::PhyRecoveryMechanism::PhyReset
4039        ) ;
4040        "PHY reset for iface creation failure"
4041    )]
4042    #[fuchsia::test(add_test_attr = false)]
4043    fn test_log_recovery_action_sends_metrics(
4044        summary: recovery::RecoverySummary,
4045        expected_reason: telemetry::RecoveryReason,
4046    ) {
4047        let _exec = TestExecutor::new();
4048        let mut test_values = test_setup();
4049        let mut phy_manager = PhyManager::new(
4050            test_values.monitor_proxy,
4051            recovery::lookup_recovery_profile(""),
4052            false,
4053            test_values.node,
4054            test_values.telemetry_sender,
4055            test_values.recovery_sender,
4056        );
4057
4058        // Send the provided recovery summary and expect the associated telemetry event.
4059        phy_manager.log_recovery_action(summary);
4060        assert_matches!(
4061            test_values.telemetry_receiver.try_next(),
4062            Ok(Some(TelemetryEvent::RecoveryEvent { reason } )) => {
4063        assert_eq!(reason, expected_reason);
4064            })
4065    }
4066
4067    #[test_case(
4068        Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 456 }) ;
4069        "recommend AP start recovery"
4070    )]
4071    #[test_case(
4072        Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 456 }) ;
4073        "recommend connection failure recovery"
4074    )]
4075    #[test_case(
4076        Defect::Iface(IfaceFailure::EmptyScanResults { iface_id: 456 }) ;
4077        "recommend empty scan recovery"
4078    )]
4079    #[test_case(
4080        Defect::Iface(IfaceFailure::FailedScan { iface_id: 456 }) ;
4081        "recommend failed scan recovery"
4082    )]
4083    #[test_case(
4084        Defect::Iface(IfaceFailure::CanceledScan { iface_id: 456 }) ;
4085        "recommend canceled scan recovery"
4086    )]
4087    #[fuchsia::test(add_test_attr = false)]
4088    fn log_defect_for_destroyed_iface(defect: Defect) {
4089        let _exec = TestExecutor::new();
4090        let test_values = test_setup();
4091
4092        let fake_phy_id = 123;
4093        let fake_iface_id = 456;
4094
4095        // Create a PhyManager and give it a PHY that doesn't have any interfaces but does have a
4096        // record of a past interface that was destroyed.
4097        let mut phy_manager = PhyManager::new(
4098            test_values.monitor_proxy,
4099            recovery::lookup_recovery_profile(""),
4100            false,
4101            test_values.node,
4102            test_values.telemetry_sender,
4103            test_values.recovery_sender,
4104        );
4105        let mut phy_container = PhyContainer::new(vec![]);
4106        let _ = phy_container.destroyed_ifaces.insert(fake_iface_id);
4107        let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
4108
4109        // Record the defect.
4110        phy_manager.record_defect(defect);
4111        assert_eq!(phy_manager.phys[&fake_phy_id].defects.events.len(), 1);
4112    }
4113
4114    #[fuchsia::test]
4115    fn test_reset_request_fails() {
4116        let mut exec = TestExecutor::new();
4117        let test_values = test_setup();
4118
4119        // Drop the DeviceMonitor request stream so that the request fails.
4120        drop(test_values.monitor_stream);
4121
4122        // Make the reset request and observe that it fails.
4123        let fut = reset_phy(&test_values.monitor_proxy, 0);
4124        let mut fut = pin!(fut);
4125        assert_matches!(
4126            exec.run_until_stalled(&mut fut),
4127            Poll::Ready(Err(PhyManagerError::InternalError))
4128        );
4129    }
4130
4131    #[fuchsia::test]
4132    fn test_reset_fails() {
4133        let mut exec = TestExecutor::new();
4134        let mut test_values = test_setup();
4135
4136        // Make the reset request.
4137        let fut = reset_phy(&test_values.monitor_proxy, 0);
4138        let mut fut = pin!(fut);
4139        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4140
4141        // Send back a failure.
4142        assert_matches!(
4143            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4144            Poll::Ready(Some(Ok(
4145                fidl_service::DeviceMonitorRequest::Reset { phy_id: 0, responder }
4146            ))) => {
4147                responder.send(Err(ZX_ERR_NOT_FOUND)).expect("sending fake reset response");
4148            }
4149        );
4150
4151        // Ensure that the failure is returned to the caller.
4152        assert_matches!(
4153            exec.run_until_stalled(&mut fut),
4154            Poll::Ready(Err(PhyManagerError::PhyResetFailure))
4155        );
4156    }
4157
4158    #[fuchsia::test]
4159    fn test_reset_succeeds() {
4160        let mut exec = TestExecutor::new();
4161        let mut test_values = test_setup();
4162
4163        // Make the reset request.
4164        let fut = reset_phy(&test_values.monitor_proxy, 0);
4165        let mut fut = pin!(fut);
4166        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4167
4168        // Send back a success.
4169        assert_matches!(
4170            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4171            Poll::Ready(Some(Ok(
4172                fidl_service::DeviceMonitorRequest::Reset { phy_id: 0, responder }
4173            ))) => {
4174                responder.send(Ok(())).expect("sending fake reset response");
4175            }
4176        );
4177
4178        // Ensure that the success is returned to the caller.
4179        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
4180    }
4181
4182    #[fuchsia::test]
4183    fn test_disconnect_request_fails() {
4184        let mut exec = TestExecutor::new();
4185        let mut test_values = test_setup();
4186
4187        // Make the disconnect request.
4188        let fut = disconnect(&test_values.monitor_proxy, 0);
4189        let mut fut = pin!(fut);
4190        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4191
4192        // First, the client SME will be requested.
4193        let sme_server = assert_matches!(
4194            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4195            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme {
4196                iface_id: 0, sme_server, responder
4197            }))) => {
4198                // Send back a positive acknowledgement.
4199                assert!(responder.send(Ok(())).is_ok());
4200                sme_server
4201            }
4202        );
4203
4204        // Drop the SME server so that the request will fail.
4205        drop(sme_server);
4206
4207        // The future should complete with an error.
4208        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
4209    }
4210
4211    #[fuchsia::test]
4212    fn test_disconnect_succeeds() {
4213        let mut exec = TestExecutor::new();
4214        let mut test_values = test_setup();
4215
4216        // Make the disconnect request.
4217        let fut = disconnect(&test_values.monitor_proxy, 0);
4218        let mut fut = pin!(fut);
4219        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4220
4221        // First, the client SME will be requested.
4222        let sme_server = assert_matches!(
4223            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4224            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme {
4225                iface_id: 0, sme_server, responder
4226            }))) => {
4227                // Send back a positive acknowledgement.
4228                assert!(responder.send(Ok(())).is_ok());
4229                sme_server
4230            }
4231        );
4232
4233        // Next, the disconnect will be requested.
4234        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4235        let mut sme_stream = sme_server.into_stream().into_future();
4236        assert_matches!(
4237            poll_sme_req(&mut exec, &mut sme_stream),
4238            Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Disconnect{
4239                responder,
4240                reason: fidl_fuchsia_wlan_sme::UserDisconnectReason::Recovery
4241            }) => {
4242                responder.send().expect("Failed to send disconnect response")
4243            }
4244        );
4245
4246        // Verify the future completes successfully.
4247        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
4248    }
4249
4250    #[fuchsia::test]
4251    fn test_disconnect_cannot_get_sme() {
4252        let mut exec = TestExecutor::new();
4253        let test_values = test_setup();
4254
4255        // Drop the DeviceMonitor stream so that the client SME cannot be obtained.
4256        drop(test_values.monitor_stream);
4257
4258        // Make the disconnect request.
4259        let fut = disconnect(&test_values.monitor_proxy, 0);
4260        let mut fut = pin!(fut);
4261        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
4262    }
4263
4264    #[fuchsia::test]
4265    fn test_stop_ap_request_fails() {
4266        let mut exec = TestExecutor::new();
4267        let mut test_values = test_setup();
4268
4269        // Make the stop AP request.
4270        let fut = stop_ap(&test_values.monitor_proxy, 0);
4271        let mut fut = pin!(fut);
4272        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4273
4274        // First, the AP SME will be requested.
4275        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4276        let sme_server = assert_matches!(
4277            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4278            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme {
4279                iface_id: 0, sme_server, responder
4280            }))) => {
4281                // Send back a positive acknowledgement.
4282                assert!(responder.send(Ok(())).is_ok());
4283                sme_server
4284            }
4285        );
4286
4287        // Drop the SME server so that the request will fail.
4288        drop(sme_server);
4289
4290        // The future should complete with an error.
4291        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
4292    }
4293
4294    #[fuchsia::test]
4295    fn test_stop_ap_fails() {
4296        let mut exec = TestExecutor::new();
4297        let mut test_values = test_setup();
4298
4299        // Make the stop AP request.
4300        let fut = stop_ap(&test_values.monitor_proxy, 0);
4301        let mut fut = pin!(fut);
4302        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4303
4304        // First, the AP SME will be requested.
4305        let sme_server = assert_matches!(
4306            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4307            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme {
4308                iface_id: 0, sme_server, responder
4309            }))) => {
4310                // Send back a positive acknowledgement.
4311                assert!(responder.send(Ok(())).is_ok());
4312                sme_server
4313            }
4314        );
4315
4316        // Expect the stop AP request.
4317        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4318        let mut sme_stream = sme_server.into_stream().into_future();
4319        assert_matches!(
4320            poll_ap_sme_req(&mut exec, &mut sme_stream),
4321            Poll::Ready(fidl_fuchsia_wlan_sme::ApSmeRequest::Stop{
4322                responder,
4323            }) => {
4324                responder.send(fidl_sme::StopApResultCode::InternalError).expect("Failed to send stop AP response")
4325            }
4326        );
4327
4328        // The future should complete with an error.
4329        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
4330    }
4331
4332    #[fuchsia::test]
4333    fn test_stop_ap_succeeds() {
4334        let mut exec = TestExecutor::new();
4335        let mut test_values = test_setup();
4336
4337        // Make the stop AP request.
4338        let fut = stop_ap(&test_values.monitor_proxy, 0);
4339        let mut fut = pin!(fut);
4340        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4341
4342        // First, the AP SME will be requested.
4343        let sme_server = assert_matches!(
4344            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4345            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme {
4346                iface_id: 0, sme_server, responder
4347            }))) => {
4348                // Send back a positive acknowledgement.
4349                assert!(responder.send(Ok(())).is_ok());
4350                sme_server
4351            }
4352        );
4353
4354        // Expect the stop AP request.
4355        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4356        let mut sme_stream = sme_server.into_stream().into_future();
4357        assert_matches!(
4358            poll_ap_sme_req(&mut exec, &mut sme_stream),
4359            Poll::Ready(fidl_fuchsia_wlan_sme::ApSmeRequest::Stop{
4360                responder,
4361            }) => {
4362                responder.send(fidl_sme::StopApResultCode::Success).expect("Failed to send stop AP response")
4363            }
4364        );
4365
4366        // The future should complete with an error.
4367        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(())));
4368    }
4369
4370    #[fuchsia::test]
4371    fn test_stop_ap_cannot_get_sme() {
4372        let mut exec = TestExecutor::new();
4373        let test_values = test_setup();
4374
4375        // Drop the DeviceMonitor stream so that the client SME cannot be obtained.
4376        drop(test_values.monitor_stream);
4377
4378        // Make the disconnect request.
4379        let fut = stop_ap(&test_values.monitor_proxy, 0);
4380        let mut fut = pin!(fut);
4381        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
4382    }
4383
4384    fn phy_manager_for_recovery_test(
4385        device_monitor: fidl_service::DeviceMonitorProxy,
4386        node: inspect::Node,
4387        telemetry_sender: TelemetrySender,
4388        recovery_action_sender: recovery::RecoveryActionSender,
4389    ) -> PhyManager {
4390        let mut phy_manager = PhyManager::new(
4391            device_monitor,
4392            recovery::lookup_recovery_profile("thresholded_recovery"),
4393            true,
4394            node,
4395            telemetry_sender,
4396            recovery_action_sender,
4397        );
4398
4399        // Give the PhyManager client and AP interfaces.
4400        let mut phy_container =
4401            PhyContainer::new(vec![fidl_common::WlanMacRole::Client, fidl_common::WlanMacRole::Ap]);
4402        assert!(phy_container.client_ifaces.insert(1,));
4403        assert!(phy_container.ap_ifaces.insert(2));
4404        assert!(phy_manager.phys.insert(0, phy_container).is_none());
4405
4406        phy_manager
4407    }
4408
4409    #[fuchsia::test]
4410    fn test_perform_recovery_destroy_nonexistent_iface() {
4411        let mut exec = TestExecutor::new();
4412        let mut test_values = test_setup();
4413        let mut phy_manager = phy_manager_for_recovery_test(
4414            test_values.monitor_proxy,
4415            test_values.node,
4416            test_values.telemetry_sender,
4417            test_values.recovery_sender,
4418        );
4419
4420        // Suggest a recovery action to destroy nonexistent interface.
4421        let summary = recovery::RecoverySummary {
4422            defect: Defect::Iface(IfaceFailure::FailedScan { iface_id: 123 }),
4423            action: recovery::RecoveryAction::PhyRecovery(
4424                recovery::PhyRecoveryOperation::DestroyIface { iface_id: 123 },
4425            ),
4426        };
4427
4428        // The future should complete immediately.
4429        {
4430            let fut = phy_manager.perform_recovery(summary);
4431            let mut fut = pin!(fut);
4432            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4433        }
4434
4435        // No request should have been made of DeviceMonitor.
4436        assert_matches!(
4437            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4438            Poll::Pending
4439        );
4440    }
4441
4442    #[fuchsia::test]
4443    fn test_perform_recovery_destroy_client_iface_fails() {
4444        let mut exec = TestExecutor::new();
4445        let test_values = test_setup();
4446        let mut phy_manager = phy_manager_for_recovery_test(
4447            test_values.monitor_proxy,
4448            test_values.node,
4449            test_values.telemetry_sender,
4450            test_values.recovery_sender,
4451        );
4452
4453        // Drop the DeviceMonitor serving end so that destroying the interface will fail.
4454        drop(test_values.monitor_stream);
4455
4456        // Suggest a recovery action to destroy the client interface.
4457        let summary = recovery::RecoverySummary {
4458            defect: Defect::Iface(IfaceFailure::FailedScan { iface_id: 1 }),
4459            action: recovery::RecoveryAction::PhyRecovery(
4460                recovery::PhyRecoveryOperation::DestroyIface { iface_id: 1 },
4461            ),
4462        };
4463
4464        {
4465            let fut = phy_manager.perform_recovery(summary);
4466            let mut fut = pin!(fut);
4467            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4468        }
4469
4470        // Verify that the client interface is still present.
4471        assert!(phy_manager.phys[&0].client_ifaces.contains(&1));
4472    }
4473
4474    #[fuchsia::test]
4475    fn test_perform_recovery_destroy_client_iface_succeeds() {
4476        let mut exec = TestExecutor::new();
4477        let mut test_values = test_setup();
4478        let mut phy_manager = phy_manager_for_recovery_test(
4479            test_values.monitor_proxy,
4480            test_values.node,
4481            test_values.telemetry_sender,
4482            test_values.recovery_sender,
4483        );
4484
4485        // Suggest a recovery action to destroy the client interface.
4486        let summary = recovery::RecoverySummary {
4487            defect: Defect::Iface(IfaceFailure::FailedScan { iface_id: 1 }),
4488            action: recovery::RecoveryAction::PhyRecovery(
4489                recovery::PhyRecoveryOperation::DestroyIface { iface_id: 1 },
4490            ),
4491        };
4492
4493        {
4494            let fut = phy_manager.perform_recovery(summary);
4495            let mut fut = pin!(fut);
4496            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4497
4498            // Verify that the DestroyIface request was made and respond with a success.
4499            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
4500
4501            // The future should complete now.
4502            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4503        }
4504
4505        // Verify that the client interface has been removed.
4506        assert!(!phy_manager.phys[&0].client_ifaces.contains(&1));
4507
4508        // Verify that the destroyed interface ID has been recorded.
4509        assert!(phy_manager.phys[&0].destroyed_ifaces.contains(&1));
4510    }
4511
4512    #[fuchsia::test]
4513    fn test_perform_recovery_destroy_ap_iface_fails() {
4514        let mut exec = TestExecutor::new();
4515        let test_values = test_setup();
4516        let mut phy_manager = phy_manager_for_recovery_test(
4517            test_values.monitor_proxy,
4518            test_values.node,
4519            test_values.telemetry_sender,
4520            test_values.recovery_sender,
4521        );
4522
4523        // Drop the DeviceMonitor serving end so that destroying the interface will fail.
4524        drop(test_values.monitor_stream);
4525
4526        // Suggest a recovery action to destroy the AP interface.
4527        let summary = recovery::RecoverySummary {
4528            defect: Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 2 }),
4529            action: recovery::RecoveryAction::PhyRecovery(
4530                recovery::PhyRecoveryOperation::DestroyIface { iface_id: 2 },
4531            ),
4532        };
4533
4534        {
4535            let fut = phy_manager.perform_recovery(summary);
4536            let mut fut = pin!(fut);
4537            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4538        }
4539
4540        // Verify that the AP interface is still present.
4541        assert!(phy_manager.phys[&0].ap_ifaces.contains(&2));
4542    }
4543
4544    #[fuchsia::test]
4545    fn test_perform_recovery_destroy_ap_iface_succeeds() {
4546        let mut exec = TestExecutor::new();
4547        let mut test_values = test_setup();
4548        let mut phy_manager = phy_manager_for_recovery_test(
4549            test_values.monitor_proxy,
4550            test_values.node,
4551            test_values.telemetry_sender,
4552            test_values.recovery_sender,
4553        );
4554
4555        // Suggest a recovery action to destroy the AP interface.
4556        let summary = recovery::RecoverySummary {
4557            defect: Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 2 }),
4558            action: recovery::RecoveryAction::PhyRecovery(
4559                recovery::PhyRecoveryOperation::DestroyIface { iface_id: 2 },
4560            ),
4561        };
4562
4563        {
4564            let fut = phy_manager.perform_recovery(summary);
4565            let mut fut = pin!(fut);
4566            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4567
4568            // Verify that the DestroyIface request was made and respond with a success.
4569            send_destroy_iface_response(&mut exec, &mut test_values.monitor_stream, ZX_OK);
4570
4571            // The future should complete now.
4572            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4573        }
4574
4575        // Verify that the AP interface has been removed.
4576        assert!(!phy_manager.phys[&0].ap_ifaces.contains(&2));
4577
4578        // Verify the destroyed iface ID was recorded.
4579        assert!(phy_manager.phys[&0].destroyed_ifaces.contains(&2));
4580    }
4581
4582    // TODO(https://fxbug.dev/424173437) - Re-enable once IfaceManager deadlock issue has been resolved.
4583    #[ignore]
4584    #[test_case(Some("US".parse().unwrap()); "Cached country code")]
4585    #[test_case(None; "No cached country code")]
4586    #[fuchsia::test(add_test_attr = false)]
4587    fn test_perform_recovery_reset_requests_phy_reset(
4588        cached_country_code: Option<client_types::CountryCode>,
4589    ) {
4590        let mut exec = TestExecutor::new();
4591        let mut test_values = test_setup();
4592        let mut phy_manager = phy_manager_for_recovery_test(
4593            test_values.monitor_proxy,
4594            test_values.node,
4595            test_values.telemetry_sender,
4596            test_values.recovery_sender,
4597        );
4598
4599        // Set a country code in the phy manager
4600        phy_manager.saved_country_code = cached_country_code;
4601
4602        // Suggest a recovery action to reset the PHY.
4603        let summary = recovery::RecoverySummary {
4604            defect: Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 2 }),
4605            action: recovery::RecoveryAction::PhyRecovery(
4606                recovery::PhyRecoveryOperation::ResetPhy { phy_id: 0 },
4607            ),
4608        };
4609
4610        let fut = phy_manager.perform_recovery(summary);
4611        let mut fut = pin!(fut);
4612        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4613
4614        // Verify that the Reset request was made and respond with a success.
4615        assert_matches!(
4616            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4617            Poll::Ready(Some(Ok(
4618                fidl_service::DeviceMonitorRequest::Reset {
4619                    phy_id: 0,
4620                    responder,
4621                }
4622            ))) => {
4623                responder
4624                    .send(Ok(()))
4625                    .expect("failed to send reset response.");
4626            }
4627        );
4628
4629        // Check that we set the country code if we had a cached country code
4630        if let Some(cached_cc) = cached_country_code {
4631            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4632            assert_matches!(
4633                exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4634                Poll::Ready(Some(Ok(
4635                    fidl_service::DeviceMonitorRequest::SetCountry {
4636                        req: fidl_service::SetCountryRequest {
4637                            phy_id: 0,
4638                            alpha2: cc_in_req,
4639                        },
4640                        responder,
4641                    }
4642                ))) => {
4643                    assert_eq!(cc_in_req, <[u8; 2]>::from(cached_cc));
4644                    responder
4645                        .send(zx::sys::ZX_OK)
4646                        .expect("failed to send setCountry response.");
4647                }
4648            );
4649        }
4650
4651        // The future should complete now.
4652        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4653    }
4654
4655    #[fuchsia::test]
4656    fn test_perform_recovery_disconnect_issues_request() {
4657        let mut exec = TestExecutor::new();
4658        let mut test_values = test_setup();
4659        let mut phy_manager = phy_manager_for_recovery_test(
4660            test_values.monitor_proxy,
4661            test_values.node,
4662            test_values.telemetry_sender,
4663            test_values.recovery_sender,
4664        );
4665
4666        // Suggest a recovery action to disconnect the client interface.
4667        let summary = recovery::RecoverySummary {
4668            defect: Defect::Iface(IfaceFailure::FailedScan { iface_id: 1 }),
4669            action: recovery::RecoveryAction::IfaceRecovery(
4670                recovery::IfaceRecoveryOperation::Disconnect { iface_id: 1 },
4671            ),
4672        };
4673
4674        let fut = phy_manager.perform_recovery(summary);
4675        let mut fut = pin!(fut);
4676        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4677
4678        // Verify that the disconnect request was made and respond with a success.
4679        // First, the client SME will be requested.
4680        let sme_server = assert_matches!(
4681            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4682            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme {
4683                iface_id: 1, sme_server, responder
4684            }))) => {
4685                // Send back a positive acknowledgement.
4686                assert!(responder.send(Ok(())).is_ok());
4687                sme_server
4688            }
4689        );
4690
4691        // Next, the disconnect will be requested.
4692        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4693        let mut sme_stream = sme_server.into_stream().into_future();
4694        assert_matches!(
4695            poll_sme_req(&mut exec, &mut sme_stream),
4696            Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Disconnect{
4697                responder,
4698                reason: fidl_fuchsia_wlan_sme::UserDisconnectReason::Recovery
4699            }) => {
4700                responder.send().expect("Failed to send disconnect response")
4701            }
4702        );
4703
4704        // The future should complete now.
4705        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4706    }
4707
4708    #[fuchsia::test]
4709    fn test_perform_recovery_stop_ap_issues_request() {
4710        let mut exec = TestExecutor::new();
4711        let mut test_values = test_setup();
4712        let mut phy_manager = phy_manager_for_recovery_test(
4713            test_values.monitor_proxy,
4714            test_values.node,
4715            test_values.telemetry_sender,
4716            test_values.recovery_sender,
4717        );
4718
4719        // Suggest a recovery action to destroy the AP interface.
4720        let summary = recovery::RecoverySummary {
4721            defect: Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 2 }),
4722            action: recovery::RecoveryAction::IfaceRecovery(
4723                recovery::IfaceRecoveryOperation::StopAp { iface_id: 2 },
4724            ),
4725        };
4726
4727        let fut = phy_manager.perform_recovery(summary);
4728        let mut fut = pin!(fut);
4729        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4730
4731        // Verify that the StopAp request was made and respond with a success.
4732        // First, the AP SME will be requested.
4733        let sme_server = assert_matches!(
4734            exec.run_until_stalled(&mut test_values.monitor_stream.next()),
4735            Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme {
4736                iface_id: 2, sme_server, responder
4737            }))) => {
4738                // Send back a positive acknowledgement.
4739                assert!(responder.send(Ok(())).is_ok());
4740                sme_server
4741            }
4742        );
4743
4744        // Expect the stop AP request.
4745        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4746        let mut sme_stream = sme_server.into_stream().into_future();
4747        assert_matches!(
4748            poll_ap_sme_req(&mut exec, &mut sme_stream),
4749            Poll::Ready(fidl_fuchsia_wlan_sme::ApSmeRequest::Stop{
4750                responder,
4751            }) => {
4752                responder.send(fidl_sme::StopApResultCode::Success).expect("Failed to send stop AP response")
4753            }
4754        );
4755
4756        // The future should complete now.
4757        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4758    }
4759
4760    #[fuchsia::test]
4761    fn test_log_timeout_defect() {
4762        let _exec = TestExecutor::new();
4763        let mut test_values = test_setup();
4764        let mut phy_manager = phy_manager_for_recovery_test(
4765            test_values.monitor_proxy,
4766            test_values.node,
4767            test_values.telemetry_sender,
4768            test_values.recovery_sender,
4769        );
4770
4771        // Verify that there are no defects to begin with.
4772        assert_eq!(phy_manager.phys[&0].defects.events.len(), 0);
4773
4774        // Log a timeout.
4775        phy_manager.record_defect(Defect::Iface(IfaceFailure::Timeout {
4776            iface_id: 1,
4777            source: telemetry::TimeoutSource::Scan,
4778        }));
4779
4780        // Verify that the defect was recorded.
4781        assert_eq!(phy_manager.phys[&0].defects.events.len(), 1);
4782
4783        // Verify that the defect was reported to telemetry.
4784        assert_matches!(
4785            test_values.telemetry_receiver.try_next(),
4786            Ok(Some(TelemetryEvent::SmeTimeout { source: telemetry::TimeoutSource::Scan }))
4787        )
4788    }
4789}