Skip to main content

wlancfg_lib/mode_management/
phy_manager.rs

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