Skip to main content

autosar_data_abstraction/
lib.rs

1//! # Description
2//!
3//! [![Github Actions](https://github.com/DanielT/autosar-data-abstraction/actions/workflows/CI.yml/badge.svg)](https://github.com/DanielT/autosar-data-abstraction/actions)
4//!
5//! This crate provides an abstraction layer for the AUTOSAR data model.
6//! It is built on top of the crate `autosar-data` and provides complex interactions with
7//! the model on top of the elementary operations of `autosar-data`.
8//!
9//! Rather than transforming the element based model into a new form, it only presents a
10//! view into the existing model, and provides methods to retrieve and modify the data.
11//!
12//! Since the AUTOSAR data model is very complex and has many different types of elements,
13//! this crate does not aim to provide full coverage of all classes.
14//! Instead the focus is on the most common classes and their interactions.
15//!
16//! Any other data can still be accessed through the basic operations of `autosar-data`, because the
17//! calls to `autosar-data` and `autosar-data-abstraction` can be mixed freely.
18//!
19//! # Features
20//!
21//! Autosar Classic Platform:
22//! - Communication:
23//!   - Busses
24//!     - CAN
25//!     - Ethernet (both old and new style)
26//!     - FlexRay
27//!     - not supported: LIN, J1939
28//!   - PDUs
29//!   - Signals
30//!   - Transformations: SomeIp, E2E, Com
31//! - Data Types
32//!   - Basic data types
33//!   - Implementation data types
34//!   - Application data types
35//! - Software Components
36//!   - Atomic SWCs, Compositions, etc.
37//!   - Interfaces
38//!   - Ports
39//!   - Internal behavior: Runnables, Events, etc.
40//! - ECU Configuration
41//!
42//! # Example
43//!
44//! ```rust
45//! # use autosar_data::*;
46//! # use autosar_data_abstraction::*;
47//! # use autosar_data_abstraction::communication::*;
48//! # fn main() -> Result<(), AutosarAbstractionError> {
49//! let model = AutosarModelAbstraction::create("file.arxml", AutosarVersion::Autosar_00049);
50//! let package_1 = model.get_or_create_package("/System")?;
51//! let system = package_1.create_system("System", SystemCategory::SystemExtract)?;
52//! let package_2 = model.get_or_create_package("/Clusters")?;
53//!
54//! // create an Ethernet cluster and a physical channel for VLAN 33
55//! let eth_cluster = system.create_ethernet_cluster("EthCluster", &package_2)?;
56//! let vlan_info = EthernetVlanInfo {
57//!     vlan_id: 33,
58//!     vlan_name: "VLAN_33".to_string(),
59//! };
60//! let eth_channel = eth_cluster.create_physical_channel("EthChannel", Some(&vlan_info))?;
61//! let vlan_info_2 = eth_channel.vlan_info().unwrap();
62//!
63//! // create an ECU instance and connect it to the Ethernet channel
64//! let package_3 = model.get_or_create_package("/Ecus")?;
65//! let ecu_instance_a = system.create_ecu_instance("Ecu_A", &package_3)?;
66//! let ethctrl = ecu_instance_a
67//!     .create_ethernet_communication_controller(
68//!         "EthernetController",
69//!         Some("ab:cd:ef:01:02:03".to_string())
70//!     )?;
71//! let channels_iter = ethctrl.connected_channels();
72//! ethctrl.connect_physical_channel("Ecu_A_connector", &eth_channel)?;
73//! let channels_iter = ethctrl.connected_channels();
74//!
75//! // ...
76//! # Ok(())}
77//! ```
78
79#![warn(missing_docs)]
80
81use std::path::Path;
82
83use autosar_data::{
84    ArxmlFile, AutosarDataError, AutosarModel, AutosarVersion, Element, ElementName, EnumItem, WeakElement,
85};
86use thiserror::Error;
87
88// modules that are visible in the API
89pub mod communication;
90pub mod datatype;
91pub mod ecu_configuration;
92pub mod software_component;
93
94// internal modules that only serve to split up the code
95mod arpackage;
96mod ecuinstance;
97mod system;
98
99// export the content of the internal modules
100pub use arpackage::{ArPackage, ReferenceBase};
101pub use ecuinstance::*;
102pub use system::*;
103
104/// The error type `AutosarAbstractionError` wraps all errors from the crate
105#[derive(Error, Debug)]
106#[non_exhaustive]
107pub enum AutosarAbstractionError {
108    /// converting an autosar-data element to a class in the abstract model failed
109    #[error("conversion error: could not convert {} to {}", .element.element_name(), dest)]
110    ConversionError {
111        /// the element that could not be converted
112        element: Element,
113        /// the name of the destination type
114        dest: String,
115    },
116
117    /// converting an autosar-data element to a class in the abstract model failed
118    #[error("value conversion error: could not convert {} to {}", .value, .dest)]
119    ValueConversionError {
120        /// the value that could not be converted
121        value: String,
122        /// the name of the destination type
123        dest: String,
124    },
125
126    /// `ModelError` wraps [`AutosarDataError`] errors from autosar-data operations, e.g.
127    /// [`AutosarDataError::ItemDeleted`], [`AutosarDataError::IncorrectContentType`], ...
128    #[error("model error: {}", .0)]
129    ModelError(AutosarDataError),
130
131    /// an invalid Autosar path was passed as a parameter
132    #[error("invalid path: {}", .0)]
133    InvalidPath(String),
134
135    /// an item could not be created because another item already fulfills its role in the model
136    #[error("the item already exists")]
137    ItemAlreadyExists,
138
139    /// the function parameter has an invalid value
140    #[error("invalid parameter: {}", .0)]
141    InvalidParameter(String),
142}
143
144impl From<AutosarDataError> for AutosarAbstractionError {
145    fn from(err: AutosarDataError) -> Self {
146        AutosarAbstractionError::ModelError(err)
147    }
148}
149
150//#########################################################
151
152/// The `AbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model.
153pub trait AbstractionElement: Clone + PartialEq + TryFrom<autosar_data::Element> {
154    /// Get the underlying `Element` from the abstraction element
155    #[must_use]
156    fn element(&self) -> &Element;
157
158    /// Remove this element from the model
159    ///
160    /// `deep` indicates whether elements that depend on this element should also be removed.
161    fn remove(self, _deep: bool) -> Result<(), AutosarAbstractionError> {
162        let element = self.element();
163        let Some(parent) = element.parent()? else {
164            // root element cannot be removed
165            return Err(AutosarAbstractionError::InvalidParameter(
166                "cannot remove root element".to_string(),
167            ));
168        };
169
170        if element.is_identifiable() {
171            let model = element.model()?;
172            let path = element.path()?;
173            let inbound_refs = model.get_references_to(&path);
174            for ref_elem in inbound_refs.iter().filter_map(WeakElement::upgrade) {
175                let Ok(Some(parent)) = ref_elem.parent() else {
176                    continue;
177                };
178                match ref_elem.element_name() {
179                    ElementName::FibexElementRef => {
180                        // explicit handling of FIBEX-ELEMENTS -> FIBEX-ELEMENT-REF-CONDITIONAl -> FIBEX-ELEMENT-REF
181                        if let Ok(Some(grandparent)) = parent.parent() {
182                            grandparent.remove_sub_element(parent)?;
183                        }
184                    }
185                    _ => {
186                        // Fallback: just remove the reference
187                        // In many cases this leaves the model in an invalid state, but it
188                        // is always better than a dangling reference
189                        let _ = parent.remove_sub_element(ref_elem);
190                    }
191                }
192            }
193        }
194
195        parent.remove_sub_element(element.clone())?;
196        Ok(())
197    }
198}
199
200/// The `IdentifiableAbstractionElement` trait is implemented by all classes that represent elements in the AUTOSAR model that have an item name.
201pub trait IdentifiableAbstractionElement: AbstractionElement {
202    /// Get the item name of the element
203    #[must_use]
204    fn name(&self) -> Option<String> {
205        self.element().item_name()
206    }
207
208    /// Set the item name of the element
209    fn set_name(&self, name: &str) -> Result<(), AutosarAbstractionError> {
210        self.element().set_item_name(name)?;
211        Ok(())
212    }
213}
214
215macro_rules! abstraction_element {
216    ($name: ident, $base_elem: ident) => {
217        impl TryFrom<autosar_data::Element> for $name {
218            type Error = AutosarAbstractionError;
219
220            fn try_from(element: autosar_data::Element) -> Result<Self, Self::Error> {
221                if element.element_name() == autosar_data::ElementName::$base_elem {
222                    Ok($name(element))
223                } else {
224                    Err(AutosarAbstractionError::ConversionError {
225                        element,
226                        dest: stringify!($name).to_string(),
227                    })
228                }
229            }
230        }
231
232        impl AbstractionElement for $name {
233            fn element(&self) -> &autosar_data::Element {
234                &self.0
235            }
236        }
237
238        impl From<$name> for autosar_data::Element {
239            fn from(val: $name) -> Self {
240                val.0
241            }
242        }
243    };
244}
245
246pub(crate) use abstraction_element;
247
248//#########################################################
249
250/// The `AutosarModelAbstraction` wraps an `AutosarModel` and provides additional functionality
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct AutosarModelAbstraction(AutosarModel);
253
254impl AutosarModelAbstraction {
255    /// Create a new `AutosarModelAbstraction` from an `AutosarModel`
256    #[must_use]
257    pub fn new(model: AutosarModel) -> Self {
258        Self(model)
259    }
260
261    /// create a new `AutosarModelAbstraction` with an empty `AutosarModel`
262    ///
263    /// You must specify a file name for the initial file in the model. This file is not created on disk immediately.
264    /// The model also needs an `AutosarVersion`.
265    pub fn create<P: AsRef<Path>>(file_name: P, version: AutosarVersion) -> Self {
266        let model = AutosarModel::new();
267        // create the initial file in the model - create_file can return a DuplicateFileName
268        // error, but hthis is not a concern for the first file, so it is always safe to unwrap
269        model.create_file(file_name, version).unwrap();
270        Self(model)
271    }
272
273    /// create an `AutosarModelAbstraction` from a file on disk
274    pub fn from_file<P: AsRef<Path>>(file_name: P) -> Result<Self, AutosarAbstractionError> {
275        let model = AutosarModel::new();
276        model.load_file(file_name, true)?;
277        Ok(Self(model))
278    }
279
280    /// Create an `AutosarModelAbstraction` from a buffer
281    ///
282    /// Since all autosar data is always associated with a file, a file name must be provided
283    /// but no file is created on disk unless you call `write()`.
284    pub fn from_buffer<P: AsRef<Path>>(
285        buffer: &[u8],
286        file_name: P,
287        strict: bool,
288    ) -> Result<Self, AutosarAbstractionError> {
289        let model = AutosarModel::new();
290        model.load_buffer(buffer, file_name, strict)?;
291        Ok(Self(model))
292    }
293
294    /// Get the underlying `AutosarModel` from the abstraction model
295    #[must_use]
296    pub fn model(&self) -> &AutosarModel {
297        &self.0
298    }
299
300    /// Get the root element of the model
301    #[must_use]
302    pub fn root_element(&self) -> Element {
303        self.0.root_element()
304    }
305
306    /// iterate over all top-level packages
307    pub fn packages(&self) -> impl Iterator<Item = ArPackage> + Send + use<> {
308        self.0
309            .root_element()
310            .get_sub_element(ElementName::ArPackages)
311            .into_iter()
312            .flat_map(|elem| elem.sub_elements())
313            .filter_map(|elem| ArPackage::try_from(elem).ok())
314    }
315
316    /// Get a package by its path or create it if it does not exist
317    pub fn get_or_create_package(&self, path: &str) -> Result<ArPackage, AutosarAbstractionError> {
318        ArPackage::get_or_create(&self.0, path)
319    }
320
321    /// Create a new file in the model
322    pub fn create_file(&self, file_name: &str, version: AutosarVersion) -> Result<ArxmlFile, AutosarAbstractionError> {
323        let arxml_file = self.0.create_file(file_name, version)?;
324        Ok(arxml_file)
325    }
326
327    /// Load a file into the model
328    pub fn load_file<P: AsRef<Path>>(
329        &self,
330        file_name: P,
331        strict: bool,
332    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarAbstractionError> {
333        let value = self.0.load_file(file_name, strict)?;
334        Ok(value)
335    }
336
337    /// Load a buffer into the model
338    pub fn load_buffer<P: AsRef<Path>>(
339        &self,
340        buffer: &[u8],
341        file_name: P,
342        strict: bool,
343    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarAbstractionError> {
344        let value = self.0.load_buffer(buffer, file_name, strict)?;
345        Ok(value)
346    }
347
348    /// iterate over all files in the model
349    pub fn files(&self) -> impl Iterator<Item = ArxmlFile> + Send + use<> {
350        self.0.files()
351    }
352
353    /// write the model to disk, creating or updating all files in the model
354    pub fn write(&self) -> Result<(), AutosarAbstractionError> {
355        self.0.write()?;
356        Ok(())
357    }
358
359    /// Get an element by its path
360    #[must_use]
361    pub fn get_element_by_path(&self, path: &str) -> Option<Element> {
362        self.0.get_element_by_path(path)
363    }
364
365    /// find an existing SYSTEM in the model, if it exists
366    ///
367    /// # Example
368    ///
369    /// ```
370    /// # use autosar_data::*;
371    /// # use autosar_data_abstraction::*;
372    /// # fn main() -> Result<(), AutosarAbstractionError> {
373    /// let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00048);
374    /// # let package = model.get_or_create_package("/my/pkg")?;
375    /// let system = package.create_system("System", SystemCategory::SystemExtract)?;
376    /// if let Some(sys_2) = model.find_system() {
377    ///     assert_eq!(system, sys_2);
378    /// }
379    /// # Ok(())}
380    /// ```
381    #[must_use]
382    pub fn find_system(&self) -> Option<System> {
383        System::find(&self.0)
384    }
385}
386
387//#########################################################
388
389/// The `ByteOrder` is used to define the order of bytes in a multi-byte value
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub enum ByteOrder {
392    /// Most significant byte at the lowest address = big endian
393    MostSignificantByteFirst,
394    /// Most significant byte at the highest address = little endian
395    MostSignificantByteLast,
396    /// The byte order is not defined / not relevant
397    Opaque,
398}
399
400impl TryFrom<EnumItem> for ByteOrder {
401    type Error = AutosarAbstractionError;
402
403    fn try_from(value: EnumItem) -> Result<Self, Self::Error> {
404        match value {
405            EnumItem::MostSignificantByteFirst => Ok(ByteOrder::MostSignificantByteFirst),
406            EnumItem::MostSignificantByteLast => Ok(ByteOrder::MostSignificantByteLast),
407            EnumItem::Opaque => Ok(ByteOrder::Opaque),
408            _ => Err(AutosarAbstractionError::ValueConversionError {
409                value: value.to_string(),
410                dest: "ByteOrder".to_string(),
411            }),
412        }
413    }
414}
415
416impl From<ByteOrder> for EnumItem {
417    fn from(value: ByteOrder) -> Self {
418        match value {
419            ByteOrder::MostSignificantByteFirst => EnumItem::MostSignificantByteFirst,
420            ByteOrder::MostSignificantByteLast => EnumItem::MostSignificantByteLast,
421            ByteOrder::Opaque => EnumItem::Opaque,
422        }
423    }
424}
425
426//##################################################################
427
428pub(crate) fn make_unique_name(model: &AutosarModel, base_path: &str, initial_name: &str) -> String {
429    let mut full_path = format!("{base_path}/{initial_name}");
430    let mut name = initial_name.to_string();
431    let mut counter = 0;
432    while model.get_element_by_path(&full_path).is_some() {
433        counter += 1;
434        name = format!("{initial_name}_{counter}");
435        full_path = format!("{base_path}/{name}");
436    }
437
438    name
439}
440
441//##################################################################
442
443/// check if the element is used anywhere
444pub(crate) fn is_used(element: &Element) -> bool {
445    let Ok(model) = element.model() else {
446        // not connected to model -> unused
447        return false;
448    };
449    let Ok(path) = element.path() else {
450        // not connected to model any more -> unused
451        // this case is only reachable through a race condition (parallel deletion)
452        return false;
453    };
454    let references = model.get_references_to(&path);
455
456    // it is unused if there are no references to it
457    !references.is_empty()
458}
459
460//##################################################################
461
462// returns the named parent and the parent of each element that references the given element
463pub(crate) fn get_reference_parents(element: &Element) -> Result<Vec<(Element, Element)>, AutosarAbstractionError> {
464    let model = element.model()?;
465    let path = element.path()?;
466    let references = model.get_references_to(&path);
467
468    let parents = references
469        .iter()
470        .filter_map(WeakElement::upgrade)
471        .filter_map(|ref_elem| {
472            Some((
473                ref_elem.named_parent().ok().flatten()?,
474                ref_elem.parent().ok().flatten()?,
475            ))
476        })
477        .collect();
478
479    Ok(parents)
480}
481
482//##################################################################
483
484#[cfg(test)]
485mod test {
486    use super::*;
487    use autosar_data::AutosarModel;
488
489    #[test]
490    fn create_model() {
491        // create a new AutosarModelAbstraction based on an existing AutosarModel
492        let raw_model = AutosarModel::new();
493        let model = AutosarModelAbstraction::new(raw_model.clone());
494        assert_eq!(model.model(), &raw_model);
495
496        // create an empty AutosarModelAbstraction from scratch
497        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
498        let root = model.root_element();
499        assert_eq!(root.element_name(), ElementName::Autosar);
500    }
501
502    #[test]
503    fn create_model_from_file() {
504        let tempdir = tempfile::tempdir().unwrap();
505        let filename = tempdir.path().join("test.arxml");
506
507        // write a new arxml file to disk
508        let model1 = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
509        model1.write().unwrap();
510
511        // create a new model from the file
512        let model2 = AutosarModelAbstraction::from_file(filename).unwrap();
513        let root = model2.root_element();
514        assert_eq!(root.element_name(), ElementName::Autosar);
515    }
516
517    #[test]
518    fn model_files() {
519        let model = AutosarModelAbstraction::create("file1.arxml", AutosarVersion::Autosar_00049);
520        let file = model.create_file("file2.arxml", AutosarVersion::Autosar_00049).unwrap();
521        let files: Vec<_> = model.files().collect();
522        assert_eq!(files.len(), 2);
523        assert_eq!(files[1], file);
524    }
525
526    #[test]
527    fn model_load_file() {
528        let tempdir = tempfile::tempdir().unwrap();
529        let filename = tempdir.path().join("test.arxml");
530
531        // write a new arxml file to disk
532        let model = AutosarModelAbstraction::create(filename.clone(), AutosarVersion::LATEST);
533        model.write().unwrap();
534
535        // load the file into a new model
536        let model = AutosarModelAbstraction::new(AutosarModel::new());
537        let (_file, errors) = model.load_file(filename, true).unwrap();
538        assert!(errors.is_empty());
539    }
540
541    #[test]
542    fn model_packages() {
543        let model = AutosarModelAbstraction::create("filename", AutosarVersion::Autosar_00049);
544        let package = model.get_or_create_package("/package").unwrap();
545        let package2 = model.get_or_create_package("/other_package").unwrap();
546        model.get_or_create_package("/other_package/sub_package").unwrap();
547        let packages: Vec<_> = model.packages().collect();
548        assert_eq!(packages.len(), 2);
549        assert_eq!(packages[0], package);
550        assert_eq!(packages[1], package2);
551    }
552
553    #[test]
554    fn errors() {
555        let model = AutosarModel::new();
556
557        let err = AutosarAbstractionError::ConversionError {
558            element: model.root_element(),
559            dest: "TEST".to_string(),
560        };
561        let string = format!("{err}");
562        assert!(!string.is_empty());
563
564        let err = AutosarAbstractionError::InvalidPath("lorem ipsum".to_string());
565        let string = format!("{err}");
566        assert!(!string.is_empty());
567
568        let err = AutosarAbstractionError::ItemAlreadyExists;
569        let string = format!("{err}");
570        assert!(!string.is_empty());
571    }
572
573    #[test]
574    fn from_buffer() {
575        let buffer = br#"
576        <?xml version="1.0" encoding="UTF-8" standalone="no"?>
577        <AUTOSAR xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00046.xsd">
578            <AR-PACKAGES>
579                <AR-PACKAGE>
580                    <SHORT-NAME>MyPackage</SHORT-NAME>
581                </AR-PACKAGE>
582            </AR-PACKAGES>
583        </AUTOSAR>
584        "#;
585
586        let model = AutosarModelAbstraction::from_buffer(buffer, "buffer.arxml", true).unwrap();
587        let package = model.get_or_create_package("/MyPackage").unwrap();
588        assert_eq!(package.name().unwrap(), "MyPackage");
589    }
590
591    #[test]
592    fn load_buffer() {
593        let model = AutosarModelAbstraction::create("dummy", AutosarVersion::LATEST);
594        let buffer = br#"
595        <?xml version="1.0" encoding="UTF-8" standalone="no"?>
596        <AUTOSAR xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00046.xsd">
597            <AR-PACKAGES>
598                <AR-PACKAGE>
599                    <SHORT-NAME>MyPackage</SHORT-NAME>
600                </AR-PACKAGE>
601            </AR-PACKAGES>
602        </AUTOSAR>
603        "#;
604        let (_file, errors) = model.load_buffer(buffer, "buffer.arxml", true).unwrap();
605        assert!(errors.is_empty());
606        assert_eq!(model.files().count(), 2);
607    }
608}