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
537#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
541#[serde(deny_unknown_fields, rename_all = "snake_case")]
542pub enum NetstackManagedRoutesDesignation {
543 Main,
544 InterfaceLocal,
545}
546
547impl From<NetstackManagedRoutesDesignation>
548 for fnet_interfaces_admin::NetstackManagedRoutesDesignation
549{
550 fn from(value: NetstackManagedRoutesDesignation) -> Self {
551 match value {
552 NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
553 NetstackManagedRoutesDesignation::InterfaceLocal => {
554 Self::InterfaceLocal(fnet_interfaces_admin::Empty)
555 }
556 }
557 }
558}
559
560#[derive(Debug, Deserialize, PartialEq)]
563#[serde(deny_unknown_fields, rename_all = "lowercase")]
564pub struct ProvisioningRule {
565 pub matchers: HashSet<ProvisioningMatchingRule>,
568 #[serde(flatten)]
571 pub action: ProvisioningAction,
572}
573
574pub(super) struct DeviceInfoRef<'a> {
579 pub(super) device_class: DeviceClass,
580 pub(super) mac: &'a fidl_fuchsia_net_ext::MacAddress,
581 pub(super) topological_path: &'a str,
582}
583
584impl<'a> DeviceInfoRef<'a> {
585 pub(super) fn interface_type(&self) -> crate::InterfaceType {
586 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
587 (*device_class).into()
588 }
589
590 pub(super) fn is_wlan_ap(&self) -> bool {
591 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
592 match device_class {
593 DeviceClass::WlanAp => true,
594 DeviceClass::WlanClient
595 | DeviceClass::Virtual
596 | DeviceClass::Ethernet
597 | DeviceClass::Bridge
598 | DeviceClass::Ppp
599 | DeviceClass::Lowpan
600 | DeviceClass::Blackhole => false,
601 }
602 }
603}
604
605impl ProvisioningRule {
606 fn does_interface_match(&self, info: &DeviceInfoRef<'_>, interface_name: &str) -> bool {
608 self.matchers
609 .iter()
610 .all(|rule| rule.does_interface_match(info, interface_name).unwrap_or_default())
611 }
612}
613
614pub(crate) fn find_provisioning_action_from_provisioning_rules(
619 provisioning_rules: &[ProvisioningRule],
620 info: &DeviceInfoRef<'_>,
621 interface_name: &str,
622) -> ProvisioningAction {
623 provisioning_rules
624 .iter()
625 .find_map(|rule| {
626 if rule.does_interface_match(&info, &interface_name) { Some(rule.action) } else { None }
627 })
628 .unwrap_or_default()
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634 use assert_matches::assert_matches;
635 use test_case::test_case;
636
637 fn device_class_from_interface_type(ty: crate::InterfaceType) -> DeviceClass {
641 match ty {
642 crate::InterfaceType::Ethernet => DeviceClass::Ethernet,
643 crate::InterfaceType::WlanClient => DeviceClass::WlanClient,
644 crate::InterfaceType::WlanAp => DeviceClass::WlanAp,
645 crate::InterfaceType::Blackhole => DeviceClass::Blackhole,
646 }
647 }
648
649 #[test_case(
651 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
652 [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
653 crate::InterfaceType::WlanClient,
654 "wlanx1";
655 "usb_wlan"
656 )]
657 #[test_case(
658 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
659 [0x02, 0x02, 0x02, 0x02, 0x02, 0x02],
660 crate::InterfaceType::Ethernet,
661 "ethx2";
662 "usb_eth"
663 )]
664 #[test_case(
666 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/ethernet",
667 [0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
668 crate::InterfaceType::WlanClient,
669 "wlanp0014";
670 "pci_wlan"
671 )]
672 #[test_case(
673 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:14.0/ethernet",
674 [0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
675 crate::InterfaceType::Ethernet,
676 "ethp0015";
677 "pci_eth"
678 )]
679 #[test_case(
681 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
682 [0x05, 0x05, 0x05, 0x05, 0x05, 0x05],
683 crate::InterfaceType::WlanClient,
684 "wlans05006";
685 "platform_wlan"
686 )]
687 #[test_case(
688 "/dev/sys/platform/04:02:7/aml-ethernet/Designware-MAC/ethernet",
689 [0x07, 0x07, 0x07, 0x07, 0x07, 0x07],
690 crate::InterfaceType::Ethernet,
691 "ethx7";
692 "platform_eth"
693 )]
694 #[test_case(
696 "/dev/sys/unknown",
697 [0x08, 0x08, 0x08, 0x08, 0x08, 0x08],
698 crate::InterfaceType::WlanClient,
699 "wlanx8";
700 "unknown_wlan1"
701 )]
702 #[test_case(
703 "unknown",
704 [0x09, 0x09, 0x09, 0x09, 0x09, 0x09],
705 crate::InterfaceType::WlanClient,
706 "wlanx9";
707 "unknown_wlan2"
708 )]
709 #[test_case(
710 "unknown",
711 [0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a],
712 crate::InterfaceType::WlanAp,
713 "apxa";
714 "unknown_ap"
715 )]
716 #[test_case(
717 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
718 [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b],
719 crate::InterfaceType::Ethernet,
720 "ethv001e";
721 "virtio_attached_ethernet"
722 )]
723 #[test_case(
725 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
726 [0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c],
727 crate::InterfaceType::Blackhole,
728 "blackholexc";
729 "usb_blackhole")]
730 fn test_generate_name(
731 topological_path: &'static str,
732 mac: [u8; 6],
733 interface_type: crate::InterfaceType,
734 want_name: &'static str,
735 ) {
736 let interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
737 let name = interface_naming_config
738 .generate_name(&DeviceInfoRef {
739 device_class: device_class_from_interface_type(interface_type),
740 mac: &fidl_fuchsia_net_ext::MacAddress { octets: mac },
741 topological_path,
742 })
743 .expect("failed to generate the name");
744 assert_eq!(name, want_name);
745 }
746
747 struct StableNameTestCase {
748 topological_path: &'static str,
749 mac: [u8; 6],
750 interface_type: crate::InterfaceType,
751 want_name: &'static str,
752 expected_size: usize,
753 }
754
755 #[test_case([StableNameTestCase {
757 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
758 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
759 interface_type: crate::InterfaceType::WlanClient,
760 want_name: "wlanp0014",
761 expected_size: 1 }];
762 "single_interface"
763 )]
764 #[test_case([StableNameTestCase {
767 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
768 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
769 interface_type: crate::InterfaceType::WlanClient,
770 want_name: "wlanp0014",
771 expected_size: 1}, StableNameTestCase {
772 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
773 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
774 interface_type: crate::InterfaceType::WlanAp,
775 want_name: "app0014",
776 expected_size: 2 }];
777 "two_interfaces_same_topo_path_different_mac"
778 )]
779 #[test_case([StableNameTestCase {
780 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
781 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
782 interface_type: crate::InterfaceType::WlanClient,
783 want_name: "wlanp0014",
784 expected_size: 1}, StableNameTestCase {
785 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
786 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
787 interface_type: crate::InterfaceType::Ethernet,
788 want_name: "ethp01",
789 expected_size: 2 }];
790 "two_distinct_interfaces"
791 )]
792 #[test_case([StableNameTestCase {
797 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
798 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
799 interface_type: crate::InterfaceType::Ethernet,
800 want_name: "ethp01",
801 expected_size: 1 }, StableNameTestCase {
802 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
803 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
804 interface_type: crate::InterfaceType::WlanClient,
805 want_name: "wlanp01",
806 expected_size: 1 }];
807 "two_interfaces_different_device_class"
808 )]
809 fn test_generate_stable_name(test_cases: impl IntoIterator<Item = StableNameTestCase>) {
810 let mut interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
811
812 for (
814 _i,
815 StableNameTestCase { topological_path, mac, interface_type, want_name, expected_size },
816 ) in test_cases.into_iter().enumerate()
817 {
818 let (name, _identifier) = interface_naming_config
819 .generate_stable_name(
820 topological_path,
821 &fidl_fuchsia_net_ext::MacAddress { octets: mac },
822 device_class_from_interface_type(interface_type),
823 )
824 .expect("failed to get the interface name");
825 assert_eq!(name, want_name);
826 assert_eq!(interface_naming_config.interfaces.len(), expected_size);
828 }
829 }
830
831 #[test]
832 fn test_get_usb_255() {
833 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
834
835 let mut config = InterfaceNamingConfig::from_naming_rules(vec![]);
837 for n in 0u8..255u8 {
838 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
839
840 let interface_naming_id =
841 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
842
843 let name = config
844 .generate_name(&DeviceInfoRef {
845 device_class: device_class_from_interface_type(
846 crate::InterfaceType::WlanClient,
847 ),
848 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
849 topological_path: topo_usb,
850 })
851 .expect("failed to generate the name");
852 assert_eq!(name, format!("{}{:x}", "wlanx", n));
853 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
854 }
855
856 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
857 assert!(
858 config
859 .generate_name(&DeviceInfoRef {
860 device_class: device_class_from_interface_type(
861 crate::InterfaceType::WlanClient
862 ),
863 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
864 topological_path: topo_usb
865 },)
866 .is_err()
867 );
868 }
869
870 #[test]
871 fn test_get_usb_255_with_naming_rule() {
872 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
873
874 let naming_rule = NamingRule {
875 matchers: HashSet::new(),
876 naming_scheme: vec![
877 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
878 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
879 ],
880 };
881
882 let mut config = InterfaceNamingConfig::from_naming_rules(vec![naming_rule]);
884 for n in 0u8..255u8 {
885 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
886 let interface_naming_id =
887 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
888
889 let info = DeviceInfoRef {
890 device_class: DeviceClass::Ethernet,
891 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
892 topological_path: topo_usb,
893 };
894
895 let name = config.generate_name(&info).expect("failed to generate the name");
896 assert_eq!(name, format!("{n:x}{n:x}"));
899
900 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
901 }
902
903 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
904 assert!(
905 config
906 .generate_name(&DeviceInfoRef {
907 device_class: DeviceClass::Ethernet,
908 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
909 topological_path: topo_usb
910 })
911 .is_err()
912 );
913 }
914
915 fn default_device_info() -> DeviceInfoRef<'static> {
918 DeviceInfoRef {
919 device_class: DeviceClass::Ethernet,
920 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
921 topological_path: "",
922 }
923 }
924
925 #[test_case(
926 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
927 vec![BusType::PCI],
928 BusType::PCI,
929 true,
930 "0014";
931 "pci_match"
932 )]
933 #[test_case(
934 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
935 vec![BusType::USB, BusType::SDIO],
936 BusType::PCI,
937 false,
938 "0014";
939 "pci_no_match"
940 )]
941 #[test_case(
942 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
943 vec![BusType::USB],
944 BusType::USB,
945 true,
946 "0014";
947 "pci_usb_match"
948 )]
949 #[test_case(
950 "/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",
951 vec![BusType::USB],
952 BusType::USB,
953 true,
954 "050018";
955 "dwc_usb_match"
956 )]
957 #[test_case(
962 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
963 vec![BusType::PCI, BusType::SDIO],
964 BusType::USB,
965 false,
966 "0014";
967 "usb_no_match"
968 )]
969 #[test_case(
970 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
971 vec![BusType::SDIO],
972 BusType::SDIO,
973 true,
974 "05006";
975 "sdio_match"
976 )]
977 #[test_case(
978 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
979 vec![BusType::VirtIo],
980 BusType::VirtIo,
981 true,
982 "001e";
983 "virtio_match_alternate_location"
984 )]
985 #[test_case(
986 "/dev/sys/platform/pt/PC00/bus/<malformed>/00_1e_0/virtio-net/network-device",
987 vec![BusType::VirtIo],
988 BusType::VirtIo,
989 true,
990 "001e";
991 "virtio_matches_underscore_path"
992 )]
993 #[test_case(
994 "/dev/sys/platform/pt/PC00/bus/00:1e.1/00_1e_1/virtio-net/network-device",
995 vec![BusType::VirtIo],
996 BusType::VirtIo,
997 true,
998 "001e1";
999 "virtio_match_alternate_no_trim"
1000 )]
1001 #[test_case(
1002 "/dev/sys/platform/pt/PC00/bus/<unrecognized_bus_path>/network-device",
1003 vec![BusType::Unknown],
1004 BusType::Unknown,
1005 true,
1006 "ffffff";
1007 "unknown_bus_match_unrecognized"
1008 )]
1009 fn test_interface_matching_and_naming_by_bus_properties(
1010 topological_path: &'static str,
1011 bus_types: Vec<BusType>,
1012 expected_bus_type: BusType,
1013 want_match: bool,
1014 want_name: &'static str,
1015 ) {
1016 let device_info = DeviceInfoRef {
1017 topological_path: topological_path,
1018 ..default_device_info()
1021 };
1022
1023 let bus_type = get_bus_type_for_topological_path(&device_info.topological_path);
1026 assert_eq!(bus_type, expected_bus_type);
1027
1028 let matching_rule = MatchingRule::BusTypes(bus_types);
1030 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1031 assert_eq!(does_interface_match, want_match);
1032
1033 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1034 assert_eq!(name, want_name);
1035
1036 if want_name == "ffffff" {
1040 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1041 assert_eq!(name, "fffffe");
1042 }
1043 }
1044
1045 #[test_case(
1047 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1048 r"*[0-9][0-9]:[0-9][0-9]*",
1049 true;
1050 "pattern_matches"
1051 )]
1052 #[test_case("pattern/will/match/anything", r"*", true; "pattern_matches_any")]
1053 #[test_case(
1055 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1056 r"*[0-9][0-9]:00*",
1057 false;
1058 "no_matches"
1059 )]
1060 fn test_interface_matching_by_topological_path(
1061 topological_path: &'static str,
1062 glob_str: &'static str,
1063 want_match: bool,
1064 ) {
1065 let device_info = DeviceInfoRef {
1066 topological_path,
1067 ..default_device_info()
1070 };
1071
1072 let matching_rule = MatchingRule::TopologicalPath(glob::Pattern::new(glob_str).unwrap());
1074 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1075 assert_eq!(does_interface_match, want_match);
1076 }
1077
1078 #[test_case(
1080 "ethx5",
1081 r"ethx[0-9]*",
1082 true;
1083 "pattern_matches"
1084 )]
1085 #[test_case("arbitraryname", r"*", true; "pattern_matches_any")]
1086 #[test_case(
1088 "wlans1002",
1089 r"eths[0-9][0-9][0-9][0-9]*",
1090 false;
1091 "no_matches"
1092 )]
1093 fn test_interface_matching_by_interface_name(
1094 interface_name: &'static str,
1095 glob_str: &'static str,
1096 want_match: bool,
1097 ) {
1098 let provisioning_matching_rule = ProvisioningMatchingRule::InterfaceName {
1100 pattern: glob::Pattern::new(glob_str).unwrap(),
1101 };
1102 let does_interface_match = provisioning_matching_rule
1103 .does_interface_match(&default_device_info(), interface_name)
1104 .unwrap();
1105 assert_eq!(does_interface_match, want_match);
1106 }
1107
1108 #[test_case(
1109 DeviceClass::Ethernet,
1110 vec![DeviceClass::Ethernet],
1111 true;
1112 "eth_match"
1113 )]
1114 #[test_case(
1115 DeviceClass::Ethernet,
1116 vec![DeviceClass::WlanClient, DeviceClass::WlanAp],
1117 false;
1118 "eth_no_match"
1119 )]
1120 #[test_case(
1121 DeviceClass::WlanClient,
1122 vec![DeviceClass::WlanClient],
1123 true;
1124 "wlan_match"
1125 )]
1126 #[test_case(
1127 DeviceClass::WlanClient,
1128 vec![DeviceClass::Ethernet, DeviceClass::WlanAp],
1129 false;
1130 "wlan_no_match"
1131 )]
1132 #[test_case(
1133 DeviceClass::WlanAp,
1134 vec![DeviceClass::WlanAp],
1135 true;
1136 "ap_match"
1137 )]
1138 #[test_case(
1139 DeviceClass::WlanAp,
1140 vec![DeviceClass::Ethernet, DeviceClass::WlanClient],
1141 false;
1142 "ap_no_match"
1143 )]
1144 fn test_interface_matching_by_device_class(
1145 device_class: DeviceClass,
1146 device_classes: Vec<DeviceClass>,
1147 want_match: bool,
1148 ) {
1149 let device_info = DeviceInfoRef { device_class, ..default_device_info() };
1150
1151 let matching_rule = MatchingRule::DeviceClasses(device_classes);
1153 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1154 assert_eq!(does_interface_match, want_match);
1155 }
1156
1157 #[test_case(
1162 DeviceClass::Ethernet,
1163 "/dev/pci-00:15.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet"
1164 )]
1165 #[test_case(DeviceClass::WlanClient, "/dev/pci-00:14.0/ethernet")]
1166 fn test_interface_matching_by_any_matching_rule(
1167 device_class: DeviceClass,
1168 topological_path: &'static str,
1169 ) {
1170 let device_info = DeviceInfoRef {
1171 device_class,
1172 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1173 topological_path,
1174 };
1175
1176 let matching_rule = MatchingRule::Any(true);
1178 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1179 assert!(does_interface_match);
1180
1181 let matching_rule = MatchingRule::Any(false);
1183 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1184 assert!(!does_interface_match);
1185 }
1186
1187 #[test_case(
1188 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1189 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])],
1190 false;
1191 "false_single_rule"
1192 )]
1193 #[test_case(
1194 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1195 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient]), MatchingRule::Any(true)],
1196 false;
1197 "false_one_rule_of_multiple"
1198 )]
1199 #[test_case(
1200 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1201 vec![MatchingRule::Any(true)],
1202 true;
1203 "true_single_rule"
1204 )]
1205 #[test_case(
1206 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1207 vec![MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet]), MatchingRule::Any(true)],
1208 true;
1209 "true_multiple_rules"
1210 )]
1211 fn test_does_interface_match(
1212 info: DeviceInfoRef<'_>,
1213 matching_rules: Vec<MatchingRule>,
1214 want_match: bool,
1215 ) {
1216 let naming_rule =
1217 NamingRule { matchers: HashSet::from_iter(matching_rules), naming_scheme: Vec::new() };
1218 assert_eq!(naming_rule.does_interface_match(&info), want_match);
1219 }
1220
1221 #[test_case(
1222 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1223 "",
1224 vec![
1225 ProvisioningMatchingRule::Common(
1226 MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])
1227 )
1228 ],
1229 false;
1230 "false_single_rule"
1231 )]
1232 #[test_case(
1233 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1234 "wlanx5009",
1235 vec![
1236 ProvisioningMatchingRule::InterfaceName {
1237 pattern: glob::Pattern::new("ethx*").unwrap()
1238 },
1239 ProvisioningMatchingRule::Common(MatchingRule::Any(true))
1240 ],
1241 false;
1242 "false_one_rule_of_multiple"
1243 )]
1244 #[test_case(
1245 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1246 "",
1247 vec![ProvisioningMatchingRule::Common(MatchingRule::Any(true))],
1248 true;
1249 "true_single_rule"
1250 )]
1251 #[test_case(
1252 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1253 "wlanx5009",
1254 vec![
1255 ProvisioningMatchingRule::Common(
1256 MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet])
1257 ),
1258 ProvisioningMatchingRule::InterfaceName {
1259 pattern: glob::Pattern::new("wlanx*").unwrap()
1260 }
1261 ],
1262 true;
1263 "true_multiple_rules"
1264 )]
1265 fn test_does_interface_match_provisioning_rule(
1266 info: DeviceInfoRef<'_>,
1267 interface_name: &str,
1268 matching_rules: Vec<ProvisioningMatchingRule>,
1269 want_match: bool,
1270 ) {
1271 let provisioning_rule = ProvisioningRule {
1272 matchers: HashSet::from_iter(matching_rules),
1273 action: ProvisioningAction {
1274 provisioning: ProvisioningType::Local,
1275 ..Default::default()
1276 },
1277 };
1278 assert_eq!(provisioning_rule.does_interface_match(&info, interface_name), want_match);
1279 }
1280
1281 #[test_case(
1282 vec![NameCompositionRule::Static { value: String::from("x") }],
1283 default_device_info(),
1284 "x";
1285 "single_static"
1286 )]
1287 #[test_case(
1288 vec![
1289 NameCompositionRule::Static { value: String::from("eth") },
1290 NameCompositionRule::Static { value: String::from("x") },
1291 NameCompositionRule::Static { value: String::from("100") },
1292 ],
1293 default_device_info(),
1294 "ethx100";
1295 "multiple_static"
1296 )]
1297 #[test_case(
1298 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac }],
1299 DeviceInfoRef {
1300 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1301 ..default_device_info()
1302 },
1303 "1";
1304 "normalized_mac"
1305 )]
1306 #[test_case(
1307 vec![
1308 NameCompositionRule::Static { value: String::from("eth") },
1309 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1310 ],
1311 DeviceInfoRef {
1312 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x9] },
1313 ..default_device_info()
1314 },
1315 "eth9";
1316 "normalized_mac_with_static"
1317 )]
1318 #[test_case(
1319 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1320 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1321 "eth";
1322 "eth_device_class"
1323 )]
1324 #[test_case(
1325 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1326 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1327 "wlan";
1328 "wlan_device_class"
1329 )]
1330 #[test_case(
1331 vec![
1332 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1333 NameCompositionRule::Static { value: String::from("x") },
1334 ],
1335 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1336 "ethx";
1337 "device_class_with_static"
1338 )]
1339 #[test_case(
1340 vec![
1341 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1342 NameCompositionRule::Static { value: String::from("x") },
1343 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1344 ],
1345 DeviceInfoRef {
1346 device_class: DeviceClass::WlanClient,
1347 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x8] },
1348 ..default_device_info()
1349 },
1350 "wlanx8";
1351 "device_class_with_static_with_normalized_mac"
1352 )]
1353 #[test_case(
1354 vec![
1355 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1356 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1357 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1358 ],
1359 DeviceInfoRef {
1360 device_class: DeviceClass::Ethernet,
1361 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1362 ..default_device_info()
1363 },
1364 "ethp0014";
1365 "device_class_with_pci_bus_type_with_bus_path"
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/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1376 ..default_device_info()
1377 },
1378 "ethu0014";
1379 "device_class_with_pci_usb_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/05:00:18/usb-phy-composite/aml_usb_phy/dwc2/dwc2_phy/dwc2/usb-peripheral/function-000/cdc-eth-function/netdevice-migration/network-device",
1390 ..default_device_info()
1391 },
1392 "ethu050018";
1393 "device_class_with_dwc_usb_bus_type_with_bus_path"
1394 )]
1395 #[test_case(
1396 vec![NameCompositionRule::Default],
1397 DeviceInfoRef {
1398 device_class: DeviceClass::Ethernet,
1399 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1400 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x2] },
1401 },
1402 "ethx2";
1403 "default_usb_pci"
1404 )]
1405 #[test_case(
1406 vec![NameCompositionRule::Default],
1407 DeviceInfoRef {
1408 device_class: DeviceClass::Ethernet,
1409 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",
1410 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x3] },
1411 },
1412 "ethx3";
1413 "default_usb_dwc"
1414 )]
1415 #[test_case(
1416 vec![NameCompositionRule::Default],
1417 DeviceInfoRef {
1418 device_class: DeviceClass::Ethernet,
1419 topological_path: "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy/wlanphy",
1420 ..default_device_info()
1421 },
1422 "eths05006";
1423 "default_sdio"
1424 )]
1425 fn test_naming_rules(
1426 composition_rules: Vec<NameCompositionRule>,
1427 info: DeviceInfoRef<'_>,
1428 expected_name: &'static str,
1429 ) {
1430 let naming_rule = NamingRule { matchers: HashSet::new(), naming_scheme: composition_rules };
1431
1432 let name = naming_rule.generate_name(&HashMap::new(), &info);
1433 assert_eq!(name.unwrap(), expected_name.to_owned());
1434 }
1435
1436 #[test]
1437 fn test_generate_name_from_naming_rule_interface_name_exists_no_reattempt() {
1438 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
1439
1440 let shared_interface_name = "x".to_owned();
1441 let mut interfaces = HashMap::new();
1442 assert_matches!(
1443 interfaces.insert(
1444 InterfaceNamingIdentifier {
1445 mac: fidl_fuchsia_net_ext::MacAddress {
1446 octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1]
1447 },
1448 topological_path: topo_usb.to_string()
1449 },
1450 shared_interface_name.clone(),
1451 ),
1452 None
1453 );
1454
1455 let naming_rule = NamingRule {
1456 matchers: HashSet::new(),
1457 naming_scheme: vec![NameCompositionRule::Static {
1458 value: shared_interface_name.clone(),
1459 }],
1460 };
1461
1462 let name = naming_rule.generate_name(&interfaces, &default_device_info()).unwrap();
1463 assert_eq!(name, shared_interface_name);
1464 }
1465
1466 #[test]
1470 fn test_generate_name_from_naming_rule_many_unique_macs() {
1471 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
1472
1473 let naming_rule = NamingRule {
1474 matchers: HashSet::new(),
1475 naming_scheme: vec![NameCompositionRule::Dynamic {
1476 rule: DynamicNameCompositionRule::NormalizedMac,
1477 }],
1478 };
1479
1480 let mut interfaces = HashMap::new();
1482
1483 for n in 0u8..255u8 {
1484 let octets = [0x01, 0x01, 0x01, 0x01, 0x01, n];
1485 let interface_naming_id =
1486 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
1487 let info = DeviceInfoRef {
1488 device_class: DeviceClass::Ethernet,
1489 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
1490 topological_path: topo_usb,
1491 };
1492
1493 let name =
1494 naming_rule.generate_name(&interfaces, &info).expect("failed to generate the name");
1495 assert_eq!(name, format!("{n:x}"));
1496
1497 assert_matches!(interfaces.insert(interface_naming_id, name.clone()), None);
1498 }
1499 }
1500
1501 #[test_case(true, "x"; "matches_first_rule")]
1502 #[test_case(false, "ethx1"; "fallback_default")]
1503 fn test_generate_name_from_naming_rules(match_first_rule: bool, expected_name: &'static str) {
1504 let info = DeviceInfoRef {
1507 device_class: DeviceClass::Ethernet,
1508 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1509 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1510 };
1511 let name = generate_name_from_naming_rules(
1512 &[
1513 NamingRule {
1514 matchers: HashSet::from([MatchingRule::Any(match_first_rule)]),
1515 naming_scheme: vec![NameCompositionRule::Static { value: String::from("x") }],
1516 },
1517 NamingRule {
1520 matchers: HashSet::from([MatchingRule::Any(false)]),
1521 naming_scheme: vec![NameCompositionRule::Static { value: String::from("y") }],
1522 },
1523 ],
1524 &HashMap::new(),
1525 &info,
1526 )
1527 .unwrap();
1528 assert_eq!(name, expected_name.to_owned());
1529 }
1530
1531 #[test_case(true, ProvisioningType::Delegated; "matches_first_rule")]
1532 #[test_case(false, ProvisioningType::Local; "fallback_default")]
1533 fn test_find_provisioning_action_from_provisioning_rules(
1534 match_first_rule: bool,
1535 expected: ProvisioningType,
1536 ) {
1537 let provisioning_action = find_provisioning_action_from_provisioning_rules(
1538 &[ProvisioningRule {
1539 matchers: HashSet::from([ProvisioningMatchingRule::Common(MatchingRule::Any(
1540 match_first_rule,
1541 ))]),
1542 action: ProvisioningAction {
1543 provisioning: ProvisioningType::Delegated,
1544 ..Default::default()
1545 },
1546 }],
1547 &DeviceInfoRef {
1548 device_class: DeviceClass::WlanClient,
1549 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1550 topological_path: "",
1551 },
1552 "wlans5009",
1553 );
1554 assert_eq!(provisioning_action.provisioning, expected);
1555 }
1556}