22 Commits
Author SHA1 Message Date
Stone_Red f7aaa407e7 Merge pull request #2 from Stone-Red-Code/develop
Develop
2023-02-06 15:41:19 +01:00
Stone_Red 2003cdcff0 Merge branch 'main' into develop 2023-02-06 15:41:09 +01:00
Stone_Red 6f9626fcfd Add response to discover command. 2023-02-06 15:28:53 +01:00
Stone_Red fb21a9f492 Code cleanup 2023-02-06 15:15:47 +01:00
Stone_Red 7d662dc113 Ignore broadcast messages from own PC 2022-11-25 12:05:34 +01:00
Stone_Red 1f770ce131 Add missing lines to README 2022-04-28 07:28:35 +02:00
Stone_Red 4dd2b7274e Fix StartTcpListener method not stopping program if fails 2022-04-25 11:16:01 +02:00
Stone_Red f5dd85e38c Update Console.Commander.NET to the newest version 2022-04-25 11:15:23 +02:00
Stone_Red 826c6fc7ee Fix command descriptions in README.md 2022-04-25 11:14:27 +02:00
Stone_Red c2b84dd475 Code refactoring 2022-04-22 16:09:21 +02:00
Stone_Red 9f08b5ca94 Merge branch 'develop' 2022-04-21 07:35:57 +02:00
Stone_Red f906d4581c Update naming and add auto retry if hash is wrong 2022-04-21 07:35:52 +02:00
Stone_Red 69929fd58a Add rule to ignore the warning: Mark members as static to .editorconfig 2022-04-21 07:35:50 +02:00
Stone_Red 495a09ec60 Update naming and add auto retry if hash is wrong 2022-04-21 07:34:28 +02:00
Stone_Red 21abb44230 Add rule to ignore the warning: Mark members as static to .editorconfig 2022-04-21 07:33:26 +02:00
Stone_Red 090c23d4a2 Merge pull request #1 from Stone-Red-Code/develop
Develop
2022-03-07 12:29:31 +01:00
Stone_Red 6572e5bda5 Fix bug
- Fix incorrect download speed calculation
- Add debug logging to the download section
2022-03-02 19:05:45 +01:00
Stone_Red a90af811c1 Fix important bug
- Fixed a bug that caused the content of the file to be sent as the filename
2022-02-28 20:28:05 +01:00
Stone_Red 9675361695 Fix code formatting and decrease host answer delay 2022-02-27 13:47:45 +01:00
Stone_Red 715f5de435 Add missing | 2022-02-26 15:32:03 +01:00
Stone_Red a2fa7dd3f8 Resolve domains and code structure improvements
- Resolve domains when adding a new host
- Change `NotificationMessageType.Raw` to `NotificationMessageType.Info`
- Fix wrong color when using the `info` command
2022-02-26 15:29:27 +01:00
Stone_Red cf7f952012 Improve code structure
- Add separate API project
- Add notification system
2022-02-24 22:56:45 +01:00
34 changed files with 1163 additions and 1047 deletions
+10
View File
@@ -0,0 +1,10 @@
[*.cs]
# S4457: Parameter validation in "async"/"await" methods should be wrapped
dotnet_diagnostic.S4457.severity = silent
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = silent
# S1172: Unused method parameters should be removed
dotnet_diagnostic.S1172.severity = silent
+10 -5
View File
@@ -5,7 +5,12 @@ VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloader", "HyperbolicDownloader\HyperbolicDownloader.csproj", "{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloader", "HyperbolicDownloader\HyperbolicDownloader.csproj", "{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HyperbolicDownloaderGUI", "HyperbolicDownloaderGUI\HyperbolicDownloaderGUI.csproj", "{E1CE6E07-F8A6-447F-837D-9D1943A42E96}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperbolicDownloaderApi", "HyperbolicDownloaderApi\HyperbolicDownloaderApi.csproj", "{45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CA4F21C6-5B63-46C1-8576-91BB3FAE4FA7}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +22,10 @@ Global
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.Build.0 = Release|Any CPU {7BA90CAF-36A1-4D55-9CE2-E498ECC1B62D}.Release|Any CPU.Build.0 = Release|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Debug|Any CPU.Build.0 = Debug|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Release|Any CPU.ActiveCfg = Release|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1CE6E07-F8A6-447F-837D-9D1943A42E96}.Release|Any CPU.Build.0 = Release|Any CPU {45ACEEC6-9AE4-4DA5-9A08-E22F1EBA7E22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -8,9 +8,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.0" /> <None Include="..\.editorconfig" Link=".editorconfig" />
<PackageReference Include="Open.NAT" Version="2.1.0" /> </ItemGroup>
<ItemGroup>
<PackageReference Include="Console.Commander.NET" Version="1.0.1" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" /> <PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HyperbolicDownloaderApi\HyperbolicDownloaderApi.csproj" />
</ItemGroup>
</Project> </Project>
+74
View File
@@ -0,0 +1,74 @@
using Commander_Net;
using HyperbolicDownloaderApi.Commands;
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader;
internal class InputHandler
{
private readonly Commander commander = new Commander();
private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager)
{
HostCommands hostCommands = new HostCommands(hostsManager);
FileCommands fileCommands = new FileCommands(hostsManager, filesManager);
DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager);
ClientCommands clientCommands = new ClientCommands();
_ = commander.Register(input => commander.PrintHelp(input), "help");
_ = commander.Register(_ => Console.Clear(), (HelpText)"Clears the console.", "clear", "cls");
_ = commander.Register(_ => exit = true, (HelpText)"Exits the application.", "exit", "quit");
_ = commander.Register(clientCommands.ShowInfo, (HelpText)"Displays the private and public IP address.", "info", "inf");
_ = commander.Register(hostCommands.Discover, (HelpText)"Tries to find other active hosts on the local network.", "discover", "disc");
Command getCommand = commander.Register(downloadCommands.GetFile, (HelpText)"Attempts to retrieve a file from another host using a hash.", "get");
_ = getCommand.Register(downloadCommands.GetFileFrom, (HelpText)"Attempts to retrieve a file from another host using a .hyper file.", "from");
Command generateCommad = commander.Register(fileCommands.GenerateFileFull, (HelpText)"Generates a .hyper file from a file hash.", "generate", "gen");
_ = generateCommad.Register(fileCommands.GenerateFileSingle, (HelpText)"Generates a .hyper file from a file hash without checking the known hosts. This adds only the local host to the file.", "noscan");
Command addCommand = commander.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "add");
_ = addCommand.Register(fileCommands.AddFile, (HelpText)"Adds a file to the tracking list.", "file");
_ = addCommand.Register(hostCommands.AddHost, (HelpText)"Adds a host to the list of known hosts.", "host");
Command removeCommand = commander.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "remove", "rm");
_ = removeCommand.Register(fileCommands.RemoveFile, (HelpText)"Removes a file from the tracking list.", "file");
_ = removeCommand.Register(hostCommands.RemoveHost, (HelpText)"Removes a host from the list of known hosts.", "host");
Command listCommand = commander.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "list", "ls");
_ = listCommand.Register(fileCommands.ListFiles, (HelpText)"Lists all files.", "files");
_ = listCommand.Register(hostCommands.ListHosts, (HelpText)"lists all hosts.", "hosts");
_ = commander.Register(hostCommands.CheckActiveHosts, (HelpText)"Checks the status of known hosts.", "status", "check");
}
public void ReadInput()
{
while (!exit)
{
Console.WriteLine();
Console.Write("> ");
Console.CursorVisible = true;
string input = Console.ReadLine()?.Trim() ?? string.Empty;
Console.CursorVisible = false;
try
{
if (!commander.Execute(input, out _))
{
ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red);
}
}
catch (Exception ex)
{
ConsoleExt.WriteLine($"An unexpected error occurred! {ex}", ConsoleColor.Red);
}
}
}
}
@@ -1,14 +0,0 @@
namespace HyperbolicDownloader
{
internal class DataContainer
{
public string EventName { get; set; }
public string JsonData { get; set; }
public DataContainer(string eventName, string jsonData)
{
JsonData = jsonData;
EventName = eventName;
}
}
}
@@ -1,34 +0,0 @@
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader
{
internal class MessageRecivedEventArgs<T> : EventArgs
{
private readonly NetworkStream networkStream;
public MessageRecivedEventArgs(NetworkStream networkStream, IPAddress ipAddress, T data)
{
this.networkStream = networkStream;
Data = data;
IpAddress = ipAddress;
}
public T Data { get; set; }
public IPAddress IpAddress { get; set; }
public async Task SendResponseAsync(object response)
{
byte[] bytesToSend = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(response));
await networkStream.WriteAsync(bytesToSend);
}
public void SendResponse(object response)
{
SendResponseAsync(response).GetAwaiter().GetResult();
}
}
}
@@ -1,204 +0,0 @@
using HyperbolicDownloader.FileProcessing;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader.Networking
{
internal class NetworkClient
{
public bool IsListening { get; private set; } = false;
private TcpListener? tcpListener;
private readonly FilesManager filesManager;
private readonly Dictionary<string, (Type type, Delegate method)> events = new();
public NetworkClient(FilesManager filesManager)
{
this.filesManager = filesManager;
}
public static async Task<T?> SendAsync<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
byte[] bytesToRead = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize));
string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
client.Close();
if (string.IsNullOrWhiteSpace(response))
{
return default;
}
else
{
return JsonSerializer.Deserialize<T>(response);
}
}
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
client.Close();
}
public static T? Send<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
return SendAsync<T>(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public static void Send(IPAddress remoteIp, int remotePort, string eventName, object data)
{
SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public void StartListening(int port)
{
if (IsListening)
{
throw new InvalidOperationException("Already listening!");
}
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
IsListening = true;
Task.Run(async () =>
{
while (IsListening)
{
try
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (dataReceived.StartsWith("Download"))
{
_ = Upload(client, dataReceived[8..]);
continue;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
client.Close();
continue;
}
DataContainer? dataContainer = JsonSerializer.Deserialize<DataContainer>(dataReceived);
if (dataContainer is not null && events.ContainsKey(dataContainer.EventName))
{
(Type type, Delegate method) = events[dataContainer.EventName];
Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type);
object? eventArgs = Activator.CreateInstance(
eventArgsType,
nwStream,
(client.Client.RemoteEndPoint as IPEndPoint)?.Address,
JsonSerializer.Deserialize(dataContainer.JsonData, type));
method?.DynamicInvoke(this, eventArgs);
}
client.Close();
}
catch (SocketException ex)
{
if (ex.SocketErrorCode != SocketError.Interrupted)
{
throw;
}
}
}
tcpListener.Stop();
});
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
await nwStream.WriteAsync(bytesToSend);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000))
{
if (chunk is not null)
{
await nwStream.WriteAsync(chunk);
}
}
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
await nwStream.WriteAsync(bytesToSend);
}
client.Close();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
public void StopListening()
{
tcpListener?.Stop();
IsListening = false;
}
public void ListenTo<T>(string eventName, EventHandler<MessageRecivedEventArgs<T>> eventHandler)
{
events.Add(eventName, (typeof(T), eventHandler));
}
}
}
+43 -213
View File
@@ -1,61 +1,41 @@
using HyperbolicDownloader.FileProcessing; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloader.Networking; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloader.UserInterface; using HyperbolicDownloaderApi.Networking;
using Open.Nat;
using Stone_Red_Utilities.ConsoleExtentions; using Stone_Red_Utilities.ConsoleExtentions;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloader; namespace HyperbolicDownloader;
internal static class Program internal static class Program
{ {
public const int BroadcastPort = 2155; private static readonly ApiManager apiManager = new ApiManager();
public static string BasePath { get; } = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? string.Empty;
public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json");
public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json");
public static int PublicPort { get; private set; }
public static int PrivatePort { get; } = 3055;
public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult();
private static NatDevice? device;
private static Mapping? portMapping;
private static readonly HostsManager hostsManager = new();
private static readonly FilesManager filesManager = new();
private static readonly NetworkClient networkClient = new(filesManager);
private static readonly Random random = new();
private static async Task Main(string[] args) private static async Task Main(string[] args)
{ {
Console.CancelKeyPress += Console_CancelKeyPress; Console.CancelKeyPress += (_, _) => Close();
Console.CursorVisible = false; Console.CursorVisible = false;
if (File.Exists(HostsFilePath)) ApiManager.OnNotificationMessageRecived += ApiManager_OnNotificationMessageRecived;
if (File.Exists(ApiConfiguration.HostsFilePath))
{ {
string hostsJson = await File.ReadAllTextAsync(HostsFilePath); string hostsJson = await File.ReadAllTextAsync(ApiConfiguration.HostsFilePath);
hostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new()); apiManager.HostsManager.AddRange(JsonSerializer.Deserialize<List<NetworkSocket>>(hostsJson) ?? new());
} }
if (File.Exists(FilesInfoPath)) if (File.Exists(ApiConfiguration.FilesInfoPath))
{ {
string filesJson = await File.ReadAllTextAsync(FilesInfoPath); string filesJson = await File.ReadAllTextAsync(ApiConfiguration.FilesInfoPath);
filesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? new()); apiManager.FilesManager.AddRange(JsonSerializer.Deserialize<List<PrivateHyperFileInfo>>(filesJson) ?? new());
} }
InputHandler inputHandler = new InputHandler(hostsManager, filesManager); InputHandler inputHandler = new InputHandler(apiManager.HostsManager, apiManager.FilesManager);
if (args.Length > 0 && File.Exists(args[0])) if (args.Length > 0 && File.Exists(args[0]))
{ {
UserInterface.Commands.DownloadCommands downloadCommands = new UserInterface.Commands.DownloadCommands(hostsManager, filesManager); HyperbolicDownloaderApi.Commands.DownloadCommands downloadCommands = new HyperbolicDownloaderApi.Commands.DownloadCommands(apiManager.HostsManager, apiManager.FilesManager);
downloadCommands.GetFileFrom(args[0]); downloadCommands.GetFileFrom(args[0]);
Console.WriteLine("Do you want to continue using this instance? [y/N]"); Console.WriteLine("Do you want to continue using this instance? [y/N]");
if (char.ToLower(Console.ReadKey().KeyChar) != 'y') if (char.ToLower(Console.ReadKey().KeyChar) != 'y')
@@ -65,212 +45,62 @@ internal static class Program
Console.WriteLine(); Console.WriteLine();
} }
await Initialize();
inputHandler.ReadInput();
Close();
}
private static async Task Initialize()
{
Console.WriteLine("Searching for a UPnP/NAT-PMP device..."); Console.WriteLine("Searching for a UPnP/NAT-PMP device...");
_ = await OpenPorts(); _ = await ApiManager.OpenPorts();
ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()} ", ConsoleColor.Green); ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()} ", ConsoleColor.Green);
ConsoleExt.WriteLine($"The private port is: {PrivatePort}", ConsoleColor.Green); ConsoleExt.WriteLine($"The private port is: {ApiConfiguration.PrivatePort}", ConsoleColor.Green);
Console.WriteLine("Starting TCP listener..."); Console.WriteLine("Starting TCP listener...");
if (!apiManager.StartTcpListener())
try
{ {
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList); _ = Console.ReadLine();
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile);
networkClient.StartListening(PrivatePort);
}
catch (SocketException ex)
{
ConsoleExt.WriteLine($"An error occurred while starting the TCP listener! Error message: {ex.Message}", ConsoleColor.Red); // net stop hns && net start hns
Console.ReadKey();
return;
} }
BroadcastClient broadcastClient = new BroadcastClient(); Console.WriteLine("Starting broadcast listener...");
apiManager.StartBroadcastListener();
Console.WriteLine("Running local discovery routine...");
BroadcastClient.Send(BroadcastPort, PrivatePort.ToString());
await Task.Delay(3000);
int activeHostsCount = 0; int activeHostsCount = 0;
if (hostsManager.Count > 0) if (apiManager.HostsManager.Count > 0)
{ {
Console.WriteLine("Checking if hosts are active..."); Console.WriteLine("Checking if hosts are active...");
activeHostsCount = hostsManager.CheckHostsActivity(); activeHostsCount = apiManager.HostsManager.CheckHostsActivity();
} }
if (activeHostsCount == 0) if (activeHostsCount == 0)
{ {
ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red); ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red);
ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", ConsoleColor.Red); ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host or use 'discover' to find hosts in the local network.", ConsoleColor.Red);
} }
Console.WriteLine($"{hostsManager.Count} known host(s)."); Console.WriteLine($"{apiManager.HostsManager.Count} known host(s).");
Console.WriteLine($"{activeHostsCount} active host(s)."); Console.WriteLine($"{activeHostsCount} active host(s).");
Console.WriteLine("Starting broadcast listener...");
broadcastClient.StartListening(BroadcastPort);
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived;
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
inputHandler.ReadInput(); ConsoleExt.WriteLine("Ready", ConsoleColor.Green);
ClosePorts();
} }
private static async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs) private static void ApiManager_OnNotificationMessageRecived(object? sender, NotificationMessageEventArgs e)
{ {
Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {recivedEventArgs.IPEndPoint.Address}"); switch (e.NotificationMessageType)
List<NetworkSocket> hostsToSend = hostsManager.ToList();
NetworkSocket? localSocket = GetLocalSocket();
if (localSocket is null)
{ {
return; case NotificationMessageType.Info: Console.Write(e.Message); break;
} case NotificationMessageType.Success: ConsoleExt.Write(e.Message, ConsoleColor.Green); break;
case NotificationMessageType.Warning: ConsoleExt.Write(e.Message, ConsoleColor.DarkYellow); break;
hostsToSend.RemoveAll(x => x.IPAddress == recivedEventArgs.IPEndPoint.Address.ToString()); case NotificationMessageType.Error: ConsoleExt.Write(e.Message, ConsoleColor.Red); break;
hostsToSend.Add(localSocket);
bool success = int.TryParse(recivedEventArgs.Message, out int remotePort);
if (success)
{
hostsManager.Add(new NetworkSocket(recivedEventArgs.IPEndPoint.Address.ToString(), remotePort, DateTime.Now));
try
{
await NetworkClient.SendAsync(recivedEventArgs.IPEndPoint.Address, remotePort, "DiscoverAnswer", hostsToSend);
}
catch (SocketException ex)
{
Debug.WriteLine(ex);
}
} }
} }
private static void DiscoverAnswer(object? sender, MessageRecivedEventArgs<List<NetworkSocket>> recivedEventArgs) private static void Close()
{ {
Console.WriteLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s)."); ApiManager.ClosePorts();
hostsManager.AddRange(recivedEventArgs.Data); apiManager.HostsManager.SaveHosts();
}
private static void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
Console.WriteLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.");
}
private static async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{
List<NetworkSocket> 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 static async void HasFile(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
await recivedEventArgs.SendResponseAsync(filesManager.Contains(recivedEventArgs.Data));
}
public static async Task<bool> OpenPorts()
{
try
{
PublicPort = random.Next(1000, 6000);
NatDiscoverer? discoverer = new NatDiscoverer();
device = await discoverer.DiscoverDeviceAsync();
IPAddress? ip = await device.GetExternalIPAsync();
ConsoleExt.WriteLine($"The public IP address is: {ip} ", ConsoleColor.Green);
portMapping = new Mapping(Protocol.Tcp, PrivatePort, PublicPort, "HyperbolicDowloader");
await device.CreatePortMapAsync(portMapping);
ConsoleExt.WriteLine($"The public port is: {PublicPort}", ConsoleColor.Green);
return true;
}
catch (NatDeviceNotFoundException)
{
ConsoleExt.WriteLine($"Could not find a UPnP or NAT-PMP device!", ConsoleColor.Red);
return false;
}
catch (MappingException ex)
{
ConsoleExt.WriteLine($"An error occurred while mapping the private port ({PrivatePort}) to the public port ({PublicPort})! Error message: {ex.Message}", ConsoleColor.Red);
return false;
}
}
private static void ClosePorts()
{
Console.WriteLine("Closing ports...");
if (device is not null)
{
try
{
IEnumerable<Mapping>? mappings = device.GetAllMappingsAsync().GetAwaiter().GetResult();
foreach (Mapping? mapping in mappings)
{
if (mapping.Description.Contains("HyperbolicDowloader") && mapping.PrivateIP.ToString() == portMapping?.PrivateIP.ToString())
{
device.DeletePortMapAsync(mapping).GetAwaiter().GetResult();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
ConsoleExt.WriteLine("Ports closed!", ConsoleColor.DarkYellow);
Environment.Exit(0);
}
private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e)
{
ClosePorts();
hostsManager.SaveHosts();
}
public static NetworkSocket? GetLocalSocket()
{
int port = PublicPort;
string? ipAddress = null;
if (device is not null)
{
ipAddress = (device.GetExternalIPAsync()).GetAwaiter().GetResult()?.ToString();
}
if (ipAddress is null || ipAddress == "0.0.0.0")
{
ipAddress = NetworkUtilities.GetIP4Adress()?.ToString();
port = PrivatePort;
}
if (ipAddress is null)
{
return null;
}
return new NetworkSocket(ipAddress, port, DateTime.Now);
} }
} }
@@ -1,21 +0,0 @@
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class ClientCommands
{
public void ShowInfo(string _)
{
if (Program.PublicIpAddress is not null)
{
ConsoleExt.WriteLine($"The public IP address is: {Program.PublicIpAddress}", ConsoleColor.Green);
ConsoleExt.WriteLine($"The public port is: {Program.PublicPort}", ConsoleColor.Green);
Console.WriteLine();
}
ConsoleExt.WriteLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()}", ConsoleColor.Green);
ConsoleExt.WriteLine($"The private port is: {Program.PrivatePort}", ConsoleColor.Green);
}
}
@@ -1,237 +0,0 @@
using HyperbolicDownloader.FileProcessing;
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using Stone_Red_Utilities.StringExtentions;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class DownloadCommands
{
private readonly HostsManager hostsManager;
private readonly FilesManager filesManager;
public DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
{
this.hostsManager = hostsManager;
this.filesManager = filesManager;
}
public void GetFileFrom(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
ConsoleExt.WriteLine("Path is empty!", ConsoleColor.Red);
return;
}
string fullPath = Path.GetFullPath(path);
if (!File.Exists(fullPath))
{
ConsoleExt.WriteLine("Invalid file path!", ConsoleColor.Red);
}
string json = File.ReadAllText(fullPath);
PublicHyperFileInfo? publicHyperFileInfo = JsonSerializer.Deserialize<PublicHyperFileInfo>(json);
if (publicHyperFileInfo == null)
{
ConsoleExt.WriteLine("Parsing file failed!", ConsoleColor.Red);
return;
}
hostsManager.AddRange(publicHyperFileInfo.Hosts);
GetFile(publicHyperFileInfo.Hash);
}
public void GetFile(string hash)
{
if (string.IsNullOrEmpty(hash))
{
ConsoleExt.WriteLine("No hash value specified!", ConsoleColor.Red);
return;
}
hash = hash.Trim().ToLower();
foreach (NetworkSocket host in hostsManager.ToList())
{
bool validIpAdress = IPAddress.TryParse(host.IPAddress, out IPAddress? ipAddress);
if (!validIpAdress)
{
hostsManager.Remove(host, true);
continue;
}
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow);
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red);
hostsManager.Remove(host);
continue;
}
else if (!sendTask.Result)
{
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", ConsoleColor.Red);
continue;
}
host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Has the requested file", ConsoleColor.Green);
Console.WriteLine("Requesting file...");
using TcpClient tcpClient = new TcpClient();
tcpClient.Connect(ipAddress!, host.Port);
tcpClient.ReceiveBufferSize = 64000;
NetworkStream nwStream = tcpClient.GetStream();
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
byte[] reciveBuffer = new byte[64000];
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}");
nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 30000;
int bytesRead;
try
{
bytesRead = nwStream.Read(buffer, 0, tcpClient.ReceiveBufferSize);
}
catch (IOException)
{
Console.WriteLine();
ConsoleExt.WriteLine("Lost connection to other host!", ConsoleColor.Red);
continue;
}
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
string[] parts = dataReceived.Split('/');
if (parts.Length == 2) //If received data does not contain 2 parts -> error
{
bool validFileSize = int.TryParse(parts[0], out int fileSize);
if (!validFileSize || fileSize <= 0)
{
ConsoleExt.WriteLine("Invalid file size!", ConsoleColor.Red);
continue;
}
string fileName = parts[1].ToFileName();
string directoryPath = Path.Combine(Program.BasePath, "Downloads");
string filePath = Path.Combine(directoryPath, fileName);
Console.WriteLine($"File name: {fileName}");
Console.WriteLine($"Starting download...");
int totalBytesRead = 0;
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int bytesInOneSecond = 0;
int unitsPerSecond = 0;
string unit = "Kb";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (totalBytesRead < fileSize)
{
try
{
bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length);
}
catch (IOException)
{
Console.WriteLine();
ConsoleExt.WriteLine("Lost connection to other host!", ConsoleColor.Red);
break;
}
bytesRead = Math.Min(bytesRead, fileSize - totalBytesRead);
fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
bytesInOneSecond += bytesRead;
if (stopWatch.ElapsedMilliseconds >= 1000)
{
unitsPerSecond = (unitsPerSecond + bytesInOneSecond) / 2;
if (unitsPerSecond > 125000)
{
unitsPerSecond /= 125000;
unit = "Mb";
}
else
{
unitsPerSecond /= 125;
unit = "Kb";
}
bytesInOneSecond = 0;
stopWatch.Restart();
}
Console.CursorLeft = 0;
Console.Out.WriteAsync($"Downloading: {Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}% {totalBytesRead / 1000}/{fileSize / 1000}KB [{unitsPerSecond}{unit}/s] ");
}
fileStream.Close();
if (totalBytesRead < fileSize)
{
continue;
}
Console.WriteLine();
Console.WriteLine("Validating file...");
if (FileValidator.ValidateHash(filePath, hash))
{
_ = filesManager.TryAdd(filePath, out _, out _);
}
else
{
ConsoleExt.WriteLine("Warning: File hash does not match! File might me corrupted or manipulated!", ConsoleColor.DarkYellow);
}
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}");
ConsoleExt.WriteLine("Done", ConsoleColor.Green);
stopWatch.Stop();
hostsManager.SaveHosts();
return;
}
else
{
ConsoleExt.WriteLine(dataReceived, ConsoleColor.Red);
}
}
ConsoleExt.WriteLine("None of the available hosts have the requested file!", ConsoleColor.Red);
hostsManager.SaveHosts();
}
}
@@ -1,149 +0,0 @@
using HyperbolicDownloader.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloader.UserInterface.Commands;
internal class HostCommands
{
private readonly HostsManager hostsManager;
public HostCommands(HostsManager hostsManager)
{
this.hostsManager = hostsManager;
}
public void Discover(string _)
{
Console.WriteLine("Running local discovery routine...");
BroadcastClient.Send(Program.BroadcastPort, Program.PrivatePort.ToString());
Thread.Sleep(3000);
}
public void CheckActiveHosts(string _)
{
int activeHostsCount = hostsManager.CheckHostsActivity();
if (activeHostsCount == 0)
{
ConsoleExt.WriteLine("No active hosts found!", ConsoleColor.Red);
ConsoleExt.WriteLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", ConsoleColor.Red);
}
Console.WriteLine();
Console.WriteLine($"{hostsManager.Count} known host(s).");
Console.WriteLine($"{activeHostsCount} active host(s).");
}
public void ListHosts(string _)
{
int index = 0;
List<NetworkSocket> hosts = hostsManager.ToList();
if (hosts.Count == 0)
{
ConsoleExt.WriteLine("No known hosts", ConsoleColor.DarkYellow);
return;
}
foreach (NetworkSocket host in hosts)
{
index++;
Console.WriteLine($"{index}) {host.IPAddress}:{host.Port}");
Console.WriteLine($"Last active: {host.LastActive}");
Console.WriteLine();
}
Console.CursorTop--;
}
public void RemoveHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ConsoleExt.WriteLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", ConsoleColor.Red);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port < 1000 || port >= 6000)
{
ConsoleExt.WriteLine("Invalid port number!", ConsoleColor.Red);
return;
}
if (!IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
ConsoleExt.WriteLine("Invalid IP address!", ConsoleColor.Red);
return;
}
NetworkSocket hostToRemove = new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue);
if (!hostsManager.Contains(hostToRemove))
{
ConsoleExt.WriteLine("Host not in list", ConsoleColor.Red);
return;
}
hostsManager.Remove(new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue), true);
ConsoleExt.WriteLine($"Successfully Removed host!", ConsoleColor.Green);
}
public void AddHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ConsoleExt.WriteLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", ConsoleColor.Red);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port < 1000 || port >= 6000)
{
ConsoleExt.WriteLine("Invalid port number!", ConsoleColor.Red);
}
else if (IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
try
{
Console.WriteLine("Waiting for response...");
NetworkSocket? localSocket = Program.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
List<NetworkSocket>? recivedHosts = NetworkClient.Send<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket);
if (recivedHosts is not null)
{
ConsoleExt.WriteLine($"Success! Added {recivedHosts.Count} new host(s).", ConsoleColor.Green);
hostsManager.AddRange(recivedHosts);
}
else
{
ConsoleExt.WriteLine($"Invalid response!", ConsoleColor.Red);
}
}
catch (SocketException ex)
{
ConsoleExt.WriteLine($"Invalid host! Error message: {ex.Message}", ConsoleColor.Red);
}
catch (IOException ex)
{
ConsoleExt.WriteLine($"Invalid host! Error message: {ex.Message}", ConsoleColor.Red);
}
}
else
{
ConsoleExt.WriteLine("Invalid IP address!", ConsoleColor.Red);
}
}
}
@@ -1,81 +0,0 @@
using Commander_Net;
using HyperbolicDownloader.FileProcessing;
using HyperbolicDownloader.UserInterface.Commands;
using Stone_Red_Utilities.ConsoleExtentions;
namespace HyperbolicDownloader.UserInterface;
internal class InputHandler
{
private readonly HostsManager hostsManager;
private readonly Commander commander = new Commander();
private bool exit = false;
public InputHandler(HostsManager hostsManager, FilesManager filesManager)
{
HostCommands hostCommands = new HostCommands(hostsManager);
FileCommands fileCommands = new FileCommands(hostsManager, filesManager);
DownloadCommands downloadCommands = new DownloadCommands(hostsManager, filesManager);
ClientCommands clientCommands = new ClientCommands();
this.hostsManager = hostsManager;
commander.Register((_) => Console.Clear(), "clear", "cls");
commander.Register(Exit, "exit", "quit");
commander.Register(clientCommands.ShowInfo, "info", "inf");
commander.Register(hostCommands.Discover, "discover", "disc");
Command getCommand = commander.Register(downloadCommands.GetFile, "get");
getCommand.Register(downloadCommands.GetFileFrom, "from");
Command generateCommad = commander.Register(fileCommands.GenerateFileFull, "generate", "gen");
generateCommad.Register(fileCommands.GenerateFileSingle, "noscan");
Command addCommand = commander.Register(fileCommands.AddFile, "add");
addCommand.Register(hostCommands.AddHost, "host");
addCommand.Register(fileCommands.AddFile, "file");
Command removeCommand = commander.Register(fileCommands.RemoveFile, "remove", "rm");
removeCommand.Register(hostCommands.RemoveHost, "host");
removeCommand.Register(fileCommands.RemoveFile, "file");
Command listCommand = commander.Register(fileCommands.ListFiles, "list", "ls");
listCommand.Register(fileCommands.ListFiles, "files");
listCommand.Register(hostCommands.ListHosts, "hosts");
commander.Register(hostCommands.CheckActiveHosts, "status", "check");
}
public void ReadInput()
{
while (!exit)
{
Console.WriteLine();
Console.Write("> ");
Console.CursorVisible = true;
string input = Console.ReadLine()?.Trim() ?? string.Empty;
Console.CursorVisible = false;
try
{
if (!commander.Execute(input))
{
ConsoleExt.WriteLine("Unknown command!", ConsoleColor.Red);
}
}
catch (Exception ex)
{
ConsoleExt.WriteLine($"An unexpected error occurred! {ex}", ConsoleColor.Red);
}
}
}
private void Exit(string _)
{
hostsManager.SaveHosts();
exit = true;
}
}
@@ -0,0 +1,20 @@
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
namespace HyperbolicDownloaderApi.Commands;
public class ClientCommands
{
public void ShowInfo(string _)
{
if (ApiManager.PublicIpAddress is not null)
{
ApiManager.SendNotificationMessageNewLine($"The public IP address is: {ApiManager.PublicIpAddress}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"The public port is: {ApiConfiguration.PublicPort}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
}
ApiManager.SendNotificationMessageNewLine($"The private IP address is: {NetworkUtilities.GetIP4Adress()}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"The private port is: {ApiConfiguration.PrivatePort}", NotificationMessageType.Info);
}
}
@@ -0,0 +1,236 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.StringExtentions;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Commands;
public class DownloadCommands
{
private readonly HostsManager hostsManager;
private readonly FilesManager filesManager;
public DownloadCommands(HostsManager hostsManager, FilesManager filesManager)
{
this.hostsManager = hostsManager;
this.filesManager = filesManager;
}
public void GetFileFrom(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
ApiManager.SendNotificationMessageNewLine("Path is empty!", NotificationMessageType.Error);
return;
}
string fullPath = Path.GetFullPath(path);
if (!File.Exists(fullPath))
{
ApiManager.SendNotificationMessageNewLine("Invalid file path!", NotificationMessageType.Error);
}
string json = File.ReadAllText(fullPath);
PublicHyperFileInfo? publicHyperFileInfo = JsonSerializer.Deserialize<PublicHyperFileInfo>(json);
if (publicHyperFileInfo == null)
{
ApiManager.SendNotificationMessageNewLine("Parsing file failed!", NotificationMessageType.Error);
return;
}
hostsManager.AddRange(publicHyperFileInfo.Hosts);
GetFile(publicHyperFileInfo.Hash);
}
public void GetFile(string hash)
{
if (string.IsNullOrEmpty(hash))
{
ApiManager.SendNotificationMessageNewLine("No hash value specified!", NotificationMessageType.Error);
return;
}
hash = hash.Trim().ToLower();
foreach (NetworkSocket host in hostsManager.ToList())
{
bool validIpAdress = IPAddress.TryParse(host.IPAddress, out IPAddress? ipAddress);
if (!validIpAdress)
{
hostsManager.Remove(host, true);
continue;
}
ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
Console.CursorLeft = 0;
Task<bool> sendTask = NetworkClient.SendAsync<bool>(ipAddress!, host.Port, "HasFile", hash);
_ = sendTask.Wait(1000);
if (!sendTask.IsCompletedSuccessfully)
{
Console.CursorLeft = 0;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
hostsManager.Remove(host);
continue;
}
else if (!sendTask.Result)
{
host.LastActive = DateTime.Now;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", NotificationMessageType.Error);
continue;
}
host.LastActive = DateTime.Now;
ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Has the requested file", NotificationMessageType.Success);
ApiManager.SendNotificationMessageNewLine("Requesting file...");
using TcpClient tcpClient = new TcpClient();
tcpClient.Connect(ipAddress!, host.Port);
tcpClient.ReceiveBufferSize = 64000;
NetworkStream nwStream = tcpClient.GetStream();
byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
byte[] reciveBuffer = new byte[64000];
byte[] bytesToSend = Encoding.ASCII.GetBytes($"Download {hash}");
nwStream.Write(bytesToSend);
nwStream.ReadTimeout = 30000;
int bytesRead;
try
{
bytesRead = nwStream.Read(buffer, 0, 1000);
}
catch (IOException)
{
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Lost connection to other host!", NotificationMessageType.Error);
continue;
}
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
string[] parts = dataReceived.Split('/');
if (parts.Length != 2) //If received data does not contain 2 parts -> error
{
ApiManager.SendNotificationMessageNewLine(dataReceived, NotificationMessageType.Error);
continue;
}
bool validFileSize = int.TryParse(parts[0], out int fileSize);
if (!validFileSize || fileSize <= 0)
{
ApiManager.SendNotificationMessageNewLine("Invalid file size!", NotificationMessageType.Error);
continue;
}
string fileName = parts[1].ToFileName();
string directoryPath = Path.Combine(ApiConfiguration.BasePath, "Downloads");
string filePath = Path.Combine(directoryPath, fileName);
ApiManager.SendNotificationMessageNewLine($"File name: {fileName}");
ApiManager.SendNotificationMessageNewLine($"Starting download...");
int totalBytesRead = 0;
if (!Directory.Exists(directoryPath))
{
_ = Directory.CreateDirectory(directoryPath);
}
using FileStream? fileStream = new FileStream(filePath, FileMode.Create);
int bytesInOneSecond = 0;
int unitsPerSecond = 0;
string unit = "Kb";
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (totalBytesRead < fileSize)
{
try
{
bytesRead = nwStream.Read(reciveBuffer, 0, reciveBuffer.Length);
}
catch (IOException ex)
{
Debug.WriteLine(ex);
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Lost connection to other host!", NotificationMessageType.Error);
break;
}
bytesRead = Math.Min(bytesRead, fileSize - totalBytesRead);
fileStream.Write(reciveBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
bytesInOneSecond += bytesRead;
if (stopWatch.Elapsed.TotalSeconds >= 1)
{
unitsPerSecond = (int)(bytesInOneSecond * stopWatch.Elapsed.TotalSeconds);
if (unitsPerSecond > 125000)
{
unitsPerSecond /= 125000;
unit = "Mb";
}
else
{
unitsPerSecond /= 125;
unit = "Kb";
}
bytesInOneSecond = 0;
stopWatch.Restart();
}
ApiManager.SendNotificationMessage($"\rDownloading: {Math.Clamp(Math.Ceiling(100d / fileSize * totalBytesRead), 0, 100)}% {totalBytesRead / 1000}/{fileSize / 1000}KB [{unitsPerSecond}{unit}/s] ");
}
fileStream.Close();
if (totalBytesRead < fileSize)
{
continue;
}
ApiManager.SendNotificationMessageNewLine(string.Empty);
ApiManager.SendNotificationMessageNewLine("Validating file...");
if (FileValidator.ValidateHash(filePath, hash))
{
_ = filesManager.TryAdd(filePath, out _, out _);
}
else
{
ApiManager.SendNotificationMessageNewLine("Warning: File hash does not match! File might me corrupted or manipulated! Trying next host...", NotificationMessageType.Warning);
continue;
}
ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
stopWatch.Stop();
hostsManager.SaveHosts();
return;
}
ApiManager.SendNotificationMessageNewLine("None of the available hosts have the requested file!", NotificationMessageType.Error);
hostsManager.SaveHosts();
}
}
@@ -1,14 +1,13 @@
using HyperbolicDownloader.FileProcessing; using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloader.Networking; using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloader.UserInterface.Commands; namespace HyperbolicDownloaderApi.Commands;
internal class FileCommands public class FileCommands
{ {
private readonly HostsManager hostsManager; private readonly HostsManager hostsManager;
@@ -24,12 +23,12 @@ internal class FileCommands
{ {
if (filesManager.TryAdd(path, out PrivateHyperFileInfo? fileInfo, out string? message)) if (filesManager.TryAdd(path, out PrivateHyperFileInfo? fileInfo, out string? message))
{ {
ConsoleExt.WriteLine($"Added file: {fileInfo!.FilePath}", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine($"Added file: {fileInfo!.FilePath}", NotificationMessageType.Success);
Console.WriteLine($"Hash: {fileInfo.Hash}"); ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
} }
else else
{ {
ConsoleExt.WriteLine(message, ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine(message!, NotificationMessageType.Error);
} }
} }
@@ -39,11 +38,11 @@ internal class FileCommands
if (filesManager.TryRemove(hash)) if (filesManager.TryRemove(hash))
{ {
ConsoleExt.WriteLine($"Successfully removed file!", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine($"Successfully removed file!", NotificationMessageType.Success);
} }
else else
{ {
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
} }
} }
@@ -54,33 +53,33 @@ internal class FileCommands
if (fileInfos.Count == 0) if (fileInfos.Count == 0)
{ {
ConsoleExt.WriteLine("No tracked files!", ConsoleColor.DarkYellow); ApiManager.SendNotificationMessageNewLine("No tracked files!", NotificationMessageType.Warning);
return; return;
} }
foreach (PrivateHyperFileInfo fileInfo in fileInfos) foreach (PrivateHyperFileInfo fileInfo in fileInfos)
{ {
index++; index++;
Console.WriteLine($"{index}) {fileInfo.FilePath}"); ApiManager.SendNotificationMessageNewLine($"{index}) {fileInfo.FilePath}");
Console.WriteLine($"Hash: {fileInfo.Hash}"); ApiManager.SendNotificationMessageNewLine($"Hash: {fileInfo.Hash}");
Console.WriteLine(); ApiManager.SendNotificationMessageNewLine(string.Empty);
} }
Console.CursorTop--; Console.CursorTop--;
} }
public void GenerateFileSingle(string hash) public void GenerateFileSingle(string hash)
{ {
string directoryPath = Path.Combine(Program.BasePath, "GeneratedFiles"); string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
{ {
Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
} }
hash = hash.Trim().ToLower(); hash = hash.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo)) if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{ {
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return; return;
} }
@@ -89,10 +88,10 @@ internal class FileCommands
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash); PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = Program.GetLocalSocket(); NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null) if (localHost is null)
{ {
ConsoleExt.WriteLine("Network error!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Network error!", NotificationMessageType.Error);
return; return;
} }
@@ -103,23 +102,23 @@ internal class FileCommands
File.WriteAllText(filePath, json); File.WriteAllText(filePath, json);
ConsoleExt.WriteLine("Done", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}"); ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
} }
public void GenerateFileFull(string hash) public void GenerateFileFull(string hash)
{ {
string directoryPath = Path.Combine(Program.BasePath, "GeneratedFiles"); string directoryPath = Path.Combine(ApiConfiguration.BasePath, "GeneratedFiles");
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
{ {
Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
} }
hash = hash.Trim().ToLower(); hash = hash.Trim().ToLower();
if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo)) if (!filesManager.TryGet(hash, out PrivateHyperFileInfo? localHyperFileInfo))
{ {
ConsoleExt.WriteLine("The file is not being tracked!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("The file is not being tracked!", NotificationMessageType.Error);
return; return;
} }
@@ -128,10 +127,10 @@ internal class FileCommands
PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash); PublicHyperFileInfo publicHyperFileInfo = new PublicHyperFileInfo(hash);
NetworkSocket? localHost = Program.GetLocalSocket(); NetworkSocket? localHost = ApiManager.GetLocalSocket();
if (localHost is null) if (localHost is null)
{ {
ConsoleExt.WriteLine("Network error!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Network error!", NotificationMessageType.Error);
return; return;
} }
@@ -145,7 +144,7 @@ internal class FileCommands
continue; continue;
} }
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow); ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
Console.CursorLeft = 0; Console.CursorLeft = 0;
@@ -156,7 +155,7 @@ internal class FileCommands
if (!sendTask.IsCompletedSuccessfully) if (!sendTask.IsCompletedSuccessfully)
{ {
Console.CursorLeft = 0; Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
hostsManager.Remove(host); hostsManager.Remove(host);
continue; continue;
@@ -164,13 +163,13 @@ internal class FileCommands
else if (!sendTask.Result) else if (!sendTask.Result)
{ {
host.LastActive = DateTime.Now; host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Does not have the requested file", NotificationMessageType.Error);
continue; continue;
} }
host.LastActive = DateTime.Now; host.LastActive = DateTime.Now;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Has the requested file", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Has the requested file", NotificationMessageType.Success);
publicHyperFileInfo.Hosts.Add(host); publicHyperFileInfo.Hosts.Add(host);
} }
@@ -181,7 +180,7 @@ internal class FileCommands
File.WriteAllText(filePath, json); File.WriteAllText(filePath, json);
ConsoleExt.WriteLine("Done", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine("Done", NotificationMessageType.Success);
Console.WriteLine($"File saved at: {Path.GetFullPath(filePath)}"); ApiManager.SendNotificationMessageNewLine($"File saved at: {Path.GetFullPath(filePath)}");
} }
} }
@@ -0,0 +1,168 @@
using HyperbolicDownloaderApi.Managment;
using HyperbolicDownloaderApi.Networking;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Commands;
public class HostCommands
{
private readonly HostsManager hostsManager;
public HostCommands(HostsManager hostsManager)
{
this.hostsManager = hostsManager;
}
public void Discover(string _)
{
ApiManager.SendNotificationMessageNewLine("Running local discovery routine...", NotificationMessageType.Info);
int hostsCountBefore = hostsManager.Count;
BroadcastClient.Send(ApiConfiguration.BroadcastPort, ApiConfiguration.PrivatePort.ToString());
Thread.Sleep(3000);
ApiManager.SendNotificationMessageNewLine($"Found {hostsManager.Count - hostsCountBefore} host(s)", NotificationMessageType.Info);
}
public void CheckActiveHosts(string _)
{
int activeHostsCount = hostsManager.CheckHostsActivity();
if (activeHostsCount == 0)
{
ApiManager.SendNotificationMessageNewLine("No active hosts found!", NotificationMessageType.Error);
ApiManager.SendNotificationMessageNewLine("Use 'add host xxx.xxx.xxx.xxx:yyyy' to add a new host.", NotificationMessageType.Error);
}
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"{hostsManager.Count} known host(s).", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"{activeHostsCount} active host(s).", NotificationMessageType.Info);
}
public void ListHosts(string _)
{
int index = 0;
List<NetworkSocket> hosts = hostsManager.ToList();
if (hosts.Count == 0)
{
ApiManager.SendNotificationMessageNewLine("No known hosts", NotificationMessageType.Warning);
return;
}
foreach (NetworkSocket host in hosts)
{
index++;
ApiManager.SendNotificationMessageNewLine($"{index}) {host.IPAddress}:{host.Port}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine($"Last active: {host.LastActive}", NotificationMessageType.Info);
ApiManager.SendNotificationMessageNewLine(string.Empty, NotificationMessageType.Info);
}
Console.CursorTop--;
}
public void RemoveHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
_ = int.TryParse(portInput, out int port);
if (port is < 1000 or >= 6000)
{
ApiManager.SendNotificationMessageNewLine("Invalid port number!", NotificationMessageType.Error);
return;
}
if (!IPAddress.TryParse(ipAddressInput, out IPAddress? ipAddress))
{
ApiManager.SendNotificationMessageNewLine("Invalid IP address!", NotificationMessageType.Error);
return;
}
NetworkSocket hostToRemove = new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue);
if (!hostsManager.Contains(hostToRemove))
{
ApiManager.SendNotificationMessageNewLine("Host not in list", NotificationMessageType.Error);
return;
}
hostsManager.Remove(new NetworkSocket(ipAddress.ToString(), port, DateTime.MinValue), true);
ApiManager.SendNotificationMessageNewLine($"Successfully Removed host!", NotificationMessageType.Success);
}
public void AddHost(string args)
{
string[] parts = args.Split(":");
if (parts.Length != 2)
{
ApiManager.SendNotificationMessageNewLine("Invalid format! Use this format: (xxx.xxx.xxx.xxx:yyyy)", NotificationMessageType.Error);
return;
}
string ipAddressInput = parts[0];
string portInput = parts[1];
IPAddress? ipAddress;
_ = int.TryParse(portInput, out int port);
if (port is < 1000 or >= 6000)
{
ApiManager.SendNotificationMessageNewLine("Invalid port number!", NotificationMessageType.Error);
return;
}
else if (!IPAddress.TryParse(ipAddressInput, out ipAddress))
{
try
{
ipAddress = Dns.GetHostAddresses(ipAddressInput).FirstOrDefault();
}
catch (SocketException ex)
{
Debug.WriteLine(ex);
}
}
if (ipAddress is null)
{
ApiManager.SendNotificationMessageNewLine("Invalid IP address!", NotificationMessageType.Error);
return;
}
try
{
ApiManager.SendNotificationMessageNewLine("Waiting for response...", NotificationMessageType.Info);
NetworkSocket? localSocket = ApiManager.GetLocalSocket() ?? new NetworkSocket("0.0.0.0", 0, DateTime.MinValue);
List<NetworkSocket>? recivedHosts = NetworkClient.Send<List<NetworkSocket>>(ipAddress, port, "GetHostsList", localSocket);
if (recivedHosts is not null)
{
ApiManager.SendNotificationMessageNewLine($"Success! Added {recivedHosts.Count} new host(s).", NotificationMessageType.Success);
hostsManager.AddRange(recivedHosts);
}
else
{
ApiManager.SendNotificationMessageNewLine($"Invalid response!", NotificationMessageType.Error);
}
}
catch (SocketException ex)
{
ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error);
}
catch (IOException ex)
{
ApiManager.SendNotificationMessageNewLine($"Invalid host! Error message: {ex.Message}", NotificationMessageType.Error);
}
}
}
@@ -1,6 +1,6 @@
using System.IO.Compression; using System.IO.Compression;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal static class FileCompressor internal static class FileCompressor
{ {
@@ -1,8 +1,8 @@
using System.Security.Cryptography; using System.Security.Cryptography;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal class FileValidator internal static class FileValidator
{ {
public static async Task<string> CalculateHashAsync(string filePath) public static async Task<string> CalculateHashAsync(string filePath)
{ {
@@ -1,8 +1,10 @@
using System.Text.Json; using HyperbolicDownloaderApi.Managment;
namespace HyperbolicDownloader.FileProcessing; using System.Text.Json;
internal class FilesManager namespace HyperbolicDownloaderApi.FileProcessing;
public class FilesManager
{ {
private readonly List<PrivateHyperFileInfo> files = new List<PrivateHyperFileInfo>(); private readonly List<PrivateHyperFileInfo> files = new List<PrivateHyperFileInfo>();
@@ -91,6 +93,6 @@ internal class FilesManager
private void SaveFiles() private void SaveFiles()
{ {
File.WriteAllText(Program.FilesInfoPath, JsonSerializer.Serialize(files)); File.WriteAllText(ApiConfiguration.FilesInfoPath, JsonSerializer.Serialize(files));
} }
} }
@@ -1,6 +1,6 @@
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal class PrivateHyperFileInfo public class PrivateHyperFileInfo
{ {
public string Hash { get; set; } public string Hash { get; set; }
public string FilePath { get; set; } public string FilePath { get; set; }
@@ -1,8 +1,8 @@
using HyperbolicDownloader.Networking; using HyperbolicDownloaderApi.Networking;
namespace HyperbolicDownloader.FileProcessing; namespace HyperbolicDownloaderApi.FileProcessing;
internal class PublicHyperFileInfo public class PublicHyperFileInfo
{ {
public string Hash { get; set; } = string.Empty; public string Hash { get; set; } = string.Empty;
public List<NetworkSocket> Hosts { get; set; } = new(); public List<NetworkSocket> Hosts { get; set; } = new();
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Open.Nat" Version="2.1.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
using System.Reflection;
namespace HyperbolicDownloaderApi.Managment;
public static class ApiConfiguration
{
public const int BroadcastPort = 2155;
public const int PrivatePort = 3055;
public static int PublicPort { get; set; }
public static string BasePath { get; } = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? string.Empty;
public static string HostsFilePath { get; } = Path.Combine(BasePath, "Hosts.json");
public static string FilesInfoPath { get; } = Path.Combine(BasePath, "Files.json");
}
@@ -0,0 +1,218 @@
using HyperbolicDownloaderApi.FileProcessing;
using HyperbolicDownloaderApi.Networking;
using Open.Nat;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace HyperbolicDownloaderApi.Managment;
public class ApiManager
{
public static event EventHandler<NotificationMessageEventArgs>? OnNotificationMessageRecived;
public static IPAddress? PublicIpAddress => device?.GetExternalIPAsync().GetAwaiter().GetResult();
public FilesManager FilesManager { get; } = new();
public HostsManager HostsManager { get; } = new();
private static readonly Random random = new();
private static NatDevice? device;
private static Mapping? portMapping;
private readonly BroadcastClient broadcastClient = new BroadcastClient();
private readonly NetworkClient networkClient;
public ApiManager()
{
networkClient = 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<bool> OpenPorts()
{
try
{
ApiConfiguration.PublicPort = random.Next(1000, 6000);
NatDiscoverer? discoverer = new NatDiscoverer();
device = await 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;
}
}
public static void ClosePorts()
{
SendNotificationMessageNewLine("Closing ports...", NotificationMessageType.Info);
if (device is not null)
{
try
{
IEnumerable<Mapping>? mappings = device.GetAllMappingsAsync().GetAwaiter().GetResult();
foreach (Mapping? mapping in mappings)
{
if (mapping.Description.Contains("HyperbolicDowloader") && mapping.PrivateIP.ToString() == portMapping?.PrivateIP.ToString())
{
device.DeletePortMapAsync(mapping).GetAwaiter().GetResult();
}
}
}
catch (Exception ex)
{
SendNotificationMessageNewLine(ex.ToString(), NotificationMessageType.Error);
}
}
SendNotificationMessageNewLine("Ports closed!", NotificationMessageType.Warning);
Environment.Exit(0);
}
public void StartBroadcastListener()
{
broadcastClient.StartListening(ApiConfiguration.BroadcastPort);
broadcastClient.OnBroadcastRecived += BroadcastClient_OnBroadcastRecived;
}
public bool StartTcpListener()
{
try
{
networkClient.ListenTo<NetworkSocket>("GetHostsList", GetHostList);
networkClient.ListenTo<List<NetworkSocket>>("DiscoverAnswer", DiscoverAnswer);
networkClient.ListenTo<string>("Message", ReciveMessage);
networkClient.ListenTo<string>("HasFile", HasFile);
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;
}
private async void BroadcastClient_OnBroadcastRecived(object? sender, BroadcastRecivedEventArgs recivedEventArgs)
{
IPAddress remoteIpAddress = recivedEventArgs.IPEndPoint.Address;
Debug.WriteLine($"Received broadcast \"{recivedEventArgs.Message}\" from {remoteIpAddress}");
List<NetworkSocket> 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<List<NetworkSocket>> recivedEventArgs)
{
SendNotificationMessageNewLine($"Received answer from {recivedEventArgs.IpAddress}. Returned {recivedEventArgs.Data.Count} host(s).", NotificationMessageType.Info);
HostsManager.AddRange(recivedEventArgs.Data);
}
private async void GetHostList(object? sender, MessageRecivedEventArgs<NetworkSocket> recivedEventArgs)
{
List<NetworkSocket> 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<string> recivedEventArgs)
{
await recivedEventArgs.SendResponseAsync(FilesManager.Contains(recivedEventArgs.Data));
}
private void ReciveMessage(object? sender, MessageRecivedEventArgs<string> recivedEventArgs)
{
SendNotificationMessageNewLine($"Received \"{recivedEventArgs.Data}\" from {recivedEventArgs.IpAddress}.", NotificationMessageType.Info);
}
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));
}
}
@@ -0,0 +1,21 @@
namespace HyperbolicDownloaderApi.Managment;
public class NotificationMessageEventArgs : EventArgs
{
public NotificationMessageType NotificationMessageType { get; }
public string? Message { get; }
public NotificationMessageEventArgs(NotificationMessageType notificationMessageType, string? message)
{
NotificationMessageType = notificationMessageType;
Message = message;
}
}
public enum NotificationMessageType
{
Info,
Success,
Warning,
Error
}
@@ -1,11 +1,11 @@
using Stone_Red_Utilities.ConsoleExtentions; using HyperbolicDownloaderApi.Managment;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastClient internal class BroadcastClient
{ {
@@ -26,7 +26,7 @@ internal class BroadcastClient
if (ip4Address is null) if (ip4Address is null)
{ {
ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Could not find suitable network adapter!", NotificationMessageType.Error);
return; return;
} }
@@ -34,7 +34,7 @@ internal class BroadcastClient
if (addressInformation is null) if (addressInformation is null)
{ {
ConsoleExt.WriteLine("Could not find suitable network adapter!", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine("Could not find suitable network adapter!", NotificationMessageType.Error);
return; return;
} }
@@ -43,12 +43,12 @@ internal class BroadcastClient
byte[] sendbuf = Encoding.ASCII.GetBytes(message); byte[] sendbuf = Encoding.ASCII.GetBytes(message);
IPEndPoint ep = new IPEndPoint(broadcast, port); IPEndPoint ep = new IPEndPoint(broadcast, port);
socket.SendTo(sendbuf, ep); _ = socket.SendTo(sendbuf, ep);
} }
public void StartListening(int port) public void StartListening(int port)
{ {
if (IsListening == true) if (IsListening)
{ {
throw new InvalidOperationException("Already listening!"); throw new InvalidOperationException("Already listening!");
} }
@@ -58,7 +58,7 @@ internal class BroadcastClient
udpListener = new UdpClient(port); udpListener = new UdpClient(port);
IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port); IPEndPoint groupEP = new IPEndPoint(IPAddress.Any, port);
Task.Run(() => _ = Task.Run(() =>
{ {
while (IsListening) while (IsListening)
{ {
@@ -1,6 +1,6 @@
using System.Net; using System.Net;
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class BroadcastRecivedEventArgs : EventArgs internal class BroadcastRecivedEventArgs : EventArgs
{ {
@@ -0,0 +1,13 @@
namespace HyperbolicDownloaderApi.Networking;
internal class DataContainer
{
public string EventName { get; set; }
public string JsonData { get; set; }
public DataContainer(string eventName, string jsonData)
{
JsonData = jsonData;
EventName = eventName;
}
}
@@ -1,13 +1,11 @@
using HyperbolicDownloader.Networking; using HyperbolicDownloaderApi.Managment;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json; using System.Text.Json;
namespace HyperbolicDownloader; namespace HyperbolicDownloaderApi.Networking;
internal class HostsManager public class HostsManager
{ {
private List<NetworkSocket> hosts = new List<NetworkSocket>(); private List<NetworkSocket> hosts = new List<NetworkSocket>();
public int Count => hosts.Count; public int Count => hosts.Count;
@@ -16,10 +14,7 @@ internal class HostsManager
{ {
foreach (NetworkSocket host in hosts) foreach (NetworkSocket host in hosts)
{ {
if (!Contains(host)) Add(host);
{
this.hosts.Add(host);
}
} }
SaveHosts(); SaveHosts();
@@ -38,7 +33,7 @@ internal class HostsManager
{ {
if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove) if (DateTime.Now - host.LastActive >= new TimeSpan(24, 0, 0) || forceRemove)
{ {
hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port); _ = hosts.RemoveAll(x => x.IPAddress == host.IPAddress && x.Port == host.Port);
} }
SaveHosts(); SaveHosts();
} }
@@ -54,16 +49,16 @@ internal class HostsManager
int activeHostsCount = 0; int activeHostsCount = 0;
foreach (NetworkSocket host in hosts) foreach (NetworkSocket host in hosts)
{ {
ConsoleExt.Write($"{host.IPAddress}:{host.Port} > ???", ConsoleColor.DarkYellow); ApiManager.SendNotificationMessage($"{host.IPAddress}:{host.Port} > ???", NotificationMessageType.Warning);
using TcpClient tcpClient = new TcpClient(); using TcpClient tcpClient = new TcpClient();
try try
{ {
tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(1000); _ = tcpClient.ConnectAsync(host.IPAddress, host.Port).Wait(500);
Console.CursorLeft = 0; Console.CursorLeft = 0;
if (tcpClient.Connected) if (tcpClient.Connected)
{ {
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Active", ConsoleColor.Green); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Active", NotificationMessageType.Success);
host.LastActive = DateTime.Now; host.LastActive = DateTime.Now;
activeHostsCount++; activeHostsCount++;
} }
@@ -73,7 +68,7 @@ internal class HostsManager
{ {
hostsToRemove.Add(host); hostsToRemove.Add(host);
} }
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
} }
} }
catch catch
@@ -83,13 +78,13 @@ internal class HostsManager
hostsToRemove.Add(host); hostsToRemove.Add(host);
} }
Console.CursorLeft = 0; Console.CursorLeft = 0;
ConsoleExt.WriteLine($"{host.IPAddress}:{host.Port} > Inactive", ConsoleColor.Red); ApiManager.SendNotificationMessageNewLine($"{host.IPAddress}:{host.Port} > Inactive", NotificationMessageType.Error);
} }
} }
foreach (NetworkSocket host in hostsToRemove) foreach (NetworkSocket host in hostsToRemove)
{ {
hosts.Remove(host); _ = hosts.Remove(host);
} }
SaveHosts(); SaveHosts();
@@ -104,6 +99,6 @@ internal class HostsManager
public void SaveHosts() public void SaveHosts()
{ {
hosts = hosts.OrderByDescending(h => h.LastActive).ToList(); hosts = hosts.OrderByDescending(h => h.LastActive).ToList();
File.WriteAllText(Program.HostsFilePath, JsonSerializer.Serialize(hosts)); File.WriteAllText(ApiConfiguration.HostsFilePath, JsonSerializer.Serialize(hosts));
} }
} }
@@ -0,0 +1,33 @@
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class MessageRecivedEventArgs<T> : EventArgs
{
private readonly NetworkStream networkStream;
public MessageRecivedEventArgs(NetworkStream networkStream, IPAddress ipAddress, T data)
{
this.networkStream = networkStream;
Data = data;
IpAddress = ipAddress;
}
public T Data { get; set; }
public IPAddress IpAddress { get; set; }
public async Task SendResponseAsync(object response)
{
byte[] bytesToSend = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(response));
await networkStream.WriteAsync(bytesToSend);
}
public void SendResponse(object response)
{
SendResponseAsync(response).GetAwaiter().GetResult();
}
}
@@ -0,0 +1,205 @@
using HyperbolicDownloaderApi.FileProcessing;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
namespace HyperbolicDownloaderApi.Networking;
internal class NetworkClient
{
public bool IsListening { get; private set; } = false;
private TcpListener? tcpListener;
private readonly FilesManager filesManager;
private readonly Dictionary<string, (Type type, Delegate method)> events = new();
public NetworkClient(FilesManager filesManager)
{
this.filesManager = filesManager;
}
public static async Task<T?> SendAsync<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
byte[] bytesToRead = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(bytesToRead.AsMemory(0, client.ReceiveBufferSize));
string response = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
client.Close();
if (string.IsNullOrWhiteSpace(response))
{
return default;
}
else
{
return JsonSerializer.Deserialize<T>(response);
}
}
public static async Task SendAsync(IPAddress remoteIp, int remotePort, string eventName, object data)
{
if (remoteIp is null)
{
throw new ArgumentNullException(nameof(remoteIp));
}
TcpClient client = new TcpClient();
await client.ConnectAsync(remoteIp, remotePort);
NetworkStream nwStream = client.GetStream();
string stringData = JsonSerializer.Serialize(new DataContainer(eventName, JsonSerializer.Serialize(data)));
byte[] bytesToSend = Encoding.ASCII.GetBytes(stringData);
await nwStream.WriteAsync(bytesToSend);
client.Close();
}
public static T? Send<T>(IPAddress remoteIp, int remotePort, string eventName, object data)
{
return SendAsync<T>(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public static void Send(IPAddress remoteIp, int remotePort, string eventName, object data)
{
SendAsync(remoteIp, remotePort, eventName, data).GetAwaiter().GetResult();
}
public void StartListening(int port)
{
if (IsListening)
{
throw new InvalidOperationException("Already listening!");
}
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
IsListening = true;
_ = Task.Run(async () =>
{
while (IsListening)
{
try
{
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream nwStream = client.GetStream();
byte[] buffer = new byte[client.ReceiveBufferSize];
int bytesRead = await nwStream.ReadAsync(buffer.AsMemory(0, client.ReceiveBufferSize));
string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
if (dataReceived.StartsWith("Download"))
{
_ = Upload(client, dataReceived[8..]);
continue;
}
if (string.IsNullOrWhiteSpace(dataReceived))
{
client.Close();
continue;
}
DataContainer? dataContainer = JsonSerializer.Deserialize<DataContainer>(dataReceived);
if (dataContainer is not null && events.ContainsKey(dataContainer.EventName))
{
(Type type, Delegate method) = events[dataContainer.EventName];
Type eventArgsType = typeof(MessageRecivedEventArgs<>).MakeGenericType(type);
object? eventArgs = Activator.CreateInstance(
eventArgsType,
nwStream,
(client.Client.RemoteEndPoint as IPEndPoint)?.Address,
JsonSerializer.Deserialize(dataContainer.JsonData, type));
_ = (method?.DynamicInvoke(this, eventArgs));
}
client.Close();
}
catch (SocketException ex)
{
if (ex.SocketErrorCode != SocketError.Interrupted)
{
throw;
}
}
}
tcpListener.Stop();
});
}
private async Task Upload(TcpClient client, string hash)
{
try
{
byte[] bytesToSend;
hash = hash.Trim();
NetworkStream nwStream = client.GetStream();
client.SendBufferSize = 64000;
if (filesManager.TryGet(hash, out PrivateHyperFileInfo? hyperFileInfo) && File.Exists(hyperFileInfo?.FilePath))
{
FileInfo fileInfo = new FileInfo(hyperFileInfo.FilePath);
bytesToSend = Encoding.ASCII.GetBytes($"{fileInfo.Length}/{Path.GetFileName(hyperFileInfo.FilePath)}");
Array.Resize(ref bytesToSend, 1000);
await nwStream.WriteAsync(bytesToSend);
foreach (byte[]? chunk in FileCompressor.ReadChunks(hyperFileInfo.FilePath, 64000).Where(chunk => chunk is not null))
{
await nwStream.WriteAsync(chunk);
}
}
else
{
bytesToSend = Encoding.ASCII.GetBytes("File not found!");
await nwStream.WriteAsync(bytesToSend);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
client.Close();
}
}
public void StopListening()
{
tcpListener?.Stop();
IsListening = false;
}
public void ListenTo<T>(string eventName, EventHandler<MessageRecivedEventArgs<T>> eventHandler)
{
events.Add(eventName, (typeof(T), eventHandler));
}
}
@@ -1,6 +1,6 @@
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal class NetworkSocket public class NetworkSocket
{ {
public string IPAddress { get; set; } public string IPAddress { get; set; }
public int Port { get; set; } public int Port { get; set; }
@@ -2,9 +2,9 @@
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
namespace HyperbolicDownloader.Networking; namespace HyperbolicDownloaderApi.Networking;
internal static class NetworkUtilities public static class NetworkUtilities
{ {
public static UnicastIPAddressInformation? GetUnicastIPAddressInformation(IPAddress address) public static UnicastIPAddressInformation? GetUnicastIPAddressInformation(IPAddress address)
{ {
+5 -12
View File
@@ -25,7 +25,6 @@ You can generate `.hyper` files with your client using the [generate](https://gi
These files contain the hash value of the actual file and the hosts that should have the requested file. These files contain the hash value of the actual file and the hosts that should have the requested file.
You can use the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve the file or if you are using Windows you can right-click the `.hyper` file, select `open with` and select the HyperbolicDownloader executable. You can use the [get from](https://github.com/Stone-Red-Code/HyperbolicDownloader#getfrom) command to retrieve the file or if you are using Windows you can right-click the `.hyper` file, select `open with` and select the HyperbolicDownloader executable.
## Disclaimer ## Disclaimer
Keep in mind that all network traffic is not encrypted. So, do not send sensitive information with HyperbolicDownloader. Keep in mind that all network traffic is not encrypted. So, do not send sensitive information with HyperbolicDownloader.
@@ -41,31 +40,26 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Exits the application.\ **Description:** Exits the application.\
**Parameter:** `none` **Parameter:** `none`
### `clear` | `cls` ### `clear` | `cls`
**Description:** Clears the console.\ **Description:** Clears the console.\
**Parameter:** `none` **Parameter:** `none`
### `info` | `inf` ### `info` | `inf`
**Description:** Displays the private and public IP address.\ **Description:** Displays the private and public IP address.\
**Parameter:** `none` **Parameter:** `none`
### `discover` | `disc` ### `discover` | `disc`
**Description:** Tries to find other active hosts on the local network.\ **Description:** Tries to find other active hosts on the local network.\
**parameter:** `none` **parameter:** `none`
### `check` | `status` ### `check` | `status`
**Description:** Checks the status of known hosts.\ **Description:** Checks the status of known hosts.\
**parameter:** `none` **parameter:** `none`
### `list` | `ls` ### `list` | `ls`
**Description:** Lists all files\ **Description:** Lists all files\
@@ -73,15 +67,14 @@ Since none of the files are stored anywhere centralized, the quality of the file
#### `<list> files` #### `<list> files`
**Description:** Lists all files\ **Description:** Lists all files.\
**parameter:** `none` **parameter:** `none`
#### `<list> hosts` #### `<list> hosts`
**description:** lists all hosts **description:** Lists all hosts.\
**parameter:** `none` **parameter:** `none`
### `add` ### `add`
**Description:** Adds a file to the tracking list.\ **Description:** Adds a file to the tracking list.\
@@ -97,8 +90,7 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Adds a host to the list of known hosts.\ **Description:** Adds a host to the list of known hosts.\
**parameter:** `<IpAddress:port>` **parameter:** `<IpAddress:port>`
### `remove` | `rm`
### `remove` `rm`
**Description:** Removes a file from the tracking list.\ **Description:** Removes a file from the tracking list.\
**parameter:** `<FileHash>` **parameter:** `<FileHash>`
@@ -113,19 +105,20 @@ Since none of the files are stored anywhere centralized, the quality of the file
**Description:** Removes a host from the list of known hosts.\ **Description:** Removes a host from the list of known hosts.\
**parameter:** `<IpAddress:Port>` **parameter:** `<IpAddress:Port>`
### `get` ### `get`
**Description:** Attempts to retrieve a file from another host using a hash.\ **Description:** Attempts to retrieve a file from another host using a hash.\
**parameter:** `<FileHash>` **parameter:** `<FileHash>`
<a name="#getfrom"></a> <a name="#getfrom"></a>
#### `<get> from` #### `<get> from`
**Description:** Attempts to retrieve a file from another host using a .hyper file.\ **Description:** Attempts to retrieve a file from another host using a .hyper file.\
**Parameter:** `<FilePathToHyperFile>` **Parameter:** `<FilePathToHyperFile>`
<a name="#generate"></a> <a name="#generate"></a>
### `generate` | `gen` ### `generate` | `gen`
**Description:** Generates a .hyper file from a file hash.\ **Description:** Generates a .hyper file from a file hash.\