use crate::interface::types::InterfaceType;
use objc2_core_foundation::{CFArray, CFDictionary, CFRetained, CFString};
use objc2_system_configuration::{
SCNetworkInterface, SCNetworkProtocol, SCNetworkService, SCPreferences,
kSCNetworkProtocolTypeIPv4, kSCNetworkProtocolTypeIPv6,
};
use std::collections::HashMap;
#[derive(Clone, Debug, Default)]
pub(crate) struct SCInterface {
pub friendly_name: Option<String>,
pub sc_type: Option<String>,
pub dhcp_v4_enabled: Option<bool>,
pub dhcp_v6_enabled: Option<bool>,
}
impl SCInterface {
pub fn if_type(&self) -> Option<InterfaceType> {
self.sc_type.as_deref().map(map_sc_interface_type)
}
}
fn map_sc_interface_type(type_id: &str) -> InterfaceType {
match type_id {
"Bridge" => InterfaceType::Bridge,
"AirPort" => InterfaceType::Wireless80211,
"Ethernet" => InterfaceType::Ethernet,
"IEEE80211" => InterfaceType::Wireless80211,
"Loopback" => InterfaceType::Loopback,
"Modem" => InterfaceType::GenericModem,
"PPP" => InterfaceType::Ppp,
"WWAN" => InterfaceType::Wwanpp,
_ => InterfaceType::Unknown,
}
}
fn ipv4_method_to_dhcp(method: &str) -> Option<bool> {
match method {
"DHCP" => Some(true),
"Manual" | "Off" => Some(false),
_ => None,
}
}
fn ipv6_method_to_dhcp(method: &str) -> Option<bool> {
match method {
"Manual" | "LinkLocal" | "Off" => Some(false),
_ => None,
}
}
fn cast_cf_array<T>(array: CFRetained<CFArray>) -> CFRetained<CFArray<T>> {
unsafe {
let raw = CFRetained::into_raw(array);
CFRetained::from_raw(raw.cast())
}
}
fn cast_cf_dictionary<V>(dict: CFRetained<CFDictionary>) -> CFRetained<CFDictionary<CFString, V>> {
unsafe {
let raw = CFRetained::into_raw(dict);
CFRetained::from_raw(raw.cast())
}
}
fn sc_network_interfaces_all() -> CFRetained<CFArray<SCNetworkInterface>> {
cast_cf_array(SCNetworkInterface::all())
}
fn sc_network_services_all(prefs: &SCPreferences) -> Option<CFRetained<CFArray<SCNetworkService>>> {
SCNetworkService::all(prefs).map(cast_cf_array)
}
fn protocol_config_method(
service: &SCNetworkService,
protocol_type: &CFString,
) -> Option<CFRetained<CFString>> {
let protocol = service.protocol(protocol_type)?;
let protocol: CFRetained<SCNetworkProtocol> = protocol;
let config = protocol.configuration()?;
let config: CFRetained<CFDictionary<CFString, CFString>> = cast_cf_dictionary(config);
let key = CFString::from_str("ConfigMethod");
config.get(&key)
}
pub(crate) fn get_sc_interface_map() -> HashMap<String, SCInterface> {
let mut map = HashMap::new();
for sc_iface in sc_network_interfaces_all().iter() {
let Some(bsd_name) = sc_iface.bsd_name() else {
continue;
};
map.insert(
bsd_name.to_string(),
SCInterface {
friendly_name: sc_iface.localized_display_name().map(|s| s.to_string()),
sc_type: sc_iface.interface_type().map(|s| s.to_string()),
dhcp_v4_enabled: None,
dhcp_v6_enabled: None,
},
);
}
let prefs_name = CFString::from_str("netdev");
let Some(prefs) = SCPreferences::new(None, &prefs_name, None) else {
return map;
};
let Some(services) = sc_network_services_all(&prefs) else {
return map;
};
for service in services.iter() {
let Some(sc_iface) = service.interface() else {
continue;
};
let Some(bsd_name) = sc_iface.bsd_name() else {
continue;
};
let entry = map.entry(bsd_name.to_string()).or_default();
if entry.friendly_name.is_none() {
entry.friendly_name = service
.name()
.map(|s| s.to_string())
.or_else(|| sc_iface.localized_display_name().map(|s| s.to_string()));
}
if entry.sc_type.is_none() {
entry.sc_type = sc_iface.interface_type().map(|s| s.to_string());
}
if entry.dhcp_v4_enabled.is_none() {
entry.dhcp_v4_enabled =
protocol_config_method(&service, unsafe { kSCNetworkProtocolTypeIPv4 })
.and_then(|method| ipv4_method_to_dhcp(&method.to_string()));
}
if entry.dhcp_v6_enabled.is_none() {
entry.dhcp_v6_enabled =
protocol_config_method(&service, unsafe { kSCNetworkProtocolTypeIPv6 })
.and_then(|method| ipv6_method_to_dhcp(&method.to_string()));
}
}
map
}