using System.Diagnostics;
using System.Net.Sockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy;
///
/// Cheap TCP-connect probe for the -shaped driver config.
/// Parses the Gateway.Endpoint gRPC endpoint (e.g. http://host:5001 or
/// host:5001), opens a socket and closes immediately. Surfaces a green tick +
/// latency on success; red chip + SocketError on failure; "timed out" on the caller's
/// cancellation. Does NOT exchange any gRPC frames — a richer gRPC ping probe is a
/// documented follow-up.
///
public sealed class GalaxyDriverProbe : IDriverProbe
{
private static readonly JsonSerializerOptions _opts = new()
{
PropertyNameCaseInsensitive = true,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
};
///
public string DriverType => GalaxyDriverFactoryExtensions.DriverTypeName; // "GalaxyMxGateway"
///
public async Task ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
{
GalaxyDriverOptions? opts;
try { opts = JsonSerializer.Deserialize(configJson, _opts); }
catch (Exception ex) { return new(false, $"Config JSON is invalid: {ex.Message}", null); }
if (opts is null) return new(false, "Config JSON deserialized to null.", null);
var (host, port) = ExtractTarget(opts);
if (string.IsNullOrWhiteSpace(host) || port <= 0)
return new(false, "Config has no host/port to probe.", null);
var sw = Stopwatch.StartNew();
try
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(host, port, ct);
sw.Stop();
return new(true, null, sw.Elapsed);
}
catch (SocketException ex)
{
return new(false, $"Connect failed: {ex.SocketErrorCode}", null);
}
catch (OperationCanceledException)
{
return new(false, $"Probe timed out after {timeout.TotalSeconds:F0}s.", null);
}
catch (Exception ex)
{
return new(false, ex.Message, null);
}
}
private static (string host, int port) ExtractTarget(GalaxyDriverOptions opts)
{
var endpoint = opts.Gateway.Endpoint;
if (string.IsNullOrWhiteSpace(endpoint)) return (string.Empty, 0);
// Try absolute URI first (e.g. "http://hostname:5001" or "https://hostname:5001").
if (Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
{
var host = uri.Host;
// Uri.Port is -1 when not specified; default mxaccessgw port is 5001.
var port = uri.Port > 0 ? uri.Port : 5001;
return (host, port);
}
// Fallback: treat as "host:port" (no scheme).
var colonIdx = endpoint.LastIndexOf(':');
if (colonIdx > 0 && int.TryParse(endpoint[(colonIdx + 1)..], out var rawPort) && rawPort > 0)
return (endpoint[..colonIdx], rawPort);
// No port found — return the whole string as host with default port.
return (endpoint, 5001);
}
}