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/// `AutosarModel` is the top level data type in the autosar-data crate.
114///
115/// An instance of `AutosarModel` is required for all other operations.
116///
117/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
118/// It stores the association between elements and files.
119/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
120#[derive(Clone)]
121pub struct AutosarModel(Arc<RwLock<AutosarModelRaw>>);
122
123// Weak reference to an instance of AutosarModel
124#[derive(Clone)]
125pub(crate) struct WeakAutosarModel(Weak<RwLock<AutosarModelRaw>>);
126
127/// The inner autosar data model (unlocked)
128///
129/// The model contains the hierarchy of Autosar elements. It can be created manually or loaded from one or more arxml files.
130/// It stores the association between elements and files.
131/// In addition, this top-level structure provides caching of Autosar paths, to allow quick resolution of cross-references.
132pub(crate) struct AutosarModelRaw {
133 root_element: Element,
134 files: Vec<ArxmlFile>,
135 /// `identifiables` is a `HashMap` of all named elements, needed to resolve references without doing a full search.
136 identifiables: FxIndexMap<String, WeakElement>,
137 /// `reference_origins` is a `HashMap` of all referencing elements. This is needed to efficiently fix up the references when a referenced element is renamed.
138 reference_origins: FxHashMap<String, Vec<WeakElement>>,
139}
140
141/// The error type `AutosarDataError` wraps all errors that can be generated anywhere in the crate
142#[derive(Error, Debug)]
143#[non_exhaustive]
144pub enum AutosarDataError {
145 /// `IoErrorRead`: An `IoError` that occurred while reading a file
146 #[error("Failed to read {}: {ioerror}", .filename.to_string_lossy())]
147 IoErrorRead {
148 /// The filename that caused the error
149 filename: PathBuf,
150 /// The underlying `std::io::Error`
151 ioerror: std::io::Error,
152 },
153
154 /// `IoErrorOpen`: an `IoError` that occurres while opening a file
155 #[error("Failed to open {}: {ioerror}", .filename.to_string_lossy())]
156 IoErrorOpen {
157 /// The filename that caused the error
158 filename: PathBuf,
159 /// The underlying `std::io::Error`
160 ioerror: std::io::Error,
161 },
162
163 /// `IoErrorWrite`: An `IoError` that occurred while writing a file
164 #[error("Failed to write {}: {ioerror}", .filename.to_string_lossy())]
165 IoErrorWrite {
166 /// The filename that caused the error
167 filename: PathBuf,
168 /// The underlying `std::io::Error`
169 ioerror: std::io::Error,
170 },
171
172 /// `DuplicateFilenameError`: The model can't contain two files with identical names
173 #[error("Could not {verb} file {}: A file with this name is already loaded", .filename.to_string_lossy())]
174 DuplicateFilenameError {
175 /// description of the operation that failed
176 verb: &'static str,
177 /// The filename that caused the error
178 filename: PathBuf,
179 },
180
181 /// `LexerError`: An error originating in the lexer, such as unclodes strings, mismatched '<' and '>', etc
182 #[error("Failed to tokenize {} on line {line}: {source}", .filename.to_string_lossy())]
183 LexerError {
184 /// The filename that caused the error
185 filename: PathBuf,
186 /// The line number where the error occurred
187 line: usize,
188 /// The underlying `ArxmlLexerError`
189 source: ArxmlLexerError,
190 },
191
192 /// `ParserError`: A parser error
193 #[error("Failed to parse {}:{line}: {source}", .filename.to_string_lossy())]
194 ParserError {
195 /// The filename that caused the error
196 filename: PathBuf,
197 /// The line number where the error occurred
198 line: usize,
199 /// The underlying `ArxmlParserError`
200 source: ArxmlParserError,
201 },
202
203 /// 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
204 #[error("Loading failed: element path {path} of new data in {} overlaps with the existing loaded data", .filename.to_string_lossy())]
205 OverlappingDataError {
206 /// The filename that caused the error
207 filename: PathBuf,
208 /// Autosar path of the element that caused the error
209 path: String,
210 },
211
212 /// An operation failed because one of the elements involved is in the deleted state and will be freed once its reference count reaches zero
213 #[error("Operation failed: the item has been deleted")]
214 ItemDeleted,
215
216 /// A sub element could not be created at or moved to the given position
217 #[error("Invalid position for an element of this kind")]
218 InvalidPosition,
219
220 /// The Autosar version of the new file or element did not match the version already in use
221 #[error("Version mismatch between existing {} and new {}", .version_cur, .version_new)]
222 VersionMismatch {
223 /// The current version of the model
224 version_cur: AutosarVersion,
225 /// The version of the new file or element
226 version_new: AutosarVersion,
227 },
228
229 /// The Autosar version is not compatible with the data
230 #[error("Version {} is not compatible with the element data", .version)]
231 VersionIncompatibleData {
232 /// The incompatible version
233 version: AutosarVersion,
234 },
235
236 /// A function that only applies to identifiable elements was called on an element which is not identifiable
237 #[error("The element at {} is not identifiable", .xmlpath)]
238 ElementNotIdentifiable {
239 /// The "xml path" (a string representation of the path to the element) where the error occurred
240 xmlpath: String,
241 },
242
243 /// An item name is required to perform this action
244 #[error("An item name is required for element {}", .element)]
245 ItemNameRequired {
246 /// The element where the item name is required
247 element: ElementName,
248 },
249
250 /// The element has the wrong content type for the requested operation, e.g. inserting elements when the content type only allows character data
251 #[error("Incorrect content type for element {}", .element)]
252 IncorrectContentType {
253 /// The element where the content type is incorrect
254 element: ElementName,
255 },
256
257 /// Could not insert a sub element, because it conflicts with an existing sub element
258 #[error("Element insertion conflict: {} could not be inserted in {} ({})", .element, .parent, .parent_path)]
259 ElementInsertionConflict {
260 /// The name of the parent element
261 parent: ElementName,
262 /// The name of the element that could not be inserted
263 element: ElementName,
264 /// path of the parent element
265 parent_path: String,
266 },
267
268 /// The `ElementName` is not a valid sub element according to the specification.
269 #[error("Element {} is not a valid sub element of {}", .element, .parent)]
270 InvalidSubElement {
271 /// The name of the parent element
272 parent: ElementName,
273 /// The name of the element that is not a valid sub element
274 element: ElementName,
275 },
276
277 /// Remove operation failed: the given element is not a sub element of the element from which it was supposed to be removed
278 #[error("element {} not found in parent {}", .target, .parent)]
279 ElementNotFound {
280 /// The name of the element that was not found
281 target: ElementName,
282 /// The name of the parent element
283 parent: ElementName,
284 },
285
286 /// [`Element::remove_sub_element`] cannot remove the SHORT-NAME of identifiable elements, as this would render the data invalid
287 #[error("the SHORT-NAME sub element may not be removed")]
288 ShortNameRemovalForbidden,
289
290 /// get/set reference target was called for an element that is not a reference
291 #[error("The current element is not a reference")]
292 NotReferenceElement,
293
294 /// The reference is invalid
295 #[error("The reference is not valid")]
296 InvalidReference,
297
298 /// An element could not be renamed, since this item name is already used by a different element
299 #[error("Duplicate item name {} in {}", .item_name, .element)]
300 DuplicateItemName {
301 /// The name of the element that could not be renamed
302 element: ElementName,
303 /// The target name that caused the error
304 item_name: String,
305 },
306
307 /// Cannot move an element into its own sub element
308 #[error("Cannot move an element into its own sub element")]
309 ForbiddenMoveToSubElement,
310
311 /// Cannot copy an element (or a hierarchy including the element) into itself
312 #[error("Cannot create a copy that includes the destination")]
313 ForbiddenCopyOfParent,
314
315 /// A parent element is currently locked by a different operation. The operation wa aborted to avoid a deadlock.
316 #[error("A parent element is currently locked by a different operation")]
317 ParentElementLocked,
318
319 /// The attribute is invalid here
320 #[error("The attribute is not valid for this element")]
321 InvalidAttribute,
322
323 /// The attribute value is invalid
324 #[error("The given value is not valid for this attribute")]
325 InvalidAttributeValue,
326
327 /// The file is from a different model and may not be used in this operation
328 #[error("The file is from a different model and may not be used in this operation")]
329 InvalidFile,
330
331 /// The file is empty and cannot be serialized
332 #[error("The file is empty and cannot be serialized")]
333 EmptyFile,
334
335 /// The newly loaded file diverges from the combined model on an element which is not splittable according to the metamodel
336 #[error("The new file could not be merged, because it diverges from the model on non-splittable element {}", .path)]
337 InvalidFileMerge {
338 /// The path of the element where the merge failed
339 path: String,
340 },
341
342 /// The operation cannot be completed because the model does not contain any files
343 #[error("The operation cannot be completed because the model does not contain any files")]
344 NoFilesInModel,
345
346 /// Modifying the fileset of this element is not allowed
347 #[error(
348 "Modifying the fileset of this element is not allowed, because the parent of the element is not marked as splittable"
349 )]
350 FilesetModificationForbidden,
351}
352
353/// An Autosar arxml file
354#[derive(Clone)]
355pub struct ArxmlFile(Arc<RwLock<ArxmlFileRaw>>);
356
357/// Weak reference to an arxml file
358///
359/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
360#[derive(Clone)]
361pub struct WeakArxmlFile(Weak<RwLock<ArxmlFileRaw>>);
362
363/// The data of an arxml file
364pub(crate) struct ArxmlFileRaw {
365 pub(crate) version: AutosarVersion,
366 model: WeakAutosarModel,
367 pub(crate) filename: PathBuf,
368 pub(crate) xml_standalone: Option<bool>, // preserve the xml standalone attribute
369}
370
371/// An arxml element
372///
373/// This is a wrapper type which provides all the necessary manipulation functions.
374#[derive(Clone)]
375pub struct Element(Arc<RwLock<ElementRaw>>);
376
377/// Weak reference to an Element
378///
379/// (see the documentation of [`std::sync::Arc`] for an explanation of weak references)
380///
381/// This `WeakElement` can be held indefinitely without forcing the referenced data to remain valid.
382/// When access is needed, the method `upgrade()` will attempt to get a strong reference and return an [Element]
383#[derive(Clone)]
384pub struct WeakElement(Weak<RwLock<ElementRaw>>);
385
386/// The data of an arxml element
387pub(crate) struct ElementRaw {
388 pub(crate) parent: ElementOrModel,
389 pub(crate) elemname: ElementName,
390 pub(crate) elemtype: ElementType,
391 pub(crate) content: SmallVec<[ElementContent; 4]>,
392 pub(crate) attributes: SmallVec<[Attribute; 1]>,
393 pub(crate) file_membership: HashSet<WeakArxmlFile>,
394 pub(crate) comment: Option<String>,
395}
396
397/// A single attribute of an arxml element
398#[derive(Clone, PartialEq, Eq)]
399pub struct Attribute {
400 /// The name of the attribute
401 pub attrname: AttributeName,
402 /// The content of the attribute
403 pub content: CharacterData,
404}
405
406/// One content item inside an arxml element
407///
408/// Elements may contain other elements, character data, or a mixture of both, depending on their type.
409#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
410pub enum ElementContent {
411 /// A sub element
412 Element(Element),
413 /// Character data
414 CharacterData(CharacterData),
415}
416
417/// The enum `CharacterData` provides typed access to the content of elements and attributes
418///
419/// Example:
420///
421/// In the xml string ```<SHORT-NAME>SomeName</SHORT-NAME>``` the character data
422/// "`SomeName`" will be loaded as `CharacterData::String("SomeName`"), while the content of the
423/// attribute <... DEST="UNIT"> will be loaded as `CharacterData::Enum(EnumItem::Unit`)
424#[derive(Debug, PartialEq, Clone)]
425pub enum CharacterData {
426 /// Character data is an enum value
427 Enum(EnumItem),
428 /// Character data is a string
429 String(String),
430 /// Character data is an unsigned integer
431 UnsignedInteger(u64),
432 /// Character data is a floating point number
433 Float(f64),
434}
435
436/// The content type of an [Element]
437#[derive(Debug, Eq, PartialEq, Clone, Copy)]
438pub enum ContentType {
439 /// The element only contains other elements
440 Elements,
441 /// The element only contains character data
442 CharacterData,
443 /// The element contains both character data and sub elements
444 Mixed,
445}
446
447/// Holds a weak reference to either an element or an arxml file
448///
449/// This enum is used for references to the parent of each element. For all elements other than the
450/// root element, the parent is an element. The root element itself has a reference to the `ArxmlFile` structure.
451#[derive(Clone)]
452pub(crate) enum ElementOrModel {
453 Element(WeakElement),
454 Model(WeakAutosarModel),
455 None, // needed while constructing the data trees, otherwise there's a chicken vs. egg problem
456}
457
458/// Possible kinds of compatibility errors that can be found by `ArxmlFile::check_version_compatibility()`
459#[derive(Debug, PartialEq, Clone)]
460pub enum CompatibilityError {
461 /// The element is not allowed in the target version
462 IncompatibleElement {
463 /// The element that is not allowed
464 element: Element,
465 /// The version mask of the element which indicates all allowed versions
466 version_mask: u32,
467 },
468 /// The attribute is not allowed in the target version
469 IncompatibleAttribute {
470 /// The element that contains the incompatible attribute
471 element: Element,
472 /// The incompatible attribute
473 attribute: AttributeName,
474 /// The version mask of the element which indicates all versions where the attribute is allowed
475 version_mask: u32,
476 },
477 /// The attribute value is not allowed in the target version
478 IncompatibleAttributeValue {
479 /// The element that contains the incompatible attribute
480 element: Element,
481 /// The incompatible attribute
482 attribute: AttributeName,
483 /// The incompatible attribute value
484 attribute_value: String,
485 /// The version mask of the element which indicates all versions where the attribute value is allowed
486 version_mask: u32,
487 },
488}
489
490/// information about a sub element
491///
492/// This structure is returned by [`Element::list_valid_sub_elements()`]
493#[derive(Debug, PartialEq, Clone)]
494pub struct ValidSubElementInfo {
495 /// name of the potential sub element
496 pub element_name: ElementName,
497 /// is the sub element named, i.e. does it need to be created with [`Element::create_named_sub_element()`]
498 pub is_named: bool,
499 /// is the sub element currently allowed, given the existing content of the element. Note that some sub elements are mutually exclusive.
500 pub is_allowed: bool,
501}
502
503const CHECK_FILE_SIZE: usize = 4096; // 4kb
504
505/// Check a file to see if it looks like an arxml file
506///
507/// Reads the beginning of the given file and checks if the data starts with a valid arxml header.
508/// If a header is found it immediately returns true and does not check any further data
509///
510/// The function returns false if the file cannot be read or if the data does not start with an arxml header
511///
512/// # Parameters
513/// - filename: name of the file to check
514///
515/// # Example
516///
517/// ```
518/// # let filename = "";
519/// if autosar_data::check_file(filename) {
520/// // it looks like an arxml file
521/// }
522/// ```
523pub fn check_file<P: AsRef<Path>>(filename: P) -> bool {
524 let mut buffer: [u8; CHECK_FILE_SIZE] = [0; CHECK_FILE_SIZE];
525
526 if File::open(filename).and_then(|mut file| file.read(&mut buffer)).is_ok() {
527 check_buffer(&buffer)
528 } else {
529 false
530 }
531}
532
533/// Check a buffer to see if the content looks like arxml data
534///
535/// The function returns true if the buffer starts with a valid arxml header (after skipping whitespace and comments).
536/// This function does not check anything after the header.
537///
538/// # Parameters
539/// - buffer: u8 slice containing the data to check
540///
541/// # Example
542/// ```
543/// # let buffer = Vec::new();
544/// if autosar_data::check_buffer(&buffer) {
545/// // it looks like arxml data
546/// }
547/// ```
548#[must_use]
549pub fn check_buffer(buffer: &[u8]) -> bool {
550 let mut parser = ArxmlParser::new(PathBuf::from("none"), buffer, false);
551 parser.check_arxml_header()
552}
553
554/// Custom Debug implementation for `Attribute`, in order to provide better formatting
555impl std::fmt::Debug for Attribute {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 write!(f, "Attribute: {:?} = {:?}", self.attrname, self.content)
558 }
559}
560
561/// provide PartialOrd for attributes; this is used while sorting elements
562impl PartialOrd for Attribute {
563 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
564 Some(self.cmp(other))
565 }
566}
567
568/// provide Ord for attributes; this is used while sorting elements
569impl Ord for Attribute {
570 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
571 self.attrname
572 .to_str()
573 .cmp(other.attrname.to_str())
574 .then(self.content.cmp(&other.content))
575 }
576}
577
578#[cfg(test)]
579mod test {
580 use std::{error::Error, io::Write, path::PathBuf};
581 use tempfile::tempdir;
582
583 use crate::*;
584
585 #[test]
586 fn error_traits() {
587 let err = AutosarDataError::ParserError {
588 filename: PathBuf::from("filename.arxml"),
589 line: 123,
590 source: crate::parser::ArxmlParserError::InvalidArxmlFileHeader,
591 };
592 assert!(err.source().is_some());
593 let errstr = format!("{err}");
594 let errdbg = format!("{err:#?}");
595 assert!(errstr != errdbg);
596 }
597
598 #[test]
599 fn test_check_file() {
600 let dir = tempdir().unwrap();
601
602 // called on a directory rather than a file -> false
603 assert!(!check_file(dir.path()));
604
605 // nonexistent file -> false
606 let nonexistent = dir.path().with_file_name("nonexistent.arxml");
607 assert!(!check_file(nonexistent));
608
609 // arbitrary non-arxml data -> false
610 let not_arxml_file = dir.path().with_file_name("not_arxml.bin");
611 File::create(¬_arxml_file)
612 .and_then(|mut file| write!(file, "text"))
613 .unwrap();
614 assert!(!check_file(not_arxml_file));
615
616 // file containing a valid arxml header -> true
617 let header = r#"<?xml version="1.0" encoding="utf-8"?>
618 <!-- comment --><!-- comment 2 -->
619 <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">"#;
620 let arxml_file = dir.path().with_file_name("file.arxml");
621 File::create(&arxml_file)
622 .and_then(|mut file| file.write(header.as_bytes()))
623 .unwrap();
624 assert!(check_file(arxml_file));
625
626 assert!(check_buffer(header.as_bytes()));
627 }
628
629 #[test]
630 fn attribute_order() {
631 // attribute ordering: first by name, then by value
632 let a1 = Attribute {
633 attrname: AttributeName::Uuid,
634 content: CharacterData::String("Value1".to_string()),
635 };
636
637 let a2 = Attribute {
638 attrname: AttributeName::Uuid,
639 content: CharacterData::String("Value2".to_string()),
640 };
641 assert!(a1 < a2);
642
643 let a3 = Attribute {
644 attrname: AttributeName::T,
645 content: CharacterData::String("xyz".to_string()),
646 };
647 assert!(a3 < a1);
648
649 // PartialOrd
650 assert!(a1.partial_cmp(&a2) == Some(std::cmp::Ordering::Less));
651 }
652}