diff --git a/EonaCat.Network.Tester/EonaCat.Network.Tester.csproj b/EonaCat.Network.Tester/EonaCat.Network.Tester.csproj
new file mode 100644
index 0000000..3b0107d
--- /dev/null
+++ b/EonaCat.Network.Tester/EonaCat.Network.Tester.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/EonaCat.Network.Tester/Program.cs b/EonaCat.Network.Tester/Program.cs
new file mode 100644
index 0000000..ed78016
--- /dev/null
+++ b/EonaCat.Network.Tester/Program.cs
@@ -0,0 +1,130 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using EonaCat.Network;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+internal class Program
+{
+ private static WebSocket _client;
+ private static WSServer _server;
+
+ private static async Task Main(string[] args)
+ {
+ // Menu loop
+ while (true)
+ {
+ Console.WriteLine("0. Create a HTTPS certificate");
+ Console.WriteLine("1. Start the server and client");
+ Console.WriteLine("2. Send Message");
+ Console.WriteLine("3. Quit");
+
+ var choice = Console.ReadLine();
+
+ switch (choice)
+ {
+ case "0":
+ CreateCertificate();
+ break;
+
+ case "1":
+ await CreateServerAndClientAsync();
+ break;
+
+ case "2":
+ if (_client != null)
+ {
+ Console.Write("Enter message: ");
+ var message = Console.ReadLine();
+ _client.Send(message);
+ }
+ break;
+
+ case "3":
+ _client?.Close();
+ return;
+
+ default:
+ Console.WriteLine("Invalid choice. Try again.");
+ break;
+ }
+ }
+ }
+
+ private static async Task CreateServerAndClientAsync()
+ {
+ var serverUri = "wss://localhost:8443";
+ var clientUri = "wss://localhost:8443/Welcome";
+ var clientName = "TestClient";
+ var certificatePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "localhost.pfx");
+ var certificatePassword = "";
+ var requiredPassword = "";
+
+ // Start the server
+ StartServer(serverUri, certificatePath, certificatePassword, requiredPassword);
+
+ // Start the client in the main thread
+ _client = new WebSocket(clientUri);
+ _client.SslConfiguration.Certificates.Add(new X509Certificate(certificatePath, certificatePassword));
+ _client.OnConnect += (sender, e) => Console.WriteLine($"Connected to server");
+ _client.OnMessageReceived += (sender, e) => Console.WriteLine($"Received message from server: {e.Data}");
+ _client.OnDisconnect += (sender, e) => Console.WriteLine($"Disconnected from server: {e.Code} : {e.Reason}");
+ _client.OnError += (sender, e) => Console.WriteLine($"Error: {sender}\n{e}");
+
+ _client.ConnectAsync();
+
+ Console.WriteLine("Connected to the server.");
+ }
+
+ private static void CreateCertificate()
+ {
+ Console.Write("Enter hostname: (default: localhost) ");
+ string hostname = Console.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(hostname))
+ {
+ hostname = "localhost";
+ }
+
+ int days = 30;
+ Console.Write("Enter days until expiration: (default: 30 days) ");
+ if (int.TryParse(Console.ReadLine(), out int givenDays))
+ {
+ days = givenDays;
+ }
+
+ Console.Write("Enter password, enter to skip: ");
+ string password = Console.ReadLine();
+
+ RSA rsa = RSA.Create();
+
+ // Create a certificate request with the specified subject and key pair
+ CertificateRequest request = new CertificateRequest(
+ $"CN={hostname}",
+ rsa,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+
+ // Create a self-signed certificate from the certificate request
+ X509Certificate2 certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(days));
+
+ // Export the certificate to a file with password
+ byte[] certBytes = string.IsNullOrEmpty(password)
+ ? certificate.Export(X509ContentType.Pfx)
+ : certificate.Export(X509ContentType.Pfx, password);
+ File.WriteAllBytes($"{hostname}.pfx", certBytes);
+
+ Console.WriteLine($"Certificate for {hostname} created successfully and will expire on {certificate.NotAfter}.");
+ Console.WriteLine($"Path: {Path.Combine(AppContext.BaseDirectory, hostname)}.pfx");
+ }
+
+ private static void StartServer(string serverUri, string certificatePath, string certificatePassword, string requiredPassword)
+ {
+ _server = new WSServer(serverUri);
+ _server.SslConfiguration.Certificate = new X509Certificate2(certificatePath, certificatePassword);
+ _server.AddEndpoint("/Welcome");
+ _server.Start();
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network.sln b/EonaCat.Network.sln
index b6b6029..3b815a2 100644
--- a/EonaCat.Network.sln
+++ b/EonaCat.Network.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EonaCat.Network", "EonaCat.Network\EonaCat.Network.csproj", "{11B9181D-7186-4D81-A5D3-4804E9A61BA6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EonaCat.Network.Tester", "EonaCat.Network.Tester\EonaCat.Network.Tester.csproj", "{5CC25A51-3832-44CE-9940-AB9A26F21FFD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,10 +17,10 @@ Global
{11B9181D-7186-4D81-A5D3-4804E9A61BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11B9181D-7186-4D81-A5D3-4804E9A61BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11B9181D-7186-4D81-A5D3-4804E9A61BA6}.Release|Any CPU.Build.0 = Release|Any CPU
- {14643574-C40B-4268-A3EA-15C132B56EDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {14643574-C40B-4268-A3EA-15C132B56EDB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {14643574-C40B-4268-A3EA-15C132B56EDB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {14643574-C40B-4268-A3EA-15C132B56EDB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CC25A51-3832-44CE-9940-AB9A26F21FFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CC25A51-3832-44CE-9940-AB9A26F21FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CC25A51-3832-44CE-9940-AB9A26F21FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CC25A51-3832-44CE-9940-AB9A26F21FFD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/EonaCat.Network/Constants.cs b/EonaCat.Network/Constants.cs
new file mode 100644
index 0000000..ae9cd07
--- /dev/null
+++ b/EonaCat.Network/Constants.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ internal class Constants
+ {
+ public static string Version { get; set; } = "1.1.2";
+ }
+}
diff --git a/EonaCat.Network/EonaCat.Network.csproj b/EonaCat.Network/EonaCat.Network.csproj
index 00ea79e..240d545 100644
--- a/EonaCat.Network/EonaCat.Network.csproj
+++ b/EonaCat.Network/EonaCat.Network.csproj
@@ -16,9 +16,9 @@
EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server
EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver
- 1.1.1
- 1.1.0.1
- 1.1.0.1
+ 1.1.2
+ 1.1.2
+ 1.1.2
icon.png
@@ -50,6 +50,7 @@
+
diff --git a/EonaCat.Network/NetworkHelper.cs b/EonaCat.Network/Helpers/NetworkHelper.cs
similarity index 99%
rename from EonaCat.Network/NetworkHelper.cs
rename to EonaCat.Network/Helpers/NetworkHelper.cs
index ddd847b..38d651a 100644
--- a/EonaCat.Network/NetworkHelper.cs
+++ b/EonaCat.Network/Helpers/NetworkHelper.cs
@@ -1,12 +1,12 @@
-using System;
-using System.Net.Sockets;
-using System.Text;
-using EonaCat.LogSystem;
+using EonaCat.LogSystem;
using EonaCat.Quic;
using EonaCat.Quic.Connections;
using EonaCat.Quic.Events;
using EonaCat.Quic.Helpers;
using EonaCat.Quic.Streams;
+using System;
+using System.Net.Sockets;
+using System.Text;
namespace EonaCat.Network
{
diff --git a/EonaCat.Network/System/BlockChain/Block.cs b/EonaCat.Network/System/BlockChain/Block.cs
new file mode 100644
index 0000000..dc93b1d
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/Block.cs
@@ -0,0 +1,54 @@
+using EonaCat.Json;
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace EonaCat.BlockChain
+{
+ public class Block
+ {
+ public int Index { get; set; }
+ private DateTime TimeStamp { get; set; }
+ public string PreviousHash { get; set; }
+ public string Hash { get; set; }
+ public IList Transactions { get; set; }
+ private int Nonce { get; set; }
+
+ public Block(DateTime timeStamp, string previousHash, IList transactions)
+ {
+ Index = 0;
+ TimeStamp = timeStamp;
+ PreviousHash = previousHash;
+ Transactions = transactions;
+ }
+
+ ///
+ /// Calculate the hash of the block
+ ///
+ ///
+ public string CalculateHash()
+ {
+ var sha256 = SHA256.Create();
+
+ var inputBytes = Encoding.ASCII.GetBytes($"{TimeStamp}-{PreviousHash ?? ""}-{JsonHelper.ToJson(Transactions)}-{Nonce}");
+ var outputBytes = sha256.ComputeHash(inputBytes);
+
+ return Convert.ToBase64String(outputBytes);
+ }
+
+ ///
+ /// Mine the block
+ ///
+ ///
+ public void Mine(int difficulty)
+ {
+ var leadingZeros = new string('0', difficulty);
+ while (Hash == null || Hash.Substring(0, difficulty) != leadingZeros)
+ {
+ Nonce++;
+ Hash = CalculateHash();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/BlockChain/BlockChain.cs b/EonaCat.Network/System/BlockChain/BlockChain.cs
new file mode 100644
index 0000000..e1c3779
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/BlockChain.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.BlockChain
+{
+ public class BlockChain
+ {
+ public IList PendingTransactions = new List();
+ public IList Chain { set; get; }
+ private int Difficulty { set; get; } = 2;
+ private readonly int _reward = 1; //1 cryptocurrency
+
+ public void InitializeChain()
+ {
+ Chain = new List();
+ AddGenesisBlock();
+ }
+
+ private Block CreateGenesisBlock()
+ {
+ Block block = new Block(DateTime.Now, null, PendingTransactions);
+ block.Mine(Difficulty);
+ PendingTransactions = new List();
+ return block;
+ }
+
+ private void AddGenesisBlock()
+ {
+ Chain.Add(CreateGenesisBlock());
+ }
+
+ private Block GetLatestBlock()
+ {
+ return Chain[Chain.Count - 1];
+ }
+
+ public void CreateTransaction(Transaction transaction)
+ {
+ PendingTransactions.Add(transaction);
+ }
+
+ public void ProcessPendingTransactions(string minerAddress)
+ {
+ Block block = new Block(DateTime.Now, GetLatestBlock().Hash, PendingTransactions);
+ AddBlock(block);
+
+ PendingTransactions = new List();
+ CreateTransaction(new Transaction(null, minerAddress, _reward));
+ }
+
+ private void AddBlock(Block block)
+ {
+ Block latestBlock = GetLatestBlock();
+ block.Index = latestBlock.Index + 1;
+ block.PreviousHash = latestBlock.Hash;
+ //block.Hash = block.CalculateHash();
+ block.Mine(Difficulty);
+ Chain.Add(block);
+ }
+
+ public bool IsValid()
+ {
+ for (int i = 1; i < Chain.Count; i++)
+ {
+ Block currentBlock = Chain[i];
+ Block previousBlock = Chain[i - 1];
+
+ if (currentBlock.Hash != currentBlock.CalculateHash())
+ {
+ return false;
+ }
+
+ if (currentBlock.PreviousHash != previousBlock.Hash)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public int GetBalance(string address)
+ {
+ int balance = 0;
+
+ for (int i = 0; i < Chain.Count; i++)
+ {
+ for (int j = 0; j < Chain[i].Transactions.Count; j++)
+ {
+ var transaction = Chain[i].Transactions[j];
+
+ if (transaction.FromAddress == address)
+ {
+ balance -= transaction.Amount;
+ }
+
+ if (transaction.ToAddress == address)
+ {
+ balance += transaction.Amount;
+ }
+ }
+ }
+
+ return balance;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/BlockChain/ClientServerExample.cs b/EonaCat.Network/System/BlockChain/ClientServerExample.cs
new file mode 100644
index 0000000..1dacfad
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/ClientServerExample.cs
@@ -0,0 +1,75 @@
+using EonaCat.Json;
+using System;
+
+namespace EonaCat.BlockChain
+{
+ public static class ClientServerExample
+ {
+ public static int ServerPort;
+ private static P2PServer _server;
+ private static readonly P2PClient Client = new P2PClient();
+ public static BlockChain BlockChain = new BlockChain();
+ private static string _clientUsername = "Unknown";
+
+ public static void Run(int serverPort, string clientUsername)
+ {
+ BlockChain.InitializeChain();
+
+ ServerPort = serverPort;
+ _clientUsername = clientUsername;
+
+ if (ServerPort > 0)
+ {
+ _server = new P2PServer();
+ _server.Start();
+ }
+
+ if (_clientUsername != "Unknown")
+ {
+ Console.WriteLine($"Current user is {_clientUsername}");
+ }
+
+ Console.WriteLine("=========================");
+ Console.WriteLine("EonaCat BlockChain");
+ Console.WriteLine("1. Connect to a server");
+ Console.WriteLine("2. Add a transaction");
+ Console.WriteLine("3. Display BlockChain");
+ Console.WriteLine("4. Exit");
+ Console.WriteLine("=========================");
+
+ int selection = 0;
+ while (selection != 4)
+ {
+ switch (selection)
+ {
+ case 1:
+ Console.WriteLine("Please enter the server URL");
+ string serverUrl = Console.ReadLine();
+ Client.Connect($"{serverUrl}/BlockChain");
+ break;
+
+ case 2:
+ Console.WriteLine("Please enter the receiver name");
+ string receiverName = Console.ReadLine();
+ Console.WriteLine("Please enter the amount");
+ string amount = Console.ReadLine();
+ BlockChain.CreateTransaction(new Transaction(_clientUsername, receiverName, int.Parse(amount)));
+ BlockChain.ProcessPendingTransactions(_clientUsername);
+ Client.Broadcast(JsonHelper.ToJson(BlockChain));
+ break;
+
+ case 3:
+ Console.WriteLine("BlockChain");
+ Console.WriteLine(JsonHelper.ToJson(BlockChain, Formatting.Indented));
+ break;
+ }
+
+ Console.WriteLine("Please select an action");
+ string action = Console.ReadLine();
+ selection = int.Parse(action);
+ }
+
+ Client.Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/BlockChain/P2PClient.cs b/EonaCat.Network/System/BlockChain/P2PClient.cs
new file mode 100644
index 0000000..4466da3
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/P2PClient.cs
@@ -0,0 +1,83 @@
+using EonaCat.Json;
+using EonaCat.Network;
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.BlockChain
+{
+ public class P2PClient
+ {
+ private readonly IDictionary wsDict = new Dictionary();
+
+ public void Connect(string url)
+ {
+ if (!wsDict.ContainsKey(url))
+ {
+ WebSocket webSocket = new WebSocket(url);
+ webSocket.OnMessageReceived += (sender, e) =>
+ {
+ if (e.Data == "Hi Client")
+ {
+ Console.WriteLine(e.Data);
+ }
+ else
+ {
+ var newChain = JsonHelper.ToObject(e.Data);
+ if (!newChain.IsValid() || newChain.Chain.Count <= ClientServerExample.BlockChain.Chain.Count)
+ {
+ return;
+ }
+
+ var newTransactions = new List();
+ newTransactions.AddRange(newChain.PendingTransactions);
+ newTransactions.AddRange(ClientServerExample.BlockChain.PendingTransactions);
+
+ newChain.PendingTransactions = newTransactions;
+ ClientServerExample.BlockChain = newChain;
+ }
+ };
+ webSocket.Connect();
+ webSocket.Send("Hi Server");
+ webSocket.Send(JsonHelper.ToJson(ClientServerExample.BlockChain));
+ wsDict.Add(url, webSocket);
+ }
+ }
+
+ public void Send(string url, string data)
+ {
+ foreach (var item in wsDict)
+ {
+ if (item.Key == url)
+ {
+ item.Value.Send(data);
+ }
+ }
+ }
+
+ public void Broadcast(string data)
+ {
+ foreach (var item in wsDict)
+ {
+ item.Value.Send(data);
+ }
+ }
+
+ public IList GetServers()
+ {
+ IList servers = new List();
+ foreach (var item in wsDict)
+ {
+ servers.Add(item.Key);
+ }
+ return servers;
+ }
+
+ public void Close()
+ {
+ foreach (var item in wsDict)
+ {
+ item.Value.Close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/BlockChain/P2PServer.cs b/EonaCat.Network/System/BlockChain/P2PServer.cs
new file mode 100644
index 0000000..824e79d
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/P2PServer.cs
@@ -0,0 +1,51 @@
+using EonaCat.Json;
+using EonaCat.Network;
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.BlockChain
+{
+ public class P2PServer : WSEndpoint
+ {
+ private bool _chainSynched;
+ private WSServer _wss;
+
+ public void Start(string webSocketAddress = null)
+ {
+ webSocketAddress ??= $"ws://127.0.0.1:{ClientServerExample.ServerPort}";
+ _wss = new WSServer(webSocketAddress);
+ _wss.AddEndpoint("/BlockChain");
+ _wss.Start();
+ Console.WriteLine($"Started server at {webSocketAddress}");
+ }
+
+ protected override void OnMessage(MessageEventArgs e)
+ {
+ if (e.Data == "Hi Server")
+ {
+ Console.WriteLine(e.Data);
+ Send("Hi Client");
+ }
+ else
+ {
+ BlockChain newChain = JsonHelper.ToObject(e.Data);
+
+ if (newChain.IsValid() && newChain.Chain.Count > ClientServerExample.BlockChain.Chain.Count)
+ {
+ List newTransactions = new List();
+ newTransactions.AddRange(newChain.PendingTransactions);
+ newTransactions.AddRange(ClientServerExample.BlockChain.PendingTransactions);
+
+ newChain.PendingTransactions = newTransactions;
+ ClientServerExample.BlockChain = newChain;
+ }
+
+ if (!_chainSynched)
+ {
+ Send(JsonHelper.ToJson(ClientServerExample.BlockChain));
+ _chainSynched = true;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/BlockChain/Transaction.cs b/EonaCat.Network/System/BlockChain/Transaction.cs
new file mode 100644
index 0000000..a34fa96
--- /dev/null
+++ b/EonaCat.Network/System/BlockChain/Transaction.cs
@@ -0,0 +1,16 @@
+namespace EonaCat.BlockChain
+{
+ public class Transaction
+ {
+ public string FromAddress { get; set; }
+ public string ToAddress { get; set; }
+ public int Amount { get; set; }
+
+ public Transaction(string fromAddress, string toAddress, int amount)
+ {
+ FromAddress = fromAddress;
+ ToAddress = toAddress;
+ Amount = amount;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Quic/Connections/ConnectionPool.cs b/EonaCat.Network/System/Quic/Connections/ConnectionPool.cs
index ae1417f..1cf9caf 100644
--- a/EonaCat.Network/System/Quic/Connections/ConnectionPool.cs
+++ b/EonaCat.Network/System/Quic/Connections/ConnectionPool.cs
@@ -1,8 +1,8 @@
-using System;
-using System.Collections.Generic;
-using EonaCat.Quic.Infrastructure;
+using EonaCat.Quic.Infrastructure;
using EonaCat.Quic.Infrastructure.Settings;
using EonaCat.Quic.InternalInfrastructure;
+using System;
+using System.Collections.Generic;
namespace EonaCat.Quic.Connections
{
@@ -19,11 +19,11 @@ namespace EonaCat.Quic.Connections
/// Starting point for connection identifiers.
/// ConnectionId's are incremented sequentially by 1.
///
- private static NumberSpace _ns = new NumberSpace(QuicSettings.MaximumConnectionIds);
+ private static readonly NumberSpace _ns = new NumberSpace(QuicSettings.MaximumConnectionIds);
- private static Dictionary _pool = new Dictionary();
+ private static readonly Dictionary _pool = new Dictionary();
- private static List _draining = new List();
+ private static readonly List _draining = new List();
///
/// Adds a connection to the connection pool.
@@ -32,15 +32,19 @@ namespace EonaCat.Quic.Connections
///
/// Connection Id
///
- public static bool AddConnection(ConnectionData connection, out UInt64 availableConnectionId)
+ public static bool AddConnection(ConnectionData connection, out ulong availableConnectionId)
{
availableConnectionId = 0;
if (_pool.ContainsKey(connection.ConnectionId.Value))
+ {
return false;
+ }
if (_pool.Count > QuicSettings.MaximumConnectionIds)
+ {
return false;
+ }
availableConnectionId = _ns.Get();
@@ -50,16 +54,20 @@ namespace EonaCat.Quic.Connections
return true;
}
- public static void RemoveConnection(UInt64 id)
+ public static void RemoveConnection(ulong id)
{
if (_pool.ContainsKey(id))
+ {
_pool.Remove(id);
+ }
}
- public static QuicConnection Find(UInt64 id)
+ public static QuicConnection Find(ulong id)
{
if (_pool.ContainsKey(id) == false)
+ {
return null;
+ }
return _pool[id];
}
diff --git a/EonaCat.Network/System/Quic/Connections/QuicConnection.cs b/EonaCat.Network/System/Quic/Connections/QuicConnection.cs
index fb5899f..4c27f9e 100644
--- a/EonaCat.Network/System/Quic/Connections/QuicConnection.cs
+++ b/EonaCat.Network/System/Quic/Connections/QuicConnection.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using EonaCat.Quic.Constants;
+using EonaCat.Quic.Constants;
using EonaCat.Quic.Events;
using EonaCat.Quic.Exceptions;
using EonaCat.Quic.Helpers;
@@ -11,6 +9,8 @@ using EonaCat.Quic.Infrastructure.Packets;
using EonaCat.Quic.Infrastructure.Settings;
using EonaCat.Quic.InternalInfrastructure;
using EonaCat.Quic.Streams;
+using System;
+using System.Collections.Generic;
namespace EonaCat.Quic.Connections
{
@@ -22,17 +22,17 @@ namespace EonaCat.Quic.Connections
private readonly NumberSpace _numberSpace = new NumberSpace();
private readonly PacketWireTransfer _pwt;
- private UInt64 _currentTransferRate;
+ private ulong _currentTransferRate;
private ConnectionState _state;
private string _lastError;
- private Dictionary _streams;
+ private readonly Dictionary _streams;
public IntegerParts ConnectionId { get; private set; }
public IntegerParts PeerConnectionId { get; private set; }
public PacketCreator PacketCreator { get; private set; }
- public UInt64 MaxData { get; private set; }
- public UInt64 MaxStreams { get; private set; }
+ public ulong MaxData { get; private set; }
+ public ulong MaxStreams { get; private set; }
public StreamOpenedEvent OnStreamOpened { get; set; }
public ConnectionClosedEvent OnConnectionClosed { get; set; }
@@ -44,11 +44,13 @@ namespace EonaCat.Quic.Connections
/// A new stream instance or Null if the connection is terminated.
public QuicStream CreateStream(StreamType type)
{
- UInt32 streamId = _numberSpace.Get();
+ uint streamId = _numberSpace.Get();
if (_state != ConnectionState.Open)
+ {
return null;
+ }
- QuicStream stream = new QuicStream(this, new EonaCat.Quic.Helpers.StreamId(streamId, type));
+ QuicStream stream = new QuicStream(this, new StreamId(streamId, type));
_streams.Add(streamId, stream);
return stream;
@@ -61,21 +63,44 @@ namespace EonaCat.Quic.Connections
foreach (Frame frame in frames)
{
if (frame.Type == 0x01)
+ {
OnRstStreamFrame(frame);
+ }
+
if (frame.Type == 0x04)
+ {
OnRstStreamFrame(frame);
+ }
+
if (frame.Type >= 0x08 && frame.Type <= 0x0f)
+ {
stream = OnStreamFrame(frame);
+ }
+
if (frame.Type == 0x10)
+ {
OnMaxDataFrame(frame);
+ }
+
if (frame.Type == 0x11)
+ {
OnMaxStreamDataFrame(frame);
+ }
+
if (frame.Type >= 0x12 && frame.Type <= 0x13)
+ {
OnMaxStreamFrame(frame);
+ }
+
if (frame.Type == 0x14)
+ {
OnDataBlockedFrame(frame);
+ }
+
if (frame.Type >= 0x1c && frame.Type <= 0x1d)
+ {
OnConnectionCloseFrame(frame);
+ }
}
return stream;
@@ -83,13 +108,15 @@ namespace EonaCat.Quic.Connections
public void IncrementRate(int length)
{
- _currentTransferRate += (UInt32)length;
+ _currentTransferRate += (uint)length;
}
public bool MaximumReached()
{
if (_currentTransferRate >= MaxData)
+ {
return true;
+ }
return false;
}
@@ -128,10 +155,14 @@ namespace EonaCat.Quic.Connections
{
stream = new QuicStream(this, streamId);
- if ((UInt64)_streams.Count < MaxStreams)
+ if ((ulong)_streams.Count < MaxStreams)
+ {
_streams.Add(streamId.Id, stream);
+ }
else
+ {
SendMaximumStreamReachedError();
+ }
OnStreamOpened?.Invoke(stream);
}
@@ -149,7 +180,9 @@ namespace EonaCat.Quic.Connections
{
MaxDataFrame sf = (MaxDataFrame)frame;
if (sf.MaximumData.Value > MaxData)
+ {
MaxData = sf.MaximumData.Value;
+ }
}
private void OnMaxStreamDataFrame(Frame frame)
@@ -168,7 +201,9 @@ namespace EonaCat.Quic.Connections
{
MaxStreamsFrame msf = (MaxStreamsFrame)frame;
if (msf.MaximumStreams > MaxStreams)
+ {
MaxStreams = msf.MaximumStreams.Value;
+ }
}
private void OnDataBlockedFrame(Frame frame)
@@ -182,7 +217,10 @@ namespace EonaCat.Quic.Connections
StreamId streamId = sdbf.StreamId;
if (_streams.ContainsKey(streamId.Id) == false)
+ {
return;
+ }
+
QuicStream stream = _streams[streamId.Id];
stream.ProcessStreamDataBlocked(sdbf);
@@ -196,7 +234,7 @@ namespace EonaCat.Quic.Connections
_currentTransferRate = 0;
_state = ConnectionState.Open;
_lastError = string.Empty;
- _streams = new Dictionary();
+ _streams = new Dictionary();
_pwt = connection.PWT;
ConnectionId = connection.ConnectionId;
@@ -240,7 +278,9 @@ namespace EonaCat.Quic.Connections
if (_state == ConnectionState.Draining)
{
if (string.IsNullOrWhiteSpace(_lastError))
+ {
_lastError = "Protocol error";
+ }
TerminateConnection();
@@ -290,7 +330,9 @@ namespace EonaCat.Quic.Connections
// Ignore empty packets
if (data == null || data.Length <= 0)
+ {
return true;
+ }
bool result = _pwt.SendPacket(packet);
diff --git a/EonaCat.Network/System/Quic/Context/QuicStreamContext.cs b/EonaCat.Network/System/Quic/Context/QuicStreamContext.cs
index 77182b6..19316ec 100644
--- a/EonaCat.Network/System/Quic/Context/QuicStreamContext.cs
+++ b/EonaCat.Network/System/Quic/Context/QuicStreamContext.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Streams;
+using EonaCat.Quic.Streams;
+using System;
namespace EonaCat.Quic.Context
{
@@ -24,7 +24,7 @@ namespace EonaCat.Quic.Context
///
/// Unique stream identifier
///
- public UInt64 StreamId { get; private set; }
+ public ulong StreamId { get; private set; }
///
/// Send data to the client.
@@ -34,11 +34,15 @@ namespace EonaCat.Quic.Context
public bool Send(byte[] data)
{
if (Stream.CanSendData() == false)
+ {
return false;
+ }
// Ignore empty packets
if (data == null || data.Length <= 0)
+ {
return true;
+ }
// Packet packet = ConnectionContext.Connection.PacketCreator.CreateDataPacket(StreamId, data);
diff --git a/EonaCat.Network/System/Quic/Exceptions/ConnectionException.cs b/EonaCat.Network/System/Quic/Exceptions/ConnectionException.cs
index 7036515..b723049 100644
--- a/EonaCat.Network/System/Quic/Exceptions/ConnectionException.cs
+++ b/EonaCat.Network/System/Quic/Exceptions/ConnectionException.cs
@@ -7,7 +7,7 @@ namespace EonaCat.Quic.Exceptions
public class ConnectionException : Exception
{
- public ConnectionException(string message) : base(message)
+ public ConnectionException(string message) : base($"EonaCat Network: {message}")
{
}
}
diff --git a/EonaCat.Network/System/Quic/Exceptions/ServerNotStartedException.cs b/EonaCat.Network/System/Quic/Exceptions/ServerNotStartedException.cs
index 2c5ce33..16ab1af 100644
--- a/EonaCat.Network/System/Quic/Exceptions/ServerNotStartedException.cs
+++ b/EonaCat.Network/System/Quic/Exceptions/ServerNotStartedException.cs
@@ -10,7 +10,7 @@ namespace EonaCat.Quic.Exceptions
public ServerNotStartedException()
{ }
- public ServerNotStartedException(string message) : base(message)
+ public ServerNotStartedException(string message) : base($"EonaCat Network: {message}")
{
}
}
diff --git a/EonaCat.Network/System/Quic/Exceptions/StreamException.cs b/EonaCat.Network/System/Quic/Exceptions/StreamException.cs
index c82c0de..a5205c5 100644
--- a/EonaCat.Network/System/Quic/Exceptions/StreamException.cs
+++ b/EonaCat.Network/System/Quic/Exceptions/StreamException.cs
@@ -10,7 +10,7 @@ namespace EonaCat.Quic.Exceptions
public StreamException()
{ }
- public StreamException(string message) : base(message)
+ public StreamException(string message) : base($"EonaCat Network: {message}")
{
}
}
diff --git a/EonaCat.Network/System/Quic/Helpers/ByteArray.cs b/EonaCat.Network/System/Quic/Helpers/ByteArray.cs
index 98dfe28..ea82068 100644
--- a/EonaCat.Network/System/Quic/Helpers/ByteArray.cs
+++ b/EonaCat.Network/System/Quic/Helpers/ByteArray.cs
@@ -46,18 +46,18 @@ namespace EonaCat.Quic.Helpers
return ReadBytes(count.Value);
}
- public UInt16 ReadUInt16()
+ public ushort ReadUInt16()
{
byte[] bytes = ReadBytes(2);
- UInt16 result = ByteHelpers.ToUInt16(bytes);
+ ushort result = ByteHelpers.ToUInt16(bytes);
return result;
}
- public UInt32 ReadUInt32()
+ public uint ReadUInt32()
{
byte[] bytes = ReadBytes(4);
- UInt32 result = ByteHelpers.ToUInt32(bytes);
+ uint result = ByteHelpers.ToUInt32(bytes);
return result;
}
diff --git a/EonaCat.Network/System/Quic/Helpers/ByteHelpers.cs b/EonaCat.Network/System/Quic/Helpers/ByteHelpers.cs
index e52c73e..80012f5 100644
--- a/EonaCat.Network/System/Quic/Helpers/ByteHelpers.cs
+++ b/EonaCat.Network/System/Quic/Helpers/ByteHelpers.cs
@@ -8,29 +8,35 @@ namespace EonaCat.Quic.Helpers
public static class ByteHelpers
{
- public static byte[] GetBytes(UInt64 integer)
+ public static byte[] GetBytes(ulong integer)
{
byte[] result = BitConverter.GetBytes(integer);
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(result);
+ }
return result;
}
- public static byte[] GetBytes(UInt32 integer)
+ public static byte[] GetBytes(uint integer)
{
byte[] result = BitConverter.GetBytes(integer);
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(result);
+ }
return result;
}
- public static byte[] GetBytes(UInt16 integer)
+ public static byte[] GetBytes(ushort integer)
{
byte[] result = BitConverter.GetBytes(integer);
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(result);
+ }
return result;
}
@@ -42,32 +48,38 @@ namespace EonaCat.Quic.Helpers
return result;
}
- public static UInt64 ToUInt64(byte[] data)
+ public static ulong ToUInt64(byte[] data)
{
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(data);
+ }
- UInt64 result = BitConverter.ToUInt64(data, 0);
+ ulong result = BitConverter.ToUInt64(data, 0);
return result;
}
- public static UInt32 ToUInt32(byte[] data)
+ public static uint ToUInt32(byte[] data)
{
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(data);
+ }
- UInt32 result = BitConverter.ToUInt32(data, 0);
+ uint result = BitConverter.ToUInt32(data, 0);
return result;
}
- public static UInt16 ToUInt16(byte[] data)
+ public static ushort ToUInt16(byte[] data)
{
if (BitConverter.IsLittleEndian)
+ {
Array.Reverse(data);
+ }
- UInt16 result = BitConverter.ToUInt16(data, 0);
+ ushort result = BitConverter.ToUInt16(data, 0);
return result;
}
diff --git a/EonaCat.Network/System/Quic/Helpers/IntegerParts.cs b/EonaCat.Network/System/Quic/Helpers/IntegerParts.cs
index 53484e9..01f0d04 100644
--- a/EonaCat.Network/System/Quic/Helpers/IntegerParts.cs
+++ b/EonaCat.Network/System/Quic/Helpers/IntegerParts.cs
@@ -7,29 +7,25 @@ namespace EonaCat.Quic.Helpers
public class IntegerParts
{
- public const UInt64 MaxValue = 18446744073709551615;
+ public const ulong MaxValue = 18446744073709551615;
- private UInt64 _integer;
+ public ulong Value { get; }
- public UInt64 Value
- { get { return _integer; } }
+ public byte Size => RequiredBytes(Value);
- public byte Size
- { get { return RequiredBytes(Value); } }
-
- public IntegerParts(UInt64 integer)
+ public IntegerParts(ulong integer)
{
- _integer = integer;
+ Value = integer;
}
public byte[] ToByteArray()
{
- return Encode(this._integer);
+ return Encode(this.Value);
}
public static implicit operator byte[](IntegerParts integer)
{
- return Encode(integer._integer);
+ return Encode(integer.Value);
}
public static implicit operator IntegerParts(byte[] bytes)
@@ -37,17 +33,17 @@ namespace EonaCat.Quic.Helpers
return new IntegerParts(Decode(bytes));
}
- public static implicit operator IntegerParts(UInt64 integer)
+ public static implicit operator IntegerParts(ulong integer)
{
return new IntegerParts(integer);
}
- public static implicit operator UInt64(IntegerParts integer)
+ public static implicit operator ulong(IntegerParts integer)
{
- return integer._integer;
+ return integer.Value;
}
- public static byte[] Encode(UInt64 integer)
+ public static byte[] Encode(ulong integer)
{
byte requiredBytes = RequiredBytes(integer);
int offset = 8 - requiredBytes;
@@ -60,32 +56,42 @@ namespace EonaCat.Quic.Helpers
return result;
}
- public static UInt64 Decode(byte[] bytes)
+ public static ulong Decode(byte[] bytes)
{
int i = 8 - bytes.Length;
byte[] buffer = new byte[8];
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
- UInt64 res = ByteHelpers.ToUInt64(buffer);
+ ulong res = ByteHelpers.ToUInt64(buffer);
return res;
}
- private static byte RequiredBytes(UInt64 integer)
+ private static byte RequiredBytes(ulong integer)
{
byte result = 0;
if (integer <= byte.MaxValue) /* 255 */
+ {
result = 1;
- else if (integer <= UInt16.MaxValue) /* 65535 */
+ }
+ else if (integer <= ushort.MaxValue) /* 65535 */
+ {
result = 2;
- else if (integer <= UInt32.MaxValue) /* 4294967295 */
+ }
+ else if (integer <= uint.MaxValue) /* 4294967295 */
+ {
result = 4;
- else if (integer <= UInt64.MaxValue) /* 18446744073709551615 */
+ }
+ else if (integer <= ulong.MaxValue) /* 18446744073709551615 */
+ {
result = 8;
+ }
else
+ {
throw new ArgumentOutOfRangeException("Value is larger than GranularInteger.MaxValue.");
+ }
return result;
}
diff --git a/EonaCat.Network/System/Quic/Helpers/StreamId.cs b/EonaCat.Network/System/Quic/Helpers/StreamId.cs
index b350e41..8259ae4 100644
--- a/EonaCat.Network/System/Quic/Helpers/StreamId.cs
+++ b/EonaCat.Network/System/Quic/Helpers/StreamId.cs
@@ -15,15 +15,15 @@ namespace EonaCat.Quic.Helpers
public class StreamId
{
- public UInt64 Id { get; }
- public UInt64 IntegerValue { get; }
+ public ulong Id { get; }
+ public ulong IntegerValue { get; }
public StreamType Type { get; private set; }
- public StreamId(UInt64 id, StreamType type)
+ public StreamId(ulong id, StreamType type)
{
Id = id;
Type = type;
- IntegerValue = id << 2 | (UInt64)type;
+ IntegerValue = id << 2 | (ulong)type;
}
public static implicit operator byte[](StreamId id)
@@ -36,7 +36,7 @@ namespace EonaCat.Quic.Helpers
return Decode(data);
}
- public static implicit operator UInt64(StreamId streamId)
+ public static implicit operator ulong(StreamId streamId)
{
return streamId.Id;
}
@@ -46,9 +46,9 @@ namespace EonaCat.Quic.Helpers
return Decode(ByteHelpers.GetBytes(integer.Value));
}
- public static byte[] Encode(UInt64 id, StreamType type)
+ public static byte[] Encode(ulong id, StreamType type)
{
- UInt64 identifier = id << 2 | (UInt64)type;
+ ulong identifier = id << 2 | (ulong)type;
byte[] result = ByteHelpers.GetBytes(identifier);
@@ -58,9 +58,9 @@ namespace EonaCat.Quic.Helpers
public static StreamId Decode(byte[] data)
{
StreamId result;
- UInt64 id = ByteHelpers.ToUInt64(data);
- UInt64 identifier = id >> 2;
- UInt64 type = (UInt64)(0x03 & id);
+ ulong id = ByteHelpers.ToUInt64(data);
+ ulong identifier = id >> 2;
+ ulong type = 0x03 & id;
StreamType streamType = (StreamType)type;
result = new StreamId(identifier, streamType);
diff --git a/EonaCat.Network/System/Quic/Helpers/VariableInteger.cs b/EonaCat.Network/System/Quic/Helpers/VariableInteger.cs
index 87d9ec9..a1f979c 100644
--- a/EonaCat.Network/System/Quic/Helpers/VariableInteger.cs
+++ b/EonaCat.Network/System/Quic/Helpers/VariableInteger.cs
@@ -7,21 +7,18 @@ namespace EonaCat.Quic.Helpers
public class IntegerVar
{
- public const UInt64 MaxValue = 4611686018427387903;
+ public const ulong MaxValue = 4611686018427387903;
- private UInt64 _integer;
+ public ulong Value { get; }
- public UInt64 Value
- { get { return _integer; } }
-
- public IntegerVar(UInt64 integer)
+ public IntegerVar(ulong integer)
{
- _integer = integer;
+ Value = integer;
}
public static implicit operator byte[](IntegerVar integer)
{
- return Encode(integer._integer);
+ return Encode(integer.Value);
}
public static implicit operator IntegerVar(byte[] bytes)
@@ -29,14 +26,14 @@ namespace EonaCat.Quic.Helpers
return new IntegerVar(Decode(bytes));
}
- public static implicit operator IntegerVar(UInt64 integer)
+ public static implicit operator IntegerVar(ulong integer)
{
return new IntegerVar(integer);
}
- public static implicit operator UInt64(IntegerVar integer)
+ public static implicit operator ulong(IntegerVar integer)
{
- return integer._integer;
+ return integer.Value;
}
public static implicit operator IntegerVar(StreamId streamId)
@@ -53,22 +50,32 @@ namespace EonaCat.Quic.Helpers
public byte[] ToByteArray()
{
- return Encode(this._integer);
+ return Encode(this.Value);
}
- public static byte[] Encode(UInt64 integer)
+ public static byte[] Encode(ulong integer)
{
int requiredBytes = 0;
if (integer <= byte.MaxValue >> 2) /* 63 */
+ {
requiredBytes = 1;
- else if (integer <= UInt16.MaxValue >> 2) /* 16383 */
+ }
+ else if (integer <= ushort.MaxValue >> 2) /* 16383 */
+ {
requiredBytes = 2;
- else if (integer <= UInt32.MaxValue >> 2) /* 1073741823 */
+ }
+ else if (integer <= uint.MaxValue >> 2) /* 1073741823 */
+ {
requiredBytes = 4;
- else if (integer <= UInt64.MaxValue >> 2) /* 4611686018427387903 */
+ }
+ else if (integer <= ulong.MaxValue >> 2) /* 4611686018427387903 */
+ {
requiredBytes = 8;
+ }
else
+ {
throw new ArgumentOutOfRangeException("Value is larger than IntegerVar.MaxValue.");
+ }
int offset = 8 - requiredBytes;
@@ -83,7 +90,7 @@ namespace EonaCat.Quic.Helpers
return result;
}
- public static UInt64 Decode(byte[] bytes)
+ public static ulong Decode(byte[] bytes)
{
int i = 8 - bytes.Length;
byte[] buffer = new byte[8];
@@ -91,7 +98,7 @@ namespace EonaCat.Quic.Helpers
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
buffer[i] = (byte)(buffer[i] & (255 >> 2));
- UInt64 res = ByteHelpers.ToUInt64(buffer);
+ ulong res = ByteHelpers.ToUInt64(buffer);
return res;
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/ErrorCodes.cs b/EonaCat.Network/System/Quic/Infrastructure/ErrorCodes.cs
index 0f20d17..cf96230 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/ErrorCodes.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/ErrorCodes.cs
@@ -5,7 +5,7 @@ namespace EonaCat.Quic.Infrastructure
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
- public enum ErrorCode : UInt16
+ public enum ErrorCode : ushort
{
NO_ERROR = 0x0,
INTERNAL_ERROR = 0x1,
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Exceptions/ProtocolException.cs b/EonaCat.Network/System/Quic/Infrastructure/Exceptions/ProtocolException.cs
index 1a37dc0..1f3a83d 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Exceptions/ProtocolException.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Exceptions/ProtocolException.cs
@@ -11,7 +11,7 @@ namespace EonaCat.Quic.Infrastructure.Exceptions
{
}
- public ProtocolException(string message) : base(message)
+ public ProtocolException(string message) : base($"EonaCat Network: {message}")
{
}
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/FrameParser.cs b/EonaCat.Network/System/Quic/Infrastructure/FrameParser.cs
index 892de6d..3e6485c 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/FrameParser.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/FrameParser.cs
@@ -8,7 +8,7 @@ namespace EonaCat.Quic.Infrastructure
public class FrameParser
{
- private ByteArray _array;
+ private readonly ByteArray _array;
public FrameParser(ByteArray array)
{
@@ -147,7 +147,9 @@ namespace EonaCat.Quic.Infrastructure
}
if (result != null)
+ {
result.Decode(_array);
+ }
return result;
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/AckFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/AckFrame.cs
index f67c2d7..058fab6 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/AckFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/AckFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/ConnectionCloseFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/ConnectionCloseFrame.cs
index afb4875..29f98eb 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/ConnectionCloseFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/ConnectionCloseFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -29,11 +29,11 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
ActualType = 0x1c;
- ErrorCode = (UInt64)error;
- FrameType = new IntegerVar((UInt64)frameType);
+ ErrorCode = (ulong)error;
+ FrameType = new IntegerVar(frameType);
if (!string.IsNullOrWhiteSpace(reason))
{
- ReasonPhraseLength = new IntegerVar((UInt64)reason.Length);
+ ReasonPhraseLength = new IntegerVar((ulong)reason.Length);
}
else
{
@@ -70,7 +70,7 @@ namespace EonaCat.Quic.Infrastructure.Frames
if (string.IsNullOrWhiteSpace(ReasonPhrase) == false)
{
- byte[] rpl = new IntegerVar((UInt64)ReasonPhrase.Length);
+ byte[] rpl = new IntegerVar((ulong)ReasonPhrase.Length);
result.AddRange(rpl);
byte[] reasonPhrase = ByteHelpers.GetBytes(ReasonPhrase);
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/CryptoFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/CryptoFrame.cs
index 913e3e5..4235ef0 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/CryptoFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/CryptoFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/DataBlockedFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/DataBlockedFrame.cs
index 790e21b..4f90abb 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/DataBlockedFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/DataBlockedFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -15,7 +15,7 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
}
- public DataBlockedFrame(UInt64 dataLimit)
+ public DataBlockedFrame(ulong dataLimit)
{
MaximumData = dataLimit;
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxDataFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxDataFrame.cs
index 35d622c..8185194 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxDataFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxDataFrame.cs
@@ -1,12 +1,12 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Frames
{
public class MaxDataFrame : Frame
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
- // Copyright EonaCat (Jeroen Saey)
+ // Copyright EonaCat (Jeroen Saey)
// See file LICENSE or go to https://EonaCat.com/License for full license details.
public override byte Type => 0x10;
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamDataFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamDataFrame.cs
index 3dd9dc2..b3f650c 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamDataFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamDataFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -19,7 +19,7 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
}
- public MaxStreamDataFrame(UInt64 streamId, UInt64 maximumStreamData)
+ public MaxStreamDataFrame(ulong streamId, ulong maximumStreamData)
{
StreamId = streamId;
MaximumStreamData = maximumStreamData;
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamsFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamsFrame.cs
index 9c46ac8..9144a48 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamsFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/MaxStreamsFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -15,7 +15,7 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
}
- public MaxStreamsFrame(UInt64 maximumStreamId, StreamType appliesTo)
+ public MaxStreamsFrame(ulong maximumStreamId, StreamType appliesTo)
{
MaximumStreams = new IntegerVar(maximumStreamId);
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/NewConnectionIdFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/NewConnectionIdFrame.cs
index 9c2c165..f5dae09 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/NewConnectionIdFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/NewConnectionIdFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/NewTokenFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/NewTokenFrame.cs
index 9b2a9be..6637993 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/NewTokenFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/NewTokenFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/PaddingFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/PaddingFrame.cs
index 8c23103..a7bb3b5 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/PaddingFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/PaddingFrame.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/PathChallengeFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/PathChallengeFrame.cs
index 6e17429..1326b47 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/PathChallengeFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/PathChallengeFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/PathResponseFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/PathResponseFrame.cs
index 1a3aa7e..ece907d 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/PathResponseFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/PathResponseFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/PingFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/PingFrame.cs
index 77afb8d..eb80c0d 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/PingFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/PingFrame.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/ResetStreamFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/ResetStreamFrame.cs
index 62e1c37..1ed9c2a 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/ResetStreamFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/ResetStreamFrame.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/RetireConnectionIdFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/RetireConnectionIdFrame.cs
index 05abcfa..33be918 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/RetireConnectionIdFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/RetireConnectionIdFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/StopSendingFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/StopSendingFrame.cs
index 1f10cc3..9f2145a 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/StopSendingFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/StopSendingFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamDataBlockedFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamDataBlockedFrame.cs
index ab27995..545066e 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamDataBlockedFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamDataBlockedFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -17,7 +17,7 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
}
- public StreamDataBlockedFrame(UInt64 streamId, UInt64 streamDataLimit)
+ public StreamDataBlockedFrame(ulong streamId, ulong streamDataLimit)
{
StreamId = streamId;
MaximumStreamData = streamDataLimit;
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamFrame.cs
index 831643d..5401840 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamFrame.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Frames
{
@@ -22,12 +22,12 @@ namespace EonaCat.Quic.Infrastructure.Frames
{
}
- public StreamFrame(UInt64 streamId, byte[] data, UInt64 offset, bool eos)
+ public StreamFrame(ulong streamId, byte[] data, ulong offset, bool eos)
{
StreamId = streamId;
StreamData = data;
Offset = offset;
- Length = (UInt64)data.Length;
+ Length = (ulong)data.Length;
EndOfStream = eos;
}
@@ -41,11 +41,19 @@ namespace EonaCat.Quic.Infrastructure.Frames
StreamId = array.ReadIntegerVar();
if (OFF_BIT > 0)
+ {
Offset = array.ReadIntegerVar();
+ }
+
if (LEN_BIT > 0)
+ {
Length = array.ReadIntegerVar();
+ }
+
if (FIN_BIT > 0)
+ {
EndOfStream = true;
+ }
StreamData = array.ReadBytes((int)Length.Value);
}
@@ -53,11 +61,19 @@ namespace EonaCat.Quic.Infrastructure.Frames
public override byte[] Encode()
{
if (Offset != null && Offset.Value > 0)
+ {
ActualType = (byte)(ActualType | 0x04);
+ }
+
if (Length != null && Length.Value > 0)
+ {
ActualType = (byte)(ActualType | 0x02);
+ }
+
if (EndOfStream == true)
+ {
ActualType = (byte)(ActualType | 0x01);
+ }
byte OFF_BIT = (byte)(ActualType & 0x04);
byte LEN_BIT = (byte)(ActualType & 0x02);
@@ -69,10 +85,14 @@ namespace EonaCat.Quic.Infrastructure.Frames
result.AddRange(streamId);
if (OFF_BIT > 0)
+ {
result.AddRange(Offset.ToByteArray());
+ }
if (LEN_BIT > 0)
+ {
result.AddRange(Length.ToByteArray());
+ }
result.AddRange(StreamData);
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamsBlockedFrame.cs b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamsBlockedFrame.cs
index ad6eaaa..767aaff 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamsBlockedFrame.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Frames/StreamsBlockedFrame.cs
@@ -1,5 +1,5 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System;
namespace EonaCat.Quic.Infrastructure.Frames
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/NumberSpace.cs b/EonaCat.Network/System/Quic/Infrastructure/NumberSpace.cs
index 574bbcc..749852d 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/NumberSpace.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/NumberSpace.cs
@@ -7,14 +7,14 @@ namespace EonaCat.Quic.Infrastructure
public class NumberSpace
{
- private UInt32 _max = UInt32.MaxValue;
- private UInt32 _n = 0;
+ private readonly uint _max = uint.MaxValue;
+ private uint _n = 0;
public NumberSpace()
{
}
- public NumberSpace(UInt32 max)
+ public NumberSpace(uint max)
{
_max = max;
}
@@ -24,10 +24,12 @@ namespace EonaCat.Quic.Infrastructure
return _n == _max;
}
- public UInt32 Get()
+ public uint Get()
{
if (_n >= _max)
+ {
return 0;
+ }
_n++;
return _n;
diff --git a/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/InitialPacketCreator.cs b/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/InitialPacketCreator.cs
index 9917b22..d5858b7 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/InitialPacketCreator.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/InitialPacketCreator.cs
@@ -22,7 +22,9 @@ namespace EonaCat.Quic.Infrastructure.PacketProcessing
int padding = QuicSettings.PMTU - length;
for (int i = 0; i < padding; i++)
+ {
packet.AttachFrame(new PaddingFrame());
+ }
return packet;
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/PacketCreator.cs b/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/PacketCreator.cs
index fcd52d1..9d5fad6 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/PacketCreator.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/PacketProcessing/PacketCreator.cs
@@ -1,7 +1,7 @@
-using System;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
using EonaCat.Quic.Infrastructure.Frames;
using EonaCat.Quic.Infrastructure.Packets;
+using System;
namespace EonaCat.Quic.Infrastructure.PacketProcessing
{
@@ -32,7 +32,7 @@ namespace EonaCat.Quic.Infrastructure.PacketProcessing
return packet;
}
- public ShortHeaderPacket CreateDataPacket(UInt64 streamId, byte[] data, UInt64 offset, bool eos)
+ public ShortHeaderPacket CreateDataPacket(ulong streamId, byte[] data, ulong offset, bool eos)
{
ShortHeaderPacket packet = new ShortHeaderPacket(_peerConnectionId.Size);
packet.PacketNumber = _ns.Get();
diff --git a/EonaCat.Network/System/Quic/Infrastructure/PacketType.cs b/EonaCat.Network/System/Quic/Infrastructure/PacketType.cs
index 2ac7a91..7d27293 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/PacketType.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/PacketType.cs
@@ -5,7 +5,7 @@ namespace EonaCat.Quic.Infrastructure
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
- public enum PacketType : UInt16
+ public enum PacketType : ushort
{
Initial = 0x0,
ZeroRTTProtected = 0x1,
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Packets/InitialPacket.cs b/EonaCat.Network/System/Quic/Infrastructure/Packets/InitialPacket.cs
index 50b9180..b6a0f34 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Packets/InitialPacket.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Packets/InitialPacket.cs
@@ -1,6 +1,6 @@
-using System;
+using EonaCat.Quic.Helpers;
+using System;
using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
namespace EonaCat.Quic.Infrastructure.Packets
{
@@ -44,15 +44,21 @@ namespace EonaCat.Quic.Infrastructure.Packets
DestinationConnectionIdLength = array.ReadByte();
if (DestinationConnectionIdLength > 0)
+ {
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
+ }
SourceConnectionIdLength = array.ReadByte();
if (SourceConnectionIdLength > 0)
+ {
SourceConnectionId = array.ReadGranularInteger(SourceConnectionIdLength);
+ }
TokenLength = array.ReadIntegerVar();
if (TokenLength > 0)
+ {
Token = array.ReadBytes(TokenLength);
+ }
Length = array.ReadIntegerVar();
PacketNumber = array.ReadGranularInteger(pnSize);
@@ -72,13 +78,18 @@ namespace EonaCat.Quic.Infrastructure.Packets
result.Add(DestinationConnectionId.Size);
if (DestinationConnectionId.Size > 0)
+ {
result.AddRange(DestinationConnectionId.ToByteArray());
+ }
+
result.Add(SourceConnectionId.Size);
if (SourceConnectionId.Size > 0)
+ {
result.AddRange(SourceConnectionId.ToByteArray());
+ }
byte[] tokenLength = new IntegerVar(0);
- byte[] length = new IntegerVar(PacketNumber.Size + (UInt64)frames.Length);
+ byte[] length = new IntegerVar(PacketNumber.Size + (ulong)frames.Length);
result.AddRange(tokenLength);
result.AddRange(length);
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Packets/LongHeaderPacket.cs b/EonaCat.Network/System/Quic/Infrastructure/Packets/LongHeaderPacket.cs
index b70938a..5c34fdb 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Packets/LongHeaderPacket.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Packets/LongHeaderPacket.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Packets
{
@@ -42,11 +42,15 @@ namespace EonaCat.Quic.Infrastructure.Packets
DestinationConnectionIdLength = array.ReadByte();
if (DestinationConnectionIdLength > 0)
+ {
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
+ }
SourceConnectionIdLength = array.ReadByte();
if (SourceConnectionIdLength > 0)
+ {
SourceConnectionId = array.ReadGranularInteger(SourceConnectionIdLength);
+ }
this.DecodeFrames(array);
}
@@ -62,11 +66,15 @@ namespace EonaCat.Quic.Infrastructure.Packets
result.Add(DestinationConnectionId.Size);
if (DestinationConnectionId.Size > 0)
+ {
result.AddRange(DestinationConnectionId.ToByteArray());
+ }
result.Add(SourceConnectionId.Size);
if (SourceConnectionId.Size > 0)
+ {
result.AddRange(SourceConnectionId.ToByteArray());
+ }
result.AddRange(frames);
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Packets/Packet.cs b/EonaCat.Network/System/Quic/Infrastructure/Packets/Packet.cs
index fff6728..49d3625 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Packets/Packet.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Packets/Packet.cs
@@ -1,9 +1,9 @@
-using System;
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
using EonaCat.Quic.Infrastructure.Exceptions;
using EonaCat.Quic.Infrastructure.Frames;
using EonaCat.Quic.Infrastructure.Settings;
+using System;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Packets
{
@@ -18,7 +18,7 @@ namespace EonaCat.Quic.Infrastructure.Packets
protected List _frames = new List();
public abstract byte Type { get; }
- public UInt32 Version { get; set; }
+ public uint Version { get; set; }
public abstract byte[] Encode();
@@ -43,13 +43,17 @@ namespace EonaCat.Quic.Infrastructure.Packets
{
result = factory.GetFrame();
if (result != null)
+ {
_frames.Add(result);
+ }
frames++;
}
if (array.HasData())
+ {
throw new ProtocolException("Unexpected number of frames or possibly corrupted frame was sent.");
+ }
}
public virtual byte[] EncodeFrames()
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Packets/ShortHeaderPacket.cs b/EonaCat.Network/System/Quic/Infrastructure/Packets/ShortHeaderPacket.cs
index e874177..fe25cdd 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Packets/ShortHeaderPacket.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Packets/ShortHeaderPacket.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using EonaCat.Quic.Helpers;
+using EonaCat.Quic.Helpers;
+using System.Collections.Generic;
namespace EonaCat.Quic.Infrastructure.Packets
{
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Packets/Unpacker.cs b/EonaCat.Network/System/Quic/Infrastructure/Packets/Unpacker.cs
index 764706c..a4bff1c 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Packets/Unpacker.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Packets/Unpacker.cs
@@ -23,7 +23,9 @@
}
if (result == null)
+ {
return null;
+ }
result.Decode(data);
@@ -33,18 +35,31 @@
public QuicPacketType GetPacketType(byte[] data)
{
if (data == null || data.Length <= 0)
+ {
return QuicPacketType.Broken;
+ }
byte type = data[0];
if ((type & 0xC0) == 0xC0)
+ {
return QuicPacketType.Initial;
+ }
+
if ((type & 0x40) == 0x40)
+ {
return QuicPacketType.ShortHeader;
+ }
+
if ((type & 0x80) == 0x80)
+ {
return QuicPacketType.VersionNegotiation;
+ }
+
if ((type & 0xE0) == 0xE0)
+ {
return QuicPacketType.LongHeader;
+ }
return QuicPacketType.Broken;
}
diff --git a/EonaCat.Network/System/Quic/Infrastructure/Settings/QuicVersion.cs b/EonaCat.Network/System/Quic/Infrastructure/Settings/QuicVersion.cs
index 418d845..a3548e7 100644
--- a/EonaCat.Network/System/Quic/Infrastructure/Settings/QuicVersion.cs
+++ b/EonaCat.Network/System/Quic/Infrastructure/Settings/QuicVersion.cs
@@ -10,6 +10,6 @@ namespace EonaCat.Quic.Infrastructure.Settings
{
public const int CurrentVersion = 16;
- public static readonly List SupportedVersions = new List() { 15, 16 };
+ public static readonly List SupportedVersions = new List() { 15, 16 };
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Quic/InternalInfrastructure/PacketWireTransfer.cs b/EonaCat.Network/System/Quic/InternalInfrastructure/PacketWireTransfer.cs
index 81870da..8d7b4c4 100644
--- a/EonaCat.Network/System/Quic/InternalInfrastructure/PacketWireTransfer.cs
+++ b/EonaCat.Network/System/Quic/InternalInfrastructure/PacketWireTransfer.cs
@@ -1,7 +1,7 @@
-using System.Net;
-using System.Net.Sockets;
-using EonaCat.Quic.Exceptions;
+using EonaCat.Quic.Exceptions;
using EonaCat.Quic.Infrastructure.Packets;
+using System.Net;
+using System.Net.Sockets;
namespace EonaCat.Quic.InternalInfrastructure
{
@@ -10,10 +10,10 @@ namespace EonaCat.Quic.InternalInfrastructure
internal class PacketWireTransfer
{
- private UdpClient _client;
+ private readonly UdpClient _client;
private IPEndPoint _peerEndpoint;
- private Unpacker _unpacker;
+ private readonly Unpacker _unpacker;
public PacketWireTransfer(UdpClient client, IPEndPoint peerEndpoint)
{
@@ -28,7 +28,9 @@ namespace EonaCat.Quic.InternalInfrastructure
// Await response for sucessfull connection creation by the server
byte[] peerData = _client.Receive(ref _peerEndpoint);
if (peerData == null)
+ {
throw new ConnectionException("Server did not respond properly.");
+ }
Packet packet = _unpacker.Unpack(peerData);
diff --git a/EonaCat.Network/System/Quic/QuicClient.cs b/EonaCat.Network/System/Quic/QuicClient.cs
index 625815a..0aac34f 100644
--- a/EonaCat.Network/System/Quic/QuicClient.cs
+++ b/EonaCat.Network/System/Quic/QuicClient.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using EonaCat.Quic.Connections;
+using EonaCat.Quic.Connections;
using EonaCat.Quic.Exceptions;
using EonaCat.Quic.Helpers;
using EonaCat.Quic.Infrastructure.Frames;
@@ -11,6 +6,11 @@ using EonaCat.Quic.Infrastructure.PacketProcessing;
using EonaCat.Quic.Infrastructure.Packets;
using EonaCat.Quic.Infrastructure.Settings;
using EonaCat.Quic.InternalInfrastructure;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
namespace EonaCat.Quic
{
@@ -23,12 +23,12 @@ namespace EonaCat.Quic
public class QuicClient : QuicTransport
{
private IPEndPoint _peerIp;
- private UdpClient _client;
+ private readonly UdpClient _client;
private QuicConnection _connection;
- private InitialPacketCreator _packetCreator;
+ private readonly InitialPacketCreator _packetCreator;
- private UInt64 _maximumStreams = QuicSettings.MaximumStreamId;
+ private ulong _maximumStreams = QuicSettings.MaximumStreamId;
private PacketWireTransfer _pwt;
public QuicClient()
@@ -98,7 +98,9 @@ namespace EonaCat.Quic
// Break out if the first Padding Frame has been reached
if (frame is PaddingFrame)
+ {
break;
+ }
}
}
diff --git a/EonaCat.Network/System/Quic/QuicServer.cs b/EonaCat.Network/System/Quic/QuicServer.cs
index db1e251..10c13b0 100644
--- a/EonaCat.Network/System/Quic/QuicServer.cs
+++ b/EonaCat.Network/System/Quic/QuicServer.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using EonaCat.Quic.Connections;
+using EonaCat.Quic.Connections;
using EonaCat.Quic.Constants;
using EonaCat.Quic.Events;
using EonaCat.Quic.Helpers;
@@ -12,6 +8,10 @@ using EonaCat.Quic.Infrastructure.PacketProcessing;
using EonaCat.Quic.Infrastructure.Packets;
using EonaCat.Quic.Infrastructure.Settings;
using EonaCat.Quic.InternalInfrastructure;
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
namespace EonaCat.Quic
{
@@ -30,7 +30,7 @@ namespace EonaCat.Quic
private UdpClient _client;
- private int _port;
+ private readonly int _port;
private readonly string _hostname;
private bool _started;
@@ -93,7 +93,9 @@ namespace EonaCat.Quic
public void Close()
{
if (_started)
+ {
_client.Close();
+ }
}
///
@@ -105,7 +107,7 @@ namespace EonaCat.Quic
private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint)
{
QuicConnection result = null;
- UInt64 availableConnectionId;
+ ulong availableConnectionId;
byte[] data;
// Unsupported version. Version negotiation packet is sent only on initial connection. All other packets are dropped. (5.2.2 / 16th draft)
if (packet.Version != QuicVersion.CurrentVersion || !QuicVersion.SupportedVersions.Contains(packet.Version))
@@ -146,7 +148,9 @@ namespace EonaCat.Quic
data = ip.Encode();
int dataSent = _client.Send(data, data.Length, endPoint);
if (dataSent > 0)
+ {
return result;
+ }
return null;
}
diff --git a/EonaCat.Network/System/Quic/QuicTransport.cs b/EonaCat.Network/System/Quic/QuicTransport.cs
index d77fc28..2bd8d08 100644
--- a/EonaCat.Network/System/Quic/QuicTransport.cs
+++ b/EonaCat.Network/System/Quic/QuicTransport.cs
@@ -20,7 +20,9 @@ namespace EonaCat.Quic
// No suitable connection found. Discard the packet.
if (connection == null)
+ {
return;
+ }
connection.ProcessFrames(shp.GetFrames());
}
diff --git a/EonaCat.Network/System/Quic/Streams/QuicStream.cs b/EonaCat.Network/System/Quic/Streams/QuicStream.cs
index f497555..7dfaa60 100644
--- a/EonaCat.Network/System/Quic/Streams/QuicStream.cs
+++ b/EonaCat.Network/System/Quic/Streams/QuicStream.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using EonaCat.Quic.Connections;
+using EonaCat.Quic.Connections;
using EonaCat.Quic.Constants;
using EonaCat.Quic.Events;
using EonaCat.Quic.Exceptions;
@@ -9,6 +6,9 @@ using EonaCat.Quic.Helpers;
using EonaCat.Quic.Infrastructure.Frames;
using EonaCat.Quic.Infrastructure.Packets;
using EonaCat.Quic.Infrastructure.Settings;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace EonaCat.Quic.Streams
{
@@ -20,11 +20,11 @@ namespace EonaCat.Quic.Streams
///
public class QuicStream
{
- private SortedList _data = new SortedList();
- private QuicConnection _connection;
- private UInt64 _maximumStreamData;
- private UInt64 _currentTransferRate;
- private UInt64 _sendOffset;
+ private readonly SortedList _data = new SortedList();
+ private readonly QuicConnection _connection;
+ private ulong _maximumStreamData;
+ private ulong _currentTransferRate;
+ private ulong _sendOffset;
public StreamState State { get; set; }
public StreamType Type { get; set; }
@@ -32,13 +32,7 @@ namespace EonaCat.Quic.Streams
public StreamDataReceivedEvent OnStreamDataReceived { get; set; }
- public byte[] Data
- {
- get
- {
- return _data.SelectMany(v => v.Value).ToArray();
- }
- }
+ public byte[] Data => _data.SelectMany(v => v.Value).ToArray();
public QuicStream(QuicConnection connection, StreamId streamId)
{
@@ -55,7 +49,9 @@ namespace EonaCat.Quic.Streams
public bool Send(byte[] data)
{
if (Type == StreamType.ServerUnidirectional)
+ {
throw new StreamException("Cannot send data on unidirectional stream.");
+ }
_connection.IncrementRate(data.Length);
@@ -73,16 +69,20 @@ namespace EonaCat.Quic.Streams
}
byte[] buffer = new byte[dataSize];
- Buffer.BlockCopy(data, (Int32)_sendOffset, buffer, 0, dataSize);
+ Buffer.BlockCopy(data, (int)_sendOffset, buffer, 0, dataSize);
ShortHeaderPacket packet = _connection.PacketCreator.CreateDataPacket(this.StreamId.IntegerValue, buffer, _sendOffset, eos);
if (i == 0 && data.Length >= QuicSettings.MaxStreamData)
- packet.AttachFrame(new MaxStreamDataFrame(this.StreamId.IntegerValue, (UInt64)(data.Length + 1)));
+ {
+ packet.AttachFrame(new MaxStreamDataFrame(this.StreamId.IntegerValue, (ulong)(data.Length + 1)));
+ }
if (_connection.MaximumReached())
- packet.AttachFrame(new StreamDataBlockedFrame(StreamId.IntegerValue, (UInt64)data.Length));
+ {
+ packet.AttachFrame(new StreamDataBlockedFrame(StreamId.IntegerValue, (ulong)data.Length));
+ }
- _sendOffset += (UInt64)buffer.Length;
+ _sendOffset += (ulong)buffer.Length;
_connection.SendData(packet);
}
@@ -97,7 +97,9 @@ namespace EonaCat.Quic.Streams
public byte[] Receive()
{
if (Type == StreamType.ClientUnidirectional)
+ {
throw new StreamException("Cannot receive data on unidirectional stream.");
+ }
while (!IsStreamFull() || State == StreamState.Receive)
{
@@ -115,7 +117,7 @@ namespace EonaCat.Quic.Streams
_data.Clear();
}
- public void SetMaximumStreamData(UInt64 maximumData)
+ public void SetMaximumStreamData(ulong maximumData)
{
_maximumStreamData = maximumData;
}
@@ -123,10 +125,14 @@ namespace EonaCat.Quic.Streams
public bool CanSendData()
{
if (Type == StreamType.ServerUnidirectional || Type == StreamType.ClientUnidirectional)
+ {
return false;
+ }
if (State == StreamState.Receive || State == StreamState.SizeKnown)
+ {
return true;
+ }
return false;
}
@@ -134,7 +140,9 @@ namespace EonaCat.Quic.Streams
public bool IsOpen()
{
if (State == StreamState.DataReceived || State == StreamState.ResetReceived)
+ {
return false;
+ }
return true;
}
@@ -143,7 +151,9 @@ namespace EonaCat.Quic.Streams
{
// Do not accept data if the stream is reset.
if (State == StreamState.ResetReceived)
+ {
return;
+ }
byte[] data = frame.StreamData;
if (frame.Offset != null)
@@ -158,9 +168,11 @@ namespace EonaCat.Quic.Streams
// Either this frame marks the end of the stream,
// or fin frame came before the data frames
if (frame.EndOfStream)
+ {
State = StreamState.SizeKnown;
+ }
- _currentTransferRate += (UInt64)data.Length;
+ _currentTransferRate += (ulong)data.Length;
// Terminate connection if maximum stream data is reached
if (_currentTransferRate >= _maximumStreamData)
@@ -187,14 +199,16 @@ namespace EonaCat.Quic.Streams
private bool IsStreamFull()
{
- UInt64 length = 0;
+ ulong length = 0;
foreach (var kvp in _data)
{
if (kvp.Key > 0 && kvp.Key != length)
+ {
return false;
+ }
- length += (UInt64)kvp.Value.Length;
+ length += (ulong)kvp.Value.Length;
}
return true;
diff --git a/EonaCat.Network/System/Sockets/RemoteInfo.cs b/EonaCat.Network/System/Sockets/RemoteInfo.cs
index 8cbf966..26508b7 100644
--- a/EonaCat.Network/System/Sockets/RemoteInfo.cs
+++ b/EonaCat.Network/System/Sockets/RemoteInfo.cs
@@ -1,4 +1,7 @@
-using System.Net;
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System.Net;
using System.Net.Sockets;
namespace EonaCat.Network
@@ -17,4 +20,4 @@ namespace EonaCat.Network
public string ClientId { get; internal set; }
public string ClientName { get; internal set; }
}
-}
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs b/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs
index af498b3..d46a65f 100644
--- a/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs
+++ b/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs
@@ -1,12 +1,10 @@
using System;
-using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace EonaCat.Network
{
-
public class SocketTcpClient
{
private const int BUFFER_SIZE = 4096;
@@ -44,7 +42,7 @@ namespace EonaCat.Network
{
if (!IPAddress.TryParse(ipAddress, out IPAddress ip))
{
- throw new Exception("Invalid ipAddress given");
+ throw new Exception("EonaCat Network: Invalid ipAddress given");
}
return CreateSocketTcpClientAsync(ip, port);
@@ -121,4 +119,4 @@ namespace EonaCat.Network
socket.Close();
}
}
-}
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs
index 62811a8..75297b1 100644
--- a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs
+++ b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs
@@ -1,8 +1,8 @@
-using System.Net.Sockets;
+using System;
using System.Net;
-using System;
-using System.Threading.Tasks;
+using System.Net.Sockets;
using System.Threading;
+using System.Threading.Tasks;
namespace EonaCat.Network
{
diff --git a/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs b/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs
index cbb52f1..b2b6949 100644
--- a/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs
+++ b/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs
@@ -1,8 +1,8 @@
-using System.Net.Sockets;
+using System;
using System.Net;
-using System.Threading.Tasks;
-using System;
+using System.Net.Sockets;
using System.Threading;
+using System.Threading.Tasks;
namespace EonaCat.Network
{
@@ -112,4 +112,4 @@ namespace EonaCat.Network
_udpClient.Close();
}
}
-}
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs
index 263723f..d0c55ed 100644
--- a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs
+++ b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs
@@ -1,10 +1,10 @@
-using System.Net.Sockets;
+using System;
using System.Net;
-using System;
-using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
-using System.Threading.Tasks;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
using System.Threading;
+using System.Threading.Tasks;
namespace EonaCat.Network
{
@@ -57,7 +57,7 @@ namespace EonaCat.Network
{
if (!IPAddress.TryParse(ipAddress, out IPAddress ip))
{
- throw new Exception("Invalid ipAddress given");
+ throw new Exception("EonaCat Network: Invalid ipAddress given");
}
CreateUdpServer(ip, port);
@@ -148,5 +148,4 @@ namespace EonaCat.Network
}
}
}
-
-}
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/ByteOrder.cs b/EonaCat.Network/System/Sockets/Web/ByteOrder.cs
new file mode 100644
index 0000000..5e784d6
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/ByteOrder.cs
@@ -0,0 +1,12 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+namespace EonaCat.Network
+{
+ public enum ByteOrder
+ {
+ Little,
+
+ Big
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/CloseStatusCode.cs b/EonaCat.Network/System/Sockets/Web/CloseStatusCode.cs
new file mode 100644
index 0000000..01ffeae
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/CloseStatusCode.cs
@@ -0,0 +1,34 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+namespace EonaCat.Network
+{
+ public enum CloseStatusCode : ushort
+ {
+ Normal = 1000,
+
+ Away = 1001,
+
+ ProtocolError = 1002,
+
+ UnsupportedData = 1003,
+
+ Undefined = 1004,
+
+ NoStatus = 1005,
+
+ Abnormal = 1006,
+
+ InvalidData = 1007,
+
+ PolicyViolation = 1008,
+
+ TooBig = 1009,
+
+ MandatoryExtension = 1010,
+
+ ServerError = 1011,
+
+ TlsHandshakeFailure = 1015
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/CompressionMethod.cs b/EonaCat.Network/System/Sockets/Web/CompressionMethod.cs
new file mode 100644
index 0000000..ada0444
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/CompressionMethod.cs
@@ -0,0 +1,12 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+namespace EonaCat.Network
+{
+ public enum CompressionMethod : byte
+ {
+ None,
+
+ Deflate
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs
new file mode 100644
index 0000000..2ca1941
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs
@@ -0,0 +1,129 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Specialized;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents the base class for authentication in the EonaCat network library.
+ ///
+ internal abstract class AuthenticationBase
+ {
+ ///
+ /// Gets or sets the collection of authentication parameters.
+ ///
+ internal NameValueCollection Parameters;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ protected AuthenticationBase(AuthenticationSchemes scheme, NameValueCollection parameters)
+ {
+ Scheme = scheme;
+ Parameters = parameters;
+ }
+
+ ///
+ /// Gets the algorithm used for authentication.
+ ///
+ public string Algorithm => Parameters["algorithm"];
+
+ ///
+ /// Gets the nonce value for authentication.
+ ///
+ public string Nonce => Parameters["nonce"];
+
+ ///
+ /// Gets the opaque value for authentication.
+ ///
+ public string Opaque => Parameters["opaque"];
+
+ ///
+ /// Gets the quality of protection for authentication.
+ ///
+ public string Qop => Parameters["qop"];
+
+ ///
+ /// Gets the realm for authentication.
+ ///
+ public string Realm => Parameters["realm"];
+
+ ///
+ /// Gets the authentication scheme.
+ ///
+ public AuthenticationSchemes Scheme { get; }
+
+ ///
+ /// Creates a nonce value for authentication.
+ ///
+ /// A string representing the generated nonce value.
+ internal static string CreateNonceValue()
+ {
+ var buffer = new byte[16];
+ var random = new Random();
+ random.NextBytes(buffer);
+
+ var result = new StringBuilder(32);
+ foreach (var currentByte in buffer)
+ {
+ result.Append(currentByte.ToString("x2"));
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Parses the authentication parameters from the specified string value.
+ ///
+ /// The string containing authentication parameters.
+ /// A collection of authentication parameters.
+ internal static NameValueCollection ParseParameters(string value)
+ {
+ var res = new NameValueCollection();
+ foreach (var param in value.SplitHeaderValue(','))
+ {
+ var i = param.IndexOf('=');
+ var name = i > 0 ? param.Substring(0, i).Trim() : null;
+ var val = i < 0
+ ? param.Trim().Trim('"')
+ : i < param.Length - 1
+ ? param.Substring(i + 1).Trim().Trim('"')
+ : string.Empty;
+
+ res.Add(name, val);
+ }
+
+ return res;
+ }
+
+ ///
+ /// Gets the authentication string for Basic authentication.
+ ///
+ /// A string representing the Basic authentication.
+ internal abstract string ToBasicString();
+
+ ///
+ /// Gets the authentication string for Digest authentication.
+ ///
+ /// A string representing the Digest authentication.
+ internal abstract string ToDigestString();
+
+ ///
+ /// Returns a string representation of the authentication information.
+ ///
+ /// A string representation of the authentication information.
+ public override string ToString()
+ {
+ return Scheme == AuthenticationSchemes.Basic
+ ? ToBasicString()
+ : Scheme == AuthenticationSchemes.Digest
+ ? ToDigestString()
+ : string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs
new file mode 100644
index 0000000..7b98248
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs
@@ -0,0 +1,153 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System.Collections.Specialized;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an authentication challenge in the EonaCat network library.
+ ///
+ internal class AuthenticationChallenge : AuthenticationBase
+ {
+ private const string BASIC = "basic";
+ private const string DIGEST = "digest";
+ private const int DIGEST_SIZE = 128;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
+ : base(scheme, parameters)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class for Basic or Digest authentication.
+ ///
+ /// The authentication scheme.
+ /// The authentication realm.
+ internal AuthenticationChallenge(AuthenticationSchemes scheme, string realm)
+ : base(scheme, new NameValueCollection())
+ {
+ Parameters["realm"] = realm;
+ if (scheme == AuthenticationSchemes.Digest)
+ {
+ Parameters["nonce"] = CreateNonceValue();
+ Parameters["algorithm"] = "MD5";
+ Parameters["qop"] = "auth";
+ }
+ }
+
+ ///
+ /// Gets the domain for Digest authentication.
+ ///
+ public string Domain => Parameters["domain"];
+
+ ///
+ /// Gets the stale parameter for Digest authentication.
+ ///
+ public string Stale => Parameters["stale"];
+
+ ///
+ /// Creates a Basic authentication challenge with the specified realm.
+ ///
+ /// The authentication realm.
+ /// An instance of for Basic authentication.
+
+ internal static AuthenticationChallenge CreateBasicChallenge(string realm)
+ {
+ return new AuthenticationChallenge(AuthenticationSchemes.Basic, realm);
+ }
+
+ ///
+ /// Creates a Digest authentication challenge with the specified realm.
+ ///
+ /// The authentication realm.
+ /// An instance of for Digest authentication.
+ internal static AuthenticationChallenge CreateDigestChallenge(string realm)
+ {
+ return new AuthenticationChallenge(AuthenticationSchemes.Digest, realm);
+ }
+
+ ///
+ /// Parses an authentication challenge from the specified string value.
+ ///
+ /// The string containing the authentication challenge.
+ /// An instance of if successful; otherwise, null.
+ internal static AuthenticationChallenge Parse(string value)
+ {
+ var challenge = value.Split(new[] { ' ' }, 2);
+ if (challenge.Length != 2)
+ {
+ return null;
+ }
+
+ var scheme = challenge[0].ToLower();
+ return scheme == BASIC
+ ? new AuthenticationChallenge(
+ AuthenticationSchemes.Basic, ParseParameters(challenge[1]))
+ : scheme == DIGEST
+ ? new AuthenticationChallenge(
+ AuthenticationSchemes.Digest, ParseParameters(challenge[1]))
+ : null;
+ }
+
+ ///
+ /// Gets the Basic authentication string representation of the authentication challenge.
+ ///
+ /// A string representing the Basic authentication challenge.
+ internal override string ToBasicString()
+ {
+ return string.Format($"Basic realm=\"{{0}}\"", Parameters["realm"]);
+ }
+
+ ///
+ /// Gets the Digest authentication string representation of the authentication challenge.
+ ///
+ /// A string representing the Digest authentication challenge.
+ internal override string ToDigestString()
+ {
+ var output = new StringBuilder(DIGEST_SIZE);
+
+ var domain = Parameters["domain"];
+ if (domain != null)
+ {
+ output.AppendFormat($"Digest realm=\"{Parameters["realm"]}\", domain=\"{domain}\", nonce=\"{Parameters["nonce"]}\"");
+ }
+ else
+ {
+ output.AppendFormat($"Digest realm=\"{Parameters["realm"]}\", nonce=\"{Parameters["nonce"]}\"");
+ }
+
+ var opaque = Parameters["opaque"];
+ if (opaque != null)
+ {
+ output.AppendFormat($", opaque=\"{opaque}\"");
+ }
+
+ var stale = Parameters["stale"];
+ if (stale != null)
+ {
+ output.AppendFormat($", stale={stale}");
+ }
+
+ var algorithm = Parameters["algorithm"];
+ if (algorithm != null)
+ {
+ output.AppendFormat($", algorithm={algorithm}");
+ }
+
+ var qop = Parameters["qop"];
+ if (qop != null)
+ {
+ output.AppendFormat($", qop=\"{qop}\"");
+ }
+
+ return output.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs
new file mode 100644
index 0000000..0bdf2d5
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs
@@ -0,0 +1,357 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Specialized;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an authentication response in the EonaCat network library.
+ ///
+ internal class AuthenticationResponse : AuthenticationBase
+ {
+ private const string BASIC = "basic";
+ private const string DIGEST = "digest";
+ private uint _nonceCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
+ : base(scheme, parameters)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class for Basic authentication.
+ ///
+ /// The network credentials.
+ internal AuthenticationResponse(NetworkCredential credentials)
+ : this(AuthenticationSchemes.Basic, new NameValueCollection(), credentials, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class for Digest authentication.
+ ///
+ /// The authentication challenge.
+ /// The network credentials.
+ /// The nonce count.
+ internal AuthenticationResponse(
+ AuthenticationChallenge challenge, NetworkCredential credentials, uint nonceCount)
+ : this(challenge.Scheme, challenge.Parameters, credentials, nonceCount)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ /// The network credentials.
+ /// The nonce count.
+ internal AuthenticationResponse(
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters,
+ NetworkCredential credentials,
+ uint nonceCount)
+ : base(scheme, parameters)
+ {
+ Parameters["username"] = credentials.Username;
+ Parameters["password"] = credentials.Password;
+ Parameters["uri"] = credentials.Domain;
+ _nonceCount = nonceCount;
+ if (scheme == AuthenticationSchemes.Digest)
+ {
+ InitAsDigest();
+ }
+ }
+
+ ///
+ /// Gets the nonce count.
+ ///
+ internal uint NonceCount => _nonceCount < uint.MaxValue
+ ? _nonceCount
+ : 0;
+
+ ///
+ /// Gets the cnonce value for Digest authentication.
+ ///
+ public string Cnonce => Parameters["cnonce"];
+
+ ///
+ /// Gets the nonce count for Digest authentication.
+ ///
+ public string Nc => Parameters["nc"];
+
+ ///
+ /// Gets the password for authentication.
+ ///
+ public string Password => Parameters["password"];
+
+ ///
+ /// Gets the response value for authentication.
+ ///
+ public string Response => Parameters["response"];
+
+ ///
+ /// Gets the URI for authentication.
+ ///
+ public string Uri => Parameters["uri"];
+
+ ///
+ /// Gets the username for authentication.
+ ///
+ public string UserName => Parameters["username"];
+
+ ///
+ /// Creates the A1 value for Digest authentication.
+ ///
+ /// The username.
+ /// The password.
+ /// The realm.
+ /// The A1 value.
+ private static string CreateA1(string username, string password, string realm)
+ {
+ return $"{username}:{realm}:{password}";
+ }
+
+ ///
+ /// Creates the A1 value for Digest authentication with cnonce and nonce.
+ ///
+ /// The username.
+ /// The password.
+ /// The realm.
+ /// The nonce.
+ /// The cnonce.
+ /// The A1 value.
+ private static string CreateA1(
+ string username, string password, string realm, string nonce, string cnonce)
+ {
+ return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
+ }
+
+ ///
+ /// Creates the A2 value for Digest authentication.
+ ///
+ /// The HTTP method.
+ /// The URI.
+ /// The A2 value.
+ private static string CreateA2(string method, string uri)
+ {
+ return $"{method}:{uri}";
+ }
+
+ ///
+ /// Creates the A2 value for Digest authentication with an entity.
+ ///
+ /// The HTTP method.
+ /// The URI.
+ /// The entity.
+ /// The A2 value.
+ private static string CreateA2(string method, string uri, string entity)
+ {
+ return $"{method}:{uri}:{Hash(entity)}";
+ }
+
+ ///
+ /// Computes the MD5 hash of the given value.
+ ///
+ /// The value to hash.
+ /// The MD5 hash.
+ private static string Hash(string value)
+ {
+ var source = Encoding.UTF8.GetBytes(value);
+ var md5 = MD5.Create();
+ var hashed = md5.ComputeHash(source);
+
+ var result = new StringBuilder(64);
+ foreach (var currentByte in hashed)
+ {
+ result.Append(currentByte.ToString("x2"));
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Initializes the authentication as Digest.
+ ///
+ private void InitAsDigest()
+ {
+ var qops = Parameters["qop"];
+ if (qops != null)
+ {
+ if (qops.Split(',').Contains(qop => qop.Trim().ToLower() == "auth"))
+ {
+ Parameters["qop"] = "auth";
+ Parameters["cnonce"] = CreateNonceValue();
+ Parameters["nc"] = string.Format("{0:x8}", ++_nonceCount);
+ }
+ else
+ {
+ Parameters["qop"] = null;
+ }
+ }
+
+ Parameters["method"] = "GET";
+ Parameters["response"] = CreateRequestDigest(Parameters);
+ }
+
+ ///
+ /// Creates the request digest for Digest authentication.
+ ///
+ /// The authentication parameters.
+ /// The request digest.
+ internal static string CreateRequestDigest(NameValueCollection parameters)
+ {
+ var user = parameters["username"];
+ var pass = parameters["password"];
+ var realm = parameters["realm"];
+ var nonce = parameters["nonce"];
+ var uri = parameters["uri"];
+ var algo = parameters["algorithm"];
+ var qop = parameters["qop"];
+ var cnonce = parameters["cnonce"];
+ var nc = parameters["nc"];
+ var method = parameters["method"];
+
+ var a1 = algo != null && algo.ToLower() == "md5-sess"
+ ? CreateA1(user, pass, realm, nonce, cnonce)
+ : CreateA1(user, pass, realm);
+
+ var a2 = qop != null && qop.ToLower() == "auth-int"
+ ? CreateA2(method, uri, parameters["entity"])
+ : CreateA2(method, uri);
+
+ var secret = Hash(a1);
+ var data = qop != null
+ ? string.Format("{0}:{1}:{2}:{3}:{4}", nonce, nc, cnonce, qop, Hash(a2))
+ : string.Format("{0}:{1}", nonce, Hash(a2));
+
+ return Hash(string.Format("{0}:{1}", secret, data));
+ }
+
+ ///
+ /// Parses an authentication response from the specified string value.
+ ///
+ /// The string containing the authentication response.
+ /// An instance of if successful; otherwise, null.
+ internal static AuthenticationResponse Parse(string value)
+ {
+ try
+ {
+ var cred = value.Split(new[] { ' ' }, 2);
+ if (cred.Length != 2)
+ {
+ return null;
+ }
+
+ var scheme = cred[0].ToLower();
+ return scheme == BASIC
+ ? new AuthenticationResponse(
+ AuthenticationSchemes.Basic, ParseBasicCredentials(cred[1]))
+ : scheme == DIGEST
+ ? new AuthenticationResponse(
+ AuthenticationSchemes.Digest, ParseParameters(cred[1]))
+ : null;
+ }
+ catch
+ {
+ }
+
+ return null;
+ }
+
+ ///
+ /// Parses the basic credentials from the specified string value.
+ ///
+ /// The string containing basic credentials.
+ /// A collection of basic credentials.
+ internal static NameValueCollection ParseBasicCredentials(string value)
+ {
+ // Decode the basic-credentials (a Base64 encoded string).
+ var userPass = Encoding.Default.GetString(Convert.FromBase64String(value));
+
+ // The format is [\]:.
+ var i = userPass.IndexOf(':');
+ var user = userPass.Substring(0, i);
+ var pass = i < userPass.Length - 1 ? userPass.Substring(i + 1) : string.Empty;
+
+ // Check if 'domain' exists.
+ i = user.IndexOf('\\');
+ if (i > -1)
+ {
+ user = user.Substring(i + 1);
+ }
+
+ var res = new NameValueCollection();
+ res["username"] = user;
+ res["password"] = pass;
+
+ return res;
+ }
+
+ ///
+ /// Gets the Basic authentication string representation of the authentication response.
+ ///
+ /// A string representing the Basic authentication response.
+ internal override string ToBasicString()
+ {
+ var userPass = string.Format("{0}:{1}", Parameters["username"], Parameters["password"]);
+ var cred = Convert.ToBase64String(Encoding.UTF8.GetBytes(userPass));
+
+ return "Basic " + cred;
+ }
+
+ ///
+ /// Gets the Digest authentication string representation of the authentication response.
+ ///
+ /// A string representing the Digest authentication response.
+ internal override string ToDigestString()
+ {
+ var output = new StringBuilder(256);
+ output.AppendFormat($"Digest username=\"{Parameters["username"]}\", realm=\"{Parameters["realm"]}\", nonce=\"{Parameters["nonce"]}\", uri=\"{Parameters["uri"]}\", response=\"{Parameters["response"]}\"");
+
+ var opaque = Parameters["opaque"];
+ if (opaque != null)
+ {
+ output.AppendFormat($", opaque=\"{opaque}\"");
+ }
+
+ var algorithm = Parameters["algorithm"];
+ if (algorithm != null)
+ {
+ output.AppendFormat($", algorithm={algorithm}");
+ }
+
+ var qop = Parameters["qop"];
+ if (qop != null)
+ {
+ output.AppendFormat($", qop={qop}, cnonce=\"{Parameters["cnonce"]}\", nc={Parameters["nc"]}");
+ }
+
+ return output.ToString();
+ }
+
+ ///
+ /// Converts the authentication response to an identity.
+ ///
+ /// An instance of .
+ public IIdentity ToIdentity()
+ {
+ var scheme = Scheme;
+ return scheme == AuthenticationSchemes.Basic
+ ? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
+ : scheme == AuthenticationSchemes.Digest
+ ? new HttpDigestIdentity(Parameters)
+ : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationSchemes.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationSchemes.cs
new file mode 100644
index 0000000..be5cbc0
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationSchemes.cs
@@ -0,0 +1,31 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+namespace EonaCat.Network
+{
+ ///
+ /// Enumerates the possible authentication schemes in the EonaCat network library.
+ ///
+ public enum AuthenticationSchemes
+ {
+ ///
+ /// No authentication scheme.
+ ///
+ None,
+
+ ///
+ /// Digest authentication scheme.
+ ///
+ Digest = 1,
+
+ ///
+ /// Basic authentication scheme.
+ ///
+ Basic = 8,
+
+ ///
+ /// Anonymous authentication scheme.
+ ///
+ Anonymous = 0x8000
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs
new file mode 100644
index 0000000..ded296b
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs
@@ -0,0 +1,113 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents network credentials used for authentication in the EonaCat network library.
+ ///
+ public class NetworkCredential
+ {
+ private string _domain;
+ private static readonly string[] _noRoles;
+ private string _password;
+ private string[] _roles;
+
+ static NetworkCredential()
+ {
+ _noRoles = new string[0];
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified username and password.
+ ///
+ /// The username.
+ /// The password.
+ public NetworkCredential(string username, string password)
+ : this(username, password, null, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified username, password, domain, and roles.
+ ///
+ /// The username.
+ /// The password.
+ /// The domain.
+ /// The roles.
+ public NetworkCredential(
+ string username, string password, string domain, params string[] roles
+ )
+ {
+ if (username == null)
+ {
+ throw new ArgumentNullException(nameof(username));
+ }
+
+ if (username.Length == 0)
+ {
+ throw new ArgumentException("An empty string.", nameof(username));
+ }
+
+ Username = username;
+ _password = password;
+ _domain = domain;
+ _roles = roles;
+ }
+
+ ///
+ /// Gets or sets the domain associated with the network credentials.
+ ///
+ public string Domain
+ {
+ get
+ {
+ return _domain ?? string.Empty;
+ }
+
+ internal set
+ {
+ _domain = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the password associated with the network credentials.
+ ///
+ public string Password
+ {
+ get
+ {
+ return _password ?? string.Empty;
+ }
+
+ internal set
+ {
+ _password = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the roles associated with the network credentials.
+ ///
+ public string[] Roles
+ {
+ get
+ {
+ return _roles ?? _noRoles;
+ }
+
+ internal set
+ {
+ _roles = value;
+ }
+ }
+
+ ///
+ /// Gets the username associated with the network credentials.
+ ///
+ public string Username { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Chunks/Chunk.cs b/EonaCat.Network/System/Sockets/Web/Core/Chunks/Chunk.cs
new file mode 100644
index 0000000..3942cfb
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Chunks/Chunk.cs
@@ -0,0 +1,56 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a chunk of data in the EonaCat network library.
+ ///
+ internal class WebChunk
+ {
+ private readonly byte[] _data;
+ private int _offset;
+
+ ///
+ /// Initializes a new instance of the class with the specified data.
+ ///
+ /// The byte array representing the data.
+ public WebChunk(byte[] data)
+ {
+ _data = data;
+ }
+
+ ///
+ /// Gets the remaining bytes to read in the chunk.
+ ///
+ public int ReadLeft => _data.Length - _offset;
+
+ ///
+ /// Reads a specified number of bytes from the chunk into a buffer.
+ ///
+ /// The destination buffer.
+ /// The zero-based byte offset in the buffer at which to begin storing the data.
+ /// The maximum number of bytes to read.
+ /// The actual number of bytes read.
+ public int Read(byte[] buffer, int offset, int count)
+ {
+ var left = _data.Length - _offset;
+ if (left == 0)
+ {
+ return left;
+ }
+
+ if (count > left)
+ {
+ count = left;
+ }
+
+ Buffer.BlockCopy(_data, _offset, buffer, offset, count);
+ _offset += count;
+
+ return count;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs
new file mode 100644
index 0000000..a7a9e4a
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs
@@ -0,0 +1,398 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a stream for handling chunked data in the EonaCat network library.
+ ///
+ internal class ChunkStream
+ {
+ private int _chunkRead;
+ private int _chunkSize;
+ private readonly List _chunks;
+ private bool _foundSPCode;
+ private readonly StringBuilder _saved;
+ private bool _gotChunck;
+ private InputChunkState _state;
+ private int _trailerState;
+
+ ///
+ /// Initializes a new instance of the class with the specified headers.
+ ///
+ /// The web headers associated with the chunk stream.
+ public ChunkStream(WebHeaderCollection headers)
+ {
+ Headers = headers;
+ _chunkSize = -1;
+ _chunks = new List();
+ _saved = new StringBuilder();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified buffer, offset, count, and headers.
+ ///
+ /// The byte array containing chunked data.
+ /// The offset in the buffer at which to begin reading.
+ /// The number of bytes to read from the buffer.
+ /// The web headers associated with the chunk stream.
+ public ChunkStream(byte[] buffer, int offset, int count, WebHeaderCollection headers)
+ : this(headers)
+ {
+ Write(buffer, offset, count);
+ }
+
+ ///
+ /// Gets the web headers associated with the chunk stream.
+ ///
+ internal WebHeaderCollection Headers { get; }
+
+ ///
+ /// Gets the number of bytes left in the current chunk.
+ ///
+ public int ChunkLeft => _chunkSize - _chunkRead;
+
+ ///
+ /// Gets a value indicating whether more data is expected.
+ ///
+ public bool WantMore => _state != InputChunkState.End;
+
+ private int read(byte[] buffer, int offset, int count)
+ {
+ var nread = 0;
+
+ var cnt = _chunks.Count;
+ for (var i = 0; i < cnt; i++)
+ {
+ var chunk = _chunks[i];
+ if (chunk == null)
+ {
+ continue;
+ }
+
+ if (chunk.ReadLeft == 0)
+ {
+ _chunks[i] = null;
+ continue;
+ }
+
+ nread += chunk.Read(buffer, offset + nread, count - nread);
+ if (nread == count)
+ {
+ break;
+ }
+ }
+
+ return nread;
+ }
+
+ private static string RemoveChunkExtension(string value)
+ {
+ var index = value.IndexOf(';');
+ return index > -1 ? value.Substring(0, index) : value;
+ }
+
+ private InputChunkState SeekCrLf(byte[] buffer, ref int offset, int length)
+ {
+ if (!_gotChunck)
+ {
+ if (buffer[offset++] != 13)
+ {
+ ThrowProtocolViolation("CR is expected.");
+ }
+
+ _gotChunck = true;
+ if (offset == length)
+ {
+ return InputChunkState.DataEnded;
+ }
+ }
+
+ if (buffer[offset++] != 10)
+ {
+ ThrowProtocolViolation("LF is expected.");
+ }
+
+ return InputChunkState.None;
+ }
+
+ private InputChunkState SetChunkSize(byte[] buffer, ref int offset, int length)
+ {
+ byte currentByte = 0;
+ while (offset < length)
+ {
+ currentByte = buffer[offset++];
+ if (_gotChunck)
+ {
+ if (currentByte != 10)
+ {
+ ThrowProtocolViolation("LF is expected.");
+ }
+
+ break;
+ }
+
+ if (currentByte == 13)
+ {
+ _gotChunck = true;
+ continue;
+ }
+
+ if (currentByte == 10)
+ {
+ ThrowProtocolViolation("LF is unexpected.");
+ }
+
+ if (currentByte == 32) // SP
+ {
+ _foundSPCode = true;
+ }
+
+ if (!_foundSPCode)
+ {
+ _saved.Append((char)currentByte);
+ }
+
+ if (_saved.Length > 20)
+ {
+ ThrowProtocolViolation("The chunk size is too long.");
+ }
+ }
+
+ if (!_gotChunck || currentByte != 10)
+ {
+ return InputChunkState.None;
+ }
+
+ _chunkRead = 0;
+ try
+ {
+ _chunkSize = int.Parse(
+ RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
+ }
+ catch
+ {
+ ThrowProtocolViolation("The chunk size cannot be parsed.");
+ }
+
+ if (_chunkSize == 0)
+ {
+ _trailerState = 2;
+ return InputChunkState.Trailer;
+ }
+
+ return InputChunkState.Data;
+ }
+
+ private InputChunkState setTrailer(byte[] buffer, ref int offset, int length)
+ {
+ // Check if no trailer.
+ if (_trailerState == 2 && buffer[offset] == 13 && _saved.Length == 0)
+ {
+ offset++;
+ if (offset < length && buffer[offset] == 10)
+ {
+ offset++;
+ return InputChunkState.End;
+ }
+
+ offset--;
+ }
+
+ while (offset < length && _trailerState < 4)
+ {
+ var currentByte = buffer[offset++];
+ _saved.Append((char)currentByte);
+ if (_saved.Length > 4196)
+ {
+ ThrowProtocolViolation("The trailer is too long.");
+ }
+
+ if (_trailerState == 1 || _trailerState == 3)
+ {
+ if (currentByte != 10)
+ {
+ ThrowProtocolViolation("LF is expected.");
+ }
+
+ _trailerState++;
+ continue;
+ }
+
+ if (currentByte == 13)
+ {
+ _trailerState++;
+ continue;
+ }
+
+ if (currentByte == 10)
+ {
+ ThrowProtocolViolation("LF is unexpected.");
+ }
+
+ _trailerState = 0;
+ }
+
+ if (_trailerState < 4)
+ {
+ return InputChunkState.Trailer;
+ }
+
+ _saved.Length -= 2;
+ var reader = new StringReader(_saved.ToString());
+
+ string line;
+ while ((line = reader.ReadLine()) != null && line.Length > 0)
+ {
+ Headers.Add(line);
+ }
+
+ return InputChunkState.End;
+ }
+
+ private static void ThrowProtocolViolation(string message)
+ {
+ throw new WebException($"EonaCat Network: {message}", null, WebExceptionStatus.ServerProtocolViolation, null);
+ }
+
+ private void Write(byte[] buffer, ref int offset, int length)
+ {
+ if (_state == InputChunkState.End)
+ {
+ ThrowProtocolViolation("The chunks were ended.");
+ }
+
+ if (_state == InputChunkState.None)
+ {
+ _state = SetChunkSize(buffer, ref offset, length);
+ if (_state == InputChunkState.None)
+ {
+ return;
+ }
+
+ _saved.Length = 0;
+ _gotChunck = false;
+ _foundSPCode = false;
+ }
+
+ if (_state == InputChunkState.Data && offset < length)
+ {
+ _state = WriteData(buffer, ref offset, length);
+ if (_state == InputChunkState.Data)
+ {
+ return;
+ }
+ }
+
+ if (_state == InputChunkState.DataEnded && offset < length)
+ {
+ _state = SeekCrLf(buffer, ref offset, length);
+ if (_state == InputChunkState.DataEnded)
+ {
+ return;
+ }
+
+ _gotChunck = false;
+ }
+
+ if (_state == InputChunkState.Trailer && offset < length)
+ {
+ _state = setTrailer(buffer, ref offset, length);
+ if (_state == InputChunkState.Trailer)
+ {
+ return;
+ }
+
+ _saved.Length = 0;
+ }
+
+ if (offset < length)
+ {
+ Write(buffer, ref offset, length);
+ }
+ }
+
+ private InputChunkState WriteData(byte[] buffer, ref int offset, int length)
+ {
+ var cnt = length - offset;
+ var left = _chunkSize - _chunkRead;
+ if (cnt > left)
+ {
+ cnt = left;
+ }
+
+ var data = new byte[cnt];
+ Buffer.BlockCopy(buffer, offset, data, 0, cnt);
+ _chunks.Add(new WebChunk(data));
+
+ offset += cnt;
+ _chunkRead += cnt;
+
+ return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
+ }
+
+ ///
+ /// Resets the internal buffer and state of the chunk stream.
+ ///
+ internal void ResetBuffer()
+ {
+ _chunkRead = 0;
+ _chunkSize = -1;
+ _chunks.Clear();
+ }
+
+ ///
+ /// Writes a specified amount of data to the chunk stream and reads it back.
+ ///
+ /// The byte array containing data to be written to the chunk stream.
+ /// The offset in the buffer at which to begin writing.
+ /// The number of bytes to write to the chunk stream.
+ /// The number of bytes to read back from the chunk stream.
+ /// The number of bytes read from the chunk stream.
+ internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
+ {
+ Write(buffer, offset, writeCount);
+ return Read(buffer, offset, readCount);
+ }
+
+ ///
+ /// Reads a specified amount of data from the chunk stream.
+ ///
+ /// The destination buffer.
+ /// The zero-based byte offset in the buffer at which to begin storing the data.
+ /// The maximum number of bytes to read.
+ /// The total number of bytes read into the buffer.
+ public int Read(byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ {
+ return 0;
+ }
+
+ return read(buffer, offset, count);
+ }
+
+ ///
+ /// Writes a specified amount of data to the chunk stream.
+ ///
+ /// The byte array containing data to be written to the chunk stream.
+ /// The offset in the buffer at which to begin writing.
+ /// The number of bytes to write to the chunk stream.
+ public void Write(byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ {
+ return;
+ }
+
+ Write(buffer, ref offset, offset + count);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs
new file mode 100644
index 0000000..e76eb43
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs
@@ -0,0 +1,208 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.IO;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a stream for handling chunked requests in the EonaCat network library.
+ ///
+ internal class ChunkedRequestStream : RequestStream
+ {
+ private const int _bufferLength = 8192;
+ private readonly HttpListenerContext _context;
+ private bool _disposed;
+ private bool _noMoreData;
+
+ ///
+ /// Initializes a new instance of the class with the specified stream, buffer, offset, count, and context.
+ ///
+ /// The underlying stream.
+ /// The byte array used for buffering.
+ /// The offset in the buffer at which to begin reading.
+ /// The maximum number of bytes to read.
+ /// The HTTP listener context.
+ internal ChunkedRequestStream(Stream stream, byte[] buffer, int offset, int count, HttpListenerContext context)
+ : base(stream, buffer, offset, count)
+ {
+ _context = context;
+ Decoder = new ChunkStream((WebHeaderCollection)context.Request.Headers);
+ }
+
+ ///
+ /// Gets or sets the chunk stream decoder associated with the chunked request stream.
+ ///
+ internal ChunkStream Decoder { get; set; }
+
+ private void OnRead(IAsyncResult asyncResult)
+ {
+ var readBufferState = (ReadBufferState)asyncResult.AsyncState;
+ var result = readBufferState.AsyncResult;
+ try
+ {
+ var nread = base.EndRead(asyncResult);
+ Decoder.Write(result.Buffer, result.Offset, nread);
+ nread = Decoder.Read(readBufferState.Buffer, readBufferState.Offset, readBufferState.Count);
+ readBufferState.Offset += nread;
+ readBufferState.Count -= nread;
+ if (readBufferState.Count == 0 || !Decoder.WantMore || nread == 0)
+ {
+ _noMoreData = !Decoder.WantMore && nread == 0;
+ result.Count = readBufferState.InitialCount - readBufferState.Count;
+ result.Complete();
+
+ return;
+ }
+
+ result.Offset = 0;
+ result.Count = Math.Min(_bufferLength, Decoder.ChunkLeft + 6);
+ base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
+ }
+ catch (Exception ex)
+ {
+ _context.Connection.SendError(ex.Message, 400);
+ result.Complete(ex);
+ }
+ }
+
+ ///
+ /// Begins an asynchronous read operation from the stream.
+ ///
+ /// The destination buffer.
+ /// The zero-based byte offset in the buffer at which to begin storing the data.
+ /// The maximum number of bytes to read.
+ /// An optional asynchronous callback, to be called when the read is complete.
+ /// A user-provided object that distinguishes this particular asynchronous read request from other requests.
+ /// An that represents the asynchronous read operation.
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "A negative value.");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "A negative value.");
+ }
+
+ var len = buffer.Length;
+ if (offset + count > len)
+ {
+ throw new ArgumentException(
+ "The sum of 'offset' and 'count' is greater than 'buffer' length.");
+ }
+
+ var result = new HttpStreamAsyncResult(callback, state);
+ if (_noMoreData)
+ {
+ result.Complete();
+ return result;
+ }
+
+ var nread = Decoder.Read(buffer, offset, count);
+ offset += nread;
+ count -= nread;
+ if (count == 0)
+ {
+ result.Count = nread;
+ result.Complete();
+
+ return result;
+ }
+
+ if (!Decoder.WantMore)
+ {
+ _noMoreData = nread == 0;
+ result.Count = nread;
+ result.Complete();
+
+ return result;
+ }
+
+ result.Buffer = new byte[_bufferLength];
+ result.Offset = 0;
+ result.Count = _bufferLength;
+
+ var readBufferState = new ReadBufferState(buffer, offset, count, result);
+ readBufferState.InitialCount += nread;
+ base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
+
+ return result;
+ }
+
+ ///
+ /// Closes the stream.
+ ///
+ public override void Close()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ base.Close();
+ }
+
+ ///
+ /// Ends an asynchronous read operation from the stream.
+ ///
+ /// The result of the asynchronous operation.
+ /// The number of bytes read from the stream, between zero (0) and the number of bytes you requested. Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available.
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+
+ if (asyncResult == null)
+ {
+ throw new ArgumentNullException(nameof(asyncResult));
+ }
+
+ if (asyncResult is not HttpStreamAsyncResult result)
+ {
+ throw new ArgumentException("A wrong IAsyncResult.", nameof(asyncResult));
+ }
+
+ if (!result.IsCompleted)
+ {
+ result.AsyncWaitHandle.WaitOne();
+ }
+
+ if (result.HasException)
+ {
+ throw new HttpListenerException(400, "I/O operation aborted.");
+ }
+
+ return result.Count;
+ }
+
+ ///
+ /// Reads a sequence of bytes from the stream and advances the position within the stream by the number of bytes read.
+ ///
+ /// The byte array to read data into.
+ /// The zero-based byte offset in buffer at which to begin storing the data.
+ /// The maximum number of bytes to be read from the stream.
+ /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ var result = BeginRead(buffer, offset, count, null, null);
+ return EndRead(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Collections/QueryStringCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Collections/QueryStringCollection.cs
new file mode 100644
index 0000000..692a5d1
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Collections/QueryStringCollection.cs
@@ -0,0 +1,41 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System.Collections.Specialized;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a collection of query string parameters in the EonaCat network library.
+ ///
+ internal sealed class QueryStringCollection : NameValueCollection
+ {
+ ///
+ /// Converts the collection to its string representation.
+ ///
+ /// A string representation of the query string parameters.
+ public override string ToString()
+ {
+ var count = Count;
+ if (count == 0)
+ {
+ return string.Empty;
+ }
+
+ var output = new StringBuilder();
+ var keys = AllKeys;
+ foreach (var key in keys)
+ {
+ output.AppendFormat($"{key}={this[key]}&");
+ }
+
+ if (output.Length > 0)
+ {
+ output.Length--;
+ }
+
+ return output.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs
new file mode 100644
index 0000000..f9c43ca
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs
@@ -0,0 +1,603 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a collection of HTTP headers in the EonaCat Network library.
+ ///
+ [Serializable]
+ [ComVisible(true)]
+ public class WebHeaderCollection : NameValueCollection, ISerializable
+ {
+ private static readonly Dictionary _headers;
+ private readonly bool _internallyUsed;
+ private HttpHeaderType _state;
+
+ static WebHeaderCollection()
+ {
+ _headers = new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ {
+ { "Accept", new HttpHeaderInfo("Accept", HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) },
+ { "AcceptCharset", new HttpHeaderInfo("Accept-Charset", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "AcceptEncoding", new HttpHeaderInfo("Accept-Encoding", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "AcceptLanguage", new HttpHeaderInfo("Accept-Language", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "AcceptRanges", new HttpHeaderInfo("Accept-Ranges", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Age", new HttpHeaderInfo("Age", HttpHeaderType.Response) },
+ { "Allow", new HttpHeaderInfo("Allow", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Authorization", new HttpHeaderInfo("Authorization", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "CacheControl", new HttpHeaderInfo("Cache-Control", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Connection", new HttpHeaderInfo("Connection", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) },
+ { "ContentEncoding", new HttpHeaderInfo("Content-Encoding", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "ContentLanguage", new HttpHeaderInfo("Content-Language", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "ContentLength", new HttpHeaderInfo("Content-Length", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) },
+ { "ContentLocation", new HttpHeaderInfo("Content-Location", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "ContentMd5", new HttpHeaderInfo("Content-MD5", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "ContentRange", new HttpHeaderInfo("Content-Range", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "ContentType", new HttpHeaderInfo("Content-Type", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) },
+ { "Cookie", new HttpHeaderInfo("Cookie", HttpHeaderType.Request) },
+ { "Cookie2", new HttpHeaderInfo("Cookie2", HttpHeaderType.Request) },
+ { "Date", new HttpHeaderInfo("Date", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) },
+ { "Expect", new HttpHeaderInfo("Expect", HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) },
+ { "Expires", new HttpHeaderInfo("Expires", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "ETag", new HttpHeaderInfo("ETag", HttpHeaderType.Response) },
+ { "From", new HttpHeaderInfo("From", HttpHeaderType.Request) },
+ { "Host", new HttpHeaderInfo("Host", HttpHeaderType.Request | HttpHeaderType.Restricted) },
+ { "IfMatch", new HttpHeaderInfo("If-Match", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "IfModifiedSince", new HttpHeaderInfo("If-Modified-Since", HttpHeaderType.Request | HttpHeaderType.Restricted) },
+ { "IfNoneMatch", new HttpHeaderInfo("If-None-Match", HttpHeaderType.Request | HttpHeaderType.MultiValue) },
+ { "IfRange", new HttpHeaderInfo("If-Range", HttpHeaderType.Request) },
+ { "IfUnmodifiedSince", new HttpHeaderInfo("If-Unmodified-Since", HttpHeaderType.Request) },
+ { "KeepAlive", new HttpHeaderInfo("Keep-Alive", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "LastModified", new HttpHeaderInfo("Last-Modified", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "Location", new HttpHeaderInfo("Location", HttpHeaderType.Response) },
+ { "MaxForwards", new HttpHeaderInfo("Max-Forwards", HttpHeaderType.Request) },
+ { "Pragma", new HttpHeaderInfo("Pragma", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "ProxyAuthenticate", new HttpHeaderInfo("Proxy-Authenticate", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "ProxyAuthorization", new HttpHeaderInfo("Proxy-Authorization", HttpHeaderType.Request) },
+ { "ProxyConnection", new HttpHeaderInfo("Proxy-Connection", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) },
+ { "Public", new HttpHeaderInfo("Public", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Range", new HttpHeaderInfo("Range", HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) },
+ { "Referer", new HttpHeaderInfo("Referer", HttpHeaderType.Request | HttpHeaderType.Restricted) },
+ { "RetryAfter", new HttpHeaderInfo("Retry-After", HttpHeaderType.Response) },
+ { "SecWebSocketAccept", new HttpHeaderInfo("Sec-WebSocket-Accept", HttpHeaderType.Response | HttpHeaderType.Restricted) },
+ { "SecWebSocketExtensions", new HttpHeaderInfo("Sec-WebSocket-Extensions", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValueInRequest) },
+ { "SecWebSocketKey", new HttpHeaderInfo("Sec-WebSocket-Key", HttpHeaderType.Request | HttpHeaderType.Restricted) },
+ { "SecWebSocketProtocol", new HttpHeaderInfo("Sec-WebSocket-Protocol", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValueInRequest) },
+ { "SecWebSocketVersion", new HttpHeaderInfo("Sec-WebSocket-Version", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValueInResponse) },
+ { "Server", new HttpHeaderInfo("Server", HttpHeaderType.Response) },
+ { "SetCookie", new HttpHeaderInfo("Set-Cookie", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "SetCookie2", new HttpHeaderInfo("Set-Cookie2", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Te", new HttpHeaderInfo("TE", HttpHeaderType.Request) },
+ { "Trailer", new HttpHeaderInfo("Trailer", HttpHeaderType.Request | HttpHeaderType.Response) },
+ { "TransferEncoding", new HttpHeaderInfo("Transfer-Encoding", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) },
+ { "Translate", new HttpHeaderInfo("Translate", HttpHeaderType.Request) },
+ { "Upgrade", new HttpHeaderInfo("Upgrade", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "UserAgent", new HttpHeaderInfo("User-Agent", HttpHeaderType.Request | HttpHeaderType.Restricted) },
+ { "Vary", new HttpHeaderInfo("Vary", HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Via", new HttpHeaderInfo("Via", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "Warning", new HttpHeaderInfo("Warning", HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) },
+ { "WwwAuthenticate", new HttpHeaderInfo("WWW-Authenticate", HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) }
+ };
+ }
+
+ ///
+ /// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
+ ///
+ /// The HTTP header type.
+ /// A boolean indicating whether the collection is internally used.
+ internal WebHeaderCollection(HttpHeaderType state, bool internallyUsed)
+ {
+ _state = state;
+ _internallyUsed = internallyUsed;
+ }
+
+ ///
+ /// Initializes a new instance of the WebHeaderCollection class with serialization information.
+ ///
+ /// The SerializationInfo containing the data needed to serialize the WebHeaderCollection.
+ /// The StreamingContext containing the source and destination of the serialized stream associated with the WebHeaderCollection.
+ protected WebHeaderCollection(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ if (serializationInfo == null)
+ {
+ throw new ArgumentNullException(nameof(serializationInfo));
+ }
+
+ try
+ {
+ _internallyUsed = serializationInfo.GetBoolean("InternallyUsed");
+ _state = (HttpHeaderType)serializationInfo.GetInt32("State");
+
+ var cnt = serializationInfo.GetInt32("Count");
+ for (var i = 0; i < cnt; i++)
+ {
+ base.Add(
+ serializationInfo.GetString(i.ToString()),
+ serializationInfo.GetString((cnt + i).ToString()));
+ }
+ }
+ catch (SerializationException ex)
+ {
+ throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
+ }
+ }
+
+
+ public WebHeaderCollection()
+ {
+ }
+
+ internal HttpHeaderType State => _state;
+
+ public override string[] AllKeys => base.AllKeys;
+
+ public override int Count => base.Count;
+
+ public string this[HttpRequestHeader header]
+ {
+ get
+ {
+ return Get(Convert(header));
+ }
+
+ set
+ {
+ Add(header, value);
+ }
+ }
+
+ public string this[HttpResponseHeader header]
+ {
+ get
+ {
+ return Get(Convert(header));
+ }
+
+ set
+ {
+ Add(header, value);
+ }
+ }
+
+ public override KeysCollection Keys => base.Keys;
+
+ private void add(string name, string value, bool ignoreRestricted)
+ {
+ var act = ignoreRestricted
+ ? (Action)addWithoutCheckingNameAndRestricted
+ : addWithoutCheckingName;
+
+ DoWithCheckingState(act, CheckName(name), value, true);
+ }
+
+ private void addWithoutCheckingName(string name, string value)
+ {
+ DoWithoutCheckingName(base.Add, name, value);
+ }
+
+ private void addWithoutCheckingNameAndRestricted(string name, string value)
+ {
+ base.Add(name, CheckValue(value));
+ }
+
+ private static int checkColonSeparated(string header)
+ {
+ var idx = header.IndexOf(':');
+ if (idx == -1)
+ {
+ throw new ArgumentException("No colon could be found.", nameof(header));
+ }
+
+ return idx;
+ }
+
+ private static HttpHeaderType CheckHeaderType(string name)
+ {
+ var info = GetHeaderInfo(name);
+ return info == null
+ ? HttpHeaderType.Unspecified
+ : info.IsRequest && !info.IsResponse
+ ? HttpHeaderType.Request
+ : !info.IsRequest && info.IsResponse
+ ? HttpHeaderType.Response
+ : HttpHeaderType.Unspecified;
+ }
+
+ private static string CheckName(string name)
+ {
+ if (name == null || name.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ name = name.Trim();
+ if (!IsHeaderName(name))
+ {
+ throw new ArgumentException("Contains invalid characters.", nameof(name));
+ }
+
+ return name;
+ }
+
+ private void CheckRestricted(string name)
+ {
+ if (!_internallyUsed && isRestricted(name, true))
+ {
+ throw new ArgumentException("This header must be modified with the appropiate property.");
+ }
+ }
+
+ private void CheckState(bool response)
+ {
+ if (_state == HttpHeaderType.Unspecified)
+ {
+ return;
+ }
+
+ if (response && _state == HttpHeaderType.Request)
+ {
+ throw new InvalidOperationException(
+ "This collection has already been used to store the request headers.");
+ }
+
+ if (!response && _state == HttpHeaderType.Response)
+ {
+ throw new InvalidOperationException(
+ "This collection has already been used to store the response headers.");
+ }
+ }
+
+ private static string CheckValue(string value)
+ {
+ if (value == null || value.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ value = value.Trim();
+ if (value.Length > 65535)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Greater than 65,535 characters.");
+ }
+
+ if (!IsHeaderValue(value))
+ {
+ throw new ArgumentException("Contains invalid characters.", nameof(value));
+ }
+
+ return value;
+ }
+
+ private static string Convert(string key)
+ {
+ HttpHeaderInfo info;
+ return _headers.TryGetValue(key, out info) ? info.Name : string.Empty;
+ }
+
+ private void DoWithCheckingState(
+ Action action, string name, string value, bool setState)
+ {
+ var type = CheckHeaderType(name);
+ if (type == HttpHeaderType.Request)
+ {
+ DoWithCheckingState(action, name, value, false, setState);
+ }
+ else if (type == HttpHeaderType.Response)
+ {
+ DoWithCheckingState(action, name, value, true, setState);
+ }
+ else
+ {
+ action(name, value);
+ }
+ }
+
+ private void DoWithCheckingState(
+ Action action, string name, string value, bool response, bool setState)
+ {
+ CheckState(response);
+ action(name, value);
+ if (setState && _state == HttpHeaderType.Unspecified)
+ {
+ _state = response ? HttpHeaderType.Response : HttpHeaderType.Request;
+ }
+ }
+
+ private void DoWithoutCheckingName(Action action, string name, string value)
+ {
+ CheckRestricted(name);
+ action(name, CheckValue(value));
+ }
+
+ private static HttpHeaderInfo GetHeaderInfo(string name)
+ {
+ foreach (var info in _headers.Values)
+ {
+ if (info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return info;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool isRestricted(string name, bool response)
+ {
+ var info = GetHeaderInfo(name);
+ return info != null && info.IsRestricted(response);
+ }
+
+ private void removeWithoutCheckingName(string name, string unuse)
+ {
+ CheckRestricted(name);
+ base.Remove(name);
+ }
+
+ private void setWithoutCheckingName(string name, string value)
+ {
+ DoWithoutCheckingName(base.Set, name, value);
+ }
+
+ ///
+ /// Converts the specified HttpRequestHeader to a string.
+ ///
+ /// The HttpRequestHeader to convert.
+ /// A string representing the converted HttpRequestHeader.
+ internal static string Convert(HttpRequestHeader header)
+ {
+ return Convert(header.ToString());
+ }
+
+ internal static string Convert(HttpResponseHeader header)
+ {
+ return Convert(header.ToString());
+ }
+
+ internal void InternalRemove(string name)
+ {
+ base.Remove(name);
+ }
+
+ internal void InternalSet(string header, bool response)
+ {
+ var pos = checkColonSeparated(header);
+ InternalSet(header.Substring(0, pos), header.Substring(pos + 1), response);
+ }
+
+ internal void InternalSet(string name, string value, bool response)
+ {
+ value = CheckValue(value);
+ if (IsMultiValue(name, response))
+ {
+ base.Add(name, value);
+ }
+ else
+ {
+ base.Set(name, value);
+ }
+ }
+
+ internal static bool IsHeaderName(string name)
+ {
+ return name != null && name.Length > 0 && name.IsToken();
+ }
+
+ internal static bool IsHeaderValue(string value)
+ {
+ return value.IsText();
+ }
+
+ internal static bool IsMultiValue(string headerName, bool response)
+ {
+ if (headerName == null || headerName.Length == 0)
+ {
+ return false;
+ }
+
+ var info = GetHeaderInfo(headerName);
+ return info != null && info.IsMultiValue(response);
+ }
+
+ internal string ToStringMultiValue(bool response)
+ {
+ var buff = new StringBuilder();
+ Count.Times(
+ i =>
+ {
+ var key = GetKey(i);
+ if (IsMultiValue(key, response))
+ {
+ foreach (var val in GetValues(i))
+ {
+ buff.AppendFormat($"{key}: {val}\r\n");
+ }
+ }
+ else
+ {
+ buff.AppendFormat($"{key}: {Get(i)}\r\n");
+ }
+ });
+
+ return buff.Append("\r\n").ToString();
+ }
+
+ protected void AddWithoutValidate(string headerName, string headerValue)
+ {
+ add(headerName, headerValue, true);
+ }
+
+ public void Add(string header)
+ {
+ if (header == null || header.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(header));
+ }
+
+ var pos = checkColonSeparated(header);
+ add(header.Substring(0, pos), header.Substring(pos + 1), false);
+ }
+
+ public void Add(HttpRequestHeader header, string value)
+ {
+ DoWithCheckingState(addWithoutCheckingName, Convert(header), value, false, true);
+ }
+
+ public void Add(HttpResponseHeader header, string value)
+ {
+ DoWithCheckingState(addWithoutCheckingName, Convert(header), value, true, true);
+ }
+
+ public override void Add(string name, string value)
+ {
+ add(name, value, false);
+ }
+
+ public override void Clear()
+ {
+ base.Clear();
+ _state = HttpHeaderType.Unspecified;
+ }
+
+ public override string Get(int index)
+ {
+ return base.Get(index);
+ }
+
+ public override string Get(string name)
+ {
+ return base.Get(name);
+ }
+
+ public override IEnumerator GetEnumerator()
+ {
+ return base.GetEnumerator();
+ }
+
+ public override string GetKey(int index)
+ {
+ return base.GetKey(index);
+ }
+
+ public override string[] GetValues(int index)
+ {
+ var vals = base.GetValues(index);
+ return vals != null && vals.Length > 0 ? vals : null;
+ }
+
+ public override string[] GetValues(string header)
+ {
+ var vals = base.GetValues(header);
+ return vals != null && vals.Length > 0 ? vals : null;
+ }
+
+ [SecurityPermission(
+ SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
+ public override void GetObjectData(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ if (serializationInfo == null)
+ {
+ throw new ArgumentNullException(nameof(serializationInfo));
+ }
+
+ serializationInfo.AddValue("InternallyUsed", _internallyUsed);
+ serializationInfo.AddValue("State", (int)_state);
+
+ var cnt = Count;
+ serializationInfo.AddValue("Count", cnt);
+ cnt.Times(
+ i =>
+ {
+ serializationInfo.AddValue(i.ToString(), GetKey(i));
+ serializationInfo.AddValue((cnt + i).ToString(), Get(i));
+ });
+ }
+
+ public static bool IsRestricted(string headerName)
+ {
+ return isRestricted(CheckName(headerName), false);
+ }
+
+ public static bool IsRestricted(string headerName, bool response)
+ {
+ return isRestricted(CheckName(headerName), response);
+ }
+
+ public override void OnDeserialization(object sender)
+ {
+ }
+
+ public void Remove(HttpRequestHeader header)
+ {
+ DoWithCheckingState(removeWithoutCheckingName, Convert(header), null, false, false);
+ }
+
+ public void Remove(HttpResponseHeader header)
+ {
+ DoWithCheckingState(removeWithoutCheckingName, Convert(header), null, true, false);
+ }
+
+ public override void Remove(string name)
+ {
+ DoWithCheckingState(removeWithoutCheckingName, CheckName(name), null, false);
+ }
+
+ public void Set(HttpRequestHeader header, string value)
+ {
+ DoWithCheckingState(setWithoutCheckingName, Convert(header), value, false, true);
+ }
+
+ public void Set(HttpResponseHeader header, string value)
+ {
+ DoWithCheckingState(setWithoutCheckingName, Convert(header), value, true, true);
+ }
+
+ public override void Set(string name, string value)
+ {
+ DoWithCheckingState(setWithoutCheckingName, CheckName(name), value, true);
+ }
+
+ ///
+ /// Returns a byte array representing the WebHeaderCollection in UTF-8 encoding.
+ ///
+ /// A byte array representing the WebHeaderCollection.
+ public byte[] ToByteArray()
+ {
+ return Encoding.UTF8.GetBytes(ToString());
+ }
+
+ ///
+ /// Returns a string representation of the WebHeaderCollection.
+ ///
+ /// A string representing the WebHeaderCollection.
+ public override string ToString()
+ {
+ var buff = new StringBuilder();
+ Count.Times(i => buff.AppendFormat($"{GetKey(i)}: {Get(i)}\r\n"));
+
+ return buff.Append("\r\n").ToString();
+ }
+
+ [SecurityPermission(
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter,
+ SerializationFormatter = true)]
+ void ISerializable.GetObjectData(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ GetObjectData(serializationInfo, streamingContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWebSocketContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWebSocketContext.cs
new file mode 100644
index 0000000..11d4e91
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWebSocketContext.cs
@@ -0,0 +1,111 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Security.Principal;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents the context of a WebSocket connection within an .
+ ///
+ ///
+ /// This class provides access to various properties and methods for interacting with the WebSocket connection
+ /// within the context of an HTTP request handled by an .
+ ///
+ ///
+ public class HttpListenerWebSocketContext : WebSocketContext
+ {
+ private readonly HttpListenerContext _context;
+ private readonly WebSocket _websocket;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The associated with the WebSocket connection.
+ /// The WebSocket protocol negotiated during the connection.
+ internal HttpListenerWebSocketContext(HttpListenerContext context, string protocol)
+ {
+ _context = context;
+ _websocket = new WebSocket(this, protocol);
+ }
+
+ ///
+ /// Gets the stream of the underlying TCP connection.
+ ///
+ internal Stream Stream => _context.Connection.Stream;
+
+ public override CookieCollection CookieCollection => _context.Request.Cookies;
+
+ public override NameValueCollection Headers => _context.Request.Headers;
+
+ public override string Host => _context.Request.Headers["Host"];
+
+ public override bool IsAuthenticated => _context.User != null;
+
+ public override bool IsLocal => _context.Request.IsLocal;
+
+ public override bool IsSecureConnection => _context.Connection.IsSecure;
+
+ public override bool IsWebSocketRequest => _context.Request.IsWebSocketRequest;
+
+ public override string Origin => _context.Request.Headers["Origin"];
+
+ public override NameValueCollection QueryString => _context.Request.QueryString;
+
+ public override Uri RequestUri => _context.Request.Url;
+
+ public override string SecWebSocketKey => _context.Request.Headers["Sec-WebSocket-Key"];
+
+ public override IEnumerable SecWebSocketProtocols
+ {
+ get
+ {
+ var protocols = _context.Request.Headers["Sec-WebSocket-Protocol"];
+ if (protocols != null)
+ {
+ foreach (var protocol in protocols.Split(','))
+ {
+ yield return protocol.Trim();
+ }
+ }
+ }
+ }
+
+ public override string SecWebSocketVersion => _context.Request.Headers["Sec-WebSocket-Version"];
+
+ public override System.Net.IPEndPoint ServerEndPoint => _context.Connection.LocalEndPoint;
+
+ public override IPrincipal User => _context.User;
+
+ public override System.Net.IPEndPoint UserEndPoint => _context.Connection.RemoteEndPoint;
+
+ public override WebSocket WebSocket => _websocket;
+
+ ///
+ /// Closes the WebSocket connection.
+ ///
+ internal void Close()
+ {
+ _context.Connection.Close(true);
+ }
+
+ ///
+ /// Closes the WebSocket connection with the specified HTTP status code.
+ ///
+ /// The HTTP status code indicating the reason for closure.
+ internal void Close(HttpStatusCode code)
+ {
+ _context.Response.Close(code);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _context.Request.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWebSocketContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWebSocketContext.cs
new file mode 100644
index 0000000..e5cf357
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWebSocketContext.cs
@@ -0,0 +1,231 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Principal;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents the context of a WebSocket connection within a .
+ ///
+ ///
+ /// This internal class provides access to various properties and methods for interacting with the WebSocket connection
+ /// within the context of a TCP listener.
+ ///
+ internal class TcpListenerWebSocketContext : WebSocketContext
+ {
+ private CookieCollection _cookies;
+ private NameValueCollection _queryString;
+ private WebRequest _request;
+ private readonly bool _secure;
+ private readonly TcpClient _tcpClient;
+ private readonly Uri _uri;
+ private IPrincipal _user;
+ private readonly WebSocket _websocket;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The associated with the WebSocket connection.
+ /// The WebSocket protocol negotiated during the connection.
+ /// A boolean indicating whether the connection is secure.
+ /// The SSL configuration for secure connections.
+ /// The logger for logging.
+ internal TcpListenerWebSocketContext(
+ TcpClient tcpClient,
+ string protocol,
+ bool secure,
+ SSLConfigurationServer sslConfig
+ )
+ {
+ _tcpClient = tcpClient;
+ _secure = secure;
+
+ var netStream = tcpClient.GetStream();
+ if (secure)
+ {
+ var sslStream =
+ new SslStream(netStream, false, sslConfig.ClientCertificateValidationCallback);
+
+ sslStream.AuthenticateAsServer(
+ sslConfig.Certificate,
+ sslConfig.IsClientCertificateRequired,
+ sslConfig.SslProtocols,
+ sslConfig.CheckForCertificateRevocation
+ );
+
+ Stream = sslStream;
+ }
+ else
+ {
+ Stream = netStream;
+ }
+
+ _request = WebRequest.Read(Stream, 90000);
+ _uri =
+ HttpUtility.CreateRequestUrl(
+ _request.RequestUri, _request.Headers["Host"], _request.IsWebSocketRequest, secure
+ );
+
+ _websocket = new WebSocket(this, protocol);
+ }
+
+ ///
+ /// Gets the stream of the underlying TCP connection.
+ ///
+ internal Stream Stream { get; }
+
+ public override CookieCollection CookieCollection => _cookies ??= _request.Cookies;
+
+ public override NameValueCollection Headers => _request.Headers;
+
+ public override string Host => _request.Headers["Host"];
+
+ public override bool IsAuthenticated => _user != null;
+
+ public override bool IsLocal => UserEndPoint.Address.IsLocal();
+
+ public override bool IsSecureConnection => _secure;
+
+ public override bool IsWebSocketRequest => _request.IsWebSocketRequest;
+
+ public override string Origin => _request.Headers["Origin"];
+
+ public override NameValueCollection QueryString => _queryString ??=
+ HttpUtility.InternalParseQueryString(
+ _uri != null ? _uri.Query : null, Encoding.UTF8
+ )
+;
+
+ public override Uri RequestUri => _uri;
+
+ public override string SecWebSocketKey => _request.Headers["Sec-WebSocket-Key"];
+
+ public override IEnumerable SecWebSocketProtocols
+ {
+ get
+ {
+ var protocols = _request.Headers["Sec-WebSocket-Protocol"];
+ if (protocols != null)
+ {
+ foreach (var protocol in protocols.Split(','))
+ {
+ yield return protocol.Trim();
+ }
+ }
+ }
+ }
+
+ public override string SecWebSocketVersion => _request.Headers["Sec-WebSocket-Version"];
+
+ public override System.Net.IPEndPoint ServerEndPoint => (System.Net.IPEndPoint)_tcpClient.Client.LocalEndPoint;
+
+ public override IPrincipal User => _user;
+
+ public override System.Net.IPEndPoint UserEndPoint => (System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint;
+
+ public override WebSocket WebSocket => _websocket;
+
+ ///
+ /// Authenticates the WebSocket connection based on the specified authentication scheme.
+ ///
+ /// The authentication scheme to use.
+ /// The authentication realm.
+ /// A function to find network credentials based on identity.
+ /// True if authentication is successful; otherwise, false.
+ internal bool Authenticate(
+ AuthenticationSchemes scheme,
+ string realm,
+ Func credentialsFinder
+ )
+ {
+ if (scheme == AuthenticationSchemes.Anonymous)
+ {
+ return true;
+ }
+
+ if (scheme == AuthenticationSchemes.None)
+ {
+ Close(HttpStatusCode.Forbidden);
+ return false;
+ }
+
+ var chal = new AuthenticationChallenge(scheme, realm).ToString();
+
+ var retry = -1;
+ Func auth = null;
+ auth =
+ () =>
+ {
+ retry++;
+ if (retry > 99)
+ {
+ Close(HttpStatusCode.Forbidden);
+ return false;
+ }
+
+ var user =
+ HttpUtility.CreateUser(
+ _request.Headers["Authorization"],
+ scheme,
+ realm,
+ _request.HttpMethod,
+ credentialsFinder
+ );
+
+ if (user == null || !user.Identity.IsAuthenticated)
+ {
+ SendAuthenticationChallenge(chal);
+ return auth();
+ }
+
+ _user = user;
+ return true;
+ };
+
+ return auth();
+ }
+
+ ///
+ /// Closes the WebSocket connection.
+ ///
+ internal void Close()
+ {
+ Stream.Close();
+ _tcpClient.Close();
+ }
+
+ ///
+ /// Closes the WebSocket connection with the specified HTTP status code.
+ ///
+ /// The HTTP status code indicating the reason for closure.
+ internal void Close(HttpStatusCode code)
+ {
+ _websocket.Close(WebResponse.CreateCloseResponse(code));
+ }
+
+ ///
+ /// Sends an authentication challenge to the WebSocket client.
+ ///
+ /// The authentication challenge.
+ internal void SendAuthenticationChallenge(string challenge)
+ {
+ var buff = WebResponse.CreateUnauthorizedResponse(challenge).ToByteArray();
+ Stream.Write(buff, 0, buff.Length);
+ _request = WebRequest.Read(Stream, 15000);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return _request.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Context/WebSocketContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Context/WebSocketContext.cs
new file mode 100644
index 0000000..d6c45c0
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Context/WebSocketContext.cs
@@ -0,0 +1,112 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Security.Principal;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents the context of a WebSocket connection.
+ ///
+ ///
+ /// This abstract class defines properties and methods for accessing information related to a WebSocket connection,
+ /// such as headers, cookies, authentication status, and more.
+ ///
+ public abstract class WebSocketContext
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected WebSocketContext()
+ {
+ }
+
+ ///
+ /// Gets the collection of cookies associated with the WebSocket connection.
+ ///
+ public abstract CookieCollection CookieCollection { get; }
+
+ ///
+ /// Gets the collection of headers associated with the WebSocket connection.
+ ///
+ public abstract NameValueCollection Headers { get; }
+
+ ///
+ /// Gets the host information from the WebSocket connection.
+ ///
+ public abstract string Host { get; }
+
+ ///
+ /// Gets a value indicating whether the WebSocket connection is authenticated.
+ ///
+ public abstract bool IsAuthenticated { get; }
+
+ ///
+ /// Gets a value indicating whether the WebSocket connection is local.
+ ///
+ public abstract bool IsLocal { get; }
+
+ ///
+ /// Gets a value indicating whether the WebSocket connection is secure.
+ ///
+ public abstract bool IsSecureConnection { get; }
+
+ ///
+ /// Gets a value indicating whether the request is a WebSocket request.
+ ///
+ public abstract bool IsWebSocketRequest { get; }
+
+ ///
+ /// Gets the origin of the WebSocket connection.
+ ///
+ public abstract string Origin { get; }
+
+ ///
+ /// Gets the query string information from the WebSocket connection.
+ ///
+ public abstract NameValueCollection QueryString { get; }
+
+ ///
+ /// Gets the URI of the WebSocket request.
+ ///
+ public abstract Uri RequestUri { get; }
+
+ ///
+ /// Gets the value of the 'Sec-WebSocket-Key' header from the WebSocket connection.
+ ///
+ public abstract string SecWebSocketKey { get; }
+
+ ///
+ /// Gets the protocols specified in the 'Sec-WebSocket-Protocol' header from the WebSocket connection.
+ ///
+ public abstract IEnumerable SecWebSocketProtocols { get; }
+
+ ///
+ /// Gets the value of the 'Sec-WebSocket-Version' header from the WebSocket connection.
+ ///
+ public abstract string SecWebSocketVersion { get; }
+
+ ///
+ /// Gets the local endpoint of the WebSocket server.
+ ///
+ public abstract System.Net.IPEndPoint ServerEndPoint { get; }
+
+ ///
+ /// Gets the user associated with the WebSocket connection.
+ ///
+ public abstract IPrincipal User { get; }
+
+ ///
+ /// Gets the remote endpoint of the WebSocket user.
+ ///
+ public abstract System.Net.IPEndPoint UserEndPoint { get; }
+
+ ///
+ /// Gets the WebSocket instance associated with the context.
+ ///
+ public abstract WebSocket WebSocket { get; }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs
new file mode 100644
index 0000000..e53d0fa
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs
@@ -0,0 +1,593 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an HTTP cookie.
+ ///
+ [Serializable]
+ public sealed class Cookie
+ {
+ private string _comment;
+ private Uri _commentUri;
+ private bool _discard;
+ private string _domain;
+ private DateTime _expires;
+ private bool _httpOnly;
+ private string _name;
+ private string _path;
+ private string _port;
+ private int[] _ports;
+ private static readonly char[] _reservedCharsForName;
+ private static readonly char[] _reservedCharsForValue;
+ private bool _secure;
+ private readonly DateTime _timestamp;
+ private string _value;
+ private int _version;
+
+ static Cookie()
+ {
+ _reservedCharsForName = new[] { ' ', '=', ';', ',', '\n', '\r', '\t' };
+ _reservedCharsForValue = new[] { ';', ',' };
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Cookie()
+ {
+ _comment = string.Empty;
+ _domain = string.Empty;
+ _expires = DateTime.MinValue;
+ _name = string.Empty;
+ _path = string.Empty;
+ _port = string.Empty;
+ _ports = new int[0];
+ _timestamp = DateTime.Now;
+ _value = string.Empty;
+ _version = 0;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified name and value.
+ ///
+ /// The name of the cookie.
+ /// The value of the cookie.
+ public Cookie(string name, string value)
+ : this()
+ {
+ Name = name;
+ Value = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified name, value, and path.
+ ///
+ /// The name of the cookie.
+ /// The value of the cookie.
+ /// The path for which the cookie is valid.
+ public Cookie(string name, string value, string path)
+ : this(name, value)
+ {
+ Path = path;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified name, value, path, and domain.
+ ///
+ /// The name of the cookie.
+ /// The value of the cookie.
+ /// The path for which the cookie is valid.
+ /// The domain to which the cookie belongs.
+ public Cookie(string name, string value, string path, string domain)
+ : this(name, value, path)
+ {
+ Domain = domain;
+ }
+
+ internal bool ExactDomain
+ {
+ get; set;
+ }
+
+ internal int MaxAge
+ {
+ get
+ {
+ if (_expires == DateTime.MinValue)
+ {
+ return 0;
+ }
+
+ var expires = _expires.Kind != DateTimeKind.Local
+ ? _expires.ToLocalTime()
+ : _expires;
+
+ var span = expires - DateTime.Now;
+ return span > TimeSpan.Zero
+ ? (int)span.TotalSeconds
+ : 0;
+ }
+ }
+
+ internal int[] Ports => _ports;
+
+ public string Comment
+ {
+ get
+ {
+ return _comment;
+ }
+
+ set
+ {
+ _comment = value ?? string.Empty;
+ }
+ }
+
+ public Uri CommentUri
+ {
+ get
+ {
+ return _commentUri;
+ }
+
+ set
+ {
+ _commentUri = value;
+ }
+ }
+
+ public bool Discard
+ {
+ get
+ {
+ return _discard;
+ }
+
+ set
+ {
+ _discard = value;
+ }
+ }
+
+ public string Domain
+ {
+ get
+ {
+ return _domain;
+ }
+
+ set
+ {
+ if (value.IsNullOrEmpty())
+ {
+ _domain = string.Empty;
+ ExactDomain = true;
+ }
+ else
+ {
+ _domain = value;
+ ExactDomain = value[0] != '.';
+ }
+ }
+ }
+
+ public bool Expired
+ {
+ get
+ {
+ return _expires != DateTime.MinValue && _expires <= DateTime.Now;
+ }
+
+ set
+ {
+ _expires = value ? DateTime.Now : DateTime.MinValue;
+ }
+ }
+
+ public DateTime Expires
+ {
+ get
+ {
+ return _expires;
+ }
+
+ set
+ {
+ _expires = value;
+ }
+ }
+
+ public bool HttpOnly
+ {
+ get
+ {
+ return _httpOnly;
+ }
+
+ set
+ {
+ _httpOnly = value;
+ }
+ }
+
+ public string Name
+ {
+ get
+ {
+ return _name;
+ }
+
+ set
+ {
+ string msg;
+ if (!canSetName(value, out msg))
+ {
+ throw new CookieException(msg);
+ }
+
+ _name = value;
+ }
+ }
+
+ public string Path
+ {
+ get
+ {
+ return _path;
+ }
+
+ set
+ {
+ _path = value ?? string.Empty;
+ }
+ }
+
+ public string Port
+ {
+ get
+ {
+ return _port;
+ }
+
+ set
+ {
+ if (value.IsNullOrEmpty())
+ {
+ _port = string.Empty;
+ _ports = new int[0];
+
+ return;
+ }
+
+ if (!value.IsEnclosedIn('"'))
+ {
+ throw new CookieException(
+ "The value specified for the Port attribute isn't enclosed in double quotes.");
+ }
+
+ string err;
+ if (!tryCreatePorts(value, out _ports, out err))
+ {
+ throw new CookieException(
+ string.Format(
+ "The value specified for the Port attribute contains an invalid value: {0}", err));
+ }
+
+ _port = value;
+ }
+ }
+
+ public bool Secure
+ {
+ get
+ {
+ return _secure;
+ }
+
+ set
+ {
+ _secure = value;
+ }
+ }
+
+ public DateTime TimeStamp => _timestamp;
+
+ public string Value
+ {
+ get
+ {
+ return _value;
+ }
+
+ set
+ {
+ string msg;
+ if (!canSetValue(value, out msg))
+ {
+ throw new CookieException(msg);
+ }
+
+ _value = value.Length > 0 ? value : "\"\"";
+ }
+ }
+
+ public int Version
+ {
+ get
+ {
+ return _version;
+ }
+
+ set
+ {
+ if (value < 0 || value > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Not 0 or 1.");
+ }
+
+ _version = value;
+ }
+ }
+
+ private static bool canSetName(string name, out string message)
+ {
+ if (name.IsNullOrEmpty())
+ {
+ message = "The value specified for the Name is null or empty.";
+ return false;
+ }
+
+ if (name[0] == '$' || name.Contains(_reservedCharsForName))
+ {
+ message = "The value specified for the Name contains an invalid character.";
+ return false;
+ }
+
+ message = string.Empty;
+ return true;
+ }
+
+ private static bool canSetValue(string value, out string message)
+ {
+ if (value == null)
+ {
+ message = "The value specified for the Value is null.";
+ return false;
+ }
+
+ if (value.Contains(_reservedCharsForValue) && !value.IsEnclosedIn('"'))
+ {
+ message = "The value specified for the Value contains an invalid character.";
+ return false;
+ }
+
+ message = string.Empty;
+ return true;
+ }
+
+ private static int hash(int i, int j, int k, int l, int m)
+ {
+ return i ^
+ (j << 13 | j >> 19) ^
+ (k << 26 | k >> 6) ^
+ (l << 7 | l >> 25) ^
+ (m << 20 | m >> 12);
+ }
+
+ private string toResponseStringVersion0()
+ {
+ var output = new StringBuilder(64);
+ output.AppendFormat("{0}={1}", _name, _value);
+
+ if (_expires != DateTime.MinValue)
+ {
+ output.AppendFormat(
+ "; Expires={0}",
+ _expires.ToUniversalTime().ToString(
+ "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
+ CultureInfo.CreateSpecificCulture("en-US")));
+ }
+
+ if (!_path.IsNullOrEmpty())
+ {
+ output.AppendFormat("; Path={0}", _path);
+ }
+
+ if (!_domain.IsNullOrEmpty())
+ {
+ output.AppendFormat("; Domain={0}", _domain);
+ }
+
+ if (_secure)
+ {
+ output.Append("; Secure");
+ }
+
+ if (_httpOnly)
+ {
+ output.Append("; HttpOnly");
+ }
+
+ return output.ToString();
+ }
+
+ private string toResponseStringVersion1()
+ {
+ var output = new StringBuilder(64);
+ output.AppendFormat("{0}={1}; Version={2}", _name, _value, _version);
+
+ if (_expires != DateTime.MinValue)
+ {
+ output.AppendFormat("; Max-Age={0}", MaxAge);
+ }
+
+ if (!_path.IsNullOrEmpty())
+ {
+ output.AppendFormat("; Path={0}", _path);
+ }
+
+ if (!_domain.IsNullOrEmpty())
+ {
+ output.AppendFormat("; Domain={0}", _domain);
+ }
+
+ if (!_port.IsNullOrEmpty())
+ {
+ if (_port == "\"\"")
+ {
+ output.Append("; Port");
+ }
+ else
+ {
+ output.AppendFormat("; Port={0}", _port);
+ }
+ }
+
+ if (!_comment.IsNullOrEmpty())
+ {
+ output.AppendFormat("; Comment={0}", _comment.UrlEncode());
+ }
+
+ if (_commentUri != null)
+ {
+ var url = _commentUri.OriginalString;
+ output.AppendFormat("; CommentURL={0}", url.IsToken() ? url : url.Quote());
+ }
+
+ if (_discard)
+ {
+ output.Append("; Discard");
+ }
+
+ if (_secure)
+ {
+ output.Append("; Secure");
+ }
+
+ return output.ToString();
+ }
+
+ private static bool tryCreatePorts(string value, out int[] result, out string parseError)
+ {
+ var ports = value.Trim('"').Split(',');
+ var len = ports.Length;
+ var res = new int[len];
+ for (var i = 0; i < len; i++)
+ {
+ res[i] = int.MinValue;
+
+ var port = ports[i].Trim();
+ if (port.Length == 0)
+ {
+ continue;
+ }
+
+ if (!int.TryParse(port, out res[i]))
+ {
+ result = new int[0];
+ parseError = port;
+
+ return false;
+ }
+ }
+
+ result = res;
+ parseError = string.Empty;
+
+ return true;
+ }
+
+ // From client to server
+ internal string ToRequestString(Uri uri)
+ {
+ if (_name.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ if (_version == 0)
+ {
+ return string.Format("{0}={1}", _name, _value);
+ }
+
+ var output = new StringBuilder(64);
+ output.AppendFormat("$Version={0}; {1}={2}", _version, _name, _value);
+
+ if (!_path.IsNullOrEmpty())
+ {
+ output.AppendFormat("; $Path={0}", _path);
+ }
+ else if (uri != null)
+ {
+ output.AppendFormat("; $Path={0}", uri.GetAbsolutePath());
+ }
+ else
+ {
+ output.Append("; $Path=/");
+ }
+
+ var appendDomain = uri == null || uri.Host != _domain;
+ if (appendDomain && !_domain.IsNullOrEmpty())
+ {
+ output.AppendFormat("; $Domain={0}", _domain);
+ }
+
+ if (!_port.IsNullOrEmpty())
+ {
+ if (_port == "\"\"")
+ {
+ output.Append("; $Port");
+ }
+ else
+ {
+ output.AppendFormat("; $Port={0}", _port);
+ }
+ }
+
+ return output.ToString();
+ }
+
+ // From server to client
+ internal string ToResponseString()
+ {
+ return _name.Length > 0
+ ? (_version == 0 ? toResponseStringVersion0() : toResponseStringVersion1())
+ : string.Empty;
+ }
+
+ ///
+ public override bool Equals(object comparand)
+ {
+ return comparand is Cookie cookie &&
+ _name.Equals(cookie.Name, StringComparison.InvariantCultureIgnoreCase) &&
+ _value.Equals(cookie.Value, StringComparison.InvariantCulture) &&
+ _path.Equals(cookie.Path, StringComparison.InvariantCulture) &&
+ _domain.Equals(cookie.Domain, StringComparison.InvariantCultureIgnoreCase) &&
+ _version == cookie.Version;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return hash(
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
+ _value.GetHashCode(),
+ _path.GetHashCode(),
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
+ _version);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return ToRequestString(null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs
new file mode 100644
index 0000000..36e27ca
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs
@@ -0,0 +1,547 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a collection of HTTP cookies.
+ ///
+ [Serializable]
+ public class CookieCollection : ICollection, IEnumerable
+ {
+ private readonly List _list;
+ private object _sync;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CookieCollection()
+ {
+ _list = new List();
+ }
+
+
+ internal IList List => _list;
+
+ internal IEnumerable Sorted
+ {
+ get
+ {
+ var list = new List(_list);
+ if (list.Count > 1)
+ {
+ list.Sort(compareCookieWithinSorted);
+ }
+
+ return list;
+ }
+ }
+
+ ///
+ /// Gets the number of cookies in the collection.
+ ///
+ public int Count => _list.Count;
+
+ ///
+ /// Gets a value indicating whether the collection is read-only. Always returns true.
+ ///
+ public bool IsReadOnly => true;
+
+ ///
+ /// Gets a value indicating whether access to the collection is synchronized (thread-safe). Always returns false.
+ ///
+ public bool IsSynchronized => false;
+
+ ///
+ /// Gets or sets the cookie at the specified index.
+ ///
+ /// The index of the cookie to get or set.
+ /// The cookie at the specified index.
+ public Cookie this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= _list.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ return _list[index];
+ }
+ }
+
+ ///
+ /// Gets the cookie with the specified name.
+ ///
+ /// The name of the cookie to retrieve.
+ /// The cookie with the specified name, or null if the cookie is not found.
+ public Cookie this[string name]
+ {
+ get
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ foreach (var cookie in Sorted)
+ {
+ if (cookie.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return cookie;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets an object that can be used to synchronize access to the collection.
+ ///
+ public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
+
+ private static int compareCookieWithinSort(Cookie x, Cookie y)
+ {
+ return (x.Name.Length + x.Value.Length) - (y.Name.Length + y.Value.Length);
+ }
+
+ private static int compareCookieWithinSorted(Cookie x, Cookie y)
+ {
+ var ret = 0;
+ return (ret = x.Version - y.Version) != 0
+ ? ret
+ : (ret = x.Name.CompareTo(y.Name)) != 0
+ ? ret
+ : y.Path.Length - x.Path.Length;
+ }
+
+ private static CookieCollection parseRequest(string value)
+ {
+ var cookies = new CookieCollection();
+
+ Cookie cookie = null;
+ var ver = 0;
+ var pairs = splitCookieHeaderValue(value);
+ for (var i = 0; i < pairs.Length; i++)
+ {
+ var pair = pairs[i].Trim();
+ if (pair.Length == 0)
+ {
+ continue;
+ }
+
+ if (pair.StartsWith("$version", StringComparison.InvariantCultureIgnoreCase))
+ {
+ ver = int.Parse(pair.GetValue('=', true));
+ }
+ else if (pair.StartsWith("$path", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Path = pair.GetValue('=');
+ }
+ }
+ else if (pair.StartsWith("$domain", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Domain = pair.GetValue('=');
+ }
+ }
+ else if (pair.StartsWith("$port", StringComparison.InvariantCultureIgnoreCase))
+ {
+ var port = pair.Equals("$port", StringComparison.InvariantCultureIgnoreCase)
+ ? "\"\""
+ : pair.GetValue('=');
+
+ if (cookie != null)
+ {
+ cookie.Port = port;
+ }
+ }
+ else
+ {
+ if (cookie != null)
+ {
+ cookies.Add(cookie);
+ }
+
+ string name;
+ string val = string.Empty;
+
+ var pos = pair.IndexOf('=');
+ if (pos == -1)
+ {
+ name = pair;
+ }
+ else if (pos == pair.Length - 1)
+ {
+ name = pair.Substring(0, pos).TrimEnd(' ');
+ }
+ else
+ {
+ name = pair.Substring(0, pos).TrimEnd(' ');
+ val = pair.Substring(pos + 1).TrimStart(' ');
+ }
+
+ cookie = new Cookie(name, val);
+ if (ver != 0)
+ {
+ cookie.Version = ver;
+ }
+ }
+ }
+
+ if (cookie != null)
+ {
+ cookies.Add(cookie);
+ }
+
+ return cookies;
+ }
+
+ private static CookieCollection parseResponse(string value)
+ {
+ var cookies = new CookieCollection();
+
+ Cookie cookie = null;
+ var pairs = splitCookieHeaderValue(value);
+ for (var i = 0; i < pairs.Length; i++)
+ {
+ var pair = pairs[i].Trim();
+ if (pair.Length == 0)
+ {
+ continue;
+ }
+
+ if (pair.StartsWith("version", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Version = int.Parse(pair.GetValue('=', true));
+ }
+ }
+ else if (pair.StartsWith("expires", StringComparison.InvariantCultureIgnoreCase))
+ {
+ var buff = new StringBuilder(pair.GetValue('='), 32);
+ if (i < pairs.Length - 1)
+ {
+ buff.AppendFormat(", {0}", pairs[++i].Trim());
+ }
+
+ DateTime expires;
+ if (!DateTime.TryParseExact(
+ buff.ToString(),
+ new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
+ CultureInfo.CreateSpecificCulture("en-US"),
+ DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
+ out expires))
+ {
+ expires = DateTime.Now;
+ }
+
+ if (cookie != null && cookie.Expires == DateTime.MinValue)
+ {
+ cookie.Expires = expires.ToLocalTime();
+ }
+ }
+ else if (pair.StartsWith("max-age", StringComparison.InvariantCultureIgnoreCase))
+ {
+ var max = int.Parse(pair.GetValue('=', true));
+ var expires = DateTime.Now.AddSeconds(max);
+ if (cookie != null)
+ {
+ cookie.Expires = expires;
+ }
+ }
+ else if (pair.StartsWith("path", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Path = pair.GetValue('=');
+ }
+ }
+ else if (pair.StartsWith("domain", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Domain = pair.GetValue('=');
+ }
+ }
+ else if (pair.StartsWith("port", StringComparison.InvariantCultureIgnoreCase))
+ {
+ var port = pair.Equals("port", StringComparison.InvariantCultureIgnoreCase)
+ ? "\"\""
+ : pair.GetValue('=');
+
+ if (cookie != null)
+ {
+ cookie.Port = port;
+ }
+ }
+ else if (pair.StartsWith("comment", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Comment = pair.GetValue('=').UrlDecode();
+ }
+ }
+ else if (pair.StartsWith("commenturl", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.CommentUri = pair.GetValue('=', true).ToUri();
+ }
+ }
+ else if (pair.StartsWith("discard", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Discard = true;
+ }
+ }
+ else if (pair.StartsWith("secure", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.Secure = true;
+ }
+ }
+ else if (pair.StartsWith("httponly", StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (cookie != null)
+ {
+ cookie.HttpOnly = true;
+ }
+ }
+ else
+ {
+ if (cookie != null)
+ {
+ cookies.Add(cookie);
+ }
+
+ string name;
+ string val = string.Empty;
+
+ var pos = pair.IndexOf('=');
+ if (pos == -1)
+ {
+ name = pair;
+ }
+ else if (pos == pair.Length - 1)
+ {
+ name = pair.Substring(0, pos).TrimEnd(' ');
+ }
+ else
+ {
+ name = pair.Substring(0, pos).TrimEnd(' ');
+ val = pair.Substring(pos + 1).TrimStart(' ');
+ }
+
+ cookie = new Cookie(name, val);
+ }
+ }
+
+ if (cookie != null)
+ {
+ cookies.Add(cookie);
+ }
+
+ return cookies;
+ }
+
+ private int searchCookie(Cookie cookie)
+ {
+ var name = cookie.Name;
+ var path = cookie.Path;
+ var domain = cookie.Domain;
+ var ver = cookie.Version;
+
+ for (var i = _list.Count - 1; i >= 0; i--)
+ {
+ var c = _list[i];
+ if (c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) &&
+ c.Path.Equals(path, StringComparison.InvariantCulture) &&
+ c.Domain.Equals(domain, StringComparison.InvariantCultureIgnoreCase) &&
+ c.Version == ver)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static string[] splitCookieHeaderValue(string value)
+ {
+ return new List(value.SplitHeaderValue(',', ';')).ToArray();
+ }
+
+ ///
+ /// Parses the specified cookie string, creating a .
+ ///
+ /// The cookie string to parse.
+ /// True if parsing a response header; otherwise, false.
+ /// A instance representing the parsed cookies.
+ internal static CookieCollection Parse(string value, bool response)
+ {
+ return response
+ ? parseResponse(value)
+ : parseRequest(value);
+ }
+
+ internal void SetOrRemove(Cookie cookie)
+ {
+ var pos = searchCookie(cookie);
+ if (pos == -1)
+ {
+ if (!cookie.Expired)
+ {
+ _list.Add(cookie);
+ }
+
+ return;
+ }
+
+ if (!cookie.Expired)
+ {
+ _list[pos] = cookie;
+ return;
+ }
+
+ _list.RemoveAt(pos);
+ }
+
+ internal void SetOrRemove(CookieCollection cookies)
+ {
+ foreach (Cookie cookie in cookies)
+ {
+ SetOrRemove(cookie);
+ }
+ }
+
+ internal void Sort()
+ {
+ if (_list.Count > 1)
+ {
+ _list.Sort(compareCookieWithinSort);
+ }
+ }
+
+ ///
+ /// Adds the specified cookie to the collection, updating it if it already exists.
+ ///
+ /// The cookie to add or update.
+ public void Add(Cookie cookie)
+ {
+ if (cookie == null)
+ {
+ throw new ArgumentNullException(nameof(cookie));
+ }
+
+ var pos = searchCookie(cookie);
+ if (pos == -1)
+ {
+ _list.Add(cookie);
+ return;
+ }
+
+ _list[pos] = cookie;
+ }
+
+ ///
+ /// Adds the cookies from the specified to this collection, updating existing cookies.
+ ///
+ /// The to add or update from.
+ public void Add(CookieCollection cookies)
+ {
+ if (cookies == null)
+ {
+ throw new ArgumentNullException(nameof(cookies));
+ }
+
+ foreach (Cookie cookie in cookies)
+ {
+ Add(cookie);
+ }
+ }
+
+ ///
+ /// Copies the cookies in the collection to the specified array, starting at the specified index.
+ ///
+ /// The destination array.
+ /// The index in the destination array at which copying begins.
+ public void CopyTo(Array array, int index)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
+ }
+
+ if (array.Rank > 1)
+ {
+ throw new ArgumentException("Multidimensional.", nameof(array));
+ }
+
+ if (array.Length - index < _list.Count)
+ {
+ throw new ArgumentException(
+ "The number of elements in this collection is greater than the available space of the destination array.");
+ }
+
+ if (!array.GetType().GetElementType().IsAssignableFrom(typeof(Cookie)))
+ {
+ throw new InvalidCastException(
+ "The elements in this collection cannot be cast automatically to the type of the destination array.");
+ } ((IList)_list).CopyTo(array, index);
+ }
+
+ ///
+ /// Copies the cookies in the collection to the specified array, starting at the specified index.
+ ///
+ /// The destination array.
+ /// The index in the destination array at which copying begins.
+ public void CopyTo(Cookie[] array, int index)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
+ }
+
+ if (array.Length - index < _list.Count)
+ {
+ throw new ArgumentException(
+ "The number of elements in this collection is greater than the available space of the destination array.");
+ }
+
+ _list.CopyTo(array, index);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// An enumerator for the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return _list.GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs
new file mode 100644
index 0000000..1fe2ae0
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs
@@ -0,0 +1,81 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an exception specific to EonaCat Network cookies.
+ ///
+ [Serializable]
+ public class CookieException : FormatException, ISerializable
+ {
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The error message that explains the reason for the exception.
+ internal CookieException(string message)
+ : base($"EonaCat Network: {message}")
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception.
+ internal CookieException(string message, Exception innerException)
+ : base($"EonaCat Network: {message}", innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected CookieException(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CookieException()
+ : base()
+ {
+ }
+
+ ///
+ /// Populates a with the data needed to serialize the exception.
+ ///
+ /// The to populate with data.
+ /// The destination (see ) for this serialization.
+ [SecurityPermission(
+ SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
+ public override void GetObjectData(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ base.GetObjectData(serializationInfo, streamingContext);
+ }
+
+ ///
+ /// Populates a with the data needed to serialize the exception.
+ ///
+ /// The to populate with data.
+ /// The destination (see ) for this serialization.
+ [SecurityPermission(
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter,
+ SerializationFormatter = true)]
+ void ISerializable.GetObjectData(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ base.GetObjectData(serializationInfo, streamingContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs
new file mode 100644
index 0000000..d869e7e
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs
@@ -0,0 +1,533 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an endpoint listener for managing HTTP connections.
+ ///
+ internal sealed class EndPointListener
+ {
+ private List _all; // host == '+'
+ private static readonly string _defaultCertFolderPath;
+ private readonly IPEndPoint _endpoint;
+ private Dictionary _prefixes;
+ private readonly Socket _socket;
+ private List _unhandled; // host == '*'
+ private readonly Dictionary _unregistered;
+ private readonly object _unregisteredSync;
+
+ static EndPointListener()
+ {
+ _defaultCertFolderPath =
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ }
+
+ internal EndPointListener(
+ IPEndPoint endpoint,
+ bool secure,
+ string certificateFolderPath,
+ SSLConfigurationServer sslConfig,
+ bool reuseAddress
+ )
+ {
+ if (secure)
+ {
+ var cert =
+ getCertificate(endpoint.Port, certificateFolderPath, sslConfig.Certificate);
+
+ if (cert == null)
+ {
+ throw new ArgumentException("No server certificate could be found.");
+ }
+
+ IsSecure = true;
+ SslConfiguration = new SSLConfigurationServer(sslConfig);
+ SslConfiguration.Certificate = cert;
+ }
+
+ _endpoint = endpoint;
+ _prefixes = new Dictionary();
+ _unregistered = new Dictionary();
+ _unregisteredSync = ((ICollection)_unregistered).SyncRoot;
+ _socket =
+ new Socket(endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+
+ if (reuseAddress)
+ {
+ _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ }
+
+ _socket.Bind(endpoint);
+ _socket.Listen(500);
+ _socket.BeginAccept(onAccept, this);
+ }
+
+ ///
+ /// Gets the IP address of the endpoint.
+ ///
+ public IPAddress Address => _endpoint.Address;
+
+ ///
+ /// Gets a value indicating whether the endpoint is secure.
+ ///
+ public bool IsSecure { get; }
+
+ ///
+ /// Gets the port number of the endpoint.
+ ///
+ public int Port => _endpoint.Port;
+
+ ///
+ /// Gets the SSL configuration for the secure endpoint.
+ ///
+ public SSLConfigurationServer SslConfiguration { get; }
+
+ private static void addSpecial(List prefixes, HttpListenerPrefix prefix)
+ {
+ var path = prefix.Path;
+ foreach (var pref in prefixes)
+ {
+ if (pref.Path == path)
+ {
+ throw new HttpListenerException(87, "The prefix is already in use.");
+ }
+ }
+
+ prefixes.Add(prefix);
+ }
+
+ private static RSACryptoServiceProvider createRSAFromFile(string filename)
+ {
+ byte[] pvk = null;
+ using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ pvk = new byte[fs.Length];
+ fs.Read(pvk, 0, pvk.Length);
+ }
+
+ var rsa = new RSACryptoServiceProvider();
+ rsa.ImportCspBlob(pvk);
+
+ return rsa;
+ }
+
+ private static X509Certificate2 getCertificate(
+ int port, string folderPath, X509Certificate2 defaultCertificate
+ )
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ {
+ folderPath = _defaultCertFolderPath;
+ }
+
+ try
+ {
+ var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
+ var key = Path.Combine(folderPath, string.Format("{0}.key", port));
+ if (File.Exists(cer) && File.Exists(key))
+ {
+ var cert = new X509Certificate2(cer);
+ cert.PrivateKey = createRSAFromFile(key);
+
+ return cert;
+ }
+ }
+ catch
+ {
+ }
+
+ return defaultCertificate;
+ }
+
+ private void leaveIfNoPrefix()
+ {
+ if (_prefixes.Count > 0)
+ {
+ return;
+ }
+
+ var prefs = _unhandled;
+ if (prefs != null && prefs.Count > 0)
+ {
+ return;
+ }
+
+ prefs = _all;
+ if (prefs != null && prefs.Count > 0)
+ {
+ return;
+ }
+
+ EndPointManager.RemoveEndPoint(_endpoint);
+ }
+
+ private static void onAccept(IAsyncResult asyncResult)
+ {
+ var lsnr = (EndPointListener)asyncResult.AsyncState;
+
+ Socket sock = null;
+ try
+ {
+ sock = lsnr._socket.EndAccept(asyncResult);
+ }
+ catch (SocketException)
+ {
+ // TODO: Should log the error code when this class has a logging.
+ }
+ catch (ObjectDisposedException)
+ {
+ return;
+ }
+
+ try
+ {
+ lsnr._socket.BeginAccept(onAccept, lsnr);
+ }
+ catch
+ {
+ if (sock != null)
+ {
+ sock.Close();
+ }
+
+ return;
+ }
+
+ if (sock == null)
+ {
+ return;
+ }
+
+ processAccepted(sock, lsnr);
+ }
+
+ private static void processAccepted(Socket socket, EndPointListener listener)
+ {
+ HttpConnection conn = null;
+ try
+ {
+ conn = new HttpConnection(socket, listener);
+ lock (listener._unregisteredSync)
+ {
+ listener._unregistered[conn] = conn;
+ }
+
+ conn.BeginReadRequest();
+ }
+ catch
+ {
+ if (conn != null)
+ {
+ conn.Close(true);
+ return;
+ }
+
+ socket.Close();
+ }
+ }
+
+ private static bool removeSpecial(List prefixes, HttpListenerPrefix prefix)
+ {
+ var path = prefix.Path;
+ var cnt = prefixes.Count;
+ for (var i = 0; i < cnt; i++)
+ {
+ if (prefixes[i].Path == path)
+ {
+ prefixes.RemoveAt(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static HttpListener searchHttpListenerFromSpecial(
+ string path, List prefixes
+ )
+ {
+ if (prefixes == null)
+ {
+ return null;
+ }
+
+ HttpListener bestMatch = null;
+
+ var bestLen = -1;
+ foreach (var pref in prefixes)
+ {
+ var prefPath = pref.Path;
+
+ var len = prefPath.Length;
+ if (len < bestLen)
+ {
+ continue;
+ }
+
+ if (path.StartsWith(prefPath))
+ {
+ bestLen = len;
+ bestMatch = pref.Listener;
+ }
+ }
+
+ return bestMatch;
+ }
+
+ internal static bool CertificateExists(int port, string folderPath)
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ {
+ folderPath = _defaultCertFolderPath;
+ }
+
+ var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
+ var key = Path.Combine(folderPath, string.Format("{0}.key", port));
+
+ return File.Exists(cer) && File.Exists(key);
+ }
+
+ internal void RemoveConnection(HttpConnection connection)
+ {
+ lock (_unregisteredSync)
+ {
+ _unregistered.Remove(connection);
+ }
+ }
+
+ internal bool TrySearchHttpListener(Uri uri, out HttpListener listener)
+ {
+ listener = null;
+
+ if (uri == null)
+ {
+ return false;
+ }
+
+ var host = uri.Host;
+ var dns = Uri.CheckHostName(host) == UriHostNameType.Dns;
+ var port = uri.Port.ToString();
+ var path = HttpUtility.UrlDecode(uri.AbsolutePath);
+ var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
+
+ if (host != null && host.Length > 0)
+ {
+ var bestLen = -1;
+ foreach (var pref in _prefixes.Keys)
+ {
+ if (dns)
+ {
+ var prefHost = pref.Host;
+ if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host)
+ {
+ continue;
+ }
+ }
+
+ if (pref.Port != port)
+ {
+ continue;
+ }
+
+ var prefPath = pref.Path;
+
+ var len = prefPath.Length;
+ if (len < bestLen)
+ {
+ continue;
+ }
+
+ if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath))
+ {
+ bestLen = len;
+ listener = _prefixes[pref];
+ }
+ }
+
+ if (bestLen != -1)
+ {
+ return true;
+ }
+ }
+
+ var prefs = _unhandled;
+ listener = searchHttpListenerFromSpecial(path, prefs);
+ if (listener == null && pathSlash != path)
+ {
+ listener = searchHttpListenerFromSpecial(pathSlash, prefs);
+ }
+
+ if (listener != null)
+ {
+ return true;
+ }
+
+ prefs = _all;
+ listener = searchHttpListenerFromSpecial(path, prefs);
+ if (listener == null && pathSlash != path)
+ {
+ listener = searchHttpListenerFromSpecial(pathSlash, prefs);
+ }
+
+ return listener != null;
+ }
+
+ public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener)
+ {
+ List current, future;
+ if (prefix.Host == "*")
+ {
+ do
+ {
+ current = _unhandled;
+ future = current != null
+ ? new List(current)
+ : new List();
+
+ prefix.Listener = listener;
+ addSpecial(future, prefix);
+ }
+ while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
+
+ return;
+ }
+
+ if (prefix.Host == "+")
+ {
+ do
+ {
+ current = _all;
+ future = current != null
+ ? new List(current)
+ : new List();
+
+ prefix.Listener = listener;
+ addSpecial(future, prefix);
+ }
+ while (Interlocked.CompareExchange(ref _all, future, current) != current);
+
+ return;
+ }
+
+ Dictionary prefs, prefs2;
+ do
+ {
+ prefs = _prefixes;
+ if (prefs.ContainsKey(prefix))
+ {
+ if (prefs[prefix] != listener)
+ {
+ throw new HttpListenerException(
+ 87, string.Format("There's another listener for {0}.", prefix)
+ );
+ }
+
+ return;
+ }
+
+ prefs2 = new Dictionary(prefs);
+ prefs2[prefix] = listener;
+ }
+ while (Interlocked.CompareExchange(ref _prefixes, prefs2, prefs) != prefs);
+ }
+
+ public void Close()
+ {
+ _socket.Close();
+
+ HttpConnection[] conns = null;
+ lock (_unregisteredSync)
+ {
+ if (_unregistered.Count == 0)
+ {
+ return;
+ }
+
+ var keys = _unregistered.Keys;
+ conns = new HttpConnection[keys.Count];
+ keys.CopyTo(conns, 0);
+ _unregistered.Clear();
+ }
+
+ for (var i = conns.Length - 1; i >= 0; i--)
+ {
+ conns[i].Close(true);
+ }
+ }
+
+ public void RemovePrefix(HttpListenerPrefix prefix, HttpListener listener)
+ {
+ List current, future;
+ if (prefix.Host == "*")
+ {
+ do
+ {
+ current = _unhandled;
+ if (current == null)
+ {
+ break;
+ }
+
+ future = new List(current);
+ if (!removeSpecial(future, prefix))
+ {
+ break; // The prefix wasn't found.
+ }
+ }
+ while (Interlocked.CompareExchange(ref _unhandled, future, current) != current);
+
+ leaveIfNoPrefix();
+ return;
+ }
+
+ if (prefix.Host == "+")
+ {
+ do
+ {
+ current = _all;
+ if (current == null)
+ {
+ break;
+ }
+
+ future = new List(current);
+ if (!removeSpecial(future, prefix))
+ {
+ break; // The prefix wasn't found.
+ }
+ }
+ while (Interlocked.CompareExchange(ref _all, future, current) != current);
+
+ leaveIfNoPrefix();
+ return;
+ }
+
+ Dictionary prefs, prefs2;
+ do
+ {
+ prefs = _prefixes;
+ if (!prefs.ContainsKey(prefix))
+ {
+ break;
+ }
+
+ prefs2 = new Dictionary(prefs);
+ prefs2.Remove(prefix);
+ }
+ while (Interlocked.CompareExchange(ref _prefixes, prefs2, prefs) != prefs);
+
+ leaveIfNoPrefix();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs
new file mode 100644
index 0000000..acfb1a0
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs
@@ -0,0 +1,237 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Manages HTTP endpoint listeners and their prefixes.
+ ///
+ internal sealed class EndPointManager
+ {
+ private static readonly Dictionary _endpoints;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static EndPointManager()
+ {
+ _endpoints = new Dictionary();
+ }
+
+ ///
+ /// Prevents a default instance of the class from being created.
+ ///
+ private EndPointManager()
+ {
+ }
+
+ private static void addPrefix(string uriPrefix, HttpListener listener)
+ {
+ var pref = new HttpListenerPrefix(uriPrefix);
+
+ var addr = convertToIPAddress(pref.Host);
+ if (!addr.IsLocal())
+ {
+ throw new HttpListenerException(87, "Includes an invalid host.");
+ }
+
+ int port;
+ if (!int.TryParse(pref.Port, out port))
+ {
+ throw new HttpListenerException(87, "Includes an invalid port.");
+ }
+
+ if (!port.IsPortNumber())
+ {
+ throw new HttpListenerException(87, "Includes an invalid port.");
+ }
+
+ var path = pref.Path;
+ if (path.IndexOf('%') != -1)
+ {
+ throw new HttpListenerException(87, "Includes an invalid path.");
+ }
+
+ if (path.IndexOf("//", StringComparison.Ordinal) != -1)
+ {
+ throw new HttpListenerException(87, "Includes an invalid path.");
+ }
+
+ var endpoint = new IPEndPoint(addr, port);
+
+ EndPointListener lsnr;
+ if (_endpoints.TryGetValue(endpoint, out lsnr))
+ {
+ if (lsnr.IsSecure ^ pref.IsSecure)
+ {
+ throw new HttpListenerException(87, "Includes an invalid scheme.");
+ }
+ }
+ else
+ {
+ lsnr =
+ new EndPointListener(
+ endpoint,
+ pref.IsSecure,
+ listener.CertificateFolderPath,
+ listener.SslConfiguration,
+ listener.ReuseAddress
+ );
+
+ _endpoints.Add(endpoint, lsnr);
+ }
+
+ lsnr.AddPrefix(pref, listener);
+ }
+
+ private static IPAddress convertToIPAddress(string hostname)
+ {
+ return hostname == "*" || hostname == "+" ? IPAddress.Any : hostname.ToIPAddress();
+ }
+
+ private static void removePrefix(string uriPrefix, HttpListener listener)
+ {
+ var pref = new HttpListenerPrefix(uriPrefix);
+
+ var addr = convertToIPAddress(pref.Host);
+ if (!addr.IsLocal())
+ {
+ return;
+ }
+
+ int port;
+ if (!int.TryParse(pref.Port, out port))
+ {
+ return;
+ }
+
+ if (!port.IsPortNumber())
+ {
+ return;
+ }
+
+ var path = pref.Path;
+ if (path.IndexOf('%') != -1)
+ {
+ return;
+ }
+
+ if (path.IndexOf("//", StringComparison.Ordinal) != -1)
+ {
+ return;
+ }
+
+ var endpoint = new IPEndPoint(addr, port);
+
+ EndPointListener lsnr;
+ if (!_endpoints.TryGetValue(endpoint, out lsnr))
+ {
+ return;
+ }
+
+ if (lsnr.IsSecure ^ pref.IsSecure)
+ {
+ return;
+ }
+
+ lsnr.RemovePrefix(pref, listener);
+ }
+
+ ///
+ /// Removes an endpoint and closes its associated listener.
+ ///
+ /// The endpoint to be removed.
+ /// true if the endpoint is successfully removed; otherwise, false.
+ internal static bool RemoveEndPoint(IPEndPoint endpoint)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ EndPointListener lsnr;
+ if (!_endpoints.TryGetValue(endpoint, out lsnr))
+ {
+ return false;
+ }
+
+ _endpoints.Remove(endpoint);
+ lsnr.Close();
+
+ return true;
+ }
+ }
+
+ ///
+ /// Adds an HTTP listener and its associated prefixes.
+ ///
+ /// The HTTP listener to be added.
+ public static void AddListener(HttpListener listener)
+ {
+ var added = new List();
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ try
+ {
+ foreach (var pref in listener.Prefixes)
+ {
+ addPrefix(pref, listener);
+ added.Add(pref);
+ }
+ }
+ catch
+ {
+ foreach (var pref in added)
+ {
+ removePrefix(pref, listener);
+ }
+
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Adds an HTTP listener prefix.
+ ///
+ /// The URI prefix to be added.
+ /// The HTTP listener associated with the prefix.
+ public static void AddPrefix(string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ addPrefix(uriPrefix, listener);
+ }
+ }
+
+ ///
+ /// Removes an HTTP listener and its associated prefixes.
+ ///
+ /// The HTTP listener to be removed.
+ public static void RemoveListener(HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ foreach (var pref in listener.Prefixes)
+ {
+ removePrefix(pref, listener);
+ }
+ }
+ }
+
+ ///
+ /// Removes an HTTP listener prefix.
+ ///
+ /// The URI prefix to be removed.
+ /// The HTTP listener associated with the prefix.
+ public static void RemovePrefix(string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ removePrefix(uriPrefix, listener);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpBasicIdentity.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpBasicIdentity.cs
new file mode 100644
index 0000000..b770b76
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpBasicIdentity.cs
@@ -0,0 +1,31 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System.Security.Principal;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents a basic HTTP identity with a username and password.
+ ///
+ public class HttpBasicIdentity : GenericIdentity
+ {
+ private readonly string _password;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The username associated with the identity.
+ /// The password associated with the identity.
+ internal HttpBasicIdentity(string username, string password)
+ : base(username, "Basic")
+ {
+ _password = password;
+ }
+
+ ///
+ /// Gets the password associated with the identity.
+ ///
+ public virtual string Password => _password;
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs
new file mode 100644
index 0000000..eee29df
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs
@@ -0,0 +1,633 @@
+// This file is part of the EonaCat project(s) which is released under the Apache License.
+// See the LICENSE file or go to https://EonaCat.com/License for full license details.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace EonaCat.Network
+{
+ ///
+ /// Represents an HTTP connection.
+ ///
+ internal sealed class HttpConnection
+ {
+ private byte[] _buffer;
+ private const int _bufferLength = 8192;
+ private HttpListenerContext _context;
+ private bool _contextRegistered;
+ private StringBuilder _currentLine;
+ private InputState _inputState;
+ private RequestStream _inputStream;
+ private HttpListener _lastListener;
+ private LineState _lineState;
+ private readonly EndPointListener _listener;
+ private ResponseStream _outputStream;
+ private int _position;
+ private MemoryStream _requestBuffer;
+ private Socket _socket;
+ private readonly object _sync;
+ private int _timeout;
+ private readonly Dictionary _timeoutCanceled;
+ private Timer _timer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The socket associated with the connection.
+ /// The endpoint listener.
+ internal HttpConnection(Socket socket, EndPointListener listener)
+ {
+ _socket = socket;
+ _listener = listener;
+ IsSecure = listener.IsSecure;
+
+ var netStream = new NetworkStream(socket, false);
+ if (IsSecure)
+ {
+ var conf = listener.SslConfiguration;
+ var sslStream = new SslStream(netStream, false, conf.ClientCertificateValidationCallback);
+ sslStream.AuthenticateAsServer(
+ conf.Certificate,
+ conf.IsClientCertificateRequired,
+ conf.SslProtocols,
+ conf.CheckForCertificateRevocation
+ );
+
+ Stream = sslStream;
+ }
+ else
+ {
+ Stream = netStream;
+ }
+
+ _sync = new object();
+ _timeout = 90000; // 90k ms for first request, 15k ms from then on.
+ _timeoutCanceled = new Dictionary();
+ _timer = new Timer(onTimeout, this, Timeout.Infinite, Timeout.Infinite);
+
+ init();
+ }
+
+ ///
+ /// Gets a value indicating whether the connection is closed.
+ ///
+ public bool IsClosed => _socket == null;
+
+ ///
+ /// Gets a value indicating whether the connection is secure.
+ ///
+ public bool IsSecure { get; }
+
+ ///
+ /// Gets the local endpoint.
+ ///
+ public IPEndPoint LocalEndPoint => (IPEndPoint)_socket.LocalEndPoint;
+
+ ///
+ /// Gets the remote endpoint.
+ ///
+ public IPEndPoint RemoteEndPoint => (IPEndPoint)_socket.RemoteEndPoint;
+
+ ///
+ /// Gets or sets the number of reuses.
+ ///
+ public int Reuses { get; private set; }
+
+ ///
+ /// Gets the network stream associated with the connection.
+ ///
+ public Stream Stream { get; private set; }
+
+ private void close()
+ {
+ lock (_sync)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ disposeTimer();
+ disposeRequestBuffer();
+ disposeStream();
+ closeSocket();
+ }
+
+ unregisterContext();
+ removeConnection();
+ }
+
+ private void closeSocket()
+ {
+ try
+ {
+ _socket.Shutdown(SocketShutdown.Both);
+ }
+ catch
+ {
+ }
+
+ _socket.Close();
+ _socket = null;
+ }
+
+ private void disposeRequestBuffer()
+ {
+ if (_requestBuffer == null)
+ {
+ return;
+ }
+
+ _requestBuffer.Dispose();
+ _requestBuffer = null;
+ }
+
+ private void disposeStream()
+ {
+ if (Stream == null)
+ {
+ return;
+ }
+
+ _inputStream = null;
+ _outputStream = null;
+
+ Stream.Dispose();
+ Stream = null;
+ }
+
+ private void disposeTimer()
+ {
+ if (_timer == null)
+ {
+ return;
+ }
+
+ try
+ {
+ _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+ catch
+ {
+ }
+
+ _timer.Dispose();
+ _timer = null;
+ }
+
+ private void init()
+ {
+ _context = new HttpListenerContext(this);
+ _inputState = InputState.RequestLine;
+ _inputStream = null;
+ _lineState = LineState.None;
+ _outputStream = null;
+ _position = 0;
+ _requestBuffer = new MemoryStream();
+ }
+
+ private static void onRead(IAsyncResult asyncResult)
+ {
+ var conn = (HttpConnection)asyncResult.AsyncState;
+ if (conn._socket == null)
+ {
+ return;
+ }
+
+ lock (conn._sync)
+ {
+ if (conn._socket == null)
+ {
+ return;
+ }
+
+ var nread = -1;
+ var len = 0;
+ try
+ {
+ var current = conn.Reuses;
+ if (!conn._timeoutCanceled[current])
+ {
+ conn._timer.Change(Timeout.Infinite, Timeout.Infinite);
+ conn._timeoutCanceled[current] = true;
+ }
+
+ nread = conn.Stream.EndRead(asyncResult);
+ conn._requestBuffer.Write(conn._buffer, 0, nread);
+ len = (int)conn._requestBuffer.Length;
+ }
+ catch (Exception ex)
+ {
+ if (conn._requestBuffer != null && conn._requestBuffer.Length > 0)
+ {
+ conn.SendError(ex.Message, 400);
+ return;
+ }
+
+ conn.close();
+ return;
+ }
+
+ if (nread <= 0)
+ {
+ conn.close();
+ return;
+ }
+
+ if (conn.processInput(conn._requestBuffer.GetBuffer(), len))
+ {
+ if (!conn._context.HasError)
+ {
+ conn._context.Request.FinishInitialization();
+ }
+
+ if (conn._context.HasError)
+ {
+ conn.SendError();
+ return;
+ }
+
+ HttpListener lsnr;
+ if (!conn._listener.TrySearchHttpListener(conn._context.Request.Url, out lsnr))
+ {
+ conn.SendError(null, 404);
+ return;
+ }
+
+ if (conn._lastListener != lsnr)
+ {
+ conn.removeConnection();
+ if (!lsnr.AddConnection(conn))
+ {
+ conn.close();
+ return;
+ }
+
+ conn._lastListener = lsnr;
+ }
+
+ conn._context.Listener = lsnr;
+ if (!conn._context.Authenticate())
+ {
+ return;
+ }
+
+ if (conn._context.Register())
+ {
+ conn._contextRegistered = true;
+ }
+
+ return;
+ }
+
+ conn.Stream.BeginRead(conn._buffer, 0, _bufferLength, onRead, conn);
+ }
+ }
+
+ private static void onTimeout(object state)
+ {
+ var conn = (HttpConnection)state;
+ var current = conn.Reuses;
+ if (conn._socket == null)
+ {
+ return;
+ }
+
+ lock (conn._sync)
+ {
+ if (conn._socket == null)
+ {
+ return;
+ }
+
+ if (conn._timeoutCanceled[current])
+ {
+ return;
+ }
+
+ conn.SendError(null, 408);
+ }
+ }
+
+ private bool processInput(byte[] data, int length)
+ {
+ _currentLine ??= new StringBuilder(64);
+
+ var nread = 0;
+ try
+ {
+ string line;
+ while ((line = readLineFrom(data, _position, length, out nread)) != null)
+ {
+ _position += nread;
+ if (line.Length == 0)
+ {
+ if (_inputState == InputState.RequestLine)
+ {
+ continue;
+ }
+
+ if (_position > 32768)
+ {
+ _context.ErrorMessage = "Headers too long";
+ }
+
+ _currentLine = null;
+ return true;
+ }
+
+ if (_inputState == InputState.RequestLine)
+ {
+ _context.Request.SetRequestLine(line);
+ _inputState = InputState.Headers;
+ }
+ else
+ {
+ _context.Request.AddHeader(line);
+ }
+
+ if (_context.HasError)
+ {
+ return true;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _context.ErrorMessage = ex.Message;
+ return true;
+ }
+
+ _position += nread;
+ if (_position >= 32768)
+ {
+ _context.ErrorMessage = "Headers too long";
+ return true;
+ }
+
+ return false;
+ }
+
+ private string readLineFrom(byte[] buffer, int offset, int length, out int read)
+ {
+ read = 0;
+
+ for (var i = offset; i < length && _lineState != LineState.Lf; i++)
+ {
+ read++;
+
+ var b = buffer[i];
+ if (b == 13)
+ {
+ _lineState = LineState.Cr;
+ }
+ else if (b == 10)
+ {
+ _lineState = LineState.Lf;
+ }
+ else
+ {
+ _currentLine.Append((char)b);
+ }
+ }
+
+ if (_lineState != LineState.Lf)
+ {
+ return null;
+ }
+
+ var line = _currentLine.ToString();
+
+ _currentLine.Length = 0;
+ _lineState = LineState.None;
+
+ return line;
+ }
+
+ private void removeConnection()
+ {
+ if (_lastListener != null)
+ {
+ _lastListener.RemoveConnection(this);
+ }
+ else
+ {
+ _listener.RemoveConnection(this);
+ }
+ }
+
+ private void unregisterContext()
+ {
+ if (!_contextRegistered)
+ {
+ return;
+ }
+
+ _context.Unregister();
+ _contextRegistered = false;
+ }
+
+ ///
+ /// Closes the connection.
+ ///
+ /// True to force close, false otherwise.
+ internal void Close(bool force)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ if (!force)
+ {
+ GetResponseStream().Close(false);
+ if (!_context.Response.CloseConnection && _context.Request.FlushInput())
+ {
+ // Don't close. Keep working.
+ Reuses++;
+ disposeRequestBuffer();
+ unregisterContext();
+ init();
+ BeginReadRequest();
+
+ return;
+ }
+ }
+ else if (_outputStream != null)
+ {
+ _outputStream.Close(true);
+ }
+
+ close();
+ }
+ }
+
+ ///
+ /// Initiates reading the request.
+ ///
+ public void BeginReadRequest()
+ {
+ _buffer ??= new byte[_bufferLength];
+
+ if (Reuses == 1)
+ {
+ _timeout = 15000;
+ }
+
+ try
+ {
+ _timeoutCanceled.Add(Reuses, false);
+ _timer.Change(_timeout, Timeout.Infinite);
+ Stream.BeginRead(_buffer, 0, _bufferLength, onRead, this);
+ }
+ catch
+ {
+ close();
+ }
+ }
+
+ ///
+ /// Closes the connection.
+ ///
+ public void Close()
+ {
+ Close(false);
+ }
+
+ ///
+ /// Gets the request stream.
+ ///
+ /// The length of the content.
+ /// True if chunked, false otherwise.
+ /// The request stream.
+ public RequestStream GetRequestStream(long contentLength, bool chunked)
+ {
+ if (_inputStream != null || _socket == null)
+ {
+ return _inputStream;
+ }
+
+ lock (_sync)
+ {
+ if (_socket == null)
+ {
+ return _inputStream;
+ }
+
+ var buff = _requestBuffer.GetBuffer();
+ var len = (int)_requestBuffer.Length;
+ disposeRequestBuffer();
+ if (chunked)
+ {
+ _context.Response.SendInChunks = true;
+ _inputStream =
+ new ChunkedRequestStream(Stream, buff, _position, len - _position, _context);
+ }
+ else
+ {
+ _inputStream =
+ new RequestStream(Stream, buff, _position, len - _position, contentLength);
+ }
+
+ return _inputStream;
+ }
+ }
+
+ ///
+ /// Gets the response stream.
+ ///
+ /// The response stream.
+ public ResponseStream GetResponseStream()
+ {
+ if (_outputStream != null || _socket == null)
+ {
+ return _outputStream;
+ }
+
+ lock (_sync)
+ {
+ if (_socket == null)
+ {
+ return _outputStream;
+ }
+
+ var lsnr = _context.Listener;
+ var ignore = lsnr == null || lsnr.IgnoreWriteExceptions;
+ _outputStream = new ResponseStream(Stream, _context.Response, ignore);
+
+ return _outputStream;
+ }
+ }
+
+ ///
+ /// Sends an error response.
+ ///
+ public void SendError()
+ {
+ SendError(_context.ErrorMessage, _context.ErrorStatus);
+ }
+
+ ///
+ /// Sends an error response with the specified message and status code.
+ ///
+ /// The error message.
+ /// The HTTP status code.
+ public void SendError(string message, int status)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ try
+ {
+ var res = _context.Response;
+ res.StatusCode = status;
+ res.ContentType = "text/html";
+
+ var content = new StringBuilder(64);
+ content.AppendFormat("EonaCat.Network Error{0} {1}", status, res.StatusDescription);
+ if (message != null && message.Length > 0)
+ {
+ content.AppendFormat(" ({0})
", message);
+ }
+ else
+ {
+ content.Append("