fix(dcl): harden endpoint host-rewrite — IPv6 brackets + portless URLs

Code review of a1abbff7 found two edge cases: a discovery/advertised URL with no
explicit port emitted a malformed ':-1' (opc.tcp has no Uri default port), and an
IPv6 literal host could lose/double its brackets. Omit the port when neither URL
carries one; bracket an IPv6 host only when missing. +4 tests (12 total).
This commit is contained in:
Joseph Doherty
2026-07-23 16:58:44 -04:00
parent dbec3ee263
commit 86d129de6c
2 changed files with 36 additions and 1 deletions
@@ -191,11 +191,21 @@ public class RealOpcUaClient : IOpcUaClient
&& advertised.Port == reachable.Port)
return;
// An IPv6 literal must keep its brackets, or the reconstructed authority (`::1:4840`) is
// ambiguous and unparseable. Uri.Host may or may not include them depending on the scheme,
// so bracket only when missing (never double-wrap).
var host = reachable.Host;
if (reachable.HostNameType == UriHostNameType.IPv6 && !host.StartsWith('['))
host = $"[{host}]";
// opc.tcp has no default port known to Uri, so Uri.Port is -1 when the text omits one.
// Prefer the reachable URL's port, fall back to the advertised one, and emit no ":port"
// at all when neither carries one (rather than a malformed ":-1").
var port = reachable.Port >= 0 ? reachable.Port : advertised.Port;
var authority = port >= 0 ? $"{host}:{port}" : host;
// Authority-only advertisements parse to AbsolutePath "/" — drop it so we don't append a
// spurious trailing slash the server never advertised.
var path = advertised.AbsolutePath == "/" ? string.Empty : advertised.AbsolutePath;
endpoint.EndpointUrl = $"{advertised.Scheme}://{reachable.Host}:{port}{path}";
endpoint.EndpointUrl = $"{advertised.Scheme}://{authority}{path}";
}
/// <summary>