S03-H3 CLOSED baseline

This commit is contained in:
2026-08-27 11:12:22 +02:00
commit 4b5fbb6c23
30 changed files with 10130 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -u
if [ "$#" -ne 1 ]; then
printf "Uso: %s <run.log>\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
+722
View File
@@ -0,0 +1,722 @@
#!/usr/bin/env bash
set -u
if [ "$#" -ne 1 ]; then
printf "Uso: %s <directory-run|run.log>\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("<anonymous>"))
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("<I", data, 0)[0]
print(f"NUMBER_OF_SIGNALS={number_of_signals}")
if number_of_signals != expected_signals:
print("BINARY_STATUS=FAIL_SIGNAL_COUNT")
sys.exit(32)
offset = 4
names = []
type_hex_values = []
elements = []
for index in range(number_of_signals):
if offset + signal_header_bytes > 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("<I", data, offset)[0]
offset += 4
name = raw_name.split(b"\x00", 1)[0].decode("ascii", errors="replace")
names.append(name)
type_hex_values.append(type_bytes.hex())
elements.append(number_of_elements)
print(f"SIGNAL_{index}_TYPE_HEX={type_bytes.hex()}")
print(f"SIGNAL_{index}_NAME={name}")
print(f"SIGNAL_{index}_NUMBER_OF_ELEMENTS={number_of_elements}")
print("SIGNAL_NAMES=" + ",".join(names))
if names != expected_names:
print("BINARY_STATUS=FAIL_SIGNAL_NAMES")
sys.exit(34)
if any(value != expected_type_hex for value in type_hex_values):
print("BINARY_STATUS=FAIL_SIGNAL_TYPES")
sys.exit(35)
if any(value != 1 for value in elements):
print("BINARY_STATUS=FAIL_NUMBER_OF_ELEMENTS")
sys.exit(36)
header_bytes = offset
sample_bytes = expected_signals * 4
payload_bytes = len(data) - header_bytes
remainder_bytes = payload_bytes % sample_bytes
sample_count = payload_bytes // sample_bytes
print(f"HEADER_BYTES={header_bytes}")
print(f"SAMPLE_BYTES={sample_bytes}")
print(f"PAYLOAD_BYTES={payload_bytes}")
print(f"PAYLOAD_REMAINDER_BYTES={remainder_bytes}")
print(f"COMPLETE_SAMPLE_COUNT={sample_count}")
if remainder_bytes != 0:
print("BINARY_STATUS=FAIL_PAYLOAD_REMAINDER")
sys.exit(37)
records = [
struct.unpack_from("<III", data, header_bytes + index * sample_bytes)
for index in range(sample_count)
]
counters = [record[0] for record in records]
times = [record[1] for record in records]
cycle_times = [record[2] for record in records]
dt_values = [current - previous for previous, current in zip(times, times[1:])]
dt_histogram = Counter(dt_values)
distinct_dt_values = sorted(dt_histogram)
leading_zero_cycle_time_samples = 0
for value in cycle_times:
if value != 0:
break
leading_zero_cycle_time_samples += 1
print(f"DT_INTERVAL_COUNT={len(dt_values)}")
print("DT_DISTINCT_VALUES=" + (",".join(str(value) for value in distinct_dt_values) or "NOT_AVAILABLE"))
print(
"DT_HISTOGRAM="
+ (",".join(f"{value}:{dt_histogram[value]}" for value in distinct_dt_values) or "NOT_AVAILABLE")
)
print(f"DT_MIN={min(dt_values) if dt_values else 'NOT_AVAILABLE'}")
print(f"DT_MAX={max(dt_values) if dt_values else 'NOT_AVAILABLE'}")
print(f"LEADING_ZERO_CYCLE_TIME_SAMPLES={leading_zero_cycle_time_samples}")
try:
nominal_period = Decimal(nominal_period_raw)
except InvalidOperation:
nominal_period = None
if nominal_period is not None and dt_values:
anomaly_indices = [
index for index, value in enumerate(dt_values)
if Decimal(value) != nominal_period
]
print(f"DT_ANOMALY_COUNT={len(anomaly_indices)}")
print("DT_ANOMALY_INDICES=" + (",".join(str(index) for index in anomaly_indices) or "NONE"))
if anomaly_indices:
first_index = anomaly_indices[0]
print(f"DT_FIRST_ANOMALY_BETWEEN_RECORDS={first_index}_AND_{first_index + 1}")
print(f"DT_FIRST_ANOMALY_COUNTER_PAIR={counters[first_index]}_AND_{counters[first_index + 1]}")
print(f"DT_FIRST_ANOMALY_VALUE={dt_values[first_index]}")
else:
print("DT_FIRST_ANOMALY_BETWEEN_RECORDS=NONE")
print("DT_FIRST_ANOMALY_COUNTER_PAIR=NONE")
print("DT_FIRST_ANOMALY_VALUE=NONE")
else:
print("DT_ANOMALY_COUNT=NOT_AVAILABLE")
print("DT_ANOMALY_INDICES=NOT_AVAILABLE")
print("DT_FIRST_ANOMALY_BETWEEN_RECORDS=NOT_AVAILABLE")
print("DT_FIRST_ANOMALY_COUNTER_PAIR=NOT_AVAILABLE")
print("DT_FIRST_ANOMALY_VALUE=NOT_AVAILABLE")
if sample_count < 50:
print("BINARY_STATUS=FAIL_INSUFFICIENT_SAMPLES")
sys.exit(38)
counter_gap_count = sum(
1 for previous, current in zip(counters, counters[1:])
if current != previous + 1
)
time_decrease_count = sum(
1 for previous, current in zip(times, times[1:])
if current < previous
)
nonzero_cycle_times = [value for value in cycle_times if value > 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
+185
View File
@@ -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("<I", data, 0)[0]
if signal_count != len(EXPECTED_SIGNALS):
raise ExportError(
f"SIGNAL_COUNT_MISMATCH:EXPECTED_{len(EXPECTED_SIGNALS)}:OBSERVED_{signal_count}"
)
offset = 4
observed = []
for _ in range(signal_count):
type_hex = data[offset : offset + 2].hex()
offset += 2
name = decode_name(data[offset : offset + 32])
offset += 32
elements = struct.unpack_from("<I", data, offset)[0]
offset += 4
observed.append((name, type_hex, elements))
expected = tuple(
(name, UINT32_TYPE_HEX, elements) for name, elements in EXPECTED_SIGNALS
)
if tuple(observed) != expected:
raise ExportError(
"HEADER_CONTRACT_MISMATCH:"
f"EXPECTED_{expected!r}:OBSERVED_{tuple(observed)!r}"
)
if offset != EXPECTED_HEADER_BYTES:
raise ExportError(
f"HEADER_SIZE_MISMATCH:EXPECTED_{EXPECTED_HEADER_BYTES}:OBSERVED_{offset}"
)
payload = data[offset:]
remainder = len(payload) % EXPECTED_RECORD_BYTES
if remainder != 0:
raise ExportError(
f"PAYLOAD_ALIGNMENT_ERROR:REMAINDER_{remainder}:RECORD_{EXPECTED_RECORD_BYTES}"
)
if not payload:
raise ExportError("NO_COMPLETE_RECORDS")
records = [
struct.unpack_from("<8I", payload, record_offset)
for record_offset in range(0, len(payload), EXPECTED_RECORD_BYTES)
]
return data, records
def write_csv_atomic(output_path, records):
output_path.parent.mkdir(parents=True, exist_ok=True)
if output_path.exists() and not output_path.is_file():
raise ExportError(f"OUTPUT_NOT_REGULAR_FILE:{output_path}")
descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{output_path.name}.",
suffix=".tmp",
dir=output_path.parent,
text=True,
)
temporary_path = Path(temporary_name)
try:
with os.fdopen(descriptor, "w", encoding="utf-8", newline="") as stream:
writer = csv.writer(stream, lineterminator="\n")
writer.writerow(CSV_COLUMNS)
writer.writerows(records)
stream.flush()
os.fsync(stream.fileno())
if output_path.exists():
if temporary_path.read_bytes() == output_path.read_bytes():
temporary_path.unlink()
return "ALREADY_VALID"
raise ExportError(f"OUTPUT_EXISTS_BUT_DIFFERS:{output_path}")
os.chmod(temporary_path, 0o644)
os.replace(temporary_path, output_path)
return "CREATED"
finally:
if temporary_path.exists():
temporary_path.unlink()
def sha256_bytes(data):
return hashlib.sha256(data).hexdigest()
def main():
parser = argparse.ArgumentParser(
description=(
"Create a human-readable CSV from the S03 FileWriter binary. "
"The binary remains the primary evidence."
)
)
parser.add_argument("binary", type=Path)
parser.add_argument("csv_output", type=Path, nargs="?")
args = parser.parse_args()
output_path = args.csv_output
if output_path is None:
output_path = args.binary.with_suffix(".csv")
try:
binary_data, records = read_records(args.binary)
publication = write_csv_atomic(output_path, records)
csv_data = output_path.read_bytes()
except ExportError as exc:
print("CSV_EXPORT_STATUS=FAIL")
print(f"CSV_EXPORT_ERROR={exc}")
return 40
except OSError as exc:
print("CSV_EXPORT_STATUS=FAIL")
print(f"CSV_EXPORT_ERROR=OS_ERROR:{exc}")
return 41
print("PRIMARY_EVIDENCE=s03_output.bin")
print("DERIVED_ARTIFACT=s03_output.csv")
print(f"BINARY_PATH={args.binary}")
print(f"BINARY_SHA256={sha256_bytes(binary_data)}")
print(f"CSV_PATH={output_path}")
print(f"CSV_SHA256={sha256_bytes(csv_data)}")
print(f"CSV_RECORD_COUNT={len(records)}")
print(f"CSV_PUBLICATION={publication}")
print("CSV_EXPORT_STATUS=PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
+414
View File
@@ -0,0 +1,414 @@
#!/usr/bin/env bash
set -u
MINIMUM_SAMPLES="50"
EXPECTED_BASELINE_PARSER_SHA256="91c4d66770197e1986ed91eb6a4870e7da7df5a522ffdf4329b6d9250f238307"
EXPECTED_TEST_CONFIG_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e"
EXPECTED_DIAGNOSTIC_CONFIG_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PARSER="${S03_BASELINE_PARSER:-$PACKAGE_ROOT/baseline/parse_s03_binary.py}"
CONFIG_VERIFIER="$SCRIPT_DIR/verify_s03h1_config.py"
MODE="NOT_PROVIDED"
MODE_STATUS="NOT_DEMONSTRATED"
PROFILE_CONFIG_STATUS="NOT_DEMONSTRATED"
OBSERVABILITY_STATUS="NOT_DEMONSTRATED"
FUNCTIONAL_STATUS="NOT_DEMONSTRATED"
SEMANTIC_STATUS="NOT_DEMONSTRATED"
TIMING_STATUS="NOT_EVALUATED"
MEASUREMENT_ELIGIBILITY="NO"
emit_contract() {
printf "MODE=%s\n" "$MODE"
printf "MODE_STATUS=%s\n" "$MODE_STATUS"
printf "PROFILE_CONFIG_STATUS=%s\n" "$PROFILE_CONFIG_STATUS"
printf "OBSERVABILITY_STATUS=%s\n" "$OBSERVABILITY_STATUS"
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 2 ]; then
printf "Uso: %s TEST|DIAGNOSTIC <directory-run|run.log>\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
+267
View File
@@ -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())
+508
View File
@@ -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 <directory-run|run.log>\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
@@ -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())
+589
View File
@@ -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("<I", data, 0)[0]
report.details["OBSERVED_SIGNAL_COUNT"] = signal_count
if signal_count > 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("<I", data, offset)[0]
offset += 4
observed.append(
{
"name": name,
"type_hex": type_hex,
"elements": elements,
"type_offset": type_offset,
"name_offset": name_offset,
"elements_offset": elements_offset,
}
)
report.details[f"SIGNAL_{index}_NAME"] = name
report.details[f"SIGNAL_{index}_TYPE_HEX"] = type_hex
report.details[f"SIGNAL_{index}_NUMBER_OF_ELEMENTS"] = elements
report.status["HEADER_STATUS"] = "PASS"
report.details["OBSERVED_HEADER_BYTES"] = offset
header_bytes = offset
observed_names = [item["name"] for item in observed]
expected_names = [item[0] for item in EXPECTED_SIGNALS]
report.details["OBSERVED_SIGNAL_NAMES"] = ",".join(observed_names)
report.details["EXPECTED_SIGNAL_NAMES"] = ",".join(expected_names)
duplicates = sorted({name for name in observed_names if observed_names.count(name) > 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())
+347
View File
@@ -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 <directory-run|run.log>\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