autosar_data_abstraction/communication/physical_channel/ethernet/
networkendpoint.rsuse crate::communication::EthernetPhysicalChannel;
use crate::{abstraction_element, AbstractionElement, AutosarAbstractionError};
use autosar_data::{CharacterData, Element, ElementName, EnumItem};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NetworkEndpoint(Element);
abstraction_element!(NetworkEndpoint, NetworkEndpoint);
impl NetworkEndpoint {
pub(crate) fn new(
name: &str,
channel: &EthernetPhysicalChannel,
address: NetworkEndpointAddress,
) -> Result<Self, AutosarAbstractionError> {
let el_network_endpoint = channel
.element()
.get_or_create_sub_element(ElementName::NetworkEndpoints)?
.create_named_sub_element(ElementName::NetworkEndpoint, name)?;
let network_endpoint = Self(el_network_endpoint);
let result = network_endpoint.add_network_endpoint_address(address);
if let Err(error) = result {
let _ = channel.element().remove_sub_element(network_endpoint.0);
return Err(error);
}
Ok(network_endpoint)
}
pub fn add_network_endpoint_address(&self, address: NetworkEndpointAddress) -> Result<(), AutosarAbstractionError> {
let mut fixedcount = 0;
if matches!(address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed))
|| matches!(address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed))
{
fixedcount = 1;
}
for existing_address in self.addresses() {
if std::mem::discriminant(&existing_address) != std::mem::discriminant(&address) {
return Err(AutosarAbstractionError::InvalidParameter(
"you cannot mix IPv4 and IPv6 inside one NetworkEndpoint".to_string(),
));
}
if matches!(existing_address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed))
|| matches!(existing_address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed))
{
fixedcount += 1;
}
}
if fixedcount > 1 {
return Err(AutosarAbstractionError::InvalidParameter(
"Only one NetworkEndpointAddress can be a fixed address".to_string(),
));
}
let addresses = self
.0
.get_or_create_sub_element(ElementName::NetworkEndpointAddresses)?;
match address {
NetworkEndpointAddress::IPv4 {
address,
address_source,
default_gateway,
network_mask,
} => {
let cfg = addresses.create_sub_element(ElementName::Ipv4Configuration)?;
if let Some(addr) = address {
cfg.create_sub_element(ElementName::Ipv4Address)?
.set_character_data(addr)?;
}
if let Some(addr_src) = address_source {
cfg.create_sub_element(ElementName::Ipv4AddressSource)?
.set_character_data::<EnumItem>(addr_src.into())?;
}
if let Some(defgw) = default_gateway {
cfg.create_sub_element(ElementName::DefaultGateway)?
.set_character_data(defgw)?;
}
if let Some(netmask) = network_mask {
cfg.create_sub_element(ElementName::NetworkMask)?
.set_character_data(netmask)?;
}
}
NetworkEndpointAddress::IPv6 {
address,
address_source,
default_router,
} => {
let cfg = addresses.create_sub_element(ElementName::Ipv6Configuration)?;
if let Some(addr) = address {
cfg.create_sub_element(ElementName::Ipv6Address)?
.set_character_data(addr)?;
}
if let Some(addr_src) = address_source {
cfg.create_sub_element(ElementName::Ipv6AddressSource)?
.set_character_data(addr_src.to_cdata())?;
}
if let Some(dr) = default_router {
cfg.create_sub_element(ElementName::DefaultRouter)?
.set_character_data(dr)?;
}
}
}
Ok(())
}
pub fn addresses(&self) -> impl Iterator<Item = NetworkEndpointAddress> {
self.element()
.get_sub_element(ElementName::NetworkEndpointAddresses)
.into_iter()
.flat_map(|addresses| addresses.sub_elements())
.filter_map(|elem| NetworkEndpointAddress::try_from(elem).ok())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NetworkEndpointAddress {
IPv4 {
address: Option<String>,
address_source: Option<IPv4AddressSource>,
default_gateway: Option<String>,
network_mask: Option<String>,
},
IPv6 {
address: Option<String>,
address_source: Option<IPv6AddressSource>,
default_router: Option<String>,
},
}
impl TryFrom<Element> for NetworkEndpointAddress {
type Error = AutosarAbstractionError;
fn try_from(element: Element) -> Result<Self, Self::Error> {
match element.element_name() {
ElementName::Ipv4Configuration => {
let address = element
.get_sub_element(ElementName::Ipv4Address)
.and_then(|i4a| i4a.character_data())
.and_then(|cdata| cdata.string_value());
let address_source = element
.get_sub_element(ElementName::Ipv4AddressSource)
.and_then(|i4as| i4as.character_data())
.and_then(IPv4AddressSource::from_cdata);
let default_gateway = element
.get_sub_element(ElementName::DefaultGateway)
.and_then(|dg| dg.character_data())
.and_then(|cdata| cdata.string_value());
let network_mask = element
.get_sub_element(ElementName::NetworkMask)
.and_then(|nm| nm.character_data())
.and_then(|cdata| cdata.string_value());
Ok(NetworkEndpointAddress::IPv4 {
address,
address_source,
default_gateway,
network_mask,
})
}
ElementName::Ipv6Configuration => {
let address = element
.get_sub_element(ElementName::Ipv6Address)
.and_then(|i6a| i6a.character_data())
.and_then(|cdata| cdata.string_value());
let address_source = element
.get_sub_element(ElementName::Ipv6AddressSource)
.and_then(|i6as| i6as.character_data())
.and_then(IPv6AddressSource::from_cdata);
let default_router = element
.get_sub_element(ElementName::DefaultRouter)
.and_then(|dr| dr.character_data())
.and_then(|cdata| cdata.string_value());
Ok(NetworkEndpointAddress::IPv6 {
address,
address_source,
default_router,
})
}
_ => Err(AutosarAbstractionError::ConversionError {
element,
dest: "NetwworkEndpointAddress".to_string(),
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IPv4AddressSource {
AutoIp,
AutoIpDoIp,
DHCPv4,
Fixed,
}
impl IPv4AddressSource {
fn from_cdata(cdata: CharacterData) -> Option<Self> {
match cdata {
CharacterData::Enum(EnumItem::AutoIp) => Some(Self::AutoIp),
CharacterData::Enum(EnumItem::AutoIpDoip) => Some(Self::AutoIpDoIp),
CharacterData::Enum(EnumItem::Dhcpv4) => Some(Self::DHCPv4),
CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed),
_ => None,
}
}
}
impl From<IPv4AddressSource> for EnumItem {
fn from(value: IPv4AddressSource) -> Self {
match value {
IPv4AddressSource::AutoIp => EnumItem::AutoIp,
IPv4AddressSource::AutoIpDoIp => EnumItem::AutoIpDoip,
IPv4AddressSource::DHCPv4 => EnumItem::Dhcpv4,
IPv4AddressSource::Fixed => EnumItem::Fixed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IPv6AddressSource {
DHCPv6,
Fixed,
LinkLocal,
LinkLocalDoIp,
RouterAdvertisement,
}
impl IPv6AddressSource {
fn from_cdata(cdata: CharacterData) -> Option<Self> {
match cdata {
CharacterData::Enum(EnumItem::Dhcpv6) => Some(Self::DHCPv6),
CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed),
CharacterData::Enum(EnumItem::LinkLocal) => Some(Self::LinkLocal),
CharacterData::Enum(EnumItem::LinkLocalDoip) => Some(Self::LinkLocalDoIp),
CharacterData::Enum(EnumItem::RouterAdvertisement) => Some(Self::RouterAdvertisement),
_ => None,
}
}
fn to_cdata(self) -> CharacterData {
match self {
Self::DHCPv6 => CharacterData::Enum(EnumItem::Dhcpv6),
Self::Fixed => CharacterData::Enum(EnumItem::Fixed),
Self::LinkLocal => CharacterData::Enum(EnumItem::LinkLocal),
Self::LinkLocalDoIp => CharacterData::Enum(EnumItem::LinkLocalDoip),
Self::RouterAdvertisement => CharacterData::Enum(EnumItem::RouterAdvertisement),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{ArPackage, SystemCategory};
use autosar_data::{AutosarModel, AutosarVersion};
#[test]
fn test_network_endpoint_ipv4() {
let model = AutosarModel::new();
let _file = model.create_file("filename", AutosarVersion::LATEST).unwrap();
let pkg = ArPackage::get_or_create(&model, "/test").unwrap();
let system = pkg.create_system("System", SystemCategory::SystemDescription).unwrap();
let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap();
let channel = cluster.create_physical_channel("Channel", None).unwrap();
let address1 = NetworkEndpointAddress::IPv4 {
address: Some("192.168.0.1".to_string()),
address_source: Some(IPv4AddressSource::Fixed),
default_gateway: Some("192.168.0.2".to_string()),
network_mask: Some("255.255.0.0".to_string()),
};
let network_endpoint = channel
.create_network_endpoint("RemoteAddress", address1.clone(), None)
.unwrap();
assert_eq!(network_endpoint.addresses().count(), 1);
assert_eq!(network_endpoint.addresses().next().unwrap(), address1);
let address2 = NetworkEndpointAddress::IPv4 {
address: None,
address_source: Some(IPv4AddressSource::AutoIp),
default_gateway: None,
network_mask: None,
};
network_endpoint.add_network_endpoint_address(address2).unwrap();
assert_eq!(network_endpoint.addresses().count(), 2);
}
#[test]
fn test_network_endpoint_ipv6() {
let model = AutosarModel::new();
let _file = model.create_file("filename", AutosarVersion::LATEST).unwrap();
let pkg = ArPackage::get_or_create(&model, "/test").unwrap();
let system = pkg.create_system("System", SystemCategory::SystemDescription).unwrap();
let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap();
let channel = cluster.create_physical_channel("Channel", None).unwrap();
let address1 = NetworkEndpointAddress::IPv6 {
address: Some("2001:0db8:0000:0000:0000:0000:0000:0001".to_string()),
address_source: Some(IPv6AddressSource::Fixed),
default_router: Some("2001:0db8:0000:0000:0000:0000:0000:0002".to_string()),
};
let network_endpoint = channel
.create_network_endpoint("RemoteAddress", address1.clone(), None)
.unwrap();
assert_eq!(network_endpoint.addresses().count(), 1);
assert_eq!(network_endpoint.addresses().next().unwrap(), address1);
let address2 = NetworkEndpointAddress::IPv6 {
address: None,
address_source: Some(IPv6AddressSource::LinkLocal),
default_router: None,
};
network_endpoint.add_network_endpoint_address(address2).unwrap();
assert_eq!(network_endpoint.addresses().count(), 2);
}
}