using System; using System.Collections.Generic; using System.Net.NetworkInformation; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using LANCommander.SDK.Abstractions; using LANCommander.SDK.Helpers; using LANCommander.SDK.Interceptors; using LANCommander.SDK.Models; using Microsoft.Extensions.Logging; namespace LANCommander.SDK.Services; public class BeaconClient( ILogger logger, INetworkInformationProvider networkInformationProvider) { public delegate void OnBeaconResponseHandler(object sender, BeaconResponseArgs e); public event OnBeaconResponseHandler OnBeaconResponse; private List _probeClients = new(); private List _beaconClients = new(); private List _beaconMessageInterceptors = new(); public void Initialize() { _beaconMessageInterceptors = new List(); } public BeaconClient AddBeaconMessageInterceptor(IBeaconMessageInterceptor interceptor) { _beaconMessageInterceptors.Add(interceptor); return this; } /// /// Send broadcast packets across all interfaces to tell any server that we exist /// /// The port to beacon on /// The number of attempts to make before giving up /// THe interval (in ms) between probe packets /// public async Task StartProbeAsync(int port = 35891, int retryAttempts = 10, int retryInterval = 2000, CancellationToken cancellationToken = default) { int attempt = 0; foreach (var networkInterface in networkInformationProvider.GetNetworkInterfaces()) { DiscoveryProbe probeClient = null; try { probeClient = new DiscoveryProbe(networkInterface); await probeClient.BindSocketAsync(port); probeClient.OnBeaconResponse += (sender, args) => OnBeaconResponse?.Invoke(sender, args); _probeClients.Add(probeClient); } catch { // ignored probeClient?.Dispose(); _probeClients.Remove(probeClient); } } while (!cancellationToken.IsCancellationRequested) { if (attempt >= retryAttempts || cancellationToken.IsCancellationRequested) break; foreach (var probe in _probeClients) { if (probe.IsDisposed) continue; await probe.SendAsync(); } await Task.Delay(retryInterval, cancellationToken); } foreach (var client in _probeClients) client.Dispose(); _probeClients.Clear(); } /// /// Stop any active probes /// public async Task StopProbeAsync() { foreach (var probeClient in _probeClients) { probeClient.Dispose(); } } /// /// Cleans up ressources created for probing /// /// Clears list of current probe clients public void CleanupProbe() { foreach (var probeClient in _probeClients) { if (!probeClient.IsDisposed) { probeClient.Dispose(); } } _probeClients.Clear(); } /// /// Start listening for probe broadcasts /// /// Port to listen on /// The server address to send to the probe /// The name of the server to send to the probe public async Task StartBeaconAsync( int port, string address, string name) { foreach (var networkInterface in networkInformationProvider.GetNetworkInterfaces()) { try { var beaconClient = new DiscoveryBeacon(networkInterface); await beaconClient.StartAsync(port); beaconClient.OnProbe += async (beacon, probeEndPoint) => { var message = new BeaconMessage { Address = address, Name = name, Version = VersionHelper.GetCurrentVersion().ToString(), }; foreach (var interceptor in _beaconMessageInterceptors) { message = await interceptor.ExecuteAsync(message, beacon.InterfaceIPEndPoint); } await beacon.SendAsync(JsonSerializer.Serialize(message), probeEndPoint); }; logger?.LogInformation("Started beacon on network interface {NetworkInterface}", networkInterface.Name); _beaconClients.Add(beaconClient); } catch (NetworkInformationException) { logger?.LogError("Unable to start beacon on network interface {NetworkInterface}", networkInterface.Name); } catch (Exception ex) { logger?.LogError(ex, "Unknown error while starting beacon on network interface {NetworkInterface}", networkInterface.Name); } } } /// /// Kill any running beacons /// public async Task StopBeaconAsync() { foreach (var beaconClient in _beaconClients) { beaconClient.Dispose(); } } }