From 18805d69ab0ea8b124c2b41a784f769eea9f8a28 Mon Sep 17 00:00:00 2001 From: Oliver Heilwagen Date: Thu, 4 Dec 2025 08:56:06 +0100 Subject: [PATCH 1/3] COM configuration using flatbuffers - initial version. --- MODULE.bazel | 1 + bazel/tools/BUILD | 6 + bazel/tools/generate_com_config.bzl | 80 +++ bazel/tools/json_to_flatbuffer_json.py | 124 +++++ bazel/tools/validate_flatbuffer_json.bzl | 128 +++++ score/mw/com/example/ipc_bridge/BUILD | 17 +- score/mw/com/example/ipc_bridge/README.md | 49 ++ score/mw/com/impl/BUILD | 6 + score/mw/com/impl/configuration/BUILD | 111 ++++ .../com/impl/configuration/ara_com_config.fbs | 143 ++++++ .../com/impl/configuration/config_loader.cpp | 57 +++ .../mw/com/impl/configuration/config_loader.h | 38 ++ .../example/ara_com_config_other.json | 4 +- .../flatbuffer_config_loader.cpp | 476 ++++++++++++++++++ .../configuration/flatbuffer_config_loader.h | 83 +++ score/mw/com/impl/configuration/test/BUILD | 89 ++++ score/mw/com/impl/runtime.cpp | 6 +- .../mw/com/impl/runtime_single_exec_test.cpp | 102 ++-- score/mw/com/runtime_configuration.cpp | 19 +- 19 files changed, 1495 insertions(+), 44 deletions(-) create mode 100644 bazel/tools/generate_com_config.bzl create mode 100644 bazel/tools/json_to_flatbuffer_json.py create mode 100644 bazel/tools/validate_flatbuffer_json.bzl create mode 100644 score/mw/com/example/ipc_bridge/README.md create mode 100644 score/mw/com/impl/configuration/ara_com_config.fbs create mode 100644 score/mw/com/impl/configuration/config_loader.cpp create mode 100644 score/mw/com/impl/configuration/config_loader.h create mode 100644 score/mw/com/impl/configuration/flatbuffer_config_loader.cpp create mode 100644 score/mw/com/impl/configuration/flatbuffer_config_loader.h diff --git a/MODULE.bazel b/MODULE.bazel index 847493c5..9c422f57 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -122,6 +122,7 @@ download_archive( ) bazel_dep(name = "nlohmann_json", version = "3.11.3") +bazel_dep(name = "flatbuffers", version = "25.2.10") bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "rules_doxygen", version = "2.5.0") bazel_dep(name = "score_baselibs", version = "0.2.0") diff --git a/bazel/tools/BUILD b/bazel/tools/BUILD index dd819952..4606f675 100644 --- a/bazel/tools/BUILD +++ b/bazel/tools/BUILD @@ -1,3 +1,9 @@ load(":json_schema_validator_test.bzl", "json_schema_validator_tests") json_schema_validator_tests(name = "json_validator_tests") + +py_binary( + name = "json_to_flatbuffer_json", + srcs = ["json_to_flatbuffer_json.py"], + visibility = ["//visibility:public"], +) diff --git a/bazel/tools/generate_com_config.bzl b/bazel/tools/generate_com_config.bzl new file mode 100644 index 00000000..af6ec0ee --- /dev/null +++ b/bazel/tools/generate_com_config.bzl @@ -0,0 +1,80 @@ +""" +Bazel rule to generate FlatBuffer binary configuration from JSON files. + +This rule converts existing communication JSON files to a FlatBuffer friendly format. +- Convert '-' to '_' in keys (required) +- Convert keys from camelCase to snake_case (avoids warnings) +""" + +def generate_com_config(name, json, visibility = None, **kwargs): + """ + Generate a FlatBuffer binary configuration file from a JSON input. + + This rule performs two steps: + 1. Converts the input JSON to FlatBuffer friendly format + 2. Compiles the converted JSON to FlatBuffer binary format + + The schema is hardcoded to the COM FlatBuffer schema at: + //score/mw/com/impl/configuration:ara_com_config.fbs + + The output .bin file will be generated in the same directory path as the + input JSON file to match the filegroup behavior for JSON files. + + Args: + name: Name of the rule (will be used as the target name) + json: Input JSON configuration file + visibility: Visibility for the generated target (optional) + + Outputs: + A .bin file in the same directory as the input JSON + Example: "example/ara_com_config.json" -> "example/ara_com_config.bin" + + Example: + generate_com_config( + name = "my_config.bin", + json = "example/config.json", + visibility = ["//visibility:public"], + ) + """ + + # Always use the COM FlatBuffer schema + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs" + + # Preserve the full path of the JSON file, just change extension + if json.endswith(".json"): + output_path = json[:-5] + ".bin" + else: + output_path = json + ".bin" + + # Intermediate converted JSON file (keep in same directory) + if json.endswith(".json"): + converted_json = json[:-5] + "_converted.json" + else: + converted_json = json + "_converted.json" + + # Extract just the filename for the flatc intermediate output + json_filename = json.split("/")[-1] + if json_filename.endswith(".json"): + flatc_output = json_filename[:-5] + "_converted.bin" + else: + flatc_output = json_filename + "_converted.bin" + + # Step 1: Convert JSON to FlatBuffer friendly format + native.genrule( + name = name + "_convert", + srcs = [json], + outs = [converted_json], + tools = ["//bazel/tools:json_to_flatbuffer_json"], + cmd = "$(location //bazel/tools:json_to_flatbuffer_json) $(SRCS) $@", + visibility = ["//visibility:private"], + ) + + # Step 2: Compile converted JSON to FlatBuffer binary + native.genrule( + name = name, + srcs = [converted_json, schema], + outs = [output_path], + tools = ["@flatbuffers//:flatc"], + cmd = "$(location @flatbuffers//:flatc) --binary -o $(@D) $(location " + schema + ") $(location " + converted_json + ") && mv $(@D)/" + flatc_output + " $@", + visibility = visibility, + ) diff --git a/bazel/tools/json_to_flatbuffer_json.py b/bazel/tools/json_to_flatbuffer_json.py new file mode 100644 index 00000000..702f63e6 --- /dev/null +++ b/bazel/tools/json_to_flatbuffer_json.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +Convert JSON with kebab-case keys to FlatBuffer-compatible JSON with snake_case keys. + +This script recursively processes JSON objects and converts all kebab-case keys +(e.g., "asil-level") to snake_case (e.g., "asil_level") to match FlatBuffer field +naming conventions. +""" + +import json +import sys +import re +from pathlib import Path +from typing import Any + + +def to_snake_case(text: str) -> str: + """ + Convert kebab-case or camelCase string to snake_case + + Args: + text: String in kebab-case or camelCase format + + Returns: + String in snake_case format + """ + result = text.replace('-', '_') + + # Preserve consecutive uppercase letters (like ID, QM, B) + # Insert underscore before uppercase letter that follows lowercase letter + result = re.sub(r'([a-z])([A-Z])', r'\1_\2', result) + # Insert underscore before uppercase letter that is followed by lowercase + result = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', result) + + return result.lower() + + +def convert_json(obj: Any) -> Any: + """ + Recursively converts all dictionary keys to snake_case and applies custom conversion + for specific string values which are implemented as enum in FlatBuffers. + + Args: + obj: JSON object (dict, list, or primitive type) + + Returns: + Converted JSON object + """ + if isinstance(obj, dict): + converted = {} + for key, value in obj.items(): + new_key = to_snake_case(key) + new_value = convert_enum_value(new_key, value) + converted[new_key] = convert_json(new_value) + return converted + elif isinstance(obj, list): + return [convert_json(item) for item in obj] + else: + return obj + + +def convert_enum_value(key: str, value: Any) -> Any: + """ + Convert communication JSON enum string values to FlatBuffer friendly format. + + Args: + key: The field name + value: The field value + + Returns: + Converted value + """ + if not isinstance(value, str): + return value + + # Binding type: "SOME/IP" -> "SOME_IP", "SHM" -> "SHM" + if key in ('binding',): + return value.replace('/', '_') + + # Permission checks: "file-permissions-on-empty" -> "FILE_PERMISSIONS_ON_EMPTY", "strict" -> "STRICT" + if key in ('permission_checks',): + return value.replace('-', '_').upper() + + return value + + +def main(): + """Main entry point for the script.""" + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + + input_file = Path(sys.argv[1]) + output_file = Path(sys.argv[2]) + + # Validate input file exists + if not input_file.exists(): + print(f"Error: Input file '{input_file}' does not exist", file=sys.stderr) + sys.exit(1) + + try: + with open(input_file, 'r', encoding='utf-8') as f: + data = json.load(f) + converted_data = convert_json(data) + + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(converted_data, f, indent=4, ensure_ascii=False) + f.write('\n') + + print(f"Successfully converted {input_file} -> {output_file}") + + except json.JSONDecodeError as e: + print(f"Error: Failed to parse JSON from '{input_file}': {e}", file=sys.stderr) + sys.exit(1) + except IOError as e: + print(f"Error: I/O error: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error: Unexpected error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/bazel/tools/validate_flatbuffer_json.bzl b/bazel/tools/validate_flatbuffer_json.bzl new file mode 100644 index 00000000..db071588 --- /dev/null +++ b/bazel/tools/validate_flatbuffer_json.bzl @@ -0,0 +1,128 @@ +""" +Validate a JSON file against a FlatBuffer schema. + +This rule converts existing communication JSON files to a FlatBuffer friendly format. +- Convert '-' to '_' in keys (required) +- Convert keys from camelCase to snake_case (avoids warnings) +""" + +def _validate_json_flatbuffer_test_impl(ctx): + """Implementation of the validation test rule.""" + + # Create a script that performs the validation + script = """#!/bin/bash +set -euo pipefail + +readonly expected_failure={expected_failure} +readonly converter='{converter}' +readonly flatc='{flatc}' +readonly schema='{schema}' +readonly json='{json}' +readonly tmpdir=$(mktemp -d) + +cleanup() {{ + rm -rf "$tmpdir" +}} +trap cleanup EXIT + +# Step 1: Convert JSON to FlatBuffer friendly format +"$converter" "$json" "$tmpdir/converted.json" + +# Step 2: Validate with flatc by compiling to binary (validates structure) +# Capture both stdout and stderr, suppress output unless there's an error +set +e +output=$("$flatc" --binary -o "$tmpdir" "$schema" "$tmpdir/converted.json" 2>&1) +ret=$? +set -e + +if test "$expected_failure" = true && test "$ret" -ne 0; then + echo "Expected validation to fail, and it did (exit code $ret)." + echo "" + echo "FlatBuffer validation errors:" + echo "$output" + echo "" + echo "Test PASSED." + exit 0 +elif test "$expected_failure" = false && test "$ret" -eq 0; then + echo "Expected validation to succeed, and it did. Test PASSED." + exit 0 +fi + +# Test failed - show what went wrong +if test "$ret" -ne 0; then + echo "FlatBuffer validation errors:" + echo "$output" +fi + +echo "" +echo "Test FAILED: FlatBuffer validation of '$json' against '$schema' exited with code $ret, but expected_failure={expected_failure}" +exit 1 +""".format( + expected_failure = "true" if ctx.attr.expected_failure else "false", + converter = ctx.executable._converter.short_path, + flatc = ctx.executable._flatc.short_path, + schema = ctx.file.schema.short_path, + json = ctx.file.json.short_path, + ) + + ctx.actions.write( + output = ctx.outputs.executable, + content = script, + is_executable = True, + ) + + runfiles = ctx.runfiles( + files = [ctx.file.json, ctx.file.schema], + ).merge(ctx.attr._converter[DefaultInfo].default_runfiles) + runfiles = runfiles.merge(ctx.attr._flatc[DefaultInfo].default_runfiles) + + return [DefaultInfo(runfiles = runfiles)] + +validate_json_flatbuffer_test = rule( + implementation = _validate_json_flatbuffer_test_impl, + attrs = { + "json": attr.label( + allow_single_file = [".json"], + mandatory = True, + doc = "Input JSON file to validate", + ), + "schema": attr.label( + allow_single_file = [".fbs"], + mandatory = True, + doc = "FlatBuffer schema file (.fbs) to validate against", + ), + "expected_failure": attr.bool( + default = False, + doc = "If True, test passes when validation fails (for testing invalid inputs)", + ), + "_converter": attr.label( + default = Label("//bazel/tools:json_to_flatbuffer_json"), + executable = True, + cfg = "exec", + ), + "_flatc": attr.label( + default = Label("@flatbuffers//:flatc"), + executable = True, + cfg = "exec", + ), + }, + test = True, + doc = """ + Validates a JSON file against a FlatBuffer schema. + This rule converts existing communication JSON files to a FlatBuffer friendly format. + + Example: + validate_json_flatbuffer_test( + name = "valid_config_test", + json = "valid_config.json", + schema = "ara_com_config.fbs", + ) + + validate_json_flatbuffer_test( + name = "invalid_config_test", + json = "invalid_config.json", + schema = "ara_com_config.fbs", + expected_failure = True, + ) + """, +) diff --git a/score/mw/com/example/ipc_bridge/BUILD b/score/mw/com/example/ipc_bridge/BUILD index 3979affd..b7514f58 100644 --- a/score/mw/com/example/ipc_bridge/BUILD +++ b/score/mw/com/example/ipc_bridge/BUILD @@ -13,6 +13,7 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:generate_com_config.bzl", "generate_com_config") cc_binary( name = "ipc_bridge_cpp", @@ -21,7 +22,10 @@ cc_binary( "assert_handler.h", "main.cpp", ], - data = ["etc/mw_com_config.json"], + data = [ + "etc/mw_com_config.json", + ":mw_com_config.bin", + ], features = COMPILER_WARNING_FEATURES, deps = [ ":sample_sender_receiver", @@ -79,7 +83,10 @@ rust_library( rust_binary( name = "ipc_bridge_rs", srcs = ["ipc_bridge.rs"], - data = ["etc/mw_com_config.json"], + data = [ + "etc/mw_com_config.json", + ":mw_com_config.bin", + ], features = ["link_std_cpp_lib"], rustc_flags = ["-Clink-arg=-lstdc++"], deps = [ @@ -103,3 +110,9 @@ cc_library( exports_files([ "etc/mw_com_config.json", ]) + +generate_com_config( + name = "mw_com_config.bin", + json = "etc/mw_com_config.json", + visibility = ["//visibility:public"], +) diff --git a/score/mw/com/example/ipc_bridge/README.md b/score/mw/com/example/ipc_bridge/README.md new file mode 100644 index 00000000..88d68753 --- /dev/null +++ b/score/mw/com/example/ipc_bridge/README.md @@ -0,0 +1,49 @@ +# IPC Bridge Example + +Demonstrates inter-process communication using shared memory with both C++ and Rust implementations. + +## Overview + +The example sends `MapApiLanesStamped` samples from a sender/skeleton process to a receiver/proxy process. Configuration can be provided in JSON or FlatBuffer binary format. + +## C++ Example + +**Receiver (Terminal 1):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_cpp -- --mode recv -n 5 -t 200 --service_instance_manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.json +``` + +**Sender (Terminal 2):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_cpp -- --mode send -n 5 -t 200 --service_instance_manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.json +``` + +## Rust Example + +**Receiver (Terminal 1):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_rs -- --mode recv --service-instance-manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.json +``` + +**Sender (Terminal 2):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_rs -- --mode send --service-instance-manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.json +``` + +## Using FlatBuffer Configuration + +Replace `.json` with `.bin` in the service instance manifest argument e.g.: + +## C++ Example + +**Receiver (Terminal 1):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_cpp -- --mode recv -n 5 -t 200 --service_instance_manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.bin +``` + +**Sender (Terminal 2):** +```bash +bazel run //score/mw/com/example/ipc_bridge:ipc_bridge_cpp -- --mode send -n 5 -t 200 --service_instance_manifest score/mw/com/example/ipc_bridge/etc/mw_com_config.bin +``` + +The configuration format is automatically detected by file extension. The `.bin` file is generated from the `.json` source during build diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 908c0aa2..9037605a 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -826,11 +826,17 @@ cc_test( timeout = "moderate", srcs = ["runtime_single_exec_test.cpp"], data = [ + "//score/mw/com/impl/configuration:ara_com_config.bin", "//score/mw/com/impl/configuration:ara_com_config.json", + "//score/mw/com/impl/configuration:ara_com_config_disabled_trace_config.bin", "//score/mw/com/impl/configuration:ara_com_config_disabled_trace_config.json", + "//score/mw/com/impl/configuration:ara_com_config_invalid_trace_config_path.bin", "//score/mw/com/impl/configuration:ara_com_config_invalid_trace_config_path.json", + "//score/mw/com/impl/configuration:ara_com_config_other.bin", "//score/mw/com/impl/configuration:ara_com_config_other.json", + "//score/mw/com/impl/configuration:ara_com_config_valid_trace_config.bin", "//score/mw/com/impl/configuration:ara_com_config_valid_trace_config.json", + "//score/mw/com/impl/configuration:ara_com_config_valid_trace_config_external.bin", "//score/mw/com/impl/tracing/configuration:comtrace_filter_config_small.json", ], features = [ diff --git a/score/mw/com/impl/configuration/BUILD b/score/mw/com/impl/configuration/BUILD index 5e0fae51..d256dfe8 100644 --- a/score/mw/com/impl/configuration/BUILD +++ b/score/mw/com/impl/configuration/BUILD @@ -12,9 +12,30 @@ # ******************************************************************************* load("@score_baselibs//:bazel/unit_tests.bzl", "cc_gtest_unit_test", "cc_unit_test_suites_for_host_and_qnx") +load("//bazel/tools:generate_com_config.bzl", "generate_com_config") load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") load("//score/mw:common_features.bzl", "COMPILER_WARNING_FEATURES") +exports_files( + [ + "ara_com_config.fbs", + ], + visibility = [ + "//bazel/tools:__pkg__", + "//score/mw/com/example/ipc_bridge:__pkg__", + "//score/mw/com/impl/configuration/test:__pkg__", + ], +) + +# Generate C++ header from FlatBuffer schema +genrule( + name = "generate_com_flatbuffer_headers", + srcs = ["ara_com_config.fbs"], + outs = ["ara_com_config_generated.h"], + cmd = "$(location @flatbuffers//:flatc) --cpp --scoped-enums -o $(@D) $(location ara_com_config.fbs)", + tools = ["@flatbuffers//:flatc"], +) + validate_json_schema_test( name = "validate_ara_com_config_schema", json = "example/ara_com_config.json", @@ -63,6 +84,59 @@ cc_library( ], ) +cc_library( + name = "flatbuffer_config_loader", + srcs = [ + "flatbuffer_config_loader.cpp", + # Include headers as srcs to make them available without depending on their implementations + "lola_event_id.h", + "lola_field_id.h", + "lola_service_id.h", + "lola_service_instance_deployment.h", + "lola_service_type_deployment.h", + "service_identifier_type.h", + "service_instance_deployment.h", + "service_type_deployment.h", + "service_version_type.h", + "someip_service_instance_deployment.h", + "binding_service_type_deployment.h", + "binding_service_type_deployment_impl.h", + ], + hdrs = [ + "flatbuffer_config_loader.h", + ":generate_com_flatbuffer_headers", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl/configuration:__subpackages__", + ], + deps = [ + ":configuration_local", + ":global_configuration", + ":tracing_configuration", + "//score/mw/com/impl:instance_specifier", + "@flatbuffers//:runtime_cc", + "@score_baselibs//score/mw/log", + ], +) + +cc_library( + name = "config_loader", + srcs = ["config_loader.cpp"], + hdrs = ["config_loader.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + ":config_parser", + ":configuration_local", + ":flatbuffer_config_loader", + ], +) + cc_library( name = "configuration_local", srcs = ["configuration.cpp"], @@ -413,6 +487,7 @@ cc_library( tags = ["FFI"], visibility = ["//score/mw/com/impl:__subpackages__"], deps = [ + ":config_loader", ":config_parser", ":configuration_error", ":configuration_local", @@ -733,3 +808,39 @@ filegroup( ], visibility = ["//visibility:public"], ) + +generate_com_config( + name = "ara_com_config.bin", + json = "example/ara_com_config.json", + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_other.bin", + json = "example/ara_com_config_other.json", + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_invalid_trace_config_path.bin", + json = "example/ara_com_config_invalid_trace_config_path.json", + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_disabled_trace_config.bin", + json = "example/ara_com_config_disabled_trace_config.json", + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_valid_trace_config.bin", + json = "example/ara_com_config_valid_trace_config.json", + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_valid_trace_config_external.bin", + json = "example/ara_com_config_valid_trace_config_external.json", + visibility = ["//visibility:public"], +) diff --git a/score/mw/com/impl/configuration/ara_com_config.fbs b/score/mw/com/impl/configuration/ara_com_config.fbs new file mode 100644 index 00000000..622ebc0b --- /dev/null +++ b/score/mw/com/impl/configuration/ara_com_config.fbs @@ -0,0 +1,143 @@ +namespace score.mw.com.impl.configuration; + +// Binding technology types +enum BindingType:uint8 { + SOME_IP = 0, // JSON: "SOME/IP" + SHM = 1 // JSON: "SHM" +} + +// ASIL level types +enum AsilLevel:uint8 { + QM = 0, // JSON: "QM" + B = 1 // JSON: "B" +} + +// Permission check strategy +enum PermissionCheckStrategy:uint8 { + FILE_PERMISSIONS_ON_EMPTY = 0, // JSON: "file-permissions-on-empty" + STRICT = 1 // JSON: "strict" +} + +// Shared memory size calculation mode +enum ShmSizeCalcMode:uint8 { + SIMULATION = 0 // JSON: "SIMULATION" +} + +struct Version { + major:uint32; + minor:uint32; +} + +table ComConfiguration { + service_types:[ServiceType] (required); + service_instances:[ServiceInstance] (required); + global:Global; + tracing:Tracing; +} + +table ServiceType { + service_type_name:string (required); + version:Version (required); + bindings:[BindingElement] (required); +} + +table BindingElement { + binding:BindingType; + service_id:uint16; + events:[Event]; + fields:[Field]; +} + +table Event { + event_name:string (required); + // JSON validation: eventId is 8 bit for LoLa, 15 bit for SOME/IP - using uint16 to accommodate both + event_id:uint16; +} + +table Field { + field_name:string (required); + // JSON validation: fieldId is 8 bit for LoLa, 15 bit for SOME/IP - using uint16 to accommodate both + field_id:uint16; + get:bool = false; + set:bool = false; +} + +// ServiceInstances definitions +table ServiceInstance { + instance_specifier:string (required); + service_type_name:string (required); + version:Version (required); + instances:[Instance] (required); +} + +table Instance { + // JSON validation: instanceId is optional 16 bit value (used for subscriber/R-port side) + instance_id:uint16; + asil_level:AsilLevel; + binding:BindingType; + shm_size:uint32; + control_asil_b_shm_size:uint32; + control_qm_shm_size:uint32; + permission_checks:PermissionCheckStrategy = FILE_PERMISSIONS_ON_EMPTY; + allowed_consumer:AllowedConsumer; + allowed_provider:AllowedProvider; + events:[InstanceEvent]; + fields:[InstanceField]; +} + +// User IDs for access control +table AllowedConsumer { + qm:[uint32]; + b:[uint32]; +} + +table AllowedProvider { + qm:[uint32]; + b:[uint32]; +} + +table InstanceEvent { + event_name:string (required); + max_samples:uint16 (deprecated); + // JSON validation: number_of_sample_slots must be between 1 and 65535 - not enforceable in FlatBuffers + number_of_sample_slots:uint16 = 1; + max_subscribers:uint16; + enforce_max_samples:bool = true; + number_of_ipc_tracing_slots:uint16 = 0; +} + +table InstanceField { + field_name:string (required); + // JSON validation: number_of_sample_slots must be between 1 and 65535 - not enforceable in FlatBuffers + number_of_sample_slots:uint16 = 1; + max_subscribers:uint16; + enforce_max_samples:bool = true; + number_of_ipc_tracing_slots:uint16 = 0; +} + +// Global definitions +table Global { + asil_level:AsilLevel = QM; + application_id:uint32; + queue_size:QueueSize; + shm_size_calc_mode:ShmSizeCalcMode = SIMULATION; +} + +table QueueSize { + qm_receiver:uint16 = 10; + b_receiver:uint16 = 10; + // JSON validation: max value is 100 - not enforceable in FlatBuffers + b_sender:uint16 = 20; +} + +// Tracing definitions +table Tracing { + // JSON validation: default value is true + enable:bool = true; + application_instance_id:string (required); + // JSON validation: default value is "./etc/mw_com_trace_filter.json" - not enforceable in FlatBuffers + trace_filter_config_path:string; +} + +root_type ComConfiguration; +file_identifier "SCOM"; diff --git a/score/mw/com/impl/configuration/config_loader.cpp b/score/mw/com/impl/configuration/config_loader.cpp new file mode 100644 index 00000000..f6b3c63b --- /dev/null +++ b/score/mw/com/impl/configuration/config_loader.cpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/configuration/config_loader.h" + +#include "score/mw/com/impl/configuration/config_parser.h" +#include "score/mw/com/impl/configuration/flatbuffer_config_loader.h" +#include "score/mw/log/logging.h" + +#include +#include + +namespace score::mw::com::impl::configuration +{ + +namespace +{ +bool ShouldUseFlatBuffer(const std::string_view path) noexcept +{ + // Auto-detect based on file extension + const std::string_view flatbuffer_extension = ".bin"; + if (path.length() > flatbuffer_extension.length()) + { + std::string_view extension = path.substr(path.length() - flatbuffer_extension.length()); + if (extension == flatbuffer_extension) + { + return true; + } + } + return false; +} +} // namespace + +Configuration Load(const std::string_view path) noexcept +{ + if (ShouldUseFlatBuffer(path)) + { + // Warning is used to see usage in the default ipc bridge test setup + score::mw::log::LogWarn("lola") << "Loading FlatBuffer configuration from: " << path; + return FlatBufferConfigLoader::CreateConfiguration(path); + } + + // Warning is used to see usage in the default ipc bridge test setup + score::mw::log::LogWarn("lola") << "Loading JSON configuration from: " << path; + return Parse(path); +} + +} // namespace score::mw::com::impl::configuration diff --git a/score/mw/com/impl/configuration/config_loader.h b/score/mw/com/impl/configuration/config_loader.h new file mode 100644 index 00000000..40c7ceb9 --- /dev/null +++ b/score/mw/com/impl/configuration/config_loader.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_CONFIGURATION_CONFIG_LOADER_H +#define SCORE_MW_COM_IMPL_CONFIGURATION_CONFIG_LOADER_H + +#include "score/mw/com/impl/configuration/configuration.h" + +#include + +namespace score::mw::com::impl::configuration +{ + +/** + * @brief Load configuration from a file path. + * + * This function automatically detects the file format (JSON or FlatBuffer binary) + * and uses the appropriate parser. The selection is based on the file extension: + * - ".json" for JSON format + * - ".bin" for FlatBuffer binary format + * + * @param path Path to the configuration file (.json or .bin) + * @return Configuration object populated from the file + */ +Configuration Load(const std::string_view path) noexcept; + +} // namespace score::mw::com::impl::configuration + +#endif // SCORE_MW_COM_IMPL_CONFIGURATION_CONFIG_LOADER_H diff --git a/score/mw/com/impl/configuration/example/ara_com_config_other.json b/score/mw/com/impl/configuration/example/ara_com_config_other.json index a9824689..a990e3cc 100644 --- a/score/mw/com/impl/configuration/example/ara_com_config_other.json +++ b/score/mw/com/impl/configuration/example/ara_com_config_other.json @@ -83,8 +83,8 @@ "global": { "asil-level": "B", "queue-size": { - "QM": 8, - "B": 5 + "QM-receiver": 8, + "B-receiver": 5 } }, "tracing": { diff --git a/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp new file mode 100644 index 00000000..69d0db09 --- /dev/null +++ b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp @@ -0,0 +1,476 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "score/mw/com/impl/configuration/flatbuffer_config_loader.h" + +#include "score/mw/com/impl/configuration/lola_event_id.h" +#include "score/mw/com/impl/configuration/lola_event_instance_deployment.h" +#include "score/mw/com/impl/configuration/lola_field_id.h" +#include "score/mw/com/impl/configuration/lola_field_instance_deployment.h" +#include "score/mw/com/impl/configuration/lola_service_id.h" +#include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" +#include "score/mw/com/impl/configuration/lola_service_instance_id.h" +#include "score/mw/com/impl/configuration/lola_service_type_deployment.h" +#include "score/mw/com/impl/configuration/quality_type.h" +#include "score/mw/com/impl/configuration/service_identifier_type.h" +#include "score/mw/com/impl/configuration/service_instance_deployment.h" +#include "score/mw/com/impl/configuration/service_type_deployment.h" +#include "score/mw/com/impl/configuration/service_version_type.h" +#include "score/mw/com/impl/configuration/shm_size_calc_mode.h" + +#include "score/mw/com/impl/instance_specifier.h" + +#include "score/mw/log/logging.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::impl::configuration +{ + +void FlatBufferConfigLoader::MmapDeleter::operator()(void* ptr) const noexcept +{ + if (ptr && size > 0) { munmap(ptr, size); } +} + +namespace +{ + +// Helper to convert FlatBuffer AsilLevel to QualityType +QualityType ConvertAsilLevel(AsilLevel asil_level) noexcept +{ + return (asil_level == AsilLevel::B) ? QualityType::kASIL_B : QualityType::kASIL_QM; +} + +// Helper to parse event deployments from FlatBuffer Instance +LolaServiceInstanceDeployment::EventInstanceMapping ParseEventDeployments(const Instance* instance) +{ + LolaServiceInstanceDeployment::EventInstanceMapping events; + + if (instance->events() != nullptr) + { + for (const auto* event : *instance->events()) + { + if (event != nullptr) + { + std::optional slots = event->number_of_sample_slots() > 0 + ? std::optional(event->number_of_sample_slots()) : std::nullopt; + std::optional max_subs = event->max_subscribers() > 0 + ? std::optional(static_cast(event->max_subscribers())) : std::nullopt; + + LolaEventInstanceDeployment event_deploy( + slots, max_subs, std::nullopt, event->enforce_max_samples(), + static_cast(event->number_of_ipc_tracing_slots())); + // event_name is obliged to contain a value (marked as required) + events.emplace(event->event_name()->str(), std::move(event_deploy)); + } + } + } + + return events; +} + +// Helper to parse field deployments from FlatBuffer Instance +LolaServiceInstanceDeployment::FieldInstanceMapping ParseFieldDeployments(const Instance* instance) +{ + LolaServiceInstanceDeployment::FieldInstanceMapping fields; + + if (instance->fields() != nullptr) + { + for (const auto* field : *instance->fields()) + { + if (field != nullptr) + { + std::optional slots = field->number_of_sample_slots() > 0 + ? std::optional(field->number_of_sample_slots()) : std::nullopt; + std::optional max_subs = field->max_subscribers() > 0 + ? std::optional(static_cast(field->max_subscribers())) : std::nullopt; + + LolaFieldInstanceDeployment field_deploy( + slots, max_subs, std::nullopt, field->enforce_max_samples(), + static_cast(field->number_of_ipc_tracing_slots())); + // field_name is obliged to contain a value (marked as required) + fields.emplace(field->field_name()->str(), std::move(field_deploy)); + } + } + } + + return fields; +} + +// Helper to parse permission mappings from FlatBuffer Permissions (AllowedConsumer or AllowedProvider) +template +std::unordered_map> ParsePermissions(const PermissionsType* permissions) +{ + std::unordered_map> permission_map; + + if (permissions != nullptr) + { + if (permissions->qm() != nullptr) + { + std::vector qm_users(permissions->qm()->begin(), permissions->qm()->end()); + permission_map.emplace(QualityType::kASIL_QM, std::move(qm_users)); + } + if (permissions->b() != nullptr) + { + std::vector b_users(permissions->b()->begin(), permissions->b()->end()); + permission_map.emplace(QualityType::kASIL_B, std::move(b_users)); + } + } + + return permission_map; +} + +// Helper to set optional memory sizes on deployment +void SetMemorySizes(LolaServiceInstanceDeployment& deployment, const Instance* instance) +{ + if (instance->shm_size() > 0) + { + deployment.shared_memory_size_ = static_cast(instance->shm_size()); + } + if (instance->control_asil_b_shm_size() > 0) + { + deployment.control_asil_b_memory_size_ = static_cast(instance->control_asil_b_shm_size()); + } + if (instance->control_qm_shm_size() > 0) + { + deployment.control_qm_memory_size_ = static_cast(instance->control_qm_shm_size()); + } +} + +// Helper to create LolaServiceInstanceDeployment from FlatBuffer Instance +score::mw::com::impl::LolaServiceInstanceDeployment CreateLolaServiceInstanceDeployment(const Instance* instance) +{ + score::cpp::optional instance_id; + if (instance->instance_id() != 0) + { + instance_id = LolaServiceInstanceId{instance->instance_id()}; + } + + auto events = ParseEventDeployments(instance); + auto fields = ParseFieldDeployments(instance); + auto allowed_consumer = ParsePermissions(instance->allowed_consumer()); + auto allowed_provider = ParsePermissions(instance->allowed_provider()); + + bool strict_permission = (instance->permission_checks() == PermissionCheckStrategy::STRICT); + + LolaServiceInstanceDeployment deployment(instance_id, std::move(events), std::move(fields), + strict_permission, std::move(allowed_consumer), + std::move(allowed_provider)); + + SetMemorySizes(deployment, instance); + + return deployment; +} + +} // namespace + +Configuration FlatBufferConfigLoader::CreateConfiguration(std::string_view path) noexcept +{ + FlatBufferConfigLoader loader(path); + + return score::mw::com::impl::Configuration( + loader.CreateServiceTypes(), + loader.CreateServiceInstances(), + loader.CreateGlobalConfiguration(), + loader.CreateTracingConfiguration() + ); +} + +FlatBufferConfigLoader::FlatBufferConfigLoader(std::string_view path) + : fd_(-1), com_config_(nullptr) +{ + LoadBuffer(path); +} + +FlatBufferConfigLoader::~FlatBufferConfigLoader() +{ + if (fd_ != -1) + { + close(fd_); + } +} + +void FlatBufferConfigLoader::LoadBuffer(std::string_view path) +{ + fd_ = open(path.data(), O_RDONLY); + if (fd_ == -1) + { + ::score::mw::log::LogFatal("lola") << "Failed to open FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + std::terminate(); + } + + // Get file size + struct stat st; + if (fstat(fd_, &st) == -1) + { + ::score::mw::log::LogFatal("lola") << "Failed to stat FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + std::terminate(); + } + + if (st.st_size == 0) + { + ::score::mw::log::LogFatal("lola") << "FlatBuffer file is empty: " << path; + std::terminate(); + } + + std::size_t mapped_size = static_cast(st.st_size); + + // Map file into memory + void* ptr = mmap(nullptr, mapped_size, PROT_READ, MAP_PRIVATE, fd_, 0); + if (ptr == MAP_FAILED) + { + ::score::mw::log::LogFatal("lola") << "Failed to mmap FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + std::terminate(); + } + mapped_ptr_ = std::unique_ptr(ptr, MmapDeleter{mapped_size}); + + // Verify the FlatBuffer using the mapped region + const uint8_t* data = reinterpret_cast(mapped_ptr_.get()); + flatbuffers::Verifier verifier(data, mapped_size); + if (!VerifyComConfigurationBuffer(verifier)) + { + ::score::mw::log::LogFatal("lola") << "FlatBuffer verification failed for: " << path; + std::terminate(); + } + + // Get the root ComConfiguration (points into the mmap'd region) + com_config_ = GetComConfiguration(data); + if (com_config_ == nullptr) + { + ::score::mw::log::LogFatal("lola") << "Failed to get ComConfiguration from buffer: " << path; + std::terminate(); + } +} + +Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes() const noexcept +{ + Configuration::ServiceTypeDeployments service_type_deployments; + + // service_types is obliged to contain a value (marked as required) + for (const auto* service_type : *com_config_->service_types()) + { + const auto* version = service_type->version(); + if (version == nullptr) + { + ::score::mw::log::LogFatal("lola") << "Service type missing version. Terminating"; + std::terminate(); + } + + // service_type_name is obliged to contain a value (marked as required) + auto service_identifier = make_ServiceIdentifierType( + service_type->service_type_name()->str(), version->major(), version->minor()); + + ServiceTypeDeployment::BindingInformation binding_info = score::cpp::blank{}; + + // bindings is obliged to contain a value (marked as required) + // Process all bindings - currently only SHM is supported, use the first SHM binding found + for (const auto* binding : *service_type->bindings()) + { + if (binding->binding() == BindingType::SHM) + { + // Create LolaServiceTypeDeployment (SHM binding) + LolaServiceId service_id{binding->service_id()}; + LolaServiceTypeDeployment::EventIdMapping events; + LolaServiceTypeDeployment::FieldIdMapping fields; + + if (binding->events() != nullptr) + { + for (const auto* event : *binding->events()) + { + // event_name is obliged to contain a value (marked as required) + events.emplace(event->event_name()->str(), LolaEventId{static_cast(event->event_id())}); + } + } + + if (binding->fields() != nullptr) + { + for (const auto* field : *binding->fields()) + { + // field_name is obliged to contain a value (marked as required) + fields.emplace(field->field_name()->str(), LolaFieldId{static_cast(field->field_id())}); + } + } + + binding_info = LolaServiceTypeDeployment{service_id, std::move(events), std::move(fields)}; + break; // Use first SHM binding + } + else if (binding->binding() == BindingType::SOME_IP) + { + // Skip SOME/IP - not supported yet + continue; + } + else + { + ::score::mw::log::LogFatal("lola") << "Unknown binding type provided. Required argument."; + std::terminate(); + } + } + + ServiceTypeDeployment service_deployment{binding_info}; + + const auto inserted = service_type_deployments.emplace( + std::piecewise_construct, + std::forward_as_tuple(service_identifier), + std::forward_as_tuple(std::move(service_deployment))); + + if (!inserted.second) + { + ::score::mw::log::LogFatal("lola") << "Service Type was deployed twice in FlatBuffer"; + std::terminate(); + } + } + + return service_type_deployments; +} + +Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceInstances() const noexcept +{ + Configuration::ServiceInstanceDeployments service_instances; + + // service_instances is obliged to contain a value (marked as required) + for (const auto* service_instance : *com_config_->service_instances()) + { + // service_instance and instance_specifier are obliged to contain a value (marked as required) + auto instance_spec_result = InstanceSpecifier::Create(service_instance->instance_specifier()->str()); + if (!instance_spec_result) + { + ::score::mw::log::LogFatal("lola") << "Invalid instance specifier in FlatBuffer. Terminating"; + std::terminate(); + } + InstanceSpecifier instance_spec = std::move(instance_spec_result.value()); + + // version and service_type_name are obliged to contain a value (marked as required) + const auto* version = service_instance->version(); + auto service_identifier = make_ServiceIdentifierType( + service_instance->service_type_name()->str(), version->major(), version->minor()); + + if (service_instance->instances() == nullptr || service_instance->instances()->size() == 0) + { + ::score::mw::log::LogFatal("lola") << "Service instance missing deployment instances. Terminating"; + std::terminate(); + } + + // Find the single SHM instance - multi-binding not supported + const Instance* shm_instance = nullptr; + for (const auto* instance : *service_instance->instances()) + { + if (instance->binding() == BindingType::SHM) + { + if (shm_instance != nullptr) + { + ::score::mw::log::LogFatal("lola") << "Multiple SHM bindings for " << service_identifier.ToString() + << ". Multi-Binding not supported"; + std::terminate(); + } + shm_instance = instance; + } + else if (instance->binding() == BindingType::SOME_IP) + { + ::score::mw::log::LogFatal("lola") << "Provided SOME/IP binding, which cannot be parsed."; + std::terminate(); + } + else + { + ::score::mw::log::LogFatal("lola") << "Unknown binding type provided. Required argument."; + std::terminate(); + } + } + + if (shm_instance == nullptr) + { + ::score::mw::log::LogFatal("lola") << "No SHM binding found for " << service_identifier.ToString(); + std::terminate(); + } + QualityType asil_level = ConvertAsilLevel(shm_instance->asil_level()); + ServiceInstanceDeployment::BindingInformation binding_info = CreateLolaServiceInstanceDeployment(shm_instance); + ServiceInstanceDeployment deployment(service_identifier, std::move(binding_info), asil_level, instance_spec); + service_instances.emplace(std::move(instance_spec), std::move(deployment)); + } + + return service_instances; +} + +GlobalConfiguration FlatBufferConfigLoader::CreateGlobalConfiguration() const noexcept +{ + GlobalConfiguration global_config; + + if (com_config_->global() != nullptr) + { + const auto* global = com_config_->global(); + + // Set ASIL level + QualityType asil_level = ConvertAsilLevel(global->asil_level()); + global_config.SetProcessAsilLevel(asil_level); + + // Set application ID if present + if (global->application_id() != 0) + { + global_config.SetApplicationId(global->application_id()); + } + + // Set queue sizes + if (global->queue_size() != nullptr) + { + const auto* queue_size = global->queue_size(); + global_config.SetReceiverMessageQueueSize(QualityType::kASIL_QM, + static_cast(queue_size->qm_receiver())); + global_config.SetReceiverMessageQueueSize(QualityType::kASIL_B, + static_cast(queue_size->b_receiver())); + global_config.SetSenderMessageQueueSize(static_cast(queue_size->b_sender())); + } + + // Set SHM size calculation mode + ShmSizeCalculationMode shm_mode = ShmSizeCalculationMode::kSimulation; + global_config.SetShmSizeCalcMode(shm_mode); + } + + return global_config; +} + +TracingConfiguration FlatBufferConfigLoader::CreateTracingConfiguration() const noexcept +{ + TracingConfiguration tracing_config; + + if (com_config_->tracing() != nullptr) + { + const auto* tracing = com_config_->tracing(); + + tracing_config.SetTracingEnabled(tracing->enable()); + + // application_instance_id is obliged to contain a value (marked as required) + tracing_config.SetApplicationInstanceID(tracing->application_instance_id()->str()); + + if (tracing->trace_filter_config_path() != nullptr) + { + tracing_config.SetTracingTraceFilterConfigPath(tracing->trace_filter_config_path()->str()); + } + else + { + // Default path if not provided + tracing_config.SetTracingTraceFilterConfigPath("./etc/mw_com_trace_filter.json"); + } + } + + return tracing_config; +} + +} // namespace score::mw::com::impl::configuration diff --git a/score/mw/com/impl/configuration/flatbuffer_config_loader.h b/score/mw/com/impl/configuration/flatbuffer_config_loader.h new file mode 100644 index 00000000..2b3d92c7 --- /dev/null +++ b/score/mw/com/impl/configuration/flatbuffer_config_loader.h @@ -0,0 +1,83 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#ifndef SCORE_MW_COM_IMPL_CONFIGURATION_FLATBUFFER_CONFIG_LOADER_H +#define SCORE_MW_COM_IMPL_CONFIGURATION_FLATBUFFER_CONFIG_LOADER_H + +#include "score/mw/com/impl/configuration/configuration.h" +#include "score/mw/com/impl/configuration/global_configuration.h" +#include "score/mw/com/impl/configuration/tracing_configuration.h" +#include "score/mw/com/impl/configuration/ara_com_config_generated.h" + +#include +#include +#include +#include + +#include +namespace score::mw::com::impl::configuration +{ + +/// FlatBuffer configuration loader that reads and converts FlatBuffer binary files +/// to Configuration objects. Manages the lifetime of the FlatBuffer data. +class FlatBufferConfigLoader +{ + public: + /// Load and convert a FlatBuffer binary configuration file to a Configuration object + /// @param path Path to the FlatBuffer binary file (.bin) + /// @return Configuration object constructed from the FlatBuffer data + /// @note Terminates process on file read errors or invalid FlatBuffer data + static Configuration CreateConfiguration(std::string_view path) noexcept; + + private: + /// Private constructor - use CreateConfiguration() factory method + explicit FlatBufferConfigLoader(std::string_view path); + + /// Destructor - closes file descriptor and unmaps memory + ~FlatBufferConfigLoader(); + + // Non-copyable + FlatBufferConfigLoader(const FlatBufferConfigLoader&) = delete; + FlatBufferConfigLoader& operator=(const FlatBufferConfigLoader&) = delete; + + /// Load and verify the FlatBuffer binary file + void LoadBuffer(std::string_view path); + + /// Create service type deployments from FlatBuffer data + Configuration::ServiceTypeDeployments CreateServiceTypes() const noexcept; + + /// Create service instance deployments from FlatBuffer data + Configuration::ServiceInstanceDeployments CreateServiceInstances() const noexcept; + + /// Create global configuration from FlatBuffer data + GlobalConfiguration CreateGlobalConfiguration() const noexcept; + + /// Create tracing configuration from FlatBuffer data + TracingConfiguration CreateTracingConfiguration() const noexcept; + + /// Deleter for mmap'd memory + struct MmapDeleter { std::size_t size; void operator()(void* ptr) const noexcept; }; + + // File descriptor - closed in destructor + int fd_ = -1; + + // RAII mmap'd memory - automatically unmapped + std::unique_ptr mapped_ptr_; + + // Non-owning view into mmap'd region + const ComConfiguration* com_config_; +}; + +} // namespace score::mw::com::impl::configuration + +#endif // SCORE_MW_COM_IMPL_CONFIGURATION_FLATBUFFER_CONFIG_LOADER_H diff --git a/score/mw/com/impl/configuration/test/BUILD b/score/mw/com/impl/configuration/test/BUILD index 2a1ada52..9abab42e 100644 --- a/score/mw/com/impl/configuration/test/BUILD +++ b/score/mw/com/impl/configuration/test/BUILD @@ -12,6 +12,7 @@ # ******************************************************************************* load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") +load("//bazel/tools:validate_flatbuffer_json.bzl", "validate_json_flatbuffer_test") load("//score/mw:common_features.bzl", "COMPILER_WARNING_FEATURES") cc_library( @@ -131,3 +132,91 @@ validate_json_schema_test( schema = "//score/mw/com/impl/configuration:ara_com_config_schema", tags = ["lint"], ) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_consumer_name", + expected_failure = True, + json = "invalid_json_consumer_name.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_provider_name", + expected_failure = True, + json = "invalid_json_provider_name.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_permission_check_value", + expected_failure = True, + json = "invalid_json_unknown_permission_check_value.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_service_type", + expected_failure = True, + json = "invalid_json_incomplete_service_type.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_binding", + expected_failure = True, + json = "invalid_json_incomplete_binding.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_event", + expected_failure = True, + json = "invalid_json_incomplete_event.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_field", + expected_failure = True, + json = "invalid_json_incomplete_field.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_service_instance", + expected_failure = True, + json = "invalid_json_incomplete_service_instance.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_instance", + expected_failure = True, + json = "invalid_json_incomplete_instance.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_instance_event", + expected_failure = True, + json = "invalid_json_incomplete_instance_event.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) + +validate_json_flatbuffer_test( + name = "flatbuffer_invalid_json_incomplete_instance_field", + expected_failure = True, + json = "invalid_json_incomplete_instance_field.json", + schema = "//score/mw/com/impl/configuration:ara_com_config.fbs", + tags = ["lint"], +) diff --git a/score/mw/com/impl/runtime.cpp b/score/mw/com/impl/runtime.cpp index 09ca8273..3a6c284b 100644 --- a/score/mw/com/impl/runtime.cpp +++ b/score/mw/com/impl/runtime.cpp @@ -12,7 +12,7 @@ ********************************************************************************/ #include "score/mw/com/impl/runtime.h" -#include "score/mw/com/impl/configuration/config_parser.h" +#include "score/mw/com/impl/configuration/config_loader.h" #include "score/mw/com/impl/instance_specifier.h" #include "score/mw/com/impl/plumbing/binding_runtime_factory.h" #include "score/mw/com/impl/tracing/configuration/tracing_filter_config_parser.h" @@ -119,7 +119,7 @@ void Runtime::Initialize(const runtime::RuntimeConfiguration& runtime_configurat warn_double_init(); } - auto config = configuration::Parse(runtime_configuration.GetConfigurationPath().Native()); + auto config = configuration::Load(runtime_configuration.GetConfigurationPath().Native()); StoreConfiguration(std::move(config)); } @@ -153,7 +153,7 @@ Runtime& Runtime::getInstanceInternal() noexcept if (!initialization_config_.has_value()) { runtime::RuntimeConfiguration runtime_configuration{}; - auto configuration = configuration::Parse(runtime_configuration.GetConfigurationPath().Native()); + auto configuration = configuration::Load(runtime_configuration.GetConfigurationPath().Native()); auto tracing_config = ParseTraceConfig(configuration); return std::make_pair(std::move(configuration), std::move(tracing_config)); } diff --git a/score/mw/com/impl/runtime_single_exec_test.cpp b/score/mw/com/impl/runtime_single_exec_test.cpp index d009b53d..458cc56d 100644 --- a/score/mw/com/impl/runtime_single_exec_test.cpp +++ b/score/mw/com/impl/runtime_single_exec_test.cpp @@ -44,9 +44,14 @@ namespace // This is required to support resets of a Meyer-Singleton. // This fixture should be used sparingly, since it has quite a big runtime penalty. // Every test must be executed within TestInSeparateProcess()! -class SingleTestPerProcessFixture : public ::testing::Test +class SingleTestPerProcessFixture : public ::testing::TestWithParam { public: + void SetUp() override + { + config_file_extension_ = GetParam(); + } + void TearDown() override { // Safeguard against accidentially not using TestInSeparateProcess() @@ -91,11 +96,20 @@ class SingleTestPerProcessFixture : public ::testing::Test protected: InstanceSpecifier tire_pressure_port_{InstanceSpecifier::Create(std::string{"abc/abc/TirePressurePort"}).value()}; - std::string config_with_tire_pressure_port_{get_path("ara_com_config.json")}; InstanceSpecifier tire_pressure_port_other_{ InstanceSpecifier::Create(std::string{"abc/abc/TirePressurePortOther"}).value()}; - std::string config_with_tire_pressure_port_other_{get_path("ara_com_config_other.json")}; bool tested_in_separate_process_{false}; + std::string config_file_extension_; + + std::string config_with_tire_pressure_port() const + { + return get_path("ara_com_config" + config_file_extension_); + } + + std::string config_with_tire_pressure_port_other() const + { + return get_path("ara_com_config_other" + config_file_extension_); + } private: static void PrintTestPartResults() @@ -139,13 +153,17 @@ void WithConfigAtDefaultPath(const std::string& source_path) filesystem::Path dir{"etc"}; score::cpp::ignore = filesystem.RemoveAll(dir); ASSERT_TRUE(filesystem.CreateDirectories(dir).has_value()); - filesystem::Path target{"etc/mw_com_config.json"}; + + // Determine target filename based on source file extension + const bool is_flatbuffer = source_path.find(".bin") != std::string::npos; + filesystem::Path target = is_flatbuffer ? filesystem::Path{"etc/mw_com_config.bin"} + : filesystem::Path{"etc/mw_com_config.json"}; ASSERT_TRUE(filesystem.CopyFile(filesystem::Path{source_path}, target).has_value()); } using RuntimeInitializationTest = SingleTestPerProcessFixture; -TEST_F(RuntimeInitializationTest, InitializationLoadsCorrectConfiguration) +TEST_P(RuntimeInitializationTest, InitializationLoadsCorrectConfiguration) { RecordProperty("Verifies", "SCR-6221480, SCR-21781439"); RecordProperty("Description", "InstanceSpecifier resolution can not retrieve wrong InstanceIdentifier."); @@ -155,7 +173,7 @@ TEST_F(RuntimeInitializationTest, InitializationLoadsCorrectConfiguration) TestInSeparateProcess([this]() { // Given a RuntimeConfiguration - const auto runtime_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; + const auto runtime_configuration = runtime::RuntimeConfiguration{config_with_tire_pressure_port()}; // When initializing the runtime with the call args Runtime::Initialize(runtime_configuration); @@ -166,12 +184,12 @@ TEST_F(RuntimeInitializationTest, InitializationLoadsCorrectConfiguration) }); } -TEST_F(RuntimeInitializationTest, SecondInitializationUpdatesRuntimeIfRuntimeHasNotYetBeenUsed) +TEST_P(RuntimeInitializationTest, SecondInitializationUpdatesRuntimeIfRuntimeHasNotYetBeenUsed) { TestInSeparateProcess([this]() { // Given two RuntimeConfigurations containing different configuration file paths - const auto runtime_configuration_1 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; - const auto runtime_configuration_2 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other_}; + const auto runtime_configuration_1 = runtime::RuntimeConfiguration{config_with_tire_pressure_port()}; + const auto runtime_configuration_2 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other()}; // and that the runtime has been initialised with the first configuration Runtime::Initialize(runtime_configuration_1); @@ -187,12 +205,12 @@ TEST_F(RuntimeInitializationTest, SecondInitializationUpdatesRuntimeIfRuntimeHas }); } -TEST_F(RuntimeInitializationTest, SecondInitializationDoesNotUpdateRuntimeIfRuntimeHasAlreadyBeenUsed) +TEST_P(RuntimeInitializationTest, SecondInitializationDoesNotUpdateRuntimeIfRuntimeHasAlreadyBeenUsed) { TestInSeparateProcess([this]() { // Given two RuntimeConfigurations containing different configuration file paths - const auto runtime_configuration_1 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_}; - const auto runtime_configuration_2 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other_}; + const auto runtime_configuration_1 = runtime::RuntimeConfiguration{config_with_tire_pressure_port()}; + const auto runtime_configuration_2 = runtime::RuntimeConfiguration{config_with_tire_pressure_port_other()}; // and that the runtime has been initialised with the first configuration Runtime::Initialize(runtime_configuration_1); @@ -211,11 +229,11 @@ TEST_F(RuntimeInitializationTest, SecondInitializationDoesNotUpdateRuntimeIfRunt }); } -TEST_F(RuntimeInitializationTest, ImplicitInitializationLoadsCorrectConfiguration) +TEST_P(RuntimeInitializationTest, ImplicitInitializationLoadsCorrectConfiguration) { TestInSeparateProcess([this]() { // Given a configuration with one instance specifier provided at default location - WithConfigAtDefaultPath(config_with_tire_pressure_port_); + WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When implicitly default-initializing the runtime auto& runtime = Runtime::getInstance(); @@ -228,7 +246,7 @@ TEST_F(RuntimeInitializationTest, ImplicitInitializationLoadsCorrectConfiguratio using RuntimeTest = SingleTestPerProcessFixture; -TEST_F(RuntimeTest, CannotResolveUnknownInstanceSpecifier) +TEST_P(RuntimeTest, CannotResolveUnknownInstanceSpecifier) { RecordProperty("Verifies", "SCR-6221480, SCR-21781439"); RecordProperty("Description", "InstanceSpecifier resolution can not retrieve wrong InstanceIdentifier."); @@ -238,7 +256,7 @@ TEST_F(RuntimeTest, CannotResolveUnknownInstanceSpecifier) TestInSeparateProcess([this]() { // Given aconfiguration without the tire_pressure_port_other_ instance specifier - WithConfigAtDefaultPath(config_with_tire_pressure_port_); + WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When resolving this instance specifier auto identifiers = Runtime::getInstance().resolve(tire_pressure_port_other_); @@ -248,11 +266,11 @@ TEST_F(RuntimeTest, CannotResolveUnknownInstanceSpecifier) }); } -TEST_F(RuntimeTest, CanRetrieveConfiguredBinding) +TEST_P(RuntimeTest, CanRetrieveConfiguredBinding) { TestInSeparateProcess([this]() { // Given a configuration, which contains lola bindings - WithConfigAtDefaultPath(config_with_tire_pressure_port_); + WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When retrieving the lola binding const auto* unit = Runtime::getInstance().GetBindingRuntime(BindingType::kLoLa); @@ -262,11 +280,11 @@ TEST_F(RuntimeTest, CanRetrieveConfiguredBinding) }); } -TEST_F(RuntimeTest, CannotRetrieveUnconfiguredBinding) +TEST_P(RuntimeTest, CannotRetrieveUnconfiguredBinding) { TestInSeparateProcess([this]() { // Given a configuration, which does not contain Fake bindings - WithConfigAtDefaultPath(config_with_tire_pressure_port_); + WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When retrieving the fake binding const auto* unit = Runtime::getInstance().GetBindingRuntime(BindingType::kFake); @@ -276,7 +294,7 @@ TEST_F(RuntimeTest, CannotRetrieveUnconfiguredBinding) }); } -TEST_F(RuntimeTest, HandleTypeContainsEventsSpecifiedInConfiguration) +TEST_P(RuntimeTest, HandleTypeContainsEventsSpecifiedInConfiguration) { RecordProperty("Verifies", "SCR-15600146"); RecordProperty("Description", @@ -288,7 +306,7 @@ TEST_F(RuntimeTest, HandleTypeContainsEventsSpecifiedInConfiguration) TestInSeparateProcess([this]() { // Given a configuration with an instance specifier - WithConfigAtDefaultPath(config_with_tire_pressure_port_); + WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When creating a handle from the InstanceSpecifier auto identifiers = Runtime::getInstance().resolve(tire_pressure_port_); @@ -302,7 +320,7 @@ TEST_F(RuntimeTest, HandleTypeContainsEventsSpecifiedInConfiguration) }); } -TEST_F(RuntimeTest, TracingIsDisabledWhenTraceFilterConfigPathIsInvalid) +TEST_P(RuntimeTest, TracingIsDisabledWhenTraceFilterConfigPathIsInvalid) { RecordProperty("Verifies", "SCR-18159104"); RecordProperty("Description", @@ -312,10 +330,10 @@ TEST_F(RuntimeTest, TracingIsDisabledWhenTraceFilterConfigPathIsInvalid) RecordProperty("Priority", "1"); RecordProperty("DerivationTechnique", "Analysis of requirements"); - TestInSeparateProcess([]() { + TestInSeparateProcess([this]() { // Given a configuration file which contains a TraceFilterConfigPath that does not point to a valid tracing // configuration - WithConfigAtDefaultPath(get_path("ara_com_config_invalid_trace_config_path.json")); + WithConfigAtDefaultPath(get_path("ara_com_config_invalid_trace_config_path" + config_file_extension_)); // When implicitly default-initializing the runtime score::cpp::ignore = Runtime::getInstance(); @@ -325,11 +343,11 @@ TEST_F(RuntimeTest, TracingIsDisabledWhenTraceFilterConfigPathIsInvalid) }); } -TEST_F(RuntimeTest, TracingRuntimeIsDisabledWhenTracingDisabledInConfig) +TEST_P(RuntimeTest, TracingRuntimeIsDisabledWhenTracingDisabledInConfig) { - TestInSeparateProcess([]() { + TestInSeparateProcess([this]() { // Given a configuration with valid and disabled tracing configuration - WithConfigAtDefaultPath(get_path("ara_com_config_disabled_trace_config.json")); + WithConfigAtDefaultPath(get_path("ara_com_config_disabled_trace_config" + config_file_extension_)); // When implicitly default-initializing the runtime score::cpp::ignore = Runtime::getInstance(); @@ -339,15 +357,15 @@ TEST_F(RuntimeTest, TracingRuntimeIsDisabledWhenTracingDisabledInConfig) }); } -TEST_F(RuntimeTest, TracingRuntimeIsCreatedIfConfiguredCorrectly) +TEST_P(RuntimeTest, TracingRuntimeIsCreatedIfConfiguredCorrectly) { - TestInSeparateProcess([]() { + TestInSeparateProcess([this]() { // Given a configuration with valid and enabled tracing configuration - auto default_path = get_path("ara_com_config_valid_trace_config.json"); - auto json_path = default_path.find("external") != std::string::npos - ? get_path("ara_com_config_valid_trace_config_external.json") + auto default_path = get_path("ara_com_config_valid_trace_config" + config_file_extension_); + auto config_path = default_path.find("external") != std::string::npos + ? get_path("ara_com_config_valid_trace_config_external" + config_file_extension_) : default_path; - WithConfigAtDefaultPath(json_path); + WithConfigAtDefaultPath(config_path); // When implicitly default-initializing the runtime score::cpp::ignore = Runtime::getInstance(); @@ -357,5 +375,21 @@ TEST_F(RuntimeTest, TracingRuntimeIsCreatedIfConfiguredCorrectly) }); } +INSTANTIATE_TEST_SUITE_P( + JsonAndFlatBuffer, + RuntimeInitializationTest, + ::testing::Values(".json", ".bin"), + [](const ::testing::TestParamInfo& info) { + return info.param == ".json" ? "Json" : "FlatBuffer"; + }); + +INSTANTIATE_TEST_SUITE_P( + JsonAndFlatBuffer, + RuntimeTest, + ::testing::Values(".json", ".bin"), + [](const ::testing::TestParamInfo& info) { + return info.param == ".json" ? "Json" : "FlatBuffer"; + }); + } // namespace } // namespace score::mw::com::impl diff --git a/score/mw/com/runtime_configuration.cpp b/score/mw/com/runtime_configuration.cpp index 7a3e5463..92445d6f 100644 --- a/score/mw/com/runtime_configuration.cpp +++ b/score/mw/com/runtime_configuration.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,13 +32,25 @@ namespace score::mw::com::runtime namespace { -constexpr auto kDefaultConfigurationPath = "./etc/mw_com_config.json"; +constexpr auto kDefaultConfigurationPathJson = "./etc/mw_com_config.json"; +constexpr auto kDefaultConfigurationPathBin = "./etc/mw_com_config.bin"; constexpr auto kDeprecatedConfigurationPathCommandLineKey = std::string_view{"-service_instance_manifest"}; constexpr auto kConfigurationPathCommandLineKey = std::string_view{"--service_instance_manifest"}; +filesystem::Path GetDefaultConfigurationPath() +{ + // Try FlatBuffer configuration first - use simple file existence check + std::ifstream bin_file(kDefaultConfigurationPathBin); + if (bin_file.good()) + { + return kDefaultConfigurationPathBin; + } + return kDefaultConfigurationPathJson; +} + } // namespace -RuntimeConfiguration::RuntimeConfiguration() : RuntimeConfiguration{kDefaultConfigurationPath} {} +RuntimeConfiguration::RuntimeConfiguration() : RuntimeConfiguration{GetDefaultConfigurationPath()} {} RuntimeConfiguration::RuntimeConfiguration(filesystem::Path configuration_path) : configuration_path_{std::move(configuration_path)} @@ -51,7 +64,7 @@ RuntimeConfiguration::RuntimeConfiguration(const std::int32_t argc, score::Strin argv, static_cast::size_type>(argc)); auto configuration_path = ParseConfigurationPath(command_line_arguments); configuration_path_ = - configuration_path.has_value() ? std::move(configuration_path).value() : kDefaultConfigurationPath; + configuration_path.has_value() ? std::move(configuration_path).value() : GetDefaultConfigurationPath(); } const filesystem::Path& RuntimeConfiguration::GetConfigurationPath() const From 75dcfcc07cc64b7bdcf2485473922de4bf3388df Mon Sep 17 00:00:00 2001 From: Oliver Heilwagen Date: Tue, 9 Dec 2025 14:54:19 +0100 Subject: [PATCH 2/3] Add lola method configuration to flatbuffer config --- bazel/tools/generate_com_config.bzl | 92 +++++++++------ score/mw/com/example/ipc_bridge/BUILD | 1 + score/mw/com/impl/configuration/BUILD | 19 +++ .../com/impl/configuration/ara_com_config.fbs | 14 +++ .../ara_com_config_flatbuffer_conform.json | 110 ++++++++++++++++++ .../flatbuffer_config_loader.cpp | 46 +++++++- 6 files changed, 246 insertions(+), 36 deletions(-) create mode 100644 score/mw/com/impl/configuration/example/ara_com_config_flatbuffer_conform.json diff --git a/bazel/tools/generate_com_config.bzl b/bazel/tools/generate_com_config.bzl index af6ec0ee..910cb319 100644 --- a/bazel/tools/generate_com_config.bzl +++ b/bazel/tools/generate_com_config.bzl @@ -6,13 +6,12 @@ This rule converts existing communication JSON files to a FlatBuffer friendly fo - Convert keys from camelCase to snake_case (avoids warnings) """ -def generate_com_config(name, json, visibility = None, **kwargs): +def generate_com_config(name, json, convert, visibility = None, **kwargs): """ Generate a FlatBuffer binary configuration file from a JSON input. - This rule performs two steps: - 1. Converts the input JSON to FlatBuffer friendly format - 2. Compiles the converted JSON to FlatBuffer binary format + This rule can optionally convert the input JSON to FlatBuffer friendly format + before compiling to FlatBuffer binary format. The schema is hardcoded to the COM FlatBuffer schema at: //score/mw/com/impl/configuration:ara_com_config.fbs @@ -23,6 +22,9 @@ def generate_com_config(name, json, visibility = None, **kwargs): Args: name: Name of the rule (will be used as the target name) json: Input JSON configuration file + convert: Boolean - if True, converts JSON to FlatBuffer friendly format + (dash to underscore, camelCase to snake_case). If False, uses + JSON as-is (assumes it's already FlatBuffer conform) visibility: Visibility for the generated target (optional) Outputs: @@ -33,6 +35,7 @@ def generate_com_config(name, json, visibility = None, **kwargs): generate_com_config( name = "my_config.bin", json = "example/config.json", + convert = True, visibility = ["//visibility:public"], ) """ @@ -46,35 +49,58 @@ def generate_com_config(name, json, visibility = None, **kwargs): else: output_path = json + ".bin" - # Intermediate converted JSON file (keep in same directory) - if json.endswith(".json"): - converted_json = json[:-5] + "_converted.json" - else: - converted_json = json + "_converted.json" + if convert: + # Intermediate converted JSON file (keep in same directory) + if json.endswith(".json"): + converted_json = json[:-5] + "_converted.json" + else: + converted_json = json + "_converted.json" - # Extract just the filename for the flatc intermediate output - json_filename = json.split("/")[-1] - if json_filename.endswith(".json"): - flatc_output = json_filename[:-5] + "_converted.bin" - else: - flatc_output = json_filename + "_converted.bin" + # Extract just the filename for the flatc intermediate output + json_filename = json.split("/")[-1] + if json_filename.endswith(".json"): + flatc_output = json_filename[:-5] + "_converted.bin" + else: + flatc_output = json_filename + "_converted.bin" + + # Step 1: Convert JSON to FlatBuffer friendly format + native.genrule( + name = name + "_convert", + srcs = [json], + outs = [converted_json], + tools = ["//bazel/tools:json_to_flatbuffer_json"], + cmd = "$(location //bazel/tools:json_to_flatbuffer_json) $(SRCS) $@", + visibility = ["//visibility:private"], + ) - # Step 1: Convert JSON to FlatBuffer friendly format - native.genrule( - name = name + "_convert", - srcs = [json], - outs = [converted_json], - tools = ["//bazel/tools:json_to_flatbuffer_json"], - cmd = "$(location //bazel/tools:json_to_flatbuffer_json) $(SRCS) $@", - visibility = ["//visibility:private"], - ) + # Step 2: Compile converted JSON to FlatBuffer binary + native.genrule( + name = name, + srcs = [converted_json, schema], + outs = [output_path], + tools = ["@flatbuffers//:flatc"], + cmd = "$(location @flatbuffers//:flatc) --binary -o $(@D) $(location " + schema + ") $(location " + converted_json + ") && mv $(@D)/" + flatc_output + " $@", + visibility = visibility, + ) + else: + # Extract just the filename for the flatc intermediate output + json_filename = json.split("/")[-1] + if json_filename.endswith(".json"): + flatc_output = json_filename[:-5] + ".bin" + else: + flatc_output = json_filename + ".bin" - # Step 2: Compile converted JSON to FlatBuffer binary - native.genrule( - name = name, - srcs = [converted_json, schema], - outs = [output_path], - tools = ["@flatbuffers//:flatc"], - cmd = "$(location @flatbuffers//:flatc) --binary -o $(@D) $(location " + schema + ") $(location " + converted_json + ") && mv $(@D)/" + flatc_output + " $@", - visibility = visibility, - ) + # Only move if the output paths differ + if flatc_output == output_path.split("/")[-1]: + cmd = "$(location @flatbuffers//:flatc) --binary -o $(@D) $(location " + schema + ") $(location " + json + ")" + else: + cmd = "$(location @flatbuffers//:flatc) --binary -o $(@D) $(location " + schema + ") $(location " + json + ") && mv $(@D)/" + flatc_output + " $@" + + native.genrule( + name = name, + srcs = [json, schema], + outs = [output_path], + tools = ["@flatbuffers//:flatc"], + cmd = cmd, + visibility = visibility, + ) diff --git a/score/mw/com/example/ipc_bridge/BUILD b/score/mw/com/example/ipc_bridge/BUILD index b7514f58..4db4b08e 100644 --- a/score/mw/com/example/ipc_bridge/BUILD +++ b/score/mw/com/example/ipc_bridge/BUILD @@ -114,5 +114,6 @@ exports_files([ generate_com_config( name = "mw_com_config.bin", json = "etc/mw_com_config.json", + convert = True, visibility = ["//visibility:public"], ) diff --git a/score/mw/com/impl/configuration/BUILD b/score/mw/com/impl/configuration/BUILD index d256dfe8..4d17b06a 100644 --- a/score/mw/com/impl/configuration/BUILD +++ b/score/mw/com/impl/configuration/BUILD @@ -809,38 +809,57 @@ filegroup( visibility = ["//visibility:public"], ) +filegroup( + name = "ara_com_config_flatbuffer_conform.json", + srcs = ["example/ara_com_config_flatbuffer_conform.json"], + visibility = ["//visibility:public"], +) + generate_com_config( name = "ara_com_config.bin", json = "example/ara_com_config.json", + convert = True, visibility = ["//visibility:public"], ) generate_com_config( name = "ara_com_config_other.bin", json = "example/ara_com_config_other.json", + convert = True, visibility = ["//visibility:public"], ) generate_com_config( name = "ara_com_config_invalid_trace_config_path.bin", json = "example/ara_com_config_invalid_trace_config_path.json", + convert = True, visibility = ["//visibility:public"], ) generate_com_config( name = "ara_com_config_disabled_trace_config.bin", json = "example/ara_com_config_disabled_trace_config.json", + convert = True, visibility = ["//visibility:public"], ) generate_com_config( name = "ara_com_config_valid_trace_config.bin", json = "example/ara_com_config_valid_trace_config.json", + convert = True, visibility = ["//visibility:public"], ) generate_com_config( name = "ara_com_config_valid_trace_config_external.bin", json = "example/ara_com_config_valid_trace_config_external.json", + convert = True, + visibility = ["//visibility:public"], +) + +generate_com_config( + name = "ara_com_config_flatbuffer_conform.bin", + json = "example/ara_com_config_flatbuffer_conform.json", + convert = False, visibility = ["//visibility:public"], ) diff --git a/score/mw/com/impl/configuration/ara_com_config.fbs b/score/mw/com/impl/configuration/ara_com_config.fbs index 622ebc0b..6aa5d773 100644 --- a/score/mw/com/impl/configuration/ara_com_config.fbs +++ b/score/mw/com/impl/configuration/ara_com_config.fbs @@ -46,6 +46,7 @@ table BindingElement { service_id:uint16; events:[Event]; fields:[Field]; + methods:[Method]; } table Event { @@ -62,6 +63,12 @@ table Field { set:bool = false; } +table Method { + method_name:string (required); + // JSON validation: methodId is 8 bit for LoLa, 15 bit for SOME/IP - using uint16 to accommodate both + method_id:uint16; +} + // ServiceInstances definitions table ServiceInstance { instance_specifier:string (required); @@ -83,6 +90,7 @@ table Instance { allowed_provider:AllowedProvider; events:[InstanceEvent]; fields:[InstanceField]; + methods:[InstanceMethod]; } // User IDs for access control @@ -115,6 +123,12 @@ table InstanceField { number_of_ipc_tracing_slots:uint16 = 0; } +table InstanceMethod { + method_name:string (required); + // JSON validation: queue_size must be between 1 and 255 - not enforceable in FlatBuffers + queue_size:uint16 = 1; +} + // Global definitions table Global { asil_level:AsilLevel = QM; diff --git a/score/mw/com/impl/configuration/example/ara_com_config_flatbuffer_conform.json b/score/mw/com/impl/configuration/example/ara_com_config_flatbuffer_conform.json new file mode 100644 index 00000000..f07b81bc --- /dev/null +++ b/score/mw/com/impl/configuration/example/ara_com_config_flatbuffer_conform.json @@ -0,0 +1,110 @@ +{ + "service_types": [ + { + "service_type_name": "/bmw/ncar/services/TirePressureService", + "version": { + "major": 12, + "minor": 34 + }, + "bindings": [ + { + "binding": "SHM", + "service_id": 1234, + "events": [ + { + "event_name": "CurrentPressureFrontLeft", + "event_id": 20 + } + ], + "fields": [ + { + "field_name": "CurrentTemperatureFrontLeft", + "field_id": 30 + } + ], + "methods": [ + { + "method_name": "SetPressure", + "method_id": 40 + } + ] + } + ] + } + ], + "service_instances": [ + { + "instance_specifier": "abc/abc/TirePressurePort", + "service_type_name": "/bmw/ncar/services/TirePressureService", + "version": { + "major": 12, + "minor": 34 + }, + "instances": [ + { + "instance_id": 1234, + "asil_level": "B", + "binding": "SHM", + "shm_size": 10000, + "control_asil_b_shm_size": 20000, + "control_qm_shm_size": 30000, + "events": [ + { + "event_name": "CurrentPressureFrontLeft", + "number_of_sample_slots": 50, + "max_subscribers": 5, + "number_of_ipc_tracing_slots": 0 + } + ], + "fields": [ + { + "field_name": "CurrentTemperatureFrontLeft", + "number_of_sample_slots": 60, + "max_subscribers": 6, + "number_of_ipc_tracing_slots": 7 + } + ], + "methods": [ + { + "method_name": "SetPressure", + "queue_size": 20 + } + ], + "allowed_consumer": { + "qm": [ + 42, + 43 + ], + "b": [ + 54, + 55 + ] + }, + "allowed_provider": { + "qm": [ + 15 + ], + "b": [ + 15 + ] + } + } + ] + } + ], + "global": { + "asil_level": "B", + "application_id": 1234, + "queue_size": { + "qm_receiver": 8, + "b_receiver": 5, + "b_sender": 12 + }, + "shm_size_calc_mode": "SIMULATION" + }, + "tracing": { + "enable": true, + "application_instance_id": "ara_com_example", + "trace_filter_config_path": "./mw_com_trace_filter.json" + } +} diff --git a/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp index 69d0db09..fa06b91f 100644 --- a/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp +++ b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp @@ -17,6 +17,8 @@ #include "score/mw/com/impl/configuration/lola_event_instance_deployment.h" #include "score/mw/com/impl/configuration/lola_field_id.h" #include "score/mw/com/impl/configuration/lola_field_instance_deployment.h" +#include "score/mw/com/impl/configuration/lola_method_id.h" +#include "score/mw/com/impl/configuration/lola_method_instance_deployment.h" #include "score/mw/com/impl/configuration/lola_service_id.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_id.h" @@ -116,6 +118,33 @@ LolaServiceInstanceDeployment::FieldInstanceMapping ParseFieldDeployments(const return fields; } +// Helper to parse method deployments from FlatBuffer Instance +LolaServiceInstanceDeployment::MethodInstanceMapping ParseMethodDeployments(const Instance* instance) +{ + LolaServiceInstanceDeployment::MethodInstanceMapping methods; + + if (instance->methods() != nullptr) + { + for (const auto* method : *instance->methods()) + { + if (method != nullptr) + { + std::optional queue_size = std::nullopt; + if (method->queue_size() > 0) + { + queue_size = static_cast(method->queue_size()); + } + + LolaMethodInstanceDeployment method_deploy(queue_size); + // method_name is obliged to contain a value (marked as required) + methods.emplace(method->method_name()->str(), std::move(method_deploy)); + } + } + } + + return methods; +} + // Helper to parse permission mappings from FlatBuffer Permissions (AllowedConsumer or AllowedProvider) template std::unordered_map> ParsePermissions(const PermissionsType* permissions) @@ -167,14 +196,15 @@ score::mw::com::impl::LolaServiceInstanceDeployment CreateLolaServiceInstanceDep auto events = ParseEventDeployments(instance); auto fields = ParseFieldDeployments(instance); + auto methods = ParseMethodDeployments(instance); auto allowed_consumer = ParsePermissions(instance->allowed_consumer()); auto allowed_provider = ParsePermissions(instance->allowed_provider()); bool strict_permission = (instance->permission_checks() == PermissionCheckStrategy::STRICT); LolaServiceInstanceDeployment deployment(instance_id, std::move(events), std::move(fields), - strict_permission, std::move(allowed_consumer), - std::move(allowed_provider)); + std::move(methods), strict_permission, + std::move(allowed_consumer), std::move(allowed_provider)); SetMemorySizes(deployment, instance); @@ -291,6 +321,7 @@ Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes LolaServiceId service_id{binding->service_id()}; LolaServiceTypeDeployment::EventIdMapping events; LolaServiceTypeDeployment::FieldIdMapping fields; + LolaServiceTypeDeployment::MethodIdMapping methods; if (binding->events() != nullptr) { @@ -310,7 +341,16 @@ Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes } } - binding_info = LolaServiceTypeDeployment{service_id, std::move(events), std::move(fields)}; + if (binding->methods() != nullptr) + { + for (const auto* method : *binding->methods()) + { + // method_name is obliged to contain a value (marked as required) + methods.emplace(method->method_name()->str(), LolaMethodId{static_cast(method->method_id())}); + } + } + + binding_info = LolaServiceTypeDeployment{service_id, std::move(events), std::move(fields), std::move(methods)}; break; // Use first SHM binding } else if (binding->binding() == BindingType::SOME_IP) From 524c2110a300a5bda2c66a139af058828c3afa77 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 14 Jan 2026 16:36:52 +0100 Subject: [PATCH 3/3] Flatbuffers PR, fine tunings & cleanup --- bazel/tools/generate_com_config.bzl | 34 +-- bazel/tools/json_to_flatbuffer_json.py | 16 +- score/mw/com/impl/configuration/BUILD | 19 +- .../com/impl/configuration/config_loader.cpp | 8 +- .../flatbuffer_config_loader.cpp | 257 ++++++++++-------- .../configuration/flatbuffer_config_loader.h | 14 +- .../mw/com/impl/runtime_single_exec_test.cpp | 44 ++- 7 files changed, 203 insertions(+), 189 deletions(-) diff --git a/bazel/tools/generate_com_config.bzl b/bazel/tools/generate_com_config.bzl index 910cb319..ed0bffe5 100644 --- a/bazel/tools/generate_com_config.bzl +++ b/bazel/tools/generate_com_config.bzl @@ -6,7 +6,7 @@ This rule converts existing communication JSON files to a FlatBuffer friendly fo - Convert keys from camelCase to snake_case (avoids warnings) """ -def generate_com_config(name, json, convert, visibility = None, **kwargs): +def generate_com_config(name, json, convert, visibility = None): """ Generate a FlatBuffer binary configuration file from a JSON input. @@ -43,25 +43,23 @@ def generate_com_config(name, json, convert, visibility = None, **kwargs): # Always use the COM FlatBuffer schema schema = "//score/mw/com/impl/configuration:ara_com_config.fbs" - # Preserve the full path of the JSON file, just change extension + # Extract base filepath and filename (remove .json extension if present) if json.endswith(".json"): - output_path = json[:-5] + ".bin" + filepath_base = json[:-5] + filename_base = json.split("/")[-1][:-5] else: - output_path = json + ".bin" + filepath_base = json + filename_base = json.split("/")[-1] + + # Preserve the full path of the JSON file, just change extension + output_path = filepath_base + ".bin" if convert: # Intermediate converted JSON file (keep in same directory) - if json.endswith(".json"): - converted_json = json[:-5] + "_converted.json" - else: - converted_json = json + "_converted.json" + converted_json = filepath_base + "_converted.json" - # Extract just the filename for the flatc intermediate output - json_filename = json.split("/")[-1] - if json_filename.endswith(".json"): - flatc_output = json_filename[:-5] + "_converted.bin" - else: - flatc_output = json_filename + "_converted.bin" + # Intermediate flatc output filename + flatc_output = filename_base + "_converted.bin" # Step 1: Convert JSON to FlatBuffer friendly format native.genrule( @@ -83,12 +81,8 @@ def generate_com_config(name, json, convert, visibility = None, **kwargs): visibility = visibility, ) else: - # Extract just the filename for the flatc intermediate output - json_filename = json.split("/")[-1] - if json_filename.endswith(".json"): - flatc_output = json_filename[:-5] + ".bin" - else: - flatc_output = json_filename + ".bin" + # Intermediate flatc output filename + flatc_output = filename_base + ".bin" # Only move if the output paths differ if flatc_output == output_path.split("/")[-1]: diff --git a/bazel/tools/json_to_flatbuffer_json.py b/bazel/tools/json_to_flatbuffer_json.py index 702f63e6..88eb36b9 100644 --- a/bazel/tools/json_to_flatbuffer_json.py +++ b/bazel/tools/json_to_flatbuffer_json.py @@ -74,11 +74,11 @@ def convert_enum_value(key: str, value: Any) -> Any: return value # Binding type: "SOME/IP" -> "SOME_IP", "SHM" -> "SHM" - if key in ('binding',): + if key == 'binding': return value.replace('/', '_') # Permission checks: "file-permissions-on-empty" -> "FILE_PERMISSIONS_ON_EMPTY", "strict" -> "STRICT" - if key in ('permission_checks',): + if key == 'permission_checks': return value.replace('-', '_').upper() return value @@ -101,12 +101,12 @@ def main(): try: with open(input_file, 'r', encoding='utf-8') as f: data = json.load(f) - converted_data = convert_json(data) - - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(converted_data, f, indent=4, ensure_ascii=False) - f.write('\n') - + + converted_data = convert_json(data) + + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(converted_data, f, indent=4, ensure_ascii=False) + f.write('\n') print(f"Successfully converted {input_file} -> {output_file}") except json.JSONDecodeError as e: diff --git a/score/mw/com/impl/configuration/BUILD b/score/mw/com/impl/configuration/BUILD index 4d17b06a..385a0944 100644 --- a/score/mw/com/impl/configuration/BUILD +++ b/score/mw/com/impl/configuration/BUILD @@ -89,24 +89,19 @@ cc_library( srcs = [ "flatbuffer_config_loader.cpp", # Include headers as srcs to make them available without depending on their implementations - "lola_event_id.h", - "lola_field_id.h", - "lola_service_id.h", - "lola_service_instance_deployment.h", - "lola_service_type_deployment.h", - "service_identifier_type.h", - "service_instance_deployment.h", - "service_type_deployment.h", "service_version_type.h", - "someip_service_instance_deployment.h", - "binding_service_type_deployment.h", - "binding_service_type_deployment_impl.h", ], hdrs = [ "flatbuffer_config_loader.h", ":generate_com_flatbuffer_headers", ], features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":lola_service_instance_deployment", + ":service_type_deployment", + ":someip_service_instance_deployment", + ":binding_service_type_deployment", + ], tags = ["FFI"], visibility = [ "//score/mw/com/impl/configuration:__subpackages__", @@ -748,7 +743,7 @@ cc_gtest_unit_test( cc_unit_test_suites_for_host_and_qnx( name = "unit_test_suite", cc_unit_tests = [ - "shm_size_calc_mode_test", + ":shm_size_calc_mode_test", ":config_parser_test", ":config_parser_methods_test", ":configuration_common_resources_test", diff --git a/score/mw/com/impl/configuration/config_loader.cpp b/score/mw/com/impl/configuration/config_loader.cpp index f6b3c63b..497d00ad 100644 --- a/score/mw/com/impl/configuration/config_loader.cpp +++ b/score/mw/com/impl/configuration/config_loader.cpp @@ -42,15 +42,15 @@ bool ShouldUseFlatBuffer(const std::string_view path) noexcept Configuration Load(const std::string_view path) noexcept { + // If .bin exists, use FlatBuffer loader if (ShouldUseFlatBuffer(path)) { - // Warning is used to see usage in the default ipc bridge test setup - score::mw::log::LogWarn("lola") << "Loading FlatBuffer configuration from: " << path; + score::mw::log::LogInfo("lola") << "Loading FlatBuffer configuration from: " << path; return FlatBufferConfigLoader::CreateConfiguration(path); } - // Warning is used to see usage in the default ipc bridge test setup - score::mw::log::LogWarn("lola") << "Loading JSON configuration from: " << path; + // Otherwise, use JSON parser + score::mw::log::LogInfo("lola") << "Loading JSON configuration from: " << path; return Parse(path); } diff --git a/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp index fa06b91f..fc2ad2d5 100644 --- a/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp +++ b/score/mw/com/impl/configuration/flatbuffer_config_loader.cpp @@ -13,17 +13,7 @@ #include "score/mw/com/impl/configuration/flatbuffer_config_loader.h" -#include "score/mw/com/impl/configuration/lola_event_id.h" -#include "score/mw/com/impl/configuration/lola_event_instance_deployment.h" -#include "score/mw/com/impl/configuration/lola_field_id.h" -#include "score/mw/com/impl/configuration/lola_field_instance_deployment.h" -#include "score/mw/com/impl/configuration/lola_method_id.h" -#include "score/mw/com/impl/configuration/lola_method_instance_deployment.h" -#include "score/mw/com/impl/configuration/lola_service_id.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" -#include "score/mw/com/impl/configuration/lola_service_instance_id.h" -#include "score/mw/com/impl/configuration/lola_service_type_deployment.h" -#include "score/mw/com/impl/configuration/quality_type.h" #include "score/mw/com/impl/configuration/service_identifier_type.h" #include "score/mw/com/impl/configuration/service_instance_deployment.h" #include "score/mw/com/impl/configuration/service_type_deployment.h" @@ -34,23 +24,26 @@ #include "score/mw/log/logging.h" -#include -#include -#include -#include +#include #include #include -#include #include #include +#include #include +#include +#include +#include namespace score::mw::com::impl::configuration { void FlatBufferConfigLoader::MmapDeleter::operator()(void* ptr) const noexcept { - if (ptr && size > 0) { munmap(ptr, size); } + if (ptr && size > 0) + { + munmap(ptr, size); + } } namespace @@ -66,27 +59,32 @@ QualityType ConvertAsilLevel(AsilLevel asil_level) noexcept LolaServiceInstanceDeployment::EventInstanceMapping ParseEventDeployments(const Instance* instance) { LolaServiceInstanceDeployment::EventInstanceMapping events; - + if (instance->events() != nullptr) { for (const auto* event : *instance->events()) { if (event != nullptr) { - std::optional slots = event->number_of_sample_slots() > 0 - ? std::optional(event->number_of_sample_slots()) : std::nullopt; - std::optional max_subs = event->max_subscribers() > 0 - ? std::optional(static_cast(event->max_subscribers())) : std::nullopt; - - LolaEventInstanceDeployment event_deploy( - slots, max_subs, std::nullopt, event->enforce_max_samples(), - static_cast(event->number_of_ipc_tracing_slots())); + std::optional slots = event->number_of_sample_slots() > 0 + ? std::optional(event->number_of_sample_slots()) + : std::nullopt; + std::optional max_subs = + event->max_subscribers() > 0 + ? std::optional(static_cast(event->max_subscribers())) + : std::nullopt; + + LolaEventInstanceDeployment event_deploy(slots, + max_subs, + std::nullopt, + event->enforce_max_samples(), + static_cast(event->number_of_ipc_tracing_slots())); // event_name is obliged to contain a value (marked as required) events.emplace(event->event_name()->str(), std::move(event_deploy)); } } } - + return events; } @@ -94,27 +92,32 @@ LolaServiceInstanceDeployment::EventInstanceMapping ParseEventDeployments(const LolaServiceInstanceDeployment::FieldInstanceMapping ParseFieldDeployments(const Instance* instance) { LolaServiceInstanceDeployment::FieldInstanceMapping fields; - + if (instance->fields() != nullptr) { for (const auto* field : *instance->fields()) { if (field != nullptr) { - std::optional slots = field->number_of_sample_slots() > 0 - ? std::optional(field->number_of_sample_slots()) : std::nullopt; - std::optional max_subs = field->max_subscribers() > 0 - ? std::optional(static_cast(field->max_subscribers())) : std::nullopt; - - LolaFieldInstanceDeployment field_deploy( - slots, max_subs, std::nullopt, field->enforce_max_samples(), - static_cast(field->number_of_ipc_tracing_slots())); + std::optional slots = field->number_of_sample_slots() > 0 + ? std::optional(field->number_of_sample_slots()) + : std::nullopt; + std::optional max_subs = + field->max_subscribers() > 0 + ? std::optional(static_cast(field->max_subscribers())) + : std::nullopt; + + LolaFieldInstanceDeployment field_deploy(slots, + max_subs, + std::nullopt, + field->enforce_max_samples(), + static_cast(field->number_of_ipc_tracing_slots())); // field_name is obliged to contain a value (marked as required) fields.emplace(field->field_name()->str(), std::move(field_deploy)); } } } - + return fields; } @@ -122,7 +125,7 @@ LolaServiceInstanceDeployment::FieldInstanceMapping ParseFieldDeployments(const LolaServiceInstanceDeployment::MethodInstanceMapping ParseMethodDeployments(const Instance* instance) { LolaServiceInstanceDeployment::MethodInstanceMapping methods; - + if (instance->methods() != nullptr) { for (const auto* method : *instance->methods()) @@ -134,23 +137,23 @@ LolaServiceInstanceDeployment::MethodInstanceMapping ParseMethodDeployments(cons { queue_size = static_cast(method->queue_size()); } - + LolaMethodInstanceDeployment method_deploy(queue_size); // method_name is obliged to contain a value (marked as required) methods.emplace(method->method_name()->str(), std::move(method_deploy)); } } } - + return methods; } // Helper to parse permission mappings from FlatBuffer Permissions (AllowedConsumer or AllowedProvider) -template +template std::unordered_map> ParsePermissions(const PermissionsType* permissions) { std::unordered_map> permission_map; - + if (permissions != nullptr) { if (permissions->qm() != nullptr) @@ -164,7 +167,7 @@ std::unordered_map> ParsePermissions(const Permi permission_map.emplace(QualityType::kASIL_B, std::move(b_users)); } } - + return permission_map; } @@ -186,28 +189,32 @@ void SetMemorySizes(LolaServiceInstanceDeployment& deployment, const Instance* i } // Helper to create LolaServiceInstanceDeployment from FlatBuffer Instance -score::mw::com::impl::LolaServiceInstanceDeployment CreateLolaServiceInstanceDeployment(const Instance* instance) -{ +score::mw::com::impl::LolaServiceInstanceDeployment CreateLolaServiceInstanceDeployment(const Instance* instance) +{ score::cpp::optional instance_id; if (instance->instance_id() != 0) { instance_id = LolaServiceInstanceId{instance->instance_id()}; } - + auto events = ParseEventDeployments(instance); auto fields = ParseFieldDeployments(instance); auto methods = ParseMethodDeployments(instance); auto allowed_consumer = ParsePermissions(instance->allowed_consumer()); auto allowed_provider = ParsePermissions(instance->allowed_provider()); - + bool strict_permission = (instance->permission_checks() == PermissionCheckStrategy::STRICT); - - LolaServiceInstanceDeployment deployment(instance_id, std::move(events), std::move(fields), - std::move(methods), strict_permission, - std::move(allowed_consumer), std::move(allowed_provider)); - + + LolaServiceInstanceDeployment deployment(instance_id, + std::move(events), + std::move(fields), + std::move(methods), + strict_permission, + std::move(allowed_consumer), + std::move(allowed_provider)); + SetMemorySizes(deployment, instance); - + return deployment; } @@ -216,17 +223,14 @@ score::mw::com::impl::LolaServiceInstanceDeployment CreateLolaServiceInstanceDep Configuration FlatBufferConfigLoader::CreateConfiguration(std::string_view path) noexcept { FlatBufferConfigLoader loader(path); - - return score::mw::com::impl::Configuration( - loader.CreateServiceTypes(), - loader.CreateServiceInstances(), - loader.CreateGlobalConfiguration(), - loader.CreateTracingConfiguration() - ); + + return score::mw::com::impl::Configuration(loader.CreateServiceTypes(), + loader.CreateServiceInstances(), + loader.CreateGlobalConfiguration(), + loader.CreateTracingConfiguration()); } -FlatBufferConfigLoader::FlatBufferConfigLoader(std::string_view path) - : fd_(-1), com_config_(nullptr) +FlatBufferConfigLoader::FlatBufferConfigLoader(std::string_view path) : fd_(-1), com_config_(nullptr) { LoadBuffer(path); } @@ -241,10 +245,11 @@ FlatBufferConfigLoader::~FlatBufferConfigLoader() void FlatBufferConfigLoader::LoadBuffer(std::string_view path) { - fd_ = open(path.data(), O_RDONLY); + fd_ = open(std::string(path).c_str(), O_RDONLY); if (fd_ == -1) { - ::score::mw::log::LogFatal("lola") << "Failed to open FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + ::score::mw::log::LogFatal("lola") + << "Failed to open FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; std::terminate(); } @@ -252,7 +257,8 @@ void FlatBufferConfigLoader::LoadBuffer(std::string_view path) struct stat st; if (fstat(fd_, &st) == -1) { - ::score::mw::log::LogFatal("lola") << "Failed to stat FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + ::score::mw::log::LogFatal("lola") + << "Failed to stat FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; std::terminate(); } @@ -268,7 +274,8 @@ void FlatBufferConfigLoader::LoadBuffer(std::string_view path) void* ptr = mmap(nullptr, mapped_size, PROT_READ, MAP_PRIVATE, fd_, 0); if (ptr == MAP_FAILED) { - ::score::mw::log::LogFatal("lola") << "Failed to mmap FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; + ::score::mw::log::LogFatal("lola") + << "Failed to mmap FlatBuffer file: " << path << " (" << std::string_view(std::strerror(errno)) << ")"; std::terminate(); } mapped_ptr_ = std::unique_ptr(ptr, MmapDeleter{mapped_size}); @@ -292,25 +299,25 @@ void FlatBufferConfigLoader::LoadBuffer(std::string_view path) } Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes() const noexcept -{ +{ Configuration::ServiceTypeDeployments service_type_deployments; - + // service_types is obliged to contain a value (marked as required) for (const auto* service_type : *com_config_->service_types()) - { + { const auto* version = service_type->version(); if (version == nullptr) { ::score::mw::log::LogFatal("lola") << "Service type missing version. Terminating"; std::terminate(); } - + // service_type_name is obliged to contain a value (marked as required) - auto service_identifier = make_ServiceIdentifierType( - service_type->service_type_name()->str(), version->major(), version->minor()); - + auto service_identifier = + make_ServiceIdentifierType(service_type->service_type_name()->str(), version->major(), version->minor()); + ServiceTypeDeployment::BindingInformation binding_info = score::cpp::blank{}; - + // bindings is obliged to contain a value (marked as required) // Process all bindings - currently only SHM is supported, use the first SHM binding found for (const auto* binding : *service_type->bindings()) @@ -322,41 +329,46 @@ Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes LolaServiceTypeDeployment::EventIdMapping events; LolaServiceTypeDeployment::FieldIdMapping fields; LolaServiceTypeDeployment::MethodIdMapping methods; - + if (binding->events() != nullptr) { for (const auto* event : *binding->events()) { // event_name is obliged to contain a value (marked as required) - events.emplace(event->event_name()->str(), LolaEventId{static_cast(event->event_id())}); + events.emplace(event->event_name()->str(), + LolaEventId{static_cast(event->event_id())}); } } - + if (binding->fields() != nullptr) { for (const auto* field : *binding->fields()) { // field_name is obliged to contain a value (marked as required) - fields.emplace(field->field_name()->str(), LolaFieldId{static_cast(field->field_id())}); + fields.emplace(field->field_name()->str(), + LolaFieldId{static_cast(field->field_id())}); } } - + if (binding->methods() != nullptr) { for (const auto* method : *binding->methods()) { // method_name is obliged to contain a value (marked as required) - methods.emplace(method->method_name()->str(), LolaMethodId{static_cast(method->method_id())}); + methods.emplace(method->method_name()->str(), + LolaMethodId{static_cast(method->method_id())}); } } - - binding_info = LolaServiceTypeDeployment{service_id, std::move(events), std::move(fields), std::move(methods)}; + + binding_info = + LolaServiceTypeDeployment{service_id, std::move(events), std::move(fields), std::move(methods)}; break; // Use first SHM binding } else if (binding->binding() == BindingType::SOME_IP) { // Skip SOME/IP - not supported yet - continue; + ::score::mw::log::LogFatal("lola") << "Provided SOME/IP binding, which is not supported yet."; + std::terminate(); } else { @@ -364,32 +376,38 @@ Configuration::ServiceTypeDeployments FlatBufferConfigLoader::CreateServiceTypes std::terminate(); } } - + + if (binding_info == score::cpp::blank{}) + { + ::score::mw::log::LogFatal("lola") + << "No SHM binding found for Service Type: " << service_identifier.ToString(); + std::terminate(); + } + ServiceTypeDeployment service_deployment{binding_info}; - - const auto inserted = service_type_deployments.emplace( - std::piecewise_construct, - std::forward_as_tuple(service_identifier), - std::forward_as_tuple(std::move(service_deployment))); - + + const auto inserted = service_type_deployments.emplace(std::piecewise_construct, + std::forward_as_tuple(service_identifier), + std::forward_as_tuple(std::move(service_deployment))); + if (!inserted.second) { ::score::mw::log::LogFatal("lola") << "Service Type was deployed twice in FlatBuffer"; std::terminate(); } } - + return service_type_deployments; } Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceInstances() const noexcept -{ +{ Configuration::ServiceInstanceDeployments service_instances; - + // service_instances is obliged to contain a value (marked as required) for (const auto* service_instance : *com_config_->service_instances()) { - // service_instance and instance_specifier are obliged to contain a value (marked as required) + // service_instance and instance_specifier are obliged to contain a value (marked as required) auto instance_spec_result = InstanceSpecifier::Create(service_instance->instance_specifier()->str()); if (!instance_spec_result) { @@ -397,18 +415,18 @@ Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceI std::terminate(); } InstanceSpecifier instance_spec = std::move(instance_spec_result.value()); - + // version and service_type_name are obliged to contain a value (marked as required) - const auto* version = service_instance->version(); + const auto* version = service_instance->version(); auto service_identifier = make_ServiceIdentifierType( service_instance->service_type_name()->str(), version->major(), version->minor()); - + if (service_instance->instances() == nullptr || service_instance->instances()->size() == 0) { ::score::mw::log::LogFatal("lola") << "Service instance missing deployment instances. Terminating"; std::terminate(); } - + // Find the single SHM instance - multi-binding not supported const Instance* shm_instance = nullptr; for (const auto* instance : *service_instance->instances()) @@ -418,7 +436,7 @@ Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceI if (shm_instance != nullptr) { ::score::mw::log::LogFatal("lola") << "Multiple SHM bindings for " << service_identifier.ToString() - << ". Multi-Binding not supported"; + << ". Multi-Binding not supported"; std::terminate(); } shm_instance = instance; @@ -434,7 +452,7 @@ Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceI std::terminate(); } } - + if (shm_instance == nullptr) { ::score::mw::log::LogFatal("lola") << "No SHM binding found for " << service_identifier.ToString(); @@ -445,60 +463,65 @@ Configuration::ServiceInstanceDeployments FlatBufferConfigLoader::CreateServiceI ServiceInstanceDeployment deployment(service_identifier, std::move(binding_info), asil_level, instance_spec); service_instances.emplace(std::move(instance_spec), std::move(deployment)); } - + return service_instances; } GlobalConfiguration FlatBufferConfigLoader::CreateGlobalConfiguration() const noexcept -{ +{ GlobalConfiguration global_config; - + if (com_config_->global() != nullptr) { const auto* global = com_config_->global(); - + // Set ASIL level QualityType asil_level = ConvertAsilLevel(global->asil_level()); global_config.SetProcessAsilLevel(asil_level); - + // Set application ID if present if (global->application_id() != 0) { global_config.SetApplicationId(global->application_id()); } - + // Set queue sizes if (global->queue_size() != nullptr) { const auto* queue_size = global->queue_size(); - global_config.SetReceiverMessageQueueSize(QualityType::kASIL_QM, - static_cast(queue_size->qm_receiver())); - global_config.SetReceiverMessageQueueSize(QualityType::kASIL_B, - static_cast(queue_size->b_receiver())); + global_config.SetReceiverMessageQueueSize(QualityType::kASIL_QM, + static_cast(queue_size->qm_receiver())); + global_config.SetReceiverMessageQueueSize(QualityType::kASIL_B, + static_cast(queue_size->b_receiver())); global_config.SetSenderMessageQueueSize(static_cast(queue_size->b_sender())); } - - // Set SHM size calculation mode + + // Set SHM size calculation mode. + // NOTE: SHM size calculation currently only supports the simulation mode. + // Therefore, we always use ShmSizeCalculationMode::kSimulation here, + // regardless of any potential configuration in the FlatBuffer `global` + // object. If additional modes are supported in the future, this code + // should be extended to read the mode from the FlatBuffer. ShmSizeCalculationMode shm_mode = ShmSizeCalculationMode::kSimulation; global_config.SetShmSizeCalcMode(shm_mode); } - + return global_config; } TracingConfiguration FlatBufferConfigLoader::CreateTracingConfiguration() const noexcept -{ +{ TracingConfiguration tracing_config; - + if (com_config_->tracing() != nullptr) { const auto* tracing = com_config_->tracing(); - + tracing_config.SetTracingEnabled(tracing->enable()); // application_instance_id is obliged to contain a value (marked as required) tracing_config.SetApplicationInstanceID(tracing->application_instance_id()->str()); - + if (tracing->trace_filter_config_path() != nullptr) { tracing_config.SetTracingTraceFilterConfigPath(tracing->trace_filter_config_path()->str()); @@ -509,7 +532,7 @@ TracingConfiguration FlatBufferConfigLoader::CreateTracingConfiguration() const tracing_config.SetTracingTraceFilterConfigPath("./etc/mw_com_trace_filter.json"); } } - + return tracing_config; } diff --git a/score/mw/com/impl/configuration/flatbuffer_config_loader.h b/score/mw/com/impl/configuration/flatbuffer_config_loader.h index 2b3d92c7..39f04254 100644 --- a/score/mw/com/impl/configuration/flatbuffer_config_loader.h +++ b/score/mw/com/impl/configuration/flatbuffer_config_loader.h @@ -14,15 +14,15 @@ #ifndef SCORE_MW_COM_IMPL_CONFIGURATION_FLATBUFFER_CONFIG_LOADER_H #define SCORE_MW_COM_IMPL_CONFIGURATION_FLATBUFFER_CONFIG_LOADER_H +#include "score/mw/com/impl/configuration/ara_com_config_generated.h" #include "score/mw/com/impl/configuration/configuration.h" #include "score/mw/com/impl/configuration/global_configuration.h" #include "score/mw/com/impl/configuration/tracing_configuration.h" -#include "score/mw/com/impl/configuration/ara_com_config_generated.h" #include +#include #include #include -#include #include namespace score::mw::com::impl::configuration @@ -66,16 +66,20 @@ class FlatBufferConfigLoader TracingConfiguration CreateTracingConfiguration() const noexcept; /// Deleter for mmap'd memory - struct MmapDeleter { std::size_t size; void operator()(void* ptr) const noexcept; }; + struct MmapDeleter + { + std::size_t size; + void operator()(void* ptr) const noexcept; + }; // File descriptor - closed in destructor int fd_ = -1; - // RAII mmap'd memory - automatically unmapped + // RAII mmap'd memory - automatically unmapped std::unique_ptr mapped_ptr_; // Non-owning view into mmap'd region - const ComConfiguration* com_config_; + const ComConfiguration* com_config_ = nullptr; }; } // namespace score::mw::com::impl::configuration diff --git a/score/mw/com/impl/runtime_single_exec_test.cpp b/score/mw/com/impl/runtime_single_exec_test.cpp index 458cc56d..8e8473b5 100644 --- a/score/mw/com/impl/runtime_single_exec_test.cpp +++ b/score/mw/com/impl/runtime_single_exec_test.cpp @@ -23,9 +23,9 @@ #include #include -#include #include #include +#include #include #include @@ -54,7 +54,7 @@ class SingleTestPerProcessFixture : public ::testing::TestWithParam void TearDown() override { - // Safeguard against accidentially not using TestInSeparateProcess() + // Safeguard against accidentally not using TestInSeparateProcess() ASSERT_TRUE(tested_in_separate_process_); } @@ -117,7 +117,7 @@ class SingleTestPerProcessFixture : public ::testing::TestWithParam auto* const instance = testing::UnitTest::GetInstance(); auto* const test_result = instance->current_test_info()->result(); auto* const listener = instance->listeners().default_result_printer(); - for (auto i = 0; i < test_result->total_part_count(); ++i) + for (int i = 0; i < test_result->total_part_count(); ++i) { listener->OnTestPartResult(test_result->GetTestPartResult(i)); } @@ -156,8 +156,8 @@ void WithConfigAtDefaultPath(const std::string& source_path) // Determine target filename based on source file extension const bool is_flatbuffer = source_path.find(".bin") != std::string::npos; - filesystem::Path target = is_flatbuffer ? filesystem::Path{"etc/mw_com_config.bin"} - : filesystem::Path{"etc/mw_com_config.json"}; + filesystem::Path target = + is_flatbuffer ? filesystem::Path{"etc/mw_com_config.bin"} : filesystem::Path{"etc/mw_com_config.json"}; ASSERT_TRUE(filesystem.CopyFile(filesystem::Path{source_path}, target).has_value()); } @@ -255,7 +255,7 @@ TEST_P(RuntimeTest, CannotResolveUnknownInstanceSpecifier) RecordProperty("DerivationTechnique", "Analysis of requirements"); TestInSeparateProcess([this]() { - // Given aconfiguration without the tire_pressure_port_other_ instance specifier + // Given a configuration without the tire_pressure_port_other_ instance specifier WithConfigAtDefaultPath(config_with_tire_pressure_port()); // When resolving this instance specifier @@ -363,8 +363,8 @@ TEST_P(RuntimeTest, TracingRuntimeIsCreatedIfConfiguredCorrectly) // Given a configuration with valid and enabled tracing configuration auto default_path = get_path("ara_com_config_valid_trace_config" + config_file_extension_); auto config_path = default_path.find("external") != std::string::npos - ? get_path("ara_com_config_valid_trace_config_external" + config_file_extension_) - : default_path; + ? get_path("ara_com_config_valid_trace_config_external" + config_file_extension_) + : default_path; WithConfigAtDefaultPath(config_path); // When implicitly default-initializing the runtime @@ -375,21 +375,19 @@ TEST_P(RuntimeTest, TracingRuntimeIsCreatedIfConfiguredCorrectly) }); } -INSTANTIATE_TEST_SUITE_P( - JsonAndFlatBuffer, - RuntimeInitializationTest, - ::testing::Values(".json", ".bin"), - [](const ::testing::TestParamInfo& info) { - return info.param == ".json" ? "Json" : "FlatBuffer"; - }); - -INSTANTIATE_TEST_SUITE_P( - JsonAndFlatBuffer, - RuntimeTest, - ::testing::Values(".json", ".bin"), - [](const ::testing::TestParamInfo& info) { - return info.param == ".json" ? "Json" : "FlatBuffer"; - }); +INSTANTIATE_TEST_SUITE_P(JsonAndFlatBuffer, + RuntimeInitializationTest, + ::testing::Values(".json", ".bin"), + [](const ::testing::TestParamInfo& info) { + return info.param == ".json" ? "Json" : "FlatBuffer"; + }); + +INSTANTIATE_TEST_SUITE_P(JsonAndFlatBuffer, + RuntimeTest, + ::testing::Values(".json", ".bin"), + [](const ::testing::TestParamInfo& info) { + return info.param == ".json" ? "Json" : "FlatBuffer"; + }); } // namespace } // namespace score::mw::com::impl