using System.Net; using System.Net.Sockets; using System.Text; using System.Text.Json; using QuickIP.Client.Models; namespace QuickIP.Client.Services; public sealed class DiscoveryService { private const int DiscoveryPort = 50000; public async Task DiscoverAsync(string localIPv4, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(localIPv4)) { return null; } using var client = new UdpClient(AddressFamily.InterNetwork) { EnableBroadcast = true, }; client.Client.Bind(new IPEndPoint(IPAddress.Parse(localIPv4), 0)); var request = new { protocol_version = 1, message_type = "discover", request_id = Guid.NewGuid().ToString(), client_name = Environment.MachineName, }; var payload = JsonSerializer.SerializeToUtf8Bytes(request); await client.SendAsync(payload, payload.Length, new IPEndPoint(IPAddress.Broadcast, DiscoveryPort)); using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(TimeSpan.FromSeconds(3)); try { var result = await client.ReceiveAsync(timeoutCts.Token); var response = JsonSerializer.Deserialize(result.Buffer); if (response is null || response.MessageType != "discover_response") { return null; } return new DiscoveredDevice { DeviceId = response.DeviceId ?? string.Empty, Hostname = response.Hostname ?? string.Empty, AgentVersion = response.AgentVersion ?? string.Empty, Lan2Ip = response.Lan2Ip ?? string.Empty, AuthRequired = response.AuthRequired, }; } catch (OperationCanceledException) { return null; } } private sealed class DiscoveryResponse { public string? MessageType { get; set; } public string? DeviceId { get; set; } public string? Hostname { get; set; } public string? AgentVersion { get; set; } public string? Lan2Ip { get; set; } public bool AuthRequired { get; set; } } }