69 lines
3.1 KiB
C#
69 lines
3.1 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// Parsed <c>ab://gateway[:port]/cip-path</c> host-address string used by the AbCip driver
|
|
/// as the <c>hostName</c> key across <see cref="Core.Abstractions.IHostConnectivityProbe"/>,
|
|
/// <see cref="Core.Abstractions.IPerCallHostResolver"/>, and the Polly bulkhead key
|
|
/// <c>(DriverInstanceId, hostName)</c> per v2 plan decision #144.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Format matches what libplctag's <c>gateway=...</c> + <c>path=...</c> attributes
|
|
/// consume, so no translation is needed at the wire layer — the parsed <see cref="CipPath"/>
|
|
/// is handed to the native library verbatim.</para>
|
|
/// <list type="bullet">
|
|
/// <item><c>ab://10.0.0.5/1,0</c> — single-chassis ControlLogix, CPU in slot 0.</item>
|
|
/// <item><c>ab://10.0.0.5/1,4</c> — CPU in slot 4.</item>
|
|
/// <item><c>ab://10.0.0.5/1,2,2,192.168.50.20,1,0</c> — bridged ControlLogix.</item>
|
|
/// <item><c>ab://10.0.0.5/</c> (empty path) — Micro800 / MicroLogix without backplane routing.</item>
|
|
/// <item><c>ab://10.0.0.5:44818/1,0</c> — explicit EIP port (default 44818).</item>
|
|
/// </list>
|
|
/// <para>Opaque to the rest of the stack: Admin UI, telemetry, and logs display the full
|
|
/// string so an incident ticket can be matched to the exact gateway + CIP route.</para>
|
|
/// </remarks>
|
|
public sealed record AbCipHostAddress(string Gateway, int Port, string CipPath)
|
|
{
|
|
/// <summary>Default EtherNet/IP TCP port — spec-reserved.</summary>
|
|
public const int DefaultEipPort = 44818;
|
|
|
|
/// <summary>Recompose the canonical <c>ab://...</c> form.</summary>
|
|
public override string ToString() => Port == DefaultEipPort
|
|
? $"ab://{Gateway}/{CipPath}"
|
|
: $"ab://{Gateway}:{Port}/{CipPath}";
|
|
|
|
/// <summary>
|
|
/// Parse <paramref name="value"/>. Returns <c>null</c> on any malformed input — callers
|
|
/// should treat a null return as a config-validation failure rather than catching.
|
|
/// </summary>
|
|
public static AbCipHostAddress? TryParse(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
const string prefix = "ab://";
|
|
if (!value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;
|
|
|
|
var remainder = value[prefix.Length..];
|
|
var slashIdx = remainder.IndexOf('/');
|
|
if (slashIdx < 0) return null;
|
|
|
|
var authority = remainder[..slashIdx];
|
|
var cipPath = remainder[(slashIdx + 1)..];
|
|
if (string.IsNullOrEmpty(authority)) return null;
|
|
|
|
var port = DefaultEipPort;
|
|
var colonIdx = authority.LastIndexOf(':');
|
|
string gateway;
|
|
if (colonIdx >= 0)
|
|
{
|
|
gateway = authority[..colonIdx];
|
|
if (!int.TryParse(authority[(colonIdx + 1)..], out port) || port <= 0 || port > 65535)
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
gateway = authority;
|
|
}
|
|
if (string.IsNullOrEmpty(gateway)) return null;
|
|
|
|
return new AbCipHostAddress(gateway, port, cipPath);
|
|
}
|
|
}
|