autosar_data_abstraction/communication/physical_channel/ethernet/
networkendpoint.rs

1use crate::communication::EthernetPhysicalChannel;
2use crate::{AbstractionElement, AutosarAbstractionError, IdentifiableAbstractionElement, abstraction_element};
3use autosar_data::{CharacterData, Element, ElementName, EnumItem};
4
5/// A network endpoint contains address information for a connection
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct NetworkEndpoint(Element);
8abstraction_element!(NetworkEndpoint, NetworkEndpoint);
9impl IdentifiableAbstractionElement for NetworkEndpoint {}
10
11impl NetworkEndpoint {
12    pub(crate) fn new(
13        name: &str,
14        channel: &EthernetPhysicalChannel,
15        address: NetworkEndpointAddress,
16    ) -> Result<Self, AutosarAbstractionError> {
17        let el_network_endpoint = channel
18            .element()
19            .get_or_create_sub_element(ElementName::NetworkEndpoints)?
20            .create_named_sub_element(ElementName::NetworkEndpoint, name)?;
21
22        let network_endpoint = Self(el_network_endpoint);
23        let result = network_endpoint.add_network_endpoint_address(address);
24        if let Err(error) = result {
25            let _ = channel.element().remove_sub_element(network_endpoint.0);
26            return Err(error);
27        }
28
29        Ok(network_endpoint)
30    }
31
32    /// add a network endpoint address to this `NetworkEndpoint`
33    ///
34    /// A `NetworkEndpoint` may have multiple sets of address information. The following restrictions apply:
35    ///
36    /// - all addresses must have the same type, i.e. all IPv4 or all IPv6
37    /// - only one of them may be a `Fixed` address, all others must be dynamic (DHCP, automatic link local, etc.)
38    pub fn add_network_endpoint_address(&self, address: NetworkEndpointAddress) -> Result<(), AutosarAbstractionError> {
39        let mut fixedcount = 0;
40        if matches!(address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed))
41            || matches!(address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed))
42        {
43            fixedcount = 1;
44        }
45        for existing_address in self.addresses() {
46            if std::mem::discriminant(&existing_address) != std::mem::discriminant(&address) {
47                return Err(AutosarAbstractionError::InvalidParameter(
48                    "you cannot mix IPv4 and IPv6 inside one NetworkEndpoint".to_string(),
49                ));
50            }
51            if matches!(existing_address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed))
52                || matches!(existing_address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed))
53            {
54                fixedcount += 1;
55            }
56        }
57        if fixedcount > 1 {
58            return Err(AutosarAbstractionError::InvalidParameter(
59                "Only one NetworkEndpointAddress can be a fixed address".to_string(),
60            ));
61        }
62
63        let addresses = self
64            .0
65            .get_or_create_sub_element(ElementName::NetworkEndpointAddresses)?;
66        match address {
67            NetworkEndpointAddress::IPv4 {
68                address,
69                address_source,
70                default_gateway,
71                network_mask,
72            } => {
73                let cfg = addresses.create_sub_element(ElementName::Ipv4Configuration)?;
74                if let Some(addr) = address {
75                    cfg.create_sub_element(ElementName::Ipv4Address)?
76                        .set_character_data(addr)?;
77                }
78                if let Some(addr_src) = address_source {
79                    cfg.create_sub_element(ElementName::Ipv4AddressSource)?
80                        .set_character_data::<EnumItem>(addr_src.into())?;
81                }
82                if let Some(defgw) = default_gateway {
83                    cfg.create_sub_element(ElementName::DefaultGateway)?
84                        .set_character_data(defgw)?;
85                }
86                if let Some(netmask) = network_mask {
87                    cfg.create_sub_element(ElementName::NetworkMask)?
88                        .set_character_data(netmask)?;
89                }
90            }
91            NetworkEndpointAddress::IPv6 {
92                address,
93                address_source,
94                default_router,
95            } => {
96                let cfg = addresses.create_sub_element(ElementName::Ipv6Configuration)?;
97                if let Some(addr) = address {
98                    cfg.create_sub_element(ElementName::Ipv6Address)?
99                        .set_character_data(addr)?;
100                }
101                if let Some(addr_src) = address_source {
102                    cfg.create_sub_element(ElementName::Ipv6AddressSource)?
103                        .set_character_data(addr_src.to_cdata())?;
104                }
105                if let Some(dr) = default_router {
106                    cfg.create_sub_element(ElementName::DefaultRouter)?
107                        .set_character_data(dr)?;
108                }
109            }
110        }
111        Ok(())
112    }
113
114    /// iterator over all addresses in the `NetworkEndpoint`
115    pub fn addresses(&self) -> impl Iterator<Item = NetworkEndpointAddress> + Send + use<> {
116        self.element()
117            .get_sub_element(ElementName::NetworkEndpointAddresses)
118            .into_iter()
119            .flat_map(|addresses| addresses.sub_elements())
120            .filter_map(|elem| NetworkEndpointAddress::try_from(elem).ok())
121    }
122}
123
124//##################################################################
125
126/// address information for a network endpoint
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub enum NetworkEndpointAddress {
129    /// IPv4 addressing information
130    IPv4 {
131        /// IPv4 address in the form "a.b.c.d". This is used if the address source is FIXED
132        address: Option<String>,
133        /// defines how the address is obtained
134        address_source: Option<IPv4AddressSource>,
135        /// ip address of the default gateway
136        default_gateway: Option<String>,
137        /// Network mask in the form "a.b.c.d"
138        network_mask: Option<String>,
139    },
140    /// IPv6 addressing information
141    IPv6 {
142        /// IPv6 address, without abbreviation
143        address: Option<String>,
144        /// defines how the address is obtained
145        address_source: Option<IPv6AddressSource>,
146        /// IP address of the default router
147        default_router: Option<String>,
148    },
149}
150
151impl TryFrom<Element> for NetworkEndpointAddress {
152    type Error = AutosarAbstractionError;
153
154    fn try_from(element: Element) -> Result<Self, Self::Error> {
155        match element.element_name() {
156            ElementName::Ipv4Configuration => {
157                let address = element
158                    .get_sub_element(ElementName::Ipv4Address)
159                    .and_then(|i4a| i4a.character_data())
160                    .and_then(|cdata| cdata.string_value());
161                let address_source = element
162                    .get_sub_element(ElementName::Ipv4AddressSource)
163                    .and_then(|i4as| i4as.character_data())
164                    .and_then(IPv4AddressSource::from_cdata);
165                let default_gateway = element
166                    .get_sub_element(ElementName::DefaultGateway)
167                    .and_then(|dg| dg.character_data())
168                    .and_then(|cdata| cdata.string_value());
169                let network_mask = element
170                    .get_sub_element(ElementName::NetworkMask)
171                    .and_then(|nm| nm.character_data())
172                    .and_then(|cdata| cdata.string_value());
173
174                Ok(NetworkEndpointAddress::IPv4 {
175                    address,
176                    address_source,
177                    default_gateway,
178                    network_mask,
179                })
180            }
181            ElementName::Ipv6Configuration => {
182                let address = element
183                    .get_sub_element(ElementName::Ipv6Address)
184                    .and_then(|i6a| i6a.character_data())
185                    .and_then(|cdata| cdata.string_value());
186                let address_source = element
187                    .get_sub_element(ElementName::Ipv6AddressSource)
188                    .and_then(|i6as| i6as.character_data())
189                    .and_then(IPv6AddressSource::from_cdata);
190                let default_router = element
191                    .get_sub_element(ElementName::DefaultRouter)
192                    .and_then(|dr| dr.character_data())
193                    .and_then(|cdata| cdata.string_value());
194
195                Ok(NetworkEndpointAddress::IPv6 {
196                    address,
197                    address_source,
198                    default_router,
199                })
200            }
201            _ => Err(AutosarAbstractionError::ConversionError {
202                element,
203                dest: "NetwworkEndpointAddress".to_string(),
204            }),
205        }
206    }
207}
208
209/// `IPv4AddressSource` defines how the address of an IPv4 `NetworkEndpoint` is obtained
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum IPv4AddressSource {
212    /// use `AutoIp` (aka APIPA) to assign a link-local address
213    AutoIp,
214    /// use `AutoIp` with `DoIp` settings to assign a link-local address
215    AutoIpDoIp,
216    /// dynamic assignment using DHCP
217    DHCPv4,
218    /// static IP address configuration - the address must be specified in `NetworkEndpointAddress`
219    Fixed,
220}
221
222impl IPv4AddressSource {
223    fn from_cdata(cdata: CharacterData) -> Option<Self> {
224        match cdata {
225            CharacterData::Enum(EnumItem::AutoIp) => Some(Self::AutoIp),
226            CharacterData::Enum(EnumItem::AutoIpDoip) => Some(Self::AutoIpDoIp),
227            CharacterData::Enum(EnumItem::Dhcpv4) => Some(Self::DHCPv4),
228            CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed),
229            _ => None,
230        }
231    }
232}
233
234impl From<IPv4AddressSource> for EnumItem {
235    fn from(value: IPv4AddressSource) -> Self {
236        match value {
237            IPv4AddressSource::AutoIp => EnumItem::AutoIp,
238            IPv4AddressSource::AutoIpDoIp => EnumItem::AutoIpDoip,
239            IPv4AddressSource::DHCPv4 => EnumItem::Dhcpv4,
240            IPv4AddressSource::Fixed => EnumItem::Fixed,
241        }
242    }
243}
244
245/// `IPv6AddressSource` defines how the address of an IPv6 `NetworkEndpoint` is obtained
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub enum IPv6AddressSource {
248    /// dynamic assignment using DHCP
249    DHCPv6,
250    /// static IP address configuration - the address must be specified in `NetworkEndpointAddress`
251    Fixed,
252    /// automatic link local address assignment
253    LinkLocal,
254    /// automatic link local address assignment using doip parameters
255    LinkLocalDoIp,
256    /// IPv6 stateless autoconfiguration
257    RouterAdvertisement,
258}
259
260impl IPv6AddressSource {
261    fn from_cdata(cdata: CharacterData) -> Option<Self> {
262        match cdata {
263            CharacterData::Enum(EnumItem::Dhcpv6) => Some(Self::DHCPv6),
264            CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed),
265            CharacterData::Enum(EnumItem::LinkLocal) => Some(Self::LinkLocal),
266            CharacterData::Enum(EnumItem::LinkLocalDoip) => Some(Self::LinkLocalDoIp),
267            CharacterData::Enum(EnumItem::RouterAdvertisement) => Some(Self::RouterAdvertisement),
268            _ => None,
269        }
270    }
271
272    fn to_cdata(self) -> CharacterData {
273        match self {
274            Self::DHCPv6 => CharacterData::Enum(EnumItem::Dhcpv6),
275            Self::Fixed => CharacterData::Enum(EnumItem::Fixed),
276            Self::LinkLocal => CharacterData::Enum(EnumItem::LinkLocal),
277            Self::LinkLocalDoIp => CharacterData::Enum(EnumItem::LinkLocalDoip),
278            Self::RouterAdvertisement => CharacterData::Enum(EnumItem::RouterAdvertisement),
279        }
280    }
281}
282
283//##################################################################
284
285#[cfg(test)]
286mod test {
287    use super::*;
288    use crate::{AutosarModelAbstraction, SystemCategory};
289    use autosar_data::AutosarVersion;
290
291    #[test]
292    fn test_network_endpoint_ipv4() {
293        let model = AutosarModelAbstraction::create("filename", AutosarVersion::LATEST);
294        let pkg = model.get_or_create_package("/test").unwrap();
295        let system = pkg.create_system("System", SystemCategory::SystemDescription).unwrap();
296        let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap();
297        let channel = cluster.create_physical_channel("Channel", None).unwrap();
298
299        // create a static socket connection between the local_socket and the remote_socket
300        let address1 = NetworkEndpointAddress::IPv4 {
301            address: Some("192.168.0.1".to_string()),
302            address_source: Some(IPv4AddressSource::Fixed),
303            default_gateway: Some("192.168.0.2".to_string()),
304            network_mask: Some("255.255.0.0".to_string()),
305        };
306        let network_endpoint = channel
307            .create_network_endpoint("RemoteAddress", address1.clone(), None)
308            .unwrap();
309        assert_eq!(network_endpoint.addresses().count(), 1);
310        assert_eq!(network_endpoint.addresses().next().unwrap(), address1);
311
312        let address2 = NetworkEndpointAddress::IPv4 {
313            address: None,
314            address_source: Some(IPv4AddressSource::AutoIp),
315            default_gateway: None,
316            network_mask: None,
317        };
318        network_endpoint.add_network_endpoint_address(address2).unwrap();
319        assert_eq!(network_endpoint.addresses().count(), 2);
320    }
321
322    #[test]
323    fn test_network_endpoint_ipv6() {
324        let model = AutosarModelAbstraction::create("filename", AutosarVersion::LATEST);
325        let pkg = model.get_or_create_package("/test").unwrap();
326        let system = pkg.create_system("System", SystemCategory::SystemDescription).unwrap();
327        let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap();
328        let channel = cluster.create_physical_channel("Channel", None).unwrap();
329
330        // create a static socket connection between the local_socket and the remote_socket
331        let address1 = NetworkEndpointAddress::IPv6 {
332            address: Some("2001:0db8:0000:0000:0000:0000:0000:0001".to_string()),
333            address_source: Some(IPv6AddressSource::Fixed),
334            default_router: Some("2001:0db8:0000:0000:0000:0000:0000:0002".to_string()),
335        };
336        let network_endpoint = channel
337            .create_network_endpoint("RemoteAddress", address1.clone(), None)
338            .unwrap();
339        assert_eq!(network_endpoint.addresses().count(), 1);
340        assert_eq!(network_endpoint.addresses().next().unwrap(), address1);
341
342        let address2 = NetworkEndpointAddress::IPv6 {
343            address: None,
344            address_source: Some(IPv6AddressSource::LinkLocal),
345            default_router: None,
346        };
347        network_endpoint.add_network_endpoint_address(address2).unwrap();
348        assert_eq!(network_endpoint.addresses().count(), 2);
349    }
350}