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