using System.Net; using System.Net.Sockets; using System.Text; namespace Nonemm.Rig.Tests; /// A stand-in for hamlib's `rigctld`, answering in the extended form the client /// asks for. It records what it was told so a test can check the commands that /// went out, not only what came back. public sealed class FakeRigctld : IDisposable { private readonly TcpListener listener; private readonly CancellationTokenSource stopping = new(); private readonly List received = []; private readonly Lock guard = new(); public FakeRigctld() { listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); _ = Task.Run(() => ServeAsync(stopping.Token)); } public int Port => ((IPEndPoint)listener.LocalEndpoint).Port; public long FrequencyHertz { get; set; } = 14_025_000; public string Mode { get; set; } = "CW"; public bool IsSplit { get; set; } public long TransmitFrequencyHertz { get; set; } /// -11 is what hamlib returns for something the radio cannot do. public bool SupportsSplit { get; set; } = true; public IReadOnlyList Received { get { lock (guard) { return [.. received]; } } } public void Dispose() { stopping.Cancel(); listener.Stop(); stopping.Dispose(); } private async Task ServeAsync(CancellationToken cancellation) { try { using TcpClient client = await listener.AcceptTcpClientAsync(cancellation); using StreamReader reader = new(client.GetStream(), Encoding.ASCII); using StreamWriter writer = new(client.GetStream(), Encoding.ASCII) { AutoFlush = true }; while (await reader.ReadLineAsync(cancellation) is { } line) { lock (guard) { received.Add(line); } await writer.WriteAsync(Answer(line.TrimStart('+').Trim())); } } catch (Exception e) when (e is OperationCanceledException or IOException or SocketException) { } } private string Answer(string command) { string verb = command.Split(' ')[0]; return verb switch { "f" => $"get_freq:\nFrequency: {FrequencyHertz}\nRPRT 0\n", "m" => $"get_mode:\nMode: {Mode}\nPassband: 500\nRPRT 0\n", "s" when !SupportsSplit => "RPRT -11\n", "s" => $"get_split_vfo:\nSplit: {(IsSplit ? 1 : 0)}\nTX VFO: VFOB\nRPRT 0\n", "i" => $"get_split_freq:\nTX Frequency: {TransmitFrequencyHertz}\nRPRT 0\n", _ => $"{verb}: {command}\nRPRT 0\n", }; } }