diff --git a/Cargo.lock b/Cargo.lock index 1022dabe..3d080e82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,6 +281,31 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "autosar-data" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "966f7a3f9eed49783f5ace2fd90aa81dc8328af2a7f6b14e1b93bbcddc16f48f" +dependencies = [ + "autosar-data-specification", + "fxhash", + "indexmap 2.6.0", + "num-traits", + "parking_lot 0.12.3", + "smallvec", + "thiserror", +] + +[[package]] +name = "autosar-data-specification" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29aab7eb036d0fdaa696ea39ba87c68c913567e90ef86b58f7acc94fb1966d60" +dependencies = [ + "num-derive", + "num-traits", +] + [[package]] name = "autotools" version = "0.2.7" @@ -3451,6 +3476,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -3759,6 +3795,7 @@ dependencies = [ "opendut-edgar-kernel-modules", "opendut-edgar-plugin-api", "opendut-netbird-client-api", + "opendut-restbus-simulation", "opendut-types", "opendut-util", "opentelemetry", @@ -3877,6 +3914,20 @@ dependencies = [ "url", ] +[[package]] +name = "opendut-restbus-simulation" +version = "0.1.0" +dependencies = [ + "anyhow", + "autosar-data", + "nix 0.29.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "opendut-theo" version = "0.3.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 69e3df20..acab7062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "opendut-edgar/netbird-client-api", "opendut-edgar/opendut-edgar-kernel-modules", "opendut-edgar/plugin-api", + "opendut-edgar/restbus-simulation", "opendut-lea", "opendut-types", "opendut-util", @@ -39,6 +40,7 @@ opendut-edgar-plugin-api = { path = "opendut-edgar/plugin-api" } opendut-netbird-client-api = { path = "opendut-edgar/netbird-client-api" } opendut-edgar-kernel-modules = { path = "opendut-edgar/opendut-edgar-kernel-modules" } opendut-vpn-netbird = { path = "opendut-vpn/opendut-vpn-netbird" } +opendut-restbus-simulation = { path = "opendut-edgar/restbus-simulation" } opendut-types = { path = "opendut-types" } opendut-theo = { path = ".ci/docker/theo" } opendut-util = { path = "opendut-util" } @@ -48,6 +50,7 @@ opendut-vpn = { path = "opendut-vpn" } anyhow = "1.0.79" assert_fs = "1.1.1" async-trait = "0.1.77" +autosar-data = "0.14.0" axum = "0.6.20" axum-server = "0.5.1" axum-server-dual-protocol = "0.5.2" diff --git a/doc/src/architecture/restbus-simulation/index.md b/doc/src/architecture/restbus-simulation/index.md new file mode 100644 index 00000000..3b992450 --- /dev/null +++ b/doc/src/architecture/restbus-simulation/index.md @@ -0,0 +1,85 @@ +# Restbus Simulation + +### Summary +This module provides a restbus-simulation to the OpenDuT user and it is split into an **_ARXML parser (AXP)_** +sub-module and a **_restbus-simulation (RSIM)_** sub-module. The AXP parses an ARXML file and the parsed data +can be used by the RSIM to establish a working simulation that knows about all the Frames/PDUs/Signals and +handles the simulation of them. + +Live changes of signals/timings/... shall be implemented by the end-user, which can use a simple API of the RSIM to +define changes in an abstract way. The goal is not to achieve the same functionality as the +well-known restbus-simulation tool. Instead, the OpenDuT user should have an easy possibility +to simulate a base environment that improves testing. + +Current implementation state: + - AXP -> Done + - RSIM -> Base implementation done and can be used already. Have to extend it + to handle all types of PDUs and do some more modifications. + - RSIM API -> Todo + +### ARXML Parser (AXP) sub-module +This module parses an ARXML (Autosar XML) file and extracts all values necessary for a restbus-simulation. +First, the [autosar-data crate](https://crates.io/crates/autosar-data/0.9.0) is used for parsing an ARXML file. +Then, important data is extracted from the parsed data and some post-processing is made. The resulting +data is stored in structures, which basically represent different +[Autosar Elements](https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_TPS_SystemTemplate.pdf). + +Parsing and post-processing a big ARXML file can take a long time. For example, for a ~300 MB ARXML file, we need +around 40 seconds on a standard laptop. Therefore, the parser can be instructed to serialize the resulting structures and store them into +a file. This enables a +very quick re-establishment of everything, since we do not need to parse and post-process data for a second time. +Instead, the next time we run the program, we just can deserialize the data, which takes less than one second. + +The resulting structures can be modified before passing them to the RSIM. There is no direct API for creation/modification of +structures implemented yet, but manually modifying the structures by making use of AXP helper methods is easily possible. +If a use-case for structure modification exists, then a later API might be implemented, which +should not take a lot of time. However, currently, the idea is that everything is already properly defined through the +ARXML file. + +### Restbus-Simulation (RSIM) sub-module +The RSIM can be fed with the structures coming from the AXP. With these structures, the RSIM exactly knows how +Frames/PDUs/Signals/Timings/Initial Values, ... look like. It handles all the lower-level things and controls what is and how it is send +to the Bus. The user always has just an abstract overview of everything. See the **Configuration** section for learning about the configuration of everything. + +The RSIM makes use of the [Linux SocketCAN Broadcast Manager (BCM)](https://www.kernel.org/doc/Documentation/networking/can.txt), +which handles all the timing of (optionally) periodically sent messages. The BCM is setup and modified +via **BCM sockets**, in which we can define message bytes and their timing information. The Kernel handles then +the correct message transmission + timing. Furthermore, the BCM will also be used +to dynamically modify messages and their timing during runtime. + +The user itself has to provide the status changing logic. +With a simple API (see next section), the user can instruct the RSIM to modify the data that is sent to the bus. +The user has then control over single signals, timings, and more, by using the API. **For example**, we have a periodically sent message definining +that the car's doors are locked. The RSIM completely handles the periodic sending with the right timing etc. +The user can then tell the RSIM that the status +has changed by instructing the RSIM to change the signal (lock status) of that particular message or all messages +referencing that signal. This will be possible +with a simple API call like "Change _Signal CarLock_ to 0 (false)". As a result, the message/s referencing the signal +will be adapted automatically by the RSIM and the user does not need to know about any low-level implementation. +The details to the API will follow and will be +defined in the **RSIM API** section. +Right now no API exists, and the RSIM just plays Frames with initial values to the Bus. + +### AXP + RSIM API and Integration +*_Idea to discuss:_* +- Implement MQTT client (MC) that controls AXP and RSIM +- MC builds basically the API. Every instance that can communicate with the MQTT server, also can communicate with our client. +- MC gets started in a separate thread by main Edgar on startup (if enabled) +- MC listens to instructions by polling pre-existing MQTT server and sends results/responses to MQTT server +- AXP can be instructed via API (i.e. via MC) to parse files that are located on EDGAR + - ARXML file is assumed to be located on Edgar indepedently by transfer from CARL to EDGAR or previous manual transfer + - AXP can be instructed separately from RSIM or a setup can be combined, i.e., a single command leads to parsing of ARXML file and restbus-simulation setup +- RSIM API provides control of RSIM by providing a simple API that can be used via MQTT (MC polls from server for commands) + - Even with the simple API, a very fine-grained control of RSIM is possible, since every important feature should be available. + - openDuT user provides updates of signals via API, while RSIM handles all the low-level details and ensures correctness of transmitted messages and their timings + - It's the user's task to provide the logic for dynamic changes of signals/timings/... during runtime + - Either the user logic is integrated via separate binaries/Python files/..., or we might be able to use (compiled) CAPL files, that already contain the logic for existing restbus simulations + +### Configuration +The restbus-simulation can be enabled and configured via the [edgar.toml](https://github.com/eclipse-opendut/opendut/blob/restbus-simulation/opendut-edgar/edgar.toml) file. It is disabled by default. When enabling it, then it runs as long as the main edgar is running. It automatically gets stopped when the main edgar is stopped. + +### References: + - [autosar-data crate](https://crates.io/crates/autosar-data/0.9.0) + - [Autosar System Template](https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_TPS_SystemTemplate.pdf) + - [Linux SocketCAN Broadcast Manager (BCM)](https://www.kernel.org/doc/Documentation/networking/can.txt) + diff --git a/opendut-edgar/Cargo.toml b/opendut-edgar/Cargo.toml index 8c283bb5..82a33121 100644 --- a/opendut-edgar/Cargo.toml +++ b/opendut-edgar/Cargo.toml @@ -10,6 +10,7 @@ opendut-auth = { workspace = true, features = ["registration_client"] } opendut-carl-api = { workspace = true, features = ["client"] } opendut-edgar-kernel-modules = { workspace = true } opendut-netbird-client-api = { workspace = true } +opendut-restbus-simulation = { workspace = true } opendut-types = { workspace = true } opendut-util = { workspace = true } diff --git a/opendut-edgar/edgar.toml b/opendut-edgar/edgar.toml index 909caaac..e3188e5a 100644 --- a/opendut-edgar/edgar.toml +++ b/opendut-edgar/edgar.toml @@ -49,3 +49,10 @@ cpu.collection.interval.ms = 5000 ping.interval.ms = 30000 target.bandwidth.kilobit.per.second = 100_000 rperf.backoff.max.elapsed.time.ms = 120000 + +[restbus-simulation] +enabled = false +arxml.path = "./opendut-edgar/restbus-simulation/src/system-4.2.arxml" +arxml.serialization = true +simulation.target.cluster = "Cluster0" +simulation.interface = "vcan0" diff --git a/opendut-edgar/restbus-simulation/Cargo.toml b/opendut-edgar/restbus-simulation/Cargo.toml new file mode 100755 index 00000000..809cbaed --- /dev/null +++ b/opendut-edgar/restbus-simulation/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "opendut-restbus-simulation" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +autosar-data = { workspace = true } +nix = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/opendut-edgar/restbus-simulation/arxml_parser.rs b/opendut-edgar/restbus-simulation/arxml_parser.rs deleted file mode 100755 index c830d7f1..00000000 --- a/opendut-edgar/restbus-simulation/arxml_parser.rs +++ /dev/null @@ -1,489 +0,0 @@ -use core::panic; -use std::time::Instant; -use std::collections::HashMap; - -use autosar_data::{AutosarModel, CharacterData, Element, ElementName, EnumItem}; - -use crate::arxml_structs::*; -use crate::arxml_utils::*; - -/* -- Arxml parser that is able to extract all values necessary for a restbus simulation -- See main method for usage example. -*/ - -/* -- TODO: - - finish parsing and fill up structures - - What about TPConfig and get_init_value_from_signals and get_init_value_from_signals? - - create restbus simulation based on parsed data in a different source code file - - include signal desc - -- Improvements at some stage: - - Provide options to store parsed data for quicker restart - - Put structure defintions in separete source code file - - be able to manually add stuff to restbus -> provide interface - -- Code inside DEBUG comments will be removed at a later stage -*/ - - -// Future restbus simulation structure used to setup and control restbus simulation. Will be moved to seprarate source code file. -/*pub struct RestbusSimulation { - -}*/ - -// Parser structure -pub struct ArxmlParser { -} - -// Use autosar-data library to parse data like in this example: -// https://github.com/DanielT/autosar-data/blob/main/autosar-data/examples/businfo/main.rs -// Do I have to add license to this file or is project license enough? -impl ArxmlParser { - fn handle_isignal_to_pdu_mappings(&self, mapping: &Element, - signals: &mut HashMap, - signal_groups: &mut Vec) - { - if let Some(signal) = mapping - .get_sub_element(ElementName::ISignalRef) - .and_then(|elem| elem.get_reference_target().ok()) - { - let refpath = get_required_string(&mapping, - ElementName::ISignalRef); - - let name = get_required_item_name(&signal, "ISignalRef"); - - let byte_order = get_required_string(&mapping, ElementName::PackingByteOrder); - - let start_pos = get_required_int_value(&mapping, - ElementName::StartPosition); - - let length = get_required_int_value(&signal, - ElementName::Length); - - let mut init_values: InitValues = InitValues::NotExist(true); - - if let Some(init_value_elem) = signal.get_sub_element(ElementName::InitValue) { - process_init_value(&init_value_elem, &mut init_values, &name); - } - signals.insert(refpath, (name, byte_order, start_pos, length, init_values)); - } else if let Some(signal_group) = mapping - .get_sub_element(ElementName::ISignalGroupRef) - .and_then(|elem| elem.get_reference_target().ok()) - { - // store the signal group for now - signal_groups.push(signal_group); - } - } - - fn handle_isignals(&self, pdu: &Element, grouped_signals: &mut Vec, ungrouped_signals: &mut Vec) -> Option<()> { - //let mut signals: HashMap, Option)> = HashMap::new(); - let mut signals: HashMap = HashMap::new(); - let mut signal_groups = Vec::new(); - - - if let Some(isignal_to_pdu_mappings) = pdu.get_sub_element(ElementName::ISignalToPduMappings) { - // collect information about the signals and signal groups - for mapping in isignal_to_pdu_mappings.sub_elements() { - self.handle_isignal_to_pdu_mappings(&mapping, &mut signals, &mut signal_groups); - } - } - - for signal_group in &signal_groups { - process_signal_group(signal_group, &mut signals, grouped_signals); - } - - let remaining_signals: Vec<(String, String, i64, i64, InitValues)> = signals.values().cloned().collect(); - if remaining_signals.len() > 0 { - for (name, byte_order, start_pos, length, init_values) in remaining_signals { - let isignal_struct: ISignal = ISignal { - name: name, - byte_order: get_byte_order(&byte_order), - start_pos: start_pos, - length: length, - init_values: init_values - }; - ungrouped_signals.push(isignal_struct); - } - } - - ungrouped_signals.sort_by(|a, b| a.start_pos.cmp(&b.start_pos)); - - Some(()) - } - - fn handle_isignal_ipdu(&self, pdu: &Element) -> Option { - // Find out these values: ... - let mut cyclic_timing_period_value: f64 = 0_f64; - let mut cyclic_timing_period_tolerance: Option = None; - - let mut cyclic_timing_offset_value: f64 = 0_f64; - let mut cyclic_timing_offset_tolerance: Option = None; - - let mut number_of_repetitions: i64 = 0; - let mut repetition_period_value: f64 = 0_f64; - let mut repetition_period_tolerance: Option = None; - - if let Some(tx_mode_true_timing) = pdu - .get_sub_element(ElementName::IPduTimingSpecifications) - .and_then(|elem| elem.get_sub_element(ElementName::IPduTiming)) - .and_then(|elem| elem.get_sub_element(ElementName::TransmissionModeDeclaration)) - .and_then(|elem| elem.get_sub_element(ElementName::TransmissionModeTrueTiming)) - { - if let Some(cyclic_timing) = tx_mode_true_timing - .get_sub_element(ElementName::CyclicTiming) - { - get_sub_element_and_time_range(&cyclic_timing, ElementName::TimePeriod, &mut cyclic_timing_period_value, &mut cyclic_timing_period_tolerance); - - get_sub_element_and_time_range(&cyclic_timing, ElementName::TimeOffset, &mut cyclic_timing_offset_value, &mut cyclic_timing_offset_tolerance); - } - if let Some(event_timing) = tx_mode_true_timing - .get_sub_element(ElementName::EventControlledTiming) - { - number_of_repetitions = get_optional_int_value(&event_timing, - ElementName::NumberOfRepetitions); - - get_sub_element_and_time_range(&event_timing, ElementName::RepetitionPeriod, &mut repetition_period_value, &mut repetition_period_tolerance); - } - } - - let unused_bit_pattern = get_unused_bit_pattern(&pdu); - - let mut grouped_signals: Vec = Vec::new(); - - let mut ungrouped_signals: Vec = Vec::new(); - - self.handle_isignals(pdu, &mut grouped_signals, &mut ungrouped_signals); - - let isginal_ipdu: ISignalIPDU = ISignalIPDU { - cyclic_timing_period_value: cyclic_timing_period_value, - cyclic_timing_period_tolerance: cyclic_timing_period_tolerance, - cyclic_timing_offset_value: cyclic_timing_offset_value, - cyclic_timing_offset_tolerance: cyclic_timing_offset_tolerance, - number_of_repetitions: number_of_repetitions, - repetition_period_value: repetition_period_value, - repetition_period_tolerance: repetition_period_tolerance, - unused_bit_pattern: unused_bit_pattern, - ungrouped_signals: ungrouped_signals, - grouped_signals: grouped_signals - }; - - return Some(isginal_ipdu); - } - - fn handle_nm_pdu(&self, pdu: &Element) -> Option { - let unused_bit_pattern = get_unused_bit_pattern(&pdu); - - let mut grouped_signals: Vec = Vec::new(); - - let mut ungrouped_signals: Vec = Vec::new(); - - self.handle_isignals(pdu, &mut grouped_signals, &mut ungrouped_signals); - - let nm_pdu: NMPDU = NMPDU { - unused_bit_pattern: unused_bit_pattern, - ungrouped_signals: ungrouped_signals, - grouped_signals: grouped_signals - }; - - return Some(nm_pdu); - } - - /*// Add support in future in case it is needed - fn handle_container_ipdu(&self, pdu: &Element){ - let mut container_timeout: f64 = 0.0; - - let header_type = self.get_optional_string(pdu, ElementName::HeaderType); - - if let Some(container_timeout_tmp) = pdu - .get_sub_element(ElementName::ContainerTimeout) - .and_then(|elem| elem.character_data()) - .and_then(|cdata| cdata.double_value()) - { - container_timeout = container_timeout_tmp; - } - - let container_trigger = self.get_optional_string(pdu, ElementName::ContainerTrigger); - - if let Some(contained_pdu_refs) = pdu.get_sub_element(ElementName::ContainedPduTriggeringRefs) { - for contained_ref in contained_pdu_refs.sub_elements() { - if let Some(contained_pdu) = contained_ref - .get_reference_target() - .ok() - .and_then(|elem| elem.get_sub_element(ElementName::IPduRef)) - .and_then(|elem| elem.get_reference_target().ok()) - { - let pdu_name = self.get_required_item_name(&contained_pdu, "ContainedPDU"); - display_pdu(&contained_pdu, indent + 1); - } - } - } - //... - }*/ - - /*// Add support in future in case it is needed - fn handle_secured_ipdu(&self, pdu: &Element){ - - }*/ - - fn handle_pdu_mapping(&self, pdu_mapping: &Element) -> Result { - let pdu = get_required_reference( - pdu_mapping, - ElementName::PduRef); - - let pdu_name = get_required_item_name( - &pdu, "Pdu"); - - let byte_order = get_required_string(pdu_mapping, - ElementName::PackingByteOrder); - - let start_position = get_required_int_value(pdu_mapping, - ElementName::StartPosition); - - let pdu_length = get_required_int_value(&pdu, - ElementName::Length); - - let pdu_dynamic_length = get_optional_string(&pdu, - ElementName::HasDynamicLength); - - let pdu_category = get_optional_string(&pdu, - ElementName::Category); - - let pdu_contained_header_id_short = get_subelement_optional_string(&pdu, - ElementName::ContainedIPduProps, ElementName::HeaderIdShortHeader); - - let pdu_contained_header_id_long = get_subelement_optional_string(&pdu, - ElementName::ContainedIPduProps, ElementName::HeaderIdLongHeader); - - //let mut pdu_specific: PDU = PDU::Temp(0); - let pdu_specific: PDU; - - match pdu.element_name() { - ElementName::ISignalIPdu => { - if let Some(value) = self.handle_isignal_ipdu(&pdu) { - pdu_specific = PDU::ISignalIPDU(value); - } else { - panic!("Error in handle_isignal_ipdu"); - } - } - ElementName::NmPdu => { - if let Some(value) = self.handle_nm_pdu(&pdu) { - pdu_specific = PDU::NMPDU(value); - } else { - panic!("Error in handle_nm_pdu"); - } - } - /*ElementName::ContainerIPdu => { // Add support in future if needed - panic!("endounter containerpdu"); - //self.handle_container_ipdu(&pdu); - }*/ - /*ElementName::SecuredIPdu => { // Add support in future if needed - self.handle_secured_ipdu(&pdu); - }*/ - // Handle more? - _ => { - let error = format!("PDU type {} not supported. Will skip it.", pdu.element_name().to_string()); - return Err(error) - } - } - - let pdu_mapping: PDUMapping = PDUMapping { - name: pdu_name, - byte_order: get_byte_order(&byte_order), - start_position: start_position, - length: pdu_length, - dynamic_length: pdu_dynamic_length, - category: pdu_category, - contained_header_id_short: pdu_contained_header_id_short, - contained_header_id_long: pdu_contained_header_id_long, - pdu: pdu_specific - }; - - return Ok(pdu_mapping); - } - - fn handle_can_frame_triggering(&self, can_frame_triggering: &Element) -> Result { - let can_frame_triggering_name= get_required_item_name( - can_frame_triggering, "CanFrameTriggering"); - - let can_id = get_required_int_value( - &can_frame_triggering, - ElementName::Identifier); - - let frame = get_required_reference( - can_frame_triggering, - ElementName::FrameRef); - - let frame_name = get_required_item_name( - &frame, "Frame"); - - let addressing_mode = if let Some(CharacterData::Enum(value)) = can_frame_triggering - .get_sub_element(ElementName::CanAddressingMode) - .and_then(|elem| elem.character_data()) - { - value.to_string() - } else { - EnumItem::Standard.to_string() - }; - - let frame_rx_behavior = get_optional_string( - can_frame_triggering, - ElementName::CanFrameRxBehavior); - - let frame_tx_behavior = get_optional_string( - can_frame_triggering, - ElementName::CanFrameTxBehavior); - - let mut rx_range_lower: i64 = 0; - let mut rx_range_upper: i64 = 0; - if let Some(range_elem) = can_frame_triggering.get_sub_element(ElementName::RxIdentifierRange) { - rx_range_lower = get_required_int_value(&range_elem, ElementName::LowerCanId); - rx_range_upper = get_required_int_value(&range_elem, ElementName::UpperCanId); - } - - let mut rx_ecus: Vec = Vec::new(); - let mut tx_ecus: Vec = Vec::new(); - - match process_frame_ports(can_frame_triggering, &can_frame_triggering_name, &mut rx_ecus, &mut tx_ecus) { - Err(err) => return Err(err), - _ => {} - } - - let frame_length = get_optional_int_value( - &frame, - ElementName::FrameLength); - - let mut pdu_mappings_vec: Vec = Vec::new(); - - // assign here and other similar variable? - if let Some(mappings) = frame.get_sub_element(ElementName::PduToFrameMappings) { - for pdu_mapping in mappings.sub_elements() { - match self.handle_pdu_mapping(&pdu_mapping) { - Ok(value) => pdu_mappings_vec.push(value), - Err(error) => return Err(error) - } - } - } - - let can_frame_triggering_struct: CanFrameTriggering = CanFrameTriggering { - frame_triggering_name: can_frame_triggering_name, - frame_name: frame_name, - can_id: can_id, - addressing_mode: addressing_mode, - frame_rx_behavior: frame_rx_behavior, - frame_tx_behavior: frame_tx_behavior, - rx_range_lower: rx_range_lower, - rx_range_upper: rx_range_upper, - receiver_ecus: rx_ecus, - sender_ecus: tx_ecus, - frame_length: frame_length, - pdu_mappings: pdu_mappings_vec - }; - - return Ok(can_frame_triggering_struct); - } - - fn handle_can_cluster(&self, can_cluster: &Element) -> Result { - let can_cluster_name = get_required_item_name( - can_cluster, "CanCluster"); - - let can_cluster_conditional = get_required_sub_subelement( - can_cluster, - ElementName::CanClusterVariants, - ElementName::CanClusterConditional); - - //let can_cluster_baudrate = self.get_required_subelement_int_value( - let can_cluster_baudrate = get_optional_int_value( - &can_cluster_conditional, - ElementName::Baudrate); - - let can_cluster_fd_baudrate = get_optional_int_value( - &can_cluster_conditional, - ElementName::CanFdBaudrate); - - if can_cluster_baudrate == 0 && can_cluster_fd_baudrate == 0 { - let msg = format!("Baudrate and FD Baudrate of CanCluster {} do not exist or are 0. Skipping this CanCluster.", can_cluster_name); - return Err(msg.to_string()); - } - - // iterate over PhysicalChannels and handle the CanFrameTriggerings inside them - let physical_channels; - if let Some(value) = can_cluster_conditional - .get_sub_element(ElementName::PhysicalChannels).map(|elem| { - elem.sub_elements().filter(|se| se.element_name() == ElementName::CanPhysicalChannel) - }) - { - physical_channels = value; - } else { - let msg = format!("Cannot handle physical channels of CanCluster {}", can_cluster_name); - return Err(msg.to_string()); - } - - let mut can_frame_triggerings: HashMap = HashMap::new(); - for physical_channel in physical_channels { - if let Some(frame_triggerings) = physical_channel.get_sub_element(ElementName::FrameTriggerings) { - for can_frame_triggering in frame_triggerings.sub_elements() { - match self.handle_can_frame_triggering(&can_frame_triggering) { - Ok(value) => { - can_frame_triggerings.insert(value.can_id.clone(), value); - } - Err(error) => println!("[-] WARNING: {}", error), - } - } - } - } - - let can_cluster_struct: CanCluster = CanCluster { - name: can_cluster_name, - baudrate: can_cluster_baudrate, - canfd_baudrate: can_cluster_fd_baudrate, - can_frame_triggerings: can_frame_triggerings - }; - - return Ok(can_cluster_struct); - } - - // Main parsing method. Uses autosar-data libray for parsing ARXML - // In the future, it might be extended to support Etherneth, Flexray, ... - // Returns now a vector of CanCluster - pub fn parse_file(&self, file_name: String) -> Option> { - let start = Instant::now(); - - let model = AutosarModel::new(); - - if let Err(err) = model.load_file(file_name, false) { - panic!("Parsing failed. Error: {}", err.to_string()); - } - - // DEBUG - println!("[+] Duration of loading was: {:?}", start.elapsed()); - // DEBUG END - - let mut can_clusters: HashMap = HashMap::new(); - - // Iterate over Autosar elements and handle CanCluster elements - for element in model - .identifiable_elements() - .iter() - .filter_map(|path| model.get_element_by_path(&path)) - { - match element.element_name() { - ElementName::CanCluster => { - let result: Result = self.handle_can_cluster(&element); - match result { - Ok(value) => { - can_clusters.insert(value.name.clone(), value); - } - Err(error) => println!("[-] WARNING: {}", error) - } - } - _ => {} - } - } - - println!("[+] Duration of parsing: {:?}", start.elapsed()); - - return Some(can_clusters); - } -} diff --git a/opendut-edgar/restbus-simulation/arxml_structs.rs b/opendut-edgar/restbus-simulation/arxml_structs.rs deleted file mode 100755 index 7c69a25c..00000000 --- a/opendut-edgar/restbus-simulation/arxml_structs.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug)] -pub struct CanCluster { - pub name: String, - pub baudrate: i64, - pub canfd_baudrate: i64, - pub can_frame_triggerings: HashMap -} - -#[derive(Debug)] -pub struct CanFrameTriggering { - pub frame_triggering_name: String, - pub frame_name: String, - pub can_id: i64, - pub addressing_mode: String, - pub frame_rx_behavior: String, - pub frame_tx_behavior: String, - pub rx_range_lower: i64, - pub rx_range_upper: i64, - pub sender_ecus: Vec, - pub receiver_ecus: Vec, - pub frame_length: i64, - pub pdu_mappings: Vec -} - -#[derive(Debug)] -pub struct PDUMapping { - pub name: String, - pub byte_order: bool, - pub start_position: i64, - pub length: i64, - pub dynamic_length: String, - pub category: String, - pub contained_header_id_short: String, - pub contained_header_id_long: String, - pub pdu: PDU -} - -#[derive(Debug)] -pub enum PDU { - ISignalIPDU(ISignalIPDU), - NMPDU(NMPDU), -// DCMIPDU(DCMIPDU), -// NMPDU(NMPDU), -// ContaineredPDU(XY), -// Temp(i64) -} - -/*pub struct DCMIPDU { // Seems to be only DoIP relevant - diag_pdu_type: String -}*/ - -/*pub struct NMPDU { // Seems to be only needed for Ethernet, not CAN - nm_signal: String, - start_pos: i64, - length: i64 -}*/ - -#[derive(Debug)] -pub struct ISignalIPDU { - pub cyclic_timing_period_value: f64, - pub cyclic_timing_period_tolerance: Option, - pub cyclic_timing_offset_value: f64, - pub cyclic_timing_offset_tolerance: Option, - pub number_of_repetitions: i64, - pub repetition_period_value: f64, - pub repetition_period_tolerance: Option, - pub unused_bit_pattern: bool, - pub ungrouped_signals: Vec, - pub grouped_signals: Vec, -} - -#[derive(Debug)] -pub struct NMPDU { - pub unused_bit_pattern: bool, - pub ungrouped_signals: Vec, - pub grouped_signals: Vec, -} - -#[derive(Debug)] -pub struct ISignal { - pub name: String, - pub byte_order: bool, - pub start_pos: i64, - pub length: i64, - pub init_values: InitValues -} - -#[derive(Debug)] -#[derive(Clone)] -pub enum InitValues { - Single(i64), - Array(Vec), - NotExist(bool), -} - -#[derive(Debug)] -pub struct E2EDataTransformationProps { - pub transformer_name: String, - pub data_id: i64, - pub data_length: i64 -} - -#[derive(Debug)] -pub struct ISignalGroup { - pub name: String, - pub isignals: Vec, - pub data_transformations: Vec, - pub transformation_props: Vec -} - -#[derive(Debug)] -pub enum TimeRangeTolerance { - Relative(i64), - Absolute(f64), -} - -#[derive(Debug)] -pub struct TimeRange { - pub tolerance: Option, - pub value: f64, -} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/arxml_utils.rs b/opendut-edgar/restbus-simulation/arxml_utils.rs deleted file mode 100755 index 6c7883be..00000000 --- a/opendut-edgar/restbus-simulation/arxml_utils.rs +++ /dev/null @@ -1,480 +0,0 @@ -/* - HELPER METHODS -*/ -use autosar_data::{CharacterData, Element, ElementName, EnumItem}; - -use std::collections::HashMap; - -use crate::arxml_structs::*; - -pub fn decode_integer(cdata: &CharacterData) -> Option { - if let CharacterData::String(text) = cdata { - if text == "0" { - Some(0) - } else if text.starts_with("0x") { - let hexstr = text.strip_prefix("0x").unwrap(); - Some(i64::from_str_radix(hexstr, 16).ok()?) - } else if text.starts_with("0X") { - let hexstr = text.strip_prefix("0X").unwrap(); - Some(i64::from_str_radix(hexstr, 16).ok()?) - } else if text.starts_with("0b") { - let binstr = text.strip_prefix("0b").unwrap(); - Some(i64::from_str_radix(binstr, 2).ok()?) - } else if text.starts_with("0B") { - let binstr = text.strip_prefix("0B").unwrap(); - Some(i64::from_str_radix(binstr, 2).ok()?) - } else if text.starts_with('0') { - let octstr = text.strip_prefix('0').unwrap(); - Some(i64::from_str_radix(octstr, 8).ok()?) - } else { - Some(text.parse().ok()?) - } - } else { - None - } -} - -pub fn get_time_range(base: &Element) -> Option { - let value = base - .get_sub_element(ElementName::Value) - .and_then(|elem| elem.character_data()) - .and_then(|cdata| cdata.double_value())?; - - let tolerance = if let Some(absolute_tolerance) = base - .get_sub_element(ElementName::AbsoluteTolerance) - .and_then(|elem| elem.get_sub_element(ElementName::Absolute)) - .and_then(|elem| elem.character_data()) - .and_then(|cdata| cdata.double_value()) - { - Some(TimeRangeTolerance::Absolute(absolute_tolerance)) - } else { - base.get_sub_element(ElementName::RelativeTolerance) - .and_then(|elem| elem.get_sub_element(ElementName::Relative)) - .and_then(|elem| elem.character_data()) - .and_then(|cdata| decode_integer(&cdata)) - .map(TimeRangeTolerance::Relative) - }; - - Some(TimeRange { tolerance, value }) -} - -pub fn get_sub_element_and_time_range(base: &Element, sub_elem_name: ElementName, value: &mut f64, tolerance: &mut Option) { - if let Some(time_range) = base - .get_sub_element(sub_elem_name) - .and_then(|elem| get_time_range(&elem)) - { - *value = time_range.value; - *tolerance = time_range.tolerance; - } -} - -pub fn get_required_item_name(element: &Element, element_name: &str) -> String { - if let Some(item_name) = element.item_name() { - return item_name; - } else { - panic!("Error getting required item name of {}", element_name); - } -} - -pub fn get_required_sub_subelement(element: &Element, subelement_name: ElementName, sub_subelement_name: ElementName) -> Element { - if let Some(sub_subelement) = element - .get_sub_element(subelement_name) - .and_then(|elem| elem.get_sub_element(sub_subelement_name)) - { - return sub_subelement; - } else { - panic!("Error getting sub_subelement. Tried to retrieve {} and then {}", - subelement_name, - sub_subelement_name); - } -} - -pub fn get_subelement_int_value(element: &Element, subelement_name: ElementName) -> Option { - return element - .get_sub_element(subelement_name) - .and_then(|elem| elem.character_data()) - .and_then(|cdata| decode_integer(&cdata)); -} - -pub fn get_required_int_value(element: &Element, subelement_name: ElementName) -> i64 { - if let Some(int_value) = get_subelement_int_value(element, subelement_name) { - return int_value; - } else { - panic!("Error getting required integer value of {}", subelement_name); - } -} - -pub fn get_optional_int_value(element: &Element, subelement_name: ElementName) -> i64 { - if let Some(int_value) = get_subelement_int_value(element, subelement_name) { - return int_value; - } else { - return 0; - } -} - -pub fn get_required_reference(element: &Element, subelement_name: ElementName) -> Element { - if let Some(subelement) = element.get_sub_element(subelement_name) { - match subelement.get_reference_target() { - Ok(reference) => return reference, - Err(_) => {} - } - } - - panic!("Error getting required reference for {}", subelement_name); -} - -pub fn get_subelement_string_value(element: &Element, subelement_name: ElementName) -> Option { - return element - .get_sub_element(subelement_name) - .and_then(|elem| elem.character_data()) - .map(|cdata| cdata.to_string()); -} - -pub fn get_required_string(element: &Element, subelement_name: ElementName) -> String { - if let Some(value) = get_subelement_string_value(element, subelement_name) { - return value; - } else { - panic!("Error getting required String value of {}", subelement_name); - } -} - -pub fn get_optional_string(element: &Element, subelement_name: ElementName) -> String { - if let Some(value) = get_subelement_string_value(element, subelement_name) { - return value; - } else { - return String::from(""); - } -} - -pub fn get_subelement_optional_string(element: &Element, subelement_name: ElementName, sub_subelement_name: ElementName) -> String { - if let Some(value) = element.get_sub_element(subelement_name) - .and_then(|elem| elem.get_sub_element(sub_subelement_name)) - .and_then(|elem| elem.character_data()) - .map(|cdata| cdata.to_string()) - { - return value; - } else { - return String::from(""); - } -} - -pub fn ecu_of_frame_port(frame_port: &Element) -> Option { - let ecu_comm_port_instance = frame_port.parent().ok()??; - let comm_connector = ecu_comm_port_instance.parent().ok()??; - let connectors = comm_connector.parent().ok()??; - let ecu_instance = connectors.parent().ok()??; - ecu_instance.item_name() -} - -// 1: Big Endian, 0: Little Endian -pub fn get_byte_order(byte_order: &String) -> bool { - if byte_order.eq("MOST-SIGNIFICANT-BYTE-LAST") { - return false; - } - return true; -} - -// See how endianess affects PDU in 6.2.2 https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_TPS_SystemTemplate.pdf -// Currenlty assumes Little Endian byte ordering and has support for signals that are Little Endian or Big Endian -// Bit positions in undefined ranges are set to 1 -pub fn extract_init_values(unused_bit_pattern: bool, ungrouped_signals: &Vec, grouped_signals: &Vec, length: i64, byte_order: &bool) -> Vec { - // pre checks - if grouped_signals.len() > 0 && ungrouped_signals.len() > 0 { - panic!("both signal vectors are > 0"); - } - - let isignals: &Vec; - - if grouped_signals.len() > 0 { - if grouped_signals.len() > 1 { - panic!("Grouped signals > 0"); - } - isignals = &grouped_signals[0].isignals; - } else { - isignals = ungrouped_signals; - } - - let dlc: usize = length.try_into().unwrap(); - - let mut bits = vec![unused_bit_pattern; dlc * 8]; // Using unusued_bit_pattern for undefined bits - - for isignal in isignals { - let mut tmp_bit_array: Vec = Vec::new(); - let init_values = &isignal.init_values; - let isignal_byte_order = isignal.byte_order; - let isignal_length: usize = isignal.length.try_into().unwrap(); - let isignal_start: usize = isignal.start_pos.try_into().unwrap(); - - match init_values { - InitValues::Single(value) => { - let mut n = value.clone(); - - while n != 0 { - tmp_bit_array.push(n & 1 != 0); - n >>= 1; - } - - while tmp_bit_array.len() < isignal_length { - tmp_bit_array.push(false); - } - - if isignal_byte_order { - tmp_bit_array.reverse(); - } - } - InitValues::Array(values) => { - if isignal_length % 8 != 0 { - panic!("ISignal length for array is not divisable through 8. Length is {}", isignal_length); - } - - for isignal_value in values { - let byte_len: usize = 8; - let mut n = isignal_value.clone(); - let mut tmp_tmp_bit_array: Vec = Vec::new(); - - while n != 0 { - tmp_tmp_bit_array.push(n & 1 != 0); - n >>= 1; - } - - while tmp_tmp_bit_array.len() < byte_len { - tmp_tmp_bit_array.push(false); - } - - tmp_tmp_bit_array.reverse(); - - tmp_bit_array.extend(tmp_tmp_bit_array); - } - } - _ => continue - } - - if tmp_bit_array.len() != isignal.length.try_into().unwrap() { - panic!("Miscalculation for tmp_bit_array"); - } - - let mut index: usize = 0; - - while index < isignal_length { - bits[isignal_start + index] = tmp_bit_array[index]; - index += 1; - } - } - - let mut init_values: Vec = Vec::new(); - let mut current_byte: u8 = 0; - let mut bit_count = 0; - - for bit in bits { - current_byte <<= 1; - if bit { - current_byte |= 1; - } - bit_count += 1; - - if bit_count == 8 { - init_values.push(current_byte); - current_byte = 0; - bit_count = 0; - } - } - if bit_count > 0 { - current_byte <<= 8 - bit_count; - init_values.push(current_byte); - } - - if !byte_order { - for init_value in init_values.iter_mut() { - *init_value = init_value.reverse_bits(); // reverse bits of each byte - } - } - - if init_values.len() != dlc { - panic!("Error creating byte array"); - } - - /*if !byte_order { - init_values.reverse(); - }*/ - - return init_values; -} - -pub fn get_unused_bit_pattern(pdu: &Element) -> bool { - let unused_bit_pattern_int = get_required_int_value(&pdu, ElementName::UnusedBitPattern); - let unused_bit_pattern: bool; - - if unused_bit_pattern_int == 0 { - unused_bit_pattern = false; - } else if unused_bit_pattern_int == 1 { - unused_bit_pattern = true; - } else { - panic!("Error reading unused_bit_pattern. Value is {}", unused_bit_pattern_int); - } - - return unused_bit_pattern; -} - -pub fn process_frame_ports(can_frame_triggering: &Element, can_frame_triggering_name: &String, rx_ecus: &mut Vec, tx_ecus: &mut Vec) -> Result<(), String> { - if let Some(frame_ports) = can_frame_triggering.get_sub_element(ElementName::FramePortRefs) { - let frame_ports: Vec = frame_ports.sub_elements() - .filter(|se| se.element_name() == ElementName::FramePortRef) - .filter_map(|fpr| fpr.get_reference_target().ok()) - .collect(); - - for frame_port in frame_ports { - if let Some(ecu_name) = ecu_of_frame_port(&frame_port) { - if let Some(CharacterData::Enum(direction)) = frame_port - .get_sub_element(ElementName::CommunicationDirection) - .and_then(|elem| elem.character_data()) - { - match direction { - EnumItem::In => rx_ecus.push(ecu_name), - EnumItem::Out => tx_ecus.push(ecu_name), - _ => return Err(format!("Invalid direction ID encountered in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name)) - } - } else { - return Err(format!("No CommunicationDirection encountered in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name)) - } - } else { - return Err(format!("Could not extract ECUName in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name)) ; - } - } - } else { - return Err(format!("FramePortRefs in CanFrameTriggering not found. Skipping CanFrameTriggering {}", can_frame_triggering_name)); - } - - Ok(()) -} - -pub fn process_init_value(init_value_elem: &Element, init_values: &mut InitValues, signal_name: &String) { - let init_value_single: bool; - - let subelement_name = init_value_elem.get_sub_element_at(0).unwrap(); - - if subelement_name.element_name().eq(&ElementName::NumericalValueSpecification) { - init_value_single = true; - } else if subelement_name.element_name().eq(&ElementName::ArrayValueSpecification) { - init_value_single = false; - } else { - panic!("Unrecognized sublement {} for init-value", subelement_name.element_name()); - } - - if init_value_single { - if let Some(num_val) = init_value_elem.get_sub_element(ElementName::NumericalValueSpecification) { - let init_value = get_required_int_value(&num_val, ElementName::Value); - *init_values = InitValues::Single(init_value); - } else { - panic!("InitValue element does not have NumercialValueSpecification for signal {}", signal_name); - } - - } else { - let mut init_value_array: Vec = Vec::new(); - let num_val_elements = get_required_sub_subelement(&init_value_elem, - ElementName::ArrayValueSpecification, - ElementName::Elements); - - for num_val_elem in num_val_elements.sub_elements() { - init_value_array.push(get_required_int_value(&num_val_elem, ElementName::Value)); - } - - *init_values = InitValues::Array(init_value_array); - } -} - -pub fn process_signal_group(signal_group: &Element, - signals: &mut HashMap, - grouped_signals: &mut Vec) -> Option<()> - { - let group_name = get_required_item_name(&signal_group, "ISignalGroupRef"); - - let mut signal_group_signals: Vec = Vec::new(); - - let isignal_refs = signal_group.get_sub_element(ElementName::ISignalRefs)?; - - // Removing ok and needed? - for isignal_ref in isignal_refs.sub_elements() - .filter(|elem| elem.element_name() == ElementName::ISignalRef) { - if let Some(CharacterData::String(path)) = isignal_ref.character_data() { - if let Some(siginfo) = signals.get(&path) { - let siginfo_tmp = siginfo.clone(); - let isginal_tmp: ISignal = ISignal { - name: siginfo_tmp.0, - byte_order: get_byte_order(&siginfo_tmp.1), - start_pos: siginfo_tmp.2, - length: siginfo_tmp.3, - init_values: siginfo_tmp.4 - }; - - signal_group_signals.push(isginal_tmp); - signals.remove(&path); - } - } - } - - signal_group_signals.sort_by(|a, b| a.start_pos.cmp(&b.start_pos)); - - let mut data_transformations: Vec = Vec::new(); - - if let Some(com_transformations) = signal_group - .get_sub_element(ElementName::ComBasedSignalGroupTransformations) - { - for elem in com_transformations.sub_elements() { - let data_transformation = get_required_reference(&elem, - ElementName::DataTransformationRef); - - data_transformations.push(get_required_item_name( - &data_transformation, - "DataTransformation")); - } - } - - let mut props_vector: Vec = Vec::new(); - - if let Some(transformation_props) = signal_group.get_sub_element(ElementName::TransformationISignalPropss) { - for e2exf_props in transformation_props - .sub_elements() - .filter(|elem| elem.element_name() == ElementName::EndToEndTransformationISignalProps) - { - if let Some(e2exf_props_cond) = e2exf_props - .get_sub_element(ElementName::EndToEndTransformationISignalPropsVariants) - .and_then(|elem| elem.get_sub_element(ElementName::EndToEndTransformationISignalPropsConditional)) - { - let transformer_reference = get_required_reference(&e2exf_props_cond, - ElementName::TransformerRef); - - let transformer_name = get_required_item_name(&transformer_reference, - "TransformerName"); - - let data_ids = e2exf_props_cond - .get_sub_element(ElementName::DataIds)?; - - let data_id = get_required_int_value(&data_ids, - ElementName::DataId); - - let data_length = get_required_int_value(&e2exf_props_cond, - ElementName::DataLength); - - - let props_struct: E2EDataTransformationProps = E2EDataTransformationProps { - transformer_name: transformer_name, - data_id: data_id, - data_length: data_length - }; - - props_vector.push(props_struct); - } - } - } - - let isignal_group_struct: ISignalGroup = ISignalGroup { - name: group_name, - isignals: signal_group_signals, - data_transformations: data_transformations, - transformation_props: props_vector - }; - - grouped_signals.push(isignal_group_struct); - - Some(()) -} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/samples/system-4.2.arxml b/opendut-edgar/restbus-simulation/samples/system-4.2.arxml new file mode 100755 index 00000000..f9a5c0ac --- /dev/null +++ b/opendut-edgar/restbus-simulation/samples/system-4.2.arxml @@ -0,0 +1,1483 @@ + + + + + + ECU + + + + DJ + + /PDU_GROUPS/RX/DJ + /PDU_GROUPS/TX/DJ + + + + + Observe + + + + + + Dancer + + Rhythm is a Dancer! + + + /PDU_GROUPS/RX/Dancer + /PDU_GROUPS/TX/Dancer + + + + + Observe + + + + + + Guard + + + + Patrol + + + + + + + + PDU_GROUPS + + + + RX + + + + Dancer + IN + + + /ISignalIPdu/message1 + + + /ISignalIPdu/message4 + + + + + + DJ + IN + + + /ISignalIPdu/message2 + + + /ISignalIPdu/message3 + + + /ISignalIPdu/multiplexed_message_static + + + /ISignalIPdu/multiplexed_message_0 + + + /ISignalIPdu/multiplexed_message_1 + + + + + + + + TX + + + + Dancer + OUT + + + /ISignalIPdu/message2 + + + /ISignalIPdu/message3 + + + /ISignalIPdu/multiplexed_message_static + + + /ISignalIPdu/multiplexed_message_0 + + + /ISignalIPdu/multiplexed_message_1 + + + + + + DJ + OUT + + + /ISignalIPdu/message1 + + + /ISignalIPdu/message4 + + + + + + + + + + NMConfig + + + + A + + + + NightClub + /Cluster/Cluster0 + + + + DJ + /ECU/DJ/Observe + + /NMPdu/alarm_status + + + + + Dancer + /ECU/Dancer/Observe + + /NMPdu/alarm_status + + + + + Guard + /ECU/Guard/Patrol + + /NMPdu/alarm_status + + + + + + + + + + + Cluster + + + + Cluster0 + + The great CAN cluster + + + + 500000 + + + + Pch0 + + + + multiplexed_message + /CanFrame/MultiplexedMessage + STANDARD + CAN-FD + CAN-FD + 4 + + + + message1 + /CanFrame/Message1 + STANDARD + CAN-FD + 5 + + + + message2 + /CanFrame/Message2 + EXTENDED + CAN-FD + 6 + + + + message3 + /CanFrame/Message3 + STANDARD + 100 + + + + message4 + /CanFrame/Message4 + STANDARD + 101 + + + + OneToContainThemAll + /CanFrame/OneToContainThemAll + STANDARD + 102 + + + + AlarmStatus + /CanFrame/AlarmStatus + STANDARD + 1001 + + + MessageWithoutPDU + /CanFrame/MessageWithoutPDU + STANDARD + 1002 + + + + + + message1_triggering + /ISignalIPdu/message1 + + + + message2_triggering + /ISignalIPdu/message2 + + + + multiplexed_triggering + /MultiplexedIPdu/multiplexed_message + + + + message3_triggering + /ISignalIPdu/message3 + + + + message3_secured_triggering + /SecuredIPdu/message3_secured + + + + + can2.0b + 2000000 + + + + + + + + CanFrame + + + + MultiplexedMessage + 2 + + + + multiplexed_message + /MultiplexedIPdu/multiplexed_message + 0 + + + + + + Message1 + + Comment1 + Kommentar1 + + 9 + + + + message1 + /ISignalIPdu/message1 + + + + + + Message2 + 7 + + + + message2 + /ISignalIPdu/message2 + + + + + + Message3 + 6 + + + + message3 + /SecuredIPdu/message3_secured + + + + + + Message4 + 6 + + + + message4 + /ISignalIPdu/message4 + + + + + + OneToContainThemAll + 64 + + + + OneToContainThemAll_mapping + /ContainerIPdu/OneToContainThemAll + + + + + + AlarmStatus + 1 + + + + nm + /NMPdu/alarm_status + + + + + + MessageWithoutPDU + 8 + + + + + + ISignal + + + + ConstantBase + true + false + false + /Constants + + + + SystemSignalBase + false + false + false + /SystemSignal + + + + + + MultiplexedStatic + + + 0x7 + + + 3 + + + + /SwBaseType/S16 + + + + /SystemSignal/MultiplexedMessageStatic + + + + MultiplexedStatic2 + + + 0x7 + + + 8 + + + + /SwBaseType/S16 + + + + /SystemSignal/MultiplexedMessageStatic2 + + + + multiplexed_message_selector + + + 0.0 + + + 2 + + + + /SwBaseType/U8 + + + 7 + + + + + + /SystemSignal/MultiplexedMessageSelector + + + + Hello + + + 0 + + + 3 + + + + /SwBaseType/S16 + + + + /SystemSignal/Hello + + + + World1 + + + 3 + + + 2 + + + + /SwBaseType/S16 + + + + /SystemSignal/World1 + + + + World2 + + + 0 + + + 1 + + + + /SwBaseType/S16 + + + + /SystemSignal/World2 + + + + signal1 + + + 0b101 + + + 0b11 + /SystemSignal/Signal1 + + + + signal2 + 0xb + + + + /SwBaseType/S16 + + + + SystemSignalInner/Signal2 + + + + signal2_1c + 013 + + + + /SwBaseType/S16_1C + + + + /SystemSignal/SystemSignalInner/Signal2_1C + + + + signal2_sm + 11 + + + + /SwBaseType/S16_SM + + + + /SystemSignal/SystemSignalInner/Signal2_SM + + + + signal3 + 2 + + + + signal4 + 4 + + + + /SwBaseType/U8 + + + + /SystemSignal/Signal4 + + + + signal5 + 32 + + + + /SwBaseType/float + + + + + + + signal6 + + + BooleanFalse + + + 1 + /SystemSignal/Signal6 + + + + message1_CRC + 16 + + + + message1_SeqCounter + 16 + + + + message3_CRC + 8 + + + + message3_SeqCounter + 4 + + + + FireAlarm + + + BooleanFalse + + + 1 + /SystemSignal/FireAlarm + + + + message1Group + + /ISignal/signal1 + /ISignal/signal5 + /ISignal/signal6 + + + + + + /Transformers/Decepticons/Starscream + + 123 + 124 + 125 + 126 + 127 + 128 + 129 + 130 + 131 + 132 + 133 + 134 + 135 + 136 + 137 + 138 + + + + + + + + + message3Group + + /ISignal/message3_SeqCounter + /ISignal/message3_CRC + + + + + + /Transformers/Decepticons/Galvatron + + 321 + + + + + + + + + + + Transformers + + + + Decepticons + + + + Starscream + E2E + + + Profile2 + + + + + + Galvatron + E2E + + + Profile5 + + + + + + + + + + Constants + + + + BooleanTrue + + + + literal + true + + + + + + BooleanFalse + + + + literal + false + + + + + + + + MultiplexedIPdu + + + + multiplexed_message + 10 + + 0x070809 + + + + + + MOST-SIGNIFICANT-BYTE-LAST + 8 + 0 + + + + + /ISignalIPdu/multiplexed_message_0 + true + 0 + + + /ISignalIPdu/multiplexed_message_1 + false + 1 + + + + + 2 + 6 + + + /ISignalIPdu/multiplexed_message_static + + + + + + + + ContainerIPdu + + + + OneToContainThemAll + 64 + + /Cluster/Cluster0/Pch0/message1_triggering + /Cluster/Cluster0/Pch0/message2_triggering + /Cluster/Cluster0/Pch0/message3_triggering + /Cluster/Cluster0/Pch0/message3_secured_triggering + /Cluster/Cluster0/Pch0/multiplexed_triggering + + SHORT-HEADER + + + + + + ISignalIPdu + + + + multiplexed_message_static + 8 + + + + multiplexed_message_static_signal + /ISignal/MultiplexedStatic + 0 + + + + multiplexed_message_static2_signal + /ISignal/MultiplexedStatic2 + 8 + + + + + + multiplexed_message_0 + 8 + + + + multiplexed_message_selector + /ISignal/multiplexed_message_selector + 6 + + + + multiplexed_message_0_hello + /ISignal/Hello + 3 + + + + + + multiplexed_message_1 + 8 + + + + multiplexed_message_1_world1 + /ISignal/World1 + 4 + + + + multiplexed_message_1_world2 + /ISignal/World2 + 3 + + + + multiplexed_message_selector + /ISignal/multiplexed_message_selector + 6 + + + + + + message1 + 9 + + 0x0a0B0c + + + + + Counter + /ISignal/message1_SeqCounter + MOST-SIGNIFICANT-BYTE-LAST + 0 + + + + CRC + /ISignal/message1_CRC + MOST-SIGNIFICANT-BYTE-LAST + 16 + + + + Signal1 + /ISignal/signal1 + MOST-SIGNIFICANT-BYTE-FIRST + 36 + + + + Signal5 + /ISignal/signal5 + MOST-SIGNIFICANT-BYTE-LAST + 40 + + + + Signal6 + /ISignal/signal6 + MOST-SIGNIFICANT-BYTE-LAST + 32 + + + + + SignalGroup + /ISignal/message1Group + + + + + + message2 + 7 + + 0x1D2E3F + + + + + + + + 0 + + + 0.1 + + + + + + + 0 + + + 0.2 + + + + + + + + + + Signal2 + /ISignal/signal2 + MOST-SIGNIFICANT-BYTE-LAST + 18 + + + + Signal3 + /ISignal/signal3 + MOST-SIGNIFICANT-BYTE-LAST + 6 + + + + Signal4 + /ISignal/signal4 + MOST-SIGNIFICANT-BYTE-LAST + 30 + + + + + + message3 + 4 + + 0x010203 + + + + + message3_CRC + /ISignal/message3_CRC + MOST-SIGNIFICANT-BYTE-LAST + 0 + + + + message3_SeqCounter + /ISignal/message3_SeqCounter + MOST-SIGNIFICANT-BYTE-LAST + 8 + + + + SignalGroup + /ISignal/message3Group + + + + + + message4 + 6 + + + + Signal2 + /ISignal/signal2 + MOST-SIGNIFICANT-BYTE-LAST + 0 + + + + Signal2_1C + /ISignal/signal2_1c + MOST-SIGNIFICANT-BYTE-LAST + 16 + + + + Signal2_SM + /ISignal/signal2_sm + MOST-SIGNIFICANT-BYTE-LAST + 32 + + + 85 + + + + + + NMPdu + + + + alarm_status + 1 + + + + fire_alarm + /ISignal/FireAlarm + 0 + + + + + + + + SecuredIPdu + + + + message3_secured + 6 + + 0x040506 + + /SecOCProps/S/KnockKnock + /SecOCProps/S/SmellyCheese + /Cluster/Cluster0/Pch0/message3_triggering + + 1337 + + + + + + + SecOCProps + + + + S + + + + KnockKnock + 10 + + + + + + SmellyCheese + 32 + 6 + + + + + + + + Unit + + + + meters + m + + + + wizepoo + wp + + + + zilch + NoUnit + + + + + + CompuMethod + + + + Identical + IDENTICAL + + + + MultiplexedMessageSelector + TEXTTABLE + + + + 0 + 0 + + SELECT_HELLO + + + + 1 + 1 + + SELECT_WORLD + + + + 3 + 3 + + INVALID_SELECTION + + + + + + + + Signal1 + LINEAR + + + + 0 + 4 + + + 0 + 10 + + + 2 + + + + + + + + + Signal4 + TEXTTABLE + /Unit/zilch + + + + + One Comment + Ein Kommentar + + 1 + 1 + + one + + + + 2 + 2 + + two + + + + + + + + Signal6 + SCALE_LINEAR_AND_TEXTTABLE + /Unit/wizepoo + + + + + Nothing + Nichts + + 0 + 0 + + zero + + + + 0 + 1 + + + 0 + 0.1 + + + 1 + + + + + + + + + + + SystemSignal + + + + MultiplexedMessageStatic + + + + /CompuMethod/Identical + + + + + + + MultiplexedMessageStatic2 + + + + /CompuMethod/Identical + + + + + + + MultiplexedMessageSelector + + + + /CompuMethod/MultiplexedMessageSelector + + + + + + + Hello + + + + /CompuMethod/Identical + + + + + + + World1 + + + + /CompuMethod/Identical + + + + + + + World2 + + + + /CompuMethod/Identical + + + + + + + Signal1 + + Signal comment! + Signalkommentar! + + + + + /CompuMethod/Signal1 + /Unit/meters + + + + + + + Signal4 + + + + /CompuMethod/Signal4 + + + + + + + Signal6 + + + + /CompuMethod/Signal6 + + + + + + + FireAlarm + + + + /CompuMethod/Identical + + + + + + + + + SystemSignalInner + + + + Signal2 + + Signal comment! + + + + + Signal2_1C + + Signal comment! (1-Complement) + + + + + Signal2_SM + + + + + + + + + + + SwBaseType + + + + S16 + FIXED_LENGTH + 16 + 2C + + + + S16_1C + FIXED_LENGTH + 16 + 1C + + + + S16_SM + FIXED_LENGTH + 16 + SM + + + + U8 + FIXED_LENGTH + 8 + NONE + + + + float + FIXED_LENGTH + 32 + IEEE754 + + + + + + System + + + + cantools_system + SYSTEM_EXTRACT + MOST-SIGNIFICANT-BYTE-FIRST + + + + + diff --git a/opendut-edgar/restbus-simulation/src/arxml_parser.rs b/opendut-edgar/restbus-simulation/src/arxml_parser.rs new file mode 100755 index 00000000..db1f5900 --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/arxml_parser.rs @@ -0,0 +1,505 @@ +/* + Arxml parser that is able to extract all values necessary for a restbus simulation. + Uses autosar-data library to parse data like in this example: + https://github.com/DanielT/autosar-data/blob/main/autosar-data/examples/businfo/main.rs + Ideas for improvement: + - Provide options to store parsed data for quicker restart +*/ + +use crate::arxml_structs::*; +use crate::arxml_utils::*; + +use std::time::Instant; +use std::collections::HashMap; + +use anyhow::{anyhow, bail, Result}; + +use autosar_data::{AutosarModel, CharacterData, Element, ElementName, EnumItem}; + +use tracing::{error, info, warn, debug}; + + +pub struct ArxmlParser { +} + +impl ArxmlParser { + /* + 1. Parses an Autosar ISignalToIPduMapping. + 2. Extracts Autosar ISignal and ISignalGroup elements. + 2. Fills the important extracted data into the signals HashMap and signal_groups vectors. + */ + fn handle_isignal_to_pdu_mappings(&self, mapping: &Element, + signals: &mut HashMap, + signal_groups: &mut Vec) -> Result<()> + { + if let Some(signal) = mapping + .get_sub_element(ElementName::ISignalRef) + .and_then(|elem| elem.get_reference_target().ok()) + { + let refpath = get_subelement_string_value(mapping, ElementName::ISignalRef) + .ok_or_else(|| anyhow!("Error getting required String value of {}", ElementName::ISignalRef))?; + + let name = signal.item_name() + .ok_or_else(|| Error::GetItemName{item: "ISignalRef"})?; + + let byte_order = get_subelement_string_value(mapping, ElementName::PackingByteOrder) + .ok_or_else(|| anyhow!("Error getting required String value of {}", ElementName::PackingByteOrder))?; + + let start_pos = get_required_int_value(mapping, + ElementName::StartPosition)?; + + let length = get_required_int_value(&signal, + ElementName::Length)?; + + let mut init_values: InitValues = InitValues::NotExist(true); + + if let Some(mut init_value_elem) = signal.get_sub_element(ElementName::InitValue) { + process_init_value(&mut init_value_elem, &mut init_values, &name)?; + } + signals.insert(refpath, (name, byte_order, start_pos, length, init_values)); + } else if let Some(signal_group) = mapping + .get_sub_element(ElementName::ISignalGroupRef) + .and_then(|elem| elem.get_reference_target().ok()) + { + // store the signal group for now + signal_groups.push(signal_group); + } + + Ok(()) + } + + /* + 1. Parses and processes all the ISignals defined in the parent ISignalIPdu. + 2. Fills the important extracted data into the grouped_signals and ungrouped_signals vectors of structures. + */ + fn handle_isignals(&self, pdu: &Element, grouped_signals: &mut Vec, ungrouped_signals: &mut Vec) -> Result<()> { + //let mut signals: HashMap, Option)> = HashMap::new(); + let mut signals: HashMap = HashMap::new(); + let mut signal_groups = Vec::new(); + + + if let Some(isignal_to_pdu_mappings) = pdu.get_sub_element(ElementName::ISignalToPduMappings) { + // collect information about the signals and signal groups + for mapping in isignal_to_pdu_mappings.sub_elements() { + self.handle_isignal_to_pdu_mappings(&mapping, &mut signals, &mut signal_groups)?; + } + } + + for signal_group in &signal_groups { + process_signal_group(signal_group, &mut signals, grouped_signals)?; + } + + let remaining_signals: Vec<(String, String, u64, u64, InitValues)> = signals.values().cloned().collect(); + if !remaining_signals.is_empty() { + for (name, byte_order, start_pos, length, init_values) in remaining_signals { + let isignal_struct: ISignal = ISignal { + name, + byte_order: get_byte_order(&byte_order), + start_pos, + length, + init_values + }; + ungrouped_signals.push(isignal_struct); + } + } + + ungrouped_signals.sort_by(|a, b| a.start_pos.cmp(&b.start_pos)); + + Ok(()) + } + + /* + 1. Parses an Autosar ISignalIPdu element. + 2. Returns important data in a self-defined ISignalIPDU structure. + */ + fn handle_isignal_ipdu(&self, pdu: &Element) -> Result { + // Find out these values: ... + let mut cyclic_timing_period_value: f64 = 0_f64; + let mut cyclic_timing_period_tolerance: Option = None; + + let mut cyclic_timing_offset_value: f64 = 0_f64; + let mut cyclic_timing_offset_tolerance: Option = None; + + let mut number_of_repetitions: u64 = 0; + let mut repetition_period_value: f64 = 0_f64; + let mut repetition_period_tolerance: Option = None; + + if let Some(tx_mode_true_timing) = pdu + .get_sub_element(ElementName::IPduTimingSpecifications) + .and_then(|elem| elem.get_sub_element(ElementName::IPduTiming)) + .and_then(|elem| elem.get_sub_element(ElementName::TransmissionModeDeclaration)) + .and_then(|elem| elem.get_sub_element(ElementName::TransmissionModeTrueTiming)) + { + if let Some(cyclic_timing) = tx_mode_true_timing + .get_sub_element(ElementName::CyclicTiming) + { + get_sub_element_and_time_range(&cyclic_timing, ElementName::TimePeriod, &mut cyclic_timing_period_value, &mut cyclic_timing_period_tolerance); + + get_sub_element_and_time_range(&cyclic_timing, ElementName::TimeOffset, &mut cyclic_timing_offset_value, &mut cyclic_timing_offset_tolerance); + } + if let Some(event_timing) = tx_mode_true_timing + .get_sub_element(ElementName::EventControlledTiming) + { + number_of_repetitions = get_optional_int_value(&event_timing, + ElementName::NumberOfRepetitions); + + get_sub_element_and_time_range(&event_timing, ElementName::RepetitionPeriod, &mut repetition_period_value, &mut repetition_period_tolerance); + } + } + + let unused_bit_pattern = get_unused_bit_pattern(pdu); + + let mut grouped_signals: Vec = Vec::new(); + + let mut ungrouped_signals: Vec = Vec::new(); + + self.handle_isignals(pdu, &mut grouped_signals, &mut ungrouped_signals)?; + + let isginal_ipdu: ISignalIPdu = ISignalIPdu { + cyclic_timing_period_value, + cyclic_timing_period_tolerance, + cyclic_timing_offset_value, + cyclic_timing_offset_tolerance, + number_of_repetitions, + repetition_period_value, + repetition_period_tolerance, + unused_bit_pattern, + ungrouped_signals, + grouped_signals + }; + + Ok(isginal_ipdu) + } + + /* + 1. Parses an Autosar NmPdu element + 2. Returns important data in a self-defined NMPDU structure. + */ + fn handle_nm_pdu(&self, pdu: &Element) -> Result { + let unused_bit_pattern = get_unused_bit_pattern(pdu); + + let mut grouped_signals: Vec = Vec::new(); + + let mut ungrouped_signals: Vec = Vec::new(); + + self.handle_isignals(pdu, &mut grouped_signals, &mut ungrouped_signals)?; + + let nm_pdu: NmPdu = NmPdu { + unused_bit_pattern, + ungrouped_signals, + grouped_signals + }; + + Ok(nm_pdu) + } + + /* + 1. Resolves the reference inside a PduToFrameMapping to get the PDU element. + 2. Parses the Autosar PDU element + 3. Returns important data in a self-defined PDU mapping structure. + */ + fn handle_pdu_mapping(&self, pdu_mapping: &Element) -> Result { + let pdu = get_required_reference( + pdu_mapping, + ElementName::PduRef)?; + + let pdu_name = pdu.item_name() + .ok_or_else(|| Error::GetItemName{item: "Pdu"})?; + + //let byte_order = get_required_string(pdu_mapping, + let byte_order = get_optional_string(pdu_mapping, + ElementName::PackingByteOrder); + + let pdu_length = get_required_int_value(&pdu, + ElementName::Length)?; + + let pdu_dynamic_length = get_optional_string(&pdu, + ElementName::HasDynamicLength); + + let pdu_category = get_optional_string(&pdu, + ElementName::Category); + + let pdu_contained_header_id_short = get_subelement_optional_string(&pdu, + ElementName::ContainedIPduProps, ElementName::HeaderIdShortHeader); + + let pdu_contained_header_id_long = get_subelement_optional_string(&pdu, + ElementName::ContainedIPduProps, ElementName::HeaderIdLongHeader); + + let pdu_specific = match pdu.element_name() { + ElementName::ISignalIPdu => { + self.handle_isignal_ipdu(&pdu).map(Pdu::ISignalIPdu)? + } + ElementName::NmPdu => { + self.handle_nm_pdu(&pdu).map(Pdu::NmPdu)? + } + _ => { + bail!("PDU type {} not supported. Will skip it.", pdu.element_name()) + } + }; + + let pdu_mapping: PduMapping = PduMapping { + name: pdu_name, + byte_order: get_byte_order(&byte_order), + length: pdu_length, + dynamic_length: pdu_dynamic_length, + category: pdu_category, + contained_header_id_short: pdu_contained_header_id_short, + contained_header_id_long: pdu_contained_header_id_long, + pdu: pdu_specific + }; + + Ok(pdu_mapping) + } + + /* + 1. Parses an Autosar CanFrameTriggering element. + 2. Returns important data in a self-defined CanFrameTriggering structure. + */ + fn handle_can_frame_triggering(&self, can_frame_triggering: &Element, has_fd_baudrate: bool) -> Result { + let can_frame_triggering_name = can_frame_triggering.item_name() + .ok_or_else(|| Error::GetItemName{item: "CanFrameTriggering"})?; + + let can_id = get_required_int_value( + can_frame_triggering, + ElementName::Identifier)?; + + let frame = get_required_reference( + can_frame_triggering, + ElementName::FrameRef)?; + + let frame_name = frame.item_name() + .ok_or_else(|| Error::GetItemName{item: "Frame"})?; + + let addressing_mode_str = if let Some(CharacterData::Enum(value)) = can_frame_triggering + .get_sub_element(ElementName::CanAddressingMode) + .and_then(|elem| elem.character_data()) + { + value.to_string() + } else { + EnumItem::Standard.to_string() + }; + + let can_29_bit_addressing = addressing_mode_str.eq_ignore_ascii_case("EXTENDED"); + + // allow it to be missing. When missing, then derive value from CanCluster + let mut frame_rx_behavior = false; + let frame_rx_behavior_str = get_optional_string( + can_frame_triggering, + ElementName::CanFrameRxBehavior); + if frame_rx_behavior_str.to_uppercase() == *"CAN-FD" + || frame_rx_behavior_str.is_empty() && has_fd_baudrate { + frame_rx_behavior = true; + } + + // allow it to be missing. When missing, then derive value from CanCluster + let mut frame_tx_behavior = false; + let frame_tx_behavior_str = get_optional_string( + can_frame_triggering, + ElementName::CanFrameTxBehavior); + if frame_tx_behavior_str.to_uppercase() == *"CAN-FD" + || frame_tx_behavior_str.is_empty() && has_fd_baudrate { + frame_tx_behavior = true; + } + + let mut rx_range_lower: u64 = 0; + let mut rx_range_upper: u64 = 0; + if let Some(range_elem) = can_frame_triggering.get_sub_element(ElementName::RxIdentifierRange) { + rx_range_lower = get_required_int_value(&range_elem, ElementName::LowerCanId)?; + rx_range_upper = get_required_int_value(&range_elem, ElementName::UpperCanId)?; + } + + let mut rx_ecus: Vec = Vec::new(); + let mut tx_ecus: Vec = Vec::new(); + + process_frame_ports(can_frame_triggering, &can_frame_triggering_name, &mut rx_ecus, &mut tx_ecus)?; + + let frame_length = get_optional_int_value( + &frame, + ElementName::FrameLength); + + let mut pdu_mappings_vec: Vec = Vec::new(); + + // assign here and other similar variable? + if let Some(mappings) = frame.get_sub_element(ElementName::PduToFrameMappings) { + for pdu_mapping in mappings.sub_elements() { + match self.handle_pdu_mapping(&pdu_mapping) { + Ok(value) => pdu_mappings_vec.push(value), + Err(error) => bail!(error) + } + } + } + + let can_frame_triggering_struct: CanFrameTriggering = CanFrameTriggering { + frame_triggering_name: can_frame_triggering_name, + frame_name, + can_id, + can_29_bit_addressing, + frame_rx_behavior, + frame_tx_behavior, + rx_range_lower, + rx_range_upper, + receiver_ecus: rx_ecus, + sender_ecus: tx_ecus, + frame_length, + pdu_mappings: pdu_mappings_vec + }; + + Ok(can_frame_triggering_struct) + } + + /* + 1. Parses an Autosar CanCluster element + 2. Returns important data in a self-defined CanCluster structure. + */ + fn handle_can_cluster(&self, can_cluster: &Element) -> Result { + let can_cluster_name = can_cluster.item_name() + .ok_or_else(|| Error::GetItemName{item: "CanCluster"})?; + + let can_cluster_conditional = get_required_sub_subelement( + can_cluster, + ElementName::CanClusterVariants, + ElementName::CanClusterConditional)?; + + let can_cluster_baudrate = get_optional_int_value( + &can_cluster_conditional, + ElementName::Baudrate); + + let can_cluster_fd_baudrate = get_optional_int_value( + &can_cluster_conditional, + ElementName::CanFdBaudrate); + + let has_fd_baudrate = can_cluster_baudrate > 0; + + if can_cluster_baudrate == 0 && can_cluster_fd_baudrate == 0 { + bail!("Baudrate and FD Baudrate of CanCluster {} do not exist or are 0. Skipping this CanCluster.", can_cluster_name) + } + + // iterate over PhysicalChannels and handle the CanFrameTriggerings inside them + let physical_channels; + if let Some(value) = can_cluster_conditional + .get_sub_element(ElementName::PhysicalChannels).map(|elem| { + elem.sub_elements().filter(|se| se.element_name() == ElementName::CanPhysicalChannel) + }) + { + physical_channels = value; + } else { + bail!("Cannot handle physical channels of CanCluster {}", can_cluster_name) + } + + let mut can_frame_triggerings: HashMap = HashMap::new(); + for physical_channel in physical_channels { + if let Some(frame_triggerings) = physical_channel.get_sub_element(ElementName::FrameTriggerings) { + for can_frame_triggering in frame_triggerings.sub_elements() { + match self.handle_can_frame_triggering(&can_frame_triggering, has_fd_baudrate) { + Ok(value) => { + can_frame_triggerings.insert(value.can_id, value); + } + Err(error) => error!("WARNING: {}", error), + } + } + } + } + + let can_cluster_struct: CanCluster = CanCluster { + name: can_cluster_name, + baudrate: can_cluster_baudrate, + canfd_baudrate: can_cluster_fd_baudrate, + can_frame_triggerings + }; + + Ok(can_cluster_struct) + } + + /* + Main parsing method. Uses autosar-data libray for parsing ARXML. + In the future, it might be extended to support Ethernet, Flexray, ... + The resources to develop that should not be thaat high, since it is basically just extending the current parser. + Param file_name: ARXML target file name without ".ser" extension + Param safe_or_load_serialized: First look if serialized parsed data already exists by looking for file_name + ".ser". + If not exists, then parse and safe parsed structures as serialized data in file_name + ".ser" + Returns a vector of CanCluster structures. + */ + pub fn parse_file(&self, file_name: &String, safe_or_load_serialized: bool) -> Result, String> { + if safe_or_load_serialized { + info!("Loading data from serialized file"); + match load_serialized_data(file_name) { + Ok(value) => { + info!("Successfully loaded serialized data."); + return Ok(value) + } + _ => warn!("Could not load serialized data. Will continue parsing.") + } + } + + let start = Instant::now(); + + let model = AutosarModel::new(); + + if let Err(err) = model.load_file(file_name, false) { + return Err(format!("Parsing failed. Error: {}", err)); + } + + debug!("Duration of loading was: {:?}", start.elapsed()); + + let mut can_clusters: HashMap = HashMap::new(); + + // Iterate over Autosar elements and handle CanCluster elements + for element in model + .identifiable_elements() + .filter_map(|(_path, weak)| weak.upgrade()) + { + if element.element_name() == ElementName::CanCluster { + match self.handle_can_cluster(&element) { + Ok(value) => { + can_clusters.insert(value.name.clone(), value); + } + Err(error) => warn!("WARNING: {}", error) + } + } + } + + info!("Duration of parsing: {:?}", start.elapsed()); + + if safe_or_load_serialized { + info!("Storing serialized data to file"); + match store_serialized_data(file_name, &can_clusters) { + Ok(()) => info!("Successfully stored serialized data."), + _ => error!("Could not store serialized data.") + } + } + + Ok(can_clusters) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn get_sample_file_path() -> String{ + let mut sample_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + sample_file_path.push("samples/system-4.2.arxml"); + + return sample_file_path.into_os_string().into_string().unwrap() + } + + #[test] + fn test_parsing() { + let arxml_parser: ArxmlParser = ArxmlParser {}; + + let parse_res = arxml_parser.parse_file(&get_sample_file_path(), false).unwrap(); + + assert_eq!(parse_res.len(), 1); + let (cluster_name, cluster) = parse_res.iter().next().unwrap(); + + assert_eq!(&String::from("Cluster0"), cluster_name); + + println!("{}", cluster.can_frame_triggerings.len()); + + assert_eq!(cluster.can_frame_triggerings.len(), 5) + + // TODO: Extend this test + } + +} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/src/arxml_structs.rs b/opendut-edgar/restbus-simulation/src/arxml_structs.rs new file mode 100755 index 00000000..cc15129b --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/arxml_structs.rs @@ -0,0 +1,160 @@ +/* + Also see https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_TPS_SystemTemplate.pdf. +*/ + + +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; + +/* + Represents important data from Autosar CanCluster element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct CanCluster { + pub name: String, + pub baudrate: u64, + pub canfd_baudrate: u64, + pub can_frame_triggerings: HashMap +} + +/* + Represents important data from Autosar CanFrameTriggering element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct CanFrameTriggering { + pub frame_triggering_name: String, + pub frame_name: String, + pub can_id: u64, + pub can_29_bit_addressing: bool, + pub frame_rx_behavior: bool, + pub frame_tx_behavior: bool, + pub rx_range_lower: u64, + pub rx_range_upper: u64, + pub sender_ecus: Vec, + pub receiver_ecus: Vec, + pub frame_length: u64, + pub pdu_mappings: Vec +} + +/* + Represents important parent data from an Autosar *PDU element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct PduMapping { + pub name: String, + pub byte_order: bool, +// pub start_position: u64, + pub length: u64, + pub dynamic_length: String, + pub category: String, + pub contained_header_id_short: String, + pub contained_header_id_long: String, + pub pdu: Pdu +} + +/* + Enum of all supported PDU types. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub enum Pdu { + ISignalIPdu(ISignalIPdu), + NmPdu(NmPdu), +// DCMIPDU(DCMIPDU), +// NMPDU(NMPDU), +// ContaineredPDU(XY), +// Temp(i64) +} + + +/* + Represents important data from an Autosar ISignalIPDU element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct ISignalIPdu { + pub cyclic_timing_period_value: f64, + pub cyclic_timing_period_tolerance: Option, + pub cyclic_timing_offset_value: f64, + pub cyclic_timing_offset_tolerance: Option, + pub number_of_repetitions: u64, + pub repetition_period_value: f64, + pub repetition_period_tolerance: Option, + pub unused_bit_pattern: bool, + pub ungrouped_signals: Vec, + pub grouped_signals: Vec, +} + +/* + Represents important data from an Autosar NmPdu element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct NmPdu { + pub unused_bit_pattern: bool, + pub ungrouped_signals: Vec, + pub grouped_signals: Vec, +} + +/* + Represents important data from an Autosar ISignal element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct ISignal { + pub name: String, + pub byte_order: bool, + pub start_pos: u64, + pub length: u64, + pub init_values: InitValues +} + +/* + Enum of the initial values of an ISignal. +*/ +#[derive(Debug)] +#[derive(Clone)] +#[derive(Serialize, Deserialize)] +pub enum InitValues { + Single(u64), + Array(Vec), + NotExist(bool), +} + +/* + Represents important data from an Autosar ISignal element. +*/ +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct E2EDataTransformationProps { + pub transformer_name: String, + pub data_id: u64, + pub data_length: u64 +} + +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct ISignalGroup { + pub name: String, + pub isignals: Vec, + pub data_transformations: Vec, + pub transformation_props: Vec +} + +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub enum TimeRangeTolerance { + Relative(u64), + Absolute(f64), +} + +#[derive(Debug)] +#[derive(Serialize, Deserialize)] +pub struct TimeRange { + pub tolerance: Option, + pub value: f64, +} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/src/arxml_utils.rs b/opendut-edgar/restbus-simulation/src/arxml_utils.rs new file mode 100755 index 00000000..322553df --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/arxml_utils.rs @@ -0,0 +1,637 @@ +/* + HELPER METHODS. + Some are oriented on https://github.com/DanielT/autosar-data/blob/main/autosar-data/examples/businfo/main.rs. +*/ +use crate::arxml_structs::*; +use crate::restbus_structs::*; +use crate::restbus_utils::*; + +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; +use std::vec; + +use anyhow::{anyhow, bail, Result}; + +use autosar_data::{CharacterData, Element, ElementName, EnumItem}; + +use nix::libc::timeval; + +use tracing::warn; + +/* + Converts a CharacterData type from the autosar_data library + Directly taken from https://github.com/DanielT/autosar-data/blob/main/autosar-data/examples/businfo/main.rs. +*/ +pub fn decode_integer(cdata: &CharacterData) -> Option { + if let CharacterData::String(text) = cdata { + if text == "0" { + Some(0) + } else if text.starts_with("0x") || text.starts_with("0X") { + Some(u64::from_str_radix(&text[2..], 16).ok()?) + } else if text.starts_with("0b") || text.starts_with("0B") { + Some(u64::from_str_radix(&text[2..], 2).ok()?) + } else if let Some(stripped_octal) = text.strip_prefix('0') { + Some(u64::from_str_radix(stripped_octal, 8).ok()?) + } else if text.to_ascii_lowercase() == "false" { + Some(0) + } else if text.to_ascii_lowercase() == "true" { + Some(1) + } else { + Some(text.parse().ok()?) + } + } else { + None + } +} + +/* + Processes time-related element data (intended from a ISignalIPdu element) and returns a self-defined TimeRange struct. +*/ +pub fn get_time_range(base: &Element) -> Option { + let value = base + .get_sub_element(ElementName::Value) + .and_then(|elem| elem.character_data()) + .and_then(|cdata| cdata.float_value())?; + + let tolerance = if let Some(absolute_tolerance) = base + .get_sub_element(ElementName::AbsoluteTolerance) + .and_then(|elem| elem.get_sub_element(ElementName::Absolute)) + .and_then(|elem| elem.character_data()) + .and_then(|cdata| cdata.float_value()) + { + Some(TimeRangeTolerance::Absolute(absolute_tolerance)) + } else { + base.get_sub_element(ElementName::RelativeTolerance) + .and_then(|elem| elem.get_sub_element(ElementName::Relative)) + .and_then(|elem| elem.character_data()) + .and_then(|cdata| decode_integer(&cdata)) + .map(TimeRangeTolerance::Relative) + }; + + Some(TimeRange { tolerance, value }) +} + +/* + Gets a sub-element and tries to extract time-related information. +*/ +pub fn get_sub_element_and_time_range(base: &Element, sub_elem_name: ElementName, value: &mut f64, tolerance: &mut Option) { + if let Some(time_range) = base + .get_sub_element(sub_elem_name) + .and_then(|elem| get_time_range(&elem)) + { + *value = time_range.value; + *tolerance = time_range.tolerance; + } +} + +/* + Gets a required subsubelement from the element. This needs to succeed. +*/ +pub fn get_required_sub_subelement(element: &Element, subelement_name: ElementName, sub_subelement_name: ElementName) -> Result { + element.get_sub_element(subelement_name) + .and_then(|elem| elem.get_sub_element(sub_subelement_name)) + .ok_or_else(|| anyhow!("Sub-sub-element of {subelement_name}->{sub_subelement_name} does not exist")) +} + +/* + Tries to get a subelement and convert it's value to u64. +*/ +pub fn get_subelement_int_value(element: &Element, subelement_name: ElementName) -> Option { + element + .get_sub_element(subelement_name) + .and_then(|elem| elem.character_data()) + .and_then(|cdata| decode_integer(&cdata)) +} + +/* + Gets the u64 value for a element. This has to succeed. +*/ +pub fn get_required_int_value(element: &Element, subelement_name: ElementName) -> Result { + get_subelement_int_value(element, subelement_name) + .ok_or_else(|| anyhow!("Error getting required integer value of {subelement_name}")) +} + +/* + Gets the u64 value for a element. This is optional. So, if the subelement does not exist, then 0 is returned. +*/ +pub fn get_optional_int_value(element: &Element, subelement_name: ElementName) -> u64 { + get_subelement_int_value(element, subelement_name).unwrap_or_default() +} + +/* + Resolves a reference and returns the target Element. This has to succeed. +*/ +pub fn get_required_reference(element: &Element, subelement_name: ElementName) -> Result { + if let Some(subelement) = element.get_sub_element(subelement_name){ + match subelement.get_reference_target() { + Ok(reference) => return Ok(reference), + Err(err) => { + warn!("[-] Warning: Constant ref error: {}. Will try modification of target name and reference again.", err); + match subelement.character_data() { + Some(val) => { + let new_dest = "/Constants/".to_string() + val.to_string().as_str(); + match subelement.set_character_data(CharacterData::String(new_dest)) { + Ok(()) => {} + Err(err) => warn!("[-] Warning: Error setting new dest: {}", err) + } + match subelement.get_reference_target() { + Ok(reference) => return Ok(reference), + Err(err) => warn!("[-] Warning: Constant ref retry error: {}.", err), + } + } + _ => warn!("[-] Warning: No success in retry because Element CharacterData is not set."), + } + } + } + } + + bail!("Error getting required reference for {}", subelement_name) +} + +/* + Tries to get a subelement and return it's String value. +*/ +pub fn get_subelement_string_value(element: &Element, subelement_name: ElementName) -> Option { + element + .get_sub_element(subelement_name) + .and_then(|elem| elem.character_data()) + .map(|cdata| cdata.to_string()) +} + +/* + Gets the String value for a element. This is optional. So, if the subelement does not exist, then "" is returned. +*/ +pub fn get_optional_string(element: &Element, subelement_name: ElementName) -> String { + get_subelement_string_value(element, subelement_name).unwrap_or_default() +} + +/* + Gets the String value of a subsubelement. In case the subelement or subsubelement do not exist, then "" is returned. +*/ +pub fn get_subelement_optional_string(element: &Element, subelement_name: ElementName, sub_subelement_name: ElementName) -> String { + element.get_sub_element(subelement_name) + .and_then(|elem| elem.get_sub_element(sub_subelement_name)) + .and_then(|elem| elem.character_data()) + .map(|cdata| cdata.to_string()).unwrap_or_default() +} + +/* + Retrieves the ECU name by resolving multiple references. +*/ +pub fn ecu_of_frame_port(frame_port: &Element) -> Option { + let ecu_comm_port_instance = frame_port.parent().ok()??; + let comm_connector = ecu_comm_port_instance.parent().ok()??; + let connectors = comm_connector.parent().ok()??; + let ecu_instance = connectors.parent().ok()??; + ecu_instance.item_name() +} + +/* + Helper method comparing a given String with a byte order indicator. + Returns true for Big Endian, false for Little Endian +*/ +pub fn get_byte_order(byte_order: &String) -> bool { + if byte_order.eq("MOST-SIGNIFICANT-BYTE-LAST") { + return false; + } + true +} + +fn process_isignal_init_value(isignal: &ISignal, bits: &mut [bool]) -> Result<()>{ + let mut tmp_bit_array: Vec = Vec::new(); + let init_values = &isignal.init_values; + let isignal_byte_order = isignal.byte_order; + let isignal_length: usize = isignal.length.try_into()?; + let isignal_start: usize = isignal.start_pos.try_into()?; + + match init_values { + InitValues::Single(value) => { + let mut n = *value; + + while n != 0 { + tmp_bit_array.push(n & 1 != 0); + n >>= 1; + } + + while tmp_bit_array.len() < isignal_length { + tmp_bit_array.push(false); + } + + if isignal_byte_order { + tmp_bit_array.reverse(); + } + } + InitValues::Array(values) => { + if isignal_length % 8 != 0 { + bail!("ISignal length for array is not divisible by 8. Length is {}", isignal_length) + } + + for isignal_value in values { + let byte_len: usize = 8; + let mut n = *isignal_value; + let mut tmp_tmp_bit_array: Vec = Vec::new(); + + while n != 0 { + tmp_tmp_bit_array.push(n & 1 != 0); + n >>= 1; + } + + while tmp_tmp_bit_array.len() < byte_len { + tmp_tmp_bit_array.push(false); + } + + tmp_tmp_bit_array.reverse(); + + tmp_bit_array.extend(tmp_tmp_bit_array); + } + } + _ => return Ok(()) + } + + if tmp_bit_array.len() != >::try_into(isignal.length)? { + bail!("Miscalculation for tmp_bit_array") + } + + let mut index: usize = 0; + + while index < isignal_length { + bits[isignal_start + index] = tmp_bit_array[index]; + index += 1; + } + + Ok(()) +} + +/* + Extracts the initial values for a PDU by processing contained ISignal and ISignalGroup elements related to that PDU. + See how endianess affects PDU in 6.2.2 https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_TPS_SystemTemplate.pdf + Currenlty assumes Little Endian byte ordering and has support for signals that are Little Endian or Big Endian. + Bit positions in undefined ranges are set to unused_bit_pattern. +*/ +pub fn extract_init_values(unused_bit_pattern: bool, ungrouped_signals: &Vec, grouped_signals: &Vec, length: u64, byte_order: &bool) -> Result> { + let dlc: usize = length.try_into()?; + + let mut bits = vec![unused_bit_pattern; dlc * 8]; // Using unusued_bit_pattern for undefined bits + + for isignal in ungrouped_signals { + process_isignal_init_value(isignal, &mut bits)?; + } + + for isignal_group in grouped_signals { + for isignal in &isignal_group.isignals { + process_isignal_init_value(isignal, &mut bits)?; + } + } + + let mut init_values: Vec = Vec::new(); + let mut current_byte: u8 = 0; + let mut bit_count = 0; + + for bit in bits { + current_byte <<= 1; + if bit { + current_byte |= 1; + } + bit_count += 1; + + if bit_count == 8 { + init_values.push(current_byte); + current_byte = 0; + bit_count = 0; + } + } + if bit_count > 0 { + current_byte <<= 8 - bit_count; + init_values.push(current_byte); + } + + if !byte_order { + for init_value in init_values.iter_mut() { + *init_value = init_value.reverse_bits(); // reverse bits of each byte + } + } + + if init_values.len() != dlc { + bail!("Error creating byte array") + } + + Ok(init_values) +} + +/* + Extracts the bit value used for unused bits by the PDU and returns a bool representation. +*/ +pub fn get_unused_bit_pattern(pdu: &Element) -> bool { + // even though it needs to exist at least for ISignalIPdus, we keep it as optional, since at least one encounter shows that it might be missing. + // then use 0 as default value + let unused_bit_pattern_int = get_optional_int_value(pdu, ElementName::UnusedBitPattern); + + // supports values > 1. Just look at least significant bit + (unused_bit_pattern_int & 1) != 0 +} + +/* + Processes the Autosar FramePortRefs elements inside a CanFrameTriggering to find out the ECUs (names) that send and receive this underlying CAN frame. +*/ +pub fn process_frame_ports(can_frame_triggering: &Element, can_frame_triggering_name: &String, rx_ecus: &mut Vec, tx_ecus: &mut Vec) -> Result<()> { + if let Some(frame_ports) = can_frame_triggering.get_sub_element(ElementName::FramePortRefs) { + let frame_ports: Vec = frame_ports.sub_elements() + .filter(|se| se.element_name() == ElementName::FramePortRef) + .filter_map(|fpr| fpr.get_reference_target().ok()) + .collect(); + + for frame_port in frame_ports { + if let Some(ecu_name) = ecu_of_frame_port(&frame_port) { + if let Some(CharacterData::Enum(direction)) = frame_port + .get_sub_element(ElementName::CommunicationDirection) + .and_then(|elem| elem.character_data()) + { + match direction { + EnumItem::In => rx_ecus.push(ecu_name), + EnumItem::Out => tx_ecus.push(ecu_name), + _ => bail!("Invalid direction ID encountered in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name) + } + } else { + bail!("No CommunicationDirection encountered in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name) + } + } else { + bail!("Could not extract ECUName in FramePort. Skipping CanFrameTriggering {}", can_frame_triggering_name) ; + } + } + }/* else { + return Err(format!("FramePortRefs in CanFrameTriggering not found. Skipping CanFrameTriggering {}", can_frame_triggering_name)); + }*/ + + Ok(()) +} + +/* + Processes the Autosar InitValue element of an ISignal. Extracts one or more of them an put them into passed init_values argument. +*/ +pub fn process_init_value(init_value_elem: &mut Element, init_values: &mut InitValues, signal_name: &String) -> Result<()> { + let mut subelement = init_value_elem.get_sub_element_at(0) + .ok_or_else(|| anyhow!("Failed to obtain subelement of init_value_elem"))?; + + if subelement.element_name().eq(&ElementName::ConstantReference) { + let constant = get_required_reference( + &subelement, + ElementName::ConstantRef)?; + + *init_value_elem = constant.get_sub_element(ElementName::ValueSpec) + .ok_or_else(|| anyhow!("Failed to obtain subelement of constant"))?; + + subelement = init_value_elem.get_sub_element_at(0) + .ok_or_else(|| anyhow!("Failed to obtain subelement of init_value_elem"))?; + } + + let init_value_type = match subelement.element_name() { + ElementName::NumericalValueSpecification => 0, + ElementName::ArrayValueSpecification => 1, + _ => bail!("Unrecognized subelement {} for init-value", subelement.element_name()) + }; + + if init_value_type == 0 { + let num_val = init_value_elem.get_sub_element(ElementName::NumericalValueSpecification) + .ok_or_else(|| anyhow!("InitValue element does not have NumercialValueSpecification for signal {}", signal_name))?; + let init_value = get_required_int_value(&num_val, ElementName::Value)?; + *init_values = InitValues::Single(init_value); + + + } else { + let mut init_value_array: Vec = Vec::new(); + let num_val_elements = get_required_sub_subelement(init_value_elem, + ElementName::ArrayValueSpecification, + ElementName::Elements); + + for num_val_elem in num_val_elements?.sub_elements() { + init_value_array.push(get_required_int_value(&num_val_elem, ElementName::Value)?); + } + + *init_values = InitValues::Array(init_value_array); + } + + Ok(()) +} + +/* + -Processes an ISignalGroup element and extracts important data. + -Removes signals defined in ISignalGroup from signals HashMap (passed argument). + -Pushes the resulting self-defined ISignalGroup structure containing important data into the grouped_signals argument. +*/ +pub fn process_signal_group(signal_group: &Element, + signals: &mut HashMap, + grouped_signals: &mut Vec) -> Result<()> + { + let group_name = signal_group.item_name() + .ok_or_else(|| Error::GetItemName{item: "ISignalGroupRef"})?; + + let mut signal_group_signals: Vec = Vec::new(); + + let isignal_refs = signal_group.get_sub_element(ElementName::ISignalRefs) + .ok_or_else(|| anyhow!("Element has no sub-element"))?; + + // Removing ok and needed? + for isignal_ref in isignal_refs.sub_elements() + .filter(|elem| elem.element_name() == ElementName::ISignalRef) { + if let Some(CharacterData::String(path)) = isignal_ref.character_data() { + if let Some(siginfo) = signals.get(&path) { + let siginfo_tmp = siginfo.clone(); + let isginal_tmp: ISignal = ISignal { + name: siginfo_tmp.0, + byte_order: get_byte_order(&siginfo_tmp.1), + start_pos: siginfo_tmp.2, + length: siginfo_tmp.3, + init_values: siginfo_tmp.4 + }; + + signal_group_signals.push(isginal_tmp); + signals.remove(&path); + } + } + } + + signal_group_signals.sort_by(|a, b| a.start_pos.cmp(&b.start_pos)); + + let mut data_transformations: Vec = Vec::new(); + + if let Some(com_transformations) = signal_group + .get_sub_element(ElementName::ComBasedSignalGroupTransformations) + { + for elem in com_transformations.sub_elements() { + let data_transformation = get_required_reference(&elem, + ElementName::DataTransformationRef)?; + + let data_transformation_name = data_transformation.item_name() + .ok_or_else(|| Error::GetItemName{item: "DataTransformation"})?; + data_transformations.push(data_transformation_name); + } + } + + let mut props_vector: Vec = Vec::new(); + + if let Some(transformation_props) = signal_group.get_sub_element(ElementName::TransformationISignalPropss) { + for e2exf_props in transformation_props + .sub_elements() + .filter(|elem| elem.element_name() == ElementName::EndToEndTransformationISignalProps) + { + if let Some(e2exf_props_cond) = e2exf_props + .get_sub_element(ElementName::EndToEndTransformationISignalPropsVariants) + .and_then(|elem| elem.get_sub_element(ElementName::EndToEndTransformationISignalPropsConditional)) + { + let transformer_reference = get_required_reference(&e2exf_props_cond, + ElementName::TransformerRef)?; + + let transformer_name = transformer_reference.item_name() + .ok_or_else(|| Error::GetItemName{item: "TransformerName"})?; + + let data_ids = e2exf_props_cond + .get_sub_element(ElementName::DataIds).ok_or_else(|| anyhow!("Element has no sub-element"))?; + + let data_id = get_required_int_value(&data_ids, + ElementName::DataId)?; + + // allow optional for now + //let data_length = get_required_int_value(&e2exf_props_cond, + let data_length = get_optional_int_value(&e2exf_props_cond, + ElementName::DataLength); + + + let props_struct: E2EDataTransformationProps = E2EDataTransformationProps { + transformer_name, + data_id, + data_length + }; + + props_vector.push(props_struct); + } + } + } + + let isignal_group_struct: ISignalGroup = ISignalGroup { + name: group_name, + isignals: signal_group_signals, + data_transformations, + transformation_props: props_vector + }; + + grouped_signals.push(isignal_group_struct); + + Ok(()) +} + +/* + 1. Extract data from CanFrameTriggering structure that is later needed by restbus-simulation. + 2. Create TimedCanFrame sructure out of data and put the structure into timed_can_frames vector. + Note: Should normally only add one TimedCanFrame but multiple may be added in case multiple PDU Mappings exist for a Can frame. +*/ +pub fn get_timed_can_frame(can_frame_triggering: &CanFrameTriggering, timed_can_frames: &mut Vec) -> Result<()> { + let can_id: u32 = can_frame_triggering.can_id as u32; + let len: u8 = can_frame_triggering.frame_length as u8; + let addressing_mode: bool = can_frame_triggering.can_29_bit_addressing; + let frame_tx_behavior: bool = can_frame_triggering.frame_tx_behavior; + for pdu_mapping in &can_frame_triggering.pdu_mappings { + let mut count: u32 = 0; + let mut ival1_tv_sec: u64 = 0; + let mut ival1_tv_usec: u64 = 0; + let mut ival2_tv_sec: u64 = 0; + let mut ival2_tv_usec: u64 = 0; + let init_values: Vec; + match &pdu_mapping.pdu { + Pdu::ISignalIPdu(pdu) => { + count = pdu.number_of_repetitions as u32; + + if pdu.repetition_period_value != 0.0 { + ival1_tv_sec = pdu.repetition_period_value.trunc() as u64; + let fraction: f64 = pdu.repetition_period_value % 1.0; + ival1_tv_usec = (fraction * 1_000_000.0).trunc() as u64; + } + + if pdu.cyclic_timing_period_value != 0.0 { + ival2_tv_sec = pdu.cyclic_timing_period_value.trunc() as u64; + let fraction: f64 = pdu.cyclic_timing_period_value % 1.0; + ival2_tv_usec = (fraction * 1_000_000.0).trunc() as u64; + } + + init_values = extract_init_values(pdu.unused_bit_pattern, + &pdu.ungrouped_signals, + &pdu.grouped_signals, + pdu_mapping.length, + &pdu_mapping.byte_order)?; + } + Pdu::NmPdu(pdu) => { + ival2_tv_usec = 100000; // every 100 ms + init_values = extract_init_values(pdu.unused_bit_pattern, + &pdu.ungrouped_signals, + &pdu.grouped_signals, + pdu_mapping.length, + &pdu_mapping.byte_order)?; + } + } + + let ival1 = timeval { tv_sec: ival1_tv_sec as TimevalNum, tv_usec: ival1_tv_usec as TimevalNum}; + let ival2 = timeval { tv_sec: ival2_tv_sec as TimevalNum, tv_usec: ival2_tv_usec as TimevalNum}; + + let ivals: Vec = vec![ival1, ival2]; + + timed_can_frames.push(create_time_can_frame_structure(count, &ivals, can_id, len, addressing_mode, frame_tx_behavior, &init_values)); + } + Ok(()) +} + +/* + 1. Find CanFrameTriggering structure based on CAN id. + 2. Put its important data as TimedCanFrame structure into timed_can_frames vector. +*/ +pub fn get_timed_can_frame_from_id(can_clusters: &HashMap, bus_name: String, can_id: u64) -> Result> { + let mut timed_can_frames: Vec = Vec::new(); + + if let Some(can_cluster) = can_clusters.get(&bus_name) { + if let Some(can_frame_triggering) = can_cluster.can_frame_triggerings.get(&can_id) { + get_timed_can_frame(can_frame_triggering, &mut timed_can_frames)?; + } + } + + Ok(timed_can_frames) +} + +/* + 1. Iterate over all CanFrameTriggerings belonging to a CanCluster structure. + 2. Put all CanFrameTriggering important data as TimedCanFrame structures into timed_can_frames vector. +*/ +pub fn get_timed_can_frames_from_bus(can_clusters: &HashMap, bus_name: String) -> Result> { + let mut timed_can_frames: Vec = Vec::new(); + + if let Some(can_cluster) = can_clusters.get(&bus_name) { + for can_frame_triggering in can_cluster.can_frame_triggerings.values() { + get_timed_can_frame(can_frame_triggering, &mut timed_can_frames)? + } + } + + Ok(timed_can_frames) +} + +pub fn load_serialized_data(file_name: &String) -> Result> { + let mut file = File::open(file_name.to_owned() + ".ser")?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let deserialized: HashMap = serde_json::from_str(&contents)?; + + Ok(deserialized) +} + +pub fn store_serialized_data(file_name: &String, can_clusters: &HashMap) -> Result<()> { + let serialized = serde_json::to_string(can_clusters)?; + + let mut file = File::create(file_name.to_owned() + ".ser")?; + file.write_all(serialized.as_bytes())?; + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error<'a> { + #[error("Failed to get required item name of '{item}'")] + GetItemName { item: &'a str }, +} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/src/lib.rs b/opendut-edgar/restbus-simulation/src/lib.rs new file mode 100644 index 00000000..573baf1c --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/lib.rs @@ -0,0 +1,6 @@ +pub mod arxml_parser; +pub mod arxml_structs; +pub mod arxml_utils; +pub mod restbus_simulation; +pub mod restbus_utils; +pub mod restbus_structs; diff --git a/opendut-edgar/restbus-simulation/src/main.rs b/opendut-edgar/restbus-simulation/src/main.rs new file mode 100755 index 00000000..eee8f9d5 --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/main.rs @@ -0,0 +1,152 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; +use std::thread; +use std::time::Duration; + +mod arxml_parser; +mod arxml_structs; +mod arxml_utils; +mod restbus_simulation; +mod restbus_utils; +mod restbus_structs; + +use arxml_parser::*; +use arxml_structs::*; +use arxml_utils::*; +use restbus_simulation::*; +use restbus_structs::TimedCanFrame; + + +/* + Debug method. Combines the CAN ID and intial values as hex of a PDU. Returns this as string. +*/ +fn get_pdu_hex(can_id: &u64, init_values: &Vec) -> String { + let mut hex_string = String::new(); + hex_string.push_str(&format!("{}:", can_id)); + for element in init_values { + hex_string.push_str(&format!("{:02X}", element)); + } + println!("Values: {}", hex_string); + + hex_string +} + + +/* + Debug method. Iterates over all PDUs of the target bus and returns all combinations of CAN IDs and initial values as string. +*/ +fn collect_pdus(can_clusters: &HashMap, bus_name: String) -> Vec { + let mut init_values_strings: Vec = Vec::new(); + if let Some(can_cluster) = can_clusters.get(&bus_name) { + for can_frame_triggering in can_cluster.can_frame_triggerings.values() { + for pdu_mapping in &can_frame_triggering.pdu_mappings { + match &pdu_mapping.pdu { + Pdu::ISignalIPdu(pdu) => { + let init_values = extract_init_values(pdu.unused_bit_pattern, + &pdu.ungrouped_signals, + &pdu.grouped_signals, + pdu_mapping.length, + &pdu_mapping.byte_order).unwrap(); + init_values_strings.push(get_pdu_hex(&can_frame_triggering.can_id, &init_values)) + } + Pdu::NmPdu(pdu) => { + let init_values = extract_init_values(pdu.unused_bit_pattern, + &pdu.ungrouped_signals, + &pdu.grouped_signals, + pdu_mapping.length, + &pdu_mapping.byte_order).unwrap(); + init_values_strings.push(get_pdu_hex(&can_frame_triggering.can_id, &init_values)) + } + } + } + } + } + init_values_strings +} + + +/* + Play a single CAN frame from the target bus periodcically (if it is periodic) to the bus to which ifname is connected to. +*/ +fn test_find_frame_and_play(restbus_simulation: &RestbusSimulation, ifname: &String, can_clusters: &HashMap, bus_name: String, can_id: u64) { + let timed_can_frames: Vec = get_timed_can_frame_from_id(can_clusters, bus_name, can_id).unwrap(); + + match restbus_simulation.play_all(&timed_can_frames, ifname) { + Ok(_val) => println!("Successfully sent message with can id {}", can_id), + Err(error) => println!("Could not send message with can id {} because: {}", can_id, error), + } + +} + + +/* + Play all CAN frames from the target bus periodcically (only periodic frames are sent periodically) to the bus to which ifname is connected to. +*/ +fn test_bus_play_all(restbus_simulation: &RestbusSimulation, ifname: &String, can_clusters: &HashMap, bus_name: String) { + let timed_can_frames: Vec = get_timed_can_frames_from_bus(can_clusters, bus_name).unwrap(); + + match restbus_simulation.play_all(&timed_can_frames, ifname) { + Ok(_val) => println!("Successfully established restbus simulation"), + Err(error) => println!("Could not establish restbus simulation because: {}", error) + } +} + + +/* + This shows an example of how to create a restbus-simulation with an ARXML file. +*/ +fn main() -> std::io::Result<()> { + println!("[+] Starting openDuT ARXML parser over main method."); + + // Define file name. It is used as a path. + let file_name = "system-4.2.arxml"; // from https://github.com/cantools/cantools/blob/master/tests/files/arxml/system-4.2.arxml + + // Create ArxmlParser struct, which contains all the parsing methods. + let arxml_parser: ArxmlParser = ArxmlParser {}; + + // Parse the ARXML file. Use serialized file if it exists. Parsed data is stored in can cluster ARXML element representations. + if let Ok(can_clusters) = arxml_parser + .parse_file(&file_name.to_string(), true) + { + let bus_name = "Cluster0"; + let target_file = "system.txt"; + let play_single_or_all = true; + let ifname = String::from("vcan0"); + + // Debug output + for cluster in can_clusters.values() { + println!("CanCluster: {cluster:?}"); + } + + // Debug output + let mut frames = String::new(); + for frame in collect_pdus(&can_clusters, String::from(bus_name)) { + frames.push_str(frame.as_str()); + frames.push('\n'); + } + + // Debug. Found PDUs of target bus are written to file. + let mut f = File::create(target_file)?; + f.write_all(frames.as_bytes())?; + + println!("Trying to setup up restbus simulation"); + + // Create RestbusSimulation structure, which contains all the relevant methods for the restbus simulation. + let restbus_simulation: RestbusSimulation = RestbusSimulation {}; + + if !play_single_or_all { + // Play a single CAN frame from the target bus periodcically (if it is periodic) to the bus to which ifname is connected to. + test_find_frame_and_play(&restbus_simulation, &ifname, &can_clusters, String::from(bus_name), 0x3E9); + + } else { + // Play all CAN frames from the target bus periodcically (only periodic frames are sent periodically) to the bus to which ifname is connected to. + test_bus_play_all(&restbus_simulation, &ifname, &can_clusters, String::from(bus_name)); + } + + // Sleep as long as restbus-simulation should run. + thread::sleep(Duration::from_secs(300)); + } + Ok(()) +} + diff --git a/opendut-edgar/restbus-simulation/src/restbus_simulation.rs b/opendut-edgar/restbus-simulation/src/restbus_simulation.rs new file mode 100755 index 00000000..40ec96e3 --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/restbus_simulation.rs @@ -0,0 +1,71 @@ +/* + Restbus simulation that makes use of the structures parsed by the ARXML parser. Makes use of the Linux Kernel CAN Broadcast Manager. + Ideas for improvement: + - Be able to manually add stuff to restbus -> provide interface +*/ + +use crate::restbus_utils::*; +use crate::restbus_structs::*; + +use nix::libc::timeval; + + +pub struct RestbusSimulation { +} + +impl RestbusSimulation { + /* + 1. Creates a BCM socket. + 2. Converts all TimedCanFrames into regular CAN frames and puts each CAN frame into a BCM struct. + 3. All created BCM structs are written to the BCM socket (sent to the Broadcast Manager). + */ + pub fn play_all(&self, timed_can_frames: &Vec, ifname: &String) -> Result<(), String> { + let sock = create_socket()?; + + connect_socket(sock, ifname)?; + + for timed_can_frame in timed_can_frames { + let mut write_bytes: Vec = Vec::new(); + + let can_frames: Vec = vec![ + create_can_frame_structure(timed_can_frame.can_id, timed_can_frame.len, timed_can_frame.addressing_mode, timed_can_frame.frame_tx_behavior, &timed_can_frame.data_vector)]; + + let ival1 = timeval { tv_sec: timed_can_frame.ival1.tv_sec as TimevalNum, tv_usec: timed_can_frame.ival1.tv_usec as TimevalNum }; + let ival2 = timeval { tv_sec: timed_can_frame.ival2.tv_sec as TimevalNum, tv_usec: timed_can_frame.ival2.tv_usec as TimevalNum }; + + create_bcm_structure_bytes(timed_can_frame.count, ival1, ival2, timed_can_frame.can_id, timed_can_frame.frame_tx_behavior, &can_frames, &mut write_bytes); + + write_socket(sock, &write_bytes)?; + } + + Ok(()) + } + + /*pub fn play_single_bcm_frame(&self, ifname: &String, count: u32, ival1_tv_sec: i64, ival1_tv_usec: i64, + ival2_tv_sec: i64, ival2_tv_usec: i64, can_id: u32, len: u8, addressing_mode: bool, data_vector: &Vec) -> Result + { + + let sock = create_socket()?; + + connect_socket(sock, ifname)?; + + let mut write_bytes: Vec = Vec::new(); + + let mut can_frames: Vec = Vec::new(); + + can_frames.push(create_can_frame_structure(can_id, len, addressing_mode, data_vector)); + + create_bcm_structure_bytes(count, ival1_tv_sec, ival1_tv_usec, ival2_tv_sec, ival2_tv_usec, can_id, &can_frames, &mut write_bytes); + + println!("write byte is {}", write_bytes.len()); + for byte in &write_bytes { + print!("{:02x} ", byte); + } + println!(""); + + write_socket(sock, &write_bytes, write_bytes.len())?; + + return Ok(true); + + }*/ +} \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/src/restbus_structs.rs b/opendut-edgar/restbus-simulation/src/restbus_structs.rs new file mode 100755 index 00000000..fe6dab7d --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/restbus_structs.rs @@ -0,0 +1,112 @@ +use nix::libc::timeval; + + +/* + Header of messages sent to or received from the BCM.. + Same as in https://github.com/linux-can/can-utils/blob/master/include/linux/can/bcm.h. +*/ +#[repr(C)] +#[derive(Debug)] +pub struct BcmMsgHead { + pub opcode: u32, + pub flags: u32, + pub count: u32, + pub ival1: timeval, + pub ival2: timeval, + pub can_id: u32, + pub nframes: u32, +} + +/* + Enum of all supported Can frames. Currently, only Can-20 and Can-FD are supported. + */ +#[derive(Debug)] +pub enum CanFrame { + Can20Frame(Can20Frame), + CanFdFrame(CanFdFrame) +} + +/* + A Can-20 Frame. Same as in https://github.com/linux-can/can-utils/blob/master/include/linux/can.h. +*/ +#[repr(C, packed)] +#[derive(Debug)] +pub struct Can20Frame { + pub can_id: u32, + pub len: u8, + pub __pad: u8, + pub __res0: u8, + pub __res1: u8, + pub data: [u8; 8], +} + +/* + A Can-Fd Frame. Same as in https://github.com/linux-can/can-utils/blob/master/include/linux/can.h. +*/ +#[repr(C, packed)] +#[derive(Debug)] +pub struct CanFdFrame { + pub can_id: u32, + pub len: u8, + pub flags: u8, + pub __res0: u8, + pub __res1: u8, + pub data: [u8; 64], +} + +/* + A structure holding can frame information including timing, which is later used to create Can frames sent to the BCM. +*/ +#[derive(Debug)] +pub struct TimedCanFrame { + pub can_id: u32, + pub len: u8, + pub addressing_mode: bool, + pub frame_tx_behavior: bool, + pub data_vector: Vec, + pub count: u32, + pub ival1: timeval, + pub ival2: timeval, +} + +/* + Opcodes defining the operation that the BCM should do. Same as in https://github.com/linux-can/can-utils/blob/master/include/linux/can/bcm.h. +*/ +pub enum Opcode { + TxSetup = 1, +/* TxDelete, + TxRead, + TxSend, + RxSetup, + RxDelete, + RxRead, + TxStatus, + TxExpired, + RxStatus, + RxTimeout, + RxChanged*/ +} + +/* + BCM flags used in messages sent to the BCM. Same as in https://github.com/linux-can/can-utils/blob/master/include/linux/can/bcm.h. +*/ +pub enum BCMFlags { + SetTimer = 0x0001, + StartTimer = 0x0002, +/* TxCountEvt = 0x0004, + TxAnnounce = 0x0008, + TxCpCanId = 0x0010, + RxFilterId = 0x0020, + RxCheckDlc = 0x0040, + RxNoAutotimer = 0x0080, + RxAnnounceResume = 0x0100, + TxResetMultiIdx = 0x0200, + RxRtrFrame = 0x0400,*/ + CanFdFrame = 0x0800 +} + +#[cfg(target_pointer_width = "64")] +pub type TimevalNum = i64; + +#[cfg(target_pointer_width = "32")] +pub type TimevalNum = i32; \ No newline at end of file diff --git a/opendut-edgar/restbus-simulation/src/restbus_utils.rs b/opendut-edgar/restbus-simulation/src/restbus_utils.rs new file mode 100755 index 00000000..e46fba80 --- /dev/null +++ b/opendut-edgar/restbus-simulation/src/restbus_utils.rs @@ -0,0 +1,200 @@ +/* + HELPER METHODS just for restbus-simulation +*/ + +use crate::restbus_structs::*; + +use std::{mem, slice}; +use std::io::Error; +use std::os::raw::c_void; +use std::ffi::CString; + +use nix::libc::{__c_anonymous_sockaddr_can_can_addr, __c_anonymous_sockaddr_can_tp, c_int, connect, if_nametoindex, sockaddr, sockaddr_can, socket, socklen_t, timeval, write, AF_CAN, CAN_BCM, CAN_EFF_FLAG, SOCK_DGRAM}; + +/* + Create a socket using libc's socket function. This is required since not Rust equivalent library or method exists for establishing a BCM CAN socket +*/ +pub fn create_socket() -> Result { + let sock = unsafe { + socket(AF_CAN, SOCK_DGRAM, CAN_BCM) + }; + + if sock < 0 { + return Err(format!("Could not create socket due to {}", Error::last_os_error())); + } + + Ok(sock) +} + +/* + 1. Get the interface index from the interface string + 2. Setup libc sockaddr_can structure + 3. Connect the existing socket +*/ +pub fn connect_socket(sock: i32, ifname: &String) -> Result { + let ifindex = unsafe { + if let Ok(c_ifname) = CString::new(ifname.as_str()) { + if_nametoindex(c_ifname.as_ptr()) + } else { + return Err(format!("Could not get ifindex from {}", ifname)); + } + }; + + let sockaddr: sockaddr_can = sockaddr_can { + can_family: AF_CAN as u16, + can_ifindex: ifindex as i32, + can_addr: __c_anonymous_sockaddr_can_can_addr { + tp: __c_anonymous_sockaddr_can_tp { + rx_id: 0, + tx_id: 0 + } + } + }; + + let sockaddr_ptr = &sockaddr as *const sockaddr_can as *const sockaddr; + + let connect_res = unsafe { + connect(sock, sockaddr_ptr, mem::size_of::<&sockaddr_can>() as socklen_t) + }; + + if connect_res < 0 { + return Err(format!("Could not connect socket due to {}", Error::last_os_error())); + } + + Ok(connect_res) +} + +/* + Writes to existing socket +*/ +pub fn write_socket(sock: i32, buf: &[u8]) -> Result { + let wres = unsafe { + write( + sock as c_int, + buf.as_ptr() as *const c_void, + buf.len() + ) + }; + if wres < 0 { + return Err(format!("Could not write to socket due to {}", Error::last_os_error())); + } + + Ok(wres) +} + +/* + Fills u8 values from a vector into an array +*/ +fn fill_data_array(data: &mut [u8], data_vector: &[u8]) { + let mut i = 0; + while i < data_vector.len() { + data[i] = data_vector[i]; + i += 1; + } +} + +/* + Creates a self-defined CanFrame structure that is either a CanFdFrame or a Can20Frame + */ +pub fn create_can_frame_structure(can_id: u32, len: u8, addressing_mode: bool, frame_tx_behavior: bool, data_vector: &[u8]) -> CanFrame { + let eflag: u32 = if addressing_mode { CAN_EFF_FLAG } else { 0 }; + + if frame_tx_behavior { + let mut data: [u8; 64] = [0; 64]; + + fill_data_array(&mut data, data_vector); + + CanFrame::CanFdFrame( CanFdFrame { + can_id: can_id | eflag, + len, + flags: 0, // are there any relevant flags? + __res0: 0, + __res1: 0, + data, + }) + } else { + let mut data: [u8; 8] = [0; 8]; + + fill_data_array(&mut data, data_vector); + + CanFrame::Can20Frame( Can20Frame { + can_id: can_id | eflag, + len, + __pad: 0, + __res0: 0, + __res1: 0, + data, + }) + } +} + +/* + Creates a self-defined TimedCanFrame structure that holds the necessary data used for creating a CanFrame later +*/ +pub fn create_time_can_frame_structure(count: u32, ivals: &[timeval], can_id: u32, len: u8, addressing_mode: bool, frame_tx_behavior: bool, data_vector: &Vec) -> TimedCanFrame { + let mut copy_data_vector: Vec = Vec::new(); + + for data in data_vector { + copy_data_vector.push(*data); + } + + TimedCanFrame { + can_id, + len, + addressing_mode, + frame_tx_behavior, + data_vector: copy_data_vector, + count, + ival1: ivals[0], + ival2: ivals[1] + } +} + +/* + Creates a BcmMsgHead structure, which is a header of messages send to/from the CAN Broadcast Manager + See also https://github.com/linux-can/can-utils/blob/master/include/linux/can/bcm.h +*/ +pub fn create_bcm_head(count: u32, ival1: timeval, ival2: timeval, can_id: u32, frame_tx_behavior: bool, frames: &[CanFrame]) -> BcmMsgHead { + let mut canfd_flag: u32 = 0x0; + + if frame_tx_behavior { + canfd_flag = BCMFlags::CanFdFrame as u32; + } + BcmMsgHead { + opcode: Opcode::TxSetup as u32, + flags: BCMFlags::SetTimer as u32 | BCMFlags::StartTimer as u32 | canfd_flag, + count, + ival1, + ival2, + can_id, + nframes: frames.len() as u32, + } +} + +/* + Converts a BcmMsgHead structure and the payload (which are CanFrames) to a byte representation. + The write_bytes vector is filled with the bytes and can be then later be used by the caller. +*/ +pub fn create_bcm_structure_bytes(count: u32, ival1: timeval, ival2: timeval, can_id: u32, frame_tx_behavior: bool, frames: &Vec, write_bytes: &mut Vec) { + let head: BcmMsgHead = create_bcm_head(count, ival1, ival2, can_id, frame_tx_behavior, frames); + + let ptr: *const u8 = &head as *const BcmMsgHead as *const u8; + let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, mem::size_of::()) }; + + write_bytes.extend_from_slice(bytes); + + for frame in frames { + match frame { + CanFrame::Can20Frame(can20_frame) => { + let ptr: *const u8 = can20_frame as *const Can20Frame as *const u8; + let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, mem::size_of::()) }; + write_bytes.extend_from_slice(bytes); + } + CanFrame::CanFdFrame(canfd_frame) => { + let ptr: *const u8 = canfd_frame as *const CanFdFrame as *const u8; + let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr, mem::size_of::()) }; + write_bytes.extend_from_slice(bytes); + } + } + } +} \ No newline at end of file diff --git a/opendut-edgar/src/service/start.rs b/opendut-edgar/src/service/start.rs index 7c19ca8c..5cfc2cf6 100644 --- a/opendut-edgar/src/service/start.rs +++ b/opendut-edgar/src/service/start.rs @@ -8,6 +8,9 @@ use anyhow::Context; use opendut_carl_api::proto::services::peer_messaging_broker; use opendut_carl_api::proto::services::peer_messaging_broker::downstream::Message; use opendut_carl_api::proto::services::peer_messaging_broker::{ApplyPeerConfiguration, TracingContext}; +use opendut_restbus_simulation::arxml_parser::ArxmlParser; +use opendut_restbus_simulation::arxml_utils::get_timed_can_frames_from_bus; +use opendut_restbus_simulation::restbus_simulation::RestbusSimulation; use opendut_types::peer::configuration::{OldPeerConfiguration, PeerConfiguration}; use opendut_types::peer::PeerId; use opendut_util::settings::LoadedConfig; @@ -119,6 +122,33 @@ pub async fn run_stream_receiver( }, } }; + + let restbus_simulation: RestbusSimulation = RestbusSimulation {}; + + let restbus_simulation_enabled = settings.config.get::("restbus-simulation.enabled")?; + + if restbus_simulation_enabled { + let arxml_file_path = settings.config.get::("restbus-simulation.arxml.path")?; + let arxml_serialization = settings.config.get::("restbus-simulation.arxml.serialization")?; + let target_cluster = settings.config.get::("restbus-simulation.simulation.target.cluster")?; + let ifname = settings.config.get::("restbus-simulation.simulation.interface")?; + + let arxml_parser: ArxmlParser = ArxmlParser {}; + + if let Ok(can_clusters) = arxml_parser.parse_file(&arxml_file_path, arxml_serialization) { + // Play all CAN frames from the target bus periodcically (only periodic frames are sent periodically) to the bus to which ifname is connected to. + match get_timed_can_frames_from_bus(&can_clusters, target_cluster) { + Ok(timed_can_frames) => { + if let Err(err) = restbus_simulation.play_all(&timed_can_frames, &ifname) { + error!("Failed to start restbus simulation: {err}") + } + }, + Err(err) => error!("Failed to start restbus simulation - error while extracting timed CAN frames from ARXML: {err}"), + } + + info!("Successfully established restbus simulation"); + } + } let remote_address = vpn::retrieve_remote_host(&settings).await?;