Skip to main content

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