Skip to main content

autosar_data/
lib.rs

1//! Crate autosar-data
2//!
3//! This crate provides functionality to read, modify and write Autosar arxml files,
4//! both separately and in projects consisting of multiple files.
5//!
6//! Features:
7//!
8//!  - read and write arxml files
9//!  - fully validate all data when it is loaded
10//!  - non-strict mode so that invalid but structurally sound data can be loaded
11//!  - various element operations to modify and create sub-elements, data and attributes
12//!  - support for Autosar paths and cross references
13//!  - all operations are thread safe, e.g. it is possible to load mutliple files on separate threads
14//!
15//! The crate `autosar-data-abstraction` provides higher level abstractions on top of this crate, which simplify common tasks.
16//!
17//! # Examples
18//!
19//! ```no_run
20//! use autosar_data::*;
21//! # fn main() -> Result<(), AutosarDataError> {
22//! /* load a multi-file data model */
23//! let model = AutosarModel::new();
24//! let (file_1, warnings_1) = model.load_file("some_file.arxml", false)?;
25//! let (file_2, warnings_2) = model.load_file("other_file.arxml", false)?;
26//! /* load a buffer */
27//! # let buffer = b"";
28//! let (file_3, _) = model.load_buffer(buffer, "filename.arxml", true)?;
29//!
30//! /* write all files of the model */
31//! model.write()?;
32//!
33//! /* alternatively: */
34//! for file in model.files() {
35//!     let file_data = file.serialize();
36//!     // do something with file_data
37//! }
38//!
39//! /* iterate over all elements in all files */
40//! for (depth, element) in model.elements_dfs() {
41//!     if element.is_identifiable() {
42//!         /* the element is identifiable using an Autosar path */
43//!         println!("{depth}: {}, {}", element.element_name(), element.path()?);
44//!     } else {
45//!         println!("{depth}: {}", element.element_name());
46//!     }
47//! }
48//!
49//! /* get an element by its Autosar path */
50//! let pdu_element = model.get_element_by_path("/Package/Mid/PduName").unwrap();
51//!
52//! /* work with the content of elements */
53//! if let Some(length) = pdu_element
54//!     .get_sub_element(ElementName::Length)
55//!     .and_then(|elem| elem.character_data())
56//!     .and_then(|cdata| cdata.string_value())
57//! {
58//!     println!("Pdu Length: {length}");
59//! }
60//!
61//! /* modify the attributes of an element */
62//! pdu_element.set_attribute_string(AttributeName::Uuid, "12ab34cd-1234-1234-1234-12ab34cd56ef");
63//! pdu_element.remove_attribute(AttributeName::Uuid);
64//!
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! # Example Programs
70//!
71//! Two complete example programs can be found in the examples directory of the source repostitory. They are:
72//!
73//!  - businfo, which extracts information about bus settings, frames, pdus and signals from an autosar ECU extract
74//!  - `generate_files`, which for each Autosar version generates an arxml file containing a least one instance of every specified element
75//!
76
77#![warn(missing_docs)]
78
79use autosar_data_specification::{AttributeSpec, CharacterDataSpec, ContentMode, ElementType};
80use fxhash::{FxBuildHasher, FxHashMap};
81use indexmap::IndexMap;
82pub use iterators::*;
83use parking_lot::RwLock;
84use parser::ArxmlParser;
85use smallvec::SmallVec;
86use std::collections::HashSet;
87use std::path::{Path, PathBuf};
88use std::sync::{Arc, Weak};
89use std::{fs::File, io::Read};
90use thiserror::Error;
91
92mod arxmlfile;
93mod autosarmodel;
94mod chardata;
95mod element;
96mod elementraw;
97mod iterators;
98mod lexer;
99mod parser;
100
101// allow public access to the error sub-types
102pub use lexer::ArxmlLexerError;
103pub use parser::ArxmlParserError;
104
105// reexport some of the info from the specification
106pub use autosar_data_specification::AttributeName;
107pub use autosar_data_specification::AutosarVersion;
108pub use autosar_data_specification::ElementName;
109pub use autosar_data_specification::EnumItem;
110
111type FxIndexMap<K, V> = IndexMap<K, V, FxBuildHasher>;
112
113#[derive(Debug, Clone)]
114pub(crate) struct ReferenceBaseInfo {
115    pub(crate) owner_package_path: String,
116    pub(crate) package_ref: String,
117    pub(crate) package_ref_base: Option<String>,
118}
119
120/// `AutosarModel` is the top level data type in the autosar-data crate.
121///
122/// An instance of `AutosarModel` is required for all other operations.
123///
124/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
125/// It stores the association between elements and files.
126/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
127#[derive(Clone)]
128pub struct AutosarModel(Arc<RwLock<AutosarModelRaw>>);
129
130// Weak reference to an instance of AutosarModel
131#[derive(Clone)]
132pub(crate) struct WeakAutosarModel(Weak<RwLock<AutosarModelRaw>>);
133
134/// The inner autosar data model (unlocked)
135///
136/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
137/// It stores the association between elements and files.
138/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
139pub(crate) struct AutosarModelRaw {
140    root_element: Element,
141    files: Vec<ArxmlFile>,
142    /// `identifiables` is a `HashMap` of all named elements, needed to resolve references without doing a full search.
143    identifiables: FxIndexMap<String, WeakElement>,
144    /// `reference_origins` is a `HashMap` of all referencing elements.
145    reference_origins: FxHashMap<String, Vec<WeakElement>>,
146    /// `relative_reference_origins` is a `HashMap` of all referencing elements with relative paths.
147    relative_reference_origins: FxHashMap<String, Vec<(WeakElement, String)>>, // Relative path -> [(referencing element, base label)]*
148    /// `reference_bases` stores all REFERENCE-BASE declarations indexed by short label.
149    reference_bases: FxHashMap<String, Vec<ReferenceBaseInfo>>,
150}
151
152/// The error type `AutosarDataError` wraps all errors that can be generated anywhere in the crate
153#[derive(Error, Debug)]
154#[non_exhaustive]
155pub enum AutosarDataError {
156    /// `IoErrorRead`: An `IoError` that occurred while reading a file
157    #[error("Failed to read {}: {ioerror}", .filename.to_string_lossy())]
158    IoErrorRead {
159        /// The filename that caused the error
160        filename: PathBuf,
161        /// The underlying `std::io::Error`
162        ioerror: std::io::Error,
163    },
164
165    /// `IoErrorOpen`: an `IoError` that occurres while opening a file
166    #[error("Failed to open {}: {ioerror}", .filename.to_string_lossy())]
167    IoErrorOpen {
168        /// The filename that caused the error
169        filename: PathBuf,
170        /// The underlying `std::io::Error`
171        ioerror: std::io::Error,
172    },
173
174    /// `IoErrorWrite`: An `IoError` that occurred while writing a file
175    #[error("Failed to write {}: {ioerror}", .filename.to_string_lossy())]
176    IoErrorWrite {
177        /// The filename that caused the error
178        filename: PathBuf,
179        /// The underlying `std::io::Error`
180        ioerror: std::io::Error,
181    },
182
183    /// `DuplicateFilenameError`: The model can't contain two files with identical names
184    #[error("Could not {verb} file {}: A file with this name is already loaded", .filename.to_string_lossy())]
185    DuplicateFilenameError {
186        /// description of the operation that failed
187        verb: &'static str,
188        /// The filename that caused the error
189        filename: PathBuf,
190    },
191
192    /// `LexerError`: An error originating in the lexer, such as unclodes strings, mismatched '<' and '>', etc
193    #[error("Failed to tokenize {} on line {line}: {source}", .filename.to_string_lossy())]
194    LexerError {
195        /// The filename that caused the error
196        filename: PathBuf,
197        /// The line number where the error occurred
198        line: usize,
199        /// The underlying `ArxmlLexerError`
200        source: ArxmlLexerError,
201    },
202
203    /// `ParserError`: A parser error
204    #[error("Failed to parse {}:{line}: {source}", .filename.to_string_lossy())]
205    ParserError {
206        /// The filename that caused the error
207        filename: PathBuf,
208        /// The line number where the error occurred
209        line: usize,
210        /// The underlying `ArxmlParserError`
211        source: ArxmlParserError,
212    },
213
214    /// A file could not be loaded into the model, because the Autosar paths of the new data overlapped with the Autosar paths of the existing data
215    #[error("Loading failed: element path {path} of new data in {} overlaps with the existing loaded data", .filename.to_string_lossy())]
216    OverlappingDataError {
217        /// The filename that caused the error
218        filename: PathBuf,
219        /// Autosar path of the element that caused the error
220        path: String,
221    },
222
223    /// An operation failed because one of the elements involved is in the deleted state and will be freed once its reference count reaches zero
224    #[error("Operation failed: the item has been deleted")]
225    ItemDeleted,
226
227    /// A sub element could not be created at or moved to the given position
228    #[error("Invalid position for an element of this kind")]
229    InvalidPosition,
230
231    /// The Autosar version of the new file or element did not match the version already in use
232    #[error("Version mismatch between existing {} and new {}", .version_cur, .version_new)]
233    VersionMismatch {
234        /// The current version of the model
235        version_cur: AutosarVersion,
236        /// The version of the new file or element
237        version_new: AutosarVersion,
238    },
239
240    /// The Autosar version is not compatible with the data
241    #[error("Version {} is not compatible with the element data", .version)]
242    VersionIncompatibleData {
243        /// The incompatible version
244        version: AutosarVersion,
245    },
246
247    /// A function that only applies to identifiable elements was called on an element which is not identifiable
248    #[error("The element at {} is not identifiable", .xmlpath)]
249    ElementNotIdentifiable {
250        /// The "xml path" (a string representation of the path to the element) where the error occurred
251        xmlpath: String,
252    },
253
254    /// An item name is required to perform this action
255    #[error("An item name is required for element {}", .element)]
256    ItemNameRequired {
257        /// The element where the item name is required
258        element: ElementName,
259    },
260
261    /// The element has the wrong content type for the requested operation, e.g. inserting elements when the content type only allows character data
262    #[error("Incorrect content type for element {}", .element)]
263    IncorrectContentType {
264        /// The element where the content type is incorrect
265        element: ElementName,
266    },
267
268    /// Could not insert a sub element, because it conflicts with an existing sub element
269    #[error("Element insertion conflict: {} could not be inserted in {} ({})", .element, .parent, .parent_path)]
270    ElementInsertionConflict {
271        /// The name of the parent element
272        parent: ElementName,
273        /// The name of the element that could not be inserted
274        element: ElementName,
275        /// path of the parent element
276        parent_path: String,
277    },
278
279    /// The `ElementName` is not a valid sub element according to the specification.
280    #[error("Element {} is not a valid sub element of {}", .element, .parent)]
281    InvalidSubElement {
282        /// The name of the parent element
283        parent: ElementName,
284        /// The name of the element that is not a valid sub element
285        element: ElementName,
286    },
287
288    /// Remove operation failed: the given element is not a sub element of the element from which it was supposed to be removed
289    #[error("element {} not found in parent {}", .target, .parent)]
290    ElementNotFound {
291        /// The name of the element that was not found
292        target: ElementName,
293        /// The name of the parent element
294        parent: ElementName,
295    },
296
297    /// [`Element::remove_sub_element`] cannot remove the SHORT-NAME of identifiable elements, as this would render the data invalid
298    #[error("the SHORT-NAME sub element may not be removed")]
299    ShortNameRemovalForbidden,
300
301    /// get/set reference target was called for an element that is not a reference
302    #[error("The current element is not a reference")]
303    NotReferenceElement,
304
305    /// The reference is invalid
306    #[error("The reference is not valid")]
307    InvalidReference,
308
309    /// The reference base of a relative reference is not valid (not a valid label, or not a prefix of the target element)
310    #[error("The reference base is not valid")]
311    InvalidReferenceBase,
312
313    /// An element could not be renamed, since this item name is already used by a different element
314    #[error("Duplicate item name {} in {}", .item_name, .element)]
315    DuplicateItemName {
316        /// The name of the element that could not be renamed
317        element: ElementName,
318        /// The target name that caused the error
319        item_name: String,
320    },
321
322    /// Cannot move an element into its own sub element
323    #[error("Cannot move an element into its own sub element")]
324    ForbiddenMoveToSubElement,
325
326    /// Cannot copy an element (or a hierarchy including the element) into itself
327    #[error("Cannot create a copy that includes the destination")]
328    ForbiddenCopyOfParent,
329
330    /// A parent element is currently locked by a different operation. The operation wa aborted to avoid a deadlock.
331    #[error("A parent element is currently locked by a different operation")]
332    ParentElementLocked,
333
334    /// The attribute is invalid here
335    #[error("The attribute is not valid for this element")]
336    InvalidAttribute,
337
338    /// The attribute value is invalid
339    #[error("The given value is not valid for this attribute")]
340    InvalidAttributeValue,
341
342    /// The file is from a different model and may not be used in this operation
343    #[error("The file is from a different model and may not be used in this operation")]
344    InvalidFile,
345
346    /// The file is empty and cannot be serialized
347    #[error("The file is empty and cannot be serialized")]
348    EmptyFile,
349
350    /// The newly loaded file diverges from the combined model on an element which is not splittable according to the metamodel
351    #[error("The new file could not be merged, because it diverges from the model on non-splittable element {}", .path)]
352    InvalidFileMerge {
353        /// The path of the element where the merge failed
354        path: String,
355    },
356
357    /// The operation cannot be completed because the model does not contain any files
358    #[error("The operation cannot be completed because the model does not contain any files")]
359    NoFilesInModel,
360
361    /// Modifying the fileset of this element is not allowed
362    #[error(
363        "Modifying the fileset of this element is not allowed, because the parent of the element is not marked as splittable"
364    )]
365    FilesetModificationForbidden,
366}
367
368/// An Autosar arxml file
369#[derive(Clone)]
370pub struct ArxmlFile(Arc<RwLock<ArxmlFileRaw>>);
371
372/// Weak reference to an arxml file
373///
374/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
375#[derive(Clone)]
376pub struct WeakArxmlFile(Weak<RwLock<ArxmlFileRaw>>);
377
378/// The data of an arxml file
379pub(crate) struct ArxmlFileRaw {
380    pub(crate) version: AutosarVersion,
381    model: WeakAutosarModel,
382    pub(crate) filename: PathBuf,
383    pub(crate) xml_standalone: Option<bool>, // preserve the xml standalone attribute
384}
385
386/// An arxml element
387///
388/// This is a wrapper type which provides all the necessary manipulation functions.
389#[derive(Clone)]
390pub struct Element(Arc<RwLock<ElementRaw>>);
391
392/// Weak reference to an Element
393///
394/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
395///
396/// This `WeakElement` can be held indefinitely without forcing the referenced data to remain valid.
397/// When access is needed, the method `upgrade()` will attempt to get a strong reference and return an [Element]
398#[derive(Clone)]
399pub struct WeakElement(Weak<RwLock<ElementRaw>>);
400
401/// The data of an arxml element
402pub(crate) struct ElementRaw {
403    pub(crate) parent: ElementOrModel,
404    pub(crate) elemname: ElementName,
405    pub(crate) elemtype: ElementType,
406    pub(crate) content: SmallVec<[ElementContent; 4]>,
407    pub(crate) attributes: SmallVec<[Attribute; 1]>,
408    pub(crate) file_membership: HashSet<WeakArxmlFile>,
409    pub(crate) comment: Option<String>,
410}
411
412/// A single attribute of an arxml element
413#[derive(Clone, PartialEq, Eq)]
414pub struct Attribute {
415    /// The name of the attribute
416    pub attrname: AttributeName,
417    /// The content of the attribute
418    pub content: CharacterData,
419}
420
421/// One content item inside an arxml element
422///
423/// Elements may contain other elements, character data, or a mixture of both, depending on their type.
424#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
425pub enum ElementContent {
426    /// A sub element
427    Element(Element),
428    /// Character data
429    CharacterData(CharacterData),
430}
431
432/// The enum `CharacterData` provides typed access to the content of elements and attributes
433///
434/// Example:
435///
436/// In the xml string ```<SHORT-NAME>SomeName</SHORT-NAME>``` the character data
437/// "`SomeName`" will be loaded as `CharacterData::String("SomeName`"), while the content of the
438/// attribute <... DEST="UNIT"> will be loaded as `CharacterData::Enum(EnumItem::Unit`)
439#[derive(Debug, PartialEq, Clone)]
440pub enum CharacterData {
441    /// Character data is an enum value
442    Enum(EnumItem),
443    /// Character data is a string
444    String(String),
445    /// Character data is an unsigned integer
446    UnsignedInteger(u64),
447    /// Character data is a floating point number
448    Float(f64),
449}
450
451/// The content type of an [Element]
452#[derive(Debug, Eq, PartialEq, Clone, Copy)]
453pub enum ContentType {
454    /// The element only contains other elements
455    Elements,
456    /// The element only contains character data
457    CharacterData,
458    /// The element contains both character data and sub elements
459    Mixed,
460}
461
462/// Holds a weak reference to either an element or an arxml file
463///
464/// This enum is used for references to the parent of each element. For all elements other than the
465/// root element, the parent is an element. The root element itself has a reference to the `ArxmlFile` structure.
466#[derive(Clone)]
467pub(crate) enum ElementOrModel {
468    Element(WeakElement),
469    Model(WeakAutosarModel),
470    None, // needed while constructing the data trees, otherwise there's a chicken vs. egg problem
471}
472
473/// Possible kinds of compatibility errors that can be found by `ArxmlFile::check_version_compatibility()`
474#[derive(Debug, PartialEq, Clone)]
475pub enum CompatibilityError {
476    /// The element is not allowed in the target version
477    IncompatibleElement {
478        /// The element that is not allowed
479        element: Element,
480        /// The version mask of the element which indicates all allowed versions
481        version_mask: u32,
482    },
483    /// The attribute is not allowed in the target version
484    IncompatibleAttribute {
485        /// The element that contains the incompatible attribute
486        element: Element,
487        /// The incompatible attribute
488        attribute: AttributeName,
489        /// The version mask of the element which indicates all versions where the attribute is allowed
490        version_mask: u32,
491    },
492    /// The attribute value is not allowed in the target version
493    IncompatibleAttributeValue {
494        /// The element that contains the incompatible attribute
495        element: Element,
496        /// The incompatible attribute
497        attribute: AttributeName,
498        /// The incompatible attribute value
499        attribute_value: String,
500        /// The version mask of the element which indicates all versions where the attribute value is allowed
501        version_mask: u32,
502    },
503}
504
505/// information about a sub element
506///
507/// This structure is returned by [`Element::list_valid_sub_elements()`]
508#[derive(Debug, PartialEq, Clone)]
509pub struct ValidSubElementInfo {
510    /// name of the potential sub element
511    pub element_name: ElementName,
512    /// is the sub element named, i.e. does it need to be created with [`Element::create_named_sub_element()`]
513    pub is_named: bool,
514    /// is the sub element currently allowed, given the existing content of the element. Note that some sub elements are mutually exclusive.
515    pub is_allowed: bool,
516}
517
518const CHECK_FILE_SIZE: usize = 4096; // 4kb
519
520/// Check a file to see if it looks like an arxml file
521///
522/// Reads the beginning of the given file and checks if the data starts with a valid arxml header.
523/// If a header is found it immediately returns true and does not check any further data
524///
525/// The function returns false if the file cannot be read or if the data does not start with an arxml header
526///
527/// # Parameters
528/// - filename: name of the file to check
529///
530/// # Example
531///
532/// ```
533/// # let filename = "";
534/// if autosar_data::check_file(filename) {
535///     // it looks like an arxml file
536/// }
537/// ```
538pub fn check_file<P: AsRef<Path>>(filename: P) -> bool {
539    let mut buffer: [u8; CHECK_FILE_SIZE] = [0; CHECK_FILE_SIZE];
540
541    if File::open(filename).and_then(|mut file| file.read(&mut buffer)).is_ok() {
542        check_buffer(&buffer)
543    } else {
544        false
545    }
546}
547
548/// Check a buffer to see if the content looks like arxml data
549///
550/// The function returns true if the buffer starts with a valid arxml header (after skipping whitespace and comments).
551/// This function does not check anything after the header.
552///
553/// # Parameters
554/// - buffer: u8 slice containing the data to check
555///
556/// # Example
557/// ```
558/// # let buffer = Vec::new();
559/// if autosar_data::check_buffer(&buffer) {
560///     // it looks like arxml data
561/// }
562/// ```
563#[must_use]
564pub fn check_buffer(buffer: &[u8]) -> bool {
565    let mut parser = ArxmlParser::new(PathBuf::from("none"), buffer, false);
566    parser.check_arxml_header()
567}
568
569/// Custom Debug implementation for `Attribute`, in order to provide better formatting
570impl std::fmt::Debug for Attribute {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        write!(f, "Attribute: {:?} = {:?}", self.attrname, self.content)
573    }
574}
575
576/// provide PartialOrd for attributes; this is used while sorting elements
577impl PartialOrd for Attribute {
578    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
579        Some(self.cmp(other))
580    }
581}
582
583/// provide Ord for attributes; this is used while sorting elements
584impl Ord for Attribute {
585    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
586        self.attrname
587            .to_str()
588            .cmp(other.attrname.to_str())
589            .then(self.content.cmp(&other.content))
590    }
591}
592
593#[cfg(test)]
594mod test {
595    use std::{error::Error, io::Write, path::PathBuf};
596    use tempfile::tempdir;
597
598    use crate::*;
599
600    #[test]
601    fn error_traits() {
602        let err = AutosarDataError::ParserError {
603            filename: PathBuf::from("filename.arxml"),
604            line: 123,
605            source: crate::parser::ArxmlParserError::InvalidArxmlFileHeader,
606        };
607        assert!(err.source().is_some());
608        let errstr = format!("{err}");
609        let errdbg = format!("{err:#?}");
610        assert!(errstr != errdbg);
611    }
612
613    #[test]
614    fn test_check_file() {
615        let dir = tempdir().unwrap();
616
617        // called on a directory rather than a file -> false
618        assert!(!check_file(dir.path()));
619
620        // nonexistent file -> false
621        let nonexistent = dir.path().with_file_name("nonexistent.arxml");
622        assert!(!check_file(nonexistent));
623
624        // arbitrary non-arxml data -> false
625        let not_arxml_file = dir.path().with_file_name("not_arxml.bin");
626        File::create(&not_arxml_file)
627            .and_then(|mut file| write!(file, "text"))
628            .unwrap();
629        assert!(!check_file(not_arxml_file));
630
631        // file containing a valid arxml header -> true
632        let header = r#"<?xml version="1.0" encoding="utf-8"?>
633        <!-- comment --><!-- comment 2 -->
634        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
635        let arxml_file = dir.path().with_file_name("file.arxml");
636        File::create(&arxml_file)
637            .and_then(|mut file| file.write(header.as_bytes()))
638            .unwrap();
639        assert!(check_file(arxml_file));
640
641        assert!(check_buffer(header.as_bytes()));
642    }
643
644    #[test]
645    fn attribute_order() {
646        // attribute ordering: first by name, then by value
647        let a1 = Attribute {
648            attrname: AttributeName::Uuid,
649            content: CharacterData::String("Value1".to_string()),
650        };
651
652        let a2 = Attribute {
653            attrname: AttributeName::Uuid,
654            content: CharacterData::String("Value2".to_string()),
655        };
656        assert!(a1 < a2);
657
658        let a3 = Attribute {
659            attrname: AttributeName::T,
660            content: CharacterData::String("xyz".to_string()),
661        };
662        assert!(a3 < a1);
663
664        // PartialOrd
665        assert!(a1.partial_cmp(&a2) == Some(std::cmp::Ordering::Less));
666    }
667}