diff --git a/docs/design/cpp_abstraction_layer_api_for_user_applications.md b/docs/design/cpp_abstraction_layer_api_for_user_applications.md new file mode 100644 index 0000000..ba1d685 --- /dev/null +++ b/docs/design/cpp_abstraction_layer_api_for_user_applications.md @@ -0,0 +1,297 @@ +# C++ Diagnostic API Layer - Application Usage + +This API layer provides application-facing abstractions for UDS and SOVD diagnostics. +Applications implement service/resource/operation interfaces and register them via builder classes. +The resulting `DiagnosticServicesCollection` controls lifetime and hence keeps the implemented application +functionality connected to the underlying binding. + +## Design Goals + + - This API Layer acts as abstraction layer between user code and a concrete binding implementation which communicates with the SOVD Server. + - guarantees clear independence from underlying implementation details and thus, facilitates easy unit-testability of user code + - concrete implementation of the underlying binding can get easily exchanged as well as adjusted if required without having to adjust all the user code + +
+ + - This API layer should have certain pieces of information available to be able to, if required, forward such data to the SOVD Server for further processing there. + - as a result, user code must provide certain pieces of information as depicted by this design upon registration of SOVD Operations and/or Data Resources + +
+ + - user code shall have minimal hassle w.r.t. to creating as well as having to populate native SOVD reply payloads + - ideally, the API layer shall create native SOVD data structures and populate these from the user code's data structures, e.g. to JSON + +
+ + - user code which is still using legacy UDS APIs shall seamlessly continue to work as before + - such user code can then get migrated step-wise to new SOVD APIs which eases system migration to the new OpenSOVD stack + +
+ + - newly written applications shall not use the legacy UDS APIs but the native SOVD ones instead + +## Diagrams + +![SVG](./cpp_abstraction_layer_api_for_user_applications.svg) +A [PlantUML version](./cpp_abstraction_layer_api_for_user_applications.puml) is also available. + +## Coding Examples + +### UDS: ReadDataByIdentifier + +```cpp +#include "mw/diag/uds/uds.hpp" // illustrative include + +#include + +class VinReader final : public mw::diag::uds::ReadDataByIdentifier +{ + public: + explicit VinReader(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::Result Read() override + { + ByteVector bytes{&memory_resource_}; + bytes.insert(bytes.end(), {0x56, 0x49, 0x4E}); // "VIN" bytes, example only + return {std::move(bytes)}; + } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterVinReader(std::pmr::memory_resource& memory_resource) +{ + return mw::diag::uds::DiagnosticServicesCollectionBuilder{memory_resource} + .With("0xF190") // note that `With()` will implicitly forward the builder's memory_resource to `VinReader` + .Build(); +} +``` + +### UDS: WriteDataByIdentifier + +```cpp +#include "mw/diag/uds/uds.hpp" // illustrative include + +#include + +class ConfigWriter final : public mw::diag::uds::WriteDataByIdentifier +{ + public: + explicit ConfigWriter(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::Result Write(mw::diag::ByteSequence data) override + { + // parse and persist `data` here + + return {}; + } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterConfigWriter(std::pmr::memory_resource& memory_resource) +{ + return mw::diag::uds::DiagnosticServicesCollectionBuilder{memory_resource} + .With("0xF191") // note that `With()` will implicitly forward the builder's memory_resource to `ConfigWriter` + .Build(); +} +``` + +### UDS: RoutineControl + +```cpp +#include "mw/diag/uds/uds.hpp" // illustrative include + +#include + +class MyRoutine final : public mw::diag::uds::RoutineControl +{ + public: + explicit MyRoutine(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::Result Start(ByteSequence request) override + { + // implement your logic for start routine here + + ByteVector bytes{&memory_resource_}; + bytes.push_back(0x00); + return {std::move(bytes)}; + } + + mw::diag::Result Stop(ByteSequence request) override + { + // implement your logic for stop routine here + + ByteVector bytes{&memory_resource_}; + bytes.push_back(0x00); + return {std::move(bytes)}; + } + + mw::diag::Result RequestResults(ByteSequence request) override + { + // implement your logic for request routine results here + + ByteVector bytes{memory_resource_}; + bytes.insert(bytes.end(), {0x12, 0x34}); + return {std::move(bytes)}; + } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterMyRoutine(std::pmr::memory_resource& memory_resource) +{ + return mw::diag::uds::DiagnosticServicesCollectionBuilder{memory_resource} + .With("0xFF00") // note that `With()` will implicitly forward the builder's memory_resource to `MyRoutine` + .Build(); +} +``` + +### SOVD: ReadOnlyDataResource + +```cpp +#include "mw/diag/sovd/sovd.hpp" // illustrative include + +#include + +class VehicleInfoResource final : public mw::diag::sovd::ReadOnlyDataResource +{ + public: + explicit VehicleInfoResource(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::sovd::Result Get() override + { + mw::diag::sovd::JsonDataReply reply; + // reply.set({{"vin", "FOO BAR"}}); // illustrative JSON + return {std::move(reply)}; + } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterVehicleInfoResourceFor(mw::diag::sovd::DiagnosticEntity& entity, + mw::diag::sovd::JsonSchemaView json_schema, + std::pmr::memory_resource& memory_resource) +{ + return mw::diag::sovd::DiagnosticServicesCollectionBuilder{entity, memory_resource} + .With("common/VehicleInfoResource", + schema, + mw::diag::sovd::DataCategoryIdentifiers::kSysInfo, + std::nullopt) // note that `With()` will implicitly forward the builder's memory_resource to `VehicleInfoResource` + .Build(); +} +``` + +### SOVD: WritableDataResource + +```cpp +#include "mw/diag/sovd/sovd.hpp" // illustrative include + +#include + +class ConfigResource final : public mw::diag::sovd::WritableDataResource +{ + public: + explicit ConfigResource(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::sovd::Result Put(mw::diag::sovd::DiagnosticRequest request) override + { + // parse `request.data` and persist it here as required by your needs + + return {}; + } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterConfigResourceFor(mw::diag::sovd::DiagnosticEntity& entity, std::pmr::memory_resource& memory_resource) +{ + return mw::diag::sovd::DiagnosticServicesCollectionBuilder{entity, memory_resource} + .With("MyApplication/MyComponent/Configure", + mw::diag::sovd::DataCategoryIdentifiers::kParameter, + std::nullopt) // note that `With()` will implicitly forward the builder's memory_resource to `ConfigResource` + .Build(); +} +``` + +### SOVD: Operations + +```cpp +#include "mw/diag/sovd/sovd.hpp" // illustrative include + +#include + +// synchronous operation +class SelfTestOperation final : public mw::diag::sovd::Operation +{ + public: + explicit SelfTestOperation(std::pmr::memory_resource& memory_resource) : memory_resource_{memory_resource} {} + + mw::diag::sovd::Result Info(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Status(mw::diag::sovd::DiagnosticRequest) override { return {}; } + + mw::diag::sovd::Result Execute(mw::diag::sovd::DiagnosticRequest) override + { + // perform operation in place here (=> synchronously) + + return {}; + } + + mw::diag::sovd::Result Resume(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Reset(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Stop(mw::diag::sovd::DiagnosticRequest) override { return {}; } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +// asynchronous operation +class LongRunningOperation final : public mw::diag::sovd::Operation +{ + public: + explicit LongRunningOperation(std::pmr::memory_resource& memory_resource) : memory_resource_(memory_resource) {} + + mw::diag::sovd::Result Info(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Status(mw::diag::sovd::DiagnosticRequest) override + { + // return current progress/status + return {}; + } + + mw::diag::sovd::Result Execute(mw::diag::sovd::DiagnosticRequest) override + { + // trigger async work here and return initial response + return {}; + } + + mw::diag::sovd::Result Resume(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Reset(mw::diag::sovd::DiagnosticRequest) override { return {}; } + mw::diag::sovd::Result Stop(mw::diag::sovd::DiagnosticRequest) override { return {}; } + + private: + std::pmr::memory_resource& memory_resource_; +}; + +mw::diag::Result> +RegisterOperationsFor(mw::diag::sovd::DiagnosticEntity& entity, std::pmr::memory_resource& memory_resource) +{ + return mw::diag::sovd::DiagnosticServicesCollectionBuilder{entity, memory_resource} + .With("MyApplication/MyComponent/PerformSelfTest", + mw::diag::sovd::OperationInvocationPolicy::kPerformsSynchronousInvocation, + std::nullopt) // note that `With()` will implicitly forward the builder's memory_resource to `SelfTestOperation` + .With("MyApplication/MyComponent/PerformLongRunningTask", + mw::diag::sovd::OperationInvocationPolicy::kRequiresIndividualAsyncInvocations, + std::nullopt) // note that `With()` will implicitly forward the builder's memory_resource to `LongRunningOperation` + .Build(); +} +``` diff --git a/docs/design/cpp_abstraction_layer_api_for_user_applications.puml b/docs/design/cpp_abstraction_layer_api_for_user_applications.puml new file mode 100755 index 0000000..670b6a3 --- /dev/null +++ b/docs/design/cpp_abstraction_layer_api_for_user_applications.puml @@ -0,0 +1,426 @@ +@startuml CppAbstractionLayerAPIForDiagnosticServices + +!theme plain +skinparam classAttributeIconSize 0 +skinparam classFontSize 11 +skinparam packageStyle rectangle + +package "Design Goals" as DesignGoals {} +package "see cpp_abstraction_layer_api_for_user_applications.md" as DesignGoals.Reference {} + +package "mw::diag" { + enum ErrorCode { + kUnknown + kInternalError + } + + entity "Result" as DiagResult { + alias for `score::Result` + } + + entity ByteSequence { + <> + ByteSequence = std::span + } + + entity ByteVector { + <> + ByteVector = std::pmr::vector + } + + abstract class DiagnosticServicesCollection { + no public methods are defined since lifetime control of contained services is the only goal of this class + } +} + +package "mw::diag::sovd" { + class "Error" as SOVDError { + sovd_error : std::pmr::string + vendor_error : std::pmr::string + vendor_message : std::pmr::string + } + + class "Result" as SOVDResult { + alias for `score::details::expected` + } + + class TranslationIdentifier {} + class DataCategoryIdentifier {} + class DataGroupIdentifier {} + class DataGroupShortDesc {} + class JsonSchemaView {} + + enum DataCategoryIdentifiers { + kIdentification = 'identData' + kMeasurement = 'currentData' + kParameter = 'storedData' + kSysInfo = 'sysInfo' + } + + enum OperationInvocationPolicy { + kPerformsSynchronousInvocation + kRequiresIndividualAsyncInvocations + kSupportsConcurrentAsyncInvocations + } + + enum ProximityProof { + kRequired + kNotRequired + } + + enum "DiagnosticEntity::Kind" as DiagnosticEntityKind { + kApplication + } + + class "DiagnosticEntity::Mode" as DiagnosticEntityMode { + +id : std::pmr::string + +name : std::pmr::string + +translation_id : std::optional + +values : std::pmr::vector + } + + class DiagnosticEntity { + using Identifier = std::pmr::string + .. + #TO BE CLARIFIED: are these two required here or will locking be handled completely by SOVD Server instead? + #{abstract} Lock() : mw::diag::sovd::Result + #{abstract} Unlock() : mw::diag::sovd::Result + .. + {abstract} GetKind() : DiagnosticEntity::Kind + .. + {abstract} GetSupportedModes() : mw::diag::sovd::Result> + {abstract} ApplyMode(mode_id : std::pmr::string, mode_value : std::pmr::string, expiration_timeout : std::optional) : mw::diag::sovd::Result + } + + class "DiagnosticServicesCollection" as SOVDDiagnosticServicesCollection { + +explicit DiagnosticServicesCollection(DiagnosticEntity&, std::pmr::memory_resource&) + ~DiagnosticServicesCollection() + } + note right: upon destruction, will release all functionality as well as\nregistered services of the referenced DiagnosticEntity from\nthe underlying binding implementation + + class "DiagnosticServicesCollectionBuilder" as SOVDDiagnosticServicesCollectionBuilder { + +using Identifier = std::string_view + + +explicit DiagnosticServicesCollectionBuilder(DiagnosticEntity&, std::pmr::memory_resource&) + + +With(Identifier, JsonSchemaView, DataCategoryIdentifier, std::optional, Args&&...) : DiagnosticServicesCollectionBuilder& + +With(Identifier, DataCategoryIdentifier, std::optional, Args&&...) : DiagnosticServicesCollectionBuilder& + +With(Identifier, JsonSchemaView, DataCategoryIdentifier, std::optional, Args&&...) : DiagnosticServicesCollectionBuilder& + +With(DataCategoryIdentifier, std::optional, Args&&...) : DiagnosticServicesCollectionBuilder& + +With(DataGroupIdentifier, DataGroupShortDesc, std::optional, DataCategoryIdentifier, Args&&...) : DiagnosticServicesCollectionBuilder& + +With(Identifier, OperationInvocationPolicy, std::optional, Args&&...) : DiagnosticServicesCollectionBuilder& + + .. final step .. + +Build() : mw::diag::Result> + } +} + +package "mw::diag::sovd" { + class DiagnosticRequest { + +headers : std::pmr::unordered_map + +proximity_response: std::optional + +data : JSON + } + + class DiagnosticReply { + #TODO: add public Getters/Setters! + -- + -headers_ : std::pmr::unordered_map + } + + class ProximityChallenge { + +challenge : std::pmr::string + +valid_until : time_point + } + + class JsonDataReply { + #TODO: add public Getters/Setters! + -- + -data_ : JSON -> json data created from content of deriving classes + } + + interface ReadOnlyDataResource { + API definition for read-only SOVD data resources + + {abstract} Get() : mw::diag::sovd::Result + } + + interface WritableDataResource { + API definition for writable SOVD data resources + + {abstract} Put(DiagnosticRequest) : mw::diag::sovd::Result + } + + interface DataResource { + API definition for SOVD data resources + } + + enum OperationExecutionStatus { + kFailed + kRunning + kStopped + kCompleted + } + + class OperationInfoReply { + #TODO: add public Getters/Setters! + -- + -supported_modes_ : std::pmr::vector + } + + class OperationStatusReply { + #TODO: add public Getters/Setters! + -- + -capability_ : std::pmr::string + -parameters_ : JSON + } + + class ExecuteOperationReply { + #TODO: add public Getters/Setters! + } + + interface Operation { + API definition for SOVD operations + + .. standard SOVD capabilities .. + {abstract} Info(DiagnosticRequest) : mw::diag::sovd::Result + {abstract} Status(DiagnosticRequest) : mw::diag::sovd::Result + {abstract} Execute(DiagnosticRequest) : mw::diag::sovd::Result + {abstract} Resume(DiagnosticRequest) : mw::diag::sovd::Result + {abstract} Reset(DiagnosticRequest) : mw::diag::sovd::Result + {abstract} Stop(DiagnosticRequest) : mw::diag::sovd::Result + + .. OEM-specific capabilities .. + .. only to be overridden by deriving classes if required .. + {abstract} Handle(DiagnosticRequest) : mw::diag::sovd::Result + } +} + +package "mw::diag::uds" { + interface ReadDataByIdentifier { + API definition for UDS Service 'Read Data by Identifier' + + {abstract} Read() : score::Result + } + + interface WriteDataByIdentifier { + API definition for UDS Service 'Write Data by Identifier' + + {abstract} Write(ByteSequence) : mw::diag::Result + } + + abstract class DataIdentifier { + <> + } + + interface RoutineControl { + API definition for UDS RoutineControl + + {abstract} Start(ByteSequence) : Result + {abstract} Stop(ByteSequence) : Result + {abstract} RequestResults(ByteSequence) : Result + } + + interface UDSService { + API definition for a generic UDS Service + + {abstract} HandleMessage(ByteSequence) : score::Result + } + + class "SerializedReadDataByIdentifier" as SerializedReadDataByIdentifier { + {abstract} Read() : Result {override} + } + + class "SerializedWriteDataByIdentifier" as SerializedWriteDataByIdentifier { + {abstract} Write(ByteSequence) : mw::diag::Result {override} + } + + class "SerializedDataByIdentifier" as SerializedDataByIdentifier { + {abstract} Read() : Result {override} + {abstract} Write(ByteSequence) : mw::diag::Result {override} + } + + class "SerializedRoutineControl" as SerializedRoutineControl { + <