wlancfg_lib/mode_management/
phy_manager.rs

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