using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Hosting; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; using ZB.MOM.WW.Secrets.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy; /// /// Static factory registration helper for . Mirrors /// GalaxyProxyDriverFactoryExtensions / ModbusDriverFactoryExtensions. /// Server's Program.cs calls once at startup; the driver /// bootstrap pipeline materialises DriverInstance rows whose DriverType matches /// into live instances. /// /// /// "GalaxyMxGateway" is the sole Galaxy driver-type name. The legacy /// "Galaxy" proxy type (retired in PR 7.2) no longer exists; there is no /// server-side backend switch — every Galaxy DriverInstance row uses this type name. /// public static class GalaxyDriverFactoryExtensions { public const string DriverTypeName = "GalaxyMxGateway"; /// Registers the Galaxy driver factory with the given registry, secret resolver, and optional logger factory. /// The driver factory registry to register with. /// /// The shared secret resolver (from DI) used to resolve the secret: arm of /// each instance's Gateway.ApiKeySecretRef. /// /// The optional logger factory for creating drivers. public static void Register( DriverFactoryRegistry registry, ISecretResolver secretResolver, ILoggerFactory? loggerFactory = null) { ArgumentNullException.ThrowIfNull(registry); ArgumentNullException.ThrowIfNull(secretResolver); registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, secretResolver)); } /// /// Convenience for tests + standalone callers. Uses the /// null-object, so the secret: API-key arm resolves fail-closed (absent) — callers /// that need a working secret: ref must use the path that /// threads the DI resolver. /// /// The unique identifier for the driver instance. /// The driver configuration in JSON format. /// The constructed Galaxy driver instance. public static GalaxyDriver CreateInstance(string driverInstanceId, string driverConfigJson) => CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null, NullSecretResolver.Instance); /// Creates a Galaxy driver instance from configuration JSON with optional logger factory and a secret resolver. /// The unique identifier for the driver instance. /// The driver configuration in JSON format. /// The optional logger factory for creating drivers. /// The secret resolver for the driver's secret: API-key arm. /// The constructed Galaxy driver instance. public static GalaxyDriver CreateInstance( string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory, ISecretResolver secretResolver) { ArgumentNullException.ThrowIfNull(secretResolver); ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId); ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson); var dto = JsonSerializer.Deserialize(driverConfigJson, JsonOptions) ?? throw new InvalidOperationException( $"Galaxy driver config for '{driverInstanceId}' deserialised to null"); var options = new GalaxyDriverOptions( Gateway: new GalaxyGatewayOptions( Endpoint: dto.Gateway?.Endpoint ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required Gateway.Endpoint"), ApiKeySecretRef: dto.Gateway.ApiKeySecretRef ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required Gateway.ApiKeySecretRef"), UseTls: dto.Gateway.UseTls ?? true, CaCertificatePath: dto.Gateway.CaCertificatePath, ConnectTimeoutSeconds: dto.Gateway.ConnectTimeoutSeconds ?? 10, DefaultCallTimeoutSeconds: dto.Gateway.DefaultCallTimeoutSeconds ?? 30, StreamTimeoutSeconds: dto.Gateway.StreamTimeoutSeconds ?? 0), MxAccess: new GalaxyMxAccessOptions( ClientName: dto.MxAccess?.ClientName ?? throw new InvalidOperationException( $"Galaxy driver '{driverInstanceId}' missing required MxAccess.ClientName"), PublishingIntervalMs: dto.MxAccess.PublishingIntervalMs ?? 1000, WriteUserId: dto.MxAccess.WriteUserId ?? 0, EventPumpChannelCapacity: dto.MxAccess.EventPumpChannelCapacity ?? 50_000), Repository: new GalaxyRepositoryOptions( DiscoverPageSize: dto.Repository?.DiscoverPageSize ?? 5000, WatchDeployEvents: dto.Repository?.WatchDeployEvents ?? true), Reconnect: new GalaxyReconnectOptions( InitialBackoffMs: dto.Reconnect?.InitialBackoffMs ?? 500, MaxBackoffMs: dto.Reconnect?.MaxBackoffMs ?? 30_000, ReplayOnSessionLost: dto.Reconnect?.ReplayOnSessionLost ?? true)) { // v3: the deploy artifact delivers the authored raw tags (RawPath identity + TagConfig blob // carrying the camelCase "attributeRef" + WriteIdempotent flag). The driver builds its // RawPath → attributeRef table from these. Empty when the config omits them. RawTags = dto.RawTags ?? [], }; return new GalaxyDriver(driverInstanceId, options, secretResolver, loggerFactory?.CreateLogger()); } private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; /// Data transfer object for Galaxy driver configuration JSON. internal sealed class GalaxyDriverConfigDto { /// Gets or sets the gateway configuration. public GatewayDto? Gateway { get; init; } /// Gets or sets the MX Access configuration. public MxAccessDto? MxAccess { get; init; } /// Gets or sets the repository configuration. public RepositoryDto? Repository { get; init; } /// Gets or sets the reconnect configuration. public ReconnectDto? Reconnect { get; init; } /// /// Gets or sets the v3 authored raw tags. Each carries the RawPath /// identity, the driver TagConfig blob (with the Galaxy attributeRef), and the /// WriteIdempotent flag. Bound straight through to . /// public List? RawTags { get; init; } } /// Gateway configuration section. internal sealed class GatewayDto { /// Gets or sets the gateway endpoint address. public string? Endpoint { get; init; } /// Gets or sets the API key secret reference. public string? ApiKeySecretRef { get; init; } /// Gets or sets whether to use TLS. public bool? UseTls { get; init; } /// Gets or sets the CA certificate path. public string? CaCertificatePath { get; init; } /// Gets or sets the connection timeout in seconds. public int? ConnectTimeoutSeconds { get; init; } /// Gets or sets the default call timeout in seconds. public int? DefaultCallTimeoutSeconds { get; init; } /// Gets or sets the stream timeout in seconds. public int? StreamTimeoutSeconds { get; init; } } /// MX Access configuration section. internal sealed class MxAccessDto { /// Gets or sets the client name. public string? ClientName { get; init; } /// Gets or sets the publishing interval in milliseconds. public int? PublishingIntervalMs { get; init; } /// Gets or sets the write user ID. public int? WriteUserId { get; init; } /// Gets or sets the event pump channel capacity. public int? EventPumpChannelCapacity { get; init; } } /// Repository configuration section. internal sealed class RepositoryDto { /// Gets or sets the discover page size. public int? DiscoverPageSize { get; init; } /// Gets or sets whether to watch deploy events. public bool? WatchDeployEvents { get; init; } } /// Reconnect configuration section. internal sealed class ReconnectDto { /// Gets or sets the initial backoff in milliseconds. public int? InitialBackoffMs { get; init; } /// Gets or sets the maximum backoff in milliseconds. public int? MaxBackoffMs { get; init; } /// Gets or sets whether to replay on session lost. public bool? ReplayOnSessionLost { get; init; } } }