using HyperbolicDownloaderApi.FileProcessing; using HyperbolicDownloaderApi.Networking; using SharpOpenNat; using System.Diagnostics; using System.Net; using System.Net.Sockets; namespace HyperbolicDownloaderApi.Managment; public class ApiManager { public static event EventHandler? OnNotificationMessageRecived; private static readonly Random random = new(); private static INatDevice? device; private static Mapping? portMapping; private readonly BroadcastClient broadcastClient = new BroadcastClient(); private readonly NetworkClient networkClient; public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult(); public FilesManager FilesManager { get; } = new(); public HostsManager HostsManager { get; } = new(); public DirectoryWatcher DirectoryWatcher { get; } public ApiManager() { networkClient = new(FilesManager); DirectoryWatcher = new(FilesManager); } public static NetworkSocket? GetLocalSocket() { int port = ApiConfiguration.PublicPort; string? ipAddress = null; if (device is not null) { ipAddress = device.GetExternalIPAsync().GetAwaiter().GetResult()?.ToString(); } if (ipAddress is null or "0.0.0.0") { ipAddress = NetworkUtilities.GetIP4Adress()?.ToString(); port = ApiConfiguration.PrivatePort; } if (ipAddress is null) { return null; } return new NetworkSocket(ipAddress, port, DateTime.Now); } public static async Task OpenPorts() { try { ApiConfiguration.PublicPort = random.Next(1000, 6000); device = await OpenNat.Discoverer.DiscoverDeviceAsync(); IPAddress? ip = await device.GetExternalIPAsync(); SendNotificationMessageNewLine($"The public IP address is: {ip} ", NotificationMessageType.Success); portMapping = new Mapping(Protocol.Tcp, ApiConfiguration.PrivatePort, ApiConfiguration.PublicPort, "HyperbolicDowloader"); await device.CreatePortMapAsync(portMapping); SendNotificationMessageNewLine($"The public port is: {ApiConfiguration.PublicPort}", NotificationMessageType.Success); return true; } catch (NatDeviceNotFoundException) { SendNotificationMessageNewLine($"Could not find a UPnP or NAT-PMP device!", NotificationMessageType.Error); return false; } catch (MappingException ex) { SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error); return false; } catch (SocketException ex) { SendNotificationMessageNewLine($"An error occurred while mapping the private port ({ApiConfiguration.PrivatePort}) to the public port ({ApiConfiguration.PublicPort})! Error message: {ex.Message}", NotificationMessageType.Error); return false; } } public static void ClosePorts() { SendNotificationMessageNewLine("Closing ports...", NotificationMessageType.Info); if (device is not null && portMapping is not null) { try { device.DeletePortMapAsync(portMapping).GetAwaiter().GetResult(); } catch (Exception ex) { SendNotificationMessageNewLine(ex.Message, NotificationMessageType.Error); } } SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning); } public bool StartBroadcastListener() { try { broadcastClient.StartListening(ApiConfiguration.BroadcastPort); broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived; } catch (SocketException ex) { SendNotificationMessageNewLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}", NotificationMessageType.Error); // net stop hens && net start hns return false; } return true; } public bool StartTcpListener() { try { networkClient.ListenTo("GetHostsList", GetHostList); networkClient.ListenTo>("DiscoverAnswer", DiscoverAnswer); networkClient.ListenTo("Message", ReciveMessage); networkClient.ListenTo("HasFile", HasFile); networkClient.ListenTo("GetFilesList", GetFileList); networkClient.StartListening(ApiConfiguration.PrivatePort); } catch (SocketException ex) { SendNotificationMessageNewLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}", NotificationMessageType.Error); // net stop hens && net start hns return false; } return true; } internal static void SendNotificationMessage(string message, NotificationMessageType messageType = NotificationMessageType.Info) { OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message)); } internal static void SendNotificationMessageNewLine(string message, NotificationMessageType messageType = NotificationMessageType.Info) { OnNotificationMessageRecived?.Invoke(null, new NotificationMessageEventArgs(messageType, message + Environment.NewLine)); } private static void ReciveMessage(object? sender, MessageRecivedEventArgs recivedEventArgs) { SendNotificationMessageNewLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.", NotificationMessageType.Info); } private async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) { IPAddress remoteIpAddress = recivedEventArgs.IPEndPoint.Address; Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {remoteIpAddress}"); List hostsToSend = HostsManager.ToList(); NetworkSocket? localSocket = GetLocalSocket(); if (localSocket is null || remoteIpAddress.Equals(PublicIpAddress) || remoteIpAddress.Equals(NetworkUtilities.GetIP4Adress())) { Debug.WriteLine("Invalid broadcast!"); return; } _ = hostsToSend.RemoveAll(x => x.IPAddress == remoteIpAddress.ToString()); hostsToSend.Add(localSocket); bool success = int.TryParse(recivedEventArgs.Message, out int remotePort); if (success) { _ = HostsManager.Add(new NetworkSocket(remoteIpAddress.ToString(), remotePort, DateTime.Now)); try { await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend); } catch (SocketException ex) { Debug.WriteLine(ex); } } } private void DiscoverAnswer(object? sender, MessageRecivedEventArgs> recivedEventArgs) { SendNotificationMessageNewLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s).", NotificationMessageType.Info); SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Discovery answer {string.Join(", ", recivedEventArgs.Data)}", NotificationMessageType.Log); _ = HostsManager.AddRange(recivedEventArgs.Data); } private async void GetHostList(object? sender, MessageRecivedEventArgs recivedEventArgs) { SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting host list", NotificationMessageType.Log); List hostsToSend = HostsManager.ToList(); NetworkSocket? localSocket = GetLocalSocket(); if (localSocket is null) { return; } _ = hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IpAddress.ToString()); hostsToSend.Add(localSocket); if (recivedEventArgs.Data.Port != 0) { _ = HostsManager.Add(recivedEventArgs.Data); } await recivedEventArgs.SendResponseAsync(hostsToSend); } private async void HasFile(object? sender, MessageRecivedEventArgs recivedEventArgs) { SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Check if file exists [{recivedEventArgs.Data}]", NotificationMessageType.Log); await recivedEventArgs.SendResponseAsync(FilesManager.Contains(recivedEventArgs.Data)); } private async void GetFileList(object? sender, MessageRecivedEventArgs recivedEventArgs) { SendNotificationMessageNewLine($"{recivedEventArgs.IpAddress} > Requesting file list", NotificationMessageType.Log); List files = [.. FilesManager .ToList() .Select(f => new HyperFileDto(f)) .Where(f => f.Name.Contains(recivedEventArgs.Data))]; await recivedEventArgs.SendResponseAsync(files); } }