using System.Net; using System.Net.Sockets; using System.Text; using NATS.Server.Gateways; using NATS.Server.Subscriptions; namespace NATS.Server.Clustering.Tests; public class InterServerAccountProtocolTests { [Fact] public async Task Aplus_Aminus_frames_include_account_scope_and_do_not_leak_interest_across_accounts() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await remoteSocket.ConnectAsync(IPAddress.Loopback, port); using var gatewaySocket = await listener.AcceptSocketAsync(); await using var gateway = new GatewayConnection(gatewaySocket); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var handshakeTask = gateway.PerformOutboundHandshakeAsync("LOCAL", timeout.Token); (await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("GATEWAY LOCAL"); await WriteLineAsync(remoteSocket, "GATEWAY REMOTE", timeout.Token); await handshakeTask; var received = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); gateway.RemoteSubscriptionReceived = sub => { received.TrySetResult(sub); return Task.CompletedTask; }; gateway.StartLoop(timeout.Token); await WriteLineAsync(remoteSocket, "A+ A orders.*", timeout.Token); var aPlus = await received.Task.WaitAsync(timeout.Token); aPlus.Account.ShouldBe("A"); aPlus.Subject.ShouldBe("orders.*"); aPlus.IsRemoval.ShouldBeFalse(); var subList = new SubList(); subList.ApplyRemoteSub(aPlus); subList.HasRemoteInterest("A", "orders.created").ShouldBeTrue(); subList.HasRemoteInterest("B", "orders.created").ShouldBeFalse(); var removedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); gateway.RemoteSubscriptionReceived = sub => { removedTcs.TrySetResult(sub); return Task.CompletedTask; }; await WriteLineAsync(remoteSocket, "A- A orders.*", timeout.Token); var aMinus = await removedTcs.Task.WaitAsync(timeout.Token); aMinus.Account.ShouldBe("A"); aMinus.IsRemoval.ShouldBeTrue(); subList.ApplyRemoteSub(aMinus); subList.HasRemoteInterest("A", "orders.created").ShouldBeFalse(); } private static async Task ReadLineAsync(Socket socket, CancellationToken ct) { var bytes = new List(64); var single = new byte[1]; while (true) { var read = await socket.ReceiveAsync(single, SocketFlags.None, ct); if (read == 0) break; if (single[0] == (byte)'\n') break; if (single[0] != (byte)'\r') bytes.Add(single[0]); } return Encoding.ASCII.GetString([.. bytes]); } private static Task WriteLineAsync(Socket socket, string line, CancellationToken ct) => socket.SendAsync(Encoding.ASCII.GetBytes($"{line}\r\n"), SocketFlags.None, ct).AsTask(); }