commit 4b5fbb6c230f1dd654d0f1c10982f965642d4f50 Author: Dario Di Francesco Date: Thu Aug 27 11:12:22 2026 +0200 S03-H3 CLOSED baseline diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4920090 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# ============================================================ +# MARTe Benchmark Suite - Git exclusions +# ============================================================ + +# Runtime execution outputs. +# Every MARTe benchmark run generates its own directory here. +# These outputs are operational evidence, not source code. +runs/ + +# Python bytecode/cache +__pycache__/ +*.py[cod] + +# Editor temporary files +*~ +*.swp +*.swo + +# Generic temporary files +*.tmp + +# Desktop metadata +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac54991 --- /dev/null +++ b/README.md @@ -0,0 +1,738 @@ +# MARTe Benchmark Suite + +## Overview + +`marte-benchmark-suite` is a validation and benchmarking infrastructure +developed around the MARTe2 real-time framework. + +The project is designed to build a sequence of progressively more advanced +test scenarios while preserving known-good baselines from the previous stages. + +The suite does not modify MARTe2 itself. + +Instead, it provides: + +- MARTe configuration files; +- launchers; +- configuration patchers; +- runtime orchestration; +- output parsers; +- structural validators; +- semantic validators; +- execution evidence. + +The development philosophy is incremental: + +> each new stage introduces one new capability while preserving the validated +> behaviour of the previous stages. + +--- + +# Current project status + +Current validated development baseline: + +```text +S03-H3 +STATUS=CLOSED +BASELINE=KNOWN-GOOD +``` + +S03-H3 is currently the authoritative baseline on which the next stages of the +benchmark suite are intended to be built. + +The next planned stages are: + +```text +S04 → Repeatability / Multi-Run +S05 → Deterministic Parametric Generator +S06 → MARTe2 Core Scaling +``` + +These stages are not part of the current S03-H3 baseline unless explicitly +implemented and accepted later. + +--- + +# Project purpose + +The suite is intended to answer increasingly complex questions about a MARTe2 +application. + +Conceptually: + +```text +S00 +Can MARTe2 execute the basic reference application? + ↓ +S01 +Can execution timing information be observed? + ↓ +S02 +Can deterministic binary data be recorded? + ↓ +S03 +Can the recorded data be checked semantically? + ↓ +S03-H1 +Can different execution/debug profiles be supported? + ↓ +S03-H3 +Can those profiles be formalized and validated +against an authoritative runtime contract? + ↓ +S04 +Does the same valid workload remain correct +when executed repeatedly? + ↓ +S05 +Can deterministic test workloads be generated +from parameters? + ↓ +S06 +How does MARTe2 Core behave as workload complexity grows? +``` + +--- + +# High-level architecture + +The benchmark suite is layered around the installed MARTe2 runtime. + +```text + +------------------+ + | MARTe2 | + | MARTeApp.ex | + +---------+--------+ + ^ + | + common/marte_runner.sh + ^ + | + launcher/ + ^ + | + scenario / configuration + | + v + MARTe execution + | + v + RUN DIRECTORY + | + +------------+-------------+ + | | + v v + runtime logs output files + | | + +------------+-------------+ + | + v + parser / validator + | + v + PASS / FAIL result +``` + +MARTe2 and MARTe2-components remain external dependencies. + +The benchmark suite orchestrates and validates them but is intentionally kept +separate from their source trees. + +--- + +# Repository structure + +Current source structure: + +```text +marte-benchmark-suite/ +│ +├── common/ +│ ├── environment.sh +│ ├── marte_runner.sh +│ └── paths.sh +│ +├── configurations/ +│ └── generated/ +│ ├── S00/ +│ ├── S01/ +│ ├── S02/ +│ ├── S03/ +│ ├── S03H1/ +│ └── S03H3/ +│ +├── launcher/ +│ ├── marte_benchmark_launcher.sh +│ ├── marte_benchmark_launcher_s03h1.sh +│ ├── marte_benchmark_launcher_s03h3.sh +│ └── marte_benchmark_menu.sh +│ +├── patchers/ +│ └── configuration/ +│ +├── scenarios/ +│ ├── S01_timing_display/ +│ ├── S02_binary_recording/ +│ ├── S03_semantic_sentinels/ +│ ├── S03H1_execution_profiles/ +│ └── S03H3_execution_profiles/ +│ +└── runs/ +``` + +`runs/` contains execution outputs and is intentionally excluded from normal +Git version control. + +The configurations under `configurations/generated/` are NOT generic build +artifacts. They represent configuration baselines produced by controlled +project stages and are therefore versioned. + +--- + +# Common runtime layer + +## `common/paths.sh` + +Centralizes the paths used by the benchmark infrastructure. + +Its purpose is to avoid hard-coding the same MARTe and project locations in +multiple scripts. + +--- + +## `common/environment.sh` + +Defines the common execution environment used by the benchmark scripts. + +Keeping environment handling in one place improves reproducibility and avoids +different scenarios silently using different runtime assumptions. + +--- + +## `common/marte_runner.sh` + +Provides the common MARTe execution mechanism shared by benchmark stages. + +Conceptually: + +```text +launcher + ↓ +common runner + ↓ +MARTeApp.ex + ↓ +run directory + ↓ +logs / outputs +``` + +Higher-level stages should reuse the authoritative runner rather than create +alternative MARTe execution paths unless a future specification explicitly +authorizes such a change. + +--- + +# Launchers + +The `launcher/` directory contains the user-facing execution layer. + +The project currently includes launchers from different development stages, +including the S03-H1 and S03-H3 generations. + +The current S03-H3 architecture supports execution profiles such as: + +```text +MINIMAL +TEST +DIAGNOSTIC +``` + +The profiles allow the same benchmark infrastructure to expose different +levels of runtime/debug functionality without changing the fundamental +correctness contract. + +--- + +# Patchers + +Configuration patchers live under: + +```text +patchers/configuration/ +``` + +They transform an already-known configuration into the configuration required +by the next development stage. + +The dependency chain currently visible in the repository is: + +```text +S00 + ↓ +build_s01_from_s00.sh + ↓ +S01 + ↓ +build_s02_from_s01.sh + ↓ +S02 + ↓ +build_s03_from_s02.sh + ↓ +S03 +``` + +Additional controlled transformations support S03-H1 and S03-H3. + +This model helps preserve provenance: + +```text +new configuration += +known parent ++ +explicit transformation +``` + +rather than independently rewriting every configuration from scratch. + +--- + +# Validation layer + +The suite does not consider a MARTe process exit by itself sufficient proof +that a test succeeded. + +Validation is layered. + +Depending on the stage, the suite can inspect: + +```text +configuration structure + + +runtime execution + + +generated files + + +semantic content + + +expected sentinels +``` + +and only then derive the corresponding result. + +--- + +# S00 — Core reference + +S00 provides the basic MARTe2 reference configuration: + +```text +configurations/generated/S00/smoke_s0_core.marte +``` + +It acts as the root from which later benchmark configurations are progressively +derived. + +--- + +# S01 — Timing + +S01 introduces timing-related observation on top of the S00 reference +configuration. + +Relevant current files include: + +```text +patchers/configuration/build_s01_from_s00.sh +configurations/generated/S01/from_patcher/ +scenarios/S01_timing_display/validate.sh +``` + +S01 established infrastructure that later stages could reuse rather than +reimplement. + +--- + +# S02 — Binary recording + +S02 extends the previous stage with binary data recording. + +Relevant components include: + +```text +patchers/configuration/build_s02_from_s01.sh + +configurations/generated/S02/from_patcher/ + smoke_s2_binary_recording_from_s01.marte + +scenarios/S02_binary_recording/validate.sh +``` + +Binary recording provides machine-readable data that later stages can parse +and validate. + +--- + +# S03 — Semantic validation + +S03 adds semantic verification of the generated data. + +Relevant components include: + +```text +patchers/configuration/build_s03_from_s02.sh + +configurations/generated/S03/from_patcher/ + smoke_s3_semantic_sentinels_from_s02.marte + +scenarios/S03_semantic_sentinels/ + parse_s03_binary.py + validate.sh +``` + +The key conceptual change is: + +```text +"data file exists" +``` + +is not enough. + +The project also checks whether the data means what the test expected it to +mean. + +--- + +# S03-H1 — Execution profiles + +S03-H1 introduced explicit execution profiles and supporting validation +infrastructure. + +Current repository elements include: + +```text +launcher/marte_benchmark_launcher_s03h1.sh + +patchers/configuration/ + build_s03h1_from_s02.sh + +scenarios/S03H1_execution_profiles/ + export_s03_csv.py + validate.sh + verify_s03h1_config.py +``` + +The historical S03-H1 configurations currently retained are: + +```text +TEST +DIAGNOSTIC +``` + +They remain important because S03-H3 intentionally reuses validated historical +configurations where appropriate instead of duplicating them unnecessarily. + +--- + +# S03-H3 — Current known-good baseline + +S03-H3 is the current closed benchmark stage. + +Its role is to formalize the execution-profile architecture and protect the +runtime contract used by future development. + +Relevant repository elements include: + +```text +launcher/ + marte_benchmark_launcher_s03h3.sh + marte_benchmark_menu.sh + +patchers/configuration/ + build_s03h3_minimal_from_s03h1_test.sh + +configurations/generated/S03H3/from_patcher/ + smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte + +scenarios/S03H3_execution_profiles/ + validate.sh + verify_s03h3_minimal_structure.py +``` + +S03-H3 defines three go-forward execution profiles: + +```text +MINIMAL +TEST +DIAGNOSTIC +``` + +The MINIMAL configuration is derived from the validated S03-H1 TEST +configuration by explicitly removing unnecessary display functionality while +preserving the required execution behaviour. + +TEST and DIAGNOSTIC reuse their validated historical configurations. + +This avoids unnecessary duplication and reduces the number of independent +configuration variants that must be trusted. + +--- + +# Why S03-H3 matters + +Before advancing toward larger benchmark campaigns, the project needs one +well-understood baseline. + +S03-H3 provides that reference point. + +Conceptually: + +```text +KNOWN INPUT + ↓ +KNOWN CONFIGURATION + ↓ +KNOWN LAUNCHER + ↓ +KNOWN RUNTIME DEPENDENCIES + ↓ +KNOWN VALIDATION + ↓ +KNOWN-GOOD RESULT +``` + +Future stages should extend this baseline rather than silently redefine it. + +--- + +# Runtime dependencies + +The benchmark suite depends on an external MARTe installation. + +The current runtime environment includes: + +```text +MARTe2 +MARTe2-components +``` + +Important runtime objects include MARTe executables/libraries and component +libraries required by the active configurations. + +These external projects are not copied into this repository. + +This repository therefore acts as: + +```text +benchmark source + configuration + validation infrastructure +``` + +and not as a complete binary backup of MARTe. + +A separate verified restore mechanism is required for complete disaster +recovery of the known-good runtime environment. + +--- + +# Run directories + +Runtime execution artifacts are written under: + +```text +runs/ +``` + +A run directory may contain logs, generated data and validation evidence. + +These files can be large and are produced repeatedly. + +For this reason: + +```text +runs/ +``` + +is intentionally excluded through `.gitignore`. + +Git is used for source/configuration history. + +Runtime evidence and full environment recovery are handled separately. + +--- + +# Git versus full restore backup + +The Git repository and the full restore point serve different purposes. + +```text +Git +│ +├── scripts +├── configurations +├── validators +├── parsers +├── documentation +└── development history +``` + +while a complete restore point additionally needs the external runtime +environment required to reproduce the validated MARTe execution. + +Therefore: + +```text +Git backup ≠ complete runtime restore point +``` + +Both mechanisms are complementary. + +--- + +# Development governance + +The project follows a controlled development process. + +A typical new stage follows approximately: + +```text +DESIGN / SPECIFICATION + ↓ +independent technical review + ↓ +technical arbitration + ↓ +human freeze + ↓ +implementation + ↓ +tests + ↓ +independent implementation review + ↓ +technical arbitration + ↓ +human acceptance + ↓ +new known-good baseline +``` + +A development-stage implementation is therefore not automatically considered +part of the authoritative baseline simply because code exists. + +--- + +# Planned roadmap + +## S04 — Repeatability / Multi-Run + +Main question: + +> Does the same already-valid S03-H3 workload remain correct when executed +> repeatedly? + +Conceptually: + +```text +S03-H3 run #1 → result +S03-H3 run #2 → result +S03-H3 run #3 → result +... +S03-H3 run #N → result + ↓ +campaign-level repeatability result +``` + +S04 is primarily a correctness/repeatability stage. + +It is not intended to turn repeatability percentages or CycleTime observations +into performance claims. + +--- + +## S05 — Deterministic Parametric Generator + +Main question: + +> Can test workloads be generated automatically from controlled parameters +> while remaining reproducible? + +Conceptually: + +```text +parameters ++ +seed + ↓ +generator + ↓ +deterministic MARTe scenario +``` + +The same inputs should regenerate the same intended scenario. + +--- + +## S06 — MARTe2 Core Scaling + +Main question: + +> How does the MARTe2 Core workload behave as controlled complexity increases? + +S06 is expected to reuse the capabilities established by the preceding stages: + +```text +S03-H3 → correctness baseline +S04 → repeatable execution +S05 → deterministic workload generation +S06 → controlled scaling study +``` + +--- + +# Current boundary + +At the time of this README baseline: + +```text +S03-H3 = CLOSED / KNOWN-GOOD + +S04 = next development target +S05 = planned +S06 = planned +``` + +The existence of this roadmap must not be interpreted as evidence that S04, +S05 or S06 have already been implemented or validated. + +--- + +# Repository philosophy + +The repository should remain: + +```text +small +reviewable +deterministic +traceable +reproducible +``` + +Generated runtime outputs should not be mixed with source history. + +Known-good configurations, however, may intentionally be versioned even when +they were produced by controlled patchers, because they are part of the +validated benchmark baseline. + +--- + +# License and distribution + +The licensing and distribution policy for this benchmark suite must be defined +by the project owners before public redistribution. + +MARTe2 and MARTe2-components retain their respective upstream licensing and +ownership terms. diff --git a/common/environment.sh b/common/environment.sh new file mode 100644 index 0000000..d803a59 --- /dev/null +++ b/common/environment.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +# Determina la directory common indipendentemente dalla directory corrente. +MARTE_COMMON_DIR="$( + cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && + pwd +)" + +# Carica i percorsi canonici della benchmark suite. +source "$MARTE_COMMON_DIR/paths.sh" + +# Directory contenenti le librerie richieste dagli scenari S00, S01 e S02. +MARTE2_CORE_LIB_DIR="$MARTE2_ROOT/Build/$MARTE_TARGET/Core" +LINUX_TIMER_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/DataSources/LinuxTimer" +LOGGER_DATA_SOURCE_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/DataSources/LoggerDataSource" +FILE_DATA_SOURCE_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/DataSources/FileDataSource" +IOGAM_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/GAMs/IOGAM" + +# Librerie che devono essere presenti. +MARTE2_CORE_LIB="$MARTE2_CORE_LIB_DIR/MARTe2.so" +LINUX_TIMER_LIB="$LINUX_TIMER_LIB_DIR/LinuxTimer.so" +LOGGER_DATA_SOURCE_LIB="$LOGGER_DATA_SOURCE_LIB_DIR/LoggerDataSource.so" +FILE_DATA_SOURCE_LIB="$FILE_DATA_SOURCE_LIB_DIR/FileDataSource.so" +IOGAM_LIB="$IOGAM_LIB_DIR/IOGAM.so" + +# Verifica delle directory runtime. +for marte_runtime_dir in \ + "$MARTE2_CORE_LIB_DIR" \ + "$LINUX_TIMER_LIB_DIR" \ + "$LOGGER_DATA_SOURCE_LIB_DIR" \ + "$FILE_DATA_SOURCE_LIB_DIR" \ + "$IOGAM_LIB_DIR" +do + if [ ! -d "$marte_runtime_dir" ]; then + printf "DIRECTORY RUNTIME ASSENTE: %s\n" "$marte_runtime_dir" >&2 + return 2 2>/dev/null || exit 2 + fi +done + +# Verifica delle librerie runtime. +for marte_runtime_lib in \ + "$MARTE2_CORE_LIB" \ + "$LINUX_TIMER_LIB" \ + "$LOGGER_DATA_SOURCE_LIB" \ + "$FILE_DATA_SOURCE_LIB" \ + "$IOGAM_LIB" +do + if [ ! -f "$marte_runtime_lib" ]; then + printf "LIBRERIA RUNTIME ASSENTE: %s\n" "$marte_runtime_lib" >&2 + return 3 2>/dev/null || exit 3 + fi +done + +# Inserisce una directory allinizio di LD_LIBRARY_PATH solo se non è già presente. +marte_prepend_library_path() { + marte_directory="$1" + + case ":${LD_LIBRARY_PATH-}:" in + *":${marte_directory}:"*) + ;; + *) + if [ -n "${LD_LIBRARY_PATH-}" ]; then + LD_LIBRARY_PATH="${marte_directory}:${LD_LIBRARY_PATH}" + else + LD_LIBRARY_PATH="${marte_directory}" + fi + ;; + esac +} + +# Ordine inverso perché ogni voce viene inserita in testa. +marte_prepend_library_path "$FILE_DATA_SOURCE_LIB_DIR" +marte_prepend_library_path "$IOGAM_LIB_DIR" +marte_prepend_library_path "$LOGGER_DATA_SOURCE_LIB_DIR" +marte_prepend_library_path "$LINUX_TIMER_LIB_DIR" +marte_prepend_library_path "$MARTE2_CORE_LIB_DIR" + +export MARTE2_CORE_LIB_DIR +export LINUX_TIMER_LIB_DIR +export LOGGER_DATA_SOURCE_LIB_DIR +export FILE_DATA_SOURCE_LIB_DIR +export IOGAM_LIB_DIR +export LD_LIBRARY_PATH + +unset marte_runtime_dir +unset marte_runtime_lib +unset marte_directory +unset -f marte_prepend_library_path diff --git a/common/marte_runner.sh b/common/marte_runner.sh new file mode 100755 index 0000000..ae2c32e --- /dev/null +++ b/common/marte_runner.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +set -u + +if [ "$#" -ne 4 ]; then + printf "Uso: %s \n" \ + "$0" >&2 + exit 2 +fi + +SCENARIO_ID="$1" +CONFIG_FILE="$2" +REQUESTED_STATE="$3" +DURATION_SECONDS="$4" + +COMMON_DIR="$( + cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && + pwd +)" + +ENV_FILE="$COMMON_DIR/environment.sh" +PATHS_FILE="$COMMON_DIR/paths.sh" + +if [ ! -f "$ENV_FILE" ]; then + printf "FILE AMBIENTE ASSENTE: %s\n" "$ENV_FILE" >&2 + exit 3 +fi + +source "$ENV_FILE" + +if [ ! -x "$MARTE_APP" ]; then + printf "MARTeApp ASSENTE O NON ESEGUIBILE: %s\n" "$MARTE_APP" >&2 + exit 4 +fi + +if [ ! -f "$CONFIG_FILE" ]; then + printf "CONFIGURAZIONE ASSENTE: %s\n" "$CONFIG_FILE" >&2 + exit 5 +fi + +case "$SCENARIO_ID" in + *[!A-Za-z0-9_-]*|"") + printf "IDENTIFICATORE SCENARIO NON VALIDO: %s\n" "$SCENARIO_ID" >&2 + exit 6 + ;; +esac + +case "$REQUESTED_STATE" in + *[!A-Za-z0-9_-]*|"") + printf "NOME STATO NON VALIDO: %s\n" "$REQUESTED_STATE" >&2 + exit 7 + ;; +esac + +case "$DURATION_SECONDS" in + *[!0-9]*|"") + printf "DURATA NON VALIDA: %s\n" "$DURATION_SECONDS" >&2 + exit 8 + ;; +esac + +if [ "$DURATION_SECONDS" -le 0 ]; then + printf "LA DURATA DEVE ESSERE MAGGIORE DI ZERO.\n" >&2 + exit 8 +fi + +STAMP="$(date +%Y%m%d_%H%M%S)" +RUN_DIR="$RUNS_ROOT/${STAMP}_${SCENARIO_ID}" +RUN_CONFIG="$RUN_DIR/configuration.marte" +RUN_LOG="$RUN_DIR/run.log" +RUN_SUMMARY="$RUN_DIR/runner_summary.txt" +RUN_HASHES="$RUN_DIR/hashes.sha256" +RUN_COMMAND="$RUN_DIR/command.txt" + +mkdir -p -- "$RUN_DIR" + +cp --preserve=mode,timestamps -- "$CONFIG_FILE" "$RUN_CONFIG" + +RUN_DIR_PLACEHOLDER="__MARTE_RUN_DIR__" + +PLACEHOLDER_COUNT="$( + python3 - "$RUN_CONFIG" "$RUN_DIR_PLACEHOLDER" "$RUN_DIR" <<'PYRUNNER' +import sys +from pathlib import Path + +config_path = Path(sys.argv[1]) +placeholder = sys.argv[2] +run_dir = sys.argv[3] + +text = config_path.read_text(encoding="utf-8") +count = text.count(placeholder) + +if count > 0: + config_path.write_text( + text.replace(placeholder, run_dir), + encoding="utf-8" + ) + +remaining = config_path.read_text( + encoding="utf-8" +).count(placeholder) + +if remaining != 0: + print( + f"PLACEHOLDER_RESIDUI={remaining}", + file=sys.stderr + ) + sys.exit(1) + +print(count) +PYRUNNER +)" +PLACEHOLDER_RC=$? + +if [ "$PLACEHOLDER_RC" -ne 0 ]; then + printf "ERRORE DURANTE LA SOSTITUZIONE DEL PERCORSO RUN.\n" >&2 + exit 9 +fi + +{ + printf "%q " \ + timeout \ + --signal=INT \ + --kill-after=2s \ + "${DURATION_SECONDS}s" \ + "$MARTE_APP" \ + -l RealTimeLoader \ + -f "$RUN_CONFIG" \ + -s "$REQUESTED_STATE" + printf "\n" +} > "$RUN_COMMAND" + +{ + sha256sum -- "$CONFIG_FILE" + sha256sum -- "$RUN_CONFIG" + sha256sum -- "$PATHS_FILE" + sha256sum -- "$ENV_FILE" + sha256sum -- "${BASH_SOURCE[0]}" +} > "$RUN_HASHES" + +printf "============================================================\n" +printf "MARTe2 COMMON RUNNER\n" +printf "============================================================\n" +printf "Scenario: %s\n" "$SCENARIO_ID" +printf "Configurazione: %s\n" "$RUN_CONFIG" +printf "Stato: %s\n" "$REQUESTED_STATE" +printf "Durata: %s secondi\n" "$DURATION_SECONDS" +printf "Run directory: %s\n" "$RUN_DIR" +printf "Placeholder: %s sostituzioni\n" "$PLACEHOLDER_COUNT" +printf "\n" + +set +e + +timeout \ + --signal=INT \ + --kill-after=2s \ + "${DURATION_SECONDS}s" \ + "$MARTE_APP" \ + -l RealTimeLoader \ + -f "$RUN_CONFIG" \ + -s "$REQUESTED_STATE" \ + 2>&1 | +tee "$RUN_LOG" + +PIPE_CODES=("${PIPESTATUS[@]}") + +set -e + +MARTE_EXIT_CODE="${PIPE_CODES[0]}" +TEE_EXIT_CODE="${PIPE_CODES[1]}" + +{ + printf "============================================================\n" + printf "RIEPILOGO COMMON RUNNER\n" + printf "============================================================\n" + printf "Data: %s\n" "$(date --iso-8601=seconds)" + printf "Scenario: %s\n" "$SCENARIO_ID" + printf "Configurazione fonte: %s\n" "$CONFIG_FILE" + printf "Configurazione usata: %s\n" "$RUN_CONFIG" + printf "Stato richiesto: %s\n" "$REQUESTED_STATE" + printf "Durata richiesta: %s secondi\n" "$DURATION_SECONDS" + printf "Placeholder sostituiti: %s\n" "$PLACEHOLDER_COUNT" + printf "Exit code MARTe: %s\n" "$MARTE_EXIT_CODE" + printf "Exit code tee: %s\n" "$TEE_EXIT_CODE" + printf "Run log: %s\n" "$RUN_LOG" + printf "Hash: %s\n" "$RUN_HASHES" + printf "Comando: %s\n" "$RUN_COMMAND" +} | tee "$RUN_SUMMARY" + +printf "\nRUN_DIR=%s\n" "$RUN_DIR" + +if [ "$TEE_EXIT_CODE" -ne 0 ]; then + printf "ERRORE DURANTE LA SCRITTURA DEL LOG.\n" >&2 + exit 30 +fi + +exit 0 diff --git a/common/paths.sh b/common/paths.sh new file mode 100644 index 0000000..f03c964 --- /dev/null +++ b/common/paths.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Percorso padre dei repository e della benchmark suite. +MARTE_ROOT="/home/dariodf/Scrivania/MARTE" + +# Repository esterni usati dalla benchmark suite. +MARTE2_ROOT="$MARTE_ROOT/MARTe2" +MARTE2_COMPONENTS_ROOT="$MARTE_ROOT/MARTe2-components" +MARTE_DEBUG_ROOT="$MARTE_ROOT/marte-debug" + +# Directory principale della nuova infrastruttura di test. +BENCHMARK_SUITE_ROOT="$MARTE_ROOT/marte-benchmark-suite" + +# Target di compilazione attualmente utilizzato. +MARTE_TARGET="x86-linux" + +# Eseguibile MARTe2. +MARTE_APP="$MARTE2_ROOT/Build/$MARTE_TARGET/App/MARTeApp.ex" + +# Directory delle configurazioni. +CONFIGURATIONS_ROOT="$BENCHMARK_SUITE_ROOT/configurations" +BASELINES_ROOT="$CONFIGURATIONS_ROOT/baselines" +GENERATED_CONFIGURATIONS_ROOT="$CONFIGURATIONS_ROOT/generated" + +# Configurazioni attualmente disponibili. +OFFICIAL_HELLOWORLD_CONFIG="$BASELINES_ROOT/RTApp-HelloWorld-2-solution.cfg" +S00_CONFIG="$GENERATED_CONFIGURATIONS_ROOT/S00/smoke_s0_core.marte" +S01_CONFIG="$GENERATED_CONFIGURATIONS_ROOT/S01/smoke_s1_timing.marte" + +# Directory operative della suite. +SCENARIOS_ROOT="$BENCHMARK_SUITE_ROOT/scenarios" +PATCHERS_ROOT="$BENCHMARK_SUITE_ROOT/patchers" +RUNS_ROOT="$BENCHMARK_SUITE_ROOT/runs" +ANALYSIS_ROOT="$BENCHMARK_SUITE_ROOT/analysis" +LOGGING_ROOT="$BENCHMARK_SUITE_ROOT/logging" +ARCHIVE_ROOT="$BENCHMARK_SUITE_ROOT/archive" diff --git a/configurations/generated/S00/smoke_s0_core.marte b/configurations/generated/S00/smoke_s0_core.marte new file mode 100644 index 0000000..80b563d --- /dev/null +++ b/configurations/generated/S00/smoke_s0_core.marte @@ -0,0 +1,89 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = LoggerDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer GAMDisplay} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} + diff --git a/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte b/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte new file mode 100644 index 0000000..5918149 --- /dev/null +++ b/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte @@ -0,0 +1,98 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = Display + Type = uint32 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = LoggerDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer GAMDisplay} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} + diff --git a/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte b/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte new file mode 100644 index 0000000..058c5da --- /dev/null +++ b/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte @@ -0,0 +1,152 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = Display + Type = uint32 + } + } + } + +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = LoggerDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s02_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer GAMDisplay IOGAM_Writer} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} + diff --git a/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte b/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte new file mode 100644 index 0000000..8f8a03e --- /dev/null +++ b/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte @@ -0,0 +1,220 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = Display + Type = uint32 + } + } + } + +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + SentinelScalarA = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = FileWriter + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = LoggerDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s03_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} diff --git a/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte b/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte new file mode 100644 index 0000000..6bd4b2b --- /dev/null +++ b/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte @@ -0,0 +1,260 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + Time = { + DataSource = Display + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = Display + Type = uint32 + } + SentinelScalarA = { + DataSource = Display + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = Display + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = Display + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + SentinelScalarA = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = FileWriter + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = LoggerDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s03_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} diff --git a/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte b/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte new file mode 100644 index 0000000..7e5f49d --- /dev/null +++ b/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte @@ -0,0 +1,260 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } + +GAMDisplay = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = Display + Type = uint32 + } + Time = { + DataSource = Display + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = Display + Type = uint32 + } + SentinelScalarA = { + DataSource = Display + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = Display + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = Display + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + SentinelScalarA = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = FileWriter + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Display = { + Class = GAMDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s03_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} diff --git a/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte b/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte new file mode 100644 index 0000000..d8abdc1 --- /dev/null +++ b/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte @@ -0,0 +1,193 @@ +$HelloWorldApp = { + Class = RealTimeApplication + +Functions = { + Class = ReferenceContainer + +GAMTimer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = Timer + Type = uint32 + } + Time = { + Frequency = 10 + DataSource = Timer + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + } + } + +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } + +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + SentinelScalarA = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = FileWriter + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = FileWriter + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +Data = { + Class = ReferenceContainer + DefaultDataSource = DDB1 + +DDB1 = { + Class = GAMDataSource + } + +Timings = { + Class = TimingDataSource + } + +Timer = { + Class = LinuxTimer + SleepNature = "Default" + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + } + } + +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s03_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } + } + } + } + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Threads = { + Class = ReferenceContainer + +Thread1 = { + Class = RealTimeThread + CPUs = 0x1 + Functions = {GAMTimer SentinelProducer IOGAM_Writer} + } + } + } + } + +Scheduler = { + Class = GAMScheduler + TimingDataSource = Timings + } +} diff --git a/launcher/marte_benchmark_launcher.sh b/launcher/marte_benchmark_launcher.sh new file mode 100755 index 0000000..e089502 --- /dev/null +++ b/launcher/marte_benchmark_launcher.sh @@ -0,0 +1,1246 @@ +#!/usr/bin/env bash +set -u + +LAUNCHER_DIR="$( + cd "$(dirname "${BASH_SOURCE[0]}")" && + pwd +)" + +if [ -n "${MARTE_BENCHMARK_SUITE_ROOT:-}" ]; then + SUITE="$(cd "$MARTE_BENCHMARK_SUITE_ROOT" && pwd)" +else + SUITE="$(cd "$LAUNCHER_DIR/.." && pwd)" +fi + +PATCHER_S01="$SUITE/patchers/configuration/build_s01_from_s00.sh" +PATCHER_S02="$SUITE/patchers/configuration/build_s02_from_s01.sh" +PATCHER_S03="${MARTE_S03_PATCHER_OVERRIDE:-$SUITE/patchers/configuration/build_s03_from_s02.sh}" +RUNNER="$SUITE/common/marte_runner.sh" +VALIDATOR_S01="$SUITE/scenarios/S01_timing_display/validate.sh" +VALIDATOR_S02="$SUITE/scenarios/S02_binary_recording/validate.sh" +VALIDATOR_S03="${MARTE_S03_VALIDATOR_OVERRIDE:-$SUITE/scenarios/S03_semantic_sentinels/validate.sh}" + +S00_CONFIG="$SUITE/configurations/generated/S00/smoke_s0_core.marte" +S01_PATCHED_CONFIG="$SUITE/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" +S02_PATCHED_CONFIG="$SUITE/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte" +S03_PATCHED_CONFIG="${MARTE_S03_CONFIG_OVERRIDE:-$SUITE/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte}" + +CONSTANT_GAM_LIB_DIR="$SUITE/../MARTe2-components/Build/x86-linux/Components/GAMs/ConstantGAM" +if [ -f "$SUITE/common/paths.sh" ]; then + # Il file espone i percorsi canonici usati dalla suite. Il sourcing non + # modifica file e permette di rispettare target e root non standard. + source "$SUITE/common/paths.sh" + CONSTANT_GAM_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/GAMs/ConstantGAM" +fi +CONSTANT_GAM_LIB="$CONSTANT_GAM_LIB_DIR/ConstantGAM.so" + +EXPECTED_S00_SHA256="49a9b48595aab88cd8fd11d1e58ace4ab11d7d0a649e9b143bdc25c8866253d5" +EXPECTED_S01_SHA256="1513e1ca72eba56d53079ac7328908f12ddc72772456e2d75bcbacb6465d2427" +EXPECTED_S02_SHA256="8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1" +EXPECTED_S03_SHA256="0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4" + +DEFAULT_DURATION_SECONDS="8" + +S00_STATUS="NOT_CHECKED" +S01_STATUS="NOT_CHECKED" +S02_STATUS="NOT_CHECKED" +S03_STATUS="NOT_CHECKED" +S01_PATCHER_RC="NOT_RUN" +S02_PATCHER_RC="NOT_RUN" +S03_PATCHER_RC="NOT_RUN" + +print_usage() { + printf "Uso:\n" + printf " %s\n" "$0" + printf " %s S01 [durata-secondi]\n" "$0" + printf " %s S02 [durata-secondi]\n" "$0" + printf " %s S03 [durata-secondi]\n" "$0" + printf "\n" + printf "Dipendenze automatiche:\n" + printf " S01: catena S00 -> S01; esegue e valida S01.\n" + printf " S02: catena S00 -> S01 -> S02; esegue e valida S02.\n" + printf " S03: catena S00 -> S01 -> S02 -> S03; valida le sentinelle.\n" +} + +fail() { + code="$1" + shift + printf "ERRORE: %s\n" "$*" >&2 + exit "$code" +} + +check_executable() { + file="$1" + description="$2" + + if [ ! -x "$file" ]; then + fail 30 "$description assente o non eseguibile: $file" + fi +} + +validate_duration() { + duration="$1" + + case "$duration" in + *[!0-9]*|"") + printf "ERRORE: durata non valida: %s\n" "$duration" >&2 + return 1 + ;; + esac + + if [ "$duration" -le 0 ]; then + printf "ERRORE: la durata deve essere maggiore di zero.\n" >&2 + return 1 + fi + + return 0 +} + +verify_file_hash() { + file="$1" + expected_hash="$2" + description="$3" + + if [ ! -f "$file" ]; then + printf "ERRORE: %s assente: %s\n" "$description" "$file" >&2 + return 1 + fi + + actual_hash="$(sha256sum -- "$file" | awk '{print $1}')" + + if [ "$actual_hash" != "$expected_hash" ]; then + printf "ERRORE: hash inatteso per %s.\n" "$description" >&2 + printf "Atteso: %s\n" "$expected_hash" >&2 + printf "Reale: %s\n" "$actual_hash" >&2 + printf "File: %s\n" "$file" >&2 + return 1 + fi + + printf "%s pronta: %s\n" "$description" "$file" + printf "SHA-256: %s\n" "$actual_hash" + return 0 +} + +ensure_s00() { + printf "\n============================================================\n" + printf "DIPENDENZA S00 — CONFIGURAZIONE BASE\n" + printf "============================================================\n" + + if ! verify_file_hash \ + "$S00_CONFIG" \ + "$EXPECTED_S00_SHA256" \ + "Configurazione S00" + then + S00_STATUS="FAILED" + return 50 + fi + + S00_STATUS="READY" + return 0 +} + +ensure_s01() { + ensure_s00 + dependency_rc=$? + + if [ "$dependency_rc" -ne 0 ]; then + return "$dependency_rc" + fi + + printf "\n============================================================\n" + printf "DIPENDENZA S01 — PATCH S00 -> S01\n" + printf "============================================================\n" + + check_executable "$PATCHER_S01" "Patcher S01" + + timeout 10s "$PATCHER_S01" + S01_PATCHER_RC=$? + + printf "Codice patcher S01: %s\n" "$S01_PATCHER_RC" + + if [ "$S01_PATCHER_RC" -ne 0 ]; then + S01_STATUS="FAILED" + return 51 + fi + + if ! verify_file_hash \ + "$S01_PATCHED_CONFIG" \ + "$EXPECTED_S01_SHA256" \ + "Configurazione S01" + then + S01_STATUS="FAILED" + return 52 + fi + + S01_STATUS="READY" + return 0 +} + +ensure_s02() { + ensure_s01 + dependency_rc=$? + + if [ "$dependency_rc" -ne 0 ]; then + return "$dependency_rc" + fi + + printf "\n============================================================\n" + printf "DIPENDENZA S02 — PATCH S01 -> S02\n" + printf "============================================================\n" + + check_executable "$PATCHER_S02" "Patcher S02" + + timeout 10s "$PATCHER_S02" + S02_PATCHER_RC=$? + + printf "Codice patcher S02: %s\n" "$S02_PATCHER_RC" + + if [ "$S02_PATCHER_RC" -ne 0 ]; then + S02_STATUS="FAILED" + return 53 + fi + + if ! verify_file_hash \ + "$S02_PATCHED_CONFIG" \ + "$EXPECTED_S02_SHA256" \ + "Configurazione S02" + then + S02_STATUS="FAILED" + return 54 + fi + + S02_STATUS="READY" + return 0 +} + +ensure_s03() { + ensure_s02 + dependency_rc=$? + + if [ "$dependency_rc" -ne 0 ]; then + return "$dependency_rc" + fi + + printf "\n============================================================\n" + printf "DIPENDENZA S03 — PATCH S02 -> S03\n" + printf "============================================================\n" + + check_executable "$PATCHER_S03" "Patcher S03" + check_executable "$VALIDATOR_S03" "Validatore S03" + + if [ ! -f "$CONSTANT_GAM_LIB" ]; then + printf "ERRORE: libreria ConstantGAM assente: %s\n" "$CONSTANT_GAM_LIB" >&2 + S03_STATUS="FAILED" + return 55 + fi + + case ":${LD_LIBRARY_PATH-}:" in + *":${CONSTANT_GAM_LIB_DIR}:"*) + ;; + *) + if [ -n "${LD_LIBRARY_PATH-}" ]; then + LD_LIBRARY_PATH="${CONSTANT_GAM_LIB_DIR}:${LD_LIBRARY_PATH}" + else + LD_LIBRARY_PATH="$CONSTANT_GAM_LIB_DIR" + fi + ;; + esac + export LD_LIBRARY_PATH + + timeout 10s "$PATCHER_S03" "$S02_PATCHED_CONFIG" "$S03_PATCHED_CONFIG" + S03_PATCHER_RC=$? + + printf "Codice patcher S03: %s\n" "$S03_PATCHER_RC" + + if [ "$S03_PATCHER_RC" -ne 0 ]; then + S03_STATUS="FAILED" + return 56 + fi + + if ! verify_file_hash \ + "$S03_PATCHED_CONFIG" \ + "$EXPECTED_S03_SHA256" \ + "Configurazione S03" + then + S03_STATUS="FAILED" + return 57 + fi + + S03_STATUS="READY" + return 0 +} + +# ------------------------------------------------------------------ +# S02-H2 — lettura contrattuale di validation.txt +# ------------------------------------------------------------------ +# +# parse_validation_field +# +# Richiede ESATTAMENTE una riga "NOME=VALORE" nel file indicato. +# Il valore ammesso e' un token maiuscolo [A-Z][A-Z0-9_]*. +# +# Imposta: +# PARSED_FIELD_VALUE valore letto, oppure stringa vuota +# PARSED_FIELD_ERROR MISSING | DUPLICATED | MALFORMED | NO_FILE, oppure vuoto +# +# Ritorna 0 se e solo se il campo e' presente una sola volta e ben formato. +parse_validation_field() { + field_name="$1" + field_file="$2" + + PARSED_FIELD_VALUE="" + PARSED_FIELD_ERROR="" + + if [ ! -f "$field_file" ]; then + PARSED_FIELD_ERROR="NO_FILE" + return 1 + fi + + field_occurrences="$(grep -c -- "^${field_name}=" "$field_file" || true)" + + if [ -z "$field_occurrences" ]; then + field_occurrences=0 + fi + + if [ "$field_occurrences" -eq 0 ]; then + PARSED_FIELD_ERROR="MISSING" + return 1 + fi + + if [ "$field_occurrences" -gt 1 ]; then + PARSED_FIELD_ERROR="DUPLICATED" + return 1 + fi + + field_value="$( + grep -- "^${field_name}=" "$field_file" | + head -n 1 | + cut -d '=' -f 2- | + tr -d '\r' + )" + + case "$field_value" in + ""|*[!A-Z0-9_]*) + PARSED_FIELD_ERROR="MALFORMED" + return 1 + ;; + esac + + case "$field_value" in + [A-Z]*) + ;; + *) + PARSED_FIELD_ERROR="MALFORMED" + return 1 + ;; + esac + + PARSED_FIELD_VALUE="$field_value" + return 0 +} + +# ------------------------------------------------------------------ +# classify_run +# +# timing_contract: +# required lo scenario DEVE esporre FUNCTIONAL_STATUS e TIMING_STATUS +# not_applicable lo scenario non espone stato temporale (comportamento S01) +# +# Imposta: +# FUNCTIONAL_RESULT esito storico basato sull'exit code del validatore +# PARSED_FUNCTIONAL_STATUS +# PARSED_TIMING_STATUS +# MEASUREMENT_ELIGIBILITY YES | NO | UNKNOWN | NOT_APPLICABLE +# BLOCK_REASON NONE oppure motivo del blocco +# FINAL_RESULT +# FINAL_RC +# ------------------------------------------------------------------ +classify_run() { + classify_validator_rc="$1" + classify_tee_rc="$2" + classify_validation_file="$3" + classify_timing_contract="$4" + + PARSED_FUNCTIONAL_STATUS="NOT_APPLICABLE" + PARSED_TIMING_STATUS="NOT_APPLICABLE" + MEASUREMENT_ELIGIBILITY="NOT_APPLICABLE" + BLOCK_REASON="NONE" + + # --- Contratto temporale: fail-closed su token sconosciuti (H2-04) --- + case "$classify_timing_contract" in + required|not_applicable) + ;; + *) + FUNCTIONAL_RESULT="NOT_EVALUATED" + PARSED_FUNCTIONAL_STATUS="NOT_EVALUATED" + PARSED_TIMING_STATUS="NOT_EVALUATED" + MEASUREMENT_ELIGIBILITY="NO" + BLOCK_REASON="TIMING_CONTRACT_UNRECOGNISED_$classify_timing_contract" + FINAL_RESULT="INVALID_TIMING_CONTRACT" + FINAL_RC=24 + return 0 + ;; + esac + + # --- validation.txt non scritto: nessun parsing possibile --- + if [ "$classify_tee_rc" -ne 0 ]; then + FUNCTIONAL_RESULT="FAIL_VALIDATION_LOG" + FINAL_RESULT="FAIL_VALIDATION_LOG" + PARSED_FUNCTIONAL_STATUS="NOT_EVALUATED" + PARSED_TIMING_STATUS="NOT_EVALUATED" + MEASUREMENT_ELIGIBILITY="NO" + BLOCK_REASON="VALIDATION_LOG_NOT_WRITTEN" + FINAL_RC=67 + return 0 + fi + + # --- Esito funzionale storico, dall'exit code del validatore --- + case "$classify_validator_rc" in + 0) + FUNCTIONAL_RESULT="PASS" + FINAL_RC=0 + FUNCTIONAL_ESTABLISHED=1 + ;; + 10) + FUNCTIONAL_RESULT="PASS_WITH_KNOWN_SHUTDOWN_ANOMALY" + FINAL_RC=10 + FUNCTIONAL_ESTABLISHED=1 + ;; + 11) + FUNCTIONAL_RESULT="PASS_WITH_UNCLASSIFIED_SHUTDOWN_ANOMALY" + FINAL_RC=11 + FUNCTIONAL_ESTABLISHED=1 + ;; + 20|21) + FUNCTIONAL_RESULT="INCONCLUSIVE" + FINAL_RC=20 + FUNCTIONAL_ESTABLISHED=0 + ;; + *) + FUNCTIONAL_RESULT="FAIL_VALIDATOR" + FINAL_RC=68 + FUNCTIONAL_ESTABLISHED=0 + ;; + esac + + FINAL_RESULT="$FUNCTIONAL_RESULT" + + if [ "$classify_timing_contract" = "not_applicable" ]; then + return 0 + fi + + # ------------------------------------------------------------------ + # Contratto temporale richiesto. + # + # H2-01: il parsing e' SEMPRE tentato quando il tee della validazione + # e' riuscito, indipendentemente dall'exit code del validatore. Il + # validatore H1 stampa FUNCTIONAL_STATUS e TIMING_STATUS anche prima + # di uscire con 20; nasconderli sarebbe una propagazione falsa. + # + # La precedenza numerica resta invece all'exit code del validatore: + # 20 rimane 20, 68 rimane 68. Il parsing arricchisce il riepilogo, + # non riscrive un verdetto funzionale gia' emesso. + # ------------------------------------------------------------------ + + CLASSIFY_PARSE_ERROR="NONE" + + if parse_validation_field "FUNCTIONAL_STATUS" "$classify_validation_file"; then + PARSED_FUNCTIONAL_STATUS="$PARSED_FIELD_VALUE" + else + PARSED_FUNCTIONAL_STATUS="PARSE_ERROR_$PARSED_FIELD_ERROR" + CLASSIFY_PARSE_ERROR="FUNCTIONAL_STATUS_$PARSED_FIELD_ERROR" + fi + + if parse_validation_field "TIMING_STATUS" "$classify_validation_file"; then + PARSED_TIMING_STATUS="$PARSED_FIELD_VALUE" + else + PARSED_TIMING_STATUS="PARSE_ERROR_$PARSED_FIELD_ERROR" + if [ "$CLASSIFY_PARSE_ERROR" = "NONE" ]; then + CLASSIFY_PARSE_ERROR="TIMING_STATUS_$PARSED_FIELD_ERROR" + fi + fi + + # --- Nessuna run puo' essere idonea a una misura prestazionale --- + MEASUREMENT_ELIGIBILITY="NO" + + if [ "$CLASSIFY_PARSE_ERROR" != "NONE" ]; then + BLOCK_REASON="$CLASSIFY_PARSE_ERROR" + + if [ "$FUNCTIONAL_ESTABLISHED" -eq 1 ]; then + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + fi + + return 0 + fi + + if [ "$FUNCTIONAL_ESTABLISHED" -eq 0 ]; then + # Exit 20/21/altro: il verdetto funzionale prevale, ma i campi + # realmente presenti sono comunque riportati. + if [ "$PARSED_FUNCTIONAL_STATUS" = "PASS" ]; then + BLOCK_REASON="FUNCTIONAL_STATUS_CONFLICT_WITH_EXIT_CODE" + else + BLOCK_REASON="FUNCTIONAL_STATUS_NOT_PASS" + fi + return 0 + fi + + if [ "$PARSED_FUNCTIONAL_STATUS" != "PASS" ]; then + BLOCK_REASON="FUNCTIONAL_STATUS_NOT_PASS" + FINAL_RESULT="FUNCTIONAL_STATUS_CONFLICT" + FINAL_RC=23 + return 0 + fi + + case "$PARSED_TIMING_STATUS" in + NOT_VALID_*) + BLOCK_REASON="$PARSED_TIMING_STATUS" + FINAL_RESULT="FUNCTIONAL_PASS_TIMING_NOT_VALID" + FINAL_RC=12 + ;; + UNASSESSED_*) + MEASUREMENT_ELIGIBILITY="UNKNOWN" + BLOCK_REASON="$PARSED_TIMING_STATUS" + FINAL_RESULT="FUNCTIONAL_PASS_TIMING_UNASSESSED" + FINAL_RC=13 + ;; + VALID) + # H2-05: il vocabolario H1 installato non emette VALID e il + # PERFORMANCE MEASUREMENT GATE completo non esiste ancora. + # H2 non autorizza mai da solo l'idoneita' prestazionale. + BLOCK_REASON="TIMING_STATUS_VALID_NOT_YET_CONTRACTED" + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + ;; + *) + BLOCK_REASON="TIMING_STATUS_UNRECOGNISED_$PARSED_TIMING_STATUS" + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + ;; + esac + + return 0 +} + +# ------------------------------------------------------------------ +# S03 — contratto funzionale, semantico e temporale separato. +# +# Exit code candidati (non installati): +# 14 funzionalita' e semantica PASS, timing non valido +# 15 funzionalita' e semantica PASS, timing non valutato +# 20 validatore inconcludente o input mancante +# 22 contratto S03 non interpretabile / timing VALID non autorizzato +# 23 conflitto FUNCTIONAL_STATUS +# 25 conflitto SEMANTIC_STATUS +# 67 validation.txt non scritto +# 68 exit code validatore non riconosciuto +# 69 launcher_summary.txt non scritto +# ------------------------------------------------------------------ +classify_s03_run() { + classify_validator_rc="$1" + classify_tee_rc="$2" + classify_validation_file="$3" + + PARSED_FUNCTIONAL_STATUS="NOT_EVALUATED" + PARSED_SEMANTIC_STATUS="NOT_EVALUATED" + PARSED_TIMING_STATUS="NOT_EVALUATED" + PARSED_VALIDATOR_ELIGIBILITY="NOT_EVALUATED" + MEASUREMENT_ELIGIBILITY="NO" + BLOCK_REASON="NONE" + + if [ "$classify_tee_rc" -ne 0 ]; then + FUNCTIONAL_RESULT="FAIL_VALIDATION_LOG" + FINAL_RESULT="FAIL_VALIDATION_LOG" + BLOCK_REASON="VALIDATION_LOG_NOT_WRITTEN" + FINAL_RC=67 + return 0 + fi + + case "$classify_validator_rc" in + 0) + FUNCTIONAL_RESULT="PASS" + FUNCTIONAL_ESTABLISHED=1 + FINAL_RC=0 + ;; + 10) + FUNCTIONAL_RESULT="PASS_WITH_KNOWN_SHUTDOWN_ANOMALY" + FUNCTIONAL_ESTABLISHED=1 + FINAL_RC=10 + ;; + 11) + FUNCTIONAL_RESULT="PASS_WITH_UNCLASSIFIED_SHUTDOWN_ANOMALY" + FUNCTIONAL_ESTABLISHED=1 + FINAL_RC=11 + ;; + 20|21) + FUNCTIONAL_RESULT="INCONCLUSIVE" + FUNCTIONAL_ESTABLISHED=0 + FINAL_RC=20 + ;; + *) + FUNCTIONAL_RESULT="FAIL_VALIDATOR" + FUNCTIONAL_ESTABLISHED=0 + FINAL_RC=68 + ;; + esac + FINAL_RESULT="$FUNCTIONAL_RESULT" + + S03_PARSE_ERROR="NONE" + for field_name in \ + FUNCTIONAL_STATUS \ + SEMANTIC_STATUS \ + TIMING_STATUS \ + MEASUREMENT_ELIGIBILITY + do + if parse_validation_field "$field_name" "$classify_validation_file"; then + case "$field_name" in + FUNCTIONAL_STATUS) + PARSED_FUNCTIONAL_STATUS="$PARSED_FIELD_VALUE" + ;; + SEMANTIC_STATUS) + PARSED_SEMANTIC_STATUS="$PARSED_FIELD_VALUE" + ;; + TIMING_STATUS) + PARSED_TIMING_STATUS="$PARSED_FIELD_VALUE" + ;; + MEASUREMENT_ELIGIBILITY) + PARSED_VALIDATOR_ELIGIBILITY="$PARSED_FIELD_VALUE" + ;; + esac + else + case "$field_name" in + FUNCTIONAL_STATUS) + PARSED_FUNCTIONAL_STATUS="PARSE_ERROR_$PARSED_FIELD_ERROR" + ;; + SEMANTIC_STATUS) + PARSED_SEMANTIC_STATUS="PARSE_ERROR_$PARSED_FIELD_ERROR" + ;; + TIMING_STATUS) + PARSED_TIMING_STATUS="PARSE_ERROR_$PARSED_FIELD_ERROR" + ;; + MEASUREMENT_ELIGIBILITY) + PARSED_VALIDATOR_ELIGIBILITY="PARSE_ERROR_$PARSED_FIELD_ERROR" + ;; + esac + if [ "$S03_PARSE_ERROR" = "NONE" ]; then + S03_PARSE_ERROR="${field_name}_$PARSED_FIELD_ERROR" + fi + fi + done + + if [ "$S03_PARSE_ERROR" != "NONE" ]; then + BLOCK_REASON="$S03_PARSE_ERROR" + FINAL_RESULT="INCONCLUSIVE_S03_CONTRACT" + if [ "$FUNCTIONAL_ESTABLISHED" -eq 1 ]; then + FINAL_RC=22 + fi + return 0 + fi + + case "$PARSED_FUNCTIONAL_STATUS" in + PASS|NOT_DEMONSTRATED) + ;; + *) + BLOCK_REASON="FUNCTIONAL_STATUS_UNRECOGNISED_$PARSED_FUNCTIONAL_STATUS" + FINAL_RESULT="INCONCLUSIVE_S03_CONTRACT" + FINAL_RC=22 + return 0 + ;; + esac + + case "$PARSED_SEMANTIC_STATUS" in + PASS|FAIL|NOT_DEMONSTRATED) + ;; + *) + BLOCK_REASON="SEMANTIC_STATUS_UNRECOGNISED_$PARSED_SEMANTIC_STATUS" + FINAL_RESULT="INCONCLUSIVE_S03_CONTRACT" + FINAL_RC=22 + return 0 + ;; + esac + + if [ "$PARSED_VALIDATOR_ELIGIBILITY" != "NO" ]; then + BLOCK_REASON="ELIGIBILITY_CONTRACT_VIOLATION_$PARSED_VALIDATOR_ELIGIBILITY" + FINAL_RESULT="INCONCLUSIVE_S03_CONTRACT" + FINAL_RC=22 + return 0 + fi + + if [ "$FUNCTIONAL_ESTABLISHED" -eq 0 ]; then + if [ "$PARSED_FUNCTIONAL_STATUS" != "PASS" ]; then + BLOCK_REASON="FUNCTIONAL_STATUS_NOT_PASS" + elif [ "$PARSED_SEMANTIC_STATUS" != "PASS" ]; then + BLOCK_REASON="SEMANTIC_STATUS_$PARSED_SEMANTIC_STATUS" + else + BLOCK_REASON="VALIDATOR_EXIT_NOT_PASS" + fi + FINAL_RESULT="S03_VALIDATION_INCONCLUSIVE" + return 0 + fi + + if [ "$PARSED_FUNCTIONAL_STATUS" != "PASS" ]; then + BLOCK_REASON="FUNCTIONAL_STATUS_CONFLICT_WITH_EXIT_CODE" + FINAL_RESULT="FUNCTIONAL_STATUS_CONFLICT" + FINAL_RC=23 + return 0 + fi + + if [ "$PARSED_SEMANTIC_STATUS" != "PASS" ]; then + BLOCK_REASON="SEMANTIC_STATUS_CONFLICT_$PARSED_SEMANTIC_STATUS" + FINAL_RESULT="SEMANTIC_STATUS_CONFLICT" + FINAL_RC=25 + return 0 + fi + + case "$PARSED_TIMING_STATUS" in + NOT_VALID_*) + BLOCK_REASON="$PARSED_TIMING_STATUS" + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_NOT_VALID" + FINAL_RC=14 + ;; + UNASSESSED_*) + BLOCK_REASON="$PARSED_TIMING_STATUS" + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_UNASSESSED" + FINAL_RC=15 + ;; + VALID) + BLOCK_REASON="TIMING_STATUS_VALID_NOT_YET_CONTRACTED" + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + ;; + NOT_EVALUATED) + BLOCK_REASON="TIMING_STATUS_NOT_EVALUATED" + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + ;; + *) + BLOCK_REASON="TIMING_STATUS_UNRECOGNISED_$PARSED_TIMING_STATUS" + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + FINAL_RC=22 + ;; + esac + + return 0 +} + +emit_s03_summary() { + summary_destination="$1" + + { + printf "============================================================\n" + printf "RIEPILOGO LAUNCHER S03 CANDIDATO\n" + printf "============================================================\n" + printf "Scenario: S03\n" + printf "Durata: %s secondi\n" "${duration:-NOT_AVAILABLE}" + printf "Catena dipendenze: S00 -> S01 -> S02 -> S03\n" + printf "Stato S00: %s\n" "$S00_STATUS" + printf "Stato S01: %s\n" "$S01_STATUS" + printf "Stato S02: %s\n" "$S02_STATUS" + printf "Stato S03: %s\n" "$S03_STATUS" + printf "Codice patcher S01: %s\n" "$S01_PATCHER_RC" + printf "Codice patcher S02: %s\n" "$S02_PATCHER_RC" + printf "Codice patcher S03: %s\n" "$S03_PATCHER_RC" + printf "Configurazione: %s\n" "${S03_PATCHED_CONFIG:-NOT_AVAILABLE}" + printf "Run directory: %s\n" "${RUN_DIR:-NOT_AVAILABLE}" + printf "Run log: %s\n" "${RUN_LOG:-NOT_AVAILABLE}" + printf "Validazione: %s\n" "${VALIDATION_LOG:-NOT_AVAILABLE}" + printf "Codice runner: %s\n" "${RUNNER_RC:-NOT_AVAILABLE}" + printf "Codice validatore: %s\n" "${VALIDATOR_RC:-NOT_AVAILABLE}" + printf "Esito storico validatore: %s\n" "${FUNCTIONAL_RESULT:-NOT_AVAILABLE}" + printf "FUNCTIONAL_STATUS=%s\n" "$PARSED_FUNCTIONAL_STATUS" + printf "SEMANTIC_STATUS=%s\n" "$PARSED_SEMANTIC_STATUS" + printf "TIMING_STATUS=%s\n" "$PARSED_TIMING_STATUS" + printf "MEASUREMENT_ELIGIBILITY=%s\n" "$MEASUREMENT_ELIGIBILITY" + printf "BLOCK_REASON=%s\n" "$BLOCK_REASON" + printf "FINAL_RESULT=%s\n" "$FINAL_RESULT" + printf "FINAL_RC=%s\n" "$FINAL_RC" + } | tee "$summary_destination" + + summary_codes=("${PIPESTATUS[@]}") + if [ "${summary_codes[1]}" -ne 0 ]; then + return 69 + fi + return 0 +} + +run_prepared_s03() { + duration="$1" + + check_executable "$RUNNER" "Common runner" + check_executable "$VALIDATOR_S03" "Validatore S03" + + CAPTURE="$(mktemp "${TMPDIR:-/tmp}/marte_benchmark_launcher_s03.XXXXXX")" || + return 60 + trap 'rm -f -- "$CAPTURE"' EXIT HUP INT TERM + + printf "\n============================================================\n" + printf "ESECUZIONE COMMON RUNNER — S03\n" + printf "============================================================\n" + + "$RUNNER" \ + "S03_launcher" \ + "$S03_PATCHED_CONFIG" \ + "State1" \ + "$duration" \ + 2>&1 | tee "$CAPTURE" + + RUNNER_CODES=("${PIPESTATUS[@]}") + RUNNER_RC="${RUNNER_CODES[0]}" + CAPTURE_TEE_RC="${RUNNER_CODES[1]}" + + if [ "$RUNNER_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_RUNNER\n" + return 61 + fi + if [ "$CAPTURE_TEE_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_LAUNCHER_LOG\n" + return 62 + fi + + RUN_DIR="$(sed -n 's/^RUN_DIR=//p' "$CAPTURE" | tail -n 1)" + if [ -z "$RUN_DIR" ]; then + printf "ERRORE: RUN_DIR non individuata.\n" >&2 + return 63 + fi + if [ ! -d "$RUN_DIR" ]; then + printf "ERRORE: directory di run assente: %s\n" "$RUN_DIR" >&2 + return 64 + fi + + RUN_LOG="$RUN_DIR/run.log" + VALIDATION_LOG="$RUN_DIR/validation.txt" + LAUNCHER_SUMMARY="$RUN_DIR/launcher_summary.txt" + + if [ ! -f "$RUN_LOG" ]; then + printf "ERRORE: run.log assente: %s\n" "$RUN_LOG" >&2 + return 65 + fi + + printf "\n============================================================\n" + printf "VALIDAZIONE S03\n" + printf "============================================================\n" + + timeout 20s "$VALIDATOR_S03" "$RUN_DIR" 2>&1 | tee "$VALIDATION_LOG" + VALIDATION_CODES=("${PIPESTATUS[@]}") + VALIDATOR_RC="${VALIDATION_CODES[0]}" + VALIDATION_TEE_RC="${VALIDATION_CODES[1]}" + + classify_s03_run "$VALIDATOR_RC" "$VALIDATION_TEE_RC" "$VALIDATION_LOG" + + if ! emit_s03_summary "$LAUNCHER_SUMMARY"; then + FINAL_RESULT="FAIL_LAUNCHER_SUMMARY" + FINAL_RC=69 + printf "ERRORE: riepilogo launcher S03 non scritto: %s\n" \ + "$LAUNCHER_SUMMARY" >&2 + fi + + printf "\nRISULTATO FINALE: %s\n" "$FINAL_RESULT" + printf "Riepilogo: %s\n" "$LAUNCHER_SUMMARY" + + rm -f -- "$CAPTURE" + trap - EXIT HUP INT TERM + return "$FINAL_RC" +} + +run_prepared_scenario() { + scenario_name="$1" + scenario_id="$2" + config_file="$3" + validator="$4" + validator_input_mode="$5" + duration="$6" + dependency_chain="$7" + timing_contract="$8" + + check_executable "$RUNNER" "Common runner" + check_executable "$validator" "Validatore $scenario_name" + + CAPTURE="$( + mktemp "${TMPDIR:-/tmp}/marte_benchmark_launcher.XXXXXX" + )" || return 60 + + trap 'rm -f "$CAPTURE"' EXIT HUP INT TERM + + printf "\n============================================================\n" + printf "ESECUZIONE COMMON RUNNER — %s\n" "$scenario_name" + printf "============================================================\n" + + "$RUNNER" \ + "${scenario_id}_launcher" \ + "$config_file" \ + "State1" \ + "$duration" \ + 2>&1 | tee "$CAPTURE" + + RUNNER_CODES=("${PIPESTATUS[@]}") + RUNNER_RC="${RUNNER_CODES[0]}" + CAPTURE_TEE_RC="${RUNNER_CODES[1]}" + + printf "\nCodice common runner: %s\n" "$RUNNER_RC" + printf "Codice tee launcher: %s\n" "$CAPTURE_TEE_RC" + + if [ "$RUNNER_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_RUNNER\n" + return 61 + fi + + if [ "$CAPTURE_TEE_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_LAUNCHER_LOG\n" + return 62 + fi + + RUN_DIR="$( + sed -n 's/^RUN_DIR=//p' "$CAPTURE" | + tail -n 1 + )" + + if [ -z "$RUN_DIR" ]; then + printf "ERRORE: RUN_DIR non individuata.\n" >&2 + return 63 + fi + + if [ ! -d "$RUN_DIR" ]; then + printf "ERRORE: directory di run assente: %s\n" "$RUN_DIR" >&2 + return 64 + fi + + RUN_LOG="$RUN_DIR/run.log" + VALIDATION_LOG="$RUN_DIR/validation.txt" + LAUNCHER_SUMMARY="$RUN_DIR/launcher_summary.txt" + + if [ ! -f "$RUN_LOG" ]; then + printf "ERRORE: run.log assente: %s\n" "$RUN_LOG" >&2 + return 65 + fi + + case "$validator_input_mode" in + run_log) + VALIDATOR_INPUT="$RUN_LOG" + ;; + run_dir) + VALIDATOR_INPUT="$RUN_DIR" + ;; + *) + printf "ERRORE: modalità input validatore non riconosciuta: %s\n" \ + "$validator_input_mode" >&2 + return 66 + ;; + esac + + printf "\n============================================================\n" + printf "VALIDAZIONE %s\n" "$scenario_name" + printf "============================================================\n" + + timeout 15s "$validator" "$VALIDATOR_INPUT" 2>&1 | + tee "$VALIDATION_LOG" + + VALIDATION_CODES=("${PIPESTATUS[@]}") + VALIDATOR_RC="${VALIDATION_CODES[0]}" + VALIDATION_TEE_RC="${VALIDATION_CODES[1]}" + + classify_run \ + "$VALIDATOR_RC" \ + "$VALIDATION_TEE_RC" \ + "$VALIDATION_LOG" \ + "$timing_contract" + + { + printf "============================================================\n" + printf "RIEPILOGO LAUNCHER\n" + printf "============================================================\n" + printf "Scenario: %s\n" "$scenario_name" + printf "Durata: %s secondi\n" "$duration" + printf "Catena dipendenze: %s\n" "$dependency_chain" + printf "Stato S00: %s\n" "$S00_STATUS" + printf "Stato S01: %s\n" "$S01_STATUS" + printf "Stato S02: %s\n" "$S02_STATUS" + printf "Codice patcher S01: %s\n" "$S01_PATCHER_RC" + printf "Codice patcher S02: %s\n" "$S02_PATCHER_RC" + printf "Configurazione: %s\n" "$config_file" + printf "Run directory: %s\n" "$RUN_DIR" + printf "Run log: %s\n" "$RUN_LOG" + printf "Validazione: %s\n" "$VALIDATION_LOG" + printf "Codice runner: %s\n" "$RUNNER_RC" + printf "Codice validatore: %s\n" "$VALIDATOR_RC" + printf "Esito funzionale: %s\n" "$FUNCTIONAL_RESULT" + printf "FUNCTIONAL_STATUS: %s\n" "$PARSED_FUNCTIONAL_STATUS" + printf "TIMING_STATUS: %s\n" "$PARSED_TIMING_STATUS" + printf "MEASUREMENT_ELIGIBILITY: %s\n" "$MEASUREMENT_ELIGIBILITY" + printf "Motivo blocco: %s\n" "$BLOCK_REASON" + printf "Codice finale launcher: %s\n" "$FINAL_RC" + printf "Risultato finale: %s\n" "$FINAL_RESULT" + } | tee "$LAUNCHER_SUMMARY" + + SUMMARY_CODES=("${PIPESTATUS[@]}") + SUMMARY_TEE_RC="${SUMMARY_CODES[1]}" + + # H2-06: il riepilogo e' il documento che porta il nuovo contratto. + # Se non e' stato scritto, il risultato non e' verificabile a posteriori + # e non puo' essere presentato come esito valido. + if [ "$SUMMARY_TEE_RC" -ne 0 ]; then + printf "ERRORE: riepilogo launcher non scritto: %s\n" \ + "$LAUNCHER_SUMMARY" >&2 + FINAL_RESULT="FAIL_LAUNCHER_SUMMARY" + FINAL_RC=69 + fi + + printf "\nRISULTATO FINALE: %s\n" "$FINAL_RESULT" + printf "Riepilogo: %s\n" "$LAUNCHER_SUMMARY" + + rm -f -- "$CAPTURE" + trap - EXIT HUP INT TERM + + return "$FINAL_RC" +} + +run_s01() { + duration="$1" + + if ! validate_duration "$duration"; then + return 31 + fi + + S00_STATUS="NOT_CHECKED" + S01_STATUS="NOT_CHECKED" + S02_STATUS="NOT_REQUIRED" + S03_STATUS="NOT_REQUIRED" + S01_PATCHER_RC="NOT_RUN" + S02_PATCHER_RC="NOT_REQUIRED" + S03_PATCHER_RC="NOT_REQUIRED" + + printf "============================================================\n" + printf "SCENARIO S01 — TIMING DISPLAY\n" + printf "============================================================\n" + printf "Durata: %s secondi\n" "$duration" + printf "Catena automatica: S00 -> S01\n" + + ensure_s01 + PREPARE_RC=$? + + if [ "$PREPARE_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_DEPENDENCY_CHAIN\n" + return "$PREPARE_RC" + fi + + run_prepared_scenario \ + "S01" \ + "S01" \ + "$S01_PATCHED_CONFIG" \ + "$VALIDATOR_S01" \ + "run_log" \ + "$duration" \ + "S00 -> S01" \ + "not_applicable" +} + +run_s02() { + duration="$1" + + if ! validate_duration "$duration"; then + return 31 + fi + + S00_STATUS="NOT_CHECKED" + S01_STATUS="NOT_CHECKED" + S02_STATUS="NOT_CHECKED" + S03_STATUS="NOT_REQUIRED" + S01_PATCHER_RC="NOT_RUN" + S02_PATCHER_RC="NOT_RUN" + S03_PATCHER_RC="NOT_REQUIRED" + + printf "============================================================\n" + printf "SCENARIO S02 — BINARY RECORDING\n" + printf "============================================================\n" + printf "Durata: %s secondi\n" "$duration" + printf "Catena automatica: S00 -> S01 -> S02\n" + + ensure_s02 + PREPARE_RC=$? + + if [ "$PREPARE_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_DEPENDENCY_CHAIN\n" + return "$PREPARE_RC" + fi + + run_prepared_scenario \ + "S02" \ + "S02" \ + "$S02_PATCHED_CONFIG" \ + "$VALIDATOR_S02" \ + "run_dir" \ + "$duration" \ + "S00 -> S01 -> S02" \ + "required" +} + +run_s03() { + duration="$1" + + if ! validate_duration "$duration"; then + return 31 + fi + + S00_STATUS="NOT_CHECKED" + S01_STATUS="NOT_CHECKED" + S02_STATUS="NOT_CHECKED" + S03_STATUS="NOT_CHECKED" + S01_PATCHER_RC="NOT_RUN" + S02_PATCHER_RC="NOT_RUN" + S03_PATCHER_RC="NOT_RUN" + + printf "============================================================\n" + printf "SCENARIO S03 — SENTINELLE E VALIDAZIONE SEMANTICA\n" + printf "============================================================\n" + printf "Durata: %s secondi\n" "$duration" + printf "Catena automatica: S00 -> S01 -> S02 -> S03\n" + printf "Gate prestazionale: CHIUSO\n" + + ensure_s03 + PREPARE_RC=$? + + if [ "$PREPARE_RC" -ne 0 ]; then + printf "RISULTATO FINALE: FAIL_DEPENDENCY_CHAIN\n" + return "$PREPARE_RC" + fi + + run_prepared_s03 "$duration" +} + +ask_duration_and_run() { + scenario="$1" + + printf "Durata in secondi [%s]: " "$DEFAULT_DURATION_SECONDS" + + if ! IFS= read -r duration; then + printf "\nERRORE: input non disponibile.\n" >&2 + return 2 + fi + + if [ -z "$duration" ]; then + duration="$DEFAULT_DURATION_SECONDS" + fi + + case "$scenario" in + S01) + run_s01 "$duration" + ;; + S02) + run_s02 "$duration" + ;; + S03) + run_s03 "$duration" + ;; + *) + printf "ERRORE: scenario interno non riconosciuto: %s\n" "$scenario" >&2 + return 2 + ;; + esac +} + +interactive_menu() { + printf "============================================================\n" + printf "MARTe2 BENCHMARK SUITE\n" + printf "============================================================\n" + printf "1) S01 — catena automatica S00 -> S01, esegue e valida CycleTime\n" + printf "2) S02 — catena automatica S00 -> S01 -> S02, registra e valida il binario\n" + printf "3) S03 — catena S00 -> S01 -> S02 -> S03, valida le sentinelle\n" + printf "0) Esci\n" + printf "\nSelezione: " + + if ! IFS= read -r selection; then + printf "\nERRORE: input non disponibile.\n" >&2 + return 2 + fi + + case "$selection" in + 1|S01|s01) + ask_duration_and_run "S01" + ;; + 2|S02|s02) + ask_duration_and_run "S02" + ;; + 3|S03|s03) + ask_duration_and_run "S03" + ;; + 0) + printf "Uscita.\n" + return 0 + ;; + *) + printf "Selezione non valida: %s\n" "$selection" >&2 + return 2 + ;; + esac +} + +# S02-H2: consente di caricare le sole funzioni (per i test) senza +# eseguire il menu interattivo o il dispatch degli argomenti. +# +# La modalita' e' accettata SOLTANTO quando il file viene caricato con +# "source". Se il launcher viene eseguito direttamente con la variabile +# impostata, deve fallire in modo esplicito: un exit 0 senza avere +# eseguito alcuna run sarebbe un falso successo. +if [ "${MARTE_LAUNCHER_LIB_ONLY:-0}" = "1" ]; then + if [ "${BASH_SOURCE[0]}" != "$0" ]; then + return 0 + fi + + printf "ERRORE: MARTE_LAUNCHER_LIB_ONLY=1 e' ammesso solo con 'source'.\n" >&2 + printf "Il launcher e' stato eseguito direttamente: nessuna run avviata.\n" >&2 + exit 3 +fi + +case "$#" in + 0) + interactive_menu + exit $? + ;; + 1) + case "$1" in + S01|s01) + run_s01 "$DEFAULT_DURATION_SECONDS" + exit $? + ;; + S02|s02) + run_s02 "$DEFAULT_DURATION_SECONDS" + exit $? + ;; + S03|s03) + run_s03 "$DEFAULT_DURATION_SECONDS" + exit $? + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + print_usage >&2 + exit 2 + ;; + esac + ;; + 2) + case "$1" in + S01|s01) + run_s01 "$2" + exit $? + ;; + S02|s02) + run_s02 "$2" + exit $? + ;; + S03|s03) + run_s03 "$2" + exit $? + ;; + *) + print_usage >&2 + exit 2 + ;; + esac + ;; + *) + print_usage >&2 + exit 2 + ;; +esac diff --git a/launcher/marte_benchmark_launcher_s03h1.sh b/launcher/marte_benchmark_launcher_s03h1.sh new file mode 100755 index 0000000..0c274d6 --- /dev/null +++ b/launcher/marte_benchmark_launcher_s03h1.sh @@ -0,0 +1,572 @@ +#!/usr/bin/env bash +set -u + +LAUNCHER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CANDIDATE_ROOT="$(cd "$LAUNCHER_DIR/.." && pwd)" + +if [ -n "${MARTE_BENCHMARK_SUITE_ROOT:-}" ]; then + SUITE="$(cd "$MARTE_BENCHMARK_SUITE_ROOT" && pwd)" +else + SUITE="/home/dariodf/Scrivania/MARTE/marte-benchmark-suite" +fi + +PATCHER="$CANDIDATE_ROOT/patchers/configuration/build_s03h1_from_s02.sh" +VALIDATOR="$CANDIDATE_ROOT/scenarios/S03H1_execution_profiles/validate.sh" +CONFIG_VERIFIER="$CANDIDATE_ROOT/scenarios/S03H1_execution_profiles/verify_s03h1_config.py" +CSV_EXPORTER="$CANDIDATE_ROOT/scenarios/S03H1_execution_profiles/export_s03_csv.py" +TEST_CONFIG="$CANDIDATE_ROOT/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" +DIAGNOSTIC_CONFIG="$CANDIDATE_ROOT/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" + +RUNNER="$SUITE/common/marte_runner.sh" +LIVE_PATHS="$SUITE/common/paths.sh" +LIVE_ENVIRONMENT="$SUITE/common/environment.sh" +LIVE_OPERATIONAL_LAUNCHER="$SUITE/launcher/marte_benchmark_launcher.sh" +LIVE_PATCHER_S01="$SUITE/patchers/configuration/build_s01_from_s00.sh" +LIVE_PATCHER_S02="$SUITE/patchers/configuration/build_s02_from_s01.sh" +LIVE_PATCHER_S03="$SUITE/patchers/configuration/build_s03_from_s02.sh" +LIVE_VALIDATOR_S02="$SUITE/scenarios/S02_binary_recording/validate.sh" +LIVE_VALIDATOR_S03="$SUITE/scenarios/S03_semantic_sentinels/validate.sh" +LIVE_PARSER_S03="$SUITE/scenarios/S03_semantic_sentinels/parse_s03_binary.py" +LIVE_S00_CONFIG="$SUITE/configurations/generated/S00/smoke_s0_core.marte" +LIVE_S01_CONFIG="$SUITE/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" +LIVE_S02_CONFIG="$SUITE/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte" +LIVE_S03_CONFIG="$SUITE/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte" + +EXPECTED_TEST_CONFIG_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" +EXPECTED_DIAGNOSTIC_CONFIG_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3" +EXPECTED_PATCHER_SHA256="5db5f71db4ab8f007b2e78230c2506bed4c62a8e502fb1c08ca04eb14b645b82" +EXPECTED_VALIDATOR_SHA256="748f403a045f9dbed64910de67e75ba5529c02ab5894417a866225518c10b5e5" +EXPECTED_CONFIG_VERIFIER_SHA256="2f1c41b186bd2e79a9fb51fab0461a7a0dce104bf0852c0ec1ade2c55a017443" +EXPECTED_CSV_EXPORTER_SHA256="b413ad113e188141891b61b930a607890b8468051e8a61cb0f0454c519a51fb7" + +print_usage() { + printf "Uso:\n" + printf " %s S03H1 TEST\n" "$0" + printf " %s S03H1 DIAGNOSTIC\n" "$0" + printf "\n" + printf "La candidata non installa e non modifica la baseline S03 R3.\n" +} + +fail() { + code="$1" + shift + printf "S03_H1_LAUNCHER_STATUS=FAIL\n" >&2 + printf "S03_H1_LAUNCHER_ERROR=%s\n" "$*" >&2 + exit "$code" +} + +validate_duration() { + candidate_duration="$1" + case "$candidate_duration" in + *[!0-9]*|"") + return 1 + ;; + esac + [ "$candidate_duration" -gt 0 ] +} + +verify_hash() { + candidate_file="$1" + candidate_expected="$2" + candidate_label="$3" + if [ ! -f "$candidate_file" ]; then + printf "HASH_CHECK=%s STATUS=MISSING FILE=%s\n" \ + "$candidate_label" "$candidate_file" >&2 + return 1 + fi + candidate_actual="$(sha256sum -- "$candidate_file" | awk '{print $1}')" + printf "HASH_CHECK=%s EXPECTED=%s ACTUAL=%s FILE=%s\n" \ + "$candidate_label" "$candidate_expected" "$candidate_actual" "$candidate_file" + [ "$candidate_actual" = "$candidate_expected" ] +} + +verify_candidate_components() { + verify_hash "$PATCHER" "$EXPECTED_PATCHER_SHA256" "S03H1_PATCHER" || return 1 + verify_hash "$VALIDATOR" "$EXPECTED_VALIDATOR_SHA256" "S03H1_VALIDATOR" || return 1 + verify_hash "$CONFIG_VERIFIER" "$EXPECTED_CONFIG_VERIFIER_SHA256" "S03H1_CONFIG_VERIFIER" || return 1 + verify_hash "$CSV_EXPORTER" "$EXPECTED_CSV_EXPORTER_SHA256" "S03H1_CSV_EXPORTER" || return 1 + verify_hash "$CANDIDATE_CONFIG" "$EXPECTED_CANDIDATE_CONFIG_SHA256" "S03H1_${MODE}_CONFIG" || return 1 + return 0 +} + +PROTECTED_PATHS=( + "$LIVE_OPERATIONAL_LAUNCHER" + "$LIVE_S00_CONFIG" + "$LIVE_S01_CONFIG" + "$LIVE_S02_CONFIG" + "$LIVE_S03_CONFIG" + "$LIVE_PATCHER_S01" + "$LIVE_PATCHER_S02" + "$LIVE_PATCHER_S03" + "$LIVE_VALIDATOR_S02" + "$LIVE_VALIDATOR_S03" + "$LIVE_PARSER_S03" + "$RUNNER" + "$LIVE_PATHS" + "$LIVE_ENVIRONMENT" +) +PROTECTED_EXPECTED_HASHES=( + "7ef0419826c85c0364aad2403db4b8340cd815e9d9ce17edfbe5da8e6fbf2683" + "49a9b48595aab88cd8fd11d1e58ace4ab11d7d0a649e9b143bdc25c8866253d5" + "1513e1ca72eba56d53079ac7328908f12ddc72772456e2d75bcbacb6465d2427" + "8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1" + "0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4" + "a8cced1ea1711806863606e27c3ba52a3e72794dbe8c36c253570709ce3f7ccb" + "8ac51d09f6f7984af3522e373bab59e2553775c418f69a53ccb93d8d1b893391" + "96c778ba6a15353fd969b19960706d7c0ca810c0c6fd61a14b5f88960e4e2b7a" + "7cd29b216b5edc3c72750a3326aebe5a9fdfb3b7522f1d7a6e8b0a1a97b3d60a" + "95caf207c4d29037c4973d756ff5a7004a3360aad9656d068948e222a4ba0c98" + "91c4d66770197e1986ed91eb6a4870e7da7df5a522ffdf4329b6d9250f238307" + "9e4f1a6b0dabc244fd0726fd18bf07e86b29a9fc6a76d54fcaf61c79f13c9633" + "468d92c3eb048b7137ea52a768f459d2d1f26f83f5a5c174d3333d032c9583c6" + "b1faa567e480c326c72107b886f387f108907900804621337524f1f856f056ac" +) + +verify_protected_baseline() { + candidate_index=0 + while [ "$candidate_index" -lt "${#PROTECTED_PATHS[@]}" ]; do + verify_hash \ + "${PROTECTED_PATHS[$candidate_index]}" \ + "${PROTECTED_EXPECTED_HASHES[$candidate_index]}" \ + "PROTECTED_${candidate_index}" || return 1 + candidate_index=$((candidate_index + 1)) + done + return 0 +} + +write_protected_hashes() { + candidate_destination="$1" + sha256sum -- "${PROTECTED_PATHS[@]}" >"$candidate_destination" +} + +parse_contract_field() { + candidate_field="$1" + candidate_file="$2" + PARSED_FIELD_VALUE="" + PARSED_FIELD_ERROR="" + if [ ! -f "$candidate_file" ]; then + PARSED_FIELD_ERROR="NO_FILE" + return 1 + fi + candidate_count="$(grep -Ec "^${candidate_field}=[A-Z][A-Z0-9_]*$" "$candidate_file" || true)" + candidate_any_count="$(grep -Ec "^${candidate_field}=" "$candidate_file" || true)" + if [ "$candidate_any_count" -eq 0 ]; then + PARSED_FIELD_ERROR="MISSING" + return 1 + fi + if [ "$candidate_any_count" -ne 1 ]; then + PARSED_FIELD_ERROR="DUPLICATED" + return 1 + fi + if [ "$candidate_count" -ne 1 ]; then + PARSED_FIELD_ERROR="MALFORMED" + return 1 + fi + PARSED_FIELD_VALUE="$(sed -n "s/^${candidate_field}=//p" "$candidate_file")" + return 0 +} + +classify_s03h1_run() { + classify_validator_rc="$1" + classify_tee_rc="$2" + classify_validation_file="$3" + classify_csv_rc="$4" + classify_invariance_status="$5" + + FINAL_RC=20 + FINAL_RESULT="INCONCLUSIVE" + BLOCK_REASON="NONE" + PARSED_MODE_STATUS="NOT_EVALUATED" + PARSED_PROFILE_CONFIG_STATUS="NOT_EVALUATED" + PARSED_OBSERVABILITY_STATUS="NOT_EVALUATED" + PARSED_FUNCTIONAL_STATUS="NOT_EVALUATED" + PARSED_SEMANTIC_STATUS="NOT_EVALUATED" + PARSED_TIMING_STATUS="NOT_EVALUATED" + PARSED_MEASUREMENT_ELIGIBILITY="NOT_EVALUATED" + + if [ "$classify_tee_rc" -ne 0 ]; then + FINAL_RC=67 + FINAL_RESULT="FAIL_VALIDATION_LOG" + BLOCK_REASON="VALIDATION_LOG_NOT_WRITTEN" + return 0 + fi + + CONTRACT_PARSE_ERROR="NONE" + for candidate_field in \ + MODE_STATUS \ + PROFILE_CONFIG_STATUS \ + OBSERVABILITY_STATUS \ + FUNCTIONAL_STATUS \ + SEMANTIC_STATUS \ + TIMING_STATUS \ + MEASUREMENT_ELIGIBILITY + do + if parse_contract_field "$candidate_field" "$classify_validation_file"; then + case "$candidate_field" in + MODE_STATUS) PARSED_MODE_STATUS="$PARSED_FIELD_VALUE" ;; + PROFILE_CONFIG_STATUS) PARSED_PROFILE_CONFIG_STATUS="$PARSED_FIELD_VALUE" ;; + OBSERVABILITY_STATUS) PARSED_OBSERVABILITY_STATUS="$PARSED_FIELD_VALUE" ;; + FUNCTIONAL_STATUS) PARSED_FUNCTIONAL_STATUS="$PARSED_FIELD_VALUE" ;; + SEMANTIC_STATUS) PARSED_SEMANTIC_STATUS="$PARSED_FIELD_VALUE" ;; + TIMING_STATUS) PARSED_TIMING_STATUS="$PARSED_FIELD_VALUE" ;; + MEASUREMENT_ELIGIBILITY) PARSED_MEASUREMENT_ELIGIBILITY="$PARSED_FIELD_VALUE" ;; + esac + elif [ "$CONTRACT_PARSE_ERROR" = "NONE" ]; then + CONTRACT_PARSE_ERROR="${candidate_field}_${PARSED_FIELD_ERROR}" + fi + done + + if [ "$CONTRACT_PARSE_ERROR" != "NONE" ]; then + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_S03H1_CONTRACT" + BLOCK_REASON="$CONTRACT_PARSE_ERROR" + return 0 + fi + if [ "$PARSED_MEASUREMENT_ELIGIBILITY" != "NO" ]; then + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_S03H1_CONTRACT" + BLOCK_REASON="ELIGIBILITY_CONTRACT_VIOLATION_${PARSED_MEASUREMENT_ELIGIBILITY}" + return 0 + fi + if [ "$classify_invariance_status" != "PASS" ]; then + FINAL_RC=71 + FINAL_RESULT="FAIL_PROTECTED_BASELINE_CHANGED" + BLOCK_REASON="PROTECTED_BASELINE_INVARIANCE_${classify_invariance_status}" + return 0 + fi + if [ "$classify_csv_rc" -ne 0 ]; then + FINAL_RC=70 + FINAL_RESULT="FAIL_DERIVED_CSV_EXPORT" + BLOCK_REASON="CSV_EXPORT_RC_${classify_csv_rc}" + return 0 + fi + + case "$classify_validator_rc" in + 0|10|11) + ;; + 20|21) + FINAL_RC=20 + FINAL_RESULT="S03H1_VALIDATION_INCONCLUSIVE" + BLOCK_REASON="VALIDATOR_RC_${classify_validator_rc}" + return 0 + ;; + *) + FINAL_RC=68 + FINAL_RESULT="FAIL_VALIDATOR_EXIT" + BLOCK_REASON="UNRECOGNISED_VALIDATOR_RC_${classify_validator_rc}" + return 0 + ;; + esac + + for candidate_status in \ + "$PARSED_MODE_STATUS" \ + "$PARSED_PROFILE_CONFIG_STATUS" \ + "$PARSED_OBSERVABILITY_STATUS" \ + "$PARSED_FUNCTIONAL_STATUS" \ + "$PARSED_SEMANTIC_STATUS" + do + if [ "$candidate_status" != "PASS" ]; then + FINAL_RC=25 + FINAL_RESULT="S03H1_CONTRACT_STATUS_NOT_PASS" + BLOCK_REASON="REQUIRED_STATUS_${candidate_status}" + return 0 + fi + done + + case "$PARSED_TIMING_STATUS" in + NOT_VALID_*) + FINAL_RC=14 + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_NOT_VALID" + BLOCK_REASON="$PARSED_TIMING_STATUS" + ;; + UNASSESSED_*) + FINAL_RC=15 + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_UNASSESSED" + BLOCK_REASON="$PARSED_TIMING_STATUS" + ;; + *) + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_TIMING_CONTRACT" + BLOCK_REASON="TIMING_STATUS_${PARSED_TIMING_STATUS}" + ;; + esac + return 0 +} + +run_s03h1() { + duration="$1" + MODE="$2" + + case "$MODE" in + TEST) + CANDIDATE_CONFIG="$TEST_CONFIG" + EXPECTED_CANDIDATE_CONFIG_SHA256="$EXPECTED_TEST_CONFIG_SHA256" + ;; + DIAGNOSTIC) + CANDIDATE_CONFIG="$DIAGNOSTIC_CONFIG" + EXPECTED_CANDIDATE_CONFIG_SHA256="$EXPECTED_DIAGNOSTIC_CONFIG_SHA256" + ;; + *) + printf "ERRORE: MODE non riconosciuto: %s\n" "$MODE" >&2 + return 2 + ;; + esac + + if [ ! -d "$SUITE" ]; then + printf "ERRORE: suite operativa assente: %s\n" "$SUITE" >&2 + return 50 + fi + if ! verify_candidate_components; then + printf "ERRORE: integrita' candidata non dimostrata.\n" >&2 + return 51 + fi + if ! verify_protected_baseline; then + printf "ERRORE: baseline protetta assente o con hash inatteso.\n" >&2 + return 52 + fi + + WORK_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/s03h1_launcher.XXXXXX")" || return 53 + trap 'rm -rf -- "$WORK_ROOT"' EXIT HUP INT TERM + GENERATED_CONFIG="$WORK_ROOT/generated_${MODE}.marte" + PATCHER_LOG="$WORK_ROOT/patcher.log" + PROTECTED_BEFORE="$WORK_ROOT/protected_baseline_before.sha256" + CAPTURE="$WORK_ROOT/runner_capture.log" + + "$PATCHER" "$LIVE_S02_CONFIG" "$GENERATED_CONFIG" "$MODE" >"$PATCHER_LOG" 2>&1 + PATCHER_RC=$? + if [ "$PATCHER_RC" -ne 0 ]; then + printf "ERRORE: patcher candidato fallito rc=%s\n" "$PATCHER_RC" >&2 + cat -- "$PATCHER_LOG" >&2 + return 54 + fi + if ! cmp -s -- "$GENERATED_CONFIG" "$CANDIDATE_CONFIG"; then + printf "ERRORE: build candidato non coincide con config confezionata.\n" >&2 + return 55 + fi + + write_protected_hashes "$PROTECTED_BEFORE" || return 56 + + if [ ! -f "$LIVE_PATHS" ]; then + printf "ERRORE: paths.sh assente: %s\n" "$LIVE_PATHS" >&2 + return 57 + fi + # shellcheck source=/dev/null + source "$LIVE_PATHS" + CONSTANT_GAM_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/GAMs/ConstantGAM" + CONSTANT_GAM_LIB="$CONSTANT_GAM_LIB_DIR/ConstantGAM.so" + if [ ! -f "$CONSTANT_GAM_LIB" ]; then + printf "ERRORE: ConstantGAM assente: %s\n" "$CONSTANT_GAM_LIB" >&2 + return 58 + fi + case ":${LD_LIBRARY_PATH-}:" in + *":${CONSTANT_GAM_LIB_DIR}:"*) ;; + *) + if [ -n "${LD_LIBRARY_PATH-}" ]; then + LD_LIBRARY_PATH="${CONSTANT_GAM_LIB_DIR}:${LD_LIBRARY_PATH}" + else + LD_LIBRARY_PATH="$CONSTANT_GAM_LIB_DIR" + fi + ;; + esac + export LD_LIBRARY_PATH + + printf "============================================================\n" + printf "S03-H1 EXECUTION PROFILES - RUN CANDIDATA\n" + printf "============================================================\n" + printf "MODE=%s\n" "$MODE" + printf "DURATION=%s\n" "$duration" + printf "CURRENT_OPERATIONAL_BASELINE=S03_R3_INSTALLED\n" + printf "S03_H1_INSTALLATION_AUTHORIZED=NO\n" + + "$RUNNER" \ + "S03H1_${MODE}_launcher" \ + "$CANDIDATE_CONFIG" \ + "State1" \ + "$duration" \ + 2>&1 | tee "$CAPTURE" + RUNNER_CODES=("${PIPESTATUS[@]}") + RUNNER_RC="${RUNNER_CODES[0]}" + CAPTURE_TEE_RC="${RUNNER_CODES[1]}" + if [ "$RUNNER_RC" -ne 0 ]; then + printf "ERRORE: common runner fallito rc=%s\n" "$RUNNER_RC" >&2 + return 60 + fi + if [ "$CAPTURE_TEE_RC" -ne 0 ]; then + printf "ERRORE: capture common runner fallita.\n" >&2 + return 61 + fi + + RUN_DIR="$(sed -n 's/^RUN_DIR=//p' "$CAPTURE" | tail -n 1)" + if [ -z "$RUN_DIR" ] || [ ! -d "$RUN_DIR" ]; then + printf "ERRORE: RUN_DIR non individuata o assente: %s\n" "$RUN_DIR" >&2 + return 62 + fi + + RUN_LOG="$RUN_DIR/run.log" + RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt" + RUN_HASHES="$RUN_DIR/hashes.sha256" + OUTPUT_FILE="$RUN_DIR/s03_output.bin" + CSV_FILE="$RUN_DIR/s03_output.csv" + MODE_FILE="$RUN_DIR/s03h1_mode.txt" + VALIDATION_LOG="$RUN_DIR/validation.txt" + CSV_REPORT="$RUN_DIR/csv_export.txt" + LAUNCHER_SUMMARY="$RUN_DIR/launcher_summary.txt" + PROTECTED_BEFORE_RUN="$RUN_DIR/protected_baseline_before.sha256" + PROTECTED_AFTER_RUN="$RUN_DIR/protected_baseline_after.sha256" + INVARIANCE_REPORT="$RUN_DIR/protected_baseline_invariance.txt" + + cp -- "$PROTECTED_BEFORE" "$PROTECTED_BEFORE_RUN" + { + printf "SCENARIO=S03H1\n" + printf "MODE=%s\n" "$MODE" + printf "CANDIDATE_CONFIG_SHA256=%s\n" "$EXPECTED_CANDIDATE_CONFIG_SHA256" + printf "S03_H1_INSTALLATION_AUTHORIZED=NO\n" + } >"$MODE_FILE" + + sha256sum -- \ + "$MODE_FILE" \ + "${BASH_SOURCE[0]}" \ + "$PATCHER" \ + "$VALIDATOR" \ + "$CONFIG_VERIFIER" \ + "$CSV_EXPORTER" \ + "$LIVE_PARSER_S03" \ + "$RUN_LOG" \ + "$RUNNER_SUMMARY" \ + "$OUTPUT_FILE" \ + "$PROTECTED_BEFORE_RUN" \ + "${PROTECTED_PATHS[@]}" \ + >>"$RUN_HASHES" + + S03_BASELINE_PARSER="$LIVE_PARSER_S03" \ + timeout 30s "$VALIDATOR" "$MODE" "$RUN_DIR" 2>&1 | tee "$VALIDATION_LOG" + VALIDATION_CODES=("${PIPESTATUS[@]}") + VALIDATOR_RC="${VALIDATION_CODES[0]}" + VALIDATION_TEE_RC="${VALIDATION_CODES[1]}" + + "$CSV_EXPORTER" "$OUTPUT_FILE" "$CSV_FILE" >"$CSV_REPORT" 2>&1 + CSV_RC=$? + + write_protected_hashes "$PROTECTED_AFTER_RUN" + if cmp -s -- "$PROTECTED_BEFORE_RUN" "$PROTECTED_AFTER_RUN"; then + PROTECTED_BASELINE_INVARIANCE="PASS" + else + PROTECTED_BASELINE_INVARIANCE="FAIL" + fi + { + printf "PROTECTED_BASELINE_INVARIANCE=%s\n" "$PROTECTED_BASELINE_INVARIANCE" + printf "CURRENT_OPERATIONAL_BASELINE=S03_R3_INSTALLED\n" + printf "S03_H1_INSTALLATION_AUTHORIZED=NO\n" + } >"$INVARIANCE_REPORT" + + for evidence_file in \ + "$VALIDATION_LOG" \ + "$CSV_REPORT" \ + "$PROTECTED_AFTER_RUN" \ + "$INVARIANCE_REPORT" + do + if [ -f "$evidence_file" ]; then + sha256sum -- "$evidence_file" >>"$RUN_HASHES" + fi + done + if [ -f "$CSV_FILE" ]; then + sha256sum -- "$CSV_FILE" >>"$RUN_HASHES" + fi + + classify_s03h1_run \ + "$VALIDATOR_RC" \ + "$VALIDATION_TEE_RC" \ + "$VALIDATION_LOG" \ + "$CSV_RC" \ + "$PROTECTED_BASELINE_INVARIANCE" + + { + printf "============================================================\n" + printf "RIEPILOGO LAUNCHER S03-H1 CANDIDATO\n" + printf "============================================================\n" + printf "SCENARIO=S03H1\n" + printf "MODE=%s\n" "$MODE" + printf "DURATION=%s\n" "$duration" + printf "RUN_DIR=%s\n" "$RUN_DIR" + printf "RUNNER_RC=%s\n" "$RUNNER_RC" + printf "VALIDATOR_RC=%s\n" "$VALIDATOR_RC" + printf "CSV_EXPORT_RC=%s\n" "$CSV_RC" + printf "CSV_STATUS=%s\n" "$( [ "$CSV_RC" -eq 0 ] && printf PASS || printf FAIL )" + printf "MODE_STATUS=%s\n" "$PARSED_MODE_STATUS" + printf "PROFILE_CONFIG_STATUS=%s\n" "$PARSED_PROFILE_CONFIG_STATUS" + printf "OBSERVABILITY_STATUS=%s\n" "$PARSED_OBSERVABILITY_STATUS" + printf "FUNCTIONAL_STATUS=%s\n" "$PARSED_FUNCTIONAL_STATUS" + printf "SEMANTIC_STATUS=%s\n" "$PARSED_SEMANTIC_STATUS" + printf "TIMING_STATUS=%s\n" "$PARSED_TIMING_STATUS" + printf "MEASUREMENT_ELIGIBILITY=NO\n" + printf "PROTECTED_BASELINE_INVARIANCE=%s\n" "$PROTECTED_BASELINE_INVARIANCE" + printf "BLOCK_REASON=%s\n" "$BLOCK_REASON" + printf "FINAL_RESULT=%s\n" "$FINAL_RESULT" + printf "FINAL_RC=%s\n" "$FINAL_RC" + printf "S03_H1_INSTALLATION_AUTHORIZED=NO\n" + printf "CURRENT_OPERATIONAL_BASELINE=S03_R3_INSTALLED\n" + printf "PERFORMANCE_MEASUREMENT_AUTHORIZED=NO\n" + printf "S04_AUTHORIZED=NO\n" + printf "MD00_AUTHORIZED=NO\n" + } | tee "$LAUNCHER_SUMMARY" + SUMMARY_CODES=("${PIPESTATUS[@]}") + if [ "${SUMMARY_CODES[1]}" -ne 0 ]; then + FINAL_RC=69 + else + sha256sum -- "$LAUNCHER_SUMMARY" >>"$RUN_HASHES" + fi + + printf "RUN_DIR=%s\n" "$RUN_DIR" + printf "RISULTATO_FINALE=%s\n" "$FINAL_RESULT" + printf "FINAL_RC=%s\n" "$FINAL_RC" + + rm -rf -- "$WORK_ROOT" + trap - EXIT HUP INT TERM + return "$FINAL_RC" +} + +if [ "${S03H1_LAUNCHER_LIB_ONLY:-0}" = "1" ]; then + if [ "${BASH_SOURCE[0]}" != "$0" ]; then + return 0 + fi + printf "ERRORE: S03H1_LAUNCHER_LIB_ONLY=1 e' ammesso solo con source.\n" >&2 + exit 3 +fi + +case "$#" in + 1) + case "$1" in + -h|--help) + print_usage + exit 0 + ;; + *) + print_usage >&2 + exit 2 + ;; + esac + ;; + 3) + if [ "$1" != "S03H1" ] && [ "$1" != "s03h1" ]; then + print_usage >&2 + exit 2 + fi + if ! validate_duration "$2"; then + printf "ERRORE: durata non valida: %s\n" "$2" >&2 + exit 2 + fi + case "$3" in + TEST|DIAGNOSTIC) ;; + *) + printf "ERRORE: MODE non riconosciuto: %s\n" "$3" >&2 + exit 2 + ;; + esac + run_s03h1 "$2" "$3" + exit $? + ;; + *) + print_usage >&2 + exit 2 + ;; +esac diff --git a/launcher/marte_benchmark_launcher_s03h3.sh b/launcher/marte_benchmark_launcher_s03h3.sh new file mode 100755 index 0000000..c903e65 --- /dev/null +++ b/launcher/marte_benchmark_launcher_s03h3.sh @@ -0,0 +1,669 @@ +#!/usr/bin/env bash +set -u +set -o pipefail + +LAUNCHER_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +INSTALLED_SUITE="$(cd -- "$LAUNCHER_DIR/.." && pwd -P)" +WORK_ROOT="" + +print_usage() { + printf 'Uso:\n' + printf ' %s S03H3 MINIMAL\n' "$0" + printf ' %s S03H3 TEST\n' "$0" + printf ' %s S03H3 DIAGNOSTIC\n' "$0" +} + +fail() { + local code="$1" + local reason="$2" + printf 'S03_H3_LAUNCHER_STATUS=FAIL\n' >&2 + printf 'BLOCK_REASON=%s\n' "$reason" >&2 + exit "$code" +} + +is_positive_decimal() { + local value="$1" + [[ "$value" =~ ^[0-9]+$ ]] && [[ "$value" =~ [1-9] ]] +} + +verify_hash() { + local file="$1" + local expected="$2" + local label="$3" + local actual + if [ ! -f "$file" ]; then + printf 'HASH_CHECK=%s STATUS=MISSING FILE=%s\n' "$label" "$file" >&2 + return 1 + fi + actual="$(sha256sum -- "$file" | awk '{print $1}')" || return 1 + printf 'HASH_CHECK=%s EXPECTED=%s ACTUAL=%s FILE=%s\n' \ + "$label" "$expected" "$actual" "$file" + [ "$actual" = "$expected" ] +} + +readonly -a HISTORICAL_RELATIVE_PATHS=( + "launcher/marte_benchmark_launcher.sh" + "configurations/generated/S00/smoke_s0_core.marte" + "configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" + "configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte" + "configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte" + "patchers/configuration/build_s01_from_s00.sh" + "patchers/configuration/build_s02_from_s01.sh" + "patchers/configuration/build_s03_from_s02.sh" + "scenarios/S02_binary_recording/validate.sh" + "scenarios/S03_semantic_sentinels/validate.sh" + "scenarios/S03_semantic_sentinels/parse_s03_binary.py" + "common/marte_runner.sh" + "common/paths.sh" + "common/environment.sh" + "launcher/marte_benchmark_launcher_s03h1.sh" + "patchers/configuration/build_s03h1_from_s02.sh" + "scenarios/S03H1_execution_profiles/validate.sh" + "scenarios/S03H1_execution_profiles/verify_s03h1_config.py" + "scenarios/S03H1_execution_profiles/export_s03_csv.py" + "configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" + "configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" +) + +readonly -a HISTORICAL_EXPECTED_HASHES=( + "7ef0419826c85c0364aad2403db4b8340cd815e9d9ce17edfbe5da8e6fbf2683" + "49a9b48595aab88cd8fd11d1e58ace4ab11d7d0a649e9b143bdc25c8866253d5" + "1513e1ca72eba56d53079ac7328908f12ddc72772456e2d75bcbacb6465d2427" + "8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1" + "0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4" + "a8cced1ea1711806863606e27c3ba52a3e72794dbe8c36c253570709ce3f7ccb" + "8ac51d09f6f7984af3522e373bab59e2553775c418f69a53ccb93d8d1b893391" + "96c778ba6a15353fd969b19960706d7c0ca810c0c6fd61a14b5f88960e4e2b7a" + "7cd29b216b5edc3c72750a3326aebe5a9fdfb3b7522f1d7a6e8b0a1a97b3d60a" + "95caf207c4d29037c4973d756ff5a7004a3360aad9656d068948e222a4ba0c98" + "91c4d66770197e1986ed91eb6a4870e7da7df5a522ffdf4329b6d9250f238307" + "9e4f1a6b0dabc244fd0726fd18bf07e86b29a9fc6a76d54fcaf61c79f13c9633" + "468d92c3eb048b7137ea52a768f459d2d1f26f83f5a5c174d3333d032c9583c6" + "b1faa567e480c326c72107b886f387f108907900804621337524f1f856f056ac" + "f420afdff1f0a96026d14820662a4257c0da4924a491fe00182f838414e06152" + "5db5f71db4ab8f007b2e78230c2506bed4c62a8e502fb1c08ca04eb14b645b82" + "748f403a045f9dbed64910de67e75ba5529c02ab5894417a866225518c10b5e5" + "2f1c41b186bd2e79a9fb51fab0461a7a0dce104bf0852c0ec1ade2c55a017443" + "b413ad113e188141891b61b930a607890b8468051e8a61cb0f0454c519a51fb7" + "d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" + "cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3" +) + +readonly -a S03H3_ADD_RELATIVE_PATHS=( + "launcher/marte_benchmark_launcher_s03h3.sh" + "patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh" + "scenarios/S03H3_execution_profiles/validate.sh" + "scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py" + "configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte" +) + +readonly EXPECTED_PATCHER_SHA256="b3ba3c195b33a028e7ae21ba92b90a070a5e3526b455f9e3dbcebe023af05e2e" +readonly EXPECTED_VALIDATOR_SHA256="8574636315340c785f41dca37eaac097cd3b59958e7fd1160444b9c21768f2d5" +readonly EXPECTED_MINIMAL_VERIFIER_SHA256="c8d2d4880ed9cb0e8f23427617e989f5f70e82a990a49489f2863a7b3fc2cb95" +readonly EXPECTED_MINIMAL_CONFIG_SHA256="2ee67c48d748e79ec7fdbc748bc4371eb02590d7522b41990d5cb2a3649796ec" +readonly EXPECTED_TEST_CONFIG_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" +readonly EXPECTED_DIAGNOSTIC_CONFIG_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3" + +resolve_suite() { + if [ -n "${MARTE_BENCHMARK_SUITE_ROOT:-}" ]; then + [[ "$MARTE_BENCHMARK_SUITE_ROOT" = /* ]] || \ + fail 50 "TEST_OVERRIDE_NOT_ABSOLUTE" + [ -d "$MARTE_BENCHMARK_SUITE_ROOT" ] || \ + fail 50 "TEST_OVERRIDE_ROOT_MISSING" + SUITE="$(cd -- "$MARTE_BENCHMARK_SUITE_ROOT" && pwd -P)" + [ -n "$SUITE" ] && [ "$SUITE" != "/" ] || \ + fail 50 "TEST_OVERRIDE_ROOT_UNSAFE" + if [ "$SUITE" != "$INSTALLED_SUITE" ]; then + [ "${S03H3_TEST_ONLY_OVERRIDE:-NO}" = "YES" ] || \ + fail 50 "TEST_OVERRIDE_NOT_ACKNOWLEDGED" + fi + elif [ "${S03H3_TEST_ONLY_OVERRIDE:-NO}" = "YES" ]; then + fail 50 "TEST_OVERRIDE_ROOT_NOT_PROVIDED" + else + SUITE="$INSTALLED_SUITE" + fi + readonly SUITE +} + +verify_historical_baseline() { + local index=0 + while [ "$index" -lt "${#HISTORICAL_RELATIVE_PATHS[@]}" ]; do + verify_hash \ + "$SUITE/${HISTORICAL_RELATIVE_PATHS[$index]}" \ + "${HISTORICAL_EXPECTED_HASHES[$index]}" \ + "HISTORICAL_${index}" || return 1 + index=$((index + 1)) + done +} + +verify_candidate_components() { + verify_hash "$PATCHER" "$EXPECTED_PATCHER_SHA256" "S03H3_PATCHER" || return 1 + verify_hash "$VALIDATOR" "$EXPECTED_VALIDATOR_SHA256" "S03H3_VALIDATOR" || return 1 + verify_hash "$MINIMAL_VERIFIER" "$EXPECTED_MINIMAL_VERIFIER_SHA256" "S03H3_MINIMAL_VERIFIER" || return 1 + verify_hash "$MINIMAL_CONFIG" "$EXPECTED_MINIMAL_CONFIG_SHA256" "S03H3_MINIMAL_CONFIG" || return 1 +} + +build_protected_paths() { + PROTECTED_PATHS=() + local relative + for relative in "${HISTORICAL_RELATIVE_PATHS[@]}"; do + PROTECTED_PATHS+=("$SUITE/$relative") + done + for relative in "${S03H3_ADD_RELATIVE_PATHS[@]}"; do + PROTECTED_PATHS+=("$SUITE/$relative") + done + PROTECTED_PATHS+=("$SUITE/launcher/marte_benchmark_menu.sh") +} + +write_protected_snapshot() { + local destination="$1" + [ "${#PROTECTED_PATHS[@]}" -eq 27 ] || return 1 + sha256sum -- "${PROTECTED_PATHS[@]}" >"$destination" +} + +write_protected_path_list() { + local destination="$1" + printf '%s\n' "${PROTECTED_PATHS[@]}" >"$destination" +} + +parse_contract_field() { + local field="$1" + local file="$2" + local count + PARSED_FIELD_VALUE="" + PARSED_FIELD_ERROR="" + count="$(grep -Ec "^${field}=" "$file" || true)" + if [ "$count" -eq 0 ]; then + PARSED_FIELD_ERROR="MISSING" + return 1 + fi + if [ "$count" -ne 1 ]; then + PARSED_FIELD_ERROR="DUPLICATED" + return 1 + fi + PARSED_FIELD_VALUE="$(sed -n "s/^${field}=//p" "$file")" + [ -n "$PARSED_FIELD_VALUE" ] || { + PARSED_FIELD_ERROR="MALFORMED" + return 1 + } +} + +classify_s03h3_run() { + local validator_rc="$1" + local validation_tee_rc="$2" + local validation_file="$3" + local csv_rc="$4" + local invariance_status="$5" + local dependency_status="$6" + local requested_mode="$7" + local field + + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_S03H3_CONTRACT" + BLOCK_REASON="NOT_EVALUATED" + PARSED_MODE_STATUS="NOT_EVALUATED" + PARSED_PROFILE_CONFIG_STATUS="NOT_EVALUATED" + PARSED_PROFILE_RUNTIME_STATUS="NOT_EVALUATED" + PARSED_OBSERVABILITY_STATUS="NOT_EVALUATED" + PARSED_FUNCTIONAL_STATUS="NOT_EVALUATED" + PARSED_SEMANTIC_STATUS="NOT_EVALUATED" + PARSED_TIMING_STATUS="NOT_EVALUATED" + PARSED_MEASUREMENT_ELIGIBILITY="NOT_EVALUATED" + PARSED_COMPLETE_SAMPLE_COUNT="NOT_EVALUATED" + + if [ "$dependency_status" != "PASS" ]; then + FINAL_RC=51 + FINAL_RESULT="FAIL_DEPENDENCY_INTEGRITY" + BLOCK_REASON="DEPENDENCY_INTEGRITY_NOT_PASS" + return 0 + fi + if [ "$validation_tee_rc" -ne 0 ]; then + FINAL_RC=67 + FINAL_RESULT="FAIL_VALIDATION_LOG" + BLOCK_REASON="VALIDATION_LOG_NOT_WRITTEN" + return 0 + fi + case "$validator_rc" in + 0|10|11|20|21) ;; + *) + FINAL_RC=68 + FINAL_RESULT="FAIL_VALIDATOR_EXIT" + BLOCK_REASON="UNRECOGNIZED_VALIDATOR_RC_${validator_rc}" + return 0 + ;; + esac + if [ "$csv_rc" -ne 0 ]; then + FINAL_RC=70 + FINAL_RESULT="FAIL_DERIVED_CSV_EXPORT" + BLOCK_REASON="CSV_EXPORT_RC_${csv_rc}" + return 0 + fi + if [ "$invariance_status" != "PASS" ]; then + FINAL_RC=71 + FINAL_RESULT="FAIL_PROTECTED_BASELINE_CHANGED" + BLOCK_REASON="PROTECTED_BASELINE_INVARIANCE_${invariance_status}" + return 0 + fi + + CONTRACT_PARSE_ERROR="NONE" + for field in \ + MODE \ + MODE_STATUS \ + PROFILE_CONFIG_STATUS \ + PROFILE_RUNTIME_STATUS \ + OBSERVABILITY_STATUS \ + OBSERVABILITY_REASON \ + FUNCTIONAL_STATUS \ + SEMANTIC_STATUS \ + TIMING_STATUS \ + MEASUREMENT_ELIGIBILITY \ + COMPLETE_SAMPLE_COUNT \ + VALIDATION_RESULT + do + if parse_contract_field "$field" "$validation_file"; then + case "$field" in + MODE) PARSED_MODE="$PARSED_FIELD_VALUE" ;; + MODE_STATUS) PARSED_MODE_STATUS="$PARSED_FIELD_VALUE" ;; + PROFILE_CONFIG_STATUS) PARSED_PROFILE_CONFIG_STATUS="$PARSED_FIELD_VALUE" ;; + PROFILE_RUNTIME_STATUS) PARSED_PROFILE_RUNTIME_STATUS="$PARSED_FIELD_VALUE" ;; + OBSERVABILITY_STATUS) PARSED_OBSERVABILITY_STATUS="$PARSED_FIELD_VALUE" ;; + OBSERVABILITY_REASON) PARSED_OBSERVABILITY_REASON="$PARSED_FIELD_VALUE" ;; + FUNCTIONAL_STATUS) PARSED_FUNCTIONAL_STATUS="$PARSED_FIELD_VALUE" ;; + SEMANTIC_STATUS) PARSED_SEMANTIC_STATUS="$PARSED_FIELD_VALUE" ;; + TIMING_STATUS) PARSED_TIMING_STATUS="$PARSED_FIELD_VALUE" ;; + MEASUREMENT_ELIGIBILITY) PARSED_MEASUREMENT_ELIGIBILITY="$PARSED_FIELD_VALUE" ;; + COMPLETE_SAMPLE_COUNT) PARSED_COMPLETE_SAMPLE_COUNT="$PARSED_FIELD_VALUE" ;; + VALIDATION_RESULT) PARSED_VALIDATION_RESULT="$PARSED_FIELD_VALUE" ;; + esac + elif [ "$CONTRACT_PARSE_ERROR" = "NONE" ]; then + CONTRACT_PARSE_ERROR="${field}_${PARSED_FIELD_ERROR}" + fi + done + + if [ "$CONTRACT_PARSE_ERROR" = "NONE" ]; then + [ "$PARSED_MODE" = "$requested_mode" ] || CONTRACT_PARSE_ERROR="MODE_MISMATCH" + for field in \ + "$PARSED_MODE_STATUS" \ + "$PARSED_PROFILE_CONFIG_STATUS" \ + "$PARSED_PROFILE_RUNTIME_STATUS" \ + "$PARSED_OBSERVABILITY_STATUS" \ + "$PARSED_FUNCTIONAL_STATUS" \ + "$PARSED_SEMANTIC_STATUS" + do + case "$field" in + PASS|FAIL|NOT_DEMONSTRATED) ;; + *) CONTRACT_PARSE_ERROR="STATUS_VOCABULARY_INVALID"; break ;; + esac + done + fi + if [ "$CONTRACT_PARSE_ERROR" = "NONE" ]; then + [[ "$PARSED_OBSERVABILITY_REASON" =~ ^[A-Z][A-Z0-9_]*$ ]] || \ + CONTRACT_PARSE_ERROR="OBSERVABILITY_REASON_MALFORMED" + [[ "$PARSED_TIMING_STATUS" =~ ^(NOT_VALID_|UNASSESSED_)[A-Z0-9_]+$ ]] || \ + CONTRACT_PARSE_ERROR="TIMING_STATUS_UNRECOGNIZED" + [ "$PARSED_MEASUREMENT_ELIGIBILITY" = "NO" ] || \ + CONTRACT_PARSE_ERROR="ELIGIBILITY_CONTRACT_VIOLATION" + [[ "$PARSED_COMPLETE_SAMPLE_COUNT" =~ ^[0-9]+$ ]] || \ + CONTRACT_PARSE_ERROR="COMPLETE_SAMPLE_COUNT_MALFORMED" + [[ "$PARSED_VALIDATION_RESULT" =~ ^[A-Z][A-Z0-9_]*$ ]] || \ + CONTRACT_PARSE_ERROR="VALIDATION_RESULT_MALFORMED" + if [ "$PARSED_OBSERVABILITY_STATUS" = "PASS" ]; then + [ "$PARSED_OBSERVABILITY_REASON" = "NONE" ] || \ + CONTRACT_PARSE_ERROR="OBSERVABILITY_REASON_CONFLICT" + else + [ "$PARSED_OBSERVABILITY_REASON" != "NONE" ] || \ + CONTRACT_PARSE_ERROR="OBSERVABILITY_REASON_MISSING" + fi + case "$validator_rc:$PARSED_VALIDATION_RESULT" in + 0:PASS_CLEAN_SHUTDOWN|\ + 10:PASS_KNOWN_SHUTDOWN_ANOMALY|\ + 11:PASS_UNCLASSIFIED_SHUTDOWN_ANOMALY|\ + 20:FUNCTIONAL_OR_SEMANTIC_NOT_DEMONSTRATED|\ + 21:FAIL_REQUIRED_INPUT) + ;; + *) + CONTRACT_PARSE_ERROR="VALIDATION_RESULT_RC_CONFLICT" + ;; + esac + fi + + if [ "$requested_mode" = "MINIMAL" ]; then + for field in \ + CANONICAL_STRUCTURAL_COMPLETENESS_STATUS \ + MINIMAL_RUNTIME_CONFIG_STATUS + do + if parse_contract_field "$field" "$validation_file"; then + [ "$PARSED_FIELD_VALUE" = "PASS" ] || \ + CONTRACT_PARSE_ERROR="${field}_NOT_PASS" + else + CONTRACT_PARSE_ERROR="${field}_${PARSED_FIELD_ERROR}" + fi + done + fi + + if [ "$CONTRACT_PARSE_ERROR" != "NONE" ]; then + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_S03H3_CONTRACT" + BLOCK_REASON="$CONTRACT_PARSE_ERROR" + return 0 + fi + + case "$validator_rc" in + 11|20|21) + FINAL_RC=20 + FINAL_RESULT="S03H3_VALIDATION_INCONCLUSIVE" + BLOCK_REASON="VALIDATOR_RC_${validator_rc}" + return 0 + ;; + esac + + for field in \ + "$PARSED_MODE_STATUS" \ + "$PARSED_PROFILE_CONFIG_STATUS" \ + "$PARSED_PROFILE_RUNTIME_STATUS" \ + "$PARSED_OBSERVABILITY_STATUS" \ + "$PARSED_FUNCTIONAL_STATUS" \ + "$PARSED_SEMANTIC_STATUS" + do + if [ "$field" != "PASS" ]; then + FINAL_RC=25 + FINAL_RESULT="S03H3_CONTRACT_STATUS_NOT_PASS" + BLOCK_REASON="MANDATORY_STATUS_NOT_PASS" + return 0 + fi + done + + case "$PARSED_TIMING_STATUS" in + NOT_VALID_*) + FINAL_RC=14 + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_NOT_VALID" + BLOCK_REASON="$PARSED_TIMING_STATUS" + ;; + UNASSESSED_*) + FINAL_RC=15 + FINAL_RESULT="FUNCTIONAL_AND_SEMANTIC_PASS_TIMING_UNASSESSED" + BLOCK_REASON="$PARSED_TIMING_STATUS" + ;; + *) + FINAL_RC=22 + FINAL_RESULT="INCONCLUSIVE_S03H3_CONTRACT" + BLOCK_REASON="TIMING_STATUS_UNRECOGNIZED" + ;; + esac +} + +run_s03h3() { + local duration="$1" + MODE="$2" + local expected_selected_hash + local patcher_log + local protected_before + local capture + local run_dir + local validation_log + local csv_report + local launcher_summary + local protected_after + local invariance_report + local protected_path_list + + resolve_suite + [ -d "$SUITE" ] || return 50 + + PATCHER="$SUITE/patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh" + VALIDATOR="$SUITE/scenarios/S03H3_execution_profiles/validate.sh" + MINIMAL_VERIFIER="$SUITE/scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py" + MINIMAL_CONFIG="$SUITE/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte" + TEST_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" + DIAGNOSTIC_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" + RUNNER="$SUITE/common/marte_runner.sh" + LIVE_PATHS="$SUITE/common/paths.sh" + CSV_EXPORTER="$SUITE/scenarios/S03H1_execution_profiles/export_s03_csv.py" + PARSER="$SUITE/scenarios/S03_semantic_sentinels/parse_s03_binary.py" + + case "$MODE" in + MINIMAL) + SELECTED_CONFIG="$MINIMAL_CONFIG" + expected_selected_hash="$EXPECTED_MINIMAL_CONFIG_SHA256" + ;; + TEST) + SELECTED_CONFIG="$TEST_CONFIG" + expected_selected_hash="$EXPECTED_TEST_CONFIG_SHA256" + ;; + DIAGNOSTIC) + SELECTED_CONFIG="$DIAGNOSTIC_CONFIG" + expected_selected_hash="$EXPECTED_DIAGNOSTIC_CONFIG_SHA256" + ;; + *) return 2 ;; + esac + + verify_historical_baseline || return 52 + verify_candidate_components || return 51 + verify_hash "$SELECTED_CONFIG" "$expected_selected_hash" "SELECTED_${MODE}_CONFIG" || return 51 + build_protected_paths + local protected_path + for protected_path in "${PROTECTED_PATHS[@]}"; do + [ -f "$protected_path" ] || return 51 + done + + WORK_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/s03h3_launcher.XXXXXX")" || return 53 + trap 'rm -rf -- "$WORK_ROOT"' EXIT HUP INT TERM + patcher_log="$WORK_ROOT/patcher.log" + protected_before="$WORK_ROOT/protected_before.sha256" + capture="$WORK_ROOT/runner_capture.log" + + "$PATCHER" >"$patcher_log" 2>&1 + PATCHER_RC=$? + [ "$PATCHER_RC" -eq 0 ] || return 54 + verify_hash "$MINIMAL_CONFIG" "$EXPECTED_MINIMAL_CONFIG_SHA256" "MINIMAL_REGENERATION" || return 55 + python3 "$MINIMAL_VERIFIER" CANONICAL "$TEST_CONFIG" "$MINIMAL_CONFIG" >/dev/null 2>&1 || return 55 + write_protected_snapshot "$protected_before" || return 56 + + [ -f "$LIVE_PATHS" ] || return 57 + # shellcheck source=/dev/null + source "$LIVE_PATHS" || return 57 + CONSTANT_GAM_LIB_DIR="$MARTE2_COMPONENTS_ROOT/Build/$MARTE_TARGET/Components/GAMs/ConstantGAM" + CONSTANT_GAM_LIB="$CONSTANT_GAM_LIB_DIR/ConstantGAM.so" + [ -f "$CONSTANT_GAM_LIB" ] || return 58 + case ":${LD_LIBRARY_PATH-}:" in + *":${CONSTANT_GAM_LIB_DIR}:"*) ;; + *) LD_LIBRARY_PATH="${CONSTANT_GAM_LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" ;; + esac + export LD_LIBRARY_PATH + + printf '============================================================\n' + printf 'S03-H3 EXECUTION PROFILES\n' + printf '============================================================\n' + printf 'MODE=%s\n' "$MODE" + printf 'DURATION=%s\n' "$duration" + printf 'DEPENDENCY_INTEGRITY_STATUS=PASS\n' + + "$RUNNER" "S03H3_${MODE}_launcher" "$SELECTED_CONFIG" "State1" "$duration" \ + 2>&1 | tee "$capture" + RUNNER_CODES=("${PIPESTATUS[@]}") + RUNNER_RC="${RUNNER_CODES[0]}" + CAPTURE_TEE_RC="${RUNNER_CODES[1]}" + [ "$RUNNER_RC" -eq 0 ] || return 60 + [ "$CAPTURE_TEE_RC" -eq 0 ] || return 61 + + run_dir="$(sed -n 's/^RUN_DIR=//p' "$capture" | tail -n 1)" + case "$run_dir" in + "$SUITE"/runs/*_S03H3_${MODE}_launcher) ;; + *) return 62 ;; + esac + [ -d "$run_dir" ] || return 62 + + RUN_LOG="$run_dir/run.log" + RUN_CONFIG="$run_dir/configuration.marte" + RUNNER_SUMMARY="$run_dir/runner_summary.txt" + RUN_HASHES="$run_dir/hashes.sha256" + OUTPUT_FILE="$run_dir/s03_output.bin" + CSV_FILE="$run_dir/s03_output.csv" + MODE_FILE="$run_dir/s03h3_mode.txt" + validation_log="$run_dir/validation.txt" + csv_report="$run_dir/csv_export.txt" + launcher_summary="$run_dir/launcher_summary.txt" + PROTECTED_BEFORE_RUN="$run_dir/protected_baseline_before.sha256" + protected_after="$run_dir/protected_baseline_after.sha256" + invariance_report="$run_dir/protected_baseline_invariance.txt" + protected_path_list="$run_dir/protected_successor_paths.txt" + + for protected_path in "$RUN_LOG" "$RUN_CONFIG" "$RUNNER_SUMMARY" "$RUN_HASHES" "$OUTPUT_FILE"; do + [ -f "$protected_path" ] || return 63 + done + + cp -- "$protected_before" "$PROTECTED_BEFORE_RUN" || return 64 + write_protected_path_list "$protected_path_list" || return 64 + { + printf 'SCENARIO=S03H3\n' + printf 'MODE=%s\n' "$MODE" + printf 'SELECTED_CONFIG_SHA256=%s\n' "$expected_selected_hash" + } >"$MODE_FILE" || return 64 + + sha256sum -- \ + "$MODE_FILE" \ + "${BASH_SOURCE[0]}" \ + "$PATCHER" \ + "$VALIDATOR" \ + "$MINIMAL_VERIFIER" \ + "$CSV_EXPORTER" \ + "$PARSER" \ + "$RUN_LOG" \ + "$RUNNER_SUMMARY" \ + "$OUTPUT_FILE" \ + "$PROTECTED_BEFORE_RUN" \ + "$protected_path_list" \ + "${PROTECTED_PATHS[@]}" \ + >>"$RUN_HASHES" || return 64 + + timeout 30s "$VALIDATOR" "$MODE" "$run_dir" 2>&1 | tee "$validation_log" + VALIDATION_CODES=("${PIPESTATUS[@]}") + VALIDATOR_RC="${VALIDATION_CODES[0]}" + VALIDATION_TEE_RC="${VALIDATION_CODES[1]}" + + "$CSV_EXPORTER" "$OUTPUT_FILE" "$CSV_FILE" >"$csv_report" 2>&1 + CSV_EXPORT_RC=$? + if [ "$CSV_EXPORT_RC" -eq 0 ]; then + CSV_EXPORT_STATUS="PASS" + else + CSV_EXPORT_STATUS="FAIL" + fi + + if write_protected_snapshot "$protected_after" && \ + cmp -s -- "$PROTECTED_BEFORE_RUN" "$protected_after"; then + PROTECTED_BASELINE_INVARIANCE="PASS" + else + PROTECTED_BASELINE_INVARIANCE="FAIL" + fi + { + printf 'PROTECTED_BASELINE_INVARIANCE=%s\n' "$PROTECTED_BASELINE_INVARIANCE" + printf 'PROTECTED_SUCCESSOR_BASELINE_COUNT=27\n' + } >"$invariance_report" + + for protected_path in "$validation_log" "$csv_report" "$protected_after" "$invariance_report"; do + [ -f "$protected_path" ] && sha256sum -- "$protected_path" >>"$RUN_HASHES" + done + [ -f "$CSV_FILE" ] && sha256sum -- "$CSV_FILE" >>"$RUN_HASHES" + + classify_s03h3_run \ + "$VALIDATOR_RC" \ + "$VALIDATION_TEE_RC" \ + "$validation_log" \ + "$CSV_EXPORT_RC" \ + "$PROTECTED_BASELINE_INVARIANCE" \ + "PASS" \ + "$MODE" + + { + printf '============================================================\n' + printf 'RIEPILOGO LAUNCHER S03-H3\n' + printf '============================================================\n' + printf 'SCENARIO=S03H3\n' + printf 'MODE=%s\n' "$MODE" + printf 'DURATION=%s\n' "$duration" + printf 'RUN_DIR=%s\n' "$run_dir" + printf 'RUNNER_RC=%s\n' "$RUNNER_RC" + printf 'VALIDATOR_RC=%s\n' "$VALIDATOR_RC" + printf 'CSV_EXPORT_RC=%s\n' "$CSV_EXPORT_RC" + printf 'DEPENDENCY_INTEGRITY_STATUS=PASS\n' + printf 'PROTECTED_BASELINE_INVARIANCE=%s\n' "$PROTECTED_BASELINE_INVARIANCE" + printf 'CSV_EXPORT_STATUS=%s\n' "$CSV_EXPORT_STATUS" + printf 'MODE_STATUS=%s\n' "$PARSED_MODE_STATUS" + printf 'PROFILE_CONFIG_STATUS=%s\n' "$PARSED_PROFILE_CONFIG_STATUS" + printf 'PROFILE_RUNTIME_STATUS=%s\n' "$PARSED_PROFILE_RUNTIME_STATUS" + printf 'OBSERVABILITY_STATUS=%s\n' "$PARSED_OBSERVABILITY_STATUS" + printf 'FUNCTIONAL_STATUS=%s\n' "$PARSED_FUNCTIONAL_STATUS" + printf 'SEMANTIC_STATUS=%s\n' "$PARSED_SEMANTIC_STATUS" + printf 'TIMING_STATUS=%s\n' "$PARSED_TIMING_STATUS" + printf 'MEASUREMENT_ELIGIBILITY=NO\n' + printf 'BLOCK_REASON=%s\n' "$BLOCK_REASON" + printf 'FINAL_RESULT=%s\n' "$FINAL_RESULT" + printf 'FINAL_RC=%s\n' "$FINAL_RC" + printf 'PERFORMANCE_MEASUREMENT_AUTHORIZED=NO\n' + printf 'S04_AUTHORIZED=NO\n' + printf 'MD00_AUTHORIZED=NO\n' + } | tee "$launcher_summary" + SUMMARY_CODES=("${PIPESTATUS[@]}") + if [ "${SUMMARY_CODES[1]}" -ne 0 ]; then + FINAL_RC=69 + FINAL_RESULT="FAIL_LAUNCHER_SUMMARY" + BLOCK_REASON="LAUNCHER_SUMMARY_NOT_WRITTEN" + else + sha256sum -- "$launcher_summary" >>"$RUN_HASHES" + fi + + printf 'RUN_DIR=%s\n' "$run_dir" + printf 'FINAL_RESULT=%s\n' "$FINAL_RESULT" + printf 'FINAL_RC=%s\n' "$FINAL_RC" + + rm -rf -- "$WORK_ROOT" + WORK_ROOT="" + trap - EXIT HUP INT TERM + return "$FINAL_RC" +} + +if [ "${S03H3_LAUNCHER_LIB_ONLY:-0}" = "1" ]; then + if [ "${BASH_SOURCE[0]}" != "$0" ]; then + return 0 + fi + printf 'S03H3_LAUNCHER_LIB_ONLY=1 richiede source.\n' >&2 + exit 3 +fi + +case "$#" in + 1) + case "$1" in + -h|--help) + print_usage + exit 0 + ;; + *) + print_usage >&2 + exit 2 + ;; + esac + ;; + 3) + [ "$1" = "S03H3" ] || { + print_usage >&2 + exit 2 + } + is_positive_decimal "$2" || { + printf 'Durata non valida: %s\n' "$2" >&2 + exit 2 + } + case "$3" in + MINIMAL|TEST|DIAGNOSTIC) ;; + *) + printf 'Profilo non riconosciuto: %s\n' "$3" >&2 + exit 2 + ;; + esac + run_s03h3 "$2" "$3" + exit $? + ;; + *) + print_usage >&2 + exit 2 + ;; +esac diff --git a/launcher/marte_benchmark_menu.sh b/launcher/marte_benchmark_menu.sh new file mode 100755 index 0000000..207f112 --- /dev/null +++ b/launcher/marte_benchmark_menu.sh @@ -0,0 +1,512 @@ +#!/usr/bin/env bash + +set -u +set -o pipefail + +readonly DEFAULT_DURATION_SECONDS="8" +readonly TEST_OVERRIDE_ACK_VALUE="YES" + +CAPTURE="" +STATUS_DIR="" +LAUNCH_ACTIVE="NO" +INTERRUPT_PENDING="NO" + +SELECTED_SCENARIO="" +DISPLAY_SCENARIO="" +SELECTED_PROFILE="N/A" +SELECTED_DURATION="" + +LAST_LAUNCHER_RC="NOT_AVAILABLE" +LAST_RUN_DIR="NOT_AVAILABLE" +LAST_RUN_DIR_STATUS="NOT_AVAILABLE" + +cleanup_temporary_files() { + if [ -n "$CAPTURE" ]; then + rm -f -- "$CAPTURE" + CAPTURE="" + fi + + if [ -n "$STATUS_DIR" ]; then + rm -f -- \ + "$STATUS_DIR/launcher_rc" \ + "$STATUS_DIR/tee_rc" + rmdir -- "$STATUS_DIR" 2>/dev/null || true + STATUS_DIR="" + fi +} + +fatal_wrapper_error() { + local exit_code="$1" + local status="$2" + local message="$3" + + printf 'ERRORE S03-H3: %s\n' "$message" >&2 + if [ "$exit_code" -eq 70 ]; then + printf 'S03_H3_STARTUP_STATUS=%s\n' "$status" >&2 + else + printf 'S03_H3_MENU_LIFECYCLE_STATUS=%s\n' "$status" >&2 + fi + exit "$exit_code" +} + +handle_int() { + if [ "$LAUNCH_ACTIVE" = "YES" ]; then + INTERRUPT_PENDING="YES" + return 0 + fi + + printf '\nInterruzione richiesta: nessun nuovo benchmark verra avviato.\n' >&2 + printf 'LAUNCHER_EXECUTED=NO\n' >&2 + exit 130 +} + +handle_hup() { + printf '\nS03-H3 terminato da SIGHUP.\n' >&2 + exit 129 +} + +handle_term() { + printf '\nS03-H3 terminato da SIGTERM.\n' >&2 + exit 143 +} + +trap cleanup_temporary_files EXIT +trap handle_hup HUP +trap handle_int INT +trap handle_term TERM + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +if [ -z "$SCRIPT_DIR" ]; then + fatal_wrapper_error 70 "FAIL_SUITE_ROOT" \ + "impossibile determinare la directory del menu" +fi + +INSTALLED_SUITE="$(cd -- "$SCRIPT_DIR/.." && pwd -P)" +if [ -z "$INSTALLED_SUITE" ]; then + fatal_wrapper_error 70 "FAIL_SUITE_ROOT" \ + "impossibile determinare la radice della suite" +fi + +readonly -a COMPLETE_OVERRIDE_PATHS=( + "launcher/marte_benchmark_launcher.sh" + "configurations/generated/S00/smoke_s0_core.marte" + "configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" + "configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte" + "configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte" + "patchers/configuration/build_s01_from_s00.sh" + "patchers/configuration/build_s02_from_s01.sh" + "patchers/configuration/build_s03_from_s02.sh" + "scenarios/S02_binary_recording/validate.sh" + "scenarios/S03_semantic_sentinels/validate.sh" + "scenarios/S03_semantic_sentinels/parse_s03_binary.py" + "common/marte_runner.sh" + "common/paths.sh" + "common/environment.sh" + "launcher/marte_benchmark_launcher_s03h1.sh" + "patchers/configuration/build_s03h1_from_s02.sh" + "scenarios/S03H1_execution_profiles/validate.sh" + "scenarios/S03H1_execution_profiles/verify_s03h1_config.py" + "scenarios/S03H1_execution_profiles/export_s03_csv.py" + "configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" + "configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" + "launcher/marte_benchmark_launcher_s03h3.sh" + "patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh" + "scenarios/S03H3_execution_profiles/validate.sh" + "scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py" + "configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte" + "launcher/marte_benchmark_menu.sh" +) + +verify_complete_override_root() { + local relative_path + local missing_count=0 + + for relative_path in "${COMPLETE_OVERRIDE_PATHS[@]}"; do + if [ ! -f "$SUITE/$relative_path" ]; then + printf 'ERRORE S03-H3: fixture incompleta, manca %s\n' \ + "$relative_path" >&2 + missing_count=$((missing_count + 1)) + fi + done + + [ "$missing_count" -eq 0 ] +} + +OVERRIDE_ACTIVE="NO" +if [ -n "${MARTE_BENCHMARK_SUITE_ROOT:-}" ]; then + if [ "${S03H3_TEST_ONLY_OVERRIDE:-NO}" != "$TEST_OVERRIDE_ACK_VALUE" ]; then + fatal_wrapper_error 70 "FAIL_TEST_OVERRIDE_NOT_ACKNOWLEDGED" \ + "MARTE_BENCHMARK_SUITE_ROOT richiede S03H3_TEST_ONLY_OVERRIDE=YES" + fi + if [[ "$MARTE_BENCHMARK_SUITE_ROOT" != /* ]]; then + fatal_wrapper_error 70 "FAIL_TEST_OVERRIDE_NOT_ABSOLUTE" \ + "la radice di override deve essere un percorso assoluto" + fi + if [ ! -d "$MARTE_BENCHMARK_SUITE_ROOT" ]; then + fatal_wrapper_error 70 "FAIL_TEST_OVERRIDE_ROOT" \ + "la radice di override non esiste" + fi + + SUITE="$(cd -- "$MARTE_BENCHMARK_SUITE_ROOT" && pwd -P)" + if [ -z "$SUITE" ] || [ "$SUITE" = "/" ]; then + fatal_wrapper_error 70 "FAIL_TEST_OVERRIDE_ROOT" \ + "la radice di override non e sicura" + fi + OVERRIDE_ACTIVE="YES" + + if ! verify_complete_override_root; then + fatal_wrapper_error 70 "FAIL_INCOMPLETE_TEST_SUITE" \ + "l'override e ammesso soltanto per una suite completa di 27 elementi" + fi +elif [ "${S03H3_TEST_ONLY_OVERRIDE:-NO}" = "$TEST_OVERRIDE_ACK_VALUE" ]; then + fatal_wrapper_error 70 "FAIL_TEST_OVERRIDE_ROOT_MISSING" \ + "S03H3_TEST_ONLY_OVERRIDE=YES richiede MARTE_BENCHMARK_SUITE_ROOT" +else + SUITE="$INSTALLED_SUITE" +fi + +readonly SUITE +readonly OVERRIDE_ACTIVE + +STANDARD_LAUNCHER="$SUITE/launcher/marte_benchmark_launcher.sh" +S03H3_LAUNCHER="$SUITE/launcher/marte_benchmark_launcher_s03h3.sh" +readonly STANDARD_LAUNCHER +readonly S03H3_LAUNCHER + +if [ ! -x "$STANDARD_LAUNCHER" ]; then + fatal_wrapper_error 70 "FAIL_REQUIRED_LAUNCHER" \ + "launcher standard assente o non eseguibile: $STANDARD_LAUNCHER" +fi +if [ ! -x "$S03H3_LAUNCHER" ]; then + fatal_wrapper_error 70 "FAIL_REQUIRED_LAUNCHER" \ + "launcher S03-H3 assente o non eseguibile: $S03H3_LAUNCHER" +fi + +export MARTE_BENCHMARK_SUITE_ROOT="$SUITE" + +is_positive_decimal() { + local value="$1" + + [[ "$value" =~ ^[0-9]+$ ]] && [[ "$value" =~ [1-9] ]] +} + +normalize_decimal() { + local value="$1" + + while [ "${#value}" -gt 1 ] && [ "${value:0:1}" = "0" ]; do + value="${value#0}" + done + printf '%s' "$value" +} + +print_main_menu() { + printf '\n============================================================\n' + printf 'MARTe2 Benchmark Suite — menu S03-H3\n' + printf '============================================================\n' + printf '1) S01\n' + printf '2) S02\n' + printf '3) S03\n' + printf '4) S03-H3\n' + printf '0) Exit\n' + printf 'Selection: ' +} + +read_duration() { + local value + + printf 'Simulation duration [seconds] (default: %s): ' \ + "$DEFAULT_DURATION_SECONDS" + if ! IFS= read -r value; then + return 1 + fi + if [ -z "$value" ]; then + SELECTED_DURATION="$DEFAULT_DURATION_SECONDS" + return 0 + fi + if ! is_positive_decimal "$value"; then + printf 'Durata non valida: usare solo un intero decimale positivo.\n' >&2 + printf 'LAUNCHER_EXECUTED=NO\n' + return 2 + fi + + SELECTED_DURATION="$(normalize_decimal "$value")" + return 0 +} + +read_s03h3_profile() { + local value + + printf '1) MINIMAL\n' + printf '2) TEST\n' + printf '3) DIAGNOSTIC\n' + printf 'Profile: ' + if ! IFS= read -r value; then + return 1 + fi + + case "$value" in + 1) + SELECTED_PROFILE="MINIMAL" + ;; + 2) + SELECTED_PROFILE="TEST" + ;; + 3) + SELECTED_PROFILE="DIAGNOSTIC" + ;; + *) + printf 'Profilo non valido. Nessun launcher eseguito.\n' >&2 + printf 'LAUNCHER_EXECUTED=NO\n' + return 2 + ;; + esac + return 0 +} + +confirm_execution() { + local answer + + printf '\nScenario: %s\n' "$DISPLAY_SCENARIO" + printf 'Profile: %s\n' "$SELECTED_PROFILE" + printf 'Duration: %s s\n\n' "$SELECTED_DURATION" + printf 'Start execution? [y/N]: ' + if ! IFS= read -r answer; then + return 1 + fi + + case "$answer" in + y|Y) + return 0 + ;; + *) + printf 'Esecuzione annullata.\n' + printf 'LAUNCHER_EXECUTED=NO\n' + return 2 + ;; + esac +} + +delegate_launcher() { + case "$SELECTED_SCENARIO" in + S01) + "$STANDARD_LAUNCHER" S01 "$SELECTED_DURATION" + ;; + S02) + "$STANDARD_LAUNCHER" S02 "$SELECTED_DURATION" + ;; + S03) + "$STANDARD_LAUNCHER" S03 "$SELECTED_DURATION" + ;; + S03H3) + "$S03H3_LAUNCHER" S03H3 "$SELECTED_DURATION" "$SELECTED_PROFILE" + ;; + *) + return 125 + ;; + esac +} + +read_recorded_status() { + local status_file="$1" + local fallback="$2" + local value="" + + if [ -s "$status_file" ]; then + IFS= read -r value <"$status_file" || value="" + fi + if [[ "$value" =~ ^[0-9]+$ ]]; then + printf '%s' "$value" + else + printf '%s' "$fallback" + fi +} + +observe_run_dir() { + local observed="" + local observed_resolved="" + local observed_parent="" + local observed_name="" + + observed="$(sed -n 's/^RUN_DIR=//p' "$CAPTURE" | tail -n 1)" + if [ -z "$observed" ]; then + LAST_RUN_DIR="NOT_AVAILABLE" + LAST_RUN_DIR_STATUS="NOT_AVAILABLE" + elif [[ "$observed" != /* ]]; then + LAST_RUN_DIR="$observed" + LAST_RUN_DIR_STATUS="REPORTED_BUT_INVALID" + elif [ ! -d "$observed" ]; then + LAST_RUN_DIR="$observed" + LAST_RUN_DIR_STATUS="REPORTED_BUT_NOT_FOUND" + elif [ "$SELECTED_SCENARIO" = "S03H3" ]; then + observed_resolved="$(cd -- "$observed" && pwd -P)" || observed_resolved="" + observed_parent="$(dirname -- "$observed_resolved")" + observed_name="$(basename -- "$observed_resolved")" + if [ "$observed_parent" = "$SUITE/runs" ] && + [[ "$observed_name" = *_S03H3_${SELECTED_PROFILE}_launcher ]]; then + LAST_RUN_DIR="$observed_resolved" + LAST_RUN_DIR_STATUS="FOUND" + else + LAST_RUN_DIR="$observed" + LAST_RUN_DIR_STATUS="REPORTED_BUT_INVALID" + fi + else + LAST_RUN_DIR="$observed" + LAST_RUN_DIR_STATUS="FOUND" + fi +} + +print_execution_summary() { + printf '\n============================================================\n' + printf 'Riepilogo esecuzione S03-H3\n' + printf '============================================================\n' + printf 'Scenario: %s\n' "$DISPLAY_SCENARIO" + printf 'Profile: %s\n' "$SELECTED_PROFILE" + printf 'Duration: %s s\n' "$SELECTED_DURATION" + printf 'Launcher RC: %s\n' "$LAST_LAUNCHER_RC" + printf 'RUN_DIR: %s\n' "$LAST_RUN_DIR" + printf 'RUN_DIR status: %s\n' "$LAST_RUN_DIR_STATUS" + printf 'LAST_LAUNCHER_RC=%s\n' "$LAST_LAUNCHER_RC" + printf 'RUN_DIR_STATUS=%s\n' "$LAST_RUN_DIR_STATUS" + printf 'RUN_DIR=%s\n' "$LAST_RUN_DIR" + printf 'MENU_RECLASSIFIES_LAUNCHER_RESULT=NO\n' +} + +invoke_selected_launcher() { + local -a pipeline_status + local launcher_rc + local tee_rc + + cleanup_temporary_files + CAPTURE="$(mktemp "${TMPDIR:-/tmp}/marte_benchmark_menu.capture.XXXXXX")" || + fatal_wrapper_error 71 "FAIL_CAPTURE_CREATION" \ + "impossibile creare il file temporaneo di cattura" + STATUS_DIR="$(mktemp -d "${TMPDIR:-/tmp}/marte_benchmark_menu.status.XXXXXX")" || { + cleanup_temporary_files + fatal_wrapper_error 71 "FAIL_CAPTURE_CREATION" \ + "impossibile creare lo stato temporaneo della cattura" + } + + INTERRUPT_PENDING="NO" + LAUNCH_ACTIVE="YES" + ( + delegate_launcher + launcher_rc=$? + printf '%s\n' "$launcher_rc" >"$STATUS_DIR/launcher_rc" + exit "$launcher_rc" + ) 2>&1 | ( + tee "$CAPTURE" + tee_rc=$? + printf '%s\n' "$tee_rc" >"$STATUS_DIR/tee_rc" + exit "$tee_rc" + ) + pipeline_status=("${PIPESTATUS[@]}") + LAUNCH_ACTIVE="NO" + + launcher_rc="$(read_recorded_status \ + "$STATUS_DIR/launcher_rc" "${pipeline_status[0]:-125}")" + tee_rc="$(read_recorded_status \ + "$STATUS_DIR/tee_rc" "${pipeline_status[1]:-125}")" + LAST_LAUNCHER_RC="$launcher_rc" + observe_run_dir + print_execution_summary + + if [ "$INTERRUPT_PENDING" = "YES" ]; then + printf 'S03_H3_MENU_LIFECYCLE_STATUS=INTERRUPTED\n' >&2 + printf 'LAUNCHER_RC_RECLASSIFIED=NO\n' >&2 + cleanup_temporary_files + exit 130 + fi + + if [ "$tee_rc" -ne 0 ]; then + printf 'CAPTURE_TEE_STATUS=FAIL\n' >&2 + printf 'CAPTURE_TEE_RC=%s\n' "$tee_rc" >&2 + printf 'S03_H3_MENU_LIFECYCLE_STATUS=FAIL_CAPTURE_TEE\n' >&2 + cleanup_temporary_files + exit 72 + fi + + printf 'CAPTURE_TEE_STATUS=PASS\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=CONTINUE\n' + cleanup_temporary_files +} + +printf 'S03_H3_STARTUP_STATUS=PASS\n' +printf 'S03_H3_SUITE_ROOT=%s\n' "$SUITE" +printf 'S03_H3_TEST_OVERRIDE_ACTIVE=%s\n' "$OVERRIDE_ACTIVE" + +while true; do + print_main_menu + if ! IFS= read -r menu_choice; then + printf '\nEOF: uscita pulita dal menu.\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=EXIT_EOF\n' + exit 0 + fi + + SELECTED_PROFILE="N/A" + case "$menu_choice" in + 0) + printf 'Uscita dal menu S03-H3.\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=EXIT_USER\n' + exit 0 + ;; + 1) + SELECTED_SCENARIO="S01" + DISPLAY_SCENARIO="S01" + ;; + 2) + SELECTED_SCENARIO="S02" + DISPLAY_SCENARIO="S02" + ;; + 3) + SELECTED_SCENARIO="S03" + DISPLAY_SCENARIO="S03" + ;; + 4) + SELECTED_SCENARIO="S03H3" + DISPLAY_SCENARIO="S03-H3" + ;; + *) + printf 'Selezione non valida. Nessun launcher eseguito.\n' >&2 + printf 'LAUNCHER_EXECUTED=NO\n' + continue + ;; + esac + + read_duration + duration_status=$? + if [ "$duration_status" -eq 1 ]; then + printf '\nEOF prima dell esecuzione: azione annullata.\n' + printf 'LAUNCHER_EXECUTED=NO\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=EXIT_EOF\n' + exit 0 + elif [ "$duration_status" -ne 0 ]; then + continue + fi + + if [ "$SELECTED_SCENARIO" = "S03H3" ]; then + read_s03h3_profile + profile_status=$? + if [ "$profile_status" -eq 1 ]; then + printf '\nEOF prima dell esecuzione: azione annullata.\n' + printf 'LAUNCHER_EXECUTED=NO\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=EXIT_EOF\n' + exit 0 + elif [ "$profile_status" -ne 0 ]; then + continue + fi + fi + + confirm_execution + confirmation_status=$? + if [ "$confirmation_status" -eq 1 ]; then + printf '\nEOF prima della conferma: azione annullata.\n' + printf 'LAUNCHER_EXECUTED=NO\n' + printf 'S03_H3_MENU_LIFECYCLE_STATUS=EXIT_EOF\n' + exit 0 + elif [ "$confirmation_status" -ne 0 ]; then + continue + fi + + printf 'LAUNCHER_EXECUTED=YES\n' + invoke_selected_launcher +done diff --git a/patchers/configuration/build_s01_from_s00.sh b/patchers/configuration/build_s01_from_s00.sh new file mode 100755 index 0000000..07ad733 --- /dev/null +++ b/patchers/configuration/build_s01_from_s00.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +set -eu + +SUITE="/home/dariodf/Scrivania/MARTE/marte-benchmark-suite" + +DEFAULT_SOURCE="$SUITE/configurations/generated/S00/smoke_s0_core.marte" +DEFAULT_OUTPUT="$SUITE/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" + +SOURCE="${1:-$DEFAULT_SOURCE}" +OUTPUT="${2:-$DEFAULT_OUTPUT}" + +EXPECTED_SOURCE_SHA256="49a9b48595aab88cd8fd11d1e58ace4ab11d7d0a649e9b143bdc25c8866253d5" +EXPECTED_OUTPUT_SHA256="1513e1ca72eba56d53079ac7328908f12ddc72772456e2d75bcbacb6465d2427" + +fail() { + code="$1" + shift + printf "ERRORE: %s\n" "$*" >&2 + exit "$code" +} + +if [ ! -f "$SOURCE" ]; then + fail 2 "configurazione sorgente non trovata: $SOURCE" +fi + +SOURCE_SHA256="$(sha256sum "$SOURCE" | awk '{print $1}')" + +if [ "$SOURCE_SHA256" != "$EXPECTED_SOURCE_SHA256" ]; then + fail 3 "hash della configurazione sorgente non riconosciuto: $SOURCE_SHA256" +fi + +if grep -q 'State1_Thread1_CycleTime' "$SOURCE"; then + fail 4 "la configurazione sorgente contiene già il segnale S01" +fi + +OUTPUT_DIR="$(dirname "$OUTPUT")" +mkdir -p "$OUTPUT_DIR" + +if [ -e "$OUTPUT" ]; then + if [ ! -f "$OUTPUT" ]; then + fail 5 "il percorso di output esiste ma non è un file regolare: $OUTPUT" + fi + + OUTPUT_SHA256="$(sha256sum "$OUTPUT" | awk '{print $1}')" + + if [ "$OUTPUT_SHA256" = "$EXPECTED_OUTPUT_SHA256" ]; then + printf "RISULTATO GIÀ PRESENTE E CORRETTO\n" + printf "Output: %s\n" "$OUTPUT" + printf "SHA-256: %s\n" "$OUTPUT_SHA256" + exit 0 + fi + + fail 6 "il file di output esiste ma ha un contenuto inatteso: $OUTPUT" +fi + +TMP="${OUTPUT}.tmp.$$" + +cleanup() { + if [ -f "$TMP" ]; then + rm -f "$TMP" + fi +} + +trap cleanup EXIT HUP INT TERM + +set +e +awk ' +BEGIN { + inGamDisplay = 0 + inInputSignals = 0 + inOutputSignals = 0 + inputInsertions = 0 + outputInsertions = 0 +} + +{ + if ($0 ~ /^ \+GAMDisplay[[:space:]]*=[[:space:]]*\{[[:space:]]*$/) { + inGamDisplay = 1 + } + + if (inGamDisplay && + $0 ~ /^ InputSignals[[:space:]]*=[[:space:]]*\{[[:space:]]*$/) { + inInputSignals = 1 + } + + if (inGamDisplay && + $0 ~ /^ OutputSignals[[:space:]]*=[[:space:]]*\{[[:space:]]*$/) { + inOutputSignals = 1 + } + + if (inInputSignals && + $0 ~ /^ \}[[:space:]]*$/) { + print " State1_Thread1_CycleTime = {" + print " Alias = State1.Thread1_CycleTime" + print " DataSource = Timings" + print " Type = uint32" + print " }" + inputInsertions++ + inInputSignals = 0 + } + + if (inOutputSignals && + $0 ~ /^ \}[[:space:]]*$/) { + print " State1_Thread1_CycleTime = {" + print " DataSource = Display" + print " Type = uint32" + print " }" + print " } " + outputInsertions++ + inOutputSignals = 0 + next + } + + print + + if (inGamDisplay && + $0 ~ /^ \}[[:space:]]*$/) { + inGamDisplay = 0 + } +} + +END { + if (inputInsertions != 1 || outputInsertions != 1) { + exit 42 + } +} +' "$SOURCE" > "$TMP" + +AWK_RC=$? +set -e + +if [ "$AWK_RC" -ne 0 ]; then + fail 7 "trasformazione non applicata in modo univoco; codice awk: $AWK_RC" +fi + +GENERATED_SHA256="$(sha256sum "$TMP" | awk '{print $1}')" + +if [ "$GENERATED_SHA256" != "$EXPECTED_OUTPUT_SHA256" ]; then + fail 8 "output generato differente dalla S01 manuale: $GENERATED_SHA256" +fi + +mv "$TMP" "$OUTPUT" +trap - EXIT HUP INT TERM + +printf "PATCH S00 -> S01 COMPLETATA\n" +printf "Sorgente: %s\n" "$SOURCE" +printf "Output: %s\n" "$OUTPUT" +printf "SHA-256 sorgente: %s\n" "$SOURCE_SHA256" +printf "SHA-256 output: %s\n" "$GENERATED_SHA256" diff --git a/patchers/configuration/build_s02_from_s01.sh b/patchers/configuration/build_s02_from_s01.sh new file mode 100755 index 0000000..efb9df4 --- /dev/null +++ b/patchers/configuration/build_s02_from_s01.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +set -u + +SUITE="/home/dariodf/Scrivania/MARTE/marte-benchmark-suite" + +SOURCE_CONFIG="$SUITE/configurations/generated/S01/from_patcher/smoke_s1_timing_from_s00.marte" +OUTPUT_DIR="$SUITE/configurations/generated/S02/from_patcher" +OUTPUT_CONFIG="$OUTPUT_DIR/smoke_s2_binary_recording_from_s01.marte" + +EXPECTED_SOURCE_SHA256="1513e1ca72eba56d53079ac7328908f12ddc72772456e2d75bcbacb6465d2427" + +if [ ! -f "$SOURCE_CONFIG" ]; then + printf "SOURCE_STATUS=NOT_FOUND\n" + printf "SOURCE_CONFIG=%s\n" "$SOURCE_CONFIG" + exit 20 +fi + +ACTUAL_SOURCE_SHA256="$(sha256sum -- "$SOURCE_CONFIG" | awk '{print $1}')" + +printf "SOURCE_CONFIG=%s\n" "$SOURCE_CONFIG" +printf "EXPECTED_SOURCE_SHA256=%s\n" "$EXPECTED_SOURCE_SHA256" +printf "ACTUAL_SOURCE_SHA256=%s\n" "$ACTUAL_SOURCE_SHA256" + +if [ "$ACTUAL_SOURCE_SHA256" != "$EXPECTED_SOURCE_SHA256" ]; then + printf "SOURCE_HASH_STATUS=MISMATCH\n" + exit 21 +fi + +printf "SOURCE_HASH_STATUS=MATCH\n" + +mkdir -p -- "$OUTPUT_DIR" + +TEMP_CONFIG="$(mktemp "$OUTPUT_DIR/.s02_candidate_XXXXXX.marte")" + +if [ -z "$TEMP_CONFIG" ]; then + printf "TEMPORARY_FILE_STATUS=FAIL\n" + exit 22 +fi + +trap 'rm -f -- "$TEMP_CONFIG"' EXIT + +python3 - "$SOURCE_CONFIG" "$TEMP_CONFIG" <<'PY' +import re +import sys +from pathlib import Path + +source = Path(sys.argv[1]) +destination = Path(sys.argv[2]) + +text = source.read_text(encoding="utf-8") + +for forbidden in ( + "+IOGAM_Writer =", + "+FileWriter =", + "__MARTE_RUN_DIR__", +): + if forbidden in text: + print("SOURCE_ALREADY_CONTAINS_S02_CONTENT=" + forbidden) + sys.exit(30) + +writer_gam = """ +IOGAM_Writer = { + Class = IOGAM + InputSignals = { + Counter = { + DataSource = DDB1 + Type = uint32 + } + Time = { + DataSource = DDB1 + Type = uint32 + } + State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { + Counter = { + DataSource = FileWriter + Type = uint32 + } + Time = { + DataSource = FileWriter + Type = uint32 + } + State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + } + } +""" + +file_writer = """ +FileWriter = { + Class = FileDataSource::FileWriter + NumberOfBuffers = 100 + CPUMask = 0x1 + StackSize = 10000000 + Filename = "__MARTE_RUN_DIR__/s02_output.bin" + Overwrite = "yes" + FileFormat = "binary" + StoreOnTrigger = 0 + RefreshContent = 0 + Signals = { + Counter = { + Type = uint32 + } + Time = { + Type = uint32 + } + State1_Thread1_CycleTime = { + Type = uint32 + } + } + } +""" + +patched, functions_count = re.subn( + r"(?m)^ }\n \+Data = \{\n", + writer_gam + " }\n +Data = {\n", + text, + count=1, +) + +patched, data_count = re.subn( + r"(?m)^ }\n \+States = \{\n", + file_writer + " }\n +States = {\n", + patched, + count=1, +) + +patched, thread_count = re.subn( + r"Functions = \{GAMTimer GAMDisplay\}", + "Functions = {GAMTimer GAMDisplay IOGAM_Writer}", + patched, + count=1, +) + +print("FUNCTIONS_TRANSFORM_COUNT=" + str(functions_count)) +print("DATA_TRANSFORM_COUNT=" + str(data_count)) +print("THREAD_TRANSFORM_COUNT=" + str(thread_count)) + +if (functions_count, data_count, thread_count) != (1, 1, 1): + print("TRANSFORM_STATUS=FAIL") + sys.exit(31) + +checks = { + "IOGAM_WRITER_COUNT": patched.count("+IOGAM_Writer ="), + "FILE_WRITER_COUNT": patched.count("+FileWriter ="), + "FILE_WRITER_CLASS_COUNT": patched.count("Class = FileDataSource::FileWriter"), + "PLAIN_FILE_WRITER_CLASS_COUNT": patched.count("Class = FileWriter"), + "FILEWRITER_OUTPUT_COUNT": patched.count("DataSource = FileWriter"), + "RUN_DIR_PLACEHOLDER_COUNT": patched.count("__MARTE_RUN_DIR__"), + "BINARY_FORMAT_COUNT": patched.count('FileFormat = "binary"'), + "OVERWRITE_YES_COUNT": patched.count('Overwrite = "yes"'), + "CONTINUOUS_MODE_COUNT": patched.count("StoreOnTrigger = 0"), +} + +for name, value in checks.items(): + print(name + "=" + str(value)) + +expected = { + "IOGAM_WRITER_COUNT": 1, + "FILE_WRITER_COUNT": 1, + "FILE_WRITER_CLASS_COUNT": 1, + "PLAIN_FILE_WRITER_CLASS_COUNT": 0, + "FILEWRITER_OUTPUT_COUNT": 3, + "RUN_DIR_PLACEHOLDER_COUNT": 1, + "BINARY_FORMAT_COUNT": 1, + "OVERWRITE_YES_COUNT": 1, + "CONTINUOUS_MODE_COUNT": 1, +} + +if checks != expected: + print("STRUCTURAL_CHECK_STATUS=FAIL") + sys.exit(32) + +required_thread = "Functions = {GAMTimer GAMDisplay IOGAM_Writer}" + +if patched.count(required_thread) != 1: + print("THREAD_FUNCTION_LIST_STATUS=FAIL") + sys.exit(33) + +destination.write_text(patched, encoding="utf-8") + +print("STRUCTURAL_CHECK_STATUS=PASS") +print("TRANSFORM_STATUS=PASS") +PY + +PYTHON_RC=$? + +if [ "$PYTHON_RC" -ne 0 ]; then + printf "PYTHON_TRANSFORM_EXIT_CODE=%s\n" "$PYTHON_RC" + exit 23 +fi + +if [ -f "$OUTPUT_CONFIG" ]; then + if cmp -s -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + printf "OUTPUT_STATUS=ALREADY_VALID\n" + printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" + printf "OUTPUT_SHA256=%s\n" \ + "$(sha256sum -- "$OUTPUT_CONFIG" | awk '{print $1}')" + exit 0 + fi + + printf "OUTPUT_STATUS=EXISTS_BUT_DIFFERS\n" + printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" + exit 24 +fi + +chmod 0644 "$TEMP_CONFIG" + +if ! mv -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + printf "OUTPUT_STATUS=PUBLISH_FAILED\n" + exit 25 +fi + +trap - EXIT + +printf "OUTPUT_STATUS=CREATED\n" +printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" +printf "OUTPUT_SHA256=%s\n" \ + "$(sha256sum -- "$OUTPUT_CONFIG" | awk '{print $1}')" diff --git a/patchers/configuration/build_s03_from_s02.sh b/patchers/configuration/build_s03_from_s02.sh new file mode 100755 index 0000000..8dcc5dd --- /dev/null +++ b/patchers/configuration/build_s03_from_s02.sh @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +set -u + +SCRIPT_DIR="$( + cd "$(dirname "${BASH_SOURCE[0]}")" && + pwd +)" +SUITE="$(cd "$SCRIPT_DIR/../.." && pwd)" + +DEFAULT_SOURCE="$SUITE/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte" +DEFAULT_OUTPUT="$SUITE/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte" + +SOURCE_CONFIG="${1:-$DEFAULT_SOURCE}" +OUTPUT_CONFIG="${2:-$DEFAULT_OUTPUT}" + +EXPECTED_SOURCE_SHA256="8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1" +EXPECTED_OUTPUT_SHA256="0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4" + +fail() { + code="$1" + shift + printf "S03_BUILD_STATUS=FAIL\n" >&2 + printf "S03_BUILD_ERROR=%s\n" "$*" >&2 + exit "$code" +} + +if [ ! -f "$SOURCE_CONFIG" ]; then + fail 20 "SOURCE_NOT_FOUND:$SOURCE_CONFIG" +fi + +SOURCE_SHA256="$(sha256sum -- "$SOURCE_CONFIG" | awk '{print $1}')" + +printf "SOURCE_CONFIG=%s\n" "$SOURCE_CONFIG" +printf "EXPECTED_SOURCE_SHA256=%s\n" "$EXPECTED_SOURCE_SHA256" +printf "ACTUAL_SOURCE_SHA256=%s\n" "$SOURCE_SHA256" + +if [ "$SOURCE_SHA256" != "$EXPECTED_SOURCE_SHA256" ]; then + fail 21 "SOURCE_HASH_MISMATCH:$SOURCE_SHA256" +fi + +OUTPUT_DIR="$(dirname "$OUTPUT_CONFIG")" +if ! mkdir -p -- "$OUTPUT_DIR"; then + fail 22 "OUTPUT_DIRECTORY_CREATE_FAILED:$OUTPUT_DIR" +fi + +if [ -e "$OUTPUT_CONFIG" ] && [ ! -f "$OUTPUT_CONFIG" ]; then + fail 23 "OUTPUT_EXISTS_NOT_REGULAR_FILE:$OUTPUT_CONFIG" +fi + +TEMP_CONFIG="$(mktemp "$OUTPUT_DIR/.s03_candidate_XXXXXX.marte")" || + fail 24 "TEMPORARY_FILE_CREATE_FAILED:$OUTPUT_DIR" + +cleanup() { + rm -f -- "$TEMP_CONFIG" +} +trap cleanup EXIT HUP INT TERM + +PYTHONHASHSEED=0 python3 - "$SOURCE_CONFIG" "$TEMP_CONFIG" <<'PY_S03_PATCHER' +import sys +from pathlib import Path + + +source = Path(sys.argv[1]) +destination = Path(sys.argv[2]) +text = source.read_text(encoding="utf-8") + + +def require_count(haystack, needle, expected, label): + observed = haystack.count(needle) + print(f"{label}_COUNT={observed}") + if observed != expected: + raise RuntimeError(f"{label}_COUNT_EXPECTED_{expected}_OBSERVED_{observed}") + + +def replace_once(haystack, old, new, label): + require_count(haystack, old, 1, label) + return haystack.replace(old, new, 1) + + +for forbidden in ( + "+SentinelProducer =", + "SentinelScalarA", + "SentinelScalarB", + "SentinelVector", + "s03_output.bin", +): + if forbidden in text: + raise RuntimeError("SOURCE_ALREADY_CONTAINS_S03_CONTENT:" + forbidden) + +constant_gam = """ +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } +""" + +sentinel_signals = """ SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } +""" + +filewriter_signals = """ SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } +""" + +text = replace_once( + text, + " +GAMDisplay = {\n", + constant_gam + " +GAMDisplay = {\n", + "SENTINEL_PRODUCER_ANCHOR", +) + +writer_start = text.index(" +IOGAM_Writer = {\n") +writer_end = text.index(" }\n +Data = {\n", writer_start) +writer = text[writer_start:writer_end] + +writer = replace_once( + writer, + """ State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { +""", + """ State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } +""" + sentinel_signals + """ } + OutputSignals = { +""", + "WRITER_INPUT_ANCHOR", +) + +writer_output_signals = sentinel_signals.replace("DataSource = DDB1", "DataSource = FileWriter") +writer = replace_once( + writer, + """ State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + } + } +""", + """ State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } +""" + writer_output_signals + """ } + } +""", + "WRITER_OUTPUT_ANCHOR", +) + +text = text[:writer_start] + writer + text[writer_end:] + +filewriter_start = text.index(" +FileWriter = {\n") +filewriter_end = text.index(" }\n +States = {\n", filewriter_start) +filewriter = text[filewriter_start:filewriter_end] + +filewriter = replace_once( + filewriter, + ' Filename = "__MARTE_RUN_DIR__/s02_output.bin"\n', + ' Filename = "__MARTE_RUN_DIR__/s03_output.bin"\n', + "OUTPUT_FILENAME_ANCHOR", +) + +filewriter = replace_once( + filewriter, + """ State1_Thread1_CycleTime = { + Type = uint32 + } + } + } +""", + """ State1_Thread1_CycleTime = { + Type = uint32 + } +""" + filewriter_signals + """ } + } +""", + "FILEWRITER_SIGNAL_ANCHOR", +) + +text = text[:filewriter_start] + filewriter + text[filewriter_end:] + +text = replace_once( + text, + "Functions = {GAMTimer GAMDisplay IOGAM_Writer}", + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}", + "THREAD_FUNCTION_LIST_ANCHOR", +) + +# Canonicalise the historical trailing blank line so the generated candidate +# is byte-stable across copies and archive extraction tools. +text = text.rstrip("\n") + "\n" + +checks = { + "SENTINEL_PRODUCER": text.count("+SentinelProducer ="), + "CONSTANT_GAM_CLASS": text.count("Class = ConstantGAM"), + "SENTINEL_SCALAR_A_DECLARATIONS": text.count("SentinelScalarA ="), + "SENTINEL_SCALAR_B_DECLARATIONS": text.count("SentinelScalarB ="), + "SENTINEL_VECTOR_DECLARATIONS": text.count("SentinelVector ="), + "VECTOR_DIMENSION_DECLARATIONS": text.count("NumberOfDimensions = 1"), + "VECTOR_CARDINALITY_DECLARATIONS": text.count("NumberOfElements = 3"), + "S03_OUTPUT_FILENAME": text.count("__MARTE_RUN_DIR__/s03_output.bin"), + "S02_OUTPUT_FILENAME": text.count("s02_output.bin"), + "RUN_DIR_PLACEHOLDER": text.count("__MARTE_RUN_DIR__"), + "FILEWRITER_CLASS": text.count("Class = FileDataSource::FileWriter"), + "THREAD_FUNCTION_LIST": text.count( + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" + ), +} + +expected = { + "SENTINEL_PRODUCER": 1, + "CONSTANT_GAM_CLASS": 1, + "SENTINEL_SCALAR_A_DECLARATIONS": 4, + "SENTINEL_SCALAR_B_DECLARATIONS": 4, + "SENTINEL_VECTOR_DECLARATIONS": 4, + "VECTOR_DIMENSION_DECLARATIONS": 4, + "VECTOR_CARDINALITY_DECLARATIONS": 4, + "S03_OUTPUT_FILENAME": 1, + "S02_OUTPUT_FILENAME": 0, + "RUN_DIR_PLACEHOLDER": 1, + "FILEWRITER_CLASS": 1, + "THREAD_FUNCTION_LIST": 1, +} + +for name in sorted(checks): + print(f"{name}_COUNT={checks[name]}") + +if checks != expected: + raise RuntimeError(f"STRUCTURAL_CHECK_MISMATCH:{checks!r}") + +destination.write_text(text, encoding="utf-8") +print("TRANSFORM_STATUS=PASS") +print("STRUCTURAL_CHECK_STATUS=PASS") +PY_S03_PATCHER + +PYTHON_RC=$? +if [ "$PYTHON_RC" -ne 0 ]; then + fail 25 "TRANSFORM_FAILED_RC_$PYTHON_RC" +fi + +GENERATED_SHA256="$(sha256sum -- "$TEMP_CONFIG" | awk '{print $1}')" +printf "GENERATED_SHA256=%s\n" "$GENERATED_SHA256" + +if [ "$GENERATED_SHA256" != "$EXPECTED_OUTPUT_SHA256" ]; then + fail 26 "OUTPUT_HASH_MISMATCH:$GENERATED_SHA256" +fi + +if [ -f "$OUTPUT_CONFIG" ]; then + if cmp -s -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + printf "S03_BUILD_STATUS=ALREADY_VALID\n" + printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" + printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256" + exit 0 + fi + fail 27 "OUTPUT_EXISTS_BUT_DIFFERS:$OUTPUT_CONFIG" +fi + +chmod 0644 "$TEMP_CONFIG" || fail 28 "OUTPUT_CHMOD_FAILED:$TEMP_CONFIG" +if ! mv -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + fail 29 "ATOMIC_PUBLISH_FAILED:$OUTPUT_CONFIG" +fi +trap - EXIT HUP INT TERM + +printf "S03_BUILD_STATUS=CREATED\n" +printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" +printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256" diff --git a/patchers/configuration/build_s03h1_from_s02.sh b/patchers/configuration/build_s03h1_from_s02.sh new file mode 100755 index 0000000..ef9eac0 --- /dev/null +++ b/patchers/configuration/build_s03h1_from_s02.sh @@ -0,0 +1,458 @@ +#!/usr/bin/env bash +set -u + +SCRIPT_DIR="$( + cd "$(dirname "${BASH_SOURCE[0]}")" && + pwd +)" +PACKAGE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +DEFAULT_SOURCE="$PACKAGE_ROOT/baseline/smoke_s2_binary_recording_from_s01.marte" +DEFAULT_OUTPUT_ROOT="$PACKAGE_ROOT/configurations/generated/S03H1/from_patcher" + +EXPECTED_SOURCE_SHA256="8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1" +EXPECTED_TEST_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" +EXPECTED_DIAGNOSTIC_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3" + +fail() { + code="$1" + shift + printf "S03_H1_BUILD_STATUS=FAIL\n" >&2 + printf "S03_H1_BUILD_ERROR=%s\n" "$*" >&2 + exit "$code" +} + +usage() { + printf "Uso:\n" >&2 + printf " %s TEST|DIAGNOSTIC\n" "$0" >&2 + printf " %s TEST|DIAGNOSTIC\n" "$0" >&2 +} + +case "$#" in + 1) + SOURCE_CONFIG="$DEFAULT_SOURCE" + MODE="$1" + case "$MODE" in + TEST) + OUTPUT_CONFIG="$DEFAULT_OUTPUT_ROOT/smoke_s03h1_execution_profiles_test_from_s02.marte" + ;; + DIAGNOSTIC) + OUTPUT_CONFIG="$DEFAULT_OUTPUT_ROOT/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" + ;; + *) + usage + fail 20 "UNKNOWN_MODE:$MODE" + ;; + esac + ;; + 3) + SOURCE_CONFIG="$1" + OUTPUT_CONFIG="$2" + MODE="$3" + ;; + *) + usage + fail 20 "INVALID_ARGUMENT_COUNT:$#" + ;; +esac + +case "$MODE" in + TEST) + EXPECTED_OUTPUT_SHA256="$EXPECTED_TEST_SHA256" + DISPLAY_CLASS="GAMDataSource" + ;; + DIAGNOSTIC) + EXPECTED_OUTPUT_SHA256="$EXPECTED_DIAGNOSTIC_SHA256" + DISPLAY_CLASS="LoggerDataSource" + ;; + *) + usage + fail 20 "UNKNOWN_MODE:$MODE" + ;; +esac + +if [ ! -f "$SOURCE_CONFIG" ]; then + fail 21 "SOURCE_NOT_FOUND:$SOURCE_CONFIG" +fi + +SOURCE_SHA256="$(sha256sum -- "$SOURCE_CONFIG" | awk '{print $1}')" +printf "MODE=%s\n" "$MODE" +printf "SOURCE_CONFIG=%s\n" "$SOURCE_CONFIG" +printf "EXPECTED_SOURCE_SHA256=%s\n" "$EXPECTED_SOURCE_SHA256" +printf "ACTUAL_SOURCE_SHA256=%s\n" "$SOURCE_SHA256" + +if [ "$SOURCE_SHA256" != "$EXPECTED_SOURCE_SHA256" ]; then + fail 22 "SOURCE_HASH_MISMATCH:$SOURCE_SHA256" +fi + +OUTPUT_DIR="$(dirname "$OUTPUT_CONFIG")" +if ! mkdir -p -- "$OUTPUT_DIR"; then + fail 23 "OUTPUT_DIRECTORY_CREATE_FAILED:$OUTPUT_DIR" +fi + +if [ -e "$OUTPUT_CONFIG" ] && [ ! -f "$OUTPUT_CONFIG" ]; then + fail 24 "OUTPUT_EXISTS_NOT_REGULAR_FILE:$OUTPUT_CONFIG" +fi + +TEMP_CONFIG="$(mktemp "$OUTPUT_DIR/.s03h1_candidate_XXXXXX.marte")" || + fail 25 "TEMPORARY_FILE_CREATE_FAILED:$OUTPUT_DIR" + +cleanup() { + rm -f -- "$TEMP_CONFIG" +} +trap cleanup EXIT HUP INT TERM + +PYTHONHASHSEED=0 python3 - \ + "$SOURCE_CONFIG" \ + "$TEMP_CONFIG" \ + "$MODE" \ + "$DISPLAY_CLASS" <<'PY_S03H1_PATCHER' +import sys +from pathlib import Path + + +source = Path(sys.argv[1]) +destination = Path(sys.argv[2]) +mode = sys.argv[3] +display_class = sys.argv[4] +text = source.read_text(encoding="utf-8") + + +def require_count(haystack, needle, expected, label): + observed = haystack.count(needle) + print(f"{label}_COUNT={observed}") + if observed != expected: + raise RuntimeError( + f"{label}_COUNT_EXPECTED_{expected}_OBSERVED_{observed}" + ) + + +def replace_once(haystack, old, new, label): + require_count(haystack, old, 1, label) + return haystack.replace(old, new, 1) + + +for forbidden in ( + "+SentinelProducer =", + "SentinelScalarA", + "SentinelScalarB", + "SentinelVector", + "s03_output.bin", +): + if forbidden in text: + raise RuntimeError("SOURCE_ALREADY_CONTAINS_S03_CONTENT:" + forbidden) + +if mode not in {"TEST", "DIAGNOSTIC"}: + raise RuntimeError("UNKNOWN_MODE:" + mode) +if display_class not in {"GAMDataSource", "LoggerDataSource"}: + raise RuntimeError("UNKNOWN_DISPLAY_CLASS:" + display_class) + +constant_gam = """ +SentinelProducer = { + Class = ConstantGAM + OutputSignals = { + SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 324508639 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + Default = 610839776 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + Default = {16909060 286397204 555885348} + } + } + } +""" + +gam_display = f""" +GAMDisplay = {{ + Class = IOGAM + InputSignals = {{ + Counter = {{ + DataSource = DDB1 + Type = uint32 + }} + Time = {{ + DataSource = DDB1 + Type = uint32 + }} + State1_Thread1_CycleTime = {{ + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + }} + SentinelScalarA = {{ + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + }} + SentinelScalarB = {{ + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + }} + SentinelVector = {{ + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + }} + }} + OutputSignals = {{ + Counter = {{ + DataSource = Display + Type = uint32 + }} + Time = {{ + DataSource = Display + Type = uint32 + }} + State1_Thread1_CycleTime = {{ + DataSource = Display + Type = uint32 + }} + SentinelScalarA = {{ + DataSource = Display + Type = uint32 + NumberOfElements = 1 + }} + SentinelScalarB = {{ + DataSource = Display + Type = uint32 + NumberOfElements = 1 + }} + SentinelVector = {{ + DataSource = Display + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + }} + }} + }} +""" + +sentinel_signals = """ SentinelScalarA = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + DataSource = DDB1 + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + DataSource = DDB1 + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } +""" + +filewriter_signals = """ SentinelScalarA = { + Type = uint32 + NumberOfElements = 1 + } + SentinelScalarB = { + Type = uint32 + NumberOfElements = 1 + } + SentinelVector = { + Type = uint32 + NumberOfDimensions = 1 + NumberOfElements = 3 + } +""" + +require_count(text, " +GAMDisplay = {\n", 1, "GAMDISPLAY_START_ANCHOR") +require_count(text, " +IOGAM_Writer = {\n", 1, "IOGAM_WRITER_START_ANCHOR") +display_start = text.index(" +GAMDisplay = {\n") +writer_start = text.index(" +IOGAM_Writer = {\n", display_start) +text = text[:display_start] + constant_gam + gam_display + text[writer_start:] + +writer_start = text.index(" +IOGAM_Writer = {\n") +writer_end = text.index(" }\n +Data = {\n", writer_start) +writer = text[writer_start:writer_end] + +writer = replace_once( + writer, + """ State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } + } + OutputSignals = { +""", + """ State1_Thread1_CycleTime = { + Alias = State1.Thread1_CycleTime + DataSource = Timings + Type = uint32 + } +""" + sentinel_signals + """ } + OutputSignals = { +""", + "WRITER_INPUT_ANCHOR", +) + +writer_output_signals = sentinel_signals.replace( + "DataSource = DDB1", "DataSource = FileWriter" +) +writer = replace_once( + writer, + """ State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } + } + } +""", + """ State1_Thread1_CycleTime = { + DataSource = FileWriter + Type = uint32 + } +""" + writer_output_signals + """ } + } +""", + "WRITER_OUTPUT_ANCHOR", +) +text = text[:writer_start] + writer + text[writer_end:] + +text = replace_once( + text, + """ +Display = { + Class = LoggerDataSource + } +""", + f""" +Display = {{ + Class = {display_class} + }} +""", + "DISPLAY_DATASOURCE_ANCHOR", +) + +filewriter_start = text.index(" +FileWriter = {\n") +filewriter_end = text.index(" }\n +States = {\n", filewriter_start) +filewriter = text[filewriter_start:filewriter_end] +filewriter = replace_once( + filewriter, + ' Filename = "__MARTE_RUN_DIR__/s02_output.bin"\n', + ' Filename = "__MARTE_RUN_DIR__/s03_output.bin"\n', + "OUTPUT_FILENAME_ANCHOR", +) +filewriter = replace_once( + filewriter, + """ State1_Thread1_CycleTime = { + Type = uint32 + } + } + } +""", + """ State1_Thread1_CycleTime = { + Type = uint32 + } +""" + filewriter_signals + """ } + } +""", + "FILEWRITER_SIGNAL_ANCHOR", +) +text = text[:filewriter_start] + filewriter + text[filewriter_end:] + +text = replace_once( + text, + "Functions = {GAMTimer GAMDisplay IOGAM_Writer}", + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}", + "THREAD_FUNCTION_LIST_ANCHOR", +) + +text = text.rstrip("\n") + "\n" + +checks = { + "SENTINEL_PRODUCER": text.count("+SentinelProducer ="), + "CONSTANT_GAM_CLASS": text.count("Class = ConstantGAM"), + "GAMDISPLAY_OBJECT": text.count("+GAMDisplay ="), + "SENTINEL_SCALAR_A_DECLARATIONS": text.count("SentinelScalarA ="), + "SENTINEL_SCALAR_B_DECLARATIONS": text.count("SentinelScalarB ="), + "SENTINEL_VECTOR_DECLARATIONS": text.count("SentinelVector ="), + "VECTOR_DIMENSION_DECLARATIONS": text.count("NumberOfDimensions = 1"), + "VECTOR_CARDINALITY_DECLARATIONS": text.count("NumberOfElements = 3"), + "S03_OUTPUT_FILENAME": text.count("__MARTE_RUN_DIR__/s03_output.bin"), + "S02_OUTPUT_FILENAME": text.count("s02_output.bin"), + "RUN_DIR_PLACEHOLDER": text.count("__MARTE_RUN_DIR__"), + "FILEWRITER_CLASS": text.count("Class = FileDataSource::FileWriter"), + "THREAD_FUNCTION_LIST": text.count( + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" + ), + "DISPLAY_DATASOURCE_BLOCK": text.count( + f" +Display = {{\n Class = {display_class}\n }}\n" + ), +} +expected = { + "SENTINEL_PRODUCER": 1, + "CONSTANT_GAM_CLASS": 1, + "GAMDISPLAY_OBJECT": 1, + "SENTINEL_SCALAR_A_DECLARATIONS": 6, + "SENTINEL_SCALAR_B_DECLARATIONS": 6, + "SENTINEL_VECTOR_DECLARATIONS": 6, + "VECTOR_DIMENSION_DECLARATIONS": 6, + "VECTOR_CARDINALITY_DECLARATIONS": 6, + "S03_OUTPUT_FILENAME": 1, + "S02_OUTPUT_FILENAME": 0, + "RUN_DIR_PLACEHOLDER": 1, + "FILEWRITER_CLASS": 1, + "THREAD_FUNCTION_LIST": 1, + "DISPLAY_DATASOURCE_BLOCK": 1, +} + +for name in sorted(checks): + print(f"{name}_COUNT={checks[name]}") +if checks != expected: + raise RuntimeError(f"STRUCTURAL_CHECK_MISMATCH:{checks!r}") + +destination.write_text(text, encoding="utf-8") +print("TRANSFORM_STATUS=PASS") +print("STRUCTURAL_CHECK_STATUS=PASS") +PY_S03H1_PATCHER +PYTHON_RC=$? + +if [ "$PYTHON_RC" -ne 0 ]; then + fail 26 "TRANSFORM_FAILED_RC_$PYTHON_RC" +fi + +GENERATED_SHA256="$(sha256sum -- "$TEMP_CONFIG" | awk '{print $1}')" +printf "GENERATED_SHA256=%s\n" "$GENERATED_SHA256" +printf "EXPECTED_OUTPUT_SHA256=%s\n" "$EXPECTED_OUTPUT_SHA256" + +if [ "$GENERATED_SHA256" != "$EXPECTED_OUTPUT_SHA256" ]; then + fail 27 "OUTPUT_HASH_MISMATCH:$GENERATED_SHA256" +fi + +if [ -f "$OUTPUT_CONFIG" ]; then + if cmp -s -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + printf "S03_H1_BUILD_STATUS=ALREADY_VALID\n" + printf "MODE=%s\n" "$MODE" + printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" + printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256" + exit 0 + fi + fail 28 "OUTPUT_EXISTS_BUT_DIFFERS:$OUTPUT_CONFIG" +fi + +chmod 0644 "$TEMP_CONFIG" || fail 29 "OUTPUT_CHMOD_FAILED:$TEMP_CONFIG" +if ! mv -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then + fail 30 "ATOMIC_PUBLISH_FAILED:$OUTPUT_CONFIG" +fi +trap - EXIT HUP INT TERM + +printf "S03_H1_BUILD_STATUS=CREATED\n" +printf "MODE=%s\n" "$MODE" +printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG" +printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256" diff --git a/patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh b/patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh new file mode 100755 index 0000000..236caff --- /dev/null +++ b/patchers/configuration/build_s03h3_minimal_from_s03h1_test.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +set -u +set -o pipefail + +PATCHER_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +SUITE="$(cd -- "$PATCHER_DIR/../.." && pwd -P)" + +PARENT="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" +OUTPUT_DIR="$SUITE/configurations/generated/S03H3/from_patcher" +OUTPUT="$OUTPUT_DIR/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte" +VERIFIER="$SUITE/scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py" +EXPECTED_PARENT_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" + +TEMP_OUTPUT="" + +cleanup() { + if [ -n "$TEMP_OUTPUT" ]; then + rm -f -- "$TEMP_OUTPUT" + fi +} +trap cleanup EXIT HUP INT TERM + +fail() { + local code="$1" + local reason="$2" + printf 'PATCHER_STATUS=FAIL\n' >&2 + printf 'PATCHER_ERROR=%s\n' "$reason" >&2 + printf 'SOURCE_MODIFIED_IN_PLACE=NO\n' >&2 + exit "$code" +} + +if [ "$#" -ne 0 ]; then + printf 'Uso: %s\n' "$0" >&2 + exit 2 +fi + +if [ ! -f "$PARENT" ]; then + fail 10 "CANONICAL_TEST_PARENT_MISSING" +fi +if [ ! -f "$VERIFIER" ]; then + fail 11 "MINIMAL_VERIFIER_MISSING" +fi + +PARENT_SHA256_BEFORE="$(sha256sum -- "$PARENT" | awk '{print $1}')" || \ + fail 12 "PARENT_HASH_UNAVAILABLE" +if [ "$PARENT_SHA256_BEFORE" != "$EXPECTED_PARENT_SHA256" ]; then + fail 13 "CANONICAL_TEST_PARENT_SHA256_MISMATCH" +fi + +if ! mkdir -p -- "$OUTPUT_DIR"; then + fail 14 "OUTPUT_DIRECTORY_CREATION_FAILED" +fi +TEMP_OUTPUT="$(mktemp "$OUTPUT_DIR/.s03h3_minimal.XXXXXX")" || \ + fail 15 "TEMPORARY_OUTPUT_CREATION_FAILED" + +if ! python3 - "$PARENT" "$TEMP_OUTPUT" <<'PY' +import sys +from pathlib import Path + +parent = Path(sys.argv[1]) +target = Path(sys.argv[2]) +text = parent.read_bytes().decode("utf-8") + +gamdisplay_start = " +GAMDisplay = {\n" +writer_start = " +IOGAM_Writer = {\n" +display_block = ( + " +Display = {\n" + " Class = GAMDataSource\n" + " }\n" +) +old_functions = ( + " Functions = " + "{GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" +) +new_functions = ( + " Functions = " + "{GAMTimer SentinelProducer IOGAM_Writer}" +) + +checks = { + "GAMDISPLAY_ANCHOR": text.count(gamdisplay_start), + "WRITER_ANCHOR": text.count(writer_start), + "DISPLAY_BLOCK": text.count(display_block), + "FUNCTIONS_ANCHOR": text.count(old_functions), + "MINIMAL_FUNCTIONS_PREEXISTING": text.count(new_functions), +} +if checks != { + "GAMDISPLAY_ANCHOR": 1, + "WRITER_ANCHOR": 1, + "DISPLAY_BLOCK": 1, + "FUNCTIONS_ANCHOR": 1, + "MINIMAL_FUNCTIONS_PREEXISTING": 0, +}: + for name, value in checks.items(): + print(f"{name}_COUNT={value}", file=sys.stderr) + raise SystemExit(20) + +start = text.index(gamdisplay_start) +end = text.index(writer_start, start) +if end <= start: + raise SystemExit(21) + +result = text[:start] + text[end:] +result = result.replace(display_block, "", 1) +result = result.replace(old_functions, new_functions, 1) +target.write_bytes(result.encode("utf-8")) +PY +then + fail 16 "STRUCTURAL_TRANSFORMATION_FAILED" +fi + +PARENT_SHA256_AFTER="$(sha256sum -- "$PARENT" | awk '{print $1}')" || \ + fail 17 "PARENT_POST_HASH_UNAVAILABLE" +if [ "$PARENT_SHA256_AFTER" != "$PARENT_SHA256_BEFORE" ]; then + fail 18 "SOURCE_MODIFIED_IN_PLACE" +fi + +if ! python3 "$VERIFIER" CANONICAL "$PARENT" "$TEMP_OUTPUT"; then + fail 19 "CANONICAL_STRUCTURAL_COMPLETENESS_FAILED" +fi + +if [ -e "$OUTPUT" ]; then + if [ ! -f "$OUTPUT" ]; then + fail 20 "OUTPUT_EXISTS_NOT_REGULAR_FILE" + fi + if ! cmp -s -- "$TEMP_OUTPUT" "$OUTPUT"; then + fail 21 "EXISTING_OUTPUT_DIFFERS_FROM_DETERMINISTIC_RESULT" + fi + if ! python3 "$VERIFIER" CANONICAL "$PARENT" "$OUTPUT" >/dev/null; then + fail 22 "EXISTING_OUTPUT_COMPLETENESS_FAILED" + fi + printf 'PATCHER_RESULT=ALREADY_VALID\n' +else + chmod 0644 -- "$TEMP_OUTPUT" || fail 23 "OUTPUT_MODE_SET_FAILED" + mv -- "$TEMP_OUTPUT" "$OUTPUT" || fail 24 "OUTPUT_PUBLICATION_FAILED" + TEMP_OUTPUT="" + if ! python3 "$VERIFIER" CANONICAL "$PARENT" "$OUTPUT" >/dev/null; then + fail 25 "PUBLISHED_OUTPUT_COMPLETENESS_FAILED" + fi + printf 'PATCHER_RESULT=CREATED\n' +fi + +OUTPUT_SHA256="$(sha256sum -- "$OUTPUT" | awk '{print $1}')" || \ + fail 26 "OUTPUT_HASH_UNAVAILABLE" +printf 'PATCHER_STATUS=PASS\n' +printf 'PATCHER_DETERMINISM=PASS\n' +printf 'PATCHER_PARENT_HASH_MATCH=PASS\n' +printf 'CANONICAL_STRUCTURAL_COMPLETENESS_STATUS=PASS\n' +printf 'SOURCE_MODIFIED_IN_PLACE=NO\n' +printf 'MINIMAL_CONFIG=%s\n' "$OUTPUT" +printf 'MINIMAL_CONFIG_SHA256=%s\n' "$OUTPUT_SHA256" diff --git a/scenarios/S01_timing_display/validate.sh b/scenarios/S01_timing_display/validate.sh new file mode 100755 index 0000000..d41870a --- /dev/null +++ b/scenarios/S01_timing_display/validate.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +set -u + +if [ "$#" -ne 1 ]; then + printf "Uso: %s \n" "$0" >&2 + exit 2 +fi + +RUN_LOG="$1" + +if [ ! -f "$RUN_LOG" ]; then + printf "LOG ASSENTE: %s\n" "$RUN_LOG" >&2 + exit 3 +fi + +START_LINES="$( + grep -Ec \ + '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' \ + "$RUN_LOG" || true +)" + +COUNTER_SAMPLES="$( + grep -Ec \ + '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Counter \[0:0\]:[0-9]+[[:space:]]*$' \ + "$RUN_LOG" || true +)" + +CYCLE_TIME_SAMPLES="$( + grep -Ec \ + '^\[Information - LoggerBroker\.cpp:[0-9]+\]: State1_Thread1_CycleTime \[0:0\]:[0-9]+[[:space:]]*$' \ + "$RUN_LOG" || true +)" + +SIGINT_LINES="$( + grep -Ec \ + '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' \ + "$RUN_LOG" || true +)" + +STOP_OK_LINES="$( + grep -Ec \ + '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' \ + "$RUN_LOG" || true +)" + +FATAL_ERROR_LINES="$( + grep -Ec '^\[FatalError - ' "$RUN_LOG" || true +)" + +ERROR_LINES="$( + grep -Ec '^\[Error - ' "$RUN_LOG" || true +)" + +NO_ERROR_LINES="$( + grep -Ec '^\[NoError - ' "$RUN_LOG" || true +)" + +DOUBLE_STOP_LINES="$( + grep -Fc \ + 'Could not stop the RealTimeApplication. Was it ever started?' \ + "$RUN_LOG" || true +)" + +LAST_COUNTER="$( + grep -E \ + '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Counter \[0:0\]:[0-9]+[[:space:]]*$' \ + "$RUN_LOG" | + tail -n 1 | + awk -F: '{gsub(/[[:space:]]/, "", $NF); print $NF}' +)" + +printf "============================================================\n" +printf "VALIDAZIONE PRECISA S01\n" +printf "============================================================\n" +printf "Log: %s\n" "$RUN_LOG" +printf "Avvii di State1: %s\n" "$START_LINES" +printf "Campioni Counter reali: %s\n" "$COUNTER_SAMPLES" +printf "Ultimo Counter: %s\n" "${LAST_COUNTER:-non disponibile}" +printf "Campioni CycleTime reali: %s\n" "$CYCLE_TIME_SAMPLES" +printf "SIGINT ricevuti: %s\n" "$SIGINT_LINES" +printf "Stop riusciti: %s\n" "$STOP_OK_LINES" +printf "Righe FatalError reali: %s\n" "$FATAL_ERROR_LINES" +printf "Righe Error reali: %s\n" "$ERROR_LINES" +printf "Righe NoError: %s\n" "$NO_ERROR_LINES" +printf "Anomalie doppio arresto: %s\n" "$DOUBLE_STOP_LINES" + +FUNCTIONAL_OK=0 + +if [ "$START_LINES" -ge 1 ] && + [ "$COUNTER_SAMPLES" -ge 50 ] && + [ "$CYCLE_TIME_SAMPLES" -ge 50 ]; then + FUNCTIONAL_OK=1 +fi + +printf "\n============================================================\n" +printf "CLASSIFICAZIONE\n" +printf "============================================================\n" + +if [ "$FUNCTIONAL_OK" -eq 1 ]; then + printf "[VERIFICATO] Funzionamento S01 e produzione dei segnali.\n" +else + printf "[NON DIMOSTRATO] Funzionamento S01 non sufficientemente provato.\n" + exit 20 +fi + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$FATAL_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ]; then + printf "[VERIFICATO] Shutdown privo di Error e FatalError.\n" + exit 0 +fi + +printf "[DA VERIFICARE] Funzionalità riuscita, ma shutdown con anomalia.\n" +exit 10 diff --git a/scenarios/S02_binary_recording/validate.sh b/scenarios/S02_binary_recording/validate.sh new file mode 100755 index 0000000..448bf80 --- /dev/null +++ b/scenarios/S02_binary_recording/validate.sh @@ -0,0 +1,722 @@ +#!/usr/bin/env bash +set -u + +if [ "$#" -ne 1 ]; then + printf "Uso: %s \n" "$0" >&2 + exit 2 +fi + +INPUT_PATH="$1" + +if [ -d "$INPUT_PATH" ]; then + RUN_DIR="$(cd "$INPUT_PATH" >/dev/null 2>&1 && pwd)" + RUN_LOG="$RUN_DIR/run.log" +elif [ -f "$INPUT_PATH" ]; then + RUN_LOG="$(cd "$(dirname "$INPUT_PATH")" >/dev/null 2>&1 && pwd)/$(basename "$INPUT_PATH")" + RUN_DIR="$(dirname "$RUN_LOG")" +else + printf "[NON DIMOSTRATO] Percorso di validazione non trovato: %s\n" "$INPUT_PATH" >&2 + exit 21 +fi + +RUN_CONFIG="$RUN_DIR/configuration.marte" +OUTPUT_FILE="$RUN_DIR/s02_output.bin" +RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt" + +for required_file in "$RUN_LOG" "$RUN_CONFIG" "$OUTPUT_FILE"; do + if [ ! -f "$required_file" ]; then + printf "[NON DIMOSTRATO] File richiesto assente: %s\n" "$required_file" >&2 + exit 21 + fi +done + +count_lines() { + pattern="$1" + file="$2" + grep -Ec "$pattern" "$file" 2>/dev/null || true +} + +START_LINES="$(count_lines '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' "$RUN_LOG")" +APPLICATION_START_LINES="$(count_lines '^\[Information - MARTeApp\.cpp:[0-9]+\]: Application starting[[:space:]]*$' "$RUN_LOG")" +FILE_OPEN_LINES="$(count_lines '^\[Information - FileWriter\.cpp:[0-9]+\]: Going to open file with name ' "$RUN_LOG")" +SIGINT_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' "$RUN_LOG")" +STOP_OK_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' "$RUN_LOG")" +TERMINATED_LINES="$(count_lines '^\[NoError - MARTeApp\.cpp:[0-9]+\]: Application terminated[[:space:]]*$' "$RUN_LOG")" + +STARTUP_ERROR_LINES="$(count_lines 'Failed dlopen|Failed CreateByName|Failed to Initialise object|Failed to initialise the ObjectRegistryDatabase|Could not Initialise the loader' "$RUN_LOG")" +FATAL_ERROR_LINES="$(count_lines '^\[FatalError ' "$RUN_LOG")" +OS_ERROR_LINES="$(count_lines '^\[OSError ' "$RUN_LOG")" +ERROR_LINES="$(count_lines '^\[Error ' "$RUN_LOG")" +INITIALISATION_ERROR_LINES="$(count_lines '^\[InitialisationEr ' "$RUN_LOG")" +NO_ERROR_LINES="$(count_lines '^\[NoError ' "$RUN_LOG")" +WARNING_LINES="$(count_lines '^\[Warning ' "$RUN_LOG")" +PRIORITY_CLIP_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Requested a thread priority that is higher than ' "$RUN_LOG")" +PRIORITY_FAIL_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Failed to change the thread priority' "$RUN_LOG")" +UNCLASSIFIED_WARNING_LINES=$((WARNING_LINES - PRIORITY_CLIP_LINES - PRIORITY_FAIL_LINES)) + +if [ "$UNCLASSIFIED_WARNING_LINES" -lt 0 ]; then + UNCLASSIFIED_WARNING_LINES=0 +fi + +DOUBLE_STOP_LINES="$(count_lines '^\[FatalError - RealTimeApplication\.cpp:[0-9]+\]: Could not stop the RealTimeApplication\. Was it ever started\?[[:space:]]*$' "$RUN_LOG")" +ASYNC_EVENTSEM_CLOSE_LINES="$(count_lines '^\[FatalError - MemoryMapAsyncOutputBroker\.cpp:[0-9]+\]: Could not Close the EventSem\.[[:space:]]*$' "$RUN_LOG")" +PTHREAD_MUTEX_DESTROY_LINES="$(count_lines '^\[OSError - EventSem\.cpp:[0-9]+\]: Error: pthread_mutex_destroy\(\)[[:space:]]*$' "$RUN_LOG")" + +UNCLASSIFIED_FATAL_LINES=$((FATAL_ERROR_LINES - DOUBLE_STOP_LINES - ASYNC_EVENTSEM_CLOSE_LINES)) +UNCLASSIFIED_OS_ERROR_LINES=$((OS_ERROR_LINES - PTHREAD_MUTEX_DESTROY_LINES)) + +if [ "$UNCLASSIFIED_FATAL_LINES" -lt 0 ]; then + UNCLASSIFIED_FATAL_LINES=0 +fi +if [ "$UNCLASSIFIED_OS_ERROR_LINES" -lt 0 ]; then + UNCLASSIFIED_OS_ERROR_LINES=0 +fi + +PLACEHOLDER_COUNT="$(grep -o '__MARTE_RUN_DIR__' "$RUN_CONFIG" 2>/dev/null | wc -l)" +NAMESPACED_CLASS_COUNT="$(count_lines '^[[:space:]]*Class = FileDataSource::FileWriter[[:space:]]*$' "$RUN_CONFIG")" +PLAIN_CLASS_COUNT="$(count_lines '^[[:space:]]*Class = FileWriter[[:space:]]*$' "$RUN_CONFIG")" +EXPECTED_FILENAME="$RUN_DIR/s02_output.bin" +FILENAME_MATCH_LINES="$( + python3 - "$RUN_CONFIG" "$EXPECTED_FILENAME" <<'PYCONFIG' +import sys +from pathlib import Path + +config_path = Path(sys.argv[1]) +expected = f'Filename = "{sys.argv[2]}"' +count = sum( + 1 + for line in config_path.read_text(encoding="utf-8").splitlines() + if line.strip() == expected +) +print(count) +PYCONFIG +)" + +MARTE_EXIT_CODE="NOT_AVAILABLE" +TEE_EXIT_CODE="NOT_AVAILABLE" +if [ -f "$RUNNER_SUMMARY" ]; then + value="$(sed -n 's/^Exit code MARTe:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" + if [ -n "$value" ]; then + MARTE_EXIT_CODE="$value" + fi + value="$(sed -n 's/^Exit code tee:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" + if [ -n "$value" ]; then + TEE_EXIT_CODE="$value" + fi +fi + +case "$MARTE_EXIT_CODE" in + 124) + MARTE_EXIT_CLASS="TIMEOUT_EXIT_124" + ;; + 137) + MARTE_EXIT_CLASS="EXIT_137_SIGKILL" + ;; + 0) + MARTE_EXIT_CLASS="EXIT_0" + ;; + NOT_AVAILABLE) + MARTE_EXIT_CLASS="NOT_AVAILABLE" + ;; + *) + MARTE_EXIT_CLASS="OTHER_EXIT_CODE" + ;; +esac + +CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s02_validator_config_XXXXXX.log")" +BINARY_REPORT="$(mktemp "${TMPDIR:-/tmp}/s02_validator_binary_XXXXXX.log")" +trap 'rm -f -- "$CONFIG_REPORT" "$BINARY_REPORT"' EXIT + +python3 - "$RUN_CONFIG" >"$CONFIG_REPORT" 2>&1 <<'PYCONFIGOBS' +import re +import sys +from decimal import Decimal, InvalidOperation +from pathlib import Path + + +class Node: + def __init__(self, name): + self.name = name.lstrip("+$") + self.properties = {} + + +def sanitise(text): + """Remove comments while preserving strings, braces and line boundaries.""" + output = [] + index = 0 + quote = None + block_comment = False + while index < len(text): + char = text[index] + next_char = text[index + 1] if index + 1 < len(text) else "" + if block_comment: + if char == "*" and next_char == "/": + block_comment = False + output.extend(" ") + index += 2 + else: + output.append("\n" if char == "\n" else " ") + index += 1 + continue + if quote: + output.append(char) + if char == "\\" and index + 1 < len(text): + output.append(text[index + 1]) + index += 2 + continue + if char == quote: + quote = None + index += 1 + continue + if char in {'"', "'"}: + quote = char + output.append(char) + index += 1 + continue + if char == "/" and next_char == "*": + block_comment = True + output.extend(" ") + index += 2 + continue + if char == "/" and next_char == "/": + while index < len(text) and text[index] != "\n": + output.append(" ") + index += 1 + continue + if char == "#": + while index < len(text) and text[index] != "\n": + output.append(" ") + index += 1 + continue + output.append(char) + index += 1 + return "".join(output) + + +def braces_outside_strings(line): + result = [] + quote = None + index = 0 + while index < len(line): + char = line[index] + if quote: + if char == "\\" and index + 1 < len(line): + index += 2 + continue + if char == quote: + quote = None + elif char in {'"', "'"}: + quote = char + elif char in "{}": + result.append(char) + index += 1 + return result + + +text = Path(sys.argv[1]).read_text(encoding="utf-8") +clean = sanitise(text) +nodes = [] +stack = [] +object_open = re.compile(r"^\s*([+$]?[A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*\{") +assignment = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$") + +for line in clean.splitlines(): + match = object_open.match(line) + if match: + node = Node(match.group(1)) + nodes.append(node) + stack.append(node) + elif stack: + match = assignment.match(line) + if match and not match.group(2).startswith("{"): + stack[-1].properties[match.group(1)] = match.group(2).strip() + + opens_and_closes = braces_outside_strings(line) + opening_already_consumed = bool(match and object_open.match(line)) + if opening_already_consumed: + removed = False + adjusted = [] + for brace in opens_and_closes: + if brace == "{" and not removed: + removed = True + continue + adjusted.append(brace) + opens_and_closes = adjusted + for brace in opens_and_closes: + if brace == "{": + stack.append(Node("")) + elif stack: + stack.pop() + + +def report_objects(prefix, classes, property_name): + matches = [node for node in nodes if node.properties.get("Class") in classes] + names = ",".join(node.name for node in matches) or "NOT_AVAILABLE_OBJECT_NOT_FOUND" + affinities = [] + for node in matches: + value = node.properties.get(property_name, "NOT_EXPLICITLY_CONFIGURED") + affinities.append(f"{node.name}={value}") + affinity = ",".join(affinities) or "NOT_AVAILABLE_OBJECT_NOT_FOUND" + print(f"{prefix}_OBJECT_COUNT={len(matches)}") + print(f"{prefix}_OBJECT_NAMES={names}") + print(f"{prefix}_AFFINITY={affinity}") + + +report_objects("REALTIME_THREAD", {"RealTimeThread"}, "CPUs") +report_objects("FILE_WRITER", {"FileDataSource::FileWriter", "FileWriter"}, "CPUMask") +report_objects("LINUX_TIMER", {"LinuxTimer"}, "CPUMask") + +frequency_values = [] +for node in nodes: + raw = node.properties.get("Frequency") + if raw is None: + continue + raw = raw.strip().strip('"').strip("'") + try: + value = Decimal(raw) + except InvalidOperation: + continue + if value > 0: + frequency_values.append(value) + +unique_frequencies = sorted(set(frequency_values)) +if len(unique_frequencies) == 1: + frequency = unique_frequencies[0] + period = Decimal(1_000_000) / frequency + period_text = str(int(period)) if period == period.to_integral_value() else format(period, ".6f").rstrip("0").rstrip(".") + frequency_text = format(frequency, "f").rstrip("0").rstrip(".") if "." in format(frequency, "f") else format(frequency, "f") + print(f"DT_NOMINAL_PERIOD_US={period_text}") + print(f"DT_NOMINAL_SOURCE=CONFIG_FREQUENCY_{frequency_text}_HZ") +elif not unique_frequencies: + print("DT_NOMINAL_PERIOD_US=NOT_AVAILABLE") + print("DT_NOMINAL_SOURCE=NOT_AVAILABLE_NO_POSITIVE_FREQUENCY") +else: + print("DT_NOMINAL_PERIOD_US=NOT_AVAILABLE") + print("DT_NOMINAL_SOURCE=NOT_AVAILABLE_MULTIPLE_FREQUENCIES") + +print("CONFIG_OBSERVABILITY_STATUS=PASS") +PYCONFIGOBS + +CONFIG_RC=$? + +config_value() { + key="$1" + sed -n "s/^${key}=//p" "$CONFIG_REPORT" | tail -n 1 +} + +CONFIG_OBSERVABILITY_STATUS="$(config_value CONFIG_OBSERVABILITY_STATUS)" +REALTIME_THREAD_OBJECT_COUNT="$(config_value REALTIME_THREAD_OBJECT_COUNT)" +REALTIME_THREAD_OBJECT_NAMES="$(config_value REALTIME_THREAD_OBJECT_NAMES)" +REALTIME_THREAD_AFFINITY="$(config_value REALTIME_THREAD_AFFINITY)" +FILE_WRITER_OBJECT_COUNT="$(config_value FILE_WRITER_OBJECT_COUNT)" +FILE_WRITER_OBJECT_NAMES="$(config_value FILE_WRITER_OBJECT_NAMES)" +FILE_WRITER_AFFINITY="$(config_value FILE_WRITER_AFFINITY)" +LINUX_TIMER_OBJECT_COUNT="$(config_value LINUX_TIMER_OBJECT_COUNT)" +LINUX_TIMER_OBJECT_NAMES="$(config_value LINUX_TIMER_OBJECT_NAMES)" +LINUX_TIMER_AFFINITY="$(config_value LINUX_TIMER_AFFINITY)" +DT_NOMINAL_PERIOD_US="$(config_value DT_NOMINAL_PERIOD_US)" +DT_NOMINAL_SOURCE="$(config_value DT_NOMINAL_SOURCE)" + +python3 - "$OUTPUT_FILE" "${DT_NOMINAL_PERIOD_US:-NOT_AVAILABLE}" >"$BINARY_REPORT" 2>&1 <<'PY' +from collections import Counter +from decimal import Decimal, InvalidOperation +import struct +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +nominal_period_raw = sys.argv[2] +data = path.read_bytes() + +expected_names = [ + "Counter", + "Time", + "State1_Thread1_CycleTime", +] +expected_type_hex = "0408" +expected_signals = len(expected_names) +signal_header_bytes = 2 + 32 + 4 + +print(f"FILE_SIZE_BYTES={len(data)}") + +if len(data) < 4: + print("BINARY_STATUS=FAIL_HEADER_TOO_SMALL") + sys.exit(31) + +number_of_signals = struct.unpack_from(" len(data): + print(f"TRUNCATED_SIGNAL_HEADER_INDEX={index}") + print("BINARY_STATUS=FAIL_TRUNCATED_SIGNAL_HEADER") + sys.exit(33) + + type_bytes = data[offset:offset + 2] + offset += 2 + + raw_name = data[offset:offset + 32] + offset += 32 + + number_of_elements = struct.unpack_from(" 0] + +print(f"COUNTER_FIRST={counters[0]}") +print(f"COUNTER_LAST={counters[-1]}") +print(f"COUNTER_GAP_COUNT={counter_gap_count}") +print(f"TIME_FIRST={times[0]}") +print(f"TIME_LAST={times[-1]}") +print(f"TIME_DECREASE_COUNT={time_decrease_count}") +print(f"NONZERO_CYCLE_TIME_SAMPLES={len(nonzero_cycle_times)}") + +if nonzero_cycle_times: + print(f"CYCLE_TIME_MIN={min(nonzero_cycle_times)}") + print(f"CYCLE_TIME_MAX={max(nonzero_cycle_times)}") + print(f"CYCLE_TIME_MEAN={sum(nonzero_cycle_times) / len(nonzero_cycle_times):.3f}") +else: + print("CYCLE_TIME_MIN=NOT_AVAILABLE") + print("CYCLE_TIME_MAX=NOT_AVAILABLE") + print("CYCLE_TIME_MEAN=NOT_AVAILABLE") + +if counters[0] != 1 or counter_gap_count != 0: + print("BINARY_STATUS=FAIL_COUNTER_SEQUENCE") + sys.exit(39) + +if time_decrease_count != 0: + print("BINARY_STATUS=FAIL_TIME_MONOTONICITY") + sys.exit(40) + +if len(nonzero_cycle_times) == 0: + print("BINARY_STATUS=FAIL_NO_CYCLE_TIME") + sys.exit(41) + +print("BINARY_STATUS=PASS") +PY + +BINARY_RC=$? + +binary_value() { + key="$1" + sed -n "s/^${key}=//p" "$BINARY_REPORT" | tail -n 1 +} + +BINARY_STATUS="$(binary_value BINARY_STATUS)" +FILE_SIZE_BYTES="$(binary_value FILE_SIZE_BYTES)" +NUMBER_OF_SIGNALS="$(binary_value NUMBER_OF_SIGNALS)" +SIGNAL_NAMES="$(binary_value SIGNAL_NAMES)" +HEADER_BYTES="$(binary_value HEADER_BYTES)" +SAMPLE_BYTES="$(binary_value SAMPLE_BYTES)" +PAYLOAD_BYTES="$(binary_value PAYLOAD_BYTES)" +PAYLOAD_REMAINDER_BYTES="$(binary_value PAYLOAD_REMAINDER_BYTES)" +COMPLETE_SAMPLE_COUNT="$(binary_value COMPLETE_SAMPLE_COUNT)" +COUNTER_FIRST="$(binary_value COUNTER_FIRST)" +COUNTER_LAST="$(binary_value COUNTER_LAST)" +COUNTER_GAP_COUNT="$(binary_value COUNTER_GAP_COUNT)" +TIME_FIRST="$(binary_value TIME_FIRST)" +TIME_LAST="$(binary_value TIME_LAST)" +TIME_DECREASE_COUNT="$(binary_value TIME_DECREASE_COUNT)" +NONZERO_CYCLE_TIME_SAMPLES="$(binary_value NONZERO_CYCLE_TIME_SAMPLES)" +CYCLE_TIME_MIN="$(binary_value CYCLE_TIME_MIN)" +CYCLE_TIME_MAX="$(binary_value CYCLE_TIME_MAX)" +CYCLE_TIME_MEAN="$(binary_value CYCLE_TIME_MEAN)" +DT_INTERVAL_COUNT="$(binary_value DT_INTERVAL_COUNT)" +DT_DISTINCT_VALUES="$(binary_value DT_DISTINCT_VALUES)" +DT_HISTOGRAM="$(binary_value DT_HISTOGRAM)" +DT_MIN="$(binary_value DT_MIN)" +DT_MAX="$(binary_value DT_MAX)" +DT_ANOMALY_COUNT="$(binary_value DT_ANOMALY_COUNT)" +DT_ANOMALY_INDICES="$(binary_value DT_ANOMALY_INDICES)" +DT_FIRST_ANOMALY_BETWEEN_RECORDS="$(binary_value DT_FIRST_ANOMALY_BETWEEN_RECORDS)" +DT_FIRST_ANOMALY_COUNTER_PAIR="$(binary_value DT_FIRST_ANOMALY_COUNTER_PAIR)" +DT_FIRST_ANOMALY_VALUE="$(binary_value DT_FIRST_ANOMALY_VALUE)" +LEADING_ZERO_CYCLE_TIME_SAMPLES="$(binary_value LEADING_ZERO_CYCLE_TIME_SAMPLES)" + +affinity_display() { + object_count="$1" + affinity_mapping="$2" + if [ "$object_count" = "1" ]; then + printf "%s" "${affinity_mapping#*=}" + else + printf "%s" "$affinity_mapping" + fi +} + +REALTIME_THREAD_AFFINITY_DISPLAY="$(affinity_display "${REALTIME_THREAD_OBJECT_COUNT:-0}" "${REALTIME_THREAD_AFFINITY:-NOT_AVAILABLE}")" +FILE_WRITER_AFFINITY_DISPLAY="$(affinity_display "${FILE_WRITER_OBJECT_COUNT:-0}" "${FILE_WRITER_AFFINITY:-NOT_AVAILABLE}")" +LINUX_TIMER_AFFINITY_DISPLAY="$(affinity_display "${LINUX_TIMER_OBJECT_COUNT:-0}" "${LINUX_TIMER_AFFINITY:-NOT_AVAILABLE}")" + +printf "============================================================\n" +printf "VALIDAZIONE PRECISA S02\n" +printf "============================================================\n" +printf "Run directory: %s\n" "$RUN_DIR" +printf "Log: %s\n" "$RUN_LOG" +printf "Configurazione: %s\n" "$RUN_CONFIG" +printf "File binario: %s\n" "$OUTPUT_FILE" +printf "Avvii State1: %s\n" "$START_LINES" +printf "Application starting: %s\n" "$APPLICATION_START_LINES" +printf "Aperture FileWriter: %s\n" "$FILE_OPEN_LINES" +printf "Placeholder residui: %s\n" "$PLACEHOLDER_COUNT" +printf "Classe qualificata FileWriter: %s\n" "$NAMESPACED_CLASS_COUNT" +printf "Classe semplice FileWriter: %s\n" "$PLAIN_CLASS_COUNT" +printf "Filename corretti: %s\n" "$FILENAME_MATCH_LINES" +printf "Dimensione file: %s byte\n" "${FILE_SIZE_BYTES:-NOT_AVAILABLE}" +printf "Segnali binari: %s\n" "${NUMBER_OF_SIGNALS:-NOT_AVAILABLE}" +printf "Nomi segnali: %s\n" "${SIGNAL_NAMES:-NOT_AVAILABLE}" +printf "Header: %s byte\n" "${HEADER_BYTES:-NOT_AVAILABLE}" +printf "Record: %s byte\n" "${SAMPLE_BYTES:-NOT_AVAILABLE}" +printf "Payload: %s byte\n" "${PAYLOAD_BYTES:-NOT_AVAILABLE}" +printf "Residuo payload: %s byte\n" "${PAYLOAD_REMAINDER_BYTES:-NOT_AVAILABLE}" +printf "Campioni completi: %s\n" "${COMPLETE_SAMPLE_COUNT:-NOT_AVAILABLE}" +printf "Counter iniziale: %s\n" "${COUNTER_FIRST:-NOT_AVAILABLE}" +printf "Counter finale: %s\n" "${COUNTER_LAST:-NOT_AVAILABLE}" +printf "Salti Counter: %s\n" "${COUNTER_GAP_COUNT:-NOT_AVAILABLE}" +printf "Time iniziale: %s\n" "${TIME_FIRST:-NOT_AVAILABLE}" +printf "Time finale: %s\n" "${TIME_LAST:-NOT_AVAILABLE}" +printf "Regressioni Time: %s\n" "${TIME_DECREASE_COUNT:-NOT_AVAILABLE}" +printf "CycleTime non nulli: %s\n" "${NONZERO_CYCLE_TIME_SAMPLES:-NOT_AVAILABLE}" +printf "CycleTime minimo: %s\n" "${CYCLE_TIME_MIN:-NOT_AVAILABLE}" +printf "CycleTime massimo: %s\n" "${CYCLE_TIME_MAX:-NOT_AVAILABLE}" +printf "CycleTime medio: %s\n" "${CYCLE_TIME_MEAN:-NOT_AVAILABLE}" +printf "Stato parser binario: %s\n" "${BINARY_STATUS:-NOT_AVAILABLE}" +printf "Exit code MARTe: %s\n" "$MARTE_EXIT_CODE" +printf "Exit code tee: %s\n" "$TEE_EXIT_CODE" +printf "SIGINT ricevuti: %s\n" "$SIGINT_LINES" +printf "Stop riusciti: %s\n" "$STOP_OK_LINES" +printf "Application terminated: %s\n" "$TERMINATED_LINES" +printf "Errori startup: %s\n" "$STARTUP_ERROR_LINES" +printf "FatalError reali: %s\n" "$FATAL_ERROR_LINES" +printf "OSError reali: %s\n" "$OS_ERROR_LINES" +printf "Error reali: %s\n" "$ERROR_LINES" +printf "InitialisationError reali: %s\n" "$INITIALISATION_ERROR_LINES" +printf "NoError: %s\n" "$NO_ERROR_LINES" +printf "Doppio arresto: %s\n" "$DOUBLE_STOP_LINES" +printf "Close EventSem asincrono: %s\n" "$ASYNC_EVENTSEM_CLOSE_LINES" +printf "pthread_mutex_destroy: %s\n" "$PTHREAD_MUTEX_DESTROY_LINES" +printf "FatalError non classificati: %s\n" "$UNCLASSIFIED_FATAL_LINES" +printf "OSError non classificati: %s\n" "$UNCLASSIFIED_OS_ERROR_LINES" + +printf "\n--- INTERVALLI TEMPORALI (osservativo) ---\n" +printf "Periodo nominale: %s us\n" "${DT_NOMINAL_PERIOD_US:-NOT_AVAILABLE}" +printf "Fonte periodo nominale: %s\n" "${DT_NOMINAL_SOURCE:-NOT_AVAILABLE}" +printf "Intervalli dt: %s\n" "${DT_INTERVAL_COUNT:-NOT_AVAILABLE}" +printf "Valori dt distinti: %s\n" "${DT_DISTINCT_VALUES:-NOT_AVAILABLE}" +printf "Istogramma dt: %s\n" "${DT_HISTOGRAM:-NOT_AVAILABLE}" +printf "dt minimo: %s\n" "${DT_MIN:-NOT_AVAILABLE}" +printf "dt massimo: %s\n" "${DT_MAX:-NOT_AVAILABLE}" +printf "Intervalli dt anomali: %s\n" "${DT_ANOMALY_COUNT:-NOT_AVAILABLE}" +printf "Indici dt anomali: %s\n" "${DT_ANOMALY_INDICES:-NOT_AVAILABLE}" +printf "Primo dt anomalo tra record: %s\n" "${DT_FIRST_ANOMALY_BETWEEN_RECORDS:-NOT_AVAILABLE}" +printf "Primo dt anomalo Counter: %s\n" "${DT_FIRST_ANOMALY_COUNTER_PAIR:-NOT_AVAILABLE}" +printf "Primo dt anomalo valore: %s\n" "${DT_FIRST_ANOMALY_VALUE:-NOT_AVAILABLE}" +printf "CycleTime nulli iniziali: %s\n" "${LEADING_ZERO_CYCLE_TIME_SAMPLES:-NOT_AVAILABLE}" + +printf "\n--- AFFINITA' DICHIARATE IN CONFIGURAZIONE ---\n" +REALTIME_THREAD_LABEL="RealTimeThread (${REALTIME_THREAD_OBJECT_NAMES:-NOT_AVAILABLE}) CPUs:" +FILE_WRITER_LABEL="FileWriter (${FILE_WRITER_OBJECT_NAMES:-NOT_AVAILABLE}) CPUMask:" +LINUX_TIMER_LABEL="LinuxTimer (${LINUX_TIMER_OBJECT_NAMES:-NOT_AVAILABLE}) CPUMask:" +printf "%-40s %s (rc=%s)\n" "Stato parser configurazione:" "${CONFIG_OBSERVABILITY_STATUS:-NOT_AVAILABLE}" "$CONFIG_RC" +printf "%-40s %s\n" "$REALTIME_THREAD_LABEL" "${REALTIME_THREAD_AFFINITY_DISPLAY:-NOT_AVAILABLE}" +printf "%-40s %s\n" "$FILE_WRITER_LABEL" "${FILE_WRITER_AFFINITY_DISPLAY:-NOT_AVAILABLE}" +printf "%-40s %s\n" "$LINUX_TIMER_LABEL" "${LINUX_TIMER_AFFINITY_DISPLAY:-NOT_AVAILABLE}" + +printf "\n--- PRIORITA' E TERMINAZIONE ---\n" +printf "Warning totali: %s\n" "$WARNING_LINES" +printf "Warning clipping priorita': %s\n" "$PRIORITY_CLIP_LINES" +printf "Warning priorita' non applic.: %s\n" "$PRIORITY_FAIL_LINES" +printf "Warning non classificati: %s\n" "$UNCLASSIFIED_WARNING_LINES" +printf "Classe exit code: %s\n" "$MARTE_EXIT_CLASS" + +FUNCTIONAL_OK=0 + +if [ "$START_LINES" -ge 1 ] && + [ "$APPLICATION_START_LINES" -ge 1 ] && + [ "$FILE_OPEN_LINES" -ge 1 ] && + [ "$STARTUP_ERROR_LINES" -eq 0 ] && + [ "$PLACEHOLDER_COUNT" -eq 0 ] && + [ "$NAMESPACED_CLASS_COUNT" -eq 1 ] && + [ "$PLAIN_CLASS_COUNT" -eq 0 ] && + [ "$FILENAME_MATCH_LINES" -eq 1 ] && + [ "$BINARY_RC" -eq 0 ]; then + FUNCTIONAL_OK=1 +fi + +if [ "$FUNCTIONAL_OK" -eq 1 ]; then + FUNCTIONAL_STATUS="PASS" +else + FUNCTIONAL_STATUS="NOT_DEMONSTRATED" +fi + +if [ "$MARTE_EXIT_CLASS" = "EXIT_137_SIGKILL" ]; then + TIMING_STATUS="NOT_VALID_EXIT_137_SIGKILL" +elif [ "$PRIORITY_FAIL_LINES" -ge 1 ]; then + TIMING_STATUS="NOT_VALID_PRIORITY_NOT_APPLIED" +else + TIMING_STATUS="UNASSESSED_DT_SEMANTICS" +fi + +printf "\nFUNCTIONAL_STATUS=%s\n" "$FUNCTIONAL_STATUS" +printf "TIMING_STATUS=%s\n" "$TIMING_STATUS" +case "$FUNCTIONAL_STATUS:$TIMING_STATUS" in + PASS:NOT_VALID_*) + printf "FUNCTIONAL_PASS_TIMING_NOT_VALID\n" + ;; +esac + +printf "\n============================================================\n" +printf "CLASSIFICAZIONE\n" +printf "============================================================\n" + +if [ "$FUNCTIONAL_OK" -ne 1 ]; then + printf "[NON DIMOSTRATO] Funzionamento S02 e registrazione binaria non sufficientemente provati.\n" + printf "[DA VERIFICARE] Dettaglio parser binario:\n" + cat -- "$BINARY_REPORT" + exit 20 +fi + +printf "[VERIFICATO] Funzionamento S02 e registrazione binaria dimostrati.\n" + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$FATAL_ERROR_LINES" -eq 0 ] && + [ "$OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "[VERIFICATO] Shutdown privo di Error, OSError e FatalError.\n" + exit 0 +fi + +KNOWN_SHUTDOWN_TOTAL=$((DOUBLE_STOP_LINES + ASYNC_EVENTSEM_CLOSE_LINES + PTHREAD_MUTEX_DESTROY_LINES)) + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$KNOWN_SHUTDOWN_TOTAL" -ge 1 ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "[DA VERIFICARE] Funzionalità riuscita con sole anomalie di shutdown già classificate.\n" + exit 10 +fi + +printf "[DA VERIFICARE] Funzionalità riuscita, ma sono presenti anomalie di shutdown non classificate.\n" +exit 11 diff --git a/scenarios/S03H1_execution_profiles/export_s03_csv.py b/scenarios/S03H1_execution_profiles/export_s03_csv.py new file mode 100755 index 0000000..ffed4ed --- /dev/null +++ b/scenarios/S03H1_execution_profiles/export_s03_csv.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Export the approved S03 binary record layout to a derived CSV artifact.""" + +import argparse +import csv +import hashlib +import os +import struct +import sys +import tempfile +from pathlib import Path + + +UINT32_TYPE_HEX = "0408" +SIGNAL_HEADER_BYTES = 38 +EXPECTED_SIGNALS = ( + ("Counter", 1), + ("Time", 1), + ("State1_Thread1_CycleTime", 1), + ("SentinelScalarA", 1), + ("SentinelScalarB", 1), + ("SentinelVector", 3), +) +EXPECTED_HEADER_BYTES = 4 + len(EXPECTED_SIGNALS) * SIGNAL_HEADER_BYTES +EXPECTED_RECORD_BYTES = 32 +CSV_COLUMNS = ( + "Counter", + "Time", + "CycleTime", + "SentinelScalarA", + "SentinelScalarB", + "SentinelVector0", + "SentinelVector1", + "SentinelVector2", +) + + +class ExportError(Exception): + """A fail-closed binary or publication error.""" + + +def decode_name(raw): + try: + return raw.split(b"\x00", 1)[0].decode("ascii") + except UnicodeDecodeError as exc: + raise ExportError("NON_ASCII_SIGNAL_NAME") from exc + + +def read_records(binary_path): + try: + data = binary_path.read_bytes() + except OSError as exc: + raise ExportError(f"BINARY_READ_FAILED:{exc}") from exc + + if len(data) < EXPECTED_HEADER_BYTES: + raise ExportError( + f"HEADER_TOO_SMALL:EXPECTED_{EXPECTED_HEADER_BYTES}:OBSERVED_{len(data)}" + ) + + signal_count = struct.unpack_from("&2 + emit_contract + printf "VALIDATION_RESULT=FAIL_REQUIRED_INPUT\n" + exit "$code" +} + +if [ "$#" -ne 2 ]; then + printf "Uso: %s TEST|DIAGNOSTIC \n" "$0" >&2 + emit_contract + exit 2 +fi + +MODE="$1" +case "$MODE" in + TEST) + EXPECTED_PROFILE_CONFIG_SHA256="$EXPECTED_TEST_CONFIG_SHA256" + ;; + DIAGNOSTIC) + EXPECTED_PROFILE_CONFIG_SHA256="$EXPECTED_DIAGNOSTIC_CONFIG_SHA256" + ;; + *) + MODE_STATUS="FAIL" + PROFILE_CONFIG_STATUS="FAIL" + OBSERVABILITY_STATUS="FAIL" + printf "UNKNOWN_MODE=%s\n" "$MODE" >&2 + emit_contract + printf "VALIDATION_RESULT=FAIL_UNKNOWN_MODE\n" + exit 2 + ;; +esac + +INPUT_PATH="$2" +if [ -d "$INPUT_PATH" ]; then + RUN_DIR="$(cd "$INPUT_PATH" && pwd)" + RUN_LOG="$RUN_DIR/run.log" +elif [ -f "$INPUT_PATH" ]; then + RUN_LOG="$(cd "$(dirname "$INPUT_PATH")" && pwd)/$(basename "$INPUT_PATH")" + RUN_DIR="$(dirname "$RUN_LOG")" +else + fail_early 21 "Percorso di validazione non trovato: $INPUT_PATH" +fi + +RUN_CONFIG="$RUN_DIR/configuration.marte" +OUTPUT_FILE="$RUN_DIR/s03_output.bin" +RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt" +RUN_HASHES="$RUN_DIR/hashes.sha256" +MODE_FILE="$RUN_DIR/s03h1_mode.txt" + +if [ ! -f "$PARSER" ]; then + fail_early 21 "Parser baseline S03 assente: $PARSER" +fi +PARSER_SHA256="$(sha256sum -- "$PARSER" | awk '{print $1}')" +if [ "$PARSER_SHA256" != "$EXPECTED_BASELINE_PARSER_SHA256" ]; then + fail_early 21 "Parser baseline S03 con hash inatteso: $PARSER_SHA256" +fi +if [ ! -x "$CONFIG_VERIFIER" ]; then + fail_early 21 "Verificatore configurazione assente o non eseguibile: $CONFIG_VERIFIER" +fi + +for required_file in \ + "$RUN_LOG" \ + "$RUN_CONFIG" \ + "$OUTPUT_FILE" \ + "$RUNNER_SUMMARY" \ + "$RUN_HASHES" \ + "$MODE_FILE" +do + if [ ! -f "$required_file" ]; then + fail_early 21 "File richiesto assente: $required_file" + fi +done + +if [ ! -s "$RUN_HASHES" ]; then + fail_early 21 "Manifest hash assente o vuoto: $RUN_HASHES" +fi + +count_lines() { + pattern="$1" + file="$2" + grep -Ec "$pattern" "$file" || true +} + +report_value() { + key="$1" + file="$2" + sed -n "s/^${key}=//p" "$file" | tail -n 1 +} + +MODE_DECLARATION_COUNT="$(count_lines "^MODE=${MODE}$" "$MODE_FILE")" +SCENARIO_DECLARATION_COUNT="$(count_lines '^SCENARIO=S03H1$' "$MODE_FILE")" +CONFIG_HASH_DECLARATION_COUNT="$( + count_lines "^CANDIDATE_CONFIG_SHA256=${EXPECTED_PROFILE_CONFIG_SHA256}$" "$MODE_FILE" +)" +INSTALLATION_GATE_DECLARATION_COUNT="$( + count_lines '^S03_H1_INSTALLATION_AUTHORIZED=NO$' "$MODE_FILE" +)" +if [ "$MODE_DECLARATION_COUNT" -eq 1 ] && + [ "$SCENARIO_DECLARATION_COUNT" -eq 1 ] && + [ "$CONFIG_HASH_DECLARATION_COUNT" -eq 1 ] && + [ "$INSTALLATION_GATE_DECLARATION_COUNT" -eq 1 ]; then + MODE_STATUS="PASS" +else + MODE_STATUS="FAIL" +fi + +START_LINES="$(count_lines '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' "$RUN_LOG")" +APPLICATION_START_LINES="$(count_lines '^\[Information - MARTeApp\.cpp:[0-9]+\]: Application starting[[:space:]]*$' "$RUN_LOG")" +FILE_OPEN_LINES="$(count_lines '^\[Information - FileWriter\.cpp:[0-9]+\]: Going to open file with name ' "$RUN_LOG")" +SIGINT_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' "$RUN_LOG")" +STOP_OK_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' "$RUN_LOG")" +TERMINATED_LINES="$(count_lines '^\[NoError - MARTeApp\.cpp:[0-9]+\]: Application terminated[[:space:]]*$' "$RUN_LOG")" + +STARTUP_ERROR_LINES="$(count_lines 'Failed dlopen|Failed CreateByName|Failed to Initialise object|Failed to initialise the ObjectRegistryDatabase|Could not Initialise the loader' "$RUN_LOG")" +FATAL_ERROR_LINES="$(count_lines '^\[FatalError ' "$RUN_LOG")" +OS_ERROR_LINES="$(count_lines '^\[OSError ' "$RUN_LOG")" +ERROR_LINES="$(count_lines '^\[Error ' "$RUN_LOG")" +INITIALISATION_ERROR_LINES="$(count_lines '^\[InitialisationEr ' "$RUN_LOG")" +WARNING_LINES="$(count_lines '^\[Warning ' "$RUN_LOG")" + +PRIORITY_CLIP_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Requested a thread priority that is higher than ' "$RUN_LOG")" +PRIORITY_FAIL_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Failed to change the thread priority' "$RUN_LOG")" +LINUX_TIMER_DEFAULT_WARNING_LINES="$(count_lines '^\[Warning - LinuxTimer\.cpp:[0-9]+\]: (ExecutionMode|CPUMask|StackSize) not specified using: ' "$RUN_LOG")" +CONFIG_DB_NODE_WARNING_LINES="$(count_lines '^\[Warning - MARTeApp\.cpp:[0-9]+\]: \[ConfigurationDatabaseNode\] - instances: [0-9]+[[:space:]]*$' "$RUN_LOG")" + +UNCLASSIFIED_WARNING_LINES=$(( + WARNING_LINES - + PRIORITY_CLIP_LINES - + PRIORITY_FAIL_LINES - + LINUX_TIMER_DEFAULT_WARNING_LINES - + CONFIG_DB_NODE_WARNING_LINES +)) +if [ "$UNCLASSIFIED_WARNING_LINES" -lt 0 ]; then + UNCLASSIFIED_WARNING_LINES=0 +fi + +DOUBLE_STOP_LINES="$(count_lines '^\[FatalError - RealTimeApplication\.cpp:[0-9]+\]: Could not stop the RealTimeApplication\. Was it ever started\?[[:space:]]*$' "$RUN_LOG")" +ASYNC_EVENTSEM_CLOSE_LINES="$(count_lines '^\[FatalError - MemoryMapAsyncOutputBroker\.cpp:[0-9]+\]: Could not Close the EventSem\.[[:space:]]*$' "$RUN_LOG")" +PTHREAD_MUTEX_DESTROY_LINES="$(count_lines '^\[OSError - EventSem\.cpp:[0-9]+\]: Error: pthread_mutex_destroy\(\)[[:space:]]*$' "$RUN_LOG")" + +UNCLASSIFIED_FATAL_LINES=$((FATAL_ERROR_LINES - DOUBLE_STOP_LINES - ASYNC_EVENTSEM_CLOSE_LINES)) +UNCLASSIFIED_OS_ERROR_LINES=$((OS_ERROR_LINES - PTHREAD_MUTEX_DESTROY_LINES)) +if [ "$UNCLASSIFIED_FATAL_LINES" -lt 0 ]; then + UNCLASSIFIED_FATAL_LINES=0 +fi +if [ "$UNCLASSIFIED_OS_ERROR_LINES" -lt 0 ]; then + UNCLASSIFIED_OS_ERROR_LINES=0 +fi + +LOGGER_SIGNAL_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: (Counter|Time|State1_Thread1_CycleTime|SentinelScalarA|SentinelScalarB|SentinelVector) \[[0-9]+:[0-9]+\]:' "$RUN_LOG")" +LOGGER_COUNTER_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Counter \[0:0\]:' "$RUN_LOG")" +LOGGER_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Time \[0:0\]:' "$RUN_LOG")" +LOGGER_CYCLE_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: State1_Thread1_CycleTime \[0:0\]:' "$RUN_LOG")" +LOGGER_SCALAR_A_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarA \[0:0\]:' "$RUN_LOG")" +LOGGER_SCALAR_B_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarB \[0:0\]:' "$RUN_LOG")" +LOGGER_VECTOR_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelVector \[0:2\]:' "$RUN_LOG")" + +case "$MODE" in + TEST) + if [ "$LOGGER_SIGNAL_LINES" -eq 0 ] && + [ "$LOGGER_COUNTER_LINES" -eq 0 ] && + [ "$LOGGER_TIME_LINES" -eq 0 ] && + [ "$LOGGER_CYCLE_TIME_LINES" -eq 0 ] && + [ "$LOGGER_SCALAR_A_LINES" -eq 0 ] && + [ "$LOGGER_SCALAR_B_LINES" -eq 0 ] && + [ "$LOGGER_VECTOR_LINES" -eq 0 ]; then + OBSERVABILITY_STATUS="PASS" + else + OBSERVABILITY_STATUS="FAIL" + fi + ;; + DIAGNOSTIC) + if [ "$LOGGER_COUNTER_LINES" -ge 1 ] && + [ "$LOGGER_TIME_LINES" -ge 1 ] && + [ "$LOGGER_CYCLE_TIME_LINES" -ge 1 ] && + [ "$LOGGER_SCALAR_A_LINES" -ge 1 ] && + [ "$LOGGER_SCALAR_B_LINES" -ge 1 ] && + [ "$LOGGER_VECTOR_LINES" -ge 1 ]; then + OBSERVABILITY_STATUS="PASS" + else + OBSERVABILITY_STATUS="FAIL" + fi + ;; +esac + +CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h1_validator_config_XXXXXX.log")" || exit 22 +BINARY_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h1_validator_binary_XXXXXX.log")" || exit 22 +HASH_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h1_validator_hashes_XXXXXX.log")" || exit 22 +trap 'rm -f -- "$CONFIG_REPORT" "$BINARY_REPORT" "$HASH_REPORT"' EXIT HUP INT TERM + +EXPECTED_FILENAME="$RUN_DIR/s03_output.bin" +PYTHONHASHSEED=0 "$CONFIG_VERIFIER" \ + "$MODE" \ + "$RUN_CONFIG" \ + "$EXPECTED_FILENAME" \ + >"$CONFIG_REPORT" 2>&1 +CONFIG_RC=$? +PROFILE_CONFIG_STATUS="$( + report_value CONFIG_PROFILE_VERIFICATION_STATUS "$CONFIG_REPORT" +)" +if [ -z "$PROFILE_CONFIG_STATUS" ]; then + PROFILE_CONFIG_STATUS="FAIL" +fi + +PYTHONHASHSEED=0 python3 "$PARSER" "$OUTPUT_FILE" --min-samples "$MINIMUM_SAMPLES" \ + >"$BINARY_REPORT" 2>&1 +PARSER_RC=$? + +sha256sum -c --strict "$RUN_HASHES" >"$HASH_REPORT" 2>&1 +HASH_RC=$? + +FUNCTIONAL_BINARY_STATUS="$(report_value FUNCTIONAL_BINARY_STATUS "$BINARY_REPORT")" +SEMANTIC_BINARY_STATUS="$(report_value SEMANTIC_BINARY_STATUS "$BINARY_REPORT")" +BINARY_STATUS="$(report_value BINARY_STATUS "$BINARY_REPORT")" +FIRST_ERROR_CODE="$(report_value FIRST_ERROR_CODE "$BINARY_REPORT")" +PROFILE_ERROR_CODE="$(report_value PROFILE_ERROR_CODE "$CONFIG_REPORT")" +COMPLETE_SAMPLE_COUNT="$(report_value COMPLETE_SAMPLE_COUNT "$BINARY_REPORT")" +HEADER_BYTES="$(report_value OBSERVED_HEADER_BYTES "$BINARY_REPORT")" +RECORD_BYTES="$(report_value OBSERVED_RECORD_BYTES_FROM_HEADER "$BINARY_REPORT")" +PAYLOAD_BYTES="$(report_value PAYLOAD_BYTES "$BINARY_REPORT")" +PAYLOAD_REMAINDER_BYTES="$(report_value PAYLOAD_REMAINDER_BYTES "$BINARY_REPORT")" + +MARTE_EXIT_CODE="$(sed -n 's/^Exit code MARTe:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +TEE_EXIT_CODE="$(sed -n 's/^Exit code tee:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +if [ -z "$MARTE_EXIT_CODE" ]; then + MARTE_EXIT_CODE="NOT_AVAILABLE" +fi +if [ -z "$TEE_EXIT_CODE" ]; then + TEE_EXIT_CODE="NOT_AVAILABLE" +fi + +case "$MARTE_EXIT_CODE" in + 137) + TIMING_STATUS="NOT_VALID_EXIT_137_SIGKILL" + ;; + *) + if [ "$PRIORITY_FAIL_LINES" -ge 1 ]; then + TIMING_STATUS="NOT_VALID_PRIORITY_NOT_APPLIED" + else + TIMING_STATUS="UNASSESSED_DT_SEMANTICS" + fi + ;; +esac + +FUNCTIONAL_OK=0 +if [ "$START_LINES" -ge 1 ] && + [ "$APPLICATION_START_LINES" -ge 1 ] && + [ "$FILE_OPEN_LINES" -ge 1 ] && + [ "$STARTUP_ERROR_LINES" -eq 0 ] && + [ "$MODE_STATUS" = "PASS" ] && + [ "$CONFIG_RC" -eq 0 ] && + [ "$PROFILE_CONFIG_STATUS" = "PASS" ] && + [ "$OBSERVABILITY_STATUS" = "PASS" ] && + [ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] && + [ "$HASH_RC" -eq 0 ] && + [ "$TEE_EXIT_CODE" = "0" ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + FUNCTIONAL_OK=1 +fi + +if [ "$FUNCTIONAL_OK" -eq 1 ]; then + FUNCTIONAL_STATUS="PASS" +fi + +if [ "$MODE_STATUS" = "PASS" ] && + [ "$PROFILE_CONFIG_STATUS" = "PASS" ] && + [ "$SEMANTIC_BINARY_STATUS" = "PASS" ]; then + SEMANTIC_STATUS="PASS" +elif [ "$SEMANTIC_BINARY_STATUS" = "FAIL" ]; then + SEMANTIC_STATUS="FAIL" +else + SEMANTIC_STATUS="NOT_DEMONSTRATED" +fi + +printf "============================================================\n" +printf "VALIDAZIONE S03-H1 - EXECUTION PROFILES\n" +printf "============================================================\n" +printf "Mode: %s\n" "$MODE" +printf "Run directory: %s\n" "$RUN_DIR" +printf "Configurazione: %s\n" "$RUN_CONFIG" +printf "File binario: %s\n" "$OUTPUT_FILE" +printf "Manifest hash: %s\n" "$RUN_HASHES" +printf "Parser baseline SHA-256: %s\n" "$PARSER_SHA256" +printf "Mode file: %s\n" "$MODE_FILE" +printf "Mode declaration count: %s\n" "$MODE_DECLARATION_COUNT" +printf "Scenario declaration count: %s\n" "$SCENARIO_DECLARATION_COUNT" +printf "Config hash declaration count: %s\n" "$CONFIG_HASH_DECLARATION_COUNT" +printf "Installation gate count: %s\n" "$INSTALLATION_GATE_DECLARATION_COUNT" +printf "Configurazione profilo: %s (rc=%s)\n" "$PROFILE_CONFIG_STATUS" "$CONFIG_RC" +printf "Errore profilo: %s\n" "${PROFILE_ERROR_CODE:-NOT_AVAILABLE}" +printf "Osservabilita' profilo: %s\n" "$OBSERVABILITY_STATUS" +printf "Logger signal lines: %s\n" "$LOGGER_SIGNAL_LINES" +printf "Logger Counter lines: %s\n" "$LOGGER_COUNTER_LINES" +printf "Logger Time lines: %s\n" "$LOGGER_TIME_LINES" +printf "Logger CycleTime lines: %s\n" "$LOGGER_CYCLE_TIME_LINES" +printf "Logger ScalarA lines: %s\n" "$LOGGER_SCALAR_A_LINES" +printf "Logger ScalarB lines: %s\n" "$LOGGER_SCALAR_B_LINES" +printf "Logger Vector[3] lines: %s\n" "$LOGGER_VECTOR_LINES" +printf "Verifica manifest hash: %s (rc=%s)\n" "$( [ "$HASH_RC" -eq 0 ] && printf PASS || printf FAIL )" "$HASH_RC" +printf "Parser binario: %s (rc=%s)\n" "${BINARY_STATUS:-NOT_AVAILABLE}" "$PARSER_RC" +printf "Primo errore parser: %s\n" "${FIRST_ERROR_CODE:-NOT_AVAILABLE}" +printf "Header osservato: %s byte\n" "${HEADER_BYTES:-NOT_AVAILABLE}" +printf "Record da header: %s byte\n" "${RECORD_BYTES:-NOT_AVAILABLE}" +printf "Payload: %s byte\n" "${PAYLOAD_BYTES:-NOT_AVAILABLE}" +printf "Residuo payload: %s byte\n" "${PAYLOAD_REMAINDER_BYTES:-NOT_AVAILABLE}" +printf "Campioni completi: %s\n" "${COMPLETE_SAMPLE_COUNT:-NOT_AVAILABLE}" +printf "Avvii State1: %s\n" "$START_LINES" +printf "Application starting: %s\n" "$APPLICATION_START_LINES" +printf "Aperture FileWriter: %s\n" "$FILE_OPEN_LINES" +printf "Exit code MARTe: %s\n" "$MARTE_EXIT_CODE" +printf "Exit code tee: %s\n" "$TEE_EXIT_CODE" +printf "Warning priorita' non applic.: %s\n" "$PRIORITY_FAIL_LINES" +printf "Warning non classificati: %s\n" "$UNCLASSIFIED_WARNING_LINES" +printf "FatalError non classificati: %s\n" "$UNCLASSIFIED_FATAL_LINES" +printf "OSError non classificati: %s\n" "$UNCLASSIFIED_OS_ERROR_LINES" +printf "Error non classificati: %s\n" "$ERROR_LINES" +printf "InitialisationError: %s\n" "$INITIALISATION_ERROR_LINES" +printf "UNCLASSIFIED_WARNING_LINES=%s\n" "$UNCLASSIFIED_WARNING_LINES" +printf "LOGGER_SIGNAL_LINES=%s\n" "$LOGGER_SIGNAL_LINES" +printf "LOGGER_COUNTER_LINES=%s\n" "$LOGGER_COUNTER_LINES" +printf "LOGGER_TIME_LINES=%s\n" "$LOGGER_TIME_LINES" +printf "LOGGER_CYCLE_TIME_LINES=%s\n" "$LOGGER_CYCLE_TIME_LINES" +printf "LOGGER_SCALAR_A_LINES=%s\n" "$LOGGER_SCALAR_A_LINES" +printf "LOGGER_SCALAR_B_LINES=%s\n" "$LOGGER_SCALAR_B_LINES" +printf "LOGGER_VECTOR_LINES=%s\n" "$LOGGER_VECTOR_LINES" + +printf "\n--- DETTAGLIO CONFIGURAZIONE PROFILE-AWARE ---\n" +cat -- "$CONFIG_REPORT" +printf "\n--- DETTAGLIO PARSER BINARIO BASELINE ---\n" +cat -- "$BINARY_REPORT" +printf "\n--- VERIFICA HASH ---\n" +cat -- "$HASH_REPORT" + +printf "\n--- CONTRATTO S03-H1 ---\n" +emit_contract + +if [ "$FUNCTIONAL_STATUS" != "PASS" ]; then + printf "VALIDATION_RESULT=FUNCTIONAL_NOT_DEMONSTRATED\n" + exit 20 +fi + +if [ "$SEMANTIC_STATUS" != "PASS" ]; then + printf "VALIDATION_RESULT=SEMANTIC_VALIDATION_FAILED\n" + exit 20 +fi + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$FATAL_ERROR_LINES" -eq 0 ] && + [ "$OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "VALIDATION_RESULT=PASS_CLEAN_SHUTDOWN\n" + exit 0 +fi + +KNOWN_SHUTDOWN_TOTAL=$(( + DOUBLE_STOP_LINES + + ASYNC_EVENTSEM_CLOSE_LINES + + PTHREAD_MUTEX_DESTROY_LINES +)) +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$KNOWN_SHUTDOWN_TOTAL" -ge 1 ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "VALIDATION_RESULT=PASS_KNOWN_SHUTDOWN_ANOMALY\n" + exit 10 +fi + +printf "VALIDATION_RESULT=PASS_UNCLASSIFIED_SHUTDOWN_ANOMALY\n" +exit 11 diff --git a/scenarios/S03H1_execution_profiles/verify_s03h1_config.py b/scenarios/S03H1_execution_profiles/verify_s03h1_config.py new file mode 100755 index 0000000..5d08b4b --- /dev/null +++ b/scenarios/S03H1_execution_profiles/verify_s03h1_config.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +"""Fail-closed, profile-aware verifier for S03-H1 runtime configurations.""" + +import argparse +import hashlib +import sys +from pathlib import Path + + +EXPECTED_PROFILE_HASHES = { + "TEST": "d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e", + "DIAGNOSTIC": "cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3", +} +EXPECTED_DISPLAY_CLASSES = { + "TEST": "GAMDataSource", + "DIAGNOSTIC": "LoggerDataSource", +} +EXPECTED_GAMDISPLAY_BLOCK_SHA256 = ( + "13b14b504dac43df5febf230cd069c5093d951726382b1d5dee8edf43d6814d0" +) +EXPECTED_IOGAM_WRITER_BLOCK_SHA256 = ( + "b3d54ad2fae08e6640bfa0be39ead9841906b4573a3b407e5ecf21340b5c5717" +) +EXPECTED_FILEWRITER_BLOCK_SHA256 = ( + "0bb949b9d72fa758bdfd8b2039336bc1fda071ecf038304402032de63f4fc455" +) +PLACEHOLDER_LINE = ' Filename = "__MARTE_RUN_DIR__/s03_output.bin"' + + +def digest(text): + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +def extract_block(text, start_marker, end_marker): + if text.count(start_marker) != 1: + raise ValueError(f"START_MARKER_COUNT:{start_marker!r}:{text.count(start_marker)}") + start = text.index(start_marker) + try: + end = text.index(end_marker, start) + except ValueError as exc: + raise ValueError(f"END_MARKER_MISSING:{end_marker!r}") from exc + return text[start:end] + + +class Verification: + def __init__(self): + self.checks = {} + self.first_error = "NONE" + + def check(self, name, condition, error_code): + self.checks[name] = "PASS" if condition else "FAIL" + if not condition and self.first_error == "NONE": + self.first_error = error_code + + def emit(self): + for name in sorted(self.checks): + print(f"{name}={self.checks[name]}") + print(f"PROFILE_ERROR_CODE={self.first_error}") + + +def verify(mode, config_path, expected_filename): + report = Verification() + try: + text = config_path.read_text(encoding="utf-8") + except OSError as exc: + print("CONFIG_READ_STATUS=FAIL") + print(f"CONFIG_READ_ERROR={exc}") + print("PROFILE_ERROR_CODE=CONFIG_READ_FAILED") + print("CONFIG_PROFILE_VERIFICATION_STATUS=FAIL") + return 1 + + expected_line = f' Filename = "{expected_filename}"' + filename_count = sum(1 for line in text.splitlines() if line == expected_line) + placeholder_count = text.count("__MARTE_RUN_DIR__") + + canonical = text + if filename_count == 1: + canonical = text.replace(expected_line, PLACEHOLDER_LINE, 1) + + canonical_hash = digest(canonical) + expected_profile_hash = EXPECTED_PROFILE_HASHES[mode] + expected_display_class = EXPECTED_DISPLAY_CLASSES[mode] + + print(f"VERIFIED_MODE={mode}") + print(f"EXPECTED_FILENAME={expected_filename}") + print(f"EXPECTED_FILENAME_COUNT={filename_count}") + print(f"PLACEHOLDER_COUNT={placeholder_count}") + print(f"CANONICAL_CONFIG_SHA256={canonical_hash}") + print(f"EXPECTED_PROFILE_CONFIG_SHA256={expected_profile_hash}") + + report.check( + "RUNTIME_FILENAME_STATUS", + filename_count == 1 and placeholder_count == 0, + "RUNTIME_FILENAME_MISMATCH", + ) + report.check( + "GAMDISPLAY_OBJECT_STATUS", + text.count("+GAMDisplay =") == 1, + "GAMDISPLAY_OBJECT_COUNT_MISMATCH", + ) + report.check( + "THREAD_ORDER_STATUS", + text.count( + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" + ) + == 1, + "THREAD_ORDER_MISMATCH", + ) + + try: + gamdisplay = extract_block( + canonical, + " +GAMDisplay = {\n", + " +IOGAM_Writer = {\n", + ) + except ValueError as exc: + print(f"GAMDISPLAY_EXTRACTION_ERROR={exc}") + gamdisplay = "" + + display_checks = { + "GAMDISPLAY_CLASS_COUNT": gamdisplay.count("Class = IOGAM") == 1, + "GAMDISPLAY_COUNTER_COUNT": gamdisplay.count( + " Counter = {\n" + ) + == 2, + "GAMDISPLAY_TIME_COUNT": gamdisplay.count(" Time = {\n") == 2, + "GAMDISPLAY_CYCLETIME_COUNT": gamdisplay.count( + "State1_Thread1_CycleTime =" + ) + == 2, + "GAMDISPLAY_SCALAR_A_COUNT": gamdisplay.count("SentinelScalarA =") == 2, + "GAMDISPLAY_SCALAR_B_COUNT": gamdisplay.count("SentinelScalarB =") == 2, + "GAMDISPLAY_VECTOR_COUNT": gamdisplay.count("SentinelVector =") == 2, + "GAMDISPLAY_VECTOR_DIMENSION_COUNT": gamdisplay.count( + "NumberOfDimensions = 1" + ) + == 2, + "GAMDISPLAY_VECTOR_ELEMENTS_COUNT": gamdisplay.count( + "NumberOfElements = 3" + ) + == 2, + "GAMDISPLAY_OUTPUT_DATASOURCE_COUNT": gamdisplay.count( + "DataSource = Display" + ) + == 6, + } + for name, passed in display_checks.items(): + report.check(name, passed, f"{name}_MISMATCH") + + gamdisplay_hash = digest(gamdisplay) + print(f"GAMDISPLAY_BLOCK_SHA256={gamdisplay_hash}") + print( + "EXPECTED_GAMDISPLAY_BLOCK_SHA256=" + f"{EXPECTED_GAMDISPLAY_BLOCK_SHA256}" + ) + report.check( + "GAMDISPLAY_BLOCK_STATUS", + gamdisplay_hash == EXPECTED_GAMDISPLAY_BLOCK_SHA256, + "GAMDISPLAY_BLOCK_HASH_MISMATCH", + ) + + try: + writer = extract_block( + canonical, + " +IOGAM_Writer = {\n", + " }\n +Data = {\n", + ) + except ValueError as exc: + print(f"IOGAM_WRITER_EXTRACTION_ERROR={exc}") + writer = "" + writer_hash = digest(writer) + print(f"IOGAM_WRITER_BLOCK_SHA256={writer_hash}") + print( + "EXPECTED_IOGAM_WRITER_BLOCK_SHA256=" + f"{EXPECTED_IOGAM_WRITER_BLOCK_SHA256}" + ) + report.check( + "IOGAM_WRITER_INVARIANT_STATUS", + writer_hash == EXPECTED_IOGAM_WRITER_BLOCK_SHA256, + "IOGAM_WRITER_BLOCK_HASH_MISMATCH", + ) + + try: + filewriter = extract_block( + canonical, + " +FileWriter = {\n", + " }\n +States = {\n", + ) + except ValueError as exc: + print(f"FILEWRITER_EXTRACTION_ERROR={exc}") + filewriter = "" + filewriter_hash = digest(filewriter) + print(f"FILEWRITER_BLOCK_SHA256={filewriter_hash}") + print( + "EXPECTED_FILEWRITER_BLOCK_SHA256=" + f"{EXPECTED_FILEWRITER_BLOCK_SHA256}" + ) + report.check( + "FILEWRITER_INVARIANT_STATUS", + filewriter_hash == EXPECTED_FILEWRITER_BLOCK_SHA256, + "FILEWRITER_BLOCK_HASH_MISMATCH", + ) + + structural_expectations = { + "CONSTANT_GAM_COUNT": (text.count("Class = ConstantGAM"), 1), + "SENTINEL_SCALAR_A_DECLARATION_COUNT": ( + text.count("SentinelScalarA ="), + 6, + ), + "SENTINEL_SCALAR_B_DECLARATION_COUNT": ( + text.count("SentinelScalarB ="), + 6, + ), + "SENTINEL_VECTOR_DECLARATION_COUNT": ( + text.count("SentinelVector ="), + 6, + ), + "VECTOR_DIMENSION_COUNT": (text.count("NumberOfDimensions = 1"), 6), + "VECTOR_CARDINALITY_COUNT": (text.count("NumberOfElements = 3"), 6), + "FILEWRITER_CLASS_COUNT": ( + text.count("Class = FileDataSource::FileWriter"), + 1, + ), + "PLAIN_FILEWRITER_CLASS_COUNT": (text.count("Class = FileWriter"), 0), + } + for name, (observed, expected) in structural_expectations.items(): + print(f"{name}={observed}") + print(f"EXPECTED_{name}={expected}") + report.check( + f"{name}_STATUS", + observed == expected, + f"{name}_MISMATCH", + ) + + expected_display_block = ( + " +Display = {\n" + f" Class = {expected_display_class}\n" + " }\n" + ) + report.check( + "DISPLAY_DATASOURCE_CLASS_STATUS", + text.count(expected_display_block) == 1, + "DISPLAY_DATASOURCE_CLASS_MISMATCH", + ) + report.check( + "PROFILE_CANONICAL_HASH_STATUS", + canonical_hash == expected_profile_hash, + "PROFILE_CANONICAL_HASH_MISMATCH", + ) + + report.emit() + passed = all(value == "PASS" for value in report.checks.values()) + print(f"CONFIG_PROFILE_VERIFICATION_STATUS={'PASS' if passed else 'FAIL'}") + return 0 if passed else 1 + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("mode", choices=sorted(EXPECTED_PROFILE_HASHES)) + parser.add_argument("configuration", type=Path) + parser.add_argument("expected_filename") + args = parser.parse_args() + return verify(args.mode, args.configuration, args.expected_filename) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scenarios/S03H3_execution_profiles/validate.sh b/scenarios/S03H3_execution_profiles/validate.sh new file mode 100755 index 0000000..ba5bd08 --- /dev/null +++ b/scenarios/S03H3_execution_profiles/validate.sh @@ -0,0 +1,508 @@ +#!/usr/bin/env bash +set -u +set -o pipefail + +readonly MINIMUM_SAMPLES="50" +readonly EXPECTED_PARSER_SHA256="91c4d66770197e1986ed91eb6a4870e7da7df5a522ffdf4329b6d9250f238307" +readonly EXPECTED_H1_VERIFIER_SHA256="2f1c41b186bd2e79a9fb51fab0461a7a0dce104bf0852c0ec1ade2c55a017443" +readonly EXPECTED_TEST_CONFIG_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" +readonly EXPECTED_DIAGNOSTIC_CONFIG_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3" + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +SUITE="$(cd -- "$SCRIPT_DIR/../.." && pwd -P)" +PARSER="$SUITE/scenarios/S03_semantic_sentinels/parse_s03_binary.py" +H1_VERIFIER="$SUITE/scenarios/S03H1_execution_profiles/verify_s03h1_config.py" +MINIMAL_VERIFIER="$SCRIPT_DIR/verify_s03h3_minimal_structure.py" +TEST_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte" +DIAGNOSTIC_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte" +MINIMAL_CONFIG="$SUITE/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte" + +MODE="NOT_PROVIDED" +MODE_STATUS="NOT_DEMONSTRATED" +PROFILE_CONFIG_STATUS="NOT_DEMONSTRATED" +PROFILE_RUNTIME_STATUS="NOT_DEMONSTRATED" +OBSERVABILITY_STATUS="NOT_DEMONSTRATED" +OBSERVABILITY_REASON="NOT_EVALUATED" +FUNCTIONAL_STATUS="NOT_DEMONSTRATED" +SEMANTIC_STATUS="NOT_DEMONSTRATED" +TIMING_STATUS="NOT_EVALUATED" +MEASUREMENT_ELIGIBILITY="NO" +COMPLETE_SAMPLE_COUNT="NOT_DEMONSTRATED" + +CONFIG_REPORT="" +RUNTIME_CONFIG_REPORT="" +BINARY_REPORT="" +HASH_REPORT="" + +cleanup() { + rm -f -- \ + "${CONFIG_REPORT:-}" \ + "${RUNTIME_CONFIG_REPORT:-}" \ + "${BINARY_REPORT:-}" \ + "${HASH_REPORT:-}" 2>/dev/null || true +} +trap cleanup EXIT HUP INT TERM + +emit_contract() { + printf 'MODE=%s\n' "$MODE" + printf 'MODE_STATUS=%s\n' "$MODE_STATUS" + printf 'PROFILE_CONFIG_STATUS=%s\n' "$PROFILE_CONFIG_STATUS" + printf 'PROFILE_RUNTIME_STATUS=%s\n' "$PROFILE_RUNTIME_STATUS" + printf 'OBSERVABILITY_STATUS=%s\n' "$OBSERVABILITY_STATUS" + printf 'OBSERVABILITY_REASON=%s\n' "$OBSERVABILITY_REASON" + printf 'FUNCTIONAL_STATUS=%s\n' "$FUNCTIONAL_STATUS" + printf 'SEMANTIC_STATUS=%s\n' "$SEMANTIC_STATUS" + printf 'TIMING_STATUS=%s\n' "$TIMING_STATUS" + printf 'MEASUREMENT_ELIGIBILITY=%s\n' "$MEASUREMENT_ELIGIBILITY" + printf 'COMPLETE_SAMPLE_COUNT=%s\n' "$COMPLETE_SAMPLE_COUNT" +} + +fail_early() { + local code="$1" + local reason="$2" + printf '[NON DIMOSTRATO] %s\n' "$reason" >&2 + OBSERVABILITY_REASON="$reason" + emit_contract + printf 'VALIDATION_RESULT=FAIL_REQUIRED_INPUT\n' + exit "$code" +} + +count_lines() { + local pattern="$1" + local file="$2" + grep -Ec "$pattern" "$file" || true +} + +report_value_exact() { + local key="$1" + local file="$2" + local count + count="$(grep -Ec "^${key}=" "$file" || true)" + if [ "$count" -ne 1 ]; then + return 1 + fi + sed -n "s/^${key}=//p" "$file" +} + +verify_identity() { + local file="$1" + local expected="$2" + [ -f "$file" ] || return 1 + [ "$(sha256sum -- "$file" | awk '{print $1}')" = "$expected" ] +} + +if [ "$#" -ne 2 ]; then + printf 'Uso: %s MINIMAL|TEST|DIAGNOSTIC \n' "$0" >&2 + emit_contract + printf 'VALIDATION_RESULT=FAIL_INVALID_USAGE\n' + exit 2 +fi + +MODE="$1" +case "$MODE" in + MINIMAL) + SELECTED_CONFIG="$MINIMAL_CONFIG" + ;; + TEST) + SELECTED_CONFIG="$TEST_CONFIG" + EXPECTED_SELECTED_CONFIG_SHA256="$EXPECTED_TEST_CONFIG_SHA256" + ;; + DIAGNOSTIC) + SELECTED_CONFIG="$DIAGNOSTIC_CONFIG" + EXPECTED_SELECTED_CONFIG_SHA256="$EXPECTED_DIAGNOSTIC_CONFIG_SHA256" + ;; + *) + MODE_STATUS="FAIL" + PROFILE_CONFIG_STATUS="FAIL" + PROFILE_RUNTIME_STATUS="FAIL" + OBSERVABILITY_STATUS="FAIL" + OBSERVABILITY_REASON="UNKNOWN_MODE" + emit_contract + printf 'VALIDATION_RESULT=FAIL_UNKNOWN_MODE\n' + exit 2 + ;; +esac + +INPUT_PATH="$2" +if [ -d "$INPUT_PATH" ]; then + RUN_DIR="$(cd -- "$INPUT_PATH" && pwd -P)" + RUN_LOG="$RUN_DIR/run.log" +elif [ -f "$INPUT_PATH" ]; then + RUN_LOG="$(cd -- "$(dirname -- "$INPUT_PATH")" && pwd -P)/$(basename -- "$INPUT_PATH")" + RUN_DIR="$(dirname -- "$RUN_LOG")" +else + fail_early 21 "INPUT_PATH_NOT_FOUND" +fi + +RUN_CONFIG="$RUN_DIR/configuration.marte" +OUTPUT_FILE="$RUN_DIR/s03_output.bin" +RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt" +RUN_HASHES="$RUN_DIR/hashes.sha256" +MODE_FILE="$RUN_DIR/s03h3_mode.txt" + +verify_identity "$PARSER" "$EXPECTED_PARSER_SHA256" || \ + fail_early 21 "PARSER_IDENTITY_NOT_DEMONSTRATED" +verify_identity "$H1_VERIFIER" "$EXPECTED_H1_VERIFIER_SHA256" || \ + fail_early 21 "H1_VERIFIER_IDENTITY_NOT_DEMONSTRATED" +[ -x "$MINIMAL_VERIFIER" ] || fail_early 21 "MINIMAL_VERIFIER_NOT_EXECUTABLE" + +for required_file in \ + "$RUN_LOG" \ + "$RUN_CONFIG" \ + "$OUTPUT_FILE" \ + "$RUNNER_SUMMARY" \ + "$RUN_HASHES" \ + "$MODE_FILE" \ + "$SELECTED_CONFIG" +do + [ -f "$required_file" ] || fail_early 21 "REQUIRED_FILE_MISSING" +done +[ -s "$RUN_LOG" ] || fail_early 21 "RUN_LOG_EMPTY" +[ -s "$RUN_HASHES" ] || fail_early 21 "RUN_HASH_MANIFEST_EMPTY" + +SELECTED_CONFIG_SHA256="$(sha256sum -- "$SELECTED_CONFIG" | awk '{print $1}')" +if [ "$MODE" = "MINIMAL" ]; then + EXPECTED_SELECTED_CONFIG_SHA256="$SELECTED_CONFIG_SHA256" +fi + +CANONICAL_CONFIG_IDENTITY_STATUS="PASS" +if [ "$SELECTED_CONFIG_SHA256" != "$EXPECTED_SELECTED_CONFIG_SHA256" ]; then + CANONICAL_CONFIG_IDENTITY_STATUS="FAIL" +fi + +MODE_FILE_CONTENT_STATUS="PASS" +MODE_DECLARATION_COUNT=0 +SCENARIO_DECLARATION_COUNT=0 +CONFIG_HASH_DECLARATION_COUNT=0 +while IFS= read -r metadata_line || [ -n "$metadata_line" ]; do + case "$metadata_line" in + '') + ;; + SCENARIO=S03H3) + SCENARIO_DECLARATION_COUNT=$((SCENARIO_DECLARATION_COUNT + 1)) + ;; + MODE=MINIMAL|MODE=TEST|MODE=DIAGNOSTIC) + MODE_DECLARATION_COUNT=$((MODE_DECLARATION_COUNT + 1)) + [ "${metadata_line#MODE=}" = "$MODE" ] || MODE_FILE_CONTENT_STATUS="FAIL" + ;; + SELECTED_CONFIG_SHA256=*) + CONFIG_HASH_DECLARATION_COUNT=$((CONFIG_HASH_DECLARATION_COUNT + 1)) + declared_config_sha256="${metadata_line#SELECTED_CONFIG_SHA256=}" + if [[ ! "$declared_config_sha256" =~ ^[0-9a-f]{64}$ ]] || + [ "$declared_config_sha256" != "$EXPECTED_SELECTED_CONFIG_SHA256" ] || + [ "$declared_config_sha256" != "$SELECTED_CONFIG_SHA256" ]; then + MODE_FILE_CONTENT_STATUS="FAIL" + fi + ;; + *) + MODE_FILE_CONTENT_STATUS="FAIL" + ;; + esac +done <"$MODE_FILE" + +if [ "$MODE_FILE_CONTENT_STATUS" = "PASS" ] && + [ "$CANONICAL_CONFIG_IDENTITY_STATUS" = "PASS" ] && + [ "$MODE_DECLARATION_COUNT" -eq 1 ] && + [ "$SCENARIO_DECLARATION_COUNT" -eq 1 ] && + [ "$CONFIG_HASH_DECLARATION_COUNT" -eq 1 ]; then + MODE_STATUS="PASS" +else + MODE_STATUS="FAIL" +fi + +CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_config.XXXXXX")" || \ + fail_early 21 "CONFIG_REPORT_CREATION_FAILED" +RUNTIME_CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_runtime.XXXXXX")" || \ + fail_early 21 "RUNTIME_REPORT_CREATION_FAILED" +BINARY_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_binary.XXXXXX")" || \ + fail_early 21 "BINARY_REPORT_CREATION_FAILED" +HASH_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_hashes.XXXXXX")" || \ + fail_early 21 "HASH_REPORT_CREATION_FAILED" + +EXPECTED_FILENAME="$RUN_DIR/s03_output.bin" +CONFIG_RC=1 +RUNTIME_CONFIG_RC=1 +CANONICAL_STRUCTURAL_COMPLETENESS_STATUS="NOT_APPLICABLE" +MINIMAL_RUNTIME_CONFIG_STATUS="NOT_APPLICABLE" +if [ "$MODE" = "MINIMAL" ]; then + PYTHONHASHSEED=0 python3 "$MINIMAL_VERIFIER" \ + CANONICAL "$TEST_CONFIG" "$MINIMAL_CONFIG" \ + >"$CONFIG_REPORT" 2>&1 + CONFIG_RC=$? + PYTHONHASHSEED=0 python3 "$MINIMAL_VERIFIER" \ + RUNTIME "$MINIMAL_CONFIG" "$RUN_CONFIG" "$RUN_DIR" \ + >"$RUNTIME_CONFIG_REPORT" 2>&1 + RUNTIME_CONFIG_RC=$? + CANONICAL_STRUCTURAL_COMPLETENESS_STATUS="$( + report_value_exact CANONICAL_STRUCTURAL_COMPLETENESS_STATUS "$CONFIG_REPORT" || printf FAIL + )" + MINIMAL_RUNTIME_CONFIG_STATUS="$( + report_value_exact MINIMAL_RUNTIME_CONFIG_STATUS "$RUNTIME_CONFIG_REPORT" || printf FAIL + )" + if [ "$CONFIG_RC" -eq 0 ] && + [ "$RUNTIME_CONFIG_RC" -eq 0 ] && + [ "$CANONICAL_STRUCTURAL_COMPLETENESS_STATUS" = "PASS" ] && + [ "$MINIMAL_RUNTIME_CONFIG_STATUS" = "PASS" ]; then + PROFILE_CONFIG_STATUS="PASS" + else + PROFILE_CONFIG_STATUS="FAIL" + fi +else + PYTHONHASHSEED=0 python3 "$H1_VERIFIER" \ + "$MODE" "$RUN_CONFIG" "$EXPECTED_FILENAME" \ + >"$CONFIG_REPORT" 2>&1 + CONFIG_RC=$? + RUNTIME_CONFIG_RC=0 + PROFILE_CONFIG_STATUS="$( + report_value_exact CONFIG_PROFILE_VERIFICATION_STATUS "$CONFIG_REPORT" || printf FAIL + )" +fi + +GAMTIMER_RUNTIME_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function GAMTimer \[idx: 0\][[:space:]]*$' "$RUN_LOG")" +SENTINEL_RUNTIME_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function SentinelProducer \[idx: 1\][[:space:]]*$' "$RUN_LOG")" +GAMDISPLAY_IDX2_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function GAMDisplay \[idx: 2\][[:space:]]*$' "$RUN_LOG")" +IOGAM_IDX2_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function IOGAM_Writer \[idx: 2\][[:space:]]*$' "$RUN_LOG")" +IOGAM_IDX3_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function IOGAM_Writer \[idx: 3\][[:space:]]*$' "$RUN_LOG")" +FUNCTION_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function .* \[idx: [0-9]+\][[:space:]]*$' "$RUN_LOG")" +GAMTIMER_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving GAMTimer[[:space:]]*$' "$RUN_LOG")" +SENTINEL_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving SentinelProducer[[:space:]]*$' "$RUN_LOG")" +GAMDISPLAY_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving GAMDisplay[[:space:]]*$' "$RUN_LOG")" +IOGAM_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving IOGAM_Writer[[:space:]]*$' "$RUN_LOG")" + +case "$MODE" in + MINIMAL) + if [ "$GAMTIMER_RUNTIME_LINES" -eq 1 ] && + [ "$SENTINEL_RUNTIME_LINES" -eq 1 ] && + [ "$GAMDISPLAY_IDX2_LINES" -eq 0 ] && + [ "$IOGAM_IDX2_LINES" -eq 1 ] && + [ "$IOGAM_IDX3_LINES" -eq 0 ] && + [ "$GAMTIMER_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$SENTINEL_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$GAMDISPLAY_OBJECT_RESOLUTION_LINES" -eq 0 ] && + [ "$IOGAM_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$FUNCTION_RESOLUTION_LINES" -eq 3 ]; then + PROFILE_RUNTIME_STATUS="PASS" + else + PROFILE_RUNTIME_STATUS="FAIL" + fi + ;; + TEST|DIAGNOSTIC) + if [ "$GAMTIMER_RUNTIME_LINES" -eq 1 ] && + [ "$SENTINEL_RUNTIME_LINES" -eq 1 ] && + [ "$GAMDISPLAY_IDX2_LINES" -eq 1 ] && + [ "$IOGAM_IDX2_LINES" -eq 0 ] && + [ "$IOGAM_IDX3_LINES" -eq 1 ] && + [ "$GAMTIMER_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$SENTINEL_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$GAMDISPLAY_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$IOGAM_OBJECT_RESOLUTION_LINES" -eq 1 ] && + [ "$FUNCTION_RESOLUTION_LINES" -eq 4 ]; then + PROFILE_RUNTIME_STATUS="PASS" + else + PROFILE_RUNTIME_STATUS="FAIL" + fi + ;; +esac + +PYTHONHASHSEED=0 python3 "$PARSER" "$OUTPUT_FILE" --min-samples "$MINIMUM_SAMPLES" \ + >"$BINARY_REPORT" 2>&1 +PARSER_RC=$? +sha256sum -c --strict "$RUN_HASHES" >"$HASH_REPORT" 2>&1 +HASH_RC=$? + +FUNCTIONAL_BINARY_STATUS="$(report_value_exact FUNCTIONAL_BINARY_STATUS "$BINARY_REPORT" || printf FAIL)" +SEMANTIC_BINARY_STATUS="$(report_value_exact SEMANTIC_BINARY_STATUS "$BINARY_REPORT" || printf FAIL)" +BINARY_STATUS="$(report_value_exact BINARY_STATUS "$BINARY_REPORT" || printf FAIL)" +PARSER_PAYLOAD_ALIGNMENT_STATUS="$(report_value_exact PAYLOAD_ALIGNMENT_STATUS "$BINARY_REPORT" || printf FAIL)" +COMPLETE_SAMPLE_COUNT="$(report_value_exact COMPLETE_SAMPLE_COUNT "$BINARY_REPORT" || printf NOT_DEMONSTRATED)" + +COMPLETE_SAMPLE_COUNT_STATUS="FAIL" +case "$COMPLETE_SAMPLE_COUNT" in + ''|*[!0-9]*) ;; + *) + if [ "$PARSER_RC" -eq 0 ] && + [ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] && + [ "$SEMANTIC_BINARY_STATUS" = "PASS" ] && + [ "$BINARY_STATUS" = "PASS" ] && + [ "$PARSER_PAYLOAD_ALIGNMENT_STATUS" = "PASS" ]; then + COMPLETE_SAMPLE_COUNT_STATUS="PASS" + fi + ;; +esac + +LOGGER_SIGNAL_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: (Counter|Time|State1_Thread1_CycleTime|SentinelScalarA|SentinelScalarB|SentinelVector) \[[0-9]+:[0-9]+\]:' "$RUN_LOG")" +LOGGER_COUNTER_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Counter \[0:0\]:' "$RUN_LOG")" +LOGGER_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Time \[0:0\]:' "$RUN_LOG")" +LOGGER_CYCLE_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: State1_Thread1_CycleTime \[0:0\]:' "$RUN_LOG")" +LOGGER_SCALAR_A_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarA \[0:0\]:' "$RUN_LOG")" +LOGGER_SCALAR_B_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarB \[0:0\]:' "$RUN_LOG")" +LOGGER_VECTOR_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelVector \[0:2\]:' "$RUN_LOG")" + +if [ "$COMPLETE_SAMPLE_COUNT_STATUS" != "PASS" ]; then + OBSERVABILITY_STATUS="NOT_DEMONSTRATED" + OBSERVABILITY_REASON="COMPLETE_SAMPLE_COUNT_NOT_AUTHORITATIVE" +elif [ "$MODE" = "MINIMAL" ] || [ "$MODE" = "TEST" ]; then + if [ "$LOGGER_SIGNAL_LINES" -eq 0 ] && + [ "$LOGGER_COUNTER_LINES" -eq 0 ] && + [ "$LOGGER_TIME_LINES" -eq 0 ] && + [ "$LOGGER_CYCLE_TIME_LINES" -eq 0 ] && + [ "$LOGGER_SCALAR_A_LINES" -eq 0 ] && + [ "$LOGGER_SCALAR_B_LINES" -eq 0 ] && + [ "$LOGGER_VECTOR_LINES" -eq 0 ]; then + OBSERVABILITY_STATUS="PASS" + OBSERVABILITY_REASON="NONE" + else + OBSERVABILITY_STATUS="FAIL" + OBSERVABILITY_REASON="UNEXPECTED_LOGGER_SIGNAL_LINES" + fi +else + if [ "$LOGGER_COUNTER_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_TIME_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_CYCLE_TIME_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_SCALAR_A_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_SCALAR_B_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_VECTOR_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] && + [ "$LOGGER_SIGNAL_LINES" -eq $((6 * COMPLETE_SAMPLE_COUNT)) ]; then + OBSERVABILITY_STATUS="PASS" + OBSERVABILITY_REASON="NONE" + elif [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_TIME_LINES" ] && + [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_CYCLE_TIME_LINES" ] && + [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_SCALAR_A_LINES" ] && + [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_SCALAR_B_LINES" ] && + [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_VECTOR_LINES" ]; then + OBSERVABILITY_STATUS="NOT_DEMONSTRATED" + OBSERVABILITY_REASON="SHUTDOWN_SKEW" + else + OBSERVABILITY_STATUS="FAIL" + OBSERVABILITY_REASON="LOGGER_BINARY_COUNT_MISMATCH" + fi +fi + +START_LINES="$(count_lines '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' "$RUN_LOG")" +APPLICATION_START_LINES="$(count_lines '^\[Information - MARTeApp\.cpp:[0-9]+\]: Application starting[[:space:]]*$' "$RUN_LOG")" +FILE_OPEN_LINES="$(count_lines '^\[Information - FileWriter\.cpp:[0-9]+\]: Going to open file with name ' "$RUN_LOG")" +SIGINT_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' "$RUN_LOG")" +STOP_OK_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' "$RUN_LOG")" +TERMINATED_LINES="$(count_lines '^\[NoError - MARTeApp\.cpp:[0-9]+\]: Application terminated[[:space:]]*$' "$RUN_LOG")" +STARTUP_ERROR_LINES="$(count_lines 'Failed dlopen|Failed CreateByName|Failed to Initialise object|Failed to initialise the ObjectRegistryDatabase|Could not Initialise the loader' "$RUN_LOG")" +FATAL_ERROR_LINES="$(count_lines '^\[FatalError ' "$RUN_LOG")" +OS_ERROR_LINES="$(count_lines '^\[OSError ' "$RUN_LOG")" +ERROR_LINES="$(count_lines '^\[Error ' "$RUN_LOG")" +INITIALISATION_ERROR_LINES="$(count_lines '^\[InitialisationEr ' "$RUN_LOG")" +PRIORITY_FAIL_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Failed to change the thread priority' "$RUN_LOG")" +DOUBLE_STOP_LINES="$(count_lines '^\[FatalError - RealTimeApplication\.cpp:[0-9]+\]: Could not stop the RealTimeApplication\. Was it ever started\?[[:space:]]*$' "$RUN_LOG")" +ASYNC_CLOSE_LINES="$(count_lines '^\[FatalError - MemoryMapAsyncOutputBroker\.cpp:[0-9]+\]: Could not Close the EventSem\.[[:space:]]*$' "$RUN_LOG")" +MUTEX_DESTROY_LINES="$(count_lines '^\[OSError - EventSem\.cpp:[0-9]+\]: Error: pthread_mutex_destroy\(\)[[:space:]]*$' "$RUN_LOG")" +UNCLASSIFIED_FATAL_LINES=$((FATAL_ERROR_LINES - DOUBLE_STOP_LINES - ASYNC_CLOSE_LINES)) +UNCLASSIFIED_OS_ERROR_LINES=$((OS_ERROR_LINES - MUTEX_DESTROY_LINES)) +[ "$UNCLASSIFIED_FATAL_LINES" -ge 0 ] || UNCLASSIFIED_FATAL_LINES=0 +[ "$UNCLASSIFIED_OS_ERROR_LINES" -ge 0 ] || UNCLASSIFIED_OS_ERROR_LINES=0 + +MARTE_EXIT_CODE="$(sed -n 's/^Exit code MARTe:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +TEE_EXIT_CODE="$(sed -n 's/^Exit code tee:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +[ -n "$MARTE_EXIT_CODE" ] || MARTE_EXIT_CODE="NOT_AVAILABLE" +[ -n "$TEE_EXIT_CODE" ] || TEE_EXIT_CODE="NOT_AVAILABLE" +case "$MARTE_EXIT_CODE" in + 137) TIMING_STATUS="NOT_VALID_EXIT_137_SIGKILL" ;; + *) + if [ "$PRIORITY_FAIL_LINES" -ge 1 ]; then + TIMING_STATUS="NOT_VALID_PRIORITY_NOT_APPLIED" + else + TIMING_STATUS="UNASSESSED_DT_SEMANTICS" + fi + ;; +esac + +if [ "$MODE_STATUS" = "PASS" ] && + [ "$PROFILE_CONFIG_STATUS" = "PASS" ] && + [ "$PROFILE_RUNTIME_STATUS" = "PASS" ] && + [ "$OBSERVABILITY_STATUS" = "PASS" ] && + [ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] && + [ "$START_LINES" -eq 1 ] && + [ "$APPLICATION_START_LINES" -eq 1 ] && + [ "$FILE_OPEN_LINES" -eq 1 ] && + [ "$STARTUP_ERROR_LINES" -eq 0 ] && + [ "$HASH_RC" -eq 0 ] && + [ "$TEE_EXIT_CODE" = "0" ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + FUNCTIONAL_STATUS="PASS" +else + FUNCTIONAL_STATUS="NOT_DEMONSTRATED" +fi + +if [ "$MODE_STATUS" = "PASS" ] && + [ "$PROFILE_CONFIG_STATUS" = "PASS" ] && + [ "$SEMANTIC_BINARY_STATUS" = "PASS" ]; then + SEMANTIC_STATUS="PASS" +elif [ "$SEMANTIC_BINARY_STATUS" = "FAIL" ]; then + SEMANTIC_STATUS="FAIL" +else + SEMANTIC_STATUS="NOT_DEMONSTRATED" +fi + +printf '============================================================\n' +printf 'VALIDAZIONE S03-H3 - MINIMAL / TEST / DIAGNOSTIC\n' +printf '============================================================\n' +printf 'Run directory: %s\n' "$RUN_DIR" +printf 'CANONICAL_STRUCTURAL_COMPLETENESS_STATUS=%s\n' "$CANONICAL_STRUCTURAL_COMPLETENESS_STATUS" +printf 'MINIMAL_RUNTIME_CONFIG_STATUS=%s\n' "$MINIMAL_RUNTIME_CONFIG_STATUS" +printf 'FUNCTION_RESOLUTION_LINES=%s\n' "$FUNCTION_RESOLUTION_LINES" +printf 'LOGGER_SIGNAL_LINES=%s\n' "$LOGGER_SIGNAL_LINES" +printf 'LOGGER_COUNTER_LINES=%s\n' "$LOGGER_COUNTER_LINES" +printf 'LOGGER_TIME_LINES=%s\n' "$LOGGER_TIME_LINES" +printf 'LOGGER_CYCLE_TIME_LINES=%s\n' "$LOGGER_CYCLE_TIME_LINES" +printf 'LOGGER_SCALAR_A_LINES=%s\n' "$LOGGER_SCALAR_A_LINES" +printf 'LOGGER_SCALAR_B_LINES=%s\n' "$LOGGER_SCALAR_B_LINES" +printf 'LOGGER_VECTOR_LINES=%s\n' "$LOGGER_VECTOR_LINES" +printf 'PARSER_RC=%s\n' "$PARSER_RC" +printf 'HASH_VERIFICATION_STATUS=%s\n' "$( [ "$HASH_RC" -eq 0 ] && printf PASS || printf FAIL )" + +printf '\n--- CONFIGURATION VERIFIER DETAIL ---\n' +if [ "$MODE" = "MINIMAL" ]; then + sed 's/^/MINIMAL_CANONICAL_VERIFIER_/' "$CONFIG_REPORT" + sed 's/^/MINIMAL_RUNTIME_VERIFIER_/' "$RUNTIME_CONFIG_REPORT" +else + cat -- "$CONFIG_REPORT" +fi +printf '\n--- BINARY PARSER DETAIL ---\n' +sed 's/^/BINARY_PARSER_/' "$BINARY_REPORT" +printf '\n--- HASH DETAIL ---\n' +cat -- "$HASH_REPORT" + +printf '\n--- CONTRATTO S03-H3 ---\n' +emit_contract + +if [ "$FUNCTIONAL_STATUS" != "PASS" ] || [ "$SEMANTIC_STATUS" != "PASS" ]; then + printf 'VALIDATION_RESULT=FUNCTIONAL_OR_SEMANTIC_NOT_DEMONSTRATED\n' + exit 20 +fi + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$FATAL_ERROR_LINES" -eq 0 ] && + [ "$OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf 'VALIDATION_RESULT=PASS_CLEAN_SHUTDOWN\n' + exit 0 +fi + +KNOWN_SHUTDOWN_TOTAL=$((DOUBLE_STOP_LINES + ASYNC_CLOSE_LINES + MUTEX_DESTROY_LINES)) +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$KNOWN_SHUTDOWN_TOTAL" -ge 1 ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf 'VALIDATION_RESULT=PASS_KNOWN_SHUTDOWN_ANOMALY\n' + exit 10 +fi + +printf 'VALIDATION_RESULT=PASS_UNCLASSIFIED_SHUTDOWN_ANOMALY\n' +exit 11 diff --git a/scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py b/scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py new file mode 100755 index 0000000..28796ba --- /dev/null +++ b/scenarios/S03H3_execution_profiles/verify_s03h3_minimal_structure.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +"""Fail-closed structural verifier for the S03-H3 MINIMAL profile.""" + +from __future__ import annotations + +import argparse +import hashlib +import os +import re +import sys +from pathlib import Path + + +EXPECTED_TEST_SHA256 = ( + "d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e" +) +PLACEHOLDER = "__MARTE_RUN_DIR__" +TEST_FUNCTIONS = ( + " Functions = " + "{GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" +) +MINIMAL_FUNCTIONS = ( + " Functions = " + "{GAMTimer SentinelProducer IOGAM_Writer}" +) + + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def read_utf8(path: Path) -> tuple[bytes, str]: + data = path.read_bytes() + return data, data.decode("utf-8") + + +def object_matches(text: str, name: str) -> list[re.Match[str]]: + pattern = re.compile( + rf"(?m)^[ \t]*\+{re.escape(name)}[ \t]*=[ \t]*\{{[ \t]*(?:\r?\n|$)" + ) + return list(pattern.finditer(text)) + + +def object_count(text: str, name: str) -> int: + return len(object_matches(text, name)) + + +def block_span(text: str, name: str) -> tuple[int, int]: + matches = object_matches(text, name) + if len(matches) != 1: + raise ValueError(f"{name.upper()}_ANCHOR_COUNT_{len(matches)}") + + match = matches[0] + opening = text.find("{", match.start(), match.end()) + if opening < 0: + raise ValueError(f"{name.upper()}_OPENING_BRACE_MISSING") + + depth = 0 + quoted = False + escaped = False + closing = -1 + for index in range(opening, len(text)): + char = text[index] + if quoted: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == '"': + quoted = False + continue + if char == '"': + quoted = True + elif char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + closing = index + 1 + break + if depth < 0: + raise ValueError(f"{name.upper()}_UNBALANCED_BRACE") + + if closing < 0: + raise ValueError(f"{name.upper()}_CLOSING_BRACE_MISSING") + + if text.startswith("\r\n", closing): + closing += 2 + elif text.startswith("\n", closing): + closing += 1 + return match.start(), closing + + +def derive_expected_minimal(canonical: str) -> str: + if canonical.count(TEST_FUNCTIONS) != 1: + raise ValueError( + f"FUNCTIONS_ANCHOR_COUNT_{canonical.count(TEST_FUNCTIONS)}" + ) + if canonical.count(MINIMAL_FUNCTIONS) != 0: + raise ValueError("SOURCE_ALREADY_PARTIALLY_TRANSFORMED") + + spans = [ + block_span(canonical, "GAMDisplay"), + block_span(canonical, "Display"), + ] + if spans[0][0] < spans[1][1] and spans[1][0] < spans[0][1]: + raise ValueError("AUTHORIZED_BLOCK_SPANS_OVERLAP") + + transformed = canonical + for start, end in sorted(spans, reverse=True): + transformed = transformed[:start] + transformed[end:] + transformed = transformed.replace(TEST_FUNCTIONS, MINIMAL_FUNCTIONS, 1) + return transformed + + +class Report: + def __init__(self) -> None: + self.values: list[tuple[str, str]] = [] + self.failed = False + self.first_error = "NONE" + + def value(self, name: str, value: object) -> None: + self.values.append((name, str(value))) + + def status(self, name: str, passed: bool, error: str) -> None: + self.value(name, "PASS" if passed else "FAIL") + if not passed: + self.failed = True + if self.first_error == "NONE": + self.first_error = error + + def emit(self) -> int: + for name, value in self.values: + print(f"{name}={value}") + print(f"MINIMAL_VERIFIER_ERROR={self.first_error}") + return 1 if self.failed else 0 + + +def emit_canonical_failure(error: str) -> int: + report = Report() + report.value("VERIFICATION_MODE", "CANONICAL") + report.value("GAMDisplay_OBJECT_COUNT", 0) + report.value("Display_DATASOURCE_COUNT", 0) + report.value("GAMDisplay_THREAD_TOKEN_COUNT", 0) + report.value("GAMTimer_OBJECT_COUNT", 0) + report.value("SentinelProducer_OBJECT_COUNT", 0) + report.value("IOGAM_Writer_OBJECT_COUNT", 0) + report.value("FileWriter_OBJECT_COUNT", 0) + report.status("THREAD_FUNCTIONS_STATUS", False, error) + report.status("BINARY_PATH_STRUCTURE_STATUS", False, error) + report.status("CANONICAL_STRUCTURAL_COMPLETENESS_STATUS", False, error) + report.status("PROFILE_CONFIG_STATUS", False, error) + return report.emit() + + +def verify_canonical(canonical_path: Path, minimal_path: Path) -> int: + try: + canonical_bytes, canonical = read_utf8(canonical_path) + minimal_bytes, minimal = read_utf8(minimal_path) + except (OSError, UnicodeError) as exc: + return emit_canonical_failure(f"INPUT_READ_FAILED_{type(exc).__name__}") + + canonical_hash = sha256_bytes(canonical_bytes) + try: + expected_minimal = derive_expected_minimal(canonical) + except ValueError as exc: + return emit_canonical_failure(str(exc)) + + expected_bytes = expected_minimal.encode("utf-8") + complete = minimal_bytes == expected_bytes + report = Report() + report.value("VERIFICATION_MODE", "CANONICAL") + report.value("CANONICAL_TEST_SHA256", canonical_hash) + report.value("EXPECTED_CANONICAL_TEST_SHA256", EXPECTED_TEST_SHA256) + report.value("MINIMAL_CONFIG_SHA256", sha256_bytes(minimal_bytes)) + report.value("EXPECTED_MINIMAL_CONFIG_SHA256", sha256_bytes(expected_bytes)) + + counts = { + "GAMDisplay_OBJECT_COUNT": object_count(minimal, "GAMDisplay"), + "Display_DATASOURCE_COUNT": object_count(minimal, "Display"), + "GAMDisplay_THREAD_TOKEN_COUNT": len( + re.findall(r"\bGAMDisplay\b", minimal) + ), + "GAMTimer_OBJECT_COUNT": object_count(minimal, "GAMTimer"), + "SentinelProducer_OBJECT_COUNT": object_count( + minimal, "SentinelProducer" + ), + "IOGAM_Writer_OBJECT_COUNT": object_count(minimal, "IOGAM_Writer"), + "FileWriter_OBJECT_COUNT": object_count(minimal, "FileWriter"), + } + for name, value in counts.items(): + report.value(name, value) + + expected_counts = { + "GAMDisplay_OBJECT_COUNT": 0, + "Display_DATASOURCE_COUNT": 0, + "GAMDisplay_THREAD_TOKEN_COUNT": 0, + "GAMTimer_OBJECT_COUNT": 1, + "SentinelProducer_OBJECT_COUNT": 1, + "IOGAM_Writer_OBJECT_COUNT": 1, + "FileWriter_OBJECT_COUNT": 1, + } + counts_pass = counts == expected_counts + thread_pass = minimal.count(MINIMAL_FUNCTIONS) == 1 + binary_pass = ( + counts["IOGAM_Writer_OBJECT_COUNT"] == 1 + and counts["FileWriter_OBJECT_COUNT"] == 1 + and minimal.count('Filename = "__MARTE_RUN_DIR__/s03_output.bin"') == 1 + and minimal.count('FileFormat = "binary"') == 1 + and minimal.count("DataSource = FileWriter") == 6 + ) + parent_pass = canonical_hash == EXPECTED_TEST_SHA256 + + report.value("UNAUTHORIZED_ADDITION_COUNT", 0 if complete else 1) + report.status( + "CANONICAL_TEST_IDENTITY_STATUS", + parent_pass, + "CANONICAL_TEST_SHA256_MISMATCH", + ) + report.status( + "AUTHORIZED_ABSENCE_AND_PRESENCE_STATUS", + counts_pass, + "OBJECT_OR_TOKEN_COUNT_MISMATCH", + ) + report.status( + "THREAD_FUNCTIONS_STATUS", thread_pass, "THREAD_FUNCTIONS_MISMATCH" + ) + report.status( + "BINARY_PATH_STRUCTURE_STATUS", + binary_pass, + "BINARY_PATH_STRUCTURE_MISMATCH", + ) + report.status( + "CANONICAL_STRUCTURAL_COMPLETENESS_STATUS", + parent_pass and complete, + "UNAUTHORIZED_STRUCTURAL_DIFFERENCE", + ) + report.status( + "PROFILE_CONFIG_STATUS", + parent_pass and complete and counts_pass and thread_pass and binary_pass, + "MINIMAL_PROFILE_CONFIG_NOT_PROVEN", + ) + return report.emit() + + +def extract_runtime_filename(text: str) -> list[str]: + return re.findall(r'(?m)^[ \t]*Filename[ \t]*=[ \t]*"([^"]+)"[ \t]*$', text) + + +def verify_runtime(packaged_path: Path, runtime_path: Path, run_dir: Path) -> int: + report = Report() + report.value("VERIFICATION_MODE", "RUNTIME") + try: + packaged_bytes, packaged = read_utf8(packaged_path) + runtime_bytes, runtime = read_utf8(runtime_path) + except (OSError, UnicodeError) as exc: + report.status( + "MINIMAL_RUNTIME_CONFIG_STATUS", + False, + f"INPUT_READ_FAILED_{type(exc).__name__}", + ) + return report.emit() + + supplied = str(run_dir) + absolute = run_dir.is_absolute() + exists = run_dir.is_dir() + placeholder_count = packaged.count(PLACEHOLDER) + unresolved_count = runtime.count(PLACEHOLDER) + expected_runtime = packaged.replace(PLACEHOLDER, supplied) + exact = runtime_bytes == expected_runtime.encode("utf-8") + filenames = extract_runtime_filename(runtime) + expected_filename = os.path.join(supplied, "s03_output.bin") + filename_pass = filenames.count(expected_filename) == 1 and len(filenames) == 1 + + report.value("PACKAGED_MINIMAL_SHA256", sha256_bytes(packaged_bytes)) + report.value("RUNTIME_CONFIG_SHA256", sha256_bytes(runtime_bytes)) + report.value("PACKAGED_PLACEHOLDER_COUNT", placeholder_count) + report.value("RUNTIME_UNRESOLVED_PLACEHOLDER_COUNT", unresolved_count) + report.value("RUNTIME_FILENAME", filenames[0] if len(filenames) == 1 else "AMBIGUOUS") + report.value("EXPECTED_RUNTIME_FILENAME", expected_filename) + report.status("RUN_DIR_ABSOLUTE_STATUS", absolute, "RUN_DIR_NOT_ABSOLUTE") + report.status("RUN_DIR_EXISTS_STATUS", exists, "RUN_DIR_NOT_FOUND") + report.status( + "PACKAGED_PLACEHOLDER_STATUS", + placeholder_count == 1, + "PACKAGED_PLACEHOLDER_COUNT_MISMATCH", + ) + report.status( + "RUNTIME_PLACEHOLDER_RESOLUTION_STATUS", + unresolved_count == 0, + "RUNTIME_PLACEHOLDER_UNRESOLVED", + ) + report.status( + "RUNTIME_FILENAME_STATUS", filename_pass, "RUNTIME_FILENAME_OUTSIDE_RUN_DIR" + ) + report.status( + "MINIMAL_RUNTIME_CONFIG_STATUS", + absolute + and exists + and placeholder_count == 1 + and unresolved_count == 0 + and filename_pass + and exact, + "RUNTIME_DIFF_EXCEEDS_AUTHORIZED_SUBSTITUTION", + ) + return report.emit() + + +def main() -> int: + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest="mode", required=True) + + canonical = subparsers.add_parser("CANONICAL") + canonical.add_argument("canonical_test", type=Path) + canonical.add_argument("packaged_minimal", type=Path) + + runtime = subparsers.add_parser("RUNTIME") + runtime.add_argument("packaged_minimal", type=Path) + runtime.add_argument("runtime_configuration", type=Path) + runtime.add_argument("run_dir", type=Path) + + args = parser.parse_args() + if args.mode == "CANONICAL": + return verify_canonical(args.canonical_test, args.packaged_minimal) + return verify_runtime( + args.packaged_minimal, args.runtime_configuration, args.run_dir + ) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scenarios/S03_semantic_sentinels/parse_s03_binary.py b/scenarios/S03_semantic_sentinels/parse_s03_binary.py new file mode 100755 index 0000000..1550f66 --- /dev/null +++ b/scenarios/S03_semantic_sentinels/parse_s03_binary.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +"""Fail-closed parser for the MARTe2 S03 FileWriter binary contract.""" + +import argparse +import struct +import sys +from pathlib import Path + + +UINT32_TYPE_HEX = "0408" +UINT32_BYTES = 4 +SIGNAL_HEADER_BYTES = 2 + 32 + 4 + +SENTINEL_SCALAR_A = 324508639 # 0x13579BDF +SENTINEL_SCALAR_B = 610839776 # 0x2468ACE0 +SENTINEL_VECTOR = (16909060, 286397204, 555885348) + +EXPECTED_SIGNALS = ( + ("Counter", UINT32_TYPE_HEX, 1), + ("Time", UINT32_TYPE_HEX, 1), + ("State1_Thread1_CycleTime", UINT32_TYPE_HEX, 1), + ("SentinelScalarA", UINT32_TYPE_HEX, 1), + ("SentinelScalarB", UINT32_TYPE_HEX, 1), + ("SentinelVector", UINT32_TYPE_HEX, 3), +) + +EXPECTED_OFFSETS = (0, 4, 8, 12, 16, 20) +EXPECTED_RECORD_BYTES = 32 +EXPECTED_HEADER_BYTES = 4 + len(EXPECTED_SIGNALS) * SIGNAL_HEADER_BYTES + +STATUS_ORDER = ( + "HEADER_STATUS", + "RECORD_LAYOUT_STATUS", + "PAYLOAD_ALIGNMENT_STATUS", + "TRUNCATION_STATUS", + "SIGNAL_COUNT_STATUS", + "SIGNAL_NAMES_STATUS", + "SIGNAL_TYPES_STATUS", + "SIGNAL_ELEMENTS_STATUS", + "SIGNAL_ORDER_STATUS", + "COUNTER_STATUS", + "TIME_STATUS", + "CYCLE_TIME_STATUS", + "SENTINEL_SCALAR_A_STATUS", + "SENTINEL_SCALAR_B_STATUS", + "SENTINEL_VECTOR_STATUS", + "SENTINEL_SEQUENCE_STATUS", + "FUNCTIONAL_BINARY_STATUS", + "SEMANTIC_BINARY_STATUS", + "BINARY_STATUS", +) + + +def safe_value(value): + return str(value).replace("\r", "\\r").replace("\n", "\\n") + + +class Report: + def __init__(self): + self.status = {name: "NOT_EVALUATED" for name in STATUS_ORDER} + self.details = {} + self.first_error = { + "FIRST_ERROR_CODE": "NONE", + "EXPECTED": "NOT_APPLICABLE", + "OBSERVED": "NOT_APPLICABLE", + "RECORD_INDEX": "NOT_APPLICABLE", + "SIGNAL_NAME": "NOT_APPLICABLE", + "ELEMENT_INDEX": "NOT_APPLICABLE", + "BYTE_OFFSET": "NOT_APPLICABLE", + } + + def set_error( + self, + code, + expected="NOT_APPLICABLE", + observed="NOT_APPLICABLE", + record_index="NOT_APPLICABLE", + signal_name="NOT_APPLICABLE", + element_index="NOT_APPLICABLE", + byte_offset="NOT_APPLICABLE", + ): + if self.first_error["FIRST_ERROR_CODE"] != "NONE": + return + self.first_error.update( + { + "FIRST_ERROR_CODE": safe_value(code), + "EXPECTED": safe_value(expected), + "OBSERVED": safe_value(observed), + "RECORD_INDEX": safe_value(record_index), + "SIGNAL_NAME": safe_value(signal_name), + "ELEMENT_INDEX": safe_value(element_index), + "BYTE_OFFSET": safe_value(byte_offset), + } + ) + + def emit(self): + for key in STATUS_ORDER: + print(f"{key}={self.status[key]}") + for key in sorted(self.details): + print(f"{key}={safe_value(self.details[key])}") + for key in ( + "FIRST_ERROR_CODE", + "EXPECTED", + "OBSERVED", + "RECORD_INDEX", + "SIGNAL_NAME", + "ELEMENT_INDEX", + "BYTE_OFFSET", + ): + print(f"{key}={self.first_error[key]}") + + +def padded_name(raw): + try: + return raw.split(b"\x00", 1)[0].decode("ascii") + except UnicodeDecodeError: + return raw.split(b"\x00", 1)[0].decode("ascii", errors="replace") + + +def finish_early(report, exit_code): + report.status["FUNCTIONAL_BINARY_STATUS"] = "FAIL" + report.status["SEMANTIC_BINARY_STATUS"] = "NOT_DEMONSTRATED" + report.status["BINARY_STATUS"] = "FAIL" + report.emit() + return exit_code + + +def parse_binary(path, min_samples): + report = Report() + + try: + data = path.read_bytes() + except OSError as exc: + report.status["HEADER_STATUS"] = "FAIL" + report.status["TRUNCATION_STATUS"] = "FAIL" + report.details["FILE_SIZE_BYTES"] = "NOT_AVAILABLE" + report.set_error("FILE_READ_ERROR", observed=exc) + return report, finish_early(report, 31), True + + report.details["FILE_SIZE_BYTES"] = len(data) + report.details["EXPECTED_HEADER_BYTES"] = EXPECTED_HEADER_BYTES + report.details["EXPECTED_RECORD_BYTES"] = EXPECTED_RECORD_BYTES + report.details["EXPECTED_SIGNAL_COUNT"] = len(EXPECTED_SIGNALS) + + if len(data) < 4: + report.status["HEADER_STATUS"] = "FAIL" + report.status["TRUNCATION_STATUS"] = "FAIL" + report.status["SIGNAL_COUNT_STATUS"] = "NOT_EVALUATED" + report.set_error( + "HEADER_TOO_SMALL", + expected="AT_LEAST_4_BYTES", + observed=f"{len(data)}_BYTES", + byte_offset=len(data), + ) + return report, 31, False + + signal_count = struct.unpack_from(" 4096: + report.status["HEADER_STATUS"] = "FAIL" + report.status["TRUNCATION_STATUS"] = "FAIL" + report.status["SIGNAL_COUNT_STATUS"] = "FAIL" + report.set_error( + "SIGNAL_COUNT_UNREASONABLE", + expected=len(EXPECTED_SIGNALS), + observed=signal_count, + byte_offset=0, + ) + return report, 32, False + + if signal_count == len(EXPECTED_SIGNALS): + report.status["SIGNAL_COUNT_STATUS"] = "PASS" + else: + report.status["SIGNAL_COUNT_STATUS"] = "FAIL" + report.set_error( + "SIGNAL_COUNT_MISMATCH", + expected=len(EXPECTED_SIGNALS), + observed=signal_count, + byte_offset=0, + ) + + offset = 4 + observed = [] + for index in range(signal_count): + if offset + SIGNAL_HEADER_BYTES > len(data): + report.status["HEADER_STATUS"] = "FAIL" + report.status["TRUNCATION_STATUS"] = "FAIL" + report.set_error( + "TRUNCATED_SIGNAL_HEADER", + expected=SIGNAL_HEADER_BYTES, + observed=max(0, len(data) - offset), + signal_name=f"SIGNAL_{index}", + byte_offset=offset, + ) + report.details["OBSERVED_HEADER_BYTES"] = offset + return report, 33, False + + type_offset = offset + type_hex = data[offset : offset + 2].hex() + offset += 2 + name_offset = offset + name = padded_name(data[offset : offset + 32]) + offset += 32 + elements_offset = offset + elements = struct.unpack_from(" 1}) + if duplicates: + report.status["SIGNAL_NAMES_STATUS"] = "FAIL" + report.status["SIGNAL_ORDER_STATUS"] = "FAIL" + report.set_error( + "DUPLICATE_SIGNAL_NAME", + expected="UNIQUE_NAMES", + observed=",".join(duplicates), + ) + elif sorted(observed_names) == sorted(expected_names): + report.status["SIGNAL_NAMES_STATUS"] = "PASS" + if observed_names == expected_names: + report.status["SIGNAL_ORDER_STATUS"] = "PASS" + else: + report.status["SIGNAL_ORDER_STATUS"] = "FAIL" + first_index = next( + index + for index, pair in enumerate(zip(observed_names, expected_names)) + if pair[0] != pair[1] + ) + report.set_error( + "SIGNAL_ORDER_MISMATCH", + expected=expected_names[first_index], + observed=observed_names[first_index], + signal_name=observed_names[first_index], + byte_offset=observed[first_index]["name_offset"], + ) + else: + report.status["SIGNAL_NAMES_STATUS"] = "FAIL" + report.status["SIGNAL_ORDER_STATUS"] = "FAIL" + mismatch_index = 0 + for mismatch_index in range(min(len(observed_names), len(expected_names))): + if observed_names[mismatch_index] != expected_names[mismatch_index]: + break + observed_name = ( + observed_names[mismatch_index] + if mismatch_index < len(observed_names) + else "MISSING" + ) + expected_name = ( + expected_names[mismatch_index] + if mismatch_index < len(expected_names) + else "NO_ADDITIONAL_SIGNAL" + ) + byte_offset = ( + observed[mismatch_index]["name_offset"] + if mismatch_index < len(observed) + else header_bytes + ) + report.set_error( + "SIGNAL_NAME_MISMATCH", + expected=expected_name, + observed=observed_name, + signal_name=observed_name, + byte_offset=byte_offset, + ) + + types_ok = len(observed) == len(EXPECTED_SIGNALS) + elements_ok = len(observed) == len(EXPECTED_SIGNALS) + observed_offsets = [] + running_offset = 0 + + for index, item in enumerate(observed): + observed_offsets.append(running_offset) + width = UINT32_BYTES if item["type_hex"] == UINT32_TYPE_HEX else UINT32_BYTES + running_offset += width * item["elements"] + report.details[f"SIGNAL_{index}_OBSERVED_OFFSET"] = observed_offsets[-1] + if index < len(EXPECTED_OFFSETS): + report.details[f"SIGNAL_{index}_EXPECTED_OFFSET"] = EXPECTED_OFFSETS[index] + + if index >= len(EXPECTED_SIGNALS): + types_ok = False + elements_ok = False + continue + + expected_name, expected_type, expected_elements = EXPECTED_SIGNALS[index] + if item["type_hex"] != expected_type: + types_ok = False + report.set_error( + "SIGNAL_TYPE_MISMATCH", + expected=expected_type, + observed=item["type_hex"], + signal_name=expected_name, + byte_offset=item["type_offset"], + ) + if item["elements"] != expected_elements: + elements_ok = False + report.set_error( + "SIGNAL_ELEMENTS_MISMATCH", + expected=expected_elements, + observed=item["elements"], + signal_name=expected_name, + byte_offset=item["elements_offset"], + ) + + report.status["SIGNAL_TYPES_STATUS"] = "PASS" if types_ok else "FAIL" + report.status["SIGNAL_ELEMENTS_STATUS"] = "PASS" if elements_ok else "FAIL" + report.details["OBSERVED_RECORD_BYTES_FROM_HEADER"] = running_offset + + layout_ok = all( + report.status[name] == "PASS" + for name in ( + "SIGNAL_COUNT_STATUS", + "SIGNAL_NAMES_STATUS", + "SIGNAL_TYPES_STATUS", + "SIGNAL_ELEMENTS_STATUS", + "SIGNAL_ORDER_STATUS", + ) + ) and observed_offsets == list(EXPECTED_OFFSETS) + + report.status["RECORD_LAYOUT_STATUS"] = "PASS" if layout_ok else "FAIL" + if not layout_ok and report.first_error["FIRST_ERROR_CODE"] == "NONE": + report.set_error( + "RECORD_LAYOUT_MISMATCH", + expected=EXPECTED_RECORD_BYTES, + observed=running_offset, + ) + + payload_bytes = len(data) - header_bytes + remainder_bytes = payload_bytes % EXPECTED_RECORD_BYTES + sample_count = payload_bytes // EXPECTED_RECORD_BYTES + report.details["PAYLOAD_BYTES"] = payload_bytes + report.details["PAYLOAD_REMAINDER_BYTES"] = remainder_bytes + report.details["COMPLETE_SAMPLE_COUNT"] = sample_count + report.details["MINIMUM_SAMPLE_COUNT"] = min_samples + + if remainder_bytes == 0: + report.status["PAYLOAD_ALIGNMENT_STATUS"] = "PASS" + report.status["TRUNCATION_STATUS"] = "PASS" + else: + report.status["PAYLOAD_ALIGNMENT_STATUS"] = "FAIL" + report.status["TRUNCATION_STATUS"] = "FAIL" + report.set_error( + "PAYLOAD_SIZE_NOT_MULTIPLE_OF_RECORD", + expected=f"MULTIPLE_OF_{EXPECTED_RECORD_BYTES}", + observed=f"REMAINDER_{remainder_bytes}", + byte_offset=header_bytes + sample_count * EXPECTED_RECORD_BYTES, + ) + + if sample_count < min_samples: + report.set_error( + "INSUFFICIENT_SAMPLES", + expected=f"AT_LEAST_{min_samples}", + observed=sample_count, + ) + + records = [] + if remainder_bytes == 0: + for record_index in range(sample_count): + start = header_bytes + record_index * EXPECTED_RECORD_BYTES + records.append(struct.unpack_from("<8I", data, start)) + + counters = [record[0] for record in records] + times = [record[1] for record in records] + cycle_times = [record[2] for record in records] + + counter_error = None + if counters: + if counters[0] != 1: + counter_error = (0, 1, counters[0]) + else: + for index in range(1, len(counters)): + if counters[index] != counters[index - 1] + 1: + counter_error = (index, counters[index - 1] + 1, counters[index]) + break + else: + counter_error = ("NOT_APPLICABLE", "AT_LEAST_ONE_RECORD", "NO_RECORDS") + + if counter_error is None: + report.status["COUNTER_STATUS"] = "PASS" + else: + report.status["COUNTER_STATUS"] = "FAIL" + record_index, expected_counter, observed_counter = counter_error + byte_offset = ( + header_bytes + int(record_index) * EXPECTED_RECORD_BYTES + if isinstance(record_index, int) + else header_bytes + ) + report.set_error( + "COUNTER_SEQUENCE_MISMATCH", + expected=expected_counter, + observed=observed_counter, + record_index=record_index, + signal_name="Counter", + element_index=0, + byte_offset=byte_offset, + ) + + time_error = None + for index in range(1, len(times)): + if times[index] < times[index - 1]: + time_error = (index, f">={times[index - 1]}", times[index]) + break + if records and time_error is None: + report.status["TIME_STATUS"] = "PASS" + else: + report.status["TIME_STATUS"] = "FAIL" + if time_error is not None: + record_index, expected_time, observed_time = time_error + report.set_error( + "TIME_REGRESSION", + expected=expected_time, + observed=observed_time, + record_index=record_index, + signal_name="Time", + element_index=0, + byte_offset=header_bytes + record_index * EXPECTED_RECORD_BYTES + 4, + ) + + nonzero_cycle_times = [value for value in cycle_times if value > 0] + if records and nonzero_cycle_times: + report.status["CYCLE_TIME_STATUS"] = "PASS" + else: + report.status["CYCLE_TIME_STATUS"] = "FAIL" + report.set_error( + "NO_NONZERO_CYCLE_TIME", + expected="AT_LEAST_ONE_NONZERO_SAMPLE", + observed=len(nonzero_cycle_times), + signal_name="State1_Thread1_CycleTime", + byte_offset=header_bytes + 8, + ) + + sentinel_failures = { + "A": None, + "B": None, + "VECTOR": None, + } + + for record_index, record in enumerate(records): + if sentinel_failures["A"] is None and record[3] != SENTINEL_SCALAR_A: + sentinel_failures["A"] = (record_index, 0, SENTINEL_SCALAR_A, record[3], 12) + if sentinel_failures["B"] is None and record[4] != SENTINEL_SCALAR_B: + sentinel_failures["B"] = (record_index, 0, SENTINEL_SCALAR_B, record[4], 16) + for element_index, expected_value in enumerate(SENTINEL_VECTOR): + observed_value = record[5 + element_index] + if sentinel_failures["VECTOR"] is None and observed_value != expected_value: + sentinel_failures["VECTOR"] = ( + record_index, + element_index, + expected_value, + observed_value, + 20 + element_index * UINT32_BYTES, + ) + + for key, status_name, signal_name in ( + ("A", "SENTINEL_SCALAR_A_STATUS", "SentinelScalarA"), + ("B", "SENTINEL_SCALAR_B_STATUS", "SentinelScalarB"), + ("VECTOR", "SENTINEL_VECTOR_STATUS", "SentinelVector"), + ): + failure = sentinel_failures[key] + if records and failure is None: + report.status[status_name] = "PASS" + else: + report.status[status_name] = "FAIL" + if failure is not None: + record_index, element_index, expected_value, observed_value, field_offset = failure + code = { + "A": "SENTINEL_SCALAR_A_MISMATCH", + "B": "SENTINEL_SCALAR_B_MISMATCH", + "VECTOR": "SENTINEL_VECTOR_MISMATCH", + }[key] + report.set_error( + code, + expected=expected_value, + observed=observed_value, + record_index=record_index, + signal_name=signal_name, + element_index=element_index, + byte_offset=header_bytes + + record_index * EXPECTED_RECORD_BYTES + + field_offset, + ) + + sentinel_values_ok = all( + report.status[name] == "PASS" + for name in ( + "SENTINEL_SCALAR_A_STATUS", + "SENTINEL_SCALAR_B_STATUS", + "SENTINEL_VECTOR_STATUS", + ) + ) + report.status["SENTINEL_SEQUENCE_STATUS"] = ( + "PASS" if sentinel_values_ok and records else "FAIL" + ) + + if counters: + report.details["COUNTER_FIRST"] = counters[0] + report.details["COUNTER_LAST"] = counters[-1] + if times: + report.details["TIME_FIRST"] = times[0] + report.details["TIME_LAST"] = times[-1] + report.details["NONZERO_CYCLE_TIME_SAMPLES"] = len(nonzero_cycle_times) + if nonzero_cycle_times: + report.details["CYCLE_TIME_MIN"] = min(nonzero_cycle_times) + report.details["CYCLE_TIME_MAX"] = max(nonzero_cycle_times) + + base_header_ok = len(observed) >= 3 and all( + ( + observed[index]["name"], + observed[index]["type_hex"], + observed[index]["elements"], + ) + == EXPECTED_SIGNALS[index] + for index in range(3) + ) + + functional_ok = ( + report.status["HEADER_STATUS"] == "PASS" + and base_header_ok + and report.status["PAYLOAD_ALIGNMENT_STATUS"] == "PASS" + and sample_count >= min_samples + and report.status["COUNTER_STATUS"] == "PASS" + and report.status["TIME_STATUS"] == "PASS" + and report.status["CYCLE_TIME_STATUS"] == "PASS" + ) + + semantic_ok = ( + layout_ok + and report.status["PAYLOAD_ALIGNMENT_STATUS"] == "PASS" + and sample_count >= min_samples + and sentinel_values_ok + and report.status["SENTINEL_SEQUENCE_STATUS"] == "PASS" + ) + + report.status["FUNCTIONAL_BINARY_STATUS"] = "PASS" if functional_ok else "FAIL" + report.status["SEMANTIC_BINARY_STATUS"] = "PASS" if semantic_ok else "FAIL" + report.status["BINARY_STATUS"] = ( + "PASS" if functional_ok and semantic_ok else "FAIL" + ) + + return report, 0 if report.status["BINARY_STATUS"] == "PASS" else 40, False + + +def main(): + parser = argparse.ArgumentParser( + description="Validate the S03 MARTe2 FileWriter binary contract." + ) + parser.add_argument("binary", type=Path) + parser.add_argument("--min-samples", type=int, default=50) + args = parser.parse_args() + + if args.min_samples < 1: + parser.error("--min-samples must be at least 1") + + report, exit_code, already_emitted = parse_binary(args.binary, args.min_samples) + if not already_emitted: + if report.status["FUNCTIONAL_BINARY_STATUS"] == "NOT_EVALUATED": + report.status["FUNCTIONAL_BINARY_STATUS"] = "FAIL" + if report.status["SEMANTIC_BINARY_STATUS"] == "NOT_EVALUATED": + report.status["SEMANTIC_BINARY_STATUS"] = "NOT_DEMONSTRATED" + if report.status["BINARY_STATUS"] == "NOT_EVALUATED": + report.status["BINARY_STATUS"] = "FAIL" + report.emit() + return exit_code + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scenarios/S03_semantic_sentinels/validate.sh b/scenarios/S03_semantic_sentinels/validate.sh new file mode 100755 index 0000000..8b0046e --- /dev/null +++ b/scenarios/S03_semantic_sentinels/validate.sh @@ -0,0 +1,347 @@ +#!/usr/bin/env bash +set -u + +EXPECTED_S03_CONFIG_SHA256="0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4" +MINIMUM_SAMPLES="50" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PARSER="$SCRIPT_DIR/parse_s03_binary.py" + +FUNCTIONAL_STATUS="NOT_DEMONSTRATED" +SEMANTIC_STATUS="NOT_DEMONSTRATED" +TIMING_STATUS="NOT_EVALUATED" +MEASUREMENT_ELIGIBILITY="NO" + +emit_contract() { + printf "FUNCTIONAL_STATUS=%s\n" "$FUNCTIONAL_STATUS" + printf "SEMANTIC_STATUS=%s\n" "$SEMANTIC_STATUS" + printf "TIMING_STATUS=%s\n" "$TIMING_STATUS" + printf "MEASUREMENT_ELIGIBILITY=%s\n" "$MEASUREMENT_ELIGIBILITY" +} + +fail_early() { + code="$1" + shift + printf "[NON DIMOSTRATO] %s\n" "$*" >&2 + emit_contract + printf "VALIDATION_RESULT=FAIL_REQUIRED_INPUT\n" + exit "$code" +} + +if [ "$#" -ne 1 ]; then + printf "Uso: %s \n" "$0" >&2 + emit_contract + exit 2 +fi + +INPUT_PATH="$1" +if [ -d "$INPUT_PATH" ]; then + RUN_DIR="$(cd "$INPUT_PATH" && pwd)" + RUN_LOG="$RUN_DIR/run.log" +elif [ -f "$INPUT_PATH" ]; then + RUN_LOG="$(cd "$(dirname "$INPUT_PATH")" && pwd)/$(basename "$INPUT_PATH")" + RUN_DIR="$(dirname "$RUN_LOG")" +else + fail_early 21 "Percorso di validazione non trovato: $INPUT_PATH" +fi + +RUN_CONFIG="$RUN_DIR/configuration.marte" +OUTPUT_FILE="$RUN_DIR/s03_output.bin" +RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt" +RUN_HASHES="$RUN_DIR/hashes.sha256" + +if [ ! -x "$PARSER" ]; then + fail_early 21 "Parser S03 assente o non eseguibile: $PARSER" +fi + +for required_file in \ + "$RUN_LOG" \ + "$RUN_CONFIG" \ + "$OUTPUT_FILE" \ + "$RUNNER_SUMMARY" \ + "$RUN_HASHES" +do + if [ ! -f "$required_file" ]; then + fail_early 21 "File richiesto assente: $required_file" + fi +done + +if [ ! -s "$RUN_HASHES" ]; then + fail_early 21 "Manifest hash assente o vuoto: $RUN_HASHES" +fi + +count_lines() { + pattern="$1" + file="$2" + grep -Ec "$pattern" "$file" || true +} + +START_LINES="$(count_lines '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' "$RUN_LOG")" +APPLICATION_START_LINES="$(count_lines '^\[Information - MARTeApp\.cpp:[0-9]+\]: Application starting[[:space:]]*$' "$RUN_LOG")" +FILE_OPEN_LINES="$(count_lines '^\[Information - FileWriter\.cpp:[0-9]+\]: Going to open file with name ' "$RUN_LOG")" +SIGINT_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' "$RUN_LOG")" +STOP_OK_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' "$RUN_LOG")" +TERMINATED_LINES="$(count_lines '^\[NoError - MARTeApp\.cpp:[0-9]+\]: Application terminated[[:space:]]*$' "$RUN_LOG")" + +STARTUP_ERROR_LINES="$(count_lines 'Failed dlopen|Failed CreateByName|Failed to Initialise object|Failed to initialise the ObjectRegistryDatabase|Could not Initialise the loader' "$RUN_LOG")" +FATAL_ERROR_LINES="$(count_lines '^\[FatalError ' "$RUN_LOG")" +OS_ERROR_LINES="$(count_lines '^\[OSError ' "$RUN_LOG")" +ERROR_LINES="$(count_lines '^\[Error ' "$RUN_LOG")" +INITIALISATION_ERROR_LINES="$(count_lines '^\[InitialisationEr ' "$RUN_LOG")" +WARNING_LINES="$(count_lines '^\[Warning ' "$RUN_LOG")" + +PRIORITY_CLIP_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Requested a thread priority that is higher than ' "$RUN_LOG")" +PRIORITY_FAIL_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Failed to change the thread priority' "$RUN_LOG")" +LINUX_TIMER_DEFAULT_WARNING_LINES="$(count_lines '^\[Warning - LinuxTimer\.cpp:[0-9]+\]: (ExecutionMode|CPUMask|StackSize) not specified using: ' "$RUN_LOG")" +CONFIG_DB_NODE_WARNING_LINES="$(count_lines '^\[Warning - MARTeApp\.cpp:[0-9]+\]: \[ConfigurationDatabaseNode\] - instances: [0-9]+[[:space:]]*$' "$RUN_LOG")" + +UNCLASSIFIED_WARNING_LINES=$(( + WARNING_LINES - + PRIORITY_CLIP_LINES - + PRIORITY_FAIL_LINES - + LINUX_TIMER_DEFAULT_WARNING_LINES - + CONFIG_DB_NODE_WARNING_LINES +)) +if [ "$UNCLASSIFIED_WARNING_LINES" -lt 0 ]; then + UNCLASSIFIED_WARNING_LINES=0 +fi + +DOUBLE_STOP_LINES="$(count_lines '^\[FatalError - RealTimeApplication\.cpp:[0-9]+\]: Could not stop the RealTimeApplication\. Was it ever started\?[[:space:]]*$' "$RUN_LOG")" +ASYNC_EVENTSEM_CLOSE_LINES="$(count_lines '^\[FatalError - MemoryMapAsyncOutputBroker\.cpp:[0-9]+\]: Could not Close the EventSem\.[[:space:]]*$' "$RUN_LOG")" +PTHREAD_MUTEX_DESTROY_LINES="$(count_lines '^\[OSError - EventSem\.cpp:[0-9]+\]: Error: pthread_mutex_destroy\(\)[[:space:]]*$' "$RUN_LOG")" + +UNCLASSIFIED_FATAL_LINES=$((FATAL_ERROR_LINES - DOUBLE_STOP_LINES - ASYNC_EVENTSEM_CLOSE_LINES)) +UNCLASSIFIED_OS_ERROR_LINES=$((OS_ERROR_LINES - PTHREAD_MUTEX_DESTROY_LINES)) +if [ "$UNCLASSIFIED_FATAL_LINES" -lt 0 ]; then + UNCLASSIFIED_FATAL_LINES=0 +fi +if [ "$UNCLASSIFIED_OS_ERROR_LINES" -lt 0 ]; then + UNCLASSIFIED_OS_ERROR_LINES=0 +fi + +CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03_validator_config_XXXXXX.log")" || exit 22 +BINARY_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03_validator_binary_XXXXXX.log")" || exit 22 +HASH_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03_validator_hashes_XXXXXX.log")" || exit 22 +trap 'rm -f -- "$CONFIG_REPORT" "$BINARY_REPORT" "$HASH_REPORT"' EXIT HUP INT TERM + +EXPECTED_FILENAME="$RUN_DIR/s03_output.bin" +PYTHONHASHSEED=0 python3 - \ + "$RUN_CONFIG" \ + "$EXPECTED_FILENAME" \ + "$EXPECTED_S03_CONFIG_SHA256" \ + >"$CONFIG_REPORT" 2>&1 <<'PY_S03_CONFIG' +import hashlib +import sys +from pathlib import Path + + +config_path = Path(sys.argv[1]) +expected_filename = sys.argv[2] +expected_hash = sys.argv[3] +text = config_path.read_text(encoding="utf-8") + +expected_line = f' Filename = "{expected_filename}"' +placeholder_line = ' Filename = "__MARTE_RUN_DIR__/s03_output.bin"' +filename_count = sum(1 for line in text.splitlines() if line == expected_line) +placeholder_count = text.count("__MARTE_RUN_DIR__") + +canonical = text.replace(expected_line, placeholder_line, 1) +canonical_hash = hashlib.sha256(canonical.encode("utf-8")).hexdigest() + +checks = { + "EXPECTED_FILENAME_COUNT": filename_count, + "PLACEHOLDER_COUNT": placeholder_count, + "FILEWRITER_CLASS_COUNT": text.count("Class = FileDataSource::FileWriter"), + "PLAIN_FILEWRITER_CLASS_COUNT": text.count("Class = FileWriter"), + "CONSTANT_GAM_COUNT": text.count("Class = ConstantGAM"), + "SENTINEL_SCALAR_A_DECLARATION_COUNT": text.count("SentinelScalarA ="), + "SENTINEL_SCALAR_B_DECLARATION_COUNT": text.count("SentinelScalarB ="), + "SENTINEL_VECTOR_DECLARATION_COUNT": text.count("SentinelVector ="), + "VECTOR_DIMENSION_COUNT": text.count("NumberOfDimensions = 1"), + "VECTOR_CARDINALITY_COUNT": text.count("NumberOfElements = 3"), + "THREAD_ORDER_COUNT": text.count( + "Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}" + ), +} + +expected_checks = { + "EXPECTED_FILENAME_COUNT": 1, + "PLACEHOLDER_COUNT": 0, + "FILEWRITER_CLASS_COUNT": 1, + "PLAIN_FILEWRITER_CLASS_COUNT": 0, + "CONSTANT_GAM_COUNT": 1, + "SENTINEL_SCALAR_A_DECLARATION_COUNT": 4, + "SENTINEL_SCALAR_B_DECLARATION_COUNT": 4, + "SENTINEL_VECTOR_DECLARATION_COUNT": 4, + "VECTOR_DIMENSION_COUNT": 4, + "VECTOR_CARDINALITY_COUNT": 4, + "THREAD_ORDER_COUNT": 1, +} + +for key in sorted(checks): + print(f"{key}={checks[key]}") +print(f"CANONICAL_CONFIG_SHA256={canonical_hash}") +print(f"EXPECTED_CONFIG_SHA256={expected_hash}") + +if checks == expected_checks and canonical_hash == expected_hash: + print("CONFIG_STATUS=PASS") + sys.exit(0) + +print("CONFIG_STATUS=FAIL") +sys.exit(1) +PY_S03_CONFIG +CONFIG_RC=$? + +PYTHONHASHSEED=0 "$PARSER" "$OUTPUT_FILE" --min-samples "$MINIMUM_SAMPLES" \ + >"$BINARY_REPORT" 2>&1 +PARSER_RC=$? + +sha256sum -c --strict "$RUN_HASHES" >"$HASH_REPORT" 2>&1 +HASH_RC=$? + +report_value() { + key="$1" + file="$2" + sed -n "s/^${key}=//p" "$file" | tail -n 1 +} + +CONFIG_STATUS="$(report_value CONFIG_STATUS "$CONFIG_REPORT")" +FUNCTIONAL_BINARY_STATUS="$(report_value FUNCTIONAL_BINARY_STATUS "$BINARY_REPORT")" +SEMANTIC_BINARY_STATUS="$(report_value SEMANTIC_BINARY_STATUS "$BINARY_REPORT")" +BINARY_STATUS="$(report_value BINARY_STATUS "$BINARY_REPORT")" +FIRST_ERROR_CODE="$(report_value FIRST_ERROR_CODE "$BINARY_REPORT")" +COMPLETE_SAMPLE_COUNT="$(report_value COMPLETE_SAMPLE_COUNT "$BINARY_REPORT")" +HEADER_BYTES="$(report_value OBSERVED_HEADER_BYTES "$BINARY_REPORT")" +RECORD_BYTES="$(report_value OBSERVED_RECORD_BYTES_FROM_HEADER "$BINARY_REPORT")" +PAYLOAD_BYTES="$(report_value PAYLOAD_BYTES "$BINARY_REPORT")" +PAYLOAD_REMAINDER_BYTES="$(report_value PAYLOAD_REMAINDER_BYTES "$BINARY_REPORT")" + +MARTE_EXIT_CODE="$(sed -n 's/^Exit code MARTe:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +TEE_EXIT_CODE="$(sed -n 's/^Exit code tee:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)" +if [ -z "$MARTE_EXIT_CODE" ]; then + MARTE_EXIT_CODE="NOT_AVAILABLE" +fi +if [ -z "$TEE_EXIT_CODE" ]; then + TEE_EXIT_CODE="NOT_AVAILABLE" +fi + +case "$MARTE_EXIT_CODE" in + 137) + TIMING_STATUS="NOT_VALID_EXIT_137_SIGKILL" + ;; + *) + if [ "$PRIORITY_FAIL_LINES" -ge 1 ]; then + TIMING_STATUS="NOT_VALID_PRIORITY_NOT_APPLIED" + else + TIMING_STATUS="UNASSESSED_DT_SEMANTICS" + fi + ;; +esac + +FUNCTIONAL_OK=0 +if [ "$START_LINES" -ge 1 ] && + [ "$APPLICATION_START_LINES" -ge 1 ] && + [ "$FILE_OPEN_LINES" -ge 1 ] && + [ "$STARTUP_ERROR_LINES" -eq 0 ] && + [ "$CONFIG_RC" -eq 0 ] && + [ "$CONFIG_STATUS" = "PASS" ] && + [ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] && + [ "$HASH_RC" -eq 0 ] && + [ "$TEE_EXIT_CODE" = "0" ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + FUNCTIONAL_OK=1 +fi + +if [ "$FUNCTIONAL_OK" -eq 1 ]; then + FUNCTIONAL_STATUS="PASS" +fi + +if [ "$CONFIG_STATUS" = "PASS" ] && [ "$SEMANTIC_BINARY_STATUS" = "PASS" ]; then + SEMANTIC_STATUS="PASS" +elif [ "$SEMANTIC_BINARY_STATUS" = "FAIL" ]; then + SEMANTIC_STATUS="FAIL" +else + SEMANTIC_STATUS="NOT_DEMONSTRATED" +fi + +printf "============================================================\n" +printf "VALIDAZIONE S03 - SENTINELLE SEMANTICHE\n" +printf "============================================================\n" +printf "Run directory: %s\n" "$RUN_DIR" +printf "Configurazione: %s\n" "$RUN_CONFIG" +printf "File binario: %s\n" "$OUTPUT_FILE" +printf "Manifest hash: %s\n" "$RUN_HASHES" +printf "Configurazione canonica: %s (rc=%s)\n" "${CONFIG_STATUS:-NOT_AVAILABLE}" "$CONFIG_RC" +printf "Verifica manifest hash: %s (rc=%s)\n" "$( [ "$HASH_RC" -eq 0 ] && printf PASS || printf FAIL )" "$HASH_RC" +printf "Parser binario: %s (rc=%s)\n" "${BINARY_STATUS:-NOT_AVAILABLE}" "$PARSER_RC" +printf "Primo errore parser: %s\n" "${FIRST_ERROR_CODE:-NOT_AVAILABLE}" +printf "Header osservato: %s byte\n" "${HEADER_BYTES:-NOT_AVAILABLE}" +printf "Record da header: %s byte\n" "${RECORD_BYTES:-NOT_AVAILABLE}" +printf "Payload: %s byte\n" "${PAYLOAD_BYTES:-NOT_AVAILABLE}" +printf "Residuo payload: %s byte\n" "${PAYLOAD_REMAINDER_BYTES:-NOT_AVAILABLE}" +printf "Campioni completi: %s\n" "${COMPLETE_SAMPLE_COUNT:-NOT_AVAILABLE}" +printf "Avvii State1: %s\n" "$START_LINES" +printf "Application starting: %s\n" "$APPLICATION_START_LINES" +printf "Aperture FileWriter: %s\n" "$FILE_OPEN_LINES" +printf "Exit code MARTe: %s\n" "$MARTE_EXIT_CODE" +printf "Exit code tee: %s\n" "$TEE_EXIT_CODE" +printf "Warning priorita' non applic.: %s\n" "$PRIORITY_FAIL_LINES" +printf "Warning non classificati: %s\n" "$UNCLASSIFIED_WARNING_LINES" +printf "FatalError non classificati: %s\n" "$UNCLASSIFIED_FATAL_LINES" +printf "OSError non classificati: %s\n" "$UNCLASSIFIED_OS_ERROR_LINES" +printf "Error non classificati: %s\n" "$ERROR_LINES" +printf "InitialisationError: %s\n" "$INITIALISATION_ERROR_LINES" + +printf "\n--- DETTAGLIO CONFIGURAZIONE ---\n" +cat -- "$CONFIG_REPORT" +printf "\n--- DETTAGLIO PARSER BINARIO ---\n" +cat -- "$BINARY_REPORT" +printf "\n--- VERIFICA HASH ---\n" +cat -- "$HASH_REPORT" + +printf "\n--- CONTRATTO S03 ---\n" +emit_contract + +if [ "$FUNCTIONAL_STATUS" != "PASS" ]; then + printf "VALIDATION_RESULT=FUNCTIONAL_NOT_DEMONSTRATED\n" + exit 20 +fi + +if [ "$SEMANTIC_STATUS" != "PASS" ]; then + printf "VALIDATION_RESULT=SEMANTIC_VALIDATION_FAILED\n" + exit 20 +fi + +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$FATAL_ERROR_LINES" -eq 0 ] && + [ "$OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "VALIDATION_RESULT=PASS_CLEAN_SHUTDOWN\n" + exit 0 +fi + +KNOWN_SHUTDOWN_TOTAL=$(( + DOUBLE_STOP_LINES + + ASYNC_EVENTSEM_CLOSE_LINES + + PTHREAD_MUTEX_DESTROY_LINES +)) +if [ "$SIGINT_LINES" -ge 1 ] && + [ "$STOP_OK_LINES" -ge 1 ] && + [ "$TERMINATED_LINES" -ge 1 ] && + [ "$KNOWN_SHUTDOWN_TOTAL" -ge 1 ] && + [ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] && + [ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] && + [ "$ERROR_LINES" -eq 0 ] && + [ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then + printf "VALIDATION_RESULT=PASS_KNOWN_SHUTDOWN_ANOMALY\n" + exit 10 +fi + +printf "VALIDATION_RESULT=PASS_UNCLASSIFIED_SHUTDOWN_ANOMALY\n" +exit 11