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