using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; namespace IW4MAdmin.Application.IO; public class NetworkLogState : Dictionary { public UdpClientState RegisterEndpoint(IPEndPoint serverEndpoint, IPEndPoint localEndpoint) { try { lock (this) { if (!ContainsKey(serverEndpoint)) { Add(serverEndpoint, new UdpClientState { Client = new UdpClient(localEndpoint) }); } } } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) { lock (this) { // we don't add the udp client because it already exists (listening to multiple servers from one socket) Add(serverEndpoint, new UdpClientState()); } } return this[serverEndpoint]; } public List GetServerLogData(IPEndPoint serverEndpoint) { try { var state = this[serverEndpoint]; if (state == null) { return new List(); } // it's possible that we could be trying to read and write to the queue simultaneously so we need to wait this[serverEndpoint].OnAction.Wait(); var data = new List(); while (this[serverEndpoint].AvailableLogData.Count > 0) { data.Add(this[serverEndpoint].AvailableLogData.Dequeue()); } return data; } finally { if (this[serverEndpoint].OnAction.CurrentCount == 0) { this[serverEndpoint].OnAction.Release(1); } } } public void QueueServerLogData(IPEndPoint serverEndpoint, byte[] data) { var endpoint = Keys.FirstOrDefault(key => Equals(key.Address, serverEndpoint.Address) && key.Port == serverEndpoint.Port); try { if (endpoint == null) { return; } // currently our expected start and end characters var startsWithPrefix = StartsWith(data, "ÿÿÿÿprint\n"); var endsWithDelimiter = data[^1] == '\n'; // we have the data we expected if (!startsWithPrefix || !endsWithDelimiter) { return; } // it's possible that we could be trying to read and write to the queue simultaneously so we need to wait this[endpoint].OnAction.Wait(); this[endpoint].AvailableLogData.Enqueue(data); } finally { if (endpoint != null && this[endpoint].OnAction.CurrentCount == 0) { this[endpoint].OnAction.Release(1); } } } public bool EndPointExists(IPEndPoint serverEndpoint) { lock (this) { return ContainsKey(serverEndpoint); } } private static bool StartsWith(byte[] sourceArray, string match) { if (sourceArray is null) { return false; } if (match.Length > sourceArray.Length) { return false; } return !match.Where((t, i) => sourceArray[i] != (byte)t).Any(); } } public class UdpClientState { public UdpClient Client { get; set; } public Queue AvailableLogData { get; } = new(); public SemaphoreSlim OnAction { get; } = new(1, 1); ~UdpClientState() { OnAction.Dispose(); Client?.Dispose(); } }