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::Lowpan => {
363 unreachable!("unexpected to install lowpan interface")
364 }
365 crate::InterfaceType::WlanAp => INTERFACE_PREFIX_AP,
366 crate::InterfaceType::Blackhole => INTERFACE_PREFIX_BLACKHOLE,
367 }
368 .to_string(),
369 DynamicNameCompositionRule::NormalizedMac => {
370 let fidl_fuchsia_net_ext::MacAddress { octets } = info.mac;
371 let mac_identifier =
372 get_mac_identifier_from_octets(octets, info.device_class.into(), attempt_num)?;
373 format!("{mac_identifier:x}")
374 }
375 })
376 }
377}
378
379#[derive(Clone, Debug, Deserialize, PartialEq)]
383#[serde(deny_unknown_fields, rename_all = "lowercase", tag = "type")]
384pub enum NameCompositionRule {
385 Static { value: String },
386 Dynamic { rule: DynamicNameCompositionRule },
387 Default,
390}
391
392#[derive(Debug, Deserialize, PartialEq)]
395#[serde(deny_unknown_fields, rename_all = "lowercase")]
396pub struct NamingRule {
397 pub matchers: HashSet<MatchingRule>,
400 pub naming_scheme: Vec<NameCompositionRule>,
402}
403
404impl NamingRule {
405 fn generate_name(
409 &self,
410 interfaces: &HashMap<InterfaceNamingIdentifier, String>,
411 info: &DeviceInfoRef<'_>,
412 ) -> Result<String, NameGenerationError> {
413 let bus_type = get_bus_type_for_topological_path(&info.topological_path);
416
417 let expanded_rules = self
422 .naming_scheme
423 .iter()
424 .map(|rule| {
425 if let NameCompositionRule::Default = rule {
426 Either::Right(bus_type.get_default_name_composition_rules().into_iter())
427 } else {
428 Either::Left(std::iter::once(rule.clone()))
429 }
430 })
431 .flatten()
432 .collect::<Vec<_>>();
433
434 let should_reattempt_on_conflict = expanded_rules.iter().any(|rule| {
436 if let NameCompositionRule::Dynamic { rule } = rule {
437 rule.supports_retry()
438 } else {
439 false
440 }
441 });
442
443 let mut attempt_num = 0u8;
444 loop {
445 let name = expanded_rules
446 .iter()
447 .map(|rule| match rule {
448 NameCompositionRule::Static { value } => Ok(value.clone()),
449 NameCompositionRule::Dynamic { rule } => rule
451 .get_name(info, attempt_num)
452 .map_err(NameGenerationError::GenerationError),
453 NameCompositionRule::Default => {
454 unreachable!(
455 "Default naming rules should have been pre-expanded. \
456 Nested default rules are not supported."
457 );
458 }
459 })
460 .collect::<Result<String, NameGenerationError>>()?;
461
462 if interfaces.values().any(|existing_name| existing_name == &name) {
463 if should_reattempt_on_conflict {
464 attempt_num += 1;
465 continue;
467 }
468
469 log::warn!(
470 "name ({name}) already used for an interface installed by netcfg. \
471 using name since it is possible that the interface using this name is no \
472 longer active"
473 );
474 }
475 return Ok(name);
476 }
477 }
478
479 fn does_interface_match(&self, info: &DeviceInfoRef<'_>) -> bool {
481 self.matchers.iter().all(|rule| rule.does_interface_match(info).unwrap_or_default())
482 }
483}
484
485fn generate_name_from_naming_rules(
488 naming_rules: &[NamingRule],
489 interfaces: &HashMap<InterfaceNamingIdentifier, String>,
490 info: &DeviceInfoRef<'_>,
491) -> Result<String, NameGenerationError> {
492 let fallback_rule = fallback_naming_rule();
497 let first_matching_rule =
498 naming_rules.iter().find(|rule| rule.does_interface_match(&info)).unwrap_or(
499 &fallback_rule,
502 );
503
504 first_matching_rule.generate_name(interfaces, &info)
505}
506
507fn fallback_naming_rule() -> NamingRule {
509 NamingRule {
510 matchers: HashSet::from([MatchingRule::Any(true)]),
511 naming_scheme: vec![NameCompositionRule::Default],
512 }
513}
514
515#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Default)]
517#[serde(deny_unknown_fields, rename_all = "lowercase")]
518pub struct ProvisioningAction {
519 pub provisioning: ProvisioningType,
521 pub netstack_managed_routes_designation: Option<NetstackManagedRoutesDesignation>,
523}
524
525#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Default)]
531#[serde(deny_unknown_fields, rename_all = "lowercase")]
532pub enum ProvisioningType {
533 #[default]
535 Local,
536 Delegated,
539}
540
541impl ProvisioningType {
542 pub fn track_in_network_registry(&self) -> bool {
548 match self {
549 ProvisioningType::Local => true,
550 ProvisioningType::Delegated => false,
551 }
552 }
553}
554
555#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
559#[serde(deny_unknown_fields, rename_all = "snake_case")]
560pub enum NetstackManagedRoutesDesignation {
561 Main,
562 InterfaceLocal,
563}
564
565impl From<NetstackManagedRoutesDesignation>
566 for fnet_interfaces_admin::NetstackManagedRoutesDesignation
567{
568 fn from(value: NetstackManagedRoutesDesignation) -> Self {
569 match value {
570 NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
571 NetstackManagedRoutesDesignation::InterfaceLocal => {
572 Self::InterfaceLocal(fnet_interfaces_admin::Empty)
573 }
574 }
575 }
576}
577
578#[derive(Debug, Deserialize, PartialEq)]
581#[serde(deny_unknown_fields, rename_all = "lowercase")]
582pub struct ProvisioningRule {
583 pub matchers: HashSet<ProvisioningMatchingRule>,
586 #[serde(flatten)]
589 pub action: ProvisioningAction,
590}
591
592pub(super) struct DeviceInfoRef<'a> {
597 pub(super) device_class: DeviceClass,
598 pub(super) mac: &'a fidl_fuchsia_net_ext::MacAddress,
599 pub(super) topological_path: &'a str,
600}
601
602impl<'a> DeviceInfoRef<'a> {
603 pub(super) fn interface_type(&self) -> crate::InterfaceType {
604 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
605 (*device_class).into()
606 }
607
608 pub(super) fn is_wlan_ap(&self) -> bool {
609 let DeviceInfoRef { device_class, mac: _, topological_path: _ } = self;
610 match device_class {
611 DeviceClass::WlanAp => true,
612 DeviceClass::WlanClient
613 | DeviceClass::Virtual
614 | DeviceClass::Ethernet
615 | DeviceClass::Bridge
616 | DeviceClass::Ppp
617 | DeviceClass::Lowpan
618 | DeviceClass::Blackhole => false,
619 }
620 }
621}
622
623impl ProvisioningRule {
624 fn does_interface_match(&self, info: &DeviceInfoRef<'_>, interface_name: &str) -> bool {
626 self.matchers
627 .iter()
628 .all(|rule| rule.does_interface_match(info, interface_name).unwrap_or_default())
629 }
630}
631
632pub(crate) fn find_provisioning_action_from_provisioning_rules(
637 provisioning_rules: &[ProvisioningRule],
638 info: &DeviceInfoRef<'_>,
639 interface_name: &str,
640) -> ProvisioningAction {
641 provisioning_rules
642 .iter()
643 .find_map(|rule| {
644 if rule.does_interface_match(&info, &interface_name) { Some(rule.action) } else { None }
645 })
646 .unwrap_or_default()
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652 use assert_matches::assert_matches;
653 use test_case::test_case;
654
655 fn device_class_from_interface_type(ty: crate::InterfaceType) -> DeviceClass {
659 match ty {
660 crate::InterfaceType::Ethernet => DeviceClass::Ethernet,
661 crate::InterfaceType::Lowpan => DeviceClass::Lowpan,
662 crate::InterfaceType::WlanClient => DeviceClass::WlanClient,
663 crate::InterfaceType::WlanAp => DeviceClass::WlanAp,
664 crate::InterfaceType::Blackhole => DeviceClass::Blackhole,
665 }
666 }
667
668 #[test_case(
670 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
671 [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
672 crate::InterfaceType::WlanClient,
673 "wlanx1";
674 "usb_wlan"
675 )]
676 #[test_case(
677 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
678 [0x02, 0x02, 0x02, 0x02, 0x02, 0x02],
679 crate::InterfaceType::Ethernet,
680 "ethx2";
681 "usb_eth"
682 )]
683 #[test_case(
685 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/ethernet",
686 [0x03, 0x03, 0x03, 0x03, 0x03, 0x03],
687 crate::InterfaceType::WlanClient,
688 "wlanp0014";
689 "pci_wlan"
690 )]
691 #[test_case(
692 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:14.0/ethernet",
693 [0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
694 crate::InterfaceType::Ethernet,
695 "ethp0015";
696 "pci_eth"
697 )]
698 #[test_case(
700 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy",
701 [0x05, 0x05, 0x05, 0x05, 0x05, 0x05],
702 crate::InterfaceType::WlanClient,
703 "wlans05006";
704 "platform_wlan"
705 )]
706 #[test_case(
707 "/dev/sys/platform/04:02:7/aml-ethernet/Designware-MAC/ethernet",
708 [0x07, 0x07, 0x07, 0x07, 0x07, 0x07],
709 crate::InterfaceType::Ethernet,
710 "ethx7";
711 "platform_eth"
712 )]
713 #[test_case(
715 "/dev/sys/unknown",
716 [0x08, 0x08, 0x08, 0x08, 0x08, 0x08],
717 crate::InterfaceType::WlanClient,
718 "wlanx8";
719 "unknown_wlan1"
720 )]
721 #[test_case(
722 "unknown",
723 [0x09, 0x09, 0x09, 0x09, 0x09, 0x09],
724 crate::InterfaceType::WlanClient,
725 "wlanx9";
726 "unknown_wlan2"
727 )]
728 #[test_case(
729 "unknown",
730 [0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a],
731 crate::InterfaceType::WlanAp,
732 "apxa";
733 "unknown_ap"
734 )]
735 #[test_case(
736 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
737 [0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b],
738 crate::InterfaceType::Ethernet,
739 "ethv001e";
740 "virtio_attached_ethernet"
741 )]
742 #[test_case(
744 "/dev/sys/platform/pt/PCI0/bus/00:15.0/00:15.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
745 [0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c],
746 crate::InterfaceType::Blackhole,
747 "blackholexc";
748 "usb_blackhole")]
749 fn test_generate_name(
750 topological_path: &'static str,
751 mac: [u8; 6],
752 interface_type: crate::InterfaceType,
753 want_name: &'static str,
754 ) {
755 let interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
756 let name = interface_naming_config
757 .generate_name(&DeviceInfoRef {
758 device_class: device_class_from_interface_type(interface_type),
759 mac: &fidl_fuchsia_net_ext::MacAddress { octets: mac },
760 topological_path,
761 })
762 .expect("failed to generate the name");
763 assert_eq!(name, want_name);
764 }
765
766 struct StableNameTestCase {
767 topological_path: &'static str,
768 mac: [u8; 6],
769 interface_type: crate::InterfaceType,
770 want_name: &'static str,
771 expected_size: usize,
772 }
773
774 #[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 }];
781 "single_interface"
782 )]
783 #[test_case([StableNameTestCase {
786 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
787 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
788 interface_type: crate::InterfaceType::WlanClient,
789 want_name: "wlanp0014",
790 expected_size: 1}, StableNameTestCase {
791 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
792 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
793 interface_type: crate::InterfaceType::WlanAp,
794 want_name: "app0014",
795 expected_size: 2 }];
796 "two_interfaces_same_topo_path_different_mac"
797 )]
798 #[test_case([StableNameTestCase {
799 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
800 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
801 interface_type: crate::InterfaceType::WlanClient,
802 want_name: "wlanp0014",
803 expected_size: 1}, StableNameTestCase {
804 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
805 mac: [0xFE, 0x01, 0x01, 0x01, 0x01, 0x01],
806 interface_type: crate::InterfaceType::Ethernet,
807 want_name: "ethp01",
808 expected_size: 2 }];
809 "two_distinct_interfaces"
810 )]
811 #[test_case([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::Ethernet,
819 want_name: "ethp01",
820 expected_size: 1 }, StableNameTestCase {
821 topological_path: "/dev/sys/platform/pt/PCI0/bus/01:00.0/01:00.0/iwlwifi-wlan-softmac/wlan-ethernet/ethernet",
822 mac: [0x01, 0x01, 0x01, 0x01, 0x01, 0x01],
823 interface_type: crate::InterfaceType::WlanClient,
824 want_name: "wlanp01",
825 expected_size: 1 }];
826 "two_interfaces_different_device_class"
827 )]
828 fn test_generate_stable_name(test_cases: impl IntoIterator<Item = StableNameTestCase>) {
829 let mut interface_naming_config = InterfaceNamingConfig::from_naming_rules(vec![]);
830
831 for (
833 _i,
834 StableNameTestCase { topological_path, mac, interface_type, want_name, expected_size },
835 ) in test_cases.into_iter().enumerate()
836 {
837 let (name, _identifier) = interface_naming_config
838 .generate_stable_name(
839 topological_path,
840 &fidl_fuchsia_net_ext::MacAddress { octets: mac },
841 device_class_from_interface_type(interface_type),
842 )
843 .expect("failed to get the interface name");
844 assert_eq!(name, want_name);
845 assert_eq!(interface_naming_config.interfaces.len(), expected_size);
847 }
848 }
849
850 #[test]
851 fn test_get_usb_255() {
852 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
853
854 let mut config = InterfaceNamingConfig::from_naming_rules(vec![]);
856 for n in 0u8..255u8 {
857 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
858
859 let interface_naming_id =
860 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
861
862 let name = config
863 .generate_name(&DeviceInfoRef {
864 device_class: device_class_from_interface_type(
865 crate::InterfaceType::WlanClient,
866 ),
867 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
868 topological_path: topo_usb,
869 })
870 .expect("failed to generate the name");
871 assert_eq!(name, format!("{}{:x}", "wlanx", n));
872 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
873 }
874
875 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
876 assert!(
877 config
878 .generate_name(&DeviceInfoRef {
879 device_class: device_class_from_interface_type(
880 crate::InterfaceType::WlanClient
881 ),
882 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
883 topological_path: topo_usb
884 },)
885 .is_err()
886 );
887 }
888
889 #[test]
890 fn test_get_usb_255_with_naming_rule() {
891 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
892
893 let naming_rule = NamingRule {
894 matchers: HashSet::new(),
895 naming_scheme: vec![
896 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
897 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
898 ],
899 };
900
901 let mut config = InterfaceNamingConfig::from_naming_rules(vec![naming_rule]);
903 for n in 0u8..255u8 {
904 let octets = [n, 0x01, 0x01, 0x01, 0x01, 00];
905 let interface_naming_id =
906 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
907
908 let info = DeviceInfoRef {
909 device_class: DeviceClass::Ethernet,
910 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
911 topological_path: topo_usb,
912 };
913
914 let name = config.generate_name(&info).expect("failed to generate the name");
915 assert_eq!(name, format!("{n:x}{n:x}"));
918
919 assert_matches!(config.interfaces.insert(interface_naming_id, name), None);
920 }
921
922 let octets = [0x00, 0x00, 0x01, 0x01, 0x01, 00];
923 assert!(
924 config
925 .generate_name(&DeviceInfoRef {
926 device_class: DeviceClass::Ethernet,
927 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
928 topological_path: topo_usb
929 })
930 .is_err()
931 );
932 }
933
934 fn default_device_info() -> DeviceInfoRef<'static> {
937 DeviceInfoRef {
938 device_class: DeviceClass::Ethernet,
939 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
940 topological_path: "",
941 }
942 }
943
944 #[test_case(
945 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
946 vec![BusType::PCI],
947 BusType::PCI,
948 true,
949 "0014";
950 "pci_match"
951 )]
952 #[test_case(
953 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
954 vec![BusType::USB, BusType::SDIO],
955 BusType::PCI,
956 false,
957 "0014";
958 "pci_no_match"
959 )]
960 #[test_case(
961 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
962 vec![BusType::USB],
963 BusType::USB,
964 true,
965 "0014";
966 "pci_usb_match"
967 )]
968 #[test_case(
969 "/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",
970 vec![BusType::USB],
971 BusType::USB,
972 true,
973 "050018";
974 "dwc_usb_match"
975 )]
976 #[test_case(
981 "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
982 vec![BusType::PCI, BusType::SDIO],
983 BusType::USB,
984 false,
985 "0014";
986 "usb_no_match"
987 )]
988 #[test_case(
989 "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy",
990 vec![BusType::SDIO],
991 BusType::SDIO,
992 true,
993 "05006";
994 "sdio_match"
995 )]
996 #[test_case(
997 "/dev/sys/platform/pt/PC00/bus/00:1e.0/00_1e_0/virtio-net/network-device",
998 vec![BusType::VirtIo],
999 BusType::VirtIo,
1000 true,
1001 "001e";
1002 "virtio_match_alternate_location"
1003 )]
1004 #[test_case(
1005 "/dev/sys/platform/pt/PC00/bus/<malformed>/00_1e_0/virtio-net/network-device",
1006 vec![BusType::VirtIo],
1007 BusType::VirtIo,
1008 true,
1009 "001e";
1010 "virtio_matches_underscore_path"
1011 )]
1012 #[test_case(
1013 "/dev/sys/platform/pt/PC00/bus/00:1e.1/00_1e_1/virtio-net/network-device",
1014 vec![BusType::VirtIo],
1015 BusType::VirtIo,
1016 true,
1017 "001e1";
1018 "virtio_match_alternate_no_trim"
1019 )]
1020 #[test_case(
1021 "/dev/sys/platform/pt/PC00/bus/<unrecognized_bus_path>/network-device",
1022 vec![BusType::Unknown],
1023 BusType::Unknown,
1024 true,
1025 "ffffff";
1026 "unknown_bus_match_unrecognized"
1027 )]
1028 fn test_interface_matching_and_naming_by_bus_properties(
1029 topological_path: &'static str,
1030 bus_types: Vec<BusType>,
1031 expected_bus_type: BusType,
1032 want_match: bool,
1033 want_name: &'static str,
1034 ) {
1035 let device_info = DeviceInfoRef {
1036 topological_path: topological_path,
1037 ..default_device_info()
1040 };
1041
1042 let bus_type = get_bus_type_for_topological_path(&device_info.topological_path);
1045 assert_eq!(bus_type, expected_bus_type);
1046
1047 let matching_rule = MatchingRule::BusTypes(bus_types);
1049 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1050 assert_eq!(does_interface_match, want_match);
1051
1052 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1053 assert_eq!(name, want_name);
1054
1055 if want_name == "ffffff" {
1059 let name = get_normalized_bus_path_for_topo_path(&device_info.topological_path);
1060 assert_eq!(name, "fffffe");
1061 }
1062 }
1063
1064 #[test_case(
1066 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1067 r"*[0-9][0-9]:[0-9][0-9]*",
1068 true;
1069 "pattern_matches"
1070 )]
1071 #[test_case("pattern/will/match/anything", r"*", true; "pattern_matches_any")]
1072 #[test_case(
1074 "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1075 r"*[0-9][0-9]:00*",
1076 false;
1077 "no_matches"
1078 )]
1079 fn test_interface_matching_by_topological_path(
1080 topological_path: &'static str,
1081 glob_str: &'static str,
1082 want_match: bool,
1083 ) {
1084 let device_info = DeviceInfoRef {
1085 topological_path,
1086 ..default_device_info()
1089 };
1090
1091 let matching_rule = MatchingRule::TopologicalPath(glob::Pattern::new(glob_str).unwrap());
1093 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1094 assert_eq!(does_interface_match, want_match);
1095 }
1096
1097 #[test_case(
1099 "ethx5",
1100 r"ethx[0-9]*",
1101 true;
1102 "pattern_matches"
1103 )]
1104 #[test_case("arbitraryname", r"*", true; "pattern_matches_any")]
1105 #[test_case(
1107 "wlans1002",
1108 r"eths[0-9][0-9][0-9][0-9]*",
1109 false;
1110 "no_matches"
1111 )]
1112 fn test_interface_matching_by_interface_name(
1113 interface_name: &'static str,
1114 glob_str: &'static str,
1115 want_match: bool,
1116 ) {
1117 let provisioning_matching_rule = ProvisioningMatchingRule::InterfaceName {
1119 pattern: glob::Pattern::new(glob_str).unwrap(),
1120 };
1121 let does_interface_match = provisioning_matching_rule
1122 .does_interface_match(&default_device_info(), interface_name)
1123 .unwrap();
1124 assert_eq!(does_interface_match, want_match);
1125 }
1126
1127 #[test_case(
1128 DeviceClass::Ethernet,
1129 vec![DeviceClass::Ethernet],
1130 true;
1131 "eth_match"
1132 )]
1133 #[test_case(
1134 DeviceClass::Ethernet,
1135 vec![DeviceClass::WlanClient, DeviceClass::WlanAp],
1136 false;
1137 "eth_no_match"
1138 )]
1139 #[test_case(
1140 DeviceClass::WlanClient,
1141 vec![DeviceClass::WlanClient],
1142 true;
1143 "wlan_match"
1144 )]
1145 #[test_case(
1146 DeviceClass::WlanClient,
1147 vec![DeviceClass::Ethernet, DeviceClass::WlanAp],
1148 false;
1149 "wlan_no_match"
1150 )]
1151 #[test_case(
1152 DeviceClass::WlanAp,
1153 vec![DeviceClass::WlanAp],
1154 true;
1155 "ap_match"
1156 )]
1157 #[test_case(
1158 DeviceClass::WlanAp,
1159 vec![DeviceClass::Ethernet, DeviceClass::WlanClient],
1160 false;
1161 "ap_no_match"
1162 )]
1163 fn test_interface_matching_by_device_class(
1164 device_class: DeviceClass,
1165 device_classes: Vec<DeviceClass>,
1166 want_match: bool,
1167 ) {
1168 let device_info = DeviceInfoRef { device_class, ..default_device_info() };
1169
1170 let matching_rule = MatchingRule::DeviceClasses(device_classes);
1172 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1173 assert_eq!(does_interface_match, want_match);
1174 }
1175
1176 #[test_case(
1181 DeviceClass::Ethernet,
1182 "/dev/pci-00:15.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet"
1183 )]
1184 #[test_case(DeviceClass::WlanClient, "/dev/pci-00:14.0/ethernet")]
1185 fn test_interface_matching_by_any_matching_rule(
1186 device_class: DeviceClass,
1187 topological_path: &'static str,
1188 ) {
1189 let device_info = DeviceInfoRef {
1190 device_class,
1191 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1192 topological_path,
1193 };
1194
1195 let matching_rule = MatchingRule::Any(true);
1197 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1198 assert!(does_interface_match);
1199
1200 let matching_rule = MatchingRule::Any(false);
1202 let does_interface_match = matching_rule.does_interface_match(&device_info).unwrap();
1203 assert!(!does_interface_match);
1204 }
1205
1206 #[test_case(
1207 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1208 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])],
1209 false;
1210 "false_single_rule"
1211 )]
1212 #[test_case(
1213 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1214 vec![MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient]), MatchingRule::Any(true)],
1215 false;
1216 "false_one_rule_of_multiple"
1217 )]
1218 #[test_case(
1219 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1220 vec![MatchingRule::Any(true)],
1221 true;
1222 "true_single_rule"
1223 )]
1224 #[test_case(
1225 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1226 vec![MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet]), MatchingRule::Any(true)],
1227 true;
1228 "true_multiple_rules"
1229 )]
1230 fn test_does_interface_match(
1231 info: DeviceInfoRef<'_>,
1232 matching_rules: Vec<MatchingRule>,
1233 want_match: bool,
1234 ) {
1235 let naming_rule =
1236 NamingRule { matchers: HashSet::from_iter(matching_rules), naming_scheme: Vec::new() };
1237 assert_eq!(naming_rule.does_interface_match(&info), want_match);
1238 }
1239
1240 #[test_case(
1241 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1242 "",
1243 vec![
1244 ProvisioningMatchingRule::Common(
1245 MatchingRule::DeviceClasses(vec![DeviceClass::WlanClient])
1246 )
1247 ],
1248 false;
1249 "false_single_rule"
1250 )]
1251 #[test_case(
1252 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1253 "wlanx5009",
1254 vec![
1255 ProvisioningMatchingRule::InterfaceName {
1256 pattern: glob::Pattern::new("ethx*").unwrap()
1257 },
1258 ProvisioningMatchingRule::Common(MatchingRule::Any(true))
1259 ],
1260 false;
1261 "false_one_rule_of_multiple"
1262 )]
1263 #[test_case(
1264 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1265 "",
1266 vec![ProvisioningMatchingRule::Common(MatchingRule::Any(true))],
1267 true;
1268 "true_single_rule"
1269 )]
1270 #[test_case(
1271 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1272 "wlanx5009",
1273 vec![
1274 ProvisioningMatchingRule::Common(
1275 MatchingRule::DeviceClasses(vec![DeviceClass::Ethernet])
1276 ),
1277 ProvisioningMatchingRule::InterfaceName {
1278 pattern: glob::Pattern::new("wlanx*").unwrap()
1279 }
1280 ],
1281 true;
1282 "true_multiple_rules"
1283 )]
1284 fn test_does_interface_match_provisioning_rule(
1285 info: DeviceInfoRef<'_>,
1286 interface_name: &str,
1287 matching_rules: Vec<ProvisioningMatchingRule>,
1288 want_match: bool,
1289 ) {
1290 let provisioning_rule = ProvisioningRule {
1291 matchers: HashSet::from_iter(matching_rules),
1292 action: ProvisioningAction {
1293 provisioning: ProvisioningType::Local,
1294 ..Default::default()
1295 },
1296 };
1297 assert_eq!(provisioning_rule.does_interface_match(&info, interface_name), want_match);
1298 }
1299
1300 #[test_case(
1301 vec![NameCompositionRule::Static { value: String::from("x") }],
1302 default_device_info(),
1303 "x";
1304 "single_static"
1305 )]
1306 #[test_case(
1307 vec![
1308 NameCompositionRule::Static { value: String::from("eth") },
1309 NameCompositionRule::Static { value: String::from("x") },
1310 NameCompositionRule::Static { value: String::from("100") },
1311 ],
1312 default_device_info(),
1313 "ethx100";
1314 "multiple_static"
1315 )]
1316 #[test_case(
1317 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac }],
1318 DeviceInfoRef {
1319 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1320 ..default_device_info()
1321 },
1322 "1";
1323 "normalized_mac"
1324 )]
1325 #[test_case(
1326 vec![
1327 NameCompositionRule::Static { value: String::from("eth") },
1328 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1329 ],
1330 DeviceInfoRef {
1331 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x9] },
1332 ..default_device_info()
1333 },
1334 "eth9";
1335 "normalized_mac_with_static"
1336 )]
1337 #[test_case(
1338 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1339 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1340 "eth";
1341 "eth_device_class"
1342 )]
1343 #[test_case(
1344 vec![NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass }],
1345 DeviceInfoRef { device_class: DeviceClass::WlanClient, ..default_device_info() },
1346 "wlan";
1347 "wlan_device_class"
1348 )]
1349 #[test_case(
1350 vec![
1351 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1352 NameCompositionRule::Static { value: String::from("x") },
1353 ],
1354 DeviceInfoRef { device_class: DeviceClass::Ethernet, ..default_device_info() },
1355 "ethx";
1356 "device_class_with_static"
1357 )]
1358 #[test_case(
1359 vec![
1360 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1361 NameCompositionRule::Static { value: String::from("x") },
1362 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::NormalizedMac },
1363 ],
1364 DeviceInfoRef {
1365 device_class: DeviceClass::WlanClient,
1366 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x8] },
1367 ..default_device_info()
1368 },
1369 "wlanx8";
1370 "device_class_with_static_with_normalized_mac"
1371 )]
1372 #[test_case(
1373 vec![
1374 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1375 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1376 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1377 ],
1378 DeviceInfoRef {
1379 device_class: DeviceClass::Ethernet,
1380 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0_/00:14.0/ethernet",
1381 ..default_device_info()
1382 },
1383 "ethp0014";
1384 "device_class_with_pci_bus_type_with_bus_path"
1385 )]
1386 #[test_case(
1387 vec![
1388 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1389 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1390 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1391 ],
1392 DeviceInfoRef {
1393 device_class: DeviceClass::Ethernet,
1394 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1395 ..default_device_info()
1396 },
1397 "ethu0014";
1398 "device_class_with_pci_usb_bus_type_with_bus_path"
1399 )]
1400 #[test_case(
1401 vec![
1402 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::DeviceClass },
1403 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusType },
1404 NameCompositionRule::Dynamic { rule: DynamicNameCompositionRule::BusPath },
1405 ],
1406 DeviceInfoRef {
1407 device_class: DeviceClass::Ethernet,
1408 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",
1409 ..default_device_info()
1410 },
1411 "ethu050018";
1412 "device_class_with_dwc_usb_bus_type_with_bus_path"
1413 )]
1414 #[test_case(
1415 vec![NameCompositionRule::Default],
1416 DeviceInfoRef {
1417 device_class: DeviceClass::Ethernet,
1418 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1419 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x2] },
1420 },
1421 "ethx2";
1422 "default_usb_pci"
1423 )]
1424 #[test_case(
1425 vec![NameCompositionRule::Default],
1426 DeviceInfoRef {
1427 device_class: DeviceClass::Ethernet,
1428 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",
1429 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x3] },
1430 },
1431 "ethx3";
1432 "default_usb_dwc"
1433 )]
1434 #[test_case(
1435 vec![NameCompositionRule::Default],
1436 DeviceInfoRef {
1437 device_class: DeviceClass::Ethernet,
1438 topological_path: "/dev/sys/platform/05:00:6/aml-sd-emmc/sdio/broadcom-wlanphy",
1439 ..default_device_info()
1440 },
1441 "eths05006";
1442 "default_sdio"
1443 )]
1444 fn test_naming_rules(
1445 composition_rules: Vec<NameCompositionRule>,
1446 info: DeviceInfoRef<'_>,
1447 expected_name: &'static str,
1448 ) {
1449 let naming_rule = NamingRule { matchers: HashSet::new(), naming_scheme: composition_rules };
1450
1451 let name = naming_rule.generate_name(&HashMap::new(), &info);
1452 assert_eq!(name.unwrap(), expected_name.to_owned());
1453 }
1454
1455 #[test]
1456 fn test_generate_name_from_naming_rule_interface_name_exists_no_reattempt() {
1457 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
1458
1459 let shared_interface_name = "x".to_owned();
1460 let mut interfaces = HashMap::new();
1461 assert_matches!(
1462 interfaces.insert(
1463 InterfaceNamingIdentifier {
1464 mac: fidl_fuchsia_net_ext::MacAddress {
1465 octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1]
1466 },
1467 topological_path: topo_usb.to_string()
1468 },
1469 shared_interface_name.clone(),
1470 ),
1471 None
1472 );
1473
1474 let naming_rule = NamingRule {
1475 matchers: HashSet::new(),
1476 naming_scheme: vec![NameCompositionRule::Static {
1477 value: shared_interface_name.clone(),
1478 }],
1479 };
1480
1481 let name = naming_rule.generate_name(&interfaces, &default_device_info()).unwrap();
1482 assert_eq!(name, shared_interface_name);
1483 }
1484
1485 #[test]
1489 fn test_generate_name_from_naming_rule_many_unique_macs() {
1490 let topo_usb = "/dev/pci-00:14.0-fidl/xhci/usb/004/004/ifc-000/ax88179/ethernet";
1491
1492 let naming_rule = NamingRule {
1493 matchers: HashSet::new(),
1494 naming_scheme: vec![NameCompositionRule::Dynamic {
1495 rule: DynamicNameCompositionRule::NormalizedMac,
1496 }],
1497 };
1498
1499 let mut interfaces = HashMap::new();
1501
1502 for n in 0u8..255u8 {
1503 let octets = [0x01, 0x01, 0x01, 0x01, 0x01, n];
1504 let interface_naming_id =
1505 generate_identifier(&fidl_fuchsia_net_ext::MacAddress { octets }, topo_usb);
1506 let info = DeviceInfoRef {
1507 device_class: DeviceClass::Ethernet,
1508 mac: &fidl_fuchsia_net_ext::MacAddress { octets },
1509 topological_path: topo_usb,
1510 };
1511
1512 let name =
1513 naming_rule.generate_name(&interfaces, &info).expect("failed to generate the name");
1514 assert_eq!(name, format!("{n:x}"));
1515
1516 assert_matches!(interfaces.insert(interface_naming_id, name.clone()), None);
1517 }
1518 }
1519
1520 #[test_case(true, "x"; "matches_first_rule")]
1521 #[test_case(false, "ethx1"; "fallback_default")]
1522 fn test_generate_name_from_naming_rules(match_first_rule: bool, expected_name: &'static str) {
1523 let info = DeviceInfoRef {
1526 device_class: DeviceClass::Ethernet,
1527 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1528 topological_path: "/dev/sys/platform/pt/PCI0/bus/00:14.0/00:14.0/xhci/usb/004/004/ifc-000/ax88179/ethernet",
1529 };
1530 let name = generate_name_from_naming_rules(
1531 &[
1532 NamingRule {
1533 matchers: HashSet::from([MatchingRule::Any(match_first_rule)]),
1534 naming_scheme: vec![NameCompositionRule::Static { value: String::from("x") }],
1535 },
1536 NamingRule {
1539 matchers: HashSet::from([MatchingRule::Any(false)]),
1540 naming_scheme: vec![NameCompositionRule::Static { value: String::from("y") }],
1541 },
1542 ],
1543 &HashMap::new(),
1544 &info,
1545 )
1546 .unwrap();
1547 assert_eq!(name, expected_name.to_owned());
1548 }
1549
1550 #[test_case(true, ProvisioningType::Delegated; "matches_first_rule")]
1551 #[test_case(false, ProvisioningType::Local; "fallback_default")]
1552 fn test_find_provisioning_action_from_provisioning_rules(
1553 match_first_rule: bool,
1554 expected: ProvisioningType,
1555 ) {
1556 let provisioning_action = find_provisioning_action_from_provisioning_rules(
1557 &[ProvisioningRule {
1558 matchers: HashSet::from([ProvisioningMatchingRule::Common(MatchingRule::Any(
1559 match_first_rule,
1560 ))]),
1561 action: ProvisioningAction {
1562 provisioning: ProvisioningType::Delegated,
1563 ..Default::default()
1564 },
1565 }],
1566 &DeviceInfoRef {
1567 device_class: DeviceClass::WlanClient,
1568 mac: &fidl_fuchsia_net_ext::MacAddress { octets: [0x1, 0x1, 0x1, 0x1, 0x1, 0x1] },
1569 topological_path: "",
1570 },
1571 "wlans5009",
1572 );
1573 assert_eq!(provisioning_action.provisioning, expected);
1574 }
1575}