1use either::Either;
6use serde::{Deserialize, Deserializer};
7use std::collections::{HashMap, HashSet};
8use std::sync::atomic::{AtomicU32, Ordering};
9
10use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
11
12use crate::DeviceClass;
13
14const INTERFACE_PREFIX_WLAN: &str = "wlan";
15const INTERFACE_PREFIX_ETHERNET: &str = "eth";
16const INTERFACE_PREFIX_AP: &str = "ap";
17const INTERFACE_PREFIX_BLACKHOLE: &str = "blackhole";
18
19#[derive(PartialEq, Eq, Debug, Clone, Hash)]
20pub(crate) struct InterfaceNamingIdentifier {
21 pub(crate) mac: fidl_fuchsia_net_ext::MacAddress,
22}
23
24pub(crate) fn generate_identifier(
25 mac_address: &fidl_fuchsia_net_ext::MacAddress,
26) -> InterfaceNamingIdentifier {
27 InterfaceNamingIdentifier { mac: *mac_address }
28}
29
30fn get_mac_identifier_from_octets(
35 octets: &[u8; 6],
36 interface_type: crate::InterfaceType,
37 offset: u8,
38) -> Result<u8, anyhow::Error> {
39 if offset == u8::MAX {
40 return Err(anyhow::format_err!(
41 "could not find unique identifier for mac={:?}, interface_type={:?}",
42 octets,
43 interface_type
44 ));
45 }
46
47 let last_byte = octets[octets.len() - 1];
48 let (identifier, _) = last_byte.overflowing_add(offset);
49 Ok(identifier)
50}
51
52fn get_normalized_bus_path_for_topo_path(topological_path: &str) -> String {
85 static PATH_UNIQ_MARKER: AtomicU32 = AtomicU32::new(0xffffff);
86 topological_path
87 .split("/")
88 .find(|pc| {
89 pc.len() >= 7 && pc.chars().all(|c| c.is_digit(16) || c == ':' || c == '.' || c == '_')
90 })
91 .and_then(|s| {
92 Some(s.replace(&[':', '.', '_'], "").trim_end_matches(|c| c == '0').to_string())
93 })
94 .unwrap_or_else(|| format!("{:01$x}", PATH_UNIQ_MARKER.fetch_sub(1, Ordering::SeqCst), 6))
95}
96
97#[derive(Debug)]
98pub struct InterfaceNamingConfig {
99 naming_rules: Vec<NamingRule>,
100 interfaces: HashMap<InterfaceNamingIdentifier, String>,
101}
102
103impl InterfaceNamingConfig {
104 pub(crate) fn from_naming_rules(naming_rules: Vec<NamingRule>) -> InterfaceNamingConfig {
105 InterfaceNamingConfig { naming_rules, interfaces: HashMap::new() }
106 }
107
108 pub(crate) fn generate_stable_name(
110 &mut self,
111 topological_path: &str,
112 mac: &fidl_fuchsia_net_ext::MacAddress,
113 device_class: DeviceClass,
114 ) -> Result<(&str, InterfaceNamingIdentifier), NameGenerationError> {
115 let interface_naming_id = generate_identifier(mac);
116 let info = DeviceInfoRef { topological_path, mac, device_class };
117
118 match self.interfaces.remove(&interface_naming_id) {
123 Some(name) => log::info!(
124 "{name} already existed for this identifier\
125 {interface_naming_id:?}. inserting a new one."
126 ),
127 None => {
128 }
130 }
131
132 let generated_name = self.generate_name(&info)?;
133 if let Some(name) =
134 self.interfaces.insert(interface_naming_id.clone(), generated_name.clone())
135 {
136 log::error!(
137 "{name} was unexpectedly found for {interface_naming_id:?} \
138 when inserting a new name"
139 );
140 }
141
142 let generated_name = match self.interfaces.get(&interface_naming_id) {
144 Some(name) => Ok(name),
145 None => Err(NameGenerationError::GenerationError(anyhow::format_err!(
146 "expected to see name {generated_name} present since it was just added"
147 ))),
148 }?;
149
150 Ok((generated_name, interface_naming_id))
151 }
152
153 fn generate_name(&self, info: &DeviceInfoRef<'_>) -> Result<String, NameGenerationError> {
154 generate_name_from_naming_rules(&self.naming_rules, &self.interfaces, &info)
155 }
156}
157
158#[derive(Debug)]
160pub enum NameGenerationError {
161 GenerationError(anyhow::Error),
162}
163
164#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
165#[serde(deny_unknown_fields, rename_all = "lowercase")]
166pub enum BusType {
167 PCI,
168 SDIO,
169 USB,
170 Unknown,
171 VirtIo,
172}
173
174impl BusType {
175 fn get_default_name_composition_rules(&self) -> Vec<NameCompositionRule> {
181 match *self {
182 BusType::USB | BusType::Unknown => vec![
183 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
184 NameCompositionRule::Static { value: String::from("x") },
185 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
186 ],
187 BusType::PCI | BusType::SDIO | BusType::VirtIo => vec![
188 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
189 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
190 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
191 ],
192 }
193 }
194}
195
196impl std::fmt::Display for BusType {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 let name = match *self {
199 Self::PCI => "p",
200 Self::SDIO => "s",
201 Self::USB => "u",
202 Self::Unknown => "unk",
203 Self::VirtIo => "v",
204 };
205 write!(f, "{}", name)
206 }
207}
208
209fn get_bus_type_for_topological_path(topological_path: &str) -> BusType {
211 let p = topological_path;
212
213 if p.contains("/PCI0") {
214 if p.contains("/usb/") {
218 return BusType::USB;
219 }
220 return BusType::PCI;
221 } else if p.contains("/usb-peripheral/") {
222 return BusType::USB;
226 } else if p.contains("/sdio/") {
227 return BusType::SDIO;
228 } else if p.contains("/virtio-net/") {
229 return BusType::VirtIo;
230 }
231
232 BusType::Unknown
233}
234
235fn deserialize_glob_pattern<'de, D>(deserializer: D) -> Result<glob::Pattern, D::Error>
236where
237 D: Deserializer<'de>,
238{
239 let buf = String::deserialize(deserializer)?;
240 glob::Pattern::new(&buf).map_err(serde::de::Error::custom)
241}
242
243#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
245#[serde(deny_unknown_fields, rename_all = "snake_case")]
246pub enum MatchingRule {
247 BusTypes(Vec<BusType>),
248 #[serde(deserialize_with = "deserialize_glob_pattern")]
251 TopologicalPath(glob::Pattern),
252 DeviceClasses(Vec<DeviceClass>),
253 Any(bool),
255}
256
257#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
259#[serde(untagged)]
260pub enum ProvisioningMatchingRule {
261 InterfaceName {
266 #[serde(rename = "interface_name", deserialize_with = "deserialize_glob_pattern")]
267 pattern: glob::Pattern,
268 },
269 Common(MatchingRule),
270}
271
272impl MatchingRule {
273 fn does_interface_match(&self, info: &DeviceInfoRef<'_>) -> Result<bool, anyhow::Error> {
274 match &self {
275 MatchingRule::BusTypes(type_list) => {
276 let bus_type = get_bus_type_for_topological_path(info.topological_path);
279 Ok(type_list.contains(&bus_type))
280 }
281 MatchingRule::TopologicalPath(pattern) => {
282 Ok(pattern.matches(info.topological_path))
286 }
287 MatchingRule::DeviceClasses(class_list) => {
288 Ok(class_list.contains(&info.device_class))
291 }
292 MatchingRule::Any(matches_any_interface) => Ok(*matches_any_interface),
293 }
294 }
295}
296
297impl ProvisioningMatchingRule {
298 fn does_interface_match(
299 &self,
300 info: &DeviceInfoRef<'_>,
301 interface_name: &str,
302 ) -> Result<bool, anyhow::Error> {
303 match &self {
304 ProvisioningMatchingRule::InterfaceName { pattern } => {
305 Ok(pattern.matches(interface_name))
308 }
309 ProvisioningMatchingRule::Common(matching_rule) => {
310 matching_rule.does_interface_match(info)
313 }
314 }
315 }
316}
317
318#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Deserialize)]
322#[serde(deny_unknown_fields, rename_all = "snake_case")]
323pub enum DynamicNameCompositionRule {
324 BusPath,
325 BusType,
326 DeviceClass,
327 NormalizedMac,
329}
330
331impl DynamicNameCompositionRule {
332 fn supports_retry(&self) -> bool {
334 match *self {
335 DynamicNameCompositionRule::BusPath
336 | DynamicNameCompositionRule::BusType
337 | DynamicNameCompositionRule::DeviceClass => false,
338 DynamicNameCompositionRule::NormalizedMac => true,
339 }
340 }
341
342 fn get_name(&self, info: &DeviceInfoRef<'_>, attempt_num: u8) -> Result<String, anyhow::Error> {
343 Ok(match *self {
344 DynamicNameCompositionRule::BusPath => {
345 get_normalized_bus_path_for_topo_path(info.topological_path)
346 }
347 DynamicNameCompositionRule::BusType => {
348 get_bus_type_for_topological_path(info.topological_path).to_string()
349 }
350 DynamicNameCompositionRule::DeviceClass => match info.device_class.into() {
351 crate::InterfaceType::WlanClient => INTERFACE_PREFIX_WLAN,
352 crate::InterfaceType::Ethernet => INTERFACE_PREFIX_ETHERNET,
353 crate::InterfaceType::WlanAp => INTERFACE_PREFIX_AP,
354 crate::InterfaceType::Blackhole => INTERFACE_PREFIX_BLACKHOLE,
355 }
356 .to_string(),
357 DynamicNameCompositionRule::NormalizedMac => {
358 let fidl_fuchsia_net_ext::MacAddress { octets } = info.mac;
359 let mac_identifier =
360 get_mac_identifier_from_octets(octets, info.device_class.into(), attempt_num)?;
361 format!("{mac_identifier:x}")
362 }
363 })
364 }
365}
366
367#[derive(Clone, Debug, Deserialize, PartialEq)]
371#[serde(deny_unknown_fields, rename_all = "lowercase", tag = "type")]
372pub enum NameCompositionRule {
373 Static { value: String },
374 Dynamic { rule: DynamicNameCompositionRule },
375 Default,
378}
379
380#[derive(Debug, Deserialize, PartialEq)]
383#[serde(deny_unknown_fields, rename_all = "lowercase")]
384pub struct NamingRule {
385 pub matchers: HashSet<MatchingRule>,
388 pub naming_scheme: Vec<NameCompositionRule>,
390}
391
392impl NamingRule {
393 fn generate_name(
397 &self,
398 interfaces: &HashMap<InterfaceNamingIdentifier, String>,
399 info: &DeviceInfoRef<'_>,
400 ) -> Result<String, NameGenerationError> {
401 let bus_type = get_bus_type_for_topological_path(&info.topological_path);
404
405 let expanded_rules = self
410 .naming_scheme
411 .iter()
412 .map(|rule| {
413 if let NameCompositionRule::Default = rule {
414 Either::Right(bus_type.get_default_name_composition_rules().into_iter())
415 } else {
416 Either::Left(std::iter::once(rule.clone()))
417 }
418 })
419 .flatten()
420 .collect::<Vec<_>>();
421
422 let should_reattempt_on_conflict = expanded_rules.iter().any(|rule| {
424 if let NameCompositionRule::Dynamic { rule } = rule {
425 rule.supports_retry()
426 } else {
427 false
428 }
429 });
430
431 let mut attempt_num = 0u8;
432 loop {
433 let name = expanded_rules
434 .iter()
435 .map(|rule| match rule {
436 NameCompositionRule::Static { value } => Ok(value.clone()),
437 NameCompositionRule::Dynamic { rule } => rule
439 .get_name(info, attempt_num)
440 .map_err(NameGenerationError::GenerationError),
441 NameCompositionRule::Default => {
442 unreachable!(
443 "Default naming rules should have been pre-expanded. \
444 Nested default rules are not supported."
445 );
446 }
447 })
448 .collect::<Result<String, NameGenerationError>>()?;
449
450 if interfaces.values().any(|existing_name| existing_name == &name) {
451 if should_reattempt_on_conflict {
452 attempt_num += 1;
453 continue;
455 }
456
457 log::warn!(
458 "name ({name}) already used for an interface installed by netcfg. \
459 using name since it is possible that the interface using this name is no \
460 longer active"
461 );
462 }
463 return Ok(name);
464 }
465 }
466
467 fn does_interface_match(&self, info: &DeviceInfoRef<'_>) -> bool {
469 self.matchers.iter().all(|rule| rule.does_interface_match(info).unwrap_or_default())
470 }
471}
472
473fn generate_name_from_naming_rules(
476 naming_rules: &[NamingRule],
477 interfaces: &HashMap<InterfaceNamingIdentifier, String>,
478 info: &DeviceInfoRef<'_>,
479) -> Result<String, NameGenerationError> {
480 let fallback_rule = fallback_naming_rule();
485 let first_matching_rule =
486 naming_rules.iter().find(|rule| rule.does_interface_match(&info)).unwrap_or(
487 &fallback_rule,
490 );
491
492 first_matching_rule.generate_name(interfaces, &info)
493}
494
495fn fallback_naming_rule() -> NamingRule {
497 NamingRule {
498 matchers: HashSet::from([MatchingRule::Any(true)]),
499 naming_scheme: vec![NameCompositionRule::Default],
500 }
501}
502
503#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Default)]
505#[serde(deny_unknown_fields, rename_all = "lowercase")]
506pub struct ProvisioningAction {
507 pub provisioning: ProvisioningType,
509 pub netstack_managed_routes_designation: Option<NetstackManagedRoutesDesignation>,
511}
512
513#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Default)]
519#[serde(deny_unknown_fields, rename_all = "lowercase")]
520pub enum ProvisioningType {
521 #[default]
523 Local,
524 Delegated,
527}
528
529#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
533#[serde(deny_unknown_fields, rename_all = "snake_case")]
534pub enum NetstackManagedRoutesDesignation {
535 Main,
536 InterfaceLocal,
537}
538
539impl From<NetstackManagedRoutesDesignation>
540 for fnet_interfaces_admin::NetstackManagedRoutesDesignation
541{
542 fn from(value: NetstackManagedRoutesDesignation) -> Self {
543 match value {
544 NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
545 NetstackManagedRoutesDesignation::InterfaceLocal => {
546 Self::InterfaceLocal(fnet_interfaces_admin::Empty)
547 }
548 }
549 }
550}
551
552#[derive(Debug, Deserialize, PartialEq)]
555#[serde(deny_unknown_fields, rename_all = "lowercase")]
556pub struct ProvisioningRule {
557 pub matchers: HashSet<ProvisioningMatchingRule>,
560 #[serde(flatten)]
563 pub action: ProvisioningAction,
564}
565
566pub(super) struct DeviceInfoRef<'a> {
571 pub(super) device_class: DeviceClass,
572 pub(super) mac: &'a fidl_fuchsia_net_ext::MacAddress,
573 pub(super) topological_path: &'a str,
574}
575
576impl<'a> DeviceInfoRef<'a> {
577 pub(super) fn interface_type(&self) -> crate::InterfaceType {
578 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
579 (*device_class).into()
580 }
581
582 pub(super) fn is_wlan_ap(&self) -> bool {
583 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
584 match device_class {
585 DeviceClass::WlanAp => true,
586 DeviceClass::WlanClient
587 | DeviceClass::Virtual
588 | DeviceClass::Ethernet
589 | DeviceClass::Bridge
590 | DeviceClass::Ppp
591 | DeviceClass::Lowpan
592 | DeviceClass::Blackhole => false,
593 }
594 }
595}
596
597impl ProvisioningRule {
598 fn does_interface_match(&self, info: &DeviceInfoRef<'_>, interface_name: &str) -> bool {
600 self.matchers
601 .iter()
602 .all(|rule| rule.does_interface_match(info, interface_name).unwrap_or_default())
603 }
604}
605
606pub(crate) fn find_provisioning_action_from_provisioning_rules(
611 provisioning_rules: &[ProvisioningRule],
612 info: &DeviceInfoRef<'_>,
613 interface_name: &str,
614) -> ProvisioningAction {
615 provisioning_rules
616 .iter()
617 .find_map(|rule| {
618 if rule.does_interface_match(&info, &interface_name) {
619 Some(rule.action)
620 } else {
621 None
622 }
623 })
624 .unwrap_or_default()
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use assert_matches::assert_matches;
631 use test_case::test_case;
632
633 fn device_class_from_interface_type(ty: crate::InterfaceType) -> DeviceClass {
637 match ty {
638 crate::InterfaceType::Ethernet => DeviceClass::Ethernet,
639 crate::InterfaceType::WlanClient => DeviceClass::WlanClient,
640 crate::InterfaceType::WlanAp => DeviceClass::WlanAp,
641 crate::InterfaceType::Blackhole => DeviceClass::Blackhole,
642 }
643 }
644
645 #[test_case(
647 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
648 [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
649 crate::InterfaceType::WlanClient,
650 "wlanx1";
651 "usb_wlan"
652 )]
653 #[test_case(
654 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
655 [0x02, 0x02, 0x02, 0x02, 0x02, 0x02],
656 crate::InterfaceType::Ethernet,
657 "ethx2";
658 "usb_eth"
659 )]
660 #[test_case(
662 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/ethernet",
663 [0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
664 crate::InterfaceType::WlanClient,
665 "wlanp0014";
666 "pci_wlan"
667 )]
668 #[test_case(
669 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:14.0/ethernet",
670 [0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
671 crate::InterfaceType::Ethernet,
672 "ethp0015";
673 "pci_eth"
674 )]
675 #[test_case(
677 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
678 [0x05, 0x05, 0x05, 0x05, 0x05, 0x05],
679 crate::InterfaceType::WlanClient,
680 "wlans05006";
681 "platform_wlan"
682 )]
683 #[test_case(
684 "/dev/sys/platform/04:02:7/aml-ethernet/Designware-MAC/ethernet",
685 [0x07, 0x07, 0x07, 0x07, 0x07, 0x07],
686 crate::InterfaceType::Ethernet,
687 "ethx7";
688 "platform_eth"
689 )]
690 #[test_case(
692 "/dev/sys/unknown",
693 [0x08, 0x08, 0x08, 0x08, 0x08, 0x08],
694 crate::InterfaceType::WlanClient,
695 "wlanx8";
696 "unknown_wlan1"
697 )]
698 #[test_case(
699 "unknown",
700 [0x09, 0x09, 0x09, 0x09, 0x09, 0x09],
701 crate::InterfaceType::WlanClient,
702 "wlanx9";
703 "unknown_wlan2"
704 )]
705 #[test_case(
706 "unknown",
707 [0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a],
708 crate::InterfaceType::WlanAp,
709 "apxa";
710 "unknown_ap"
711 )]
712 #[test_case(
713 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
714 [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b],
715 crate::InterfaceType::Ethernet,
716 "ethv001e";
717 "virtio_attached_ethernet"
718 )]
719 #[test_case(
721 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
722 [0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c],
723 crate::InterfaceType::Blackhole,
724 "blackholexc";
725 "usb_blackhole")]
726 fn test_generate_name(
727 topological_path: &'static str,
728 mac: [u8; 6],
729 interface_type: crate::InterfaceType,
730 want_name: &'static str,
731 ) {
732 let interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
733 let name = interface_naming_config
734 .generate_name(&DeviceInfoRef {
735 device_class: device_class_from_interface_type(interface_type),
736 mac: &fidl_fuchsia_net_ext::MacAddress { octets: mac },
737 topological_path,
738 })
739 .expect("failed to generate the name");
740 assert_eq!(name, want_name);
741 }
742
743 struct StableNameTestCase {
744 topological_path: &'static str,
745 mac: [u8; 6],
746 interface_type: crate::InterfaceType,
747 want_name: &'static str,
748 expected_size: usize,
749 }
750
751 #[test_case([StableNameTestCase {
753 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
754 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
755 interface_type: crate::InterfaceType::WlanClient,
756 want_name: "wlanp0014",
757 expected_size: 1 }];
758 "single_interface"
759 )]
760 #[test_case([StableNameTestCase {
763 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
764 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
765 interface_type: crate::InterfaceType::WlanClient,
766 want_name: "wlanp0014",
767 expected_size: 1}, StableNameTestCase {
768 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
769 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
770 interface_type: crate::InterfaceType::WlanAp,
771 want_name: "app0014",
772 expected_size: 2 }];
773 "two_interfaces_same_topo_path_different_mac"
774 )]
775 #[test_case([StableNameTestCase {
776 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
777 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
778 interface_type: crate::InterfaceType::WlanClient,
779 want_name: "wlanp0014",
780 expected_size: 1}, StableNameTestCase {
781 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
782 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
783 interface_type: crate::InterfaceType::Ethernet,
784 want_name: "ethp01",
785 expected_size: 2 }];
786 "two_distinct_interfaces"
787 )]
788 #[test_case([StableNameTestCase {
793 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
794 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
795 interface_type: crate::InterfaceType::Ethernet,
796 want_name: "ethp01",
797 expected_size: 1 }, StableNameTestCase {
798 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
799 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
800 interface_type: crate::InterfaceType::WlanClient,
801 want_name: "wlanp01",
802 expected_size: 1 }];
803 "two_interfaces_different_device_class"
804 )]
805 fn test_generate_stable_name(test_cases: impl IntoIterator<Item = StableNameTestCase>) {
806 let mut interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
807
808 for (
810 _i,
811 StableNameTestCase { topological_path, mac, interface_type, want_name, expected_size },
812 ) in test_cases.into_iter().enumerate()
813 {
814 let (name, _identifier) = interface_naming_config
815 .generate_stable_name(
816 topological_path,
817 &fidl_fuchsia_net_ext::MacAddress { octets: mac },
818 device_class_from_interface_type(interface_type),
819 )
820 .expect("failed to get the interface name");
821 assert_eq!(name, want_name);
822 assert_eq!(interface_naming_config.interfaces.len(), expected_size);
824 }
825 }
826
827 #[test]
828 fn test_get_usb_255() {
829 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
830
831 let mut config = InterfaceNamingConfig::from_naming_rules(vec![]);
833 for n in 0u8..255u8 {
834 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
835
836 let interface_naming_id =
837 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets });
838
839 let name = config
840 .generate_name(&DeviceInfoRef {
841 device_class: device_class_from_interface_type(
842 crate::InterfaceType::WlanClient,
843 ),
844 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
845 topological_path: topo_usb,
846 })
847 .expect("failed to generate the name");
848 assert_eq!(name, format!("{}{:x}", "wlanx", n));
849 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
850 }
851
852 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
853 assert!(config
854 .generate_name(&DeviceInfoRef {
855 device_class: device_class_from_interface_type(crate::InterfaceType::WlanClient),
856 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
857 topological_path: topo_usb
858 },)
859 .is_err());
860 }
861
862 #[test]
863 fn test_get_usb_255_with_naming_rule() {
864 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
865
866 let naming_rule = NamingRule {
867 matchers: HashSet::new(),
868 naming_scheme: vec![
869 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
870 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
871 ],
872 };
873
874 let mut config = InterfaceNamingConfig::from_naming_rules(vec![naming_rule]);
876 for n in 0u8..255u8 {
877 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
878 let interface_naming_id =
879 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets });
880
881 let info = DeviceInfoRef {
882 device_class: DeviceClass::Ethernet,
883 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
884 topological_path: topo_usb,
885 };
886
887 let name = config.generate_name(&info).expect("failed to generate the name");
888 assert_eq!(name, format!("{n:x}{n:x}"));
891
892 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
893 }
894
895 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
896 assert!(config
897 .generate_name(&DeviceInfoRef {
898 device_class: DeviceClass::Ethernet,
899 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
900 topological_path: topo_usb
901 })
902 .is_err());
903 }
904
905 fn default_device_info() -> DeviceInfoRef<'static> {
908 DeviceInfoRef {
909 device_class: DeviceClass::Ethernet,
910 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
911 topological_path: "",
912 }
913 }
914
915 #[test_case(
916 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
917 vec![BusType::PCI],
918 BusType::PCI,
919 true,
920 "0014";
921 "pci_match"
922 )]
923 #[test_case(
924 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
925 vec![BusType::USB, BusType::SDIO],
926 BusType::PCI,
927 false,
928 "0014";
929 "pci_no_match"
930 )]
931 #[test_case(
932 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
933 vec![BusType::USB],
934 BusType::USB,
935 true,
936 "0014";
937 "pci_usb_match"
938 )]
939 #[test_case(
940 "/dev/sys/platform/05:00:18/usb-phy-composite/aml_usb_phy/dwc2/dwc2_phy/dwc2/usb-peripheral/function-000/cdc-eth-function/netdevice-migration/network-device",
941 vec![BusType::USB],
942 BusType::USB,
943 true,
944 "050018";
945 "dwc_usb_match"
946 )]
947 #[test_case(
952 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
953 vec![BusType::PCI, BusType::SDIO],
954 BusType::USB,
955 false,
956 "0014";
957 "usb_no_match"
958 )]
959 #[test_case(
960 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
961 vec![BusType::SDIO],
962 BusType::SDIO,
963 true,
964 "05006";
965 "sdio_match"
966 )]
967 #[test_case(
968 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
969 vec![BusType::VirtIo],
970 BusType::VirtIo,
971 true,
972 "001e";
973 "virtio_match_alternate_location"
974 )]
975 #[test_case(
976 "/dev/sys/platform/pt/PC00/bus/<malformed>/00_1e_0/virtio-net/network-device",
977 vec![BusType::VirtIo],
978 BusType::VirtIo,
979 true,
980 "001e";
981 "virtio_matches_underscore_path"
982 )]
983 #[test_case(
984 "/dev/sys/platform/pt/PC00/bus/00:1e.1/00_1e_1/virtio-net/network-device",
985 vec![BusType::VirtIo],
986 BusType::VirtIo,
987 true,
988 "001e1";
989 "virtio_match_alternate_no_trim"
990 )]
991 #[test_case(
992 "/dev/sys/platform/pt/PC00/bus/<unrecognized_bus_path>/network-device",
993 vec![BusType::Unknown],
994 BusType::Unknown,
995 true,
996 "ffffff";
997 "unknown_bus_match_unrecognized"
998 )]
999 fn test_interface_matching_and_naming_by_bus_properties(
1000 topological_path: &'static str,
1001 bus_types: Vec<BusType>,
1002 expected_bus_type: BusType,
1003 want_match: bool,
1004 want_name: &'static str,
1005 ) {
1006 let device_info = DeviceInfoRef {
1007 topological_path: topological_path,
1008 ..default_device_info()
1011 };
1012
1013 let bus_type = get_bus_type_for_topological_path(&device_info.topological_path);
1016 assert_eq!(bus_type, expected_bus_type);
1017
1018 let matching_rule = MatchingRule::BusTypes(bus_types);
1020 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1021 assert_eq!(does_interface_match, want_match);
1022
1023 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1024 assert_eq!(name, want_name);
1025
1026 if want_name == "ffffff" {
1030 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1031 assert_eq!(name, "fffffe");
1032 }
1033 }
1034
1035 #[test_case(
1037 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1038 r"*[0-9][0-9]:[0-9][0-9]*",
1039 true;
1040 "pattern_matches"
1041 )]
1042 #[test_case("pattern/will/match/anything", r"*", true; "pattern_matches_any")]
1043 #[test_case(
1045 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1046 r"*[0-9][0-9]:00*",
1047 false;
1048 "no_matches"
1049 )]
1050 fn test_interface_matching_by_topological_path(
1051 topological_path: &'static str,
1052 glob_str: &'static str,
1053 want_match: bool,
1054 ) {
1055 let device_info = DeviceInfoRef {
1056 topological_path,
1057 ..default_device_info()
1060 };
1061
1062 let matching_rule = MatchingRule::TopologicalPath(glob::Pattern::new(glob_str).unwrap());
1064 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1065 assert_eq!(does_interface_match, want_match);
1066 }
1067
1068 #[test_case(
1070 "ethx5",
1071 r"ethx[0-9]*",
1072 true;
1073 "pattern_matches"
1074 )]
1075 #[test_case("arbitraryname", r"*", true; "pattern_matches_any")]
1076 #[test_case(
1078 "wlans1002",
1079 r"eths[0-9][0-9][0-9][0-9]*",
1080 false;
1081 "no_matches"
1082 )]
1083 fn test_interface_matching_by_interface_name(
1084 interface_name: &'static str,
1085 glob_str: &'static str,
1086 want_match: bool,
1087 ) {
1088 let provisioning_matching_rule = ProvisioningMatchingRule::InterfaceName {
1090 pattern: glob::Pattern::new(glob_str).unwrap(),
1091 };
1092 let does_interface_match = provisioning_matching_rule
1093 .does_interface_match(&default_device_info(), interface_name)
1094 .unwrap();
1095 assert_eq!(does_interface_match, want_match);
1096 }
1097
1098 #[test_case(
1099 DeviceClass::Ethernet,
1100 vec![DeviceClass::Ethernet],
1101 true;
1102 "eth_match"
1103 )]
1104 #[test_case(
1105 DeviceClass::Ethernet,
1106 vec![DeviceClass::WlanClient, DeviceClass::WlanAp],
1107 false;
1108 "eth_no_match"
1109 )]
1110 #[test_case(
1111 DeviceClass::WlanClient,
1112 vec![DeviceClass::WlanClient],
1113 true;
1114 "wlan_match"
1115 )]
1116 #[test_case(
1117 DeviceClass::WlanClient,
1118 vec![DeviceClass::Ethernet, DeviceClass::WlanAp],
1119 false;
1120 "wlan_no_match"
1121 )]
1122 #[test_case(
1123 DeviceClass::WlanAp,
1124 vec![DeviceClass::WlanAp],
1125 true;
1126 "ap_match"
1127 )]
1128 #[test_case(
1129 DeviceClass::WlanAp,
1130 vec![DeviceClass::Ethernet, DeviceClass::WlanClient],
1131 false;
1132 "ap_no_match"
1133 )]
1134 fn test_interface_matching_by_device_class(
1135 device_class: DeviceClass,
1136 device_classes: Vec<DeviceClass>,
1137 want_match: bool,
1138 ) {
1139 let device_info = DeviceInfoRef { device_class, ..default_device_info() };
1140
1141 let matching_rule = MatchingRule::DeviceClasses(device_classes);
1143 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1144 assert_eq!(does_interface_match, want_match);
1145 }
1146
1147 #[test_case(
1152 DeviceClass::Ethernet,
1153 "/dev/pci-00:15.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet"
1154 )]
1155 #[test_case(DeviceClass::WlanClient, "/dev/pci-00:14.0/ethernet")]
1156 fn test_interface_matching_by_any_matching_rule(
1157 device_class: DeviceClass,
1158 topological_path: &'static str,
1159 ) {
1160 let device_info = DeviceInfoRef {
1161 device_class,
1162 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1163 topological_path,
1164 };
1165
1166 let matching_rule = MatchingRule::Any(true);
1168 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1169 assert!(does_interface_match);
1170
1171 let matching_rule = MatchingRule::Any(false);
1173 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1174 assert!(!does_interface_match);
1175 }
1176
1177 #[test_case(
1178 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1179 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])],
1180 false;
1181 "false_single_rule"
1182 )]
1183 #[test_case(
1184 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1185 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient]), MatchingRule::Any(true)],
1186 false;
1187 "false_one_rule_of_multiple"
1188 )]
1189 #[test_case(
1190 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1191 vec![MatchingRule::Any(true)],
1192 true;
1193 "true_single_rule"
1194 )]
1195 #[test_case(
1196 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1197 vec![MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet]), MatchingRule::Any(true)],
1198 true;
1199 "true_multiple_rules"
1200 )]
1201 fn test_does_interface_match(
1202 info: DeviceInfoRef<'_>,
1203 matching_rules: Vec<MatchingRule>,
1204 want_match: bool,
1205 ) {
1206 let naming_rule =
1207 NamingRule { matchers: HashSet::from_iter(matching_rules), naming_scheme: Vec::new() };
1208 assert_eq!(naming_rule.does_interface_match(&info), want_match);
1209 }
1210
1211 #[test_case(
1212 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1213 "",
1214 vec![
1215 ProvisioningMatchingRule::Common(
1216 MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])
1217 )
1218 ],
1219 false;
1220 "false_single_rule"
1221 )]
1222 #[test_case(
1223 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1224 "wlanx5009",
1225 vec![
1226 ProvisioningMatchingRule::InterfaceName {
1227 pattern: glob::Pattern::new("ethx*").unwrap()
1228 },
1229 ProvisioningMatchingRule::Common(MatchingRule::Any(true))
1230 ],
1231 false;
1232 "false_one_rule_of_multiple"
1233 )]
1234 #[test_case(
1235 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1236 "",
1237 vec![ProvisioningMatchingRule::Common(MatchingRule::Any(true))],
1238 true;
1239 "true_single_rule"
1240 )]
1241 #[test_case(
1242 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1243 "wlanx5009",
1244 vec![
1245 ProvisioningMatchingRule::Common(
1246 MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet])
1247 ),
1248 ProvisioningMatchingRule::InterfaceName {
1249 pattern: glob::Pattern::new("wlanx*").unwrap()
1250 }
1251 ],
1252 true;
1253 "true_multiple_rules"
1254 )]
1255 fn test_does_interface_match_provisioning_rule(
1256 info: DeviceInfoRef<'_>,
1257 interface_name: &str,
1258 matching_rules: Vec<ProvisioningMatchingRule>,
1259 want_match: bool,
1260 ) {
1261 let provisioning_rule = ProvisioningRule {
1262 matchers: HashSet::from_iter(matching_rules),
1263 action: ProvisioningAction {
1264 provisioning: ProvisioningType::Local,
1265 ..Default::default()
1266 },
1267 };
1268 assert_eq!(provisioning_rule.does_interface_match(&info, interface_name), want_match);
1269 }
1270
1271 #[test_case(
1272 vec![NameCompositionRule::Static { value: String::from("x") }],
1273 default_device_info(),
1274 "x";
1275 "single_static"
1276 )]
1277 #[test_case(
1278 vec![
1279 NameCompositionRule::Static { value: String::from("eth") },
1280 NameCompositionRule::Static { value: String::from("x") },
1281 NameCompositionRule::Static { value: String::from("100") },
1282 ],
1283 default_device_info(),
1284 "ethx100";
1285 "multiple_static"
1286 )]
1287 #[test_case(
1288 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac }],
1289 DeviceInfoRef {
1290 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1291 ..default_device_info()
1292 },
1293 "1";
1294 "normalized_mac"
1295 )]
1296 #[test_case(
1297 vec![
1298 NameCompositionRule::Static { value: String::from("eth") },
1299 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1300 ],
1301 DeviceInfoRef {
1302 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x9] },
1303 ..default_device_info()
1304 },
1305 "eth9";
1306 "normalized_mac_with_static"
1307 )]
1308 #[test_case(
1309 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1310 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1311 "eth";
1312 "eth_device_class"
1313 )]
1314 #[test_case(
1315 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1316 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1317 "wlan";
1318 "wlan_device_class"
1319 )]
1320 #[test_case(
1321 vec![
1322 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1323 NameCompositionRule::Static { value: String::from("x") },
1324 ],
1325 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1326 "ethx";
1327 "device_class_with_static"
1328 )]
1329 #[test_case(
1330 vec![
1331 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1332 NameCompositionRule::Static { value: String::from("x") },
1333 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1334 ],
1335 DeviceInfoRef {
1336 device_class: DeviceClass::WlanClient,
1337 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x8] },
1338 ..default_device_info()
1339 },
1340 "wlanx8";
1341 "device_class_with_static_with_normalized_mac"
1342 )]
1343 #[test_case(
1344 vec![
1345 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1346 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1347 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1348 ],
1349 DeviceInfoRef {
1350 device_class: DeviceClass::Ethernet,
1351 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1352 ..default_device_info()
1353 },
1354 "ethp0014";
1355 "device_class_with_pci_bus_type_with_bus_path"
1356 )]
1357 #[test_case(
1358 vec![
1359 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1360 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1361 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1362 ],
1363 DeviceInfoRef {
1364 device_class: DeviceClass::Ethernet,
1365 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1366 ..default_device_info()
1367 },
1368 "ethu0014";
1369 "device_class_with_pci_usb_bus_type_with_bus_path"
1370 )]
1371 #[test_case(
1372 vec![
1373 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1374 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1375 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1376 ],
1377 DeviceInfoRef {
1378 device_class: DeviceClass::Ethernet,
1379 topological_path: "/dev/sys/platform/05:00:18/usb-phy-composite/aml_usb_phy/dwc2/dwc2_phy/dwc2/usb-peripheral/function-000/cdc-eth-function/netdevice-migration/network-device",
1380 ..default_device_info()
1381 },
1382 "ethu050018";
1383 "device_class_with_dwc_usb_bus_type_with_bus_path"
1384 )]
1385 #[test_case(
1386 vec![NameCompositionRule::Default],
1387 DeviceInfoRef {
1388 device_class: DeviceClass::Ethernet,
1389 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1390 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x2] },
1391 },
1392 "ethx2";
1393 "default_usb_pci"
1394 )]
1395 #[test_case(
1396 vec![NameCompositionRule::Default],
1397 DeviceInfoRef {
1398 device_class: DeviceClass::Ethernet,
1399 topological_path: "/dev/sys/platform/05:00:18/usb-phy-composite/aml_usb_phy/dwc2/dwc2_phy/dwc2/usb-peripheral/function-000/cdc-eth-function/netdevice-migration/network-device",
1400 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x3] },
1401 },
1402 "ethx3";
1403 "default_usb_dwc"
1404 )]
1405 #[test_case(
1406 vec![NameCompositionRule::Default],
1407 DeviceInfoRef {
1408 device_class: DeviceClass::Ethernet,
1409 topological_path: "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
1410 ..default_device_info()
1411 },
1412 "eths05006";
1413 "default_sdio"
1414 )]
1415 fn test_naming_rules(
1416 composition_rules: Vec<NameCompositionRule>,
1417 info: DeviceInfoRef<'_>,
1418 expected_name: &'static str,
1419 ) {
1420 let naming_rule = NamingRule { matchers: HashSet::new(), naming_scheme: composition_rules };
1421
1422 let name = naming_rule.generate_name(&HashMap::new(), &info);
1423 assert_eq!(name.unwrap(), expected_name.to_owned());
1424 }
1425
1426 #[test]
1427 fn test_generate_name_from_naming_rule_interface_name_exists_no_reattempt() {
1428 let shared_interface_name = "x".to_owned();
1429 let mut interfaces = HashMap::new();
1430 assert_matches!(
1431 interfaces.insert(
1432 InterfaceNamingIdentifier {
1433 mac: fidl_fuchsia_net_ext::MacAddress {
1434 octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1]
1435 },
1436 },
1437 shared_interface_name.clone(),
1438 ),
1439 None
1440 );
1441
1442 let naming_rule = NamingRule {
1443 matchers: HashSet::new(),
1444 naming_scheme: vec![NameCompositionRule::Static {
1445 value: shared_interface_name.clone(),
1446 }],
1447 };
1448
1449 let name = naming_rule.generate_name(&interfaces, &default_device_info()).unwrap();
1450 assert_eq!(name, shared_interface_name);
1451 }
1452
1453 #[test]
1457 fn test_generate_name_from_naming_rule_many_unique_macs() {
1458 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
1459
1460 let naming_rule = NamingRule {
1461 matchers: HashSet::new(),
1462 naming_scheme: vec![NameCompositionRule::Dynamic {
1463 rule: DynamicNameCompositionRule::NormalizedMac,
1464 }],
1465 };
1466
1467 let mut interfaces = HashMap::new();
1469
1470 for n in 0u8..255u8 {
1471 let octets = [0x01, 0x01, 0x01, 0x01, 0x01, n];
1472 let interface_naming_id =
1473 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets });
1474 let info = DeviceInfoRef {
1475 device_class: DeviceClass::Ethernet,
1476 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
1477 topological_path: topo_usb,
1478 };
1479
1480 let name =
1481 naming_rule.generate_name(&interfaces, &info).expect("failed to generate the name");
1482 assert_eq!(name, format!("{n:x}"));
1483
1484 assert_matches!(interfaces.insert(interface_naming_id, name.clone()), None);
1485 }
1486 }
1487
1488 #[test_case(true, "x"; "matches_first_rule")]
1489 #[test_case(false, "ethx1"; "fallback_default")]
1490 fn test_generate_name_from_naming_rules(match_first_rule: bool, expected_name: &'static str) {
1491 let info = DeviceInfoRef {
1494 device_class: DeviceClass::Ethernet,
1495 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1496 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet"
1497 };
1498 let name = generate_name_from_naming_rules(
1499 &[
1500 NamingRule {
1501 matchers: HashSet::from([MatchingRule::Any(match_first_rule)]),
1502 naming_scheme: vec![NameCompositionRule::Static { value: String::from("x") }],
1503 },
1504 NamingRule {
1507 matchers: HashSet::from([MatchingRule::Any(false)]),
1508 naming_scheme: vec![NameCompositionRule::Static { value: String::from("y") }],
1509 },
1510 ],
1511 &HashMap::new(),
1512 &info,
1513 )
1514 .unwrap();
1515 assert_eq!(name, expected_name.to_owned());
1516 }
1517
1518 #[test_case(true, ProvisioningType::Delegated; "matches_first_rule")]
1519 #[test_case(false, ProvisioningType::Local; "fallback_default")]
1520 fn test_find_provisioning_action_from_provisioning_rules(
1521 match_first_rule: bool,
1522 expected: ProvisioningType,
1523 ) {
1524 let provisioning_action = find_provisioning_action_from_provisioning_rules(
1525 &[ProvisioningRule {
1526 matchers: HashSet::from([ProvisioningMatchingRule::Common(MatchingRule::Any(
1527 match_first_rule,
1528 ))]),
1529 action: ProvisioningAction {
1530 provisioning: ProvisioningType::Delegated,
1531 ..Default::default()
1532 },
1533 }],
1534 &DeviceInfoRef {
1535 device_class: DeviceClass::WlanClient,
1536 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1537 topological_path: "",
1538 },
1539 "wlans5009",
1540 );
1541 assert_eq!(provisioning_action.provisioning, expected);
1542 }
1543}