From 6086c373a6a6eb9e27e501341808e11bc96ac176 Mon Sep 17 00:00:00 2001 From: MonikaKumari26 Date: Mon, 2 Feb 2026 18:32:26 +0530 Subject: [PATCH] created intial rust based design draft for mw::diag --- ...raction_layer_api_for_user_applications.md | 509 ++++++++ ...ction_layer_api_for_user_applications.puml | 685 +++++++++++ ...action_layer_api_for_user_applications.svg | 1026 +++++++++++++++++ 3 files changed, 2220 insertions(+) create mode 100644 docs/design/rust_abstraction_layer_api_for_user_applications.md create mode 100644 docs/design/rust_abstraction_layer_api_for_user_applications.puml create mode 100644 docs/design/rust_abstraction_layer_api_for_user_applications.svg diff --git a/docs/design/rust_abstraction_layer_api_for_user_applications.md b/docs/design/rust_abstraction_layer_api_for_user_applications.md new file mode 100644 index 0000000..630826e --- /dev/null +++ b/docs/design/rust_abstraction_layer_api_for_user_applications.md @@ -0,0 +1,509 @@ +# Rust Diagnostic API Layer - Application Usage + +This API layer provides application-facing abstractions for UDS and SOVD diagnostics in Rust. +Applications implement service/resource/operation traits and register them via builder structs. +The resulting `DiagnosticServicesCollection` controls lifetime and keeps the implemented application +functionality connected to the underlying binding. + +## Code Generation Guidelines + +**Important**: This design is intended for automated code generation. Key constraints: + +- **Rust Edition**: 2021+ (minimum required) +- **Async Strategy**: All trait methods are currently **synchronous**. While shown in design for API clarity, the current implementation does not use async. Future versions may add async support using `#[async_trait]` macro. +- **Thread Safety**: Generic type parameters have different bounds based on their access patterns: + - **Read-only traits** (`ReadDataByIdentifier`, `ReadOnlyDataResource`): Require `Send + Sync + 'static` because immutable `&self` methods allow safe concurrent access from multiple threads + - **Write-only traits** (`WriteDataByIdentifier`, `WritableDataResource`): Require only `Send + 'static` because `&mut self` methods need exclusive access (no concurrent execution possible, so `Sync` is unnecessary) + - **Mixed traits** with both `&self` and `&mut self` methods (`DiagnosticEntity`, `Operation`, `DataIdentifier`, `DataResource`): Require `Send + Sync + 'static` because they have immutable methods that could be called concurrently (e.g., `Operation::info(&self)`, `Operation::status(&self)`) + - **Mutable-only traits** (`RoutineControl`, `UdsService`): Require only `Send + 'static` because they only have `&mut self` methods +- **Trait Objects**: All traits marked with `<>` can be used as `dyn Trait` (e.g., `Box`) +- **Feature Flags**: Both "sovd" and "uds" modules are optional and independently toggleable via Cargo features +- **Error Handling**: + - `Result` (core/UDS): Uses `ErrorCode` for low-level diagnostic errors + - Import: `use opensovd_diag::error::{Result, ErrorCode};` + - SOVD Result type: Uses `Error` for protocol-specific errors + - **Recommended**: `use opensovd_diag::sovd::{Result, Error};` for direct import when no ambiguity + - **Alternative**: `use opensovd_diag::sovd::{self, ...};` with `sovd::Result` and `sovd::Error` for explicit module qualification + - Both approaches work; choose based on context (direct import is simpler, module qualification is clearer when multiple Result types are in scope) + - No automatic conversion between error types (intentional module isolation) +- **Type Aliases** (UDS): + - `ByteVec`: Type alias for `Vec` (owned byte sequences) + - `ByteSlice`: Type alias for `&[u8]` (borrowed byte slices) + - Provides semantic clarity for diagnostic data payloads +- **Blanket Implementations**: + - **Trait Inheritance**: + - `DataIdentifier`: Auto-implemented for types that implement both `ReadDataByIdentifier` and `WriteDataByIdentifier` + - `DataResource`: Auto-implemented for types that implement both `ReadOnlyDataResource` and `WritableDataResource` + - **DiagnosticEntity Smart Pointers** (provided by crate, no feature flags needed): + - `impl DiagnosticEntity for &T` + - `impl DiagnosticEntity for &mut T` + - `impl DiagnosticEntity for Box` + - `impl DiagnosticEntity for Arc` + - `impl DiagnosticEntity for Rc` + - **Rationale**: Enable flexible ownership patterns in builder's `new(entity: T)` method without runtime overhead +- **Lifetime Management**: SOVD `DiagnosticServicesCollection<'entity>` uses lifetime parameter to support non-'static entity types + - **Entity Ownership**: Builder takes **ownership** of entity via `new(entity: T)` + - **Type erasure happens internally**: Entity is boxed as `Box` + - **No circular dependency**: Builder owns entity, then transfers it to collection during `build()` + - **Flexible input types**: You can pass `Arc`, `Box`, or `MyEntity` directly + - **Example**: `DiagnosticServicesCollectionBuilder::new(Arc::new(MyEntity::new()))` + - Collection stores: `Box` (no generic type parameter needed) + - **Performance note**: One `Box` allocation during initialization is acceptable (reviewer confirmed) + - **Future optimization**: If allocation becomes an issue, use `smallbox` crate (stack allocation for small types) + +## Design Goals + +- **Abstraction Layer**: Acts as abstraction between user code and a concrete binding implementation which communicates with the SOVD Server. + - Guarantees clear independence from underlying implementation details, facilitating easy unit-testability + - Concrete binding implementation can be easily exchanged without adjusting user code + +- **SOVD Server Integration**: API layer has necessary information available to forward data to the SOVD Server for further processing. + - User code provides required information during registration of SOVD Operations and Data Resources + +- **Simplified SOVD Payload Creation**: Minimal hassle for creating and populating native SOVD reply payloads. + - API layer creates native SOVD data structures and populates them from user data structures (e.g., to JSON) + +- **Legacy UDS Support**: User code using legacy UDS APIs continues to work seamlessly. + - Enables step-wise migration to new SOVD APIs, easing system migration to OpenSOVD stack + +- **SOVD-First Development**: Newly written applications should use native SOVD APIs instead of legacy UDS APIs. + +## Cargo Feature Flags + +**Important**: At least one protocol feature must be enabled. The crate enforces this via compile-time assertion and will not compile without it. + +- **`sovd`**: SOVD protocol support (recommended for new projects) +- **`uds`**: UDS protocol support (legacy systems) + +**No default features** - users must explicitly choose protocol(s) to encourage conscious decision-making and reduce binary bloat. + + +```toml +# SOVD only +opensovd-diag = { version = "0.1", features = ["sovd"] } + +# UDS only +opensovd-diag = { version = "0.1", features = ["uds"] } + +# Both protocols +opensovd-diag = { version = "0.1", features = ["sovd", "uds"] } + +### Compile-Time Error + +If no features are enabled, you'll see: +``` +error: At least one protocol feature must be enabled. +Add one of: features = ["sovd"], features = ["uds"], or features = ["sovd", "uds"] +to your Cargo.toml dependency. +``` + +## SOVD Type Reference + +### JSON Schema Support + +SOVD resources require JSON schema definitions for protocol compliance. Schemas are provided during resource registration and describe: +- Data structure (object, array, primitive types) +- Field names and types +- Required vs optional fields +- Validation constraints (format, min/max, patterns) + +Schemas follow the [JSON Schema](https://json-schema.org/) specification and enable: +- **Protocol Compliance**: SOVD clients know expected data structures +- **Validation**: Automatic request/response validation +- **Documentation**: Auto-generate API documentation +- **Tooling Support**: Better IDE integration and client code generation + +### DataCategoryIdentifier + +Maps Rust enums to SOVD protocol strings: + +| Rust Enum | SOVD String | +|-----------|-------------| +| `DataCategoryIdentifiers::Identification` | `"identData"` | +| `DataCategoryIdentifiers::Measurement` | `"currentData"` | +| `DataCategoryIdentifiers::Parameter` | `"storedData"` | +| `DataCategoryIdentifiers::SysInfo` | `"sysInfo"` | + +## Diagrams + +![SVG](./rust_abstraction_layer_api_for_user_applications.svg) +A [PlantUML version](./rust_abstraction_layer_api_for_user_applications.puml) is also available. + +## Coding Examples + +> **Note**: Examples require enabling the appropriate feature flags (`uds` or `sovd`) in your `Cargo.toml`. + +### UDS: ReadDataByIdentifier + +```rust +use opensovd_diag::uds::{ReadDataByIdentifier, DiagnosticServicesCollectionBuilder}; +use opensovd_diag::error::Result; +use opensovd_diag::types::ByteVec; + +pub enum VehicleDataIdentifier { + VehicleIdentificationNumber, + VehicleSpeed, + EngineRPM, +} + +impl VehicleDataIdentifier { + pub fn as_uds_did(&self) -> &'static str { + match self { + Self::VehicleIdentificationNumber => "0xF190", + Self::VehicleSpeed => "0x010D", + Self::EngineRPM => "0x010C", + } + } +} + +struct VinReader; + +impl ReadDataByIdentifier for VinReader { + fn read(&self) -> Result { + Ok(vec![0x56, 0x49, 0x4E]) // "VIN" bytes, example only + } +} + +fn register_vin_reader() -> Result { + DiagnosticServicesCollectionBuilder::new() + .with_read_did( + VehicleDataIdentifier::VehicleIdentificationNumber.as_uds_did(), + VinReader + ) + .build() +} +``` + +### UDS: WriteDataByIdentifier + +```rust +use opensovd_diag::uds::{WriteDataByIdentifier, DiagnosticServicesCollectionBuilder}; +use opensovd_diag::error::Result; +use opensovd_diag::types::ByteSlice; + +struct ConfigWriter; + +impl WriteDataByIdentifier for ConfigWriter { + fn write(&mut self, data: ByteSlice) -> Result<()> { + // parse and persist `data` here + Ok(()) + } +} + +fn register_config_writer() -> Result { + DiagnosticServicesCollectionBuilder::new() + .with_write_did( + VehicleDataIdentifier::VehicleSpeed.as_uds_did(), + ConfigWriter + ) + .build() +} +``` + +### UDS: DataIdentifier (Read & Write) + +```rust +use opensovd_diag::uds::{ReadDataByIdentifier, WriteDataByIdentifier, DataIdentifier}; +use opensovd_diag::error::{Result, ErrorCode}; +use opensovd_diag::types::{ByteVec, ByteSlice}; + +#[derive(Clone)] +struct ConfigData { + value: u32, +} + +impl ReadDataByIdentifier for ConfigData { + fn read(&self) -> Result { + Ok(self.value.to_be_bytes().to_vec()) + } +} + +impl WriteDataByIdentifier for ConfigData { + fn write(&mut self, data: ByteSlice) -> Result<()> { + if data.len() == 4 { + self.value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + Ok(()) + } else { + Err(ErrorCode::InvalidInput("Expected 4 bytes".into())) + } + } +} + +// DataIdentifier is automatically implemented for types implementing both traits + +fn register_config_data() -> Result { + DiagnosticServicesCollectionBuilder::new() + .with_data_id( + VehicleDataIdentifier::EngineRPM.as_uds_did(), + ConfigData { value: 0 } + ) + .build() +} +``` + +### UDS: RoutineControl + +```rust +use opensovd_diag::uds::{RoutineControl, DiagnosticServicesCollectionBuilder}; +use opensovd_diag::error::Result; +use opensovd_diag::types::{ByteVec, ByteSlice}; + +pub enum VehicleRoutineIdentifier { + SelfTest, + EngineCalibration, + BrakeBleeding, +} + +impl VehicleRoutineIdentifier { + pub fn as_uds_routine_id(&self) -> &'static str { + match self { + Self::SelfTest => "0xFF00", + Self::EngineCalibration => "0xFF01", + Self::BrakeBleeding => "0xFF02", + } + } +} + +struct MyRoutine { + state: u8, +} + +impl RoutineControl for MyRoutine { + fn start(&mut self, params: ByteSlice) -> Result { + // implement your logic for start routine here + self.state = 1; + Ok(vec![0x00]) + } + + fn stop(&mut self, params: ByteSlice) -> Result { + // implement your logic for stop routine here + self.state = 0; + Ok(vec![0x00]) + } + + fn request_results(&self, params: ByteSlice) -> Result { + // implement your logic for request routine results here + Ok(vec![0x12, 0x34, self.state]) + } +} + +fn register_my_routine() -> Result { + DiagnosticServicesCollectionBuilder::new() + .with_routine( + VehicleRoutineIdentifier::SelfTest.as_uds_routine_id(), + MyRoutine { state: 0 } + ) + .build() +} +``` + +### SOVD: ReadOnlyDataResource + +```rust +use opensovd_diag::sovd::{ + self, ReadOnlyDataResource, DiagnosticServicesCollectionBuilder, + DiagnosticEntity, JsonDataReply +}; +use serde_json::json; +use std::sync::Arc; + +struct VehicleInfoResource; + +#[async_trait::async_trait] +impl ReadOnlyDataResource for VehicleInfoResource { + async fn read(&self) -> sovd::Result { + Ok(JsonDataReply::new(json!({ + "vin": "WBADT43452G296706" + }))) + } +} + +fn register_vehicle_info<'entity>( + entity: Arc +) -> sovd::Result> { + DiagnosticServicesCollectionBuilder::new(entity) + .with_read_resource( + "vehicle_info", + VehicleInfoResource, + json!({ + "type": "object", + "properties": { + "vin": { "type": "string" } + }, + "required": ["vin"] + }) + ) + .build() +} +``` + +### SOVD: WritableDataResource + +```rust +use opensovd_diag::sovd::{ + self, WritableDataResource, DiagnosticServicesCollectionBuilder, + DiagnosticEntity, DiagnosticRequest +}; +use std::sync::Arc; + +struct ConfigResource { + config: serde_json::Value, +} + +impl WritableDataResource for ConfigResource { + fn write(&mut self, request: DiagnosticRequest) -> sovd::Result<()> { + // parse `request.data` and persist it here as required by your needs + self.config = request.data.clone(); + Ok(()) + } +} + +fn register_config_resource<'entity>( + entity: Arc +) -> sovd::Result> { + DiagnosticServicesCollectionBuilder::new(entity) + .with_write_resource( + "config", + ConfigResource { config: serde_json::Value::Null }, + json!({ + "type": "object", + "additionalProperties": true + }) + ) + .build() +} +``` + +### SOVD: DataResource (Read & Write) + +```rust +use opensovd_diag::sovd::{ + self, ReadOnlyDataResource, WritableDataResource, DataResource, + DiagnosticServicesCollectionBuilder, DiagnosticEntity, + JsonDataReply, DiagnosticRequest, Error +}; +use serde_json::json; +use std::sync::Arc; + +struct ParameterResource { + value: i32, +} + +#[async_trait::async_trait] +impl ReadOnlyDataResource for ParameterResource { + async fn read(&self) -> sovd::Result { + Ok(JsonDataReply::new(json!({ "parameter": self.value }))) + } +} + +impl WritableDataResource for ParameterResource { + fn write(&mut self, request: DiagnosticRequest) -> sovd::Result<()> { + if let Some(value) = request.data.get("parameter").and_then(|v| v.as_i64()) { + self.value = value as i32; + Ok(()) + } else { + Err(Error::new( + "INVALID_DATA", + "BMW_001", + "Missing or invalid 'parameter' field" + )) + } + } +} + +// DataResource is automatically implemented for types implementing both traits + +fn register_parameter_resource<'entity>( + entity: Arc +) -> sovd::Result> { + DiagnosticServicesCollectionBuilder::new(entity) + .with_data_resource( + "parameter", + ParameterResource { value: 0 }, + json!({ + "type": "object", + "properties": { + "parameter": { "type": "integer", "format": "int32" } + }, + "required": ["parameter"] + }) + ) + .build() +} +``` + +### SOVD: Operation + +```rust +use opensovd_diag::sovd::{ + self, Operation, DiagnosticServicesCollectionBuilder, DiagnosticEntity, + DiagnosticRequest, OperationInfoReply, OperationStatusReply, + ExecuteOperationReply, OperationExecutionStatus, JsonDataReply +}; +use std::sync::Arc; + +struct SelfTestOperation { + status: OperationExecutionStatus, +} + +impl SelfTestOperation { + fn new() -> Self { + Self { status: OperationExecutionStatus::Stopped } + } +} + +#[async_trait::async_trait] +impl Operation for SelfTestOperation { + // Query methods are ASYNC (may fetch from remote ECUs) + async fn info(&self, request: DiagnosticRequest) -> sovd::Result { + Ok(OperationInfoReply::default()) + } + + async fn status(&self, request: DiagnosticRequest) -> sovd::Result { + Ok(OperationStatusReply::new( + serde_json::json!({}), + self.status + )) + } + + // Control methods are SYNC NON-BLOCKING (return status immediately) + fn execute(&mut self, request: DiagnosticRequest) -> sovd::Result { + self.status = OperationExecutionStatus::Running; + Ok(ExecuteOperationReply::new( + serde_json::json!({}), + OperationExecutionStatus::Running + )) + } + + fn resume(&mut self, request: DiagnosticRequest) -> sovd::Result { + if self.status == OperationExecutionStatus::Stopped { + self.status = OperationExecutionStatus::Running; + } + Ok(ExecuteOperationReply::new( + serde_json::json!({}), + self.status + )) + } + + fn reset(&mut self, request: DiagnosticRequest) -> sovd::Result { + self.status = OperationExecutionStatus::Stopped; + Ok(ExecuteOperationReply::new( + serde_json::json!({}), + OperationExecutionStatus::Stopped + )) + } + + fn stop(&mut self, request: DiagnosticRequest) -> sovd::Result<()> { + self.status = OperationExecutionStatus::Stopped; + Ok(()) + } +} + +fn register_operation<'entity>( + entity: Arc +) -> sovd::Result> { + DiagnosticServicesCollectionBuilder::new(entity) + .with_operation("self_test", SelfTestOperation::new()) + .build() +} +``` \ No newline at end of file diff --git a/docs/design/rust_abstraction_layer_api_for_user_applications.puml b/docs/design/rust_abstraction_layer_api_for_user_applications.puml new file mode 100644 index 0000000..9c1d3d1 --- /dev/null +++ b/docs/design/rust_abstraction_layer_api_for_user_applications.puml @@ -0,0 +1,685 @@ +@startuml rust_abstraction_layer_api_for_user_applications + +skinparam classAttributeIconSize 0 +skinparam classFontSize 11 +skinparam packageStyle rectangle + +package "Design Goals" as DesignGoals {} +package "see rust_abstraction_layer_api_for_user_applications.md" as DesignGoals.Reference {} + +package "opensovd_diag" { + enum ErrorCode { + <> + Unknown + InternalError + } + + entity "Result" as DiagResult { + type alias for std::result::Result + } + + entity ByteSlice { + <> + ByteSlice<'a> = &'a [u8] + } + + entity ByteVec { + <> + ByteVec = Vec + } + + abstract class DiagnosticServicesCollection { + no public methods are defined since lifetime control of contained diagnostic services is the only goal of this class + Automatic cleanup on Drop + } +} + +package "opensovd_diag::sovd" { + class "Error" as SovdError { + <> + +new(sovd_error: impl Into, vendor_error: impl Into, vendor_message: impl Into) -> Self + +sovd_error(&self) -> &str + +vendor_error(&self) -> &str + +vendor_message(&self) -> &str + -- + -sovd_error: String + -vendor_error: String + -vendor_message: String + } + + class "Result" as Result { + type alias for std::result::Result + } + + entity Properties { + <> + type alias for IndexMap + Generic key-value properties (not HTTP-specific) + } + + entity JsonValue { + <> + type alias for serde_json::Value + } + + entity Duration { + <> + type alias for std::time::Duration + } + + entity SystemTime { + <> + type alias for std::time::SystemTime + } + + class TranslationId { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + class DataCategoryId { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + class DataGroupId { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + class JsonSchema { + <> + <> + +new(value: JsonValue) -> Self + +value(&self) -> &JsonValue + +into_value(self) -> JsonValue + -- + -value: JsonValue + } + + class ResourceIdentifier { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + class DataGroupShortDesc { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + enum DataCategoryIdentifier { + <> + Identification + Measurement + Parameter + SysInfo + -- + +as_str(&self) -> &'static str + } + + enum OperationInvocationPolicy { + <> + PerformsSynchronousInvocation + RequiresIndividualAsyncInvocations + SupportsConcurrentAsyncInvocations + } + + enum ProximityProof { + <> + Required + NotRequired + } + + enum DiagnosticEntityKind { + <> + Application + } + + class DiagnosticEntityMode { + <> + +new(id: impl Into, name: impl Into) -> Self + + +with_translation_id(self, translation_id: impl Into) -> Self + +with_values(self, values: Vec) -> Self + + +id(&self) -> &str + +name(&self) -> &str + +translation_id(&self) -> Option<&str> + +values(&self) -> &[String] + -- + -id: String + -name: String + -translation_id: Option + -values: Vec + } + + interface DiagnosticEntity <> { + {abstract} +kind(&self) -> DiagnosticEntityKind + {abstract} +async supported_modes(&self) -> Result> + {abstract} +async apply_mode(&mut self, mode_id: &str, mode_value: &str, expiration_timeout: Option) -> Result<()> + } + note right of DiagnosticEntity + Requires #[async_trait] for object safety + + Lock/unlock methods currently not included. + Pending clarification on whether locking is + application or SOVD server responsibility. + + Blanket impls provided by crate: &T, &mut T, Box, Arc, Rc + Enables flexible ownership in builder new() without overhead.new(entity) + end note + + class "DiagnosticServicesCollection<'entity>" as SovdDiagnosticServicesCollection { + +get_read_resource(&self, id: &str) -> Option<&dyn ReadOnlyDataResource> + +get_write_resource_mut(&mut self, id: &str) -> Option<&mut dyn WritableDataResource> + +get_operation(&self, id: &str) -> Option<&dyn Operation> + +get_operation_mut(&mut self, id: &str) -> Option<&mut dyn Operation> + +entity(&self) -> &dyn DiagnosticEntity + -- + -entity: Box + -read_resources: IndexMap> + -write_resources: IndexMap> + -operations: IndexMap> + } + note right of SovdDiagnosticServicesCollection + Lifetime annotation 'entity allows non-'static entity types. + Entity owned via type erasure (no circular dependency) + Automatic cleanup on Drop + end note + + class "DiagnosticServicesCollectionBuilder<'entity>" as SovdDiagnosticServicesCollectionBuilder { + +new(entity: T) -> Self + + +with_read_resource(self, id: impl Into, resource: T) -> Self + +with_write_resource(self, id: impl Into, resource: T) -> Self + +with_data_resource(self, id: impl Into, resource: T) -> Self + +with_operation(self, id: impl Into, operation: T) -> Self + + .. final step .. + +build(self) -> Result> + -- + -entity: Box + -read_resources: IndexMap> + -write_resources: IndexMap> + -operations: IndexMap> + } +} + +package "opensovd_diag::sovd" { + class DiagnosticRequest { + <> + +new(data: JsonValue) -> Self + + +with_property(self, key: impl Into, value: impl Into) -> Self + +with_properties(self, properties: IndexMap) -> Self + +with_proximity_response(self, response: impl Into) -> Self + + +property(&self, key: &str) -> Option<&String> + +properties(&self) -> &IndexMap + -- + -properties: IndexMap + -proximity_response: Option + -data: JsonValue + } + + class DiagnosticReply { + <> + +new() -> Self + + +properties(&self) -> &IndexMap + +properties_mut(&mut self) -> &mut IndexMap + +set_property(&mut self, key: impl Into, value: impl Into) + +with_property(self, key: impl Into, value: impl Into) -> Self + -- + -properties: IndexMap + } + + class ProximityChallenge { + <> + +new(challenge: impl Into, valid_until: SystemTime) -> Self + +challenge(&self) -> &str + +valid_until(&self) -> SystemTime + -- + -challenge: String + -valid_until: SystemTime + } + + class JsonDataReply { + <> + +new(data: JsonValue) -> Self + + +data(&self) -> &JsonValue + +into_data(self) -> JsonValue + +properties(&self) -> &IndexMap + +properties_mut(&mut self) -> &mut IndexMap + +with_property(self, key: impl Into, value: impl Into) -> Self + -- + -base: DiagnosticReply + -data: JsonValue + } + + interface ReadOnlyDataResource <> { + API definition for read-only SOVD data resources + + {abstract} +async read(&self) -> Result + } + + interface WritableDataResource <> { + API definition for writable SOVD data resources + + {abstract} +write(&mut self, request: DiagnosticRequest) -> Result<()> + } + + interface DataResource <> { + API definition for SOVD data resources + } + + enum OperationExecutionStatus { + <> + Failed + Running + Stopped + Completed + } + + class OperationInfoReply { + <> + +new(data: JsonValue) -> Self + + +with_proximity_challenge(self, challenge: ProximityChallenge) -> Self + +with_supported_modes(self, modes: Vec) -> Self + + +data(&self) -> &JsonValue + +proximity_challenge(&self) -> Option<&ProximityChallenge> + +supported_modes(&self) -> &[DiagnosticEntityMode] + -- + -base: JsonDataReply + -proximity_challenge: Option + -supported_modes: Vec + } + + class OperationStatusReply { + <> + +new(data: JsonValue, status: OperationExecutionStatus) -> Self + + +with_capability(self, capability: impl Into) -> Self + +with_parameters(self, parameters: JsonValue) -> Self + + +data(&self) -> &JsonValue + +status(&self) -> OperationExecutionStatus + +capability(&self) -> &str + +parameters(&self) -> &JsonValue + -- + -base: JsonDataReply + -status: OperationExecutionStatus + -capability: String + -parameters: JsonValue + } + + class ExecuteOperationReply { + <> + +new(data: JsonValue, status: OperationExecutionStatus) -> Self + + +status(&self) -> OperationExecutionStatus + +data(&self) -> &JsonValue + -- + -base: JsonDataReply + -status: OperationExecutionStatus + } + + interface Operation <> { + API definition for SOVD operations + + .. query methods (async) .. + {abstract} +async info(&self, request: DiagnosticRequest) -> Result + {abstract} +async status(&self, request: DiagnosticRequest) -> Result + + .. control methods (sync non-blocking) .. + {abstract} +execute(&mut self, request: DiagnosticRequest) -> Result + {abstract} +resume(&mut self, request: DiagnosticRequest) -> Result + {abstract} +reset(&mut self, request: DiagnosticRequest) -> Result + {abstract} +stop(&mut self, request: DiagnosticRequest) -> Result<()> + + .. OEM-specific capabilities .. + .. provided with default implementations .. + +async handle(&mut self, request: DiagnosticRequest) -> Result + } +} + +package "opensovd_diag::uds" { + class ServiceIdentifier { + <> + <> + +new(value: impl Into) -> Self + +as_str(&self) -> &str + -- + -value: String + } + + interface ReadDataByIdentifier <> { + API definition for UDS Service 'Read Data by Identifier' + + {abstract} +read(&self) -> DiagResult + } + + interface WriteDataByIdentifier <> { + API definition for UDS Service 'Write Data by Identifier' + + {abstract} +write(&mut self, data: ByteSlice<'_>) -> DiagResult<()> + } + + interface DataIdentifier <> { + API definition for UDS DataIdentifier (Read + Write) + <> + Blanket impl for T: ReadDataByIdentifier + WriteDataByIdentifier + } + + interface RoutineControl <> { + API definition for UDS RoutineControl + + {abstract} +start(&mut self, params: ByteSlice<'_>) -> DiagResult + {abstract} +stop(&mut self, params: ByteSlice<'_>) -> DiagResult + {abstract} +request_results(&self, params: ByteSlice<'_>) -> DiagResult + } + + interface UdsService <> { + API definition for a generic UDS Service + + {abstract} +handle_message(&mut self, message: ByteSlice<'_>) -> DiagResult + } + + interface UdsSerialize <> { + {abstract} +serialize(&self) -> DiagResult + } + + interface UdsDeserialize <> { + {abstract} +deserialize(data: ByteSlice<'_>) -> DiagResult + } + + interface WriteHandler <> { + API definition for handling write operations + + {abstract} +handle_write(&mut self, data: T) -> DiagResult<()> + } + + interface RoutineHandler <> { + API definition for handling routine control operations + + {abstract} +start(&mut self, params: T) -> DiagResult + {abstract} +stop(&mut self, params: T) -> DiagResult + {abstract} +results(&self, params: T) -> DiagResult + } + + class "SerializedReadDataByIdentifier" as SerializedReadDataByIdentifier { + <> + where T: UdsSerialize + Send + 'static + Automatically implements ReadDataByIdentifier + by serializing T into UDS byte format + + +new(data: T) -> Self + +read(&self) -> DiagResult + -- + -data: T + } + class "SerializedWriteDataByIdentifier" as SerializedWriteDataByIdentifier { + <> + where T: UdsDeserialize + Send + 'static + where H: WriteHandler + Send + 'static + Automatically implements WriteDataByIdentifier + by deserializing UDS bytes into T and calling handler + + +new(handler: H) -> Self + +write(&mut self, data: ByteSlice<'_>) -> DiagResult<()> + -- + -handler: H + } + + class "SerializedDataByIdentifier" as SerializedDataByIdentifier { + <> + where T: UdsSerialize + UdsDeserialize + Send + 'static + where H: WriteHandler + Send + 'static + Automatically implements DataIdentifier (both read & write) + + +new(initial_data: T, write_handler: H) -> Self + +read(&self) -> DiagResult + +write(&mut self, data: ByteSlice<'_>) -> DiagResult<()> + -- + -data: T + -write_handler: H + } + + class "SerializedRoutineControl" as SerializedRoutineControl { + <> + where T: UdsSerialize + UdsDeserialize + Send + 'static + where H: RoutineHandler + Send + 'static + Automatically implements RoutineControl + + +new(handler: H) -> Self + +start(&mut self, params: ByteSlice<'_>) -> DiagResult + +stop(&mut self, params: ByteSlice<'_>) -> DiagResult + +request_results(&self, params: ByteSlice<'_>) -> DiagResult + -- + -handler: H + } + + enum UdsResponseCode { + <> + GeneralReject + ServiceNotSupported + SubFunctionNotSupported + IncorrectMessageLengthOrInvalidFormat + ResponseTooLong + BusyRepeatRequest + ConditionsNotCorrect + NoResponseFromSubnetComponent + RequestSequenceError + NoResponseFromSubNetComponent + FailurePreventsExecutionOfRequestedAction + RequestOutOfRange + SecurityAccessDenied + AuthenticationRequired + InvalidKey + ExceededNumberOfAttempts + RequiredTimeDelayNotExpired + SecureDataTransmissionRequired + SecureDataTransmissionNotAllowed + SecureDataVerificationFailed + CertificateVerificationFailedInvalidTimePeriod + CertificateVerificationFailedInvalidSignature + CertificateVerificationFailedInvalidChainOfTrust + CertificateVerificationFailedInvalidType + CertificateVerificationFailedInvalidFormat + CertificateVerificationFailedInvalidContent + CertificateVerificationFailedInvalidScope + CertificateVerificationFailedInvalidCertificate + OwnershipVerificationFailed + ChallengeCalculationFailed + SettingAccessRightsFailed + SessionKeyCreationOrDerivationFailed + ConfigurationDataUsageFailed + DeAuthenticationFailed + UploadDownloadNotAccepted + TransferDataSuspended + GeneralProgrammingFailure + WrongBlockSequenceCounter + RequestCorrectlyReceivedResponsePending + SubFunctionNotSupportedInActiveSession + ServiceNotSupportedInActiveSession + RpmTooHigh + RpmTooLow + EngineIsRunning + EngineIsNotRunning + EngineRunTimeTooLow + TemperatureTooHigh + TemperatureTooLow + VehicleSpeedTooHigh + VehicleSpeedTooLow + ThrottleOrPedalTooHigh + ThrottleOrPedalTooLow + TransmissionRangeNotInNeutral + TransmissionRangeNotInGear + BrakeSwitchOrSwitchesNotClosed + ShifterLeverNotInPark + TorqueConverterClutchLocked + VoltageTooHigh + VoltageTooLow + ResourceTemporarilyNotAvailable + -- + +as_byte(&self) -> u8 + +from_byte(value: u8) -> Option + } + + class "DiagnosticServicesCollection" as UdsDiagnosticServicesCollection { + +get_read_did(&self, id: &str) -> Option<&dyn ReadDataByIdentifier> + +get_write_did_mut(&mut self, id: &str) -> Option<&mut dyn WriteDataByIdentifier> + +get_routine(&self, id: &str) -> Option<&dyn RoutineControl> + +get_routine_mut(&mut self, id: &str) -> Option<&mut dyn RoutineControl> + +get_service_mut(&mut self, id: &str) -> Option<&mut dyn UdsService> + -- + -read_dids: IndexMap> + -write_dids: IndexMap> + -routines: IndexMap> + -services: IndexMap> + } + + class "DiagnosticServicesCollectionBuilder" as UdsDiagnosticServicesCollectionBuilder { + +new() -> Self + + +with_data_id(self, id: impl Into, service: T) -> Self + +with_read_did(self, id: impl Into, service: T) -> Self + +with_write_did(self, id: impl Into, service: T) -> Self + +with_routine(self, id: impl Into, service: T) -> Self + +with_service(self, id: impl Into, service: T) -> Self + + .. final step .. + +build(self) -> DiagResult + -- + -read_dids: IndexMap> + -write_dids: IndexMap> + -routines: IndexMap> + -services: IndexMap> + } + + class SerializationHelper { + <> + +serialize_response(payload: &T) -> DiagResult + +deserialize_request(data: ByteSlice<'_>, handler: F) -> DiagResult<()> + } +} + +' Relationships +UdsDiagnosticServicesCollectionBuilder --> UdsDiagnosticServicesCollection : creates +UdsDiagnosticServicesCollectionBuilder ..> DataIdentifier : accepts (moved into Box) +UdsDiagnosticServicesCollectionBuilder ..> ReadDataByIdentifier : accepts (moved into Box) +UdsDiagnosticServicesCollectionBuilder ..> WriteDataByIdentifier : accepts (moved into Box) +UdsDiagnosticServicesCollectionBuilder ..> RoutineControl : accepts (moved into Box) +UdsDiagnosticServicesCollectionBuilder ..> UdsService : accepts (moved into Box) + +UdsDiagnosticServicesCollection --|> DiagnosticServicesCollection : implements + +DataIdentifier ..> ReadDataByIdentifier : requires +DataIdentifier ..> WriteDataByIdentifier : requires + +SerializedReadDataByIdentifier ..|> ReadDataByIdentifier : implements +SerializedWriteDataByIdentifier ..|> WriteDataByIdentifier : implements +SerializedDataByIdentifier ..|> DataIdentifier : implements +SerializedRoutineControl ..|> RoutineControl : implements + +UdsSerialize ..> ByteVec : produces +UdsDeserialize ..> ByteSlice : borrows + +WriteDataByIdentifier ..> ByteSlice : borrows +RoutineControl ..> ByteSlice : borrows +UdsService ..> ByteSlice : borrows + +ReadDataByIdentifier ..> ByteVec : produces +RoutineControl ..> ByteVec : produces +UdsService ..> ByteVec : produces + +SerializationHelper ..> UdsResponseCode : uses +SerializedReadDataByIdentifier ..> SerializationHelper : uses +SerializedWriteDataByIdentifier ..> SerializationHelper : uses +SerializedDataByIdentifier ..> SerializationHelper : uses +SerializedRoutineControl ..> SerializationHelper : uses + +SovdDiagnosticServicesCollectionBuilder --> SovdDiagnosticServicesCollection : creates +SovdDiagnosticServicesCollectionBuilder ..> DiagnosticEntity : takes ownership (moved into Box) +SovdDiagnosticServicesCollectionBuilder ..> ReadOnlyDataResource : accepts (moved into Box) +SovdDiagnosticServicesCollectionBuilder ..> WritableDataResource : accepts (moved into Box) +SovdDiagnosticServicesCollectionBuilder ..> DataResource : accepts (moved into Box) +SovdDiagnosticServicesCollectionBuilder ..> Operation : accepts (moved into Box) + +OperationInvocationPolicy ..> SovdDiagnosticServicesCollectionBuilder : users provide +DataCategoryIdentifier ..> SovdDiagnosticServicesCollectionBuilder : users provide +TranslationId ..> SovdDiagnosticServicesCollectionBuilder : users provide +DataCategoryId ..> SovdDiagnosticServicesCollectionBuilder : users provide +DataGroupId ..> SovdDiagnosticServicesCollectionBuilder : users provide +DataGroupShortDesc ..> SovdDiagnosticServicesCollectionBuilder : users provide +JsonSchema ..> SovdDiagnosticServicesCollectionBuilder : users provide +ResourceIdentifier ..> SovdDiagnosticServicesCollectionBuilder : users provide +ProximityProof ..> SovdDiagnosticServicesCollectionBuilder : users provide + +SovdDiagnosticServicesCollection --|> DiagnosticServicesCollection : implements +SovdDiagnosticServicesCollection *--> DiagnosticEntity : owns (boxed) + +DiagnosticEntity --> DiagnosticEntityMode : supports +DiagnosticEntity --> DiagnosticEntityKind : is of + +DataResource ..> ReadOnlyDataResource : requires +DataResource ..> WritableDataResource : requires + +ReadOnlyDataResource ..> DiagnosticRequest : takes (moved) +ReadOnlyDataResource ..> JsonDataReply : produces +ReadOnlyDataResource ..> SovdResult : produces +WritableDataResource ..> DiagnosticRequest : takes (moved) +WritableDataResource ..> SovdResult : produces + +Operation ..> DiagnosticRequest : takes (moved) +Operation ..> ExecuteOperationReply : produces +Operation ..> OperationStatusReply : produces +Operation ..> OperationInfoReply : produces +Operation ..> JsonDataReply : produces +Operation ..> SovdResult : produces + +JsonDataReply *-- DiagnosticReply : contains + +OperationInfoReply --|> JsonDataReply : extends +OperationStatusReply --|> JsonDataReply : extends +ExecuteOperationReply --|> JsonDataReply : extends + +OperationInfoReply *-- ProximityChallenge : optional +OperationStatusReply *-- OperationExecutionStatus : contains +ExecuteOperationReply *-- OperationExecutionStatus : contains + +SovdResult *-- SovdError : error variant +DiagResult *-- ErrorCode : error variant + +note right of UdsDiagnosticServicesCollectionBuilder : Builder pattern using move semantics.\nServices passed to with_* methods are consumed (ownership transferred). + +note right of SovdDiagnosticServicesCollectionBuilder : Builder with lifetime 'entity parameter and move semantics.\nEntity ownership acquired via new(entity: T).\nType erasure to Box.\nOne Box allocation (acceptable, can use smallbox if needed). + +note bottom of DataIdentifier : Blanket implementation:\nimpl DataIdentifier for T\nwhere T: ReadDataByIdentifier + WriteDataByIdentifier + +note bottom of DataResource : Blanket implementation:\nimpl DataResource for T\nwhere T: ReadOnlyDataResource + WritableDataResource + +@enduml \ No newline at end of file diff --git a/docs/design/rust_abstraction_layer_api_for_user_applications.svg b/docs/design/rust_abstraction_layer_api_for_user_applications.svg new file mode 100644 index 0000000..ea3897d --- /dev/null +++ b/docs/design/rust_abstraction_layer_api_for_user_applications.svg @@ -0,0 +1,1026 @@ +opensovd_diagopensovd_diag::sovdopensovd_diag::udsDesign Goalssee rust_abstraction_layer_api_for_user_applications.mdErrorCode«Copy, Clone, Debug, PartialEq, Eq»UnknownInternalErrorResultTtype alias for std::result::Result<T, ErrorCode>ByteSlice«type alias»ByteSlice<'a> = &'a [u8]ByteVec«type alias»ByteVec = Vec<u8>DiagnosticServicesCollectionno public methods are defined since lifetime control of contained diagnostic services is the only goal of this classAutomatic cleanup on DropError«Debug, Clone»+new(sovd_error: impl Into<String>, vendor_error: impl Into<String>, vendor_message: impl Into<String>) -> Self+sovd_error(&self) -> &str+vendor_error(&self) -> &str+vendor_message(&self) -> &str-sovd_error: String-vendor_error: String-vendor_message: StringResultTtype alias for std::result::Result<T, Error>Properties«type alias»type alias for IndexMap<String, String>Generic key-value properties (not HTTP-specific)JsonValue«type alias»type alias for serde_json::ValueDuration«type alias»type alias for std::time::DurationSystemTime«type alias»type alias for std::time::SystemTimeTranslationId«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: StringDataCategoryId«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: StringDataGroupId«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: StringJsonSchema«newtype»«Debug, Clone, PartialEq»+new(value: JsonValue) -> Self+value(&self) -> &JsonValue+into_value(self) -> JsonValue-value: JsonValueResourceIdentifier«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: StringDataGroupShortDesc«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: StringDataCategoryIdentifier«Copy, Clone, Debug, PartialEq, Eq»IdentificationMeasurementParameterSysInfo+as_str(&self) -> &'static strOperationInvocationPolicy«Copy, Clone, Debug, PartialEq, Eq»PerformsSynchronousInvocationRequiresIndividualAsyncInvocationsSupportsConcurrentAsyncInvocationsProximityProof«Copy, Clone, Debug, PartialEq, Eq»RequiredNotRequiredDiagnosticEntityKind«Copy, Clone, Debug, PartialEq, Eq»ApplicationDiagnosticEntityMode«Debug, Clone»+new(id: impl Into<String>, name: impl Into<String>) -> Self+with_translation_id(self, translation_id: impl Into<String>) -> Self+with_values(self, values: Vec<String>) -> Self+id(&self) -> &str+name(&self) -> &str+translation_id(&self) -> Option+values(&self) -> &[String]-id: String-name: String-translation_id: Option<String>-values: Vec<String>«trait»DiagnosticEntity+kind(&self) -> DiagnosticEntityKind+async supported_modes(&self) -> Result<Vec<DiagnosticEntityMode>>+async apply_mode(&mut self, mode_id: &str, mode_value: &str, expiration_timeout: Option<Duration>) -> Result<()>Requires #[async_trait] for object safetyLock/unlock methods currently not included.Pending clarification on whether locking isapplication or SOVD server responsibility.Blanket impls provided by crate: &T, &mut T, Box<T>, Arc<T>, Rc<T>Enables flexible ownership in builder new() without overhead.new<T>(entity)Thread Safety: Send + Sync- Has both &self (kind, supported_modes) and &mut self (apply_mode)- Pattern: Mixed methods → Send + SyncDiagnosticServicesCollection'entity+get_read_resource(&self, id: &str) -> Option<&dyn ReadOnlyDataResource>+get_write_resource_mut(&mut self, id: &str) -> Option<&mut dyn WritableDataResource>+get_operation(&self, id: &str) -> Option<&dyn Operation>+get_operation_mut(&mut self, id: &str) -> Option<&mut dyn Operation>+entity(&self) -> &dyn DiagnosticEntity-entity: Box<dyn DiagnosticEntity + Send + Sync + 'entity>-read_resources: IndexMap<ResourceIdentifier, Box<dyn ReadOnlyDataResource + Send + Sync>>-write_resources: IndexMap<ResourceIdentifier, Box<dyn WritableDataResource + Send>>-operations: IndexMap<ResourceIdentifier, Box<dyn Operation + Send + Sync>>Lifetime annotation 'entity allows non-'static entity types.Entity owned via type erasure (no circular dependency)Automatic cleanup on DropDiagnosticServicesCollectionBuilder'entity+new<T: DiagnosticEntity + Send + Sync + 'entity>(entity: T) -> Self+with_read_resource<T: ReadOnlyDataResource + Send + Sync + 'static>(self, id: impl Into<String>, resource: T) -> Self+with_write_resource<T: WritableDataResource + Send + 'static>(self, id: impl Into<String>, resource: T) -> Self+with_data_resource<T: DataResource + Send + Sync + 'static>(self, id: impl Into<String>, resource: T) -> Self+with_operation<T: Operation + Send + Sync + 'static>(self, id: impl Into<String>, operation: T) -> Self+build(self) -> Result<DiagnosticServicesCollection<'entity>>final step-entity: Box<dyn DiagnosticEntity + Send + Sync + 'entity>-read_resources: IndexMap<ResourceIdentifier, Box<dyn ReadOnlyDataResource + Send + Sync>>-write_resources: IndexMap<ResourceIdentifier, Box<dyn WritableDataResource + Send>>-operations: IndexMap<ResourceIdentifier, Box<dyn Operation + Send + Sync>>DiagnosticRequest«Debug, Clone»+new(data: JsonValue) -> Self+with_property(self, key: impl Into<String>, value: impl Into<String>) -> Self+with_properties(self, properties: IndexMap<String, String>) -> Self+with_proximity_response(self, response: impl Into<String>) -> Self+property(&self, key: &str) -> Option+properties(&self) -> &IndexMap<String, String>-properties: IndexMap<String, String>-proximity_response: Option<String>-data: JsonValueProperties are protocol-independent key-value pairs.Binding layer selectively passes application-relevant context(e.g., language, client ID) while filtering sensitive data(tokens, credentials) from HTTP headers.DiagnosticReply«Debug, Clone, Default»+new() -> Self+properties(&self) -> &IndexMap<String, String>+properties_mut(&mut self) -> &mut IndexMap<String, String>+set_property(&mut self, key: impl Into<String>, value: impl Into<String>)+with_property(self, key: impl Into<String>, value: impl Into<String>) -> Self-properties: IndexMap<String, String>Properties for response metadata (not HTTP headers).Binding layer converts these to appropriate protocol format.ProximityChallenge«Debug, Clone»+new(challenge: impl Into<String>, valid_until: SystemTime) -> Self+challenge(&self) -> &str+valid_until(&self) -> SystemTime-challenge: String-valid_until: SystemTimeJsonDataReply«Debug, Clone»+new(data: JsonValue) -> Self+data(&self) -> &JsonValue+into_data(self) -> JsonValue+properties(&self) -> &IndexMap<String, String>+properties_mut(&mut self) -> &mut IndexMap<String, String>+with_property(self, key: impl Into<String>, value: impl Into<String>) -> Self-base: DiagnosticReply-data: JsonValue«trait»ReadOnlyDataResourceAPI definition for read-only SOVD data resources+async read(&self) -> Result<JsonDataReply>Thread Safety: Send + SyncRationale:- Only has &self method (immutable access)- Can be safely shared across threads- Multiple threads can call read() concurrentlyAsync Reasoning:- SOVD operates over HTTP/REST (network I/O)- May fetch data from remote ECUs/services- Prevents blocking threads during network calls- Enables concurrent request handling«trait»WritableDataResourceAPI definition for writable SOVD data resources+write(&mut self, request: DiagnosticRequest) -> Result<()>Thread Safety: Send only (NOT Sync)Rationale:- Only has &mut self method (mutable exclusive access)- Cannot be safely shared across threads- Can be sent between threads but not accessed concurrently- Rust ensures exclusive access for mutationSync Non-Blocking Pattern:- Returns Result<()> immediately (no await)- Application manages state internally- Matches ISO 17978 HTTP PUT/POST pattern- No tokio dependency in application layer- Clearer semantics than implicit async behavior«trait»DataResourceAPI definition for SOVD data resourcesThread Safety: Send + SyncRationale:- Blanket impl for T: ReadOnlyDataResource + WritableDataResource- Has both &self (get) and &mut self (put) methods- Sync because &self methods can be called concurrently- Rust ensures &mut self requires exclusive accessPattern: Mixed immutable/mutable access → Send + SyncOperationExecutionStatus«Copy, Clone, Debug, PartialEq, Eq»FailedRunningStoppedCompletedOperationInfoReply«Debug, Clone»+new(data: JsonValue) -> Self+with_proximity_challenge(self, challenge: ProximityChallenge) -> Self+with_supported_modes(self, modes: Vec<DiagnosticEntityMode>) -> Self+data(&self) -> &JsonValue+proximity_challenge(&self) -> Option+supported_modes(&self) -> &[DiagnosticEntityMode]-base: JsonDataReply-proximity_challenge: Option<ProximityChallenge>-supported_modes: Vec<DiagnosticEntityMode>OperationStatusReply«Debug, Clone»+new(data: JsonValue, status: OperationExecutionStatus) -> Self+with_capability(self, capability: impl Into<String>) -> Self+with_parameters(self, parameters: JsonValue) -> Self+data(&self) -> &JsonValue+status(&self) -> OperationExecutionStatus+capability(&self) -> &str+parameters(&self) -> &JsonValue-base: JsonDataReply-status: OperationExecutionStatus-capability: String-parameters: JsonValueExecuteOperationReply«Debug, Clone»+new(data: JsonValue, status: OperationExecutionStatus) -> Self+status(&self) -> OperationExecutionStatus+data(&self) -> &JsonValue-base: JsonDataReply-status: OperationExecutionStatus«trait»OperationAPI definition for SOVD operations+async info(&self, request: DiagnosticRequest) -> Result<OperationInfoReply>+async status(&self, request: DiagnosticRequest) -> Result<OperationStatusReply>query methods (async)+execute(&mut self, request: DiagnosticRequest) -> Result<ExecuteOperationReply>+resume(&mut self, request: DiagnosticRequest) -> Result<ExecuteOperationReply>+reset(&mut self, request: DiagnosticRequest) -> Result<ExecuteOperationReply>+stop(&mut self, request: DiagnosticRequest) -> Result<()>control methods (sync non-blocking)OEM-specific capabilities+async handle(&mut self, request: DiagnosticRequest) -> Result<JsonDataReply>provided with default implementationsThread Safety: Send + SyncRationale:- Has &self methods: info(), status()- Has &mut self methods: execute(), resume(), reset(), stop(), handle()- Pattern: Mixed immutable/mutable → Send + Sync- Sync allows concurrent queries (info/status)- &mut methods require exclusive access (Rust enforces)Async vs Sync Non-Blocking Pattern:- Query methods (info, status): ASYNC - Operations may be long-running (calls to ECUs/services)- Control methods (execute, resume, reset, stop): SYNC NON-BLOCKING* Return ExecuteOperationReply with OperationExecutionStatus immediately* Application manages operation state internally* Binding layer polls via status() to check completion- handle(): ASYNC - custom OEM operations may need async I/OServiceIdentifier«newtype»«Debug, Clone, PartialEq, Eq, Hash»+new(value: impl Into<String>) -> Self+as_str(&self) -> &str-value: String«trait»ReadDataByIdentifierAPI definition for UDS Service 'Read Data by Identifier'+read(&self) -> DiagResult<ByteVec>Thread Safety: Send only (NOT Sync)Rationale:- UDS is session-based, single-threaded protocol- Binding layer processes requests sequentially- No concurrent access to same service in practice- Send allows moving collection between threadsRemoving Sync enables:- Interior mutability (Cell, RefCell) for stateful DIDs- Read counters, caching, dynamic behavior- More flexible application implementations«trait»WriteDataByIdentifierAPI definition for UDS Service 'Write Data by Identifier'+write(&mut self, data: ByteSlice<'_>) -> DiagResult<()>Thread Safety: Send only (NOT Sync)Rationale:- Only has &mut self method (mutable exclusive access)- Cannot be safely shared across threads- Can be sent between threads but not accessed concurrently- Rust ensures exclusive access for mutationPattern: &mut self only → Send only«trait»DataIdentifier«trait»Blanket impl for T: ReadDataByIdentifier + WriteDataByIdentifierAPI definition for UDS DataIdentifier (Read + Write)Thread Safety: Send only (NOT Sync)Rationale:- Blanket impl for T: ReadDataByIdentifier + WriteDataByIdentifier- Inherits Send-only constraint from ReadDataByIdentifier- UDS services don't require concurrent access- Enables interior mutability for stateful servicesNote: This is a marker trait with no additional methods.Blanket implementation:impl<T> DataIdentifier for Twhere T: ReadDataByIdentifier + WriteDataByIdentifier«trait»RoutineControlAPI definition for UDS RoutineControl+start(&mut self, params: ByteSlice<'_>) -> DiagResult<ByteVec>+stop(&mut self, params: ByteSlice<'_>) -> DiagResult<ByteVec>+request_results(&self, params: ByteSlice<'_>) -> DiagResult<ByteVec>Thread Safety: Send only (NOT Sync)Rationale:- Primarily has &mut self methods (start, stop)- Even though request_results is &self, trait stored as mutable- Cannot be safely shared across threads- Routine execution requires exclusive controlPattern: Predominantly &mut self → Send onlyDesign Choice: Treat as mutable resource despite one &self method«trait»UdsServiceAPI definition for a generic UDS Service+handle_message(&mut self, message: ByteSlice<'_>) -> DiagResult<ByteVec>Thread Safety: Send only (NOT Sync)Rationale:- Only has &mut self method- Generic UDS service handler (stateful)- Cannot be safely shared across threads- Can be sent between threads but not accessed concurrentlyPattern: &mut self only → Send only«trait»UdsSerialize+serialize(&self) -> DiagResult<ByteVec>«trait»UdsDeserialize+deserialize(data: ByteSlice<'_>) -> DiagResult<Self>«trait»WriteHandlerAPI definition for handling write operations+handle_write<T>(&mut self, data: T) -> DiagResult<()>Replaces Box<dyn Fn(T) -> DiagResult<()>>Benefits:- Single trait implementation vs multiple closures- Better type safety and debuggability- Allows mutable state (&mut self)- More idiomatic Rust«trait»RoutineHandlerAPI definition for handling routine control operations+start<T>(&mut self, params: T) -> DiagResult<T>+stop<T>(&mut self, params: T) -> DiagResult<T>+results<T>(&self, params: T) -> DiagResult<T>Replaces 3x Box<dyn Fn(T) -> DiagResult<T>>Benefits:- 67% reduction in heap allocations (3 → 1)- Single trait object vs 3 separate closures- Cohesive handler implementation- Easier to test and maintainSerializedReadDataByIdentifierT«generic»where T: UdsSerialize + Send + 'staticAutomatically implements ReadDataByIdentifierby serializing T into UDS byte format+new(data: T) -> Self+read(&self) -> DiagResult<ByteVec>-data: TType Parameter Bounds:- Send: Required for moving into Box and across threads- 'static: Required for type erasure (no borrowed data)- Sync: NOT required (UDS binding is single-threaded)Removing Sync allows T to use Cell, RefCell, etc.Example: Tracking read counts, caching, dynamic stateSerializedWriteDataByIdentifierT, H«generic»where T: UdsDeserialize + Send + 'staticwhere H: WriteHandler<T> + Send + 'staticAutomatically implements WriteDataByIdentifierby deserializing UDS bytes into T and calling handler+new(handler: H) -> Self+write(&mut self, data: ByteSlice<'_>) -> DiagResult<()>-handler: HNow uses WriteHandler<T> trait instead of closure.Benefits:- No Box allocation if H is concrete type- Can Box if needed: SerializedWriteDataByIdentifier<T, Box<dyn WriteHandler<T>>>- More flexible: handler can have mutable stateSerializedDataByIdentifierT, H«generic»where T: UdsSerialize + UdsDeserialize + Send + 'staticwhere H: WriteHandler<T> + Send + 'staticAutomatically implements DataIdentifier (both read & write)+new(initial_data: T, write_handler: H) -> Self+read(&self) -> DiagResult<ByteVec>+write(&mut self, data: ByteSlice<'_>) -> DiagResult<()>-data: T-write_handler: HNow uses WriteHandler<T> trait instead of closure.Type Parameter Bounds:- UdsSerialize: For read() serialization- UdsDeserialize: For write() deserialization- Send: Required for type erasure into Box- 'static: Required for type erasure (no borrowed data)- Clone: NOT required (removed - unnecessarily restrictive)Can be used with concrete handler or trait object:- Direct: SerializedDataByIdentifier<T, MyHandler>- Boxed: SerializedDataByIdentifier<T, Box<dyn WriteHandler<T>>>SerializedRoutineControlT, H«generic»where T: UdsSerialize + UdsDeserialize + Send + 'staticwhere H: RoutineHandler<T> + Send + 'staticAutomatically implements RoutineControl+new(handler: H) -> Self+start(&mut self, params: ByteSlice<'_>) -> DiagResult<ByteVec>+stop(&mut self, params: ByteSlice<'_>) -> DiagResult<ByteVec>+request_results(&self, params: ByteSlice<'_>) -> DiagResult<ByteVec>-handler: HUdsResponseCode«Copy, Clone, Debug, PartialEq, Eq»GeneralRejectServiceNotSupportedSubFunctionNotSupportedIncorrectMessageLengthOrInvalidFormatResponseTooLongBusyRepeatRequestConditionsNotCorrectNoResponseFromSubnetComponentRequestSequenceErrorNoResponseFromSubNetComponentFailurePreventsExecutionOfRequestedActionRequestOutOfRangeSecurityAccessDeniedAuthenticationRequiredInvalidKeyExceededNumberOfAttemptsRequiredTimeDelayNotExpiredSecureDataTransmissionRequiredSecureDataTransmissionNotAllowedSecureDataVerificationFailedCertificateVerificationFailedInvalidTimePeriodCertificateVerificationFailedInvalidSignatureCertificateVerificationFailedInvalidChainOfTrustCertificateVerificationFailedInvalidTypeCertificateVerificationFailedInvalidFormatCertificateVerificationFailedInvalidContentCertificateVerificationFailedInvalidScopeCertificateVerificationFailedInvalidCertificateOwnershipVerificationFailedChallengeCalculationFailedSettingAccessRightsFailedSessionKeyCreationOrDerivationFailedConfigurationDataUsageFailedDeAuthenticationFailedUploadDownloadNotAcceptedTransferDataSuspendedGeneralProgrammingFailureWrongBlockSequenceCounterRequestCorrectlyReceivedResponsePendingSubFunctionNotSupportedInActiveSessionServiceNotSupportedInActiveSessionRpmTooHighRpmTooLowEngineIsRunningEngineIsNotRunningEngineRunTimeTooLowTemperatureTooHighTemperatureTooLowVehicleSpeedTooHighVehicleSpeedTooLowThrottleOrPedalTooHighThrottleOrPedalTooLowTransmissionRangeNotInNeutralTransmissionRangeNotInGearBrakeSwitchOrSwitchesNotClosedShifterLeverNotInParkTorqueConverterClutchLockedVoltageTooHighVoltageTooLowResourceTemporarilyNotAvailable+as_byte(&self) -> u8+from_byte(value: u8) -> Option<Self>DiagnosticServicesCollection+get_read_did(&self, id: &str) -> Option<&dyn ReadDataByIdentifier>+get_write_did_mut(&mut self, id: &str) -> Option<&mut dyn WriteDataByIdentifier>+get_routine(&self, id: &str) -> Option<&dyn RoutineControl>+get_routine_mut(&mut self, id: &str) -> Option<&mut dyn RoutineControl>+get_service_mut(&mut self, id: &str) -> Option<&mut dyn UdsService>-read_dids: IndexMap<ServiceIdentifier, Box<dyn ReadDataByIdentifier + Send>>-write_dids: IndexMap<ServiceIdentifier, Box<dyn WriteDataByIdentifier + Send>>-routines: IndexMap<ServiceIdentifier, Box<dyn RoutineControl + Send>>-services: IndexMap<ServiceIdentifier, Box<dyn UdsService + Send>>All UDS services stored as Send only (not Sync).Rationale:- UDS binding layer is single-threaded- Services processed sequentially per session- get_read_did(&self) returns &dyn Trait but onlyone thread accesses collection at a time- Enables interior mutability in service implementationsDiagnosticServicesCollectionBuilder+new() -> Self+with_data_id<T: DataIdentifier + Send + 'static>(self, id: impl Into<String>, service: T) -> Self+with_read_did<T: ReadDataByIdentifier + Send + 'static>(self, id: impl Into<String>, service: T) -> Self+with_write_did<T: WriteDataByIdentifier + Send + 'static>(self, id: impl Into<String>, service: T) -> Self+with_routine<T: RoutineControl + Send + 'static>(self, id: impl Into<String>, service: T) -> Self+with_service<T: UdsService + Send + 'static>(self, id: impl Into<String>, service: T) -> Self+build(self) -> DiagResult<DiagnosticServicesCollection>final step-read_dids: IndexMap<ServiceIdentifier, Box<dyn ReadDataByIdentifier + Send>>-write_dids: IndexMap<ServiceIdentifier, Box<dyn WriteDataByIdentifier + Send>>-routines: IndexMap<ServiceIdentifier, Box<dyn RoutineControl + Send>>-services: IndexMap<ServiceIdentifier, Box<dyn UdsService + Send>>SerializationHelper«utility»+serialize_response<T: UdsSerialize>(payload: &T) -> DiagResult<ByteVec>+deserialize_request<T: UdsDeserialize, F>(data: ByteSlice<'_>, handler: F) -> DiagResult<()>SovdResultBuilder pattern using move semantics.Services passed to with_* methods are consumed (ownership transferred).Builder with lifetime 'entity parameter and move semantics.Entity ownership acquired via new<T: DiagnosticEntity>(entity: T).Type erasure to Box<dyn DiagnosticEntity>.One Box allocation (acceptable, can use smallbox if needed).Blanket implementation:impl<T> DataIdentifier for Twhere T: ReadDataByIdentifier + WriteDataByIdentifierBlanket implementation:impl<T> DataResource for Twhere T: ReadOnlyDataResource + WritableDataResourcecreatesaccepts (moved into Box)accepts (moved into Box)accepts (moved into Box)accepts (moved into Box)accepts (moved into Box)implementsrequiresrequiresimplementsimplementsimplementsimplementsproducesborrowsborrowsborrowsborrowsproducesproducesproducesusesusesusesusesusescreatestakes ownership (moved into Box)accepts (moved into Box)accepts (moved into Box)accepts (moved into Box)accepts (moved into Box)users provideusers provideusers provideusers provideusers provideusers provideusers provideusers provideusers provideimplementsowns (boxed)supportsis ofrequiresrequirestakes (moved)producesproducestakes (moved)producestakes (moved)producesproducesproducesproducesproducescontainsextendsextendsextendsoptionalcontainscontainserror varianterror variant \ No newline at end of file