merge: bring documentation baseline from docs/master into docs/rc-1.0.2

This commit is contained in:
n8n IEC 62304 Bot
2026-06-28 22:22:56 +02:00
2810 changed files with 1927392 additions and 25392 deletions
@@ -3,16 +3,49 @@ using Serilog;
namespace adas_core.module.ProxyDevices.Devices;
/// <summary>
/// Device that can be used to get content via http with digest auth
/// </summary>
public class HttpDevice : IProxyDevice
{
/// <summary>
/// Domain for digest auth. Optional, only used if username is provided
/// </summary>
private string _domain = "";
/// <summary>
/// Indicates whether the device has been initialized with parameters. This is important to prevent multiple initializations and to ensure that the device is ready for processing requests.
/// </summary>
private bool _isInitialized;
/// <summary>
/// Name of the device. Optional, can be used for logging or identification purposes.
/// </summary>
private string _name = "";
/// <summary>
/// Password for digest auth. Optional, only used if username is provided
/// </summary>
private string _password = "";
/// <summary>
/// URI of the device. This is a required parameter and must be a valid absolute URI. It represents the endpoint that the device will interact with when processing requests.
/// </summary>
private Uri _uri = null!;
/// <summary>
/// Username for digest auth. Optional, if not provided, the device will attempt to access the URI without authentication.
/// If provided, it will be used in conjunction with the password and domain (if specified) for authentication purposes.
/// </summary>
private string _username = "";
/// <summary>
/// Initializes the device with the provided parameters. The parameters are expected to be in a dictionary format, where the key is a string representing the parameter name and the value is an object that can be cast to the appropriate type.
/// The method checks for the presence of required parameters (like "url") and validates them, throwing exceptions if any issues are found. Optional parameters (like "name", "username", "password", and "domain") are also processed if they are present in the dictionary.
/// </summary>
/// <param name="deviceParameters">A dictionary containing the parameters required to initialize the device.</param>
/// <exception cref="InvalidOperationException">Thrown when the device is already initialized.</exception>
/// <exception cref="ArgumentNullException">Thrown when a required parameter is missing or invalid.</exception>
public void Initialize(Dictionary<string, object?> deviceParameters)
{
if (_isInitialized) throw new InvalidOperationException("Device already initialized");
@@ -28,6 +61,13 @@ public class HttpDevice : IProxyDevice
_isInitialized = true;
}
/// <summary>
/// Processes a request by sending an HTTP GET request to the specified URI using the HttpClient. If authentication parameters were provided during initialization, they will be used for the request.
/// The method handles potential timeouts by catching TaskCanceledException and rethrowing it as a TimeoutException with a descriptive message. The response from the HTTP request is returned as an HttpResponseMessage.
/// </summary>
/// <returns>An HttpResponseMessage representing the response from the HTTP request.</returns>
/// <exception cref="TimeoutException">Thrown when the request times out.</exception>
public async Task<HttpResponseMessage> Process()
{
try
@@ -42,6 +82,13 @@ public class HttpDevice : IProxyDevice
}
}
/// <summary>
/// Streams content from the specified URI using an HTTP GET request with the option to read response headers immediately. This method is similar to Process(), but it allows for streaming the response content as it is received,
/// which can be useful for large responses or when you want to start processing the data before the entire response is available.
/// Like Process(), it handles potential timeouts by catching TaskCanceledException and rethrowing it as a TimeoutException with a descriptive message.
/// </summary>
/// <returns>An HttpResponseMessage representing the response from the HTTP request.</returns>
/// <exception cref="TimeoutException">Thrown when the request times out.</exception>
public async Task<HttpResponseMessage> Stream()
{
try
@@ -57,6 +104,12 @@ public class HttpDevice : IProxyDevice
}
}
/// <summary>
/// Creates and configures an HttpClient instance based on the initialization parameters of the device.
/// If authentication parameters (username, password, and optionally domain) were provided during initialization, they will be set in the HttpClientHandler's Credentials property to enable digest authentication for the HTTP requests.
/// </summary>
/// <returns>An HttpClient instance configured with the appropriate credentials.</returns>
/// <exception cref="InvalidOperationException">Thrown when the device is not initialized.</exception>
private HttpClient GetHttpClient()
{
if (!_isInitialized) throw new InvalidOperationException("Device not initialized");
@@ -67,6 +120,10 @@ public class HttpDevice : IProxyDevice
return new HttpClient(clientHandler);
}
/// <summary>
/// Returns a string representation of the HttpDevice instance, including its name, URI, username, password, and domain. This can be useful for logging or debugging purposes to quickly identify the configuration of the device.
/// </summary>
/// <returns>A string representation of the HttpDevice instance.</returns>
public override string ToString()
{
return $"[HttpDevice] ({_name} {_uri} {_username} {_password} {_domain})";
@@ -1,8 +1,27 @@
namespace adas_core.module.ProxyDevices.Devices;
/// <summary>
/// Interface for a proxy device that can be used to process and stream data. This interface defines the methods that must be implemented by any class that wants to act as a proxy device.
/// The Initialize method is used to set up the device with the necessary parameters, while the Process and Stream methods are used to handle data processing and streaming respectively.
/// </summary>
public interface IProxyDevice
{
/// <summary>
/// Initializes the proxy device with the given parameters. This method should be called before any processing or streaming is done.
/// The parameters can include any necessary configuration settings for the device, such as connection details, authentication information, or other relevant data.
/// </summary>
/// <param name="deviceParameters">A dictionary containing the parameters required to initialize the device.</param>
void Initialize(Dictionary<string, object?> deviceParameters);
/// <summary>
/// Processes the data received by the proxy device. This method should contain the logic for handling incoming data, performing any necessary transformations or computations, and preparing the data for further use.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Process method.</returns>
Task<HttpResponseMessage> Process();
/// <summary>
/// Streams data from the proxy device. This method should contain the logic for handling outgoing data, performing any necessary transformations or computations, and preparing the data for further use.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Stream method.</returns>
Task<HttpResponseMessage> Stream();
}
@@ -1,13 +1,38 @@
namespace adas_core.module.ProxyDevices;
/// <summary>
/// Represents the settings for proxy devices, which are used to define and manage virtual devices that can be used for testing, simulation, or other purposes within the ADAS (Advanced Driver Assistance Systems) core module.
/// Each proxy device can have its own unique identifier, type, enabled status, and a set of parameters that can be customized as needed.
/// </summary>
public class ProxyDeviceSettings : List<ProxyDevice>
{
}
/// <summary>
/// Represents a single proxy device with its properties, including an identifier, type, enabled status, and a dictionary of parameters that can be used to configure the device's behavior or characteristics.
/// </summary>
public class ProxyDevice
{
/// <summary>
/// Gets or sets the unique identifier for the proxy device. This identifier is used to distinguish between different proxy devices and can be used for referencing the device in various operations or configurations.
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the type of the proxy device. This property indicates the category or classification of the device, which can be used to determine how the device should be handled or processed within the ADAS core module.
/// The type can be used to specify different behaviors, capabilities, or configurations for different types of proxy devices.
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// Gets or sets a value indicating whether the proxy device is enabled or not. If this property is set to true, the device is considered active and can be used in operations or simulations.
/// If set to false, the device is considered inactive and will not be used in any operations or simulations until it is enabled again.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Gets or sets a dictionary of parameters for the proxy device. This dictionary allows for flexible configuration of the device by storing key-value pairs,
/// where the key is a string representing the parameter name and the value is an object that can hold any type of data associated with that parameter.
/// </summary>
public Dictionary<string, object?> Parameters { get; set; } = new();
}
+196
View File
@@ -0,0 +1,196 @@
# adas-core.module.ProxyDevices — Proxy Devices Module
> A **functional module** of the ADAS Core platform.
> Encapsulates all logic for proxying communication with external hardware devices over HTTP. This module acts as a configurable gateway layer that the Host registers at startup, translating internal requests into authenticated HTTP calls to remote endpoints.
---
## Table of Contents
1. [Overview](#overview)
2. [Responsibilities](#responsibilities)
3. [Project Structure](#project-structure)
4. [Dependencies](#dependencies)
5. [Device Proxy Architecture](#device-proxy-architecture)
6. [ProxyDeviceService Flow](#proxydeviceservice-flow)
7. [Design Rules](#design-rules)
---
## Overview
`adas-core.module.ProxyDevices` is a modular satellite project that provides a **proxy gateway** for interacting with external hardware devices over HTTP. It abstracts the complexity of device authentication, connection management, and response handling behind a uniform `IProxyDevice` contract.
Key characteristics:
- **Modular Monolith Pattern** — Self-contained module with its own device drivers, service orchestration, and configuration model. No other module depends on it directly.
- **Plugin Architecture** — New device types are added by creating a class that implements `IProxyDevice` and naming it `{Type}Device`. The service discovers and instantiates them at runtime via reflection.
- **Digest Authentication Support** — The built-in `HttpDevice` supports HTTP Digest authentication via `NetworkCredential`.
- **Instance Caching** — `ProxyDeviceService` maintains an in-memory cache of initialized device instances to avoid repeated construction and initialization overhead.
- **Dual Operation Modes** — Each device supports both `Process()` (full response) and `Stream()` (response-headers-read) operations.
- **Configuration-Driven** — All devices, their types, and parameters are defined in `ProxyDeviceSettings` bound from `appsettings.json`.
---
## Responsibilities
| Concern | What this project does |
|---------|----------------------|
| **Device Proxying** | Acts as an intermediary between the Host and external HTTP-based devices, shielding the rest of the system from protocol specifics. |
| **Device Lifecycle** | Manages device initialization, caching, and disposal through the `IProxyDevice` contract. |
| **HTTP Communication** | `HttpDevice` performs authenticated and unauthenticated HTTP GET requests with timeout handling. |
| **Digest Authentication** | Supports HTTP Digest auth via `username`, `password`, and optional `domain` parameters. |
| **Reflection-Based Factory** | `ProxyDeviceService` dynamically instantiates device classes by type name at runtime (`{Type}Device`). |
| **Instance Caching** | Caches initialized `IProxyDevice` instances per `deviceId` to minimize repeated setup costs. |
| **Streaming vs Processing** | Offers `Process()` for complete responses and `Stream()` for streaming (headers-first) scenarios. |
| **Error Translation** | Converts `HttpRequestException`, `TaskCanceledException`, and missing-device errors into standardized `HttpResponseMessage` responses. |
| **Configuration Model** | Defines `ProxyDeviceSettings` and `ProxyDevice` as strongly-typed configuration objects bound from the Host's configuration system. |
| **Audit Logging** | Emits audit events for device access and proxy operations via `AuditLogs`. |
---
## Project Structure
```
adas-core.module.ProxyDevices/
├── ProxyDeviceSettings.cs # Strongly-typed settings model (list of configured proxy devices)
├── Devices/
│ ├── IProxyDevice.cs # Core contract: Initialize, Process, Stream
│ └── HttpDevice.cs # Concrete HTTP proxy with digest-auth support
└── Services/
├── IProxyDeviceService.cs # Service contract: Process(deviceId), Stream(deviceId)
└── ProxyDeviceService.cs # Orchestrator: reflection factory, caching, error handling
```
| File | Role |
|------|------|
| `ProxyDeviceSettings.cs` | Configuration root. A `List<ProxyDevice>` bound from `appsettings.json`. Each entry defines `Id`, `Type`, `Enabled`, and `Parameters`. |
| `IProxyDevice.cs` | Interface contract. All proxy devices implement `Initialize(Dictionary)`, `Process()`, and `Stream()`. |
| `HttpDevice.cs` | Concrete proxy for HTTP endpoints. Validates `url`, optional digest auth, performs GET requests, handles timeouts. |
| `IProxyDeviceService.cs` | Application-facing contract. Host controllers depend on this interface, not on module internals. |
| `ProxyDeviceService.cs` | Implementation. Reads settings, looks up devices by `Id`, lazily instantiates and caches drivers, delegates to `Process()` or `Stream()`. |
---
## Dependencies
### Downstream References
This project has **no direct project references** to other ADAS Core projects. It is a completely decoupled satellite module.
| Package | Version | Purpose |
|---------|---------|---------|
| `Microsoft.Extensions.DependencyInjection` | 10.0.8 | DI registration helpers used during Host startup. |
| `Microsoft.Extensions.Hosting` | 10.0.8 | Hosting abstractions for optional background proxy services. |
### Upstream References (projects that depend on this)
| Project | Reason |
|---------|--------|
| `adas-core` (Host) | Registers `IProxyDeviceService` and `ProxyDeviceSettings` at startup. Host controllers invoke `Process()` and `Stream()` through the service interface. |
### NuGet Packages
| Package | Version | Purpose |
|---------|---------|---------|
| `Microsoft.Extensions.DependencyInjection` | 10.0.8 | Service-collection registration helpers. |
| `Microsoft.Extensions.Hosting` | 10.0.8 | Hosting abstractions for background services. |
| `Serilog` | 4.3.1 | Structured logging for timeout and HTTP error events. |
| `AuditLogs` | 1.0.59 | Audit trail emission for device access and proxy operations. |
| `SharpCompress` | 0.49.0 | Available for potential compression pipelines in future device types. |
| `Snappier` | 1.3.1 | Fast compression library for future streaming optimization. |
---
## Device Proxy Architecture
### `IProxyDevice` Contract
```csharp
public interface IProxyDevice
{
void Initialize(Dictionary<string, object?> deviceParameters);
Task<HttpResponseMessage> Process();
Task<HttpResponseMessage> Stream();
}
```
| Member | Purpose |
|--------|---------|
| `Initialize(parameters)` | One-time setup. Validates required keys (`url`), extracts optional keys (`name`, `username`, `password`, `domain`), and marks the device as ready. |
| `Process()` | Performs a full HTTP GET request and returns the complete `HttpResponseMessage`. |
| `Stream()` | Performs an HTTP GET with `HttpCompletionOption.ResponseHeadersRead`, enabling callers to stream the body without buffering. |
### `HttpDevice` — Built-in HTTP Proxy
- **URI Validation** — Requires a non-empty, absolute `url` in parameters. Throws `ArgumentNullException` if missing or invalid.
- **Single Initialization Guard** — Throws `InvalidOperationException` if `Initialize()` is called twice.
- **Optional Digest Auth** — When `username` is provided, sets `HttpClientHandler.Credentials` with `NetworkCredential(username, password, domain)`.
- **Timeout Translation** — Catches `TaskCanceledException` (timeout) and re-throws as `TimeoutException` with a descriptive message.
- **Diagnostics** — Overrides `ToString()` to emit `[HttpDevice] (name uri username password domain)` for logging.
### Adding a New Device Type
1. Create a class in `Devices/` that implements `IProxyDevice`.
2. Name it `{Type}Device` (e.g., `TcpDevice`, `UdpDevice`).
3. Register the type string in `appsettings.json` under `ProxyDeviceSettings`.
4. `ProxyDeviceService` will resolve it automatically via reflection at first use.
## ProxyDeviceService Flow
### Device Resolution & Caching
```
[Host Controller] --> [ProxyDeviceService.Process(deviceId)]
|
v
[Look up ProxyDevice by Id in settings]
|
+---------+---------+
| |
v v
[Enabled=true] [Enabled=false / Not Found]
| |
v v
[Check _cache] [Return 404 Not Found]
| |
| +--> [Miss] --> [Reflect {Type}Device]
| [Call Initialize(params)]
| [Add to _cache]
v |
[Call device.Process()] <--+
|
v
[Return HttpResponseMessage]
```
### Key Behaviors
- **Thread-Safe Cache** — The `_cache` dictionary is guarded by a `lock` during read/write to prevent race conditions.
- **Lazy Instantiation** — Devices are created only on first access, not at startup.
- **Graceful Degradation** — If a device is not found, disabled, or throws `HttpRequestException`, the service returns an `HttpResponseMessage` with an appropriate status code rather than crashing.
- **No Dispose Lifecycle** — Cached instances are reused for the lifetime of the service. The Host manages the service scope (typically Singleton).
---
## Design Rules
1. **True Module Independence** — This project must not reference any other ADAS Core project. It communicates with the Host exclusively through its own public interfaces and the .NET configuration/options system.
2. **Configuration-Driven Discovery** — Device types, endpoints, and credentials must come exclusively from `ProxyDeviceSettings` bound to `appsettings.json`. No hardcoded URLs or auth secrets.
3. **Reflection Factory** — New device types are added by convention (`{Type}Device` implementing `IProxyDevice`). No central switch statement needs modification (Open/Closed).
4. **Cache Safety** — The device instance cache is protected by a `lock` during writes. Reads inside the lock are safe because initialization is atomic.
5. **Fail Securely** — Missing or disabled devices return `404 Not Found`. HTTP failures are translated into `HttpResponseMessage` with the original status code. Timeouts become `TimeoutException`.
6. **No Business Logic** — This module handles transport only. Any transformation, validation, or business-rule enforcement belongs in `Application` or the Host.
7. **Lazy Initialization** — Devices are instantiated on first request, not at Host startup. This prevents delaying startup when a remote device is unreachable.
8. **Digest Auth Only**`HttpDevice` supports HTTP Digest authentication. Basic auth, bearer tokens, or mTLS must be implemented in a separate `IProxyDevice` implementation if needed.
9. **Audit Every Access** — Every `Process()` and `Stream()` call emits an audit event via `AuditLogs` tagging the device ID and outcome.
10. **Singleton Service**`ProxyDeviceService` is registered as a **Singleton** because it holds cached device instances and settings that are immutable after startup.
---
<p align="center">
Back to <a href="../README.md">adas-core Root README</a>
</p>
@@ -1,7 +1,21 @@
namespace adas_core.module.ProxyDevices.Services;
/// <summary>
/// Interface for the Proxy Device Service, which handles processing and streaming of data from proxy devices.
/// </summary>
public interface IProxyDeviceService
{
/// <summary>
/// Processes data from a proxy device identified by the given device ID. This method is responsible for handling
/// </summary>
/// <param name="deviceId">The unique identifier of the proxy device to be processed.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Process method.</returns>
Task<HttpResponseMessage> Process(string deviceId);
/// <summary>
/// Streams data from a proxy device identified by the given device ID. This method is responsible for handling
/// </summary>
/// <param name="deviceId">The unique identifier of the proxy device to be streamed.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Stream method.</returns>
Task<HttpResponseMessage> Stream(string deviceId);
}
@@ -4,10 +4,25 @@ using Microsoft.Extensions.Options;
namespace adas_core.module.ProxyDevices.Services;
/// <summary>
/// ProxyDeviceService is responsible for managing proxy devices, processing requests, and streaming data from the devices.
/// It uses a cache to store device instances for efficient retrieval and ensures that only enabled devices are processed.
/// The service handles HTTP requests and returns appropriate responses based on the device's availability and functionality.
/// </summary>
/// <param name="settings">The settings for the proxy devices, including their configuration and parameters.</param>
public class ProxyDeviceService(IOptions<ProxyDeviceSettings> settings) : IProxyDeviceService
{
/// <summary>
/// _cache is a dictionary that stores instances of IProxyDevice, keyed by their deviceId.
/// </summary>
private readonly Dictionary<string, IProxyDevice?> _cache = new();
/// <summary>
/// Process method takes a deviceId as input, retrieves the corresponding device from the cache or creates a new instance if it doesn't exist, and then calls the Process method of the device.
/// It returns an HttpResponseMessage based on the outcome of the operation, handling any exceptions that may occur during the process.
/// </summary>
/// <param name="deviceId">The unique identifier of the proxy device to be processed.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Process method.</returns>
public async Task<HttpResponseMessage> Process(string deviceId)
{
try
@@ -25,6 +40,11 @@ public class ProxyDeviceService(IOptions<ProxyDeviceSettings> settings) : IProxy
}
}
/// <summary>
/// Stream method takes a deviceId as input, retrieves the corresponding device from the cache or creates a new instance if it doesn't exist, and then calls the Stream method of the device.
/// </summary>
/// <param name="deviceId">The unique identifier of the proxy device to be streamed.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the HttpResponseMessage returned by the device's Stream method.</returns>
public async Task<HttpResponseMessage> Stream(string deviceId)
{
try
@@ -42,6 +62,12 @@ public class ProxyDeviceService(IOptions<ProxyDeviceSettings> settings) : IProxy
}
}
/// <summary>
/// Retrieves the proxy device instance corresponding to the given deviceId. If the device is not found or not enabled, an HttpRequestException is thrown.
/// </summary>
/// <param name="deviceId">The unique identifier of the proxy device to be retrieved.</param>
/// <returns>The proxy device instance corresponding to the given deviceId.</returns>
/// <exception cref="HttpRequestException">Thrown when the device is not found or not enabled.</exception>
private IProxyDevice? GetDevice(string deviceId)
{
var device = settings.Value.FirstOrDefault(d => d.Id == deviceId);