using System.Net; using System.Net.Sockets; using Opc.Ua; using Opc.Ua.Server; using ZB.MOM.WW.OtOpcUa.OpcUaServer; namespace ZB.MOM.WW.OtOpcUa; /// /// Starts an on a freshly probed port, retrying on a bind race. /// Probing a free port (bind 0, read, release) and handing it to the SDK later is a TOCTOU: under a /// highly parallel test run another test can take the port in between, and the SDK then fails with /// Failed to establish tcp listener sockets for Ipv4 and IPv6.. A /// that failed to start is not safe to start again, so every attempt builds a NEW host and server on /// a NEW port and the caller reads the port off the options it builds, never from a cached value. /// This file is compiled into each test project that boots a real SDK server (linked, not a library). /// internal static class OpcUaHostStart { private const int DefaultAttempts = 5; /// Probes a currently free loopback TCP port. Racy by nature - pair with . public static int AllocateFreePort() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); return ((IPEndPoint)listener.LocalEndpoint).Port; } /// /// Builds a host (via , given the port to bind) and a server (via /// ) and starts them, retrying with a new port, host and server when /// the bind fails. Any other failure, or the last bind failure, propagates. /// /// The started host and server. The caller owns disposal of the host. public static async Task<(OpcUaApplicationHost Host, TServer Server)> StartAsync( Func createHost, Func createServer, CancellationToken ct, int maxAttempts = DefaultAttempts) where TServer : StandardServer { for (var attempt = 1; ; attempt++) { var host = createHost(AllocateFreePort()); var server = createServer(); try { await host.StartAsync(server, ct); return (host, server); } catch (Exception ex) when (attempt < maxAttempts && IsBindFailure(ex)) { try { await host.DisposeAsync(); } catch { /* the half-started host is being discarded */ } } catch { try { await host.DisposeAsync(); } catch { /* surface the original failure */ } throw; } } } /// True when the exception chain says the OPC UA listener could not bind its port. public static bool IsBindFailure(Exception? ex) { for (; ex is not null; ex = ex.InnerException) { if (ex is SocketException { SocketErrorCode: SocketError.AddressAlreadyInUse }) return true; if (ex is ServiceResultException sre && (sre.StatusCode == StatusCodes.BadNoCommunication || sre.Message.Contains("Failed to establish tcp listener", StringComparison.OrdinalIgnoreCase))) { return true; } } return false; } }