Updated
This commit is contained in:
parent
8d7e806d14
commit
681f8c725f
|
@ -1,15 +1,15 @@
|
|||
// 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;
|
||||
using EonaCat.WebSockets;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
private static WSClient _client;
|
||||
private static WSServer _server;
|
||||
private static AsyncWebSocketClient _client;
|
||||
private static AsyncWebSocketServer _server;
|
||||
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ internal class Program
|
|||
break;
|
||||
|
||||
case "1":
|
||||
await CreateServerAndClientAsync();
|
||||
await CreateServerAndClientAsync().ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case "2":
|
||||
|
@ -38,12 +38,13 @@ internal class Program
|
|||
{
|
||||
Console.Write("Enter message: ");
|
||||
var message = Console.ReadLine();
|
||||
_client.Send(message);
|
||||
await _client.SendTextAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "3":
|
||||
_client?.Close();
|
||||
_client?.CloseAsync().ConfigureAwait(false);
|
||||
return;
|
||||
|
||||
default:
|
||||
|
@ -66,64 +67,101 @@ internal class Program
|
|||
StartServer(serverUri, certificatePath, certificatePassword, requiredPassword);
|
||||
|
||||
// Start the client in the main thread
|
||||
_client = new WSClient(clientUri, 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();
|
||||
|
||||
var configuration = new AsyncWebSocketClientConfiguration();
|
||||
_client = new AsyncWebSocketClient(new Uri(clientUri), OnServerTextReceived, OnServerBinaryReceived, OnServerConnected, OnServerDisconnected, OnServerFragmentationStreamOpened, OnServerFragmentationStreamOpened, OnServerFragmentationStreamClosed, configuration);
|
||||
await _client.ConnectAsync().ConfigureAwait(false);
|
||||
//(sender, e) => Console.WriteLine($"Error: {sender}\n{e}");
|
||||
Console.WriteLine("Connected to the server.");
|
||||
}
|
||||
|
||||
private static Task OnServerFragmentationStreamClosed(AsyncWebSocketClient arg1, byte[] arg2, int arg3, int arg4)
|
||||
{
|
||||
// Do nothing
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnServerFragmentationStreamOpened(AsyncWebSocketClient arg1, byte[] arg2, int arg3, int arg4)
|
||||
{
|
||||
// Do nothing
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnServerDisconnected(AsyncWebSocketClient arg)
|
||||
{
|
||||
Console.WriteLine("Disconnected from server.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnServerConnected(AsyncWebSocketClient arg)
|
||||
{
|
||||
Console.WriteLine("Connected to server.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnServerBinaryReceived(AsyncWebSocketClient arg1, byte[] bytes, int arg3, int arg4)
|
||||
{
|
||||
Console.WriteLine($"Received binary {bytes} {arg3} {arg4}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnServerTextReceived(AsyncWebSocketClient arg1, string data)
|
||||
{
|
||||
Console.WriteLine($"Received message from server: {data}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void CreateCertificate()
|
||||
{
|
||||
Console.Write("Enter hostname: (default: localhost) ");
|
||||
string hostname = Console.ReadLine();
|
||||
var hostname = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hostname))
|
||||
{
|
||||
hostname = "localhost";
|
||||
}
|
||||
|
||||
int days = 30;
|
||||
var days = 30;
|
||||
Console.Write("Enter days until expiration: (default: 30 days) ");
|
||||
if (int.TryParse(Console.ReadLine(), out int givenDays))
|
||||
if (int.TryParse(Console.ReadLine(), out var givenDays))
|
||||
{
|
||||
days = givenDays;
|
||||
}
|
||||
|
||||
Console.Write("Enter password, enter to skip: ");
|
||||
string password = Console.ReadLine();
|
||||
var password = Console.ReadLine();
|
||||
|
||||
RSA rsa = RSA.Create();
|
||||
var rsa = RSA.Create();
|
||||
|
||||
// Create a certificate request with the specified subject and key pair
|
||||
CertificateRequest request = new CertificateRequest(
|
||||
var 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));
|
||||
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(days));
|
||||
|
||||
// Export the certificate to a file with password
|
||||
byte[] certBytes = string.IsNullOrEmpty(password)
|
||||
var 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(
|
||||
$"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)
|
||||
private static void StartServer(string serverUri, string certificatePath, string certificatePassword,
|
||||
string requiredPassword)
|
||||
{
|
||||
_server = new WSServer(serverUri);
|
||||
_server.SSL.Certificate = new X509Certificate2(certificatePath, certificatePassword);
|
||||
_server.AddEndpoint<WelcomeEndpoint>("/Welcome");
|
||||
_server.Start();
|
||||
var test = new AsyncWebSocketServerModuleCatalog();
|
||||
//var module = new AsyncWebSocketServerModule
|
||||
//test.RegisterModule();
|
||||
//_server = new AsyncWebSocketServer(serverUri);
|
||||
//_server.SSL.Certificate = new X509Certificate2(certificatePath, certificatePassword);
|
||||
//_server.AddEndpoint<WelcomeEndpoint>("/Welcome");
|
||||
//_server.Start();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
namespace EonaCat.Network
|
||||
namespace EonaCat.Network;
|
||||
|
||||
internal class Constants
|
||||
{
|
||||
internal class Constants
|
||||
{
|
||||
public static string Version { get; set; } = "1.1.4";
|
||||
}
|
||||
public static string Version { get; set; } = "1.1.4";
|
||||
}
|
|
@ -13,9 +13,9 @@
|
|||
<PackageTags>EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server</PackageTags>
|
||||
<PackageReleaseNotes></PackageReleaseNotes>
|
||||
<Description>EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver</Description>
|
||||
<Version>1.1.5</Version>
|
||||
<AssemblyVersion>1.1.5</AssemblyVersion>
|
||||
<FileVersion>1.1.5</FileVersion>
|
||||
<Version>1.1.6</Version>
|
||||
<AssemblyVersion>1.1.6</AssemblyVersion>
|
||||
<FileVersion>1.1.6</FileVersion>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
|
|
|
@ -1,136 +1,137 @@
|
|||
using EonaCat.LogSystem;
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
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
|
||||
namespace EonaCat.Network;
|
||||
// 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 static class NetworkHelper
|
||||
{
|
||||
// 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.
|
||||
internal static Logging Logger = new();
|
||||
private static QuicServer _quicServer;
|
||||
|
||||
public static class NetworkHelper
|
||||
//TODO: add udp and tcp example methods
|
||||
|
||||
/// <summary>
|
||||
/// Character bit encoding type web
|
||||
/// </summary>
|
||||
public static Encoding GlobalEncoding = Encoding.UTF8;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicClientConnected event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicConnectionEventArgs> OnQuicClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicStreamOpened event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicStreamEventArgs> OnQuicStreamOpened;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicStreamDataReceived event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicStreamEventArgs> OnQuicStreamDataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Start a Quic server
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, start successfully, <c>false</c> did not start successfully.</returns>
|
||||
/// <param name="ip">The ip bound to the server</param>
|
||||
/// <param name="port">The listening port. (default: 11000)</param>
|
||||
public static bool QuicStartServer(string ip, int port = 11000)
|
||||
{
|
||||
internal static Logging Logger = new Logging();
|
||||
private static QuicServer _quicServer;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicClientConnected event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicConnectionEventArgs> OnQuicClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicStreamOpened event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicStreamEventArgs> OnQuicStreamOpened;
|
||||
|
||||
/// <summary>
|
||||
/// OnQuicStreamDataReceived event
|
||||
/// </summary>
|
||||
public static event EventHandler<QuicStreamEventArgs> OnQuicStreamDataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Start a Quic server
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, start successfully, <c>false</c> did not start successfully.</returns>
|
||||
/// <param name="ip">The ip bound to the server</param>
|
||||
/// <param name="port">The listening port. (default: 11000)</param>
|
||||
public static bool QuicStartServer(string ip, int port = 11000)
|
||||
{
|
||||
_quicServer = new QuicServer(ip, port);
|
||||
_quicServer.OnClientConnected += ClientConnected;
|
||||
_quicServer.Start();
|
||||
Logger.Info($"The Quic server has been successfully started on ip '{ip}' and port: {port}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when Client is connected
|
||||
/// </summary>
|
||||
/// <param name="connection">The new connection</param>
|
||||
private static void ClientConnected(QuicConnection connection)
|
||||
{
|
||||
OnQuicClientConnected?.Invoke(null, new QuicConnectionEventArgs { Connection = connection });
|
||||
connection.OnStreamOpened += StreamOpened;
|
||||
}
|
||||
|
||||
private static void StreamOpened(QuicStream stream)
|
||||
{
|
||||
OnQuicStreamOpened?.Invoke(null, new QuicStreamEventArgs { Stream = stream });
|
||||
stream.OnStreamDataReceived += StreamDataReceived;
|
||||
}
|
||||
|
||||
private static void StreamDataReceived(QuicStream stream, byte[] data)
|
||||
{
|
||||
OnQuicStreamDataReceived?.Invoke(null, new QuicStreamEventArgs { Stream = stream, Data = data });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a Quic client
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="streamType"></param>
|
||||
/// <returns></returns>
|
||||
public static QuicStream QuicStartClient(string ip, int port = 11000, StreamType streamType = StreamType.ClientBidirectional)
|
||||
{
|
||||
QuicClient client = new QuicClient();
|
||||
|
||||
// Connect to peer (Server)
|
||||
QuicConnection connection = client.Connect(ip, port);
|
||||
|
||||
// Create a data stream
|
||||
return connection.CreateStream(streamType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Quic server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool QuicStopServer()
|
||||
{
|
||||
if (_quicServer != null)
|
||||
{
|
||||
_quicServer.Close();
|
||||
Logger.Info($"The Quic server has been successfully stopped");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: add udp and tcp example methods
|
||||
|
||||
/// <summary>
|
||||
/// Character bit encoding type web
|
||||
/// </summary>
|
||||
public static Encoding GlobalEncoding = Encoding.UTF8;
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var client = new UdpClient();
|
||||
|
||||
for (int i = 0; i < 5000; i++)
|
||||
{
|
||||
// TCP TEST
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5000; i++)
|
||||
{
|
||||
// UDP TEST
|
||||
}
|
||||
Console.ReadLine();
|
||||
}
|
||||
_quicServer = new QuicServer(ip, port);
|
||||
_quicServer.OnClientConnected += ClientConnected;
|
||||
_quicServer.Start();
|
||||
Logger.Info($"The Quic server has been successfully started on ip '{ip}' and port: {port}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IP type enumeration
|
||||
/// Fired when Client is connected
|
||||
/// </summary>
|
||||
public enum IPType : byte
|
||||
/// <param name="connection">The new connection</param>
|
||||
private static void ClientConnected(QuicConnection connection)
|
||||
{
|
||||
IPv4,
|
||||
IPv6
|
||||
OnQuicClientConnected?.Invoke(null, new QuicConnectionEventArgs { Connection = connection });
|
||||
connection.OnStreamOpened += StreamOpened;
|
||||
}
|
||||
|
||||
private static void StreamOpened(QuicStream stream)
|
||||
{
|
||||
OnQuicStreamOpened?.Invoke(null, new QuicStreamEventArgs { Stream = stream });
|
||||
stream.OnStreamDataReceived += StreamDataReceived;
|
||||
}
|
||||
|
||||
private static void StreamDataReceived(QuicStream stream, byte[] data)
|
||||
{
|
||||
OnQuicStreamDataReceived?.Invoke(null, new QuicStreamEventArgs { Stream = stream, Data = data });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a Quic client
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="streamType"></param>
|
||||
/// <returns></returns>
|
||||
public static QuicStream QuicStartClient(string ip, int port = 11000,
|
||||
StreamType streamType = StreamType.ClientBidirectional)
|
||||
{
|
||||
var client = new QuicClient();
|
||||
|
||||
// Connect to peer (Server)
|
||||
var connection = client.Connect(ip, port);
|
||||
|
||||
// Create a data stream
|
||||
return connection.CreateStream(streamType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Quic server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool QuicStopServer()
|
||||
{
|
||||
if (_quicServer != null)
|
||||
{
|
||||
_quicServer.Close();
|
||||
Logger.Info("The Quic server has been successfully stopped");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var client = new UdpClient();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
// TCP TEST
|
||||
}
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
// UDP TEST
|
||||
}
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IP type enumeration
|
||||
/// </summary>
|
||||
public enum IPType : byte
|
||||
{
|
||||
IPv4,
|
||||
IPv6
|
||||
}
|
|
@ -1,74 +1,72 @@
|
|||
using EonaCat.Quic.Infrastructure;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Infrastructure;
|
||||
using EonaCat.Quic.Infrastructure.Settings;
|
||||
using EonaCat.Quic.InternalInfrastructure;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.Quic.Connections
|
||||
namespace EonaCat.Quic.Connections;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Since UDP is a stateless protocol, the ConnectionPool is used as a Conenction Manager to
|
||||
/// route packets to the right "Connection".
|
||||
/// </summary>
|
||||
internal static class ConnectionPool
|
||||
{
|
||||
// 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.
|
||||
/// <summary>
|
||||
/// Starting point for connection identifiers.
|
||||
/// ConnectionId's are incremented sequentially by 1.
|
||||
/// </summary>
|
||||
private static readonly NumberSpace _ns = new(QuicSettings.MaximumConnectionIds);
|
||||
|
||||
private static readonly Dictionary<ulong, QuicConnection> _pool = new();
|
||||
|
||||
private static readonly List<QuicConnection> _draining = new();
|
||||
|
||||
/// <summary>
|
||||
/// Since UDP is a stateless protocol, the ConnectionPool is used as a Conenction Manager to
|
||||
/// route packets to the right "Connection".
|
||||
/// Adds a connection to the connection pool.
|
||||
/// For now assume that the client connection id is valid, and just send it back.
|
||||
/// Later this should change in a way that the server validates, and regenerates a connection Id.
|
||||
/// </summary>
|
||||
internal static class ConnectionPool
|
||||
/// <param name="id">Connection Id</param>
|
||||
/// <returns></returns>
|
||||
public static bool AddConnection(ConnectionData connection, out ulong availableConnectionId)
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting point for connection identifiers.
|
||||
/// ConnectionId's are incremented sequentially by 1.
|
||||
/// </summary>
|
||||
private static readonly NumberSpace _ns = new NumberSpace(QuicSettings.MaximumConnectionIds);
|
||||
availableConnectionId = 0;
|
||||
|
||||
private static readonly Dictionary<ulong, QuicConnection> _pool = new Dictionary<ulong, QuicConnection>();
|
||||
|
||||
private static readonly List<QuicConnection> _draining = new List<QuicConnection>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a connection to the connection pool.
|
||||
/// For now assume that the client connection id is valid, and just send it back.
|
||||
/// Later this should change in a way that the server validates, and regenerates a connection Id.
|
||||
/// </summary>
|
||||
/// <param name="id">Connection Id</param>
|
||||
/// <returns></returns>
|
||||
public static bool AddConnection(ConnectionData connection, out ulong availableConnectionId)
|
||||
if (_pool.ContainsKey(connection.ConnectionId.Value))
|
||||
{
|
||||
availableConnectionId = 0;
|
||||
|
||||
if (_pool.ContainsKey(connection.ConnectionId.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_pool.Count > QuicSettings.MaximumConnectionIds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
availableConnectionId = _ns.Get();
|
||||
|
||||
connection.PeerConnectionId = connection.ConnectionId;
|
||||
_pool.Add(availableConnectionId, new QuicConnection(connection));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveConnection(ulong id)
|
||||
if (_pool.Count > QuicSettings.MaximumConnectionIds)
|
||||
{
|
||||
if (_pool.ContainsKey(id))
|
||||
{
|
||||
_pool.Remove(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static QuicConnection Find(ulong id)
|
||||
{
|
||||
if (_pool.ContainsKey(id) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
availableConnectionId = _ns.Get();
|
||||
|
||||
return _pool[id];
|
||||
connection.PeerConnectionId = connection.ConnectionId;
|
||||
_pool.Add(availableConnectionId, new QuicConnection(connection));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void RemoveConnection(ulong id)
|
||||
{
|
||||
if (_pool.ContainsKey(id))
|
||||
{
|
||||
_pool.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static QuicConnection Find(ulong id)
|
||||
{
|
||||
if (_pool.ContainsKey(id) == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _pool[id];
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
namespace EonaCat.Quic.Connections
|
||||
{
|
||||
// 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.Quic.Connections;
|
||||
// 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 ConnectionState
|
||||
{
|
||||
Open,
|
||||
Closing,
|
||||
Closed,
|
||||
Draining
|
||||
}
|
||||
public enum ConnectionState
|
||||
{
|
||||
Open,
|
||||
Closing,
|
||||
Closed,
|
||||
Draining
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using EonaCat.Quic.Constants;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Constants;
|
||||
using EonaCat.Quic.Events;
|
||||
using EonaCat.Quic.Exceptions;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
@ -9,315 +10,315 @@ using EonaCat.Quic.Infrastructure.Packets;
|
|||
using EonaCat.Quic.Infrastructure.Settings;
|
||||
using EonaCat.Quic.InternalInfrastructure;
|
||||
using EonaCat.Quic.Streams;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.Quic.Connections
|
||||
namespace EonaCat.Quic.Connections;
|
||||
// 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 class QuicConnection
|
||||
{
|
||||
// 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.
|
||||
private readonly NumberSpace _numberSpace = new();
|
||||
private readonly PacketWireTransfer _pwt;
|
||||
private readonly Dictionary<ulong, QuicStream> _streams;
|
||||
|
||||
public class QuicConnection
|
||||
private ulong _currentTransferRate;
|
||||
private string _lastError;
|
||||
private ConnectionState _state;
|
||||
|
||||
internal QuicConnection(ConnectionData connection)
|
||||
{
|
||||
private readonly NumberSpace _numberSpace = new NumberSpace();
|
||||
private readonly PacketWireTransfer _pwt;
|
||||
_currentTransferRate = 0;
|
||||
_state = ConnectionState.Open;
|
||||
_lastError = string.Empty;
|
||||
_streams = new Dictionary<ulong, QuicStream>();
|
||||
_pwt = connection.PWT;
|
||||
|
||||
private ulong _currentTransferRate;
|
||||
private ConnectionState _state;
|
||||
private string _lastError;
|
||||
private readonly Dictionary<ulong, QuicStream> _streams;
|
||||
ConnectionId = connection.ConnectionId;
|
||||
PeerConnectionId = connection.PeerConnectionId;
|
||||
// Also creates a new number space
|
||||
PacketCreator = new PacketCreator(ConnectionId, PeerConnectionId);
|
||||
MaxData = QuicSettings.MaxData;
|
||||
MaxStreams = QuicSettings.MaximumStreamId;
|
||||
}
|
||||
|
||||
public IntegerParts ConnectionId { get; private set; }
|
||||
public IntegerParts PeerConnectionId { get; private set; }
|
||||
public IntegerParts ConnectionId { get; }
|
||||
public IntegerParts PeerConnectionId { get; }
|
||||
|
||||
public PacketCreator PacketCreator { get; private set; }
|
||||
public ulong MaxData { get; private set; }
|
||||
public ulong MaxStreams { get; private set; }
|
||||
public PacketCreator PacketCreator { get; }
|
||||
public ulong MaxData { get; private set; }
|
||||
public ulong MaxStreams { get; private set; }
|
||||
|
||||
public StreamOpenedEvent OnStreamOpened { get; set; }
|
||||
public ConnectionClosedEvent OnConnectionClosed { get; set; }
|
||||
public StreamOpenedEvent OnStreamOpened { get; set; }
|
||||
public ConnectionClosedEvent OnConnectionClosed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new stream for sending/receiving data.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the stream (Uni-Bidirectional)</param>
|
||||
/// <returns>A new stream instance or Null if the connection is terminated.</returns>
|
||||
public QuicStream CreateStream(StreamType type)
|
||||
/// <summary>
|
||||
/// Creates a new stream for sending/receiving data.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the stream (Uni-Bidirectional)</param>
|
||||
/// <returns>A new stream instance or Null if the connection is terminated.</returns>
|
||||
public QuicStream CreateStream(StreamType type)
|
||||
{
|
||||
var streamId = _numberSpace.Get();
|
||||
if (_state != ConnectionState.Open)
|
||||
{
|
||||
uint streamId = _numberSpace.Get();
|
||||
if (_state != ConnectionState.Open)
|
||||
return null;
|
||||
}
|
||||
|
||||
var stream = new QuicStream(this, new StreamId(streamId, type));
|
||||
_streams.Add(streamId, stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public QuicStream ProcessFrames(List<Frame> frames)
|
||||
{
|
||||
QuicStream stream = null;
|
||||
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
if (frame.Type == 0x01)
|
||||
{
|
||||
return null;
|
||||
OnRstStreamFrame(frame);
|
||||
}
|
||||
|
||||
QuicStream stream = new QuicStream(this, new StreamId(streamId, type));
|
||||
_streams.Add(streamId, stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public QuicStream ProcessFrames(List<Frame> frames)
|
||||
{
|
||||
QuicStream stream = null;
|
||||
|
||||
foreach (Frame frame in frames)
|
||||
if (frame.Type == 0x04)
|
||||
{
|
||||
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);
|
||||
}
|
||||
OnRstStreamFrame(frame);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void IncrementRate(int length)
|
||||
{
|
||||
_currentTransferRate += (uint)length;
|
||||
}
|
||||
|
||||
public bool MaximumReached()
|
||||
{
|
||||
if (_currentTransferRate >= MaxData)
|
||||
if (frame.Type >= 0x08 && frame.Type <= 0x0f)
|
||||
{
|
||||
return true;
|
||||
stream = OnStreamFrame(frame);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnConnectionCloseFrame(Frame frame)
|
||||
{
|
||||
ConnectionCloseFrame ccf = (ConnectionCloseFrame)frame;
|
||||
_state = ConnectionState.Draining;
|
||||
_lastError = ccf.ReasonPhrase;
|
||||
|
||||
OnConnectionClosed?.Invoke(this);
|
||||
}
|
||||
|
||||
private void OnRstStreamFrame(Frame frame)
|
||||
{
|
||||
ResetStreamFrame rsf = (ResetStreamFrame)frame;
|
||||
if (_streams.ContainsKey(rsf.StreamId))
|
||||
if (frame.Type == 0x10)
|
||||
{
|
||||
// Find and reset the stream
|
||||
QuicStream stream = _streams[rsf.StreamId];
|
||||
stream.ResetStream(rsf);
|
||||
OnMaxDataFrame(frame);
|
||||
}
|
||||
|
||||
// Remove the stream from the connection
|
||||
_streams.Remove(rsf.StreamId);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private QuicStream OnStreamFrame(Frame frame)
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void IncrementRate(int length)
|
||||
{
|
||||
_currentTransferRate += (uint)length;
|
||||
}
|
||||
|
||||
public bool MaximumReached()
|
||||
{
|
||||
if (_currentTransferRate >= MaxData)
|
||||
{
|
||||
QuicStream stream;
|
||||
return true;
|
||||
}
|
||||
|
||||
StreamFrame sf = (StreamFrame)frame;
|
||||
StreamId streamId = sf.StreamId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_streams.ContainsKey(streamId.Id) == false)
|
||||
private void OnConnectionCloseFrame(Frame frame)
|
||||
{
|
||||
var ccf = (ConnectionCloseFrame)frame;
|
||||
_state = ConnectionState.Draining;
|
||||
_lastError = ccf.ReasonPhrase;
|
||||
|
||||
OnConnectionClosed?.Invoke(this);
|
||||
}
|
||||
|
||||
private void OnRstStreamFrame(Frame frame)
|
||||
{
|
||||
var rsf = (ResetStreamFrame)frame;
|
||||
if (_streams.ContainsKey(rsf.StreamId))
|
||||
{
|
||||
// Find and reset the stream
|
||||
var stream = _streams[rsf.StreamId];
|
||||
stream.ResetStream(rsf);
|
||||
|
||||
// Remove the stream from the connection
|
||||
_streams.Remove(rsf.StreamId);
|
||||
}
|
||||
}
|
||||
|
||||
private QuicStream OnStreamFrame(Frame frame)
|
||||
{
|
||||
QuicStream stream;
|
||||
|
||||
var sf = (StreamFrame)frame;
|
||||
StreamId streamId = sf.StreamId;
|
||||
|
||||
if (_streams.ContainsKey(streamId.Id) == false)
|
||||
{
|
||||
stream = new QuicStream(this, streamId);
|
||||
|
||||
if ((ulong)_streams.Count < MaxStreams)
|
||||
{
|
||||
stream = new QuicStream(this, streamId);
|
||||
|
||||
if ((ulong)_streams.Count < MaxStreams)
|
||||
{
|
||||
_streams.Add(streamId.Id, stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMaximumStreamReachedError();
|
||||
}
|
||||
|
||||
OnStreamOpened?.Invoke(stream);
|
||||
_streams.Add(streamId.Id, stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = _streams[streamId.Id];
|
||||
SendMaximumStreamReachedError();
|
||||
}
|
||||
|
||||
stream.ProcessData(sf);
|
||||
|
||||
return stream;
|
||||
OnStreamOpened?.Invoke(stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = _streams[streamId.Id];
|
||||
}
|
||||
|
||||
private void OnMaxDataFrame(Frame frame)
|
||||
stream.ProcessData(sf);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private void OnMaxDataFrame(Frame frame)
|
||||
{
|
||||
var sf = (MaxDataFrame)frame;
|
||||
if (sf.MaximumData.Value > MaxData)
|
||||
{
|
||||
MaxDataFrame sf = (MaxDataFrame)frame;
|
||||
if (sf.MaximumData.Value > MaxData)
|
||||
MaxData = sf.MaximumData.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaxStreamDataFrame(Frame frame)
|
||||
{
|
||||
var msdf = (MaxStreamDataFrame)frame;
|
||||
StreamId streamId = msdf.StreamId;
|
||||
if (_streams.ContainsKey(streamId.Id))
|
||||
{
|
||||
// Find and set the new maximum stream data on the stream
|
||||
var stream = _streams[streamId.Id];
|
||||
stream.SetMaximumStreamData(msdf.MaximumStreamData.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaxStreamFrame(Frame frame)
|
||||
{
|
||||
var msf = (MaxStreamsFrame)frame;
|
||||
if (msf.MaximumStreams > MaxStreams)
|
||||
{
|
||||
MaxStreams = msf.MaximumStreams.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataBlockedFrame(Frame frame)
|
||||
{
|
||||
TerminateConnection();
|
||||
}
|
||||
|
||||
public QuicStream OpenStream()
|
||||
{
|
||||
QuicStream stream = null;
|
||||
|
||||
while (stream == null)
|
||||
{
|
||||
var packet = _pwt.ReadPacket();
|
||||
if (packet is ShortHeaderPacket shp)
|
||||
{
|
||||
MaxData = sf.MaximumData.Value;
|
||||
stream = ProcessFrames(shp.GetFrames());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaxStreamDataFrame(Frame frame)
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client only!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal void ReceivePacket()
|
||||
{
|
||||
var packet = _pwt.ReadPacket();
|
||||
|
||||
if (packet is ShortHeaderPacket shp)
|
||||
{
|
||||
MaxStreamDataFrame msdf = (MaxStreamDataFrame)frame;
|
||||
StreamId streamId = msdf.StreamId;
|
||||
if (_streams.ContainsKey(streamId.Id))
|
||||
ProcessFrames(shp.GetFrames());
|
||||
}
|
||||
|
||||
// If the connection has been closed
|
||||
if (_state == ConnectionState.Draining)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_lastError))
|
||||
{
|
||||
// Find and set the new maximum stream data on the stream
|
||||
QuicStream stream = _streams[streamId.Id];
|
||||
stream.SetMaximumStreamData(msdf.MaximumStreamData.Value);
|
||||
_lastError = "Protocol error";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaxStreamFrame(Frame frame)
|
||||
{
|
||||
MaxStreamsFrame msf = (MaxStreamsFrame)frame;
|
||||
if (msf.MaximumStreams > MaxStreams)
|
||||
{
|
||||
MaxStreams = msf.MaximumStreams.Value;
|
||||
}
|
||||
}
|
||||
TerminateConnection();
|
||||
|
||||
private void OnDataBlockedFrame(Frame frame)
|
||||
throw new ConnectionException(_lastError);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool SendData(Packet packet)
|
||||
{
|
||||
return _pwt.SendPacket(packet);
|
||||
}
|
||||
|
||||
internal void TerminateConnection()
|
||||
{
|
||||
_state = ConnectionState.Draining;
|
||||
_streams.Clear();
|
||||
|
||||
ConnectionPool.RemoveConnection(ConnectionId);
|
||||
}
|
||||
|
||||
internal void SendMaximumStreamReachedError()
|
||||
{
|
||||
var packet =
|
||||
PacketCreator.CreateConnectionClosePacket(ErrorCode.STREAM_LIMIT_ERROR, 0x00,
|
||||
ErrorConstants.MaxNumberOfStreams);
|
||||
Send(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to send protocol packets to the peer.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
/// <returns></returns>
|
||||
internal bool Send(Packet packet)
|
||||
{
|
||||
// Encode the packet
|
||||
var data = packet.Encode();
|
||||
|
||||
// Increment the connection transfer rate
|
||||
IncrementRate(data.Length);
|
||||
|
||||
// If the maximum transfer rate is reached, send FLOW_CONTROL_ERROR
|
||||
if (MaximumReached())
|
||||
{
|
||||
packet = PacketCreator.CreateConnectionClosePacket(ErrorCode.FLOW_CONTROL_ERROR, 0x00,
|
||||
ErrorConstants.MaxDataTransfer);
|
||||
|
||||
TerminateConnection();
|
||||
}
|
||||
|
||||
internal QuicConnection(ConnectionData connection)
|
||||
// Ignore empty packets
|
||||
if (data == null || data.Length <= 0)
|
||||
{
|
||||
_currentTransferRate = 0;
|
||||
_state = ConnectionState.Open;
|
||||
_lastError = string.Empty;
|
||||
_streams = new Dictionary<ulong, QuicStream>();
|
||||
_pwt = connection.PWT;
|
||||
|
||||
ConnectionId = connection.ConnectionId;
|
||||
PeerConnectionId = connection.PeerConnectionId;
|
||||
// Also creates a new number space
|
||||
PacketCreator = new PacketCreator(ConnectionId, PeerConnectionId);
|
||||
MaxData = QuicSettings.MaxData;
|
||||
MaxStreams = QuicSettings.MaximumStreamId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public QuicStream OpenStream()
|
||||
{
|
||||
QuicStream stream = null;
|
||||
var result = _pwt.SendPacket(packet);
|
||||
|
||||
while (stream == null)
|
||||
{
|
||||
Packet packet = _pwt.ReadPacket();
|
||||
if (packet is ShortHeaderPacket shp)
|
||||
{
|
||||
stream = ProcessFrames(shp.GetFrames());
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client only!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal void ReceivePacket()
|
||||
{
|
||||
Packet packet = _pwt.ReadPacket();
|
||||
|
||||
if (packet is ShortHeaderPacket shp)
|
||||
{
|
||||
ProcessFrames(shp.GetFrames());
|
||||
}
|
||||
|
||||
// If the connection has been closed
|
||||
if (_state == ConnectionState.Draining)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_lastError))
|
||||
{
|
||||
_lastError = "Protocol error";
|
||||
}
|
||||
|
||||
TerminateConnection();
|
||||
|
||||
throw new ConnectionException(_lastError);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool SendData(Packet packet)
|
||||
{
|
||||
return _pwt.SendPacket(packet);
|
||||
}
|
||||
|
||||
internal void TerminateConnection()
|
||||
{
|
||||
_state = ConnectionState.Draining;
|
||||
_streams.Clear();
|
||||
|
||||
ConnectionPool.RemoveConnection(this.ConnectionId);
|
||||
}
|
||||
|
||||
internal void SendMaximumStreamReachedError()
|
||||
{
|
||||
ShortHeaderPacket packet = PacketCreator.CreateConnectionClosePacket(Infrastructure.ErrorCode.STREAM_LIMIT_ERROR, 0x00, ErrorConstants.MaxNumberOfStreams);
|
||||
Send(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to send protocol packets to the peer.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
/// <returns></returns>
|
||||
internal bool Send(Packet packet)
|
||||
{
|
||||
// Encode the packet
|
||||
byte[] data = packet.Encode();
|
||||
|
||||
// Increment the connection transfer rate
|
||||
IncrementRate(data.Length);
|
||||
|
||||
// If the maximum transfer rate is reached, send FLOW_CONTROL_ERROR
|
||||
if (MaximumReached())
|
||||
{
|
||||
packet = PacketCreator.CreateConnectionClosePacket(Infrastructure.ErrorCode.FLOW_CONTROL_ERROR, 0x00, ErrorConstants.MaxDataTransfer);
|
||||
|
||||
TerminateConnection();
|
||||
}
|
||||
|
||||
// Ignore empty packets
|
||||
if (data == null || data.Length <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result = _pwt.SendPacket(packet);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
namespace EonaCat.Quic.Constants
|
||||
{
|
||||
// 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.Quic.Constants;
|
||||
// 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 class ErrorConstants
|
||||
{
|
||||
public const string ServerTooBusy = "The server is too busy to process your request.";
|
||||
public const string MaxDataTransfer = "Maximum data transfer reached.";
|
||||
public const string MaxNumberOfStreams = "Maximum number of streams reached.";
|
||||
public const string PMTUNotReached = "PMTU have not been reached.";
|
||||
}
|
||||
public class ErrorConstants
|
||||
{
|
||||
public const string ServerTooBusy = "The server is too busy to process your request.";
|
||||
public const string MaxDataTransfer = "Maximum data transfer reached.";
|
||||
public const string MaxNumberOfStreams = "Maximum number of streams reached.";
|
||||
public const string PMTUNotReached = "PMTU have not been reached.";
|
||||
}
|
|
@ -1,77 +1,74 @@
|
|||
using EonaCat.Quic.Streams;
|
||||
|
||||
namespace EonaCat.Quic.Context
|
||||
namespace EonaCat.Quic.Context;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to represent the stream.
|
||||
/// </summary>
|
||||
public class QuicStreamContext
|
||||
{
|
||||
// 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.
|
||||
/// <summary>
|
||||
/// Internal constructor to prevent creating the context outside the scope of Quic.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
internal QuicStreamContext(QuicStream stream)
|
||||
{
|
||||
Stream = stream;
|
||||
StreamId = stream.StreamId;
|
||||
}
|
||||
///// <summary>
|
||||
///// The connection's context.
|
||||
///// </summary>
|
||||
//public QuicContext ConnectionContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to represent the stream.
|
||||
/// Data received
|
||||
/// </summary>
|
||||
public class QuicStreamContext
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique stream identifier
|
||||
/// </summary>
|
||||
public ulong StreamId { get; private set; }
|
||||
|
||||
internal QuicStream Stream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Send data to the client.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public bool Send(byte[] data)
|
||||
{
|
||||
///// <summary>
|
||||
///// The connection's context.
|
||||
///// </summary>
|
||||
//public QuicContext ConnectionContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data received
|
||||
/// </summary>
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique stream identifier
|
||||
/// </summary>
|
||||
public ulong StreamId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Send data to the client.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public bool Send(byte[] data)
|
||||
if (Stream.CanSendData() == false)
|
||||
{
|
||||
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);
|
||||
|
||||
// bool result = ConnectionContext.Send(packet);
|
||||
|
||||
//return result;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
// Ignore empty packets
|
||||
if (data == null || data.Length <= 0)
|
||||
{
|
||||
// TODO: Close out the stream by sending appropriate packets to the peer
|
||||
return true;
|
||||
}
|
||||
|
||||
internal QuicStream Stream { get; set; }
|
||||
// Packet packet = ConnectionContext.Connection.PacketCreator.CreateDataPacket(StreamId, data);
|
||||
|
||||
/// <summary>
|
||||
/// Internal constructor to prevent creating the context outside the scope of Quic.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
internal QuicStreamContext(QuicStream stream)
|
||||
{
|
||||
Stream = stream;
|
||||
StreamId = stream.StreamId;
|
||||
}
|
||||
// bool result = ConnectionContext.Send(packet);
|
||||
|
||||
internal void SetData(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
//return result;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
// TODO: Close out the stream by sending appropriate packets to the peer
|
||||
}
|
||||
|
||||
internal void SetData(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Streams;
|
||||
|
||||
namespace EonaCat.Quic.Events
|
||||
{
|
||||
// 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.Quic.Events;
|
||||
// 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 delegate void ClientConnectedEvent(QuicConnection connection);
|
||||
public delegate void ClientConnectedEvent(QuicConnection connection);
|
||||
|
||||
public delegate void StreamOpenedEvent(QuicStream stream);
|
||||
public delegate void StreamOpenedEvent(QuicStream stream);
|
||||
|
||||
public delegate void StreamDataReceivedEvent(QuicStream stream, byte[] data);
|
||||
public delegate void StreamDataReceivedEvent(QuicStream stream, byte[] data);
|
||||
|
||||
public delegate void ConnectionClosedEvent(QuicConnection connection);
|
||||
}
|
||||
public delegate void ConnectionClosedEvent(QuicConnection connection);
|
|
@ -1,19 +1,17 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Streams;
|
||||
|
||||
namespace EonaCat.Quic.Events
|
||||
namespace EonaCat.Quic.Events;
|
||||
// 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 class QuicStreamEventArgs
|
||||
{
|
||||
// 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 QuicStream Stream { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
|
||||
public class QuicStreamEventArgs
|
||||
{
|
||||
public QuicStream Stream { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
|
||||
public class QuicConnectionEventArgs
|
||||
{
|
||||
public QuicConnection Connection { get; set; }
|
||||
}
|
||||
public class QuicConnectionEventArgs
|
||||
{
|
||||
public QuicConnection Connection { get; set; }
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Exceptions
|
||||
{
|
||||
// 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.Quic.Exceptions;
|
||||
// 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 class ConnectionException : Exception
|
||||
public class ConnectionException : Exception
|
||||
{
|
||||
public ConnectionException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
public ConnectionException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Exceptions
|
||||
namespace EonaCat.Quic.Exceptions;
|
||||
// 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 class ServerNotStartedException : Exception
|
||||
{
|
||||
// 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 class ServerNotStartedException : Exception
|
||||
public ServerNotStartedException()
|
||||
{
|
||||
public ServerNotStartedException()
|
||||
{ }
|
||||
}
|
||||
|
||||
public ServerNotStartedException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
public ServerNotStartedException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Exceptions
|
||||
namespace EonaCat.Quic.Exceptions;
|
||||
// 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 class StreamException : Exception
|
||||
{
|
||||
// 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 class StreamException : Exception
|
||||
public StreamException()
|
||||
{
|
||||
public StreamException()
|
||||
{ }
|
||||
}
|
||||
|
||||
public StreamException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
public StreamException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,99 +1,97 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Helpers
|
||||
namespace EonaCat.Quic.Helpers;
|
||||
// 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 class ByteArray
|
||||
{
|
||||
// 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.
|
||||
private readonly byte[] _array;
|
||||
private readonly int _length;
|
||||
|
||||
public class ByteArray
|
||||
private int _offset;
|
||||
|
||||
public ByteArray(byte[] array)
|
||||
{
|
||||
private readonly byte[] _array;
|
||||
private readonly int _length;
|
||||
_array = array;
|
||||
_length = array.Length;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
private int _offset;
|
||||
public byte ReadByte()
|
||||
{
|
||||
var result = _array[_offset++];
|
||||
return result;
|
||||
}
|
||||
|
||||
public ByteArray(byte[] array)
|
||||
{
|
||||
_array = array;
|
||||
_length = array.Length;
|
||||
_offset = 0;
|
||||
}
|
||||
public byte PeekByte()
|
||||
{
|
||||
var result = _array[_offset];
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
byte result = _array[_offset++];
|
||||
return result;
|
||||
}
|
||||
public byte[] ReadBytes(int count)
|
||||
{
|
||||
var bytes = new byte[count];
|
||||
Buffer.BlockCopy(_array, _offset, bytes, 0, count);
|
||||
|
||||
public byte PeekByte()
|
||||
{
|
||||
byte result = _array[_offset];
|
||||
return result;
|
||||
}
|
||||
_offset += count;
|
||||
|
||||
public byte[] ReadBytes(int count)
|
||||
{
|
||||
byte[] bytes = new byte[count];
|
||||
Buffer.BlockCopy(_array, _offset, bytes, 0, count);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
_offset += count;
|
||||
public byte[] ReadBytes(IntegerVar count)
|
||||
{
|
||||
return ReadBytes(count.Value);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
var bytes = ReadBytes(2);
|
||||
var result = ByteHelpers.ToUInt16(bytes);
|
||||
|
||||
public byte[] ReadBytes(IntegerVar count)
|
||||
{
|
||||
return ReadBytes(count.Value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
byte[] bytes = ReadBytes(2);
|
||||
ushort result = ByteHelpers.ToUInt16(bytes);
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
var bytes = ReadBytes(4);
|
||||
var result = ByteHelpers.ToUInt32(bytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
byte[] bytes = ReadBytes(4);
|
||||
uint result = ByteHelpers.ToUInt32(bytes);
|
||||
public IntegerVar ReadIntegerVar()
|
||||
{
|
||||
// Set Token Length and Token
|
||||
var initial = PeekByte();
|
||||
var size = IntegerVar.Size(initial);
|
||||
|
||||
return result;
|
||||
}
|
||||
var bytes = new byte[size];
|
||||
Buffer.BlockCopy(_array, _offset, bytes, 0, size);
|
||||
_offset += size;
|
||||
|
||||
public IntegerVar ReadIntegerVar()
|
||||
{
|
||||
// Set Token Length and Token
|
||||
byte initial = PeekByte();
|
||||
int size = IntegerVar.Size(initial);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[size];
|
||||
Buffer.BlockCopy(_array, _offset, bytes, 0, size);
|
||||
_offset += size;
|
||||
public IntegerParts ReadGranularInteger(int size)
|
||||
{
|
||||
var data = ReadBytes(size);
|
||||
IntegerParts result = data;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IntegerParts ReadGranularInteger(int size)
|
||||
{
|
||||
byte[] data = ReadBytes(size);
|
||||
IntegerParts result = data;
|
||||
public StreamId ReadStreamId()
|
||||
{
|
||||
var streamId = ReadBytes(8);
|
||||
StreamId result = streamId;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public StreamId ReadStreamId()
|
||||
{
|
||||
byte[] streamId = ReadBytes(8);
|
||||
StreamId result = streamId;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool HasData()
|
||||
{
|
||||
return _offset < _length;
|
||||
}
|
||||
public bool HasData()
|
||||
{
|
||||
return _offset < _length;
|
||||
}
|
||||
}
|
|
@ -1,94 +1,92 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Quic.Helpers
|
||||
namespace EonaCat.Quic.Helpers;
|
||||
// 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 static class ByteHelpers
|
||||
{
|
||||
// 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 static class ByteHelpers
|
||||
public static byte[] GetBytes(ulong integer)
|
||||
{
|
||||
public static byte[] GetBytes(ulong integer)
|
||||
var result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
Array.Reverse(result);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes(uint integer)
|
||||
{
|
||||
byte[] result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
public static byte[] GetBytes(uint integer)
|
||||
{
|
||||
var result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(result);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes(ushort integer)
|
||||
{
|
||||
byte[] result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
public static byte[] GetBytes(ushort integer)
|
||||
{
|
||||
var result = BitConverter.GetBytes(integer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(result);
|
||||
}
|
||||
|
||||
public static byte[] GetBytes(string str)
|
||||
{
|
||||
byte[] result = Encoding.UTF8.GetBytes(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
public static byte[] GetBytes(string str)
|
||||
{
|
||||
var result = Encoding.UTF8.GetBytes(str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ulong ToUInt64(byte[] data)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
public static ulong ToUInt64(byte[] data)
|
||||
var result = BitConverter.ToUInt64(data, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static uint ToUInt32(byte[] data)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
ulong result = BitConverter.ToUInt64(data, 0);
|
||||
|
||||
return result;
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
public static uint ToUInt32(byte[] data)
|
||||
var result = BitConverter.ToUInt32(data, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ushort ToUInt16(byte[] data)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
uint result = BitConverter.ToUInt32(data, 0);
|
||||
|
||||
return result;
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
public static ushort ToUInt16(byte[] data)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(data);
|
||||
}
|
||||
var result = BitConverter.ToUInt16(data, 0);
|
||||
|
||||
ushort result = BitConverter.ToUInt16(data, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
public static string GetString(byte[] str)
|
||||
{
|
||||
var result = Encoding.UTF8.GetString(str);
|
||||
|
||||
public static string GetString(byte[] str)
|
||||
{
|
||||
string result = Encoding.UTF8.GetString(str);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,99 +1,97 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Helpers
|
||||
namespace EonaCat.Quic.Helpers;
|
||||
// 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 class IntegerParts
|
||||
{
|
||||
// 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 const ulong MaxValue = 18446744073709551615;
|
||||
|
||||
public class IntegerParts
|
||||
public IntegerParts(ulong integer)
|
||||
{
|
||||
public const ulong MaxValue = 18446744073709551615;
|
||||
Value = integer;
|
||||
}
|
||||
|
||||
public ulong Value { get; }
|
||||
public ulong Value { get; }
|
||||
|
||||
public byte Size => RequiredBytes(Value);
|
||||
public byte Size => RequiredBytes(Value);
|
||||
|
||||
public IntegerParts(ulong integer)
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encode(Value);
|
||||
}
|
||||
|
||||
public static implicit operator byte[](IntegerParts integer)
|
||||
{
|
||||
return Encode(integer.Value);
|
||||
}
|
||||
|
||||
public static implicit operator IntegerParts(byte[] bytes)
|
||||
{
|
||||
return new IntegerParts(Decode(bytes));
|
||||
}
|
||||
|
||||
public static implicit operator IntegerParts(ulong integer)
|
||||
{
|
||||
return new IntegerParts(integer);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(IntegerParts integer)
|
||||
{
|
||||
return integer.Value;
|
||||
}
|
||||
|
||||
public static byte[] Encode(ulong integer)
|
||||
{
|
||||
var requiredBytes = RequiredBytes(integer);
|
||||
var offset = 8 - requiredBytes;
|
||||
|
||||
var uInt64Bytes = ByteHelpers.GetBytes(integer);
|
||||
|
||||
var result = new byte[requiredBytes];
|
||||
Buffer.BlockCopy(uInt64Bytes, offset, result, 0, requiredBytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ulong Decode(byte[] bytes)
|
||||
{
|
||||
var i = 8 - bytes.Length;
|
||||
var buffer = new byte[8];
|
||||
|
||||
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
|
||||
|
||||
var res = ByteHelpers.ToUInt64(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static byte RequiredBytes(ulong integer)
|
||||
{
|
||||
byte result = 0;
|
||||
|
||||
if (integer <= byte.MaxValue) /* 255 */
|
||||
{
|
||||
Value = integer;
|
||||
result = 1;
|
||||
}
|
||||
else if (integer <= ushort.MaxValue) /* 65535 */
|
||||
{
|
||||
result = 2;
|
||||
}
|
||||
else if (integer <= uint.MaxValue) /* 4294967295 */
|
||||
{
|
||||
result = 4;
|
||||
}
|
||||
else if (integer <= ulong.MaxValue) /* 18446744073709551615 */
|
||||
{
|
||||
result = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Value is larger than GranularInteger.MaxValue.");
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encode(this.Value);
|
||||
}
|
||||
|
||||
public static implicit operator byte[](IntegerParts integer)
|
||||
{
|
||||
return Encode(integer.Value);
|
||||
}
|
||||
|
||||
public static implicit operator IntegerParts(byte[] bytes)
|
||||
{
|
||||
return new IntegerParts(Decode(bytes));
|
||||
}
|
||||
|
||||
public static implicit operator IntegerParts(ulong integer)
|
||||
{
|
||||
return new IntegerParts(integer);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(IntegerParts integer)
|
||||
{
|
||||
return integer.Value;
|
||||
}
|
||||
|
||||
public static byte[] Encode(ulong integer)
|
||||
{
|
||||
byte requiredBytes = RequiredBytes(integer);
|
||||
int offset = 8 - requiredBytes;
|
||||
|
||||
byte[] uInt64Bytes = ByteHelpers.GetBytes(integer);
|
||||
|
||||
byte[] result = new byte[requiredBytes];
|
||||
Buffer.BlockCopy(uInt64Bytes, offset, result, 0, requiredBytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ulong Decode(byte[] bytes)
|
||||
{
|
||||
int i = 8 - bytes.Length;
|
||||
byte[] buffer = new byte[8];
|
||||
|
||||
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
|
||||
|
||||
ulong res = ByteHelpers.ToUInt64(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static byte RequiredBytes(ulong integer)
|
||||
{
|
||||
byte result = 0;
|
||||
|
||||
if (integer <= byte.MaxValue) /* 255 */
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
else if (integer <= ushort.MaxValue) /* 65535 */
|
||||
{
|
||||
result = 2;
|
||||
}
|
||||
else if (integer <= uint.MaxValue) /* 4294967295 */
|
||||
{
|
||||
result = 4;
|
||||
}
|
||||
else if (integer <= ulong.MaxValue) /* 18446744073709551615 */
|
||||
{
|
||||
result = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Value is larger than GranularInteger.MaxValue.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,69 +1,67 @@
|
|||
namespace EonaCat.Quic.Helpers
|
||||
{
|
||||
// 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.Quic.Helpers;
|
||||
// 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 StreamType
|
||||
public enum StreamType
|
||||
{
|
||||
ClientBidirectional = 0x0,
|
||||
ServerBidirectional = 0x1,
|
||||
ClientUnidirectional = 0x2,
|
||||
ServerUnidirectional = 0x3
|
||||
}
|
||||
|
||||
public class StreamId
|
||||
{
|
||||
public StreamId(ulong id, StreamType type)
|
||||
{
|
||||
ClientBidirectional = 0x0,
|
||||
ServerBidirectional = 0x1,
|
||||
ClientUnidirectional = 0x2,
|
||||
ServerUnidirectional = 0x3
|
||||
Id = id;
|
||||
Type = type;
|
||||
IntegerValue = (id << 2) | (ulong)type;
|
||||
}
|
||||
|
||||
public class StreamId
|
||||
public ulong Id { get; }
|
||||
public ulong IntegerValue { get; }
|
||||
public StreamType Type { get; }
|
||||
|
||||
public static implicit operator byte[](StreamId id)
|
||||
{
|
||||
public ulong Id { get; }
|
||||
public ulong IntegerValue { get; }
|
||||
public StreamType Type { get; private set; }
|
||||
return Encode(id.Id, id.Type);
|
||||
}
|
||||
|
||||
public StreamId(ulong id, StreamType type)
|
||||
{
|
||||
Id = id;
|
||||
Type = type;
|
||||
IntegerValue = id << 2 | (ulong)type;
|
||||
}
|
||||
public static implicit operator StreamId(byte[] data)
|
||||
{
|
||||
return Decode(data);
|
||||
}
|
||||
|
||||
public static implicit operator byte[](StreamId id)
|
||||
{
|
||||
return Encode(id.Id, id.Type);
|
||||
}
|
||||
public static implicit operator ulong(StreamId streamId)
|
||||
{
|
||||
return streamId.Id;
|
||||
}
|
||||
|
||||
public static implicit operator StreamId(byte[] data)
|
||||
{
|
||||
return Decode(data);
|
||||
}
|
||||
public static implicit operator StreamId(IntegerVar integer)
|
||||
{
|
||||
return Decode(ByteHelpers.GetBytes(integer.Value));
|
||||
}
|
||||
|
||||
public static implicit operator ulong(StreamId streamId)
|
||||
{
|
||||
return streamId.Id;
|
||||
}
|
||||
public static byte[] Encode(ulong id, StreamType type)
|
||||
{
|
||||
var identifier = (id << 2) | (ulong)type;
|
||||
|
||||
public static implicit operator StreamId(IntegerVar integer)
|
||||
{
|
||||
return Decode(ByteHelpers.GetBytes(integer.Value));
|
||||
}
|
||||
var result = ByteHelpers.GetBytes(identifier);
|
||||
|
||||
public static byte[] Encode(ulong id, StreamType type)
|
||||
{
|
||||
ulong identifier = id << 2 | (ulong)type;
|
||||
return result;
|
||||
}
|
||||
|
||||
byte[] result = ByteHelpers.GetBytes(identifier);
|
||||
public static StreamId Decode(byte[] data)
|
||||
{
|
||||
StreamId result;
|
||||
var id = ByteHelpers.ToUInt64(data);
|
||||
var identifier = id >> 2;
|
||||
var type = 0x03 & id;
|
||||
var streamType = (StreamType)type;
|
||||
|
||||
return result;
|
||||
}
|
||||
result = new StreamId(identifier, streamType);
|
||||
|
||||
public static StreamId Decode(byte[] data)
|
||||
{
|
||||
StreamId result;
|
||||
ulong id = ByteHelpers.ToUInt64(data);
|
||||
ulong identifier = id >> 2;
|
||||
ulong type = 0x03 & id;
|
||||
StreamType streamType = (StreamType)type;
|
||||
|
||||
result = new StreamId(identifier, streamType);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,106 +1,104 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Helpers
|
||||
namespace EonaCat.Quic.Helpers;
|
||||
// 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 class IntegerVar
|
||||
{
|
||||
// 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 const ulong MaxValue = 4611686018427387903;
|
||||
|
||||
public class IntegerVar
|
||||
public IntegerVar(ulong integer)
|
||||
{
|
||||
public const ulong MaxValue = 4611686018427387903;
|
||||
Value = integer;
|
||||
}
|
||||
|
||||
public ulong Value { get; }
|
||||
public ulong Value { get; }
|
||||
|
||||
public IntegerVar(ulong integer)
|
||||
public static implicit operator byte[](IntegerVar integer)
|
||||
{
|
||||
return Encode(integer.Value);
|
||||
}
|
||||
|
||||
public static implicit operator IntegerVar(byte[] bytes)
|
||||
{
|
||||
return new IntegerVar(Decode(bytes));
|
||||
}
|
||||
|
||||
public static implicit operator IntegerVar(ulong integer)
|
||||
{
|
||||
return new IntegerVar(integer);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(IntegerVar integer)
|
||||
{
|
||||
return integer.Value;
|
||||
}
|
||||
|
||||
public static implicit operator IntegerVar(StreamId streamId)
|
||||
{
|
||||
return new IntegerVar(streamId.IntegerValue);
|
||||
}
|
||||
|
||||
public static int Size(byte firstByte)
|
||||
{
|
||||
var result = (int)Math.Pow(2, firstByte >> 6);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encode(Value);
|
||||
}
|
||||
|
||||
public static byte[] Encode(ulong integer)
|
||||
{
|
||||
var requiredBytes = 0;
|
||||
if (integer <= byte.MaxValue >> 2) /* 63 */
|
||||
{
|
||||
Value = integer;
|
||||
requiredBytes = 1;
|
||||
}
|
||||
else if (integer <= ushort.MaxValue >> 2) /* 16383 */
|
||||
{
|
||||
requiredBytes = 2;
|
||||
}
|
||||
else if (integer <= uint.MaxValue >> 2) /* 1073741823 */
|
||||
{
|
||||
requiredBytes = 4;
|
||||
}
|
||||
else if (integer <= ulong.MaxValue >> 2) /* 4611686018427387903 */
|
||||
{
|
||||
requiredBytes = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Value is larger than IntegerVar.MaxValue.");
|
||||
}
|
||||
|
||||
public static implicit operator byte[](IntegerVar integer)
|
||||
{
|
||||
return Encode(integer.Value);
|
||||
}
|
||||
var offset = 8 - requiredBytes;
|
||||
|
||||
public static implicit operator IntegerVar(byte[] bytes)
|
||||
{
|
||||
return new IntegerVar(Decode(bytes));
|
||||
}
|
||||
var uInt64Bytes = ByteHelpers.GetBytes(integer);
|
||||
var first = uInt64Bytes[offset];
|
||||
first = (byte)(first | ((requiredBytes / 2) << 6));
|
||||
uInt64Bytes[offset] = first;
|
||||
|
||||
public static implicit operator IntegerVar(ulong integer)
|
||||
{
|
||||
return new IntegerVar(integer);
|
||||
}
|
||||
var result = new byte[requiredBytes];
|
||||
Buffer.BlockCopy(uInt64Bytes, offset, result, 0, requiredBytes);
|
||||
|
||||
public static implicit operator ulong(IntegerVar integer)
|
||||
{
|
||||
return integer.Value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static implicit operator IntegerVar(StreamId streamId)
|
||||
{
|
||||
return new IntegerVar(streamId.IntegerValue);
|
||||
}
|
||||
public static ulong Decode(byte[] bytes)
|
||||
{
|
||||
var i = 8 - bytes.Length;
|
||||
var buffer = new byte[8];
|
||||
|
||||
public static int Size(byte firstByte)
|
||||
{
|
||||
int result = (int)Math.Pow(2, (firstByte >> 6));
|
||||
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
|
||||
buffer[i] = (byte)(buffer[i] & (255 >> 2));
|
||||
|
||||
return result;
|
||||
}
|
||||
var res = ByteHelpers.ToUInt64(buffer);
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encode(this.Value);
|
||||
}
|
||||
|
||||
public static byte[] Encode(ulong integer)
|
||||
{
|
||||
int requiredBytes = 0;
|
||||
if (integer <= byte.MaxValue >> 2) /* 63 */
|
||||
{
|
||||
requiredBytes = 1;
|
||||
}
|
||||
else if (integer <= ushort.MaxValue >> 2) /* 16383 */
|
||||
{
|
||||
requiredBytes = 2;
|
||||
}
|
||||
else if (integer <= uint.MaxValue >> 2) /* 1073741823 */
|
||||
{
|
||||
requiredBytes = 4;
|
||||
}
|
||||
else if (integer <= ulong.MaxValue >> 2) /* 4611686018427387903 */
|
||||
{
|
||||
requiredBytes = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Value is larger than IntegerVar.MaxValue.");
|
||||
}
|
||||
|
||||
int offset = 8 - requiredBytes;
|
||||
|
||||
byte[] uInt64Bytes = ByteHelpers.GetBytes(integer);
|
||||
byte first = uInt64Bytes[offset];
|
||||
first = (byte)(first | (requiredBytes / 2) << 6);
|
||||
uInt64Bytes[offset] = first;
|
||||
|
||||
byte[] result = new byte[requiredBytes];
|
||||
Buffer.BlockCopy(uInt64Bytes, offset, result, 0, requiredBytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ulong Decode(byte[] bytes)
|
||||
{
|
||||
int i = 8 - bytes.Length;
|
||||
byte[] buffer = new byte[8];
|
||||
|
||||
Buffer.BlockCopy(bytes, 0, buffer, i, bytes.Length);
|
||||
buffer[i] = (byte)(buffer[i] & (255 >> 2));
|
||||
|
||||
ulong res = ByteHelpers.ToUInt64(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -1,26 +1,24 @@
|
|||
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.
|
||||
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 : ushort
|
||||
{
|
||||
NO_ERROR = 0x0,
|
||||
INTERNAL_ERROR = 0x1,
|
||||
CONNECTION_REFUSED = 0x2,
|
||||
FLOW_CONTROL_ERROR = 0x3,
|
||||
STREAM_LIMIT_ERROR = 0x4,
|
||||
STREAM_STATE_ERROR = 0x5,
|
||||
FINAL_SIZE_ERROR = 0x6,
|
||||
FRAME_ENCODING_ERROR = 0x7,
|
||||
TRANSPORT_PARAMETER_ERROR = 0x8,
|
||||
CONNECTION_ID_LIMIT_ERROR = 0x9,
|
||||
PROTOCOL_VIOLATION = 0xA,
|
||||
INVALID_TOKEN = 0xB,
|
||||
APPLICATION_ERROR = 0xC,
|
||||
CRYPTO_BUFFER_EXCEEDED = 0xD,
|
||||
KEY_UPDATE_ERROR = 0xE,
|
||||
AEAD_LIMIT_REACHED = 0xF,
|
||||
CRYPTO_ERROR = 0x100
|
||||
}
|
||||
public enum ErrorCode : ushort
|
||||
{
|
||||
NO_ERROR = 0x0,
|
||||
INTERNAL_ERROR = 0x1,
|
||||
CONNECTION_REFUSED = 0x2,
|
||||
FLOW_CONTROL_ERROR = 0x3,
|
||||
STREAM_LIMIT_ERROR = 0x4,
|
||||
STREAM_STATE_ERROR = 0x5,
|
||||
FINAL_SIZE_ERROR = 0x6,
|
||||
FRAME_ENCODING_ERROR = 0x7,
|
||||
TRANSPORT_PARAMETER_ERROR = 0x8,
|
||||
CONNECTION_ID_LIMIT_ERROR = 0x9,
|
||||
PROTOCOL_VIOLATION = 0xA,
|
||||
INVALID_TOKEN = 0xB,
|
||||
APPLICATION_ERROR = 0xC,
|
||||
CRYPTO_BUFFER_EXCEEDED = 0xD,
|
||||
KEY_UPDATE_ERROR = 0xE,
|
||||
AEAD_LIMIT_REACHED = 0xF,
|
||||
CRYPTO_ERROR = 0x100
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Exceptions
|
||||
namespace EonaCat.Quic.Infrastructure.Exceptions;
|
||||
// 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 class ProtocolException : Exception
|
||||
{
|
||||
// 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 class ProtocolException : Exception
|
||||
public ProtocolException()
|
||||
{
|
||||
public ProtocolException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public ProtocolException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
public ProtocolException(string message) : base($"EonaCat Network: {message}")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,154 +1,152 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using EonaCat.Quic.Infrastructure.Frames;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure
|
||||
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 class FrameParser
|
||||
{
|
||||
// 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.
|
||||
private readonly ByteArray _array;
|
||||
|
||||
public class FrameParser
|
||||
public FrameParser(ByteArray array)
|
||||
{
|
||||
private readonly ByteArray _array;
|
||||
_array = array;
|
||||
}
|
||||
|
||||
public FrameParser(ByteArray array)
|
||||
public Frame GetFrame()
|
||||
{
|
||||
Frame result;
|
||||
var frameType = _array.PeekByte();
|
||||
switch (frameType)
|
||||
{
|
||||
_array = array;
|
||||
case 0x00:
|
||||
result = new PaddingFrame();
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
result = new PingFrame();
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
result = new AckFrame();
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
result = new AckFrame();
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
result = new ResetStreamFrame();
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
result = new StopSendingFrame();
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
result = new CryptoFrame();
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
result = new NewTokenFrame();
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0a:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0b:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0d:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0e:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0f:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
result = new MaxDataFrame();
|
||||
break;
|
||||
|
||||
case 0x11:
|
||||
result = new MaxStreamDataFrame();
|
||||
break;
|
||||
|
||||
case 0x12:
|
||||
result = new MaxStreamsFrame();
|
||||
break;
|
||||
|
||||
case 0x13:
|
||||
result = new MaxStreamsFrame();
|
||||
break;
|
||||
|
||||
case 0x14:
|
||||
result = new DataBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
result = new StreamDataBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x16:
|
||||
result = new StreamsBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x17:
|
||||
result = new StreamsBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x18:
|
||||
result = new NewConnectionIdFrame();
|
||||
break;
|
||||
|
||||
case 0x19:
|
||||
result = new RetireConnectionIdFrame();
|
||||
break;
|
||||
|
||||
case 0x1a:
|
||||
result = new PathChallengeFrame();
|
||||
break;
|
||||
|
||||
case 0x1b:
|
||||
result = new PathResponseFrame();
|
||||
break;
|
||||
|
||||
case 0x1c:
|
||||
result = new ConnectionCloseFrame();
|
||||
break;
|
||||
|
||||
case 0x1d:
|
||||
result = new ConnectionCloseFrame();
|
||||
break;
|
||||
|
||||
default:
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
|
||||
public Frame GetFrame()
|
||||
{
|
||||
Frame result;
|
||||
byte frameType = _array.PeekByte();
|
||||
switch (frameType)
|
||||
{
|
||||
case 0x00:
|
||||
result = new PaddingFrame();
|
||||
break;
|
||||
result?.Decode(_array);
|
||||
|
||||
case 0x01:
|
||||
result = new PingFrame();
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
result = new AckFrame();
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
result = new AckFrame();
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
result = new ResetStreamFrame();
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
result = new StopSendingFrame();
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
result = new CryptoFrame();
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
result = new NewTokenFrame();
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0a:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0b:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0c:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0d:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0e:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x0f:
|
||||
result = new StreamFrame();
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
result = new MaxDataFrame();
|
||||
break;
|
||||
|
||||
case 0x11:
|
||||
result = new MaxStreamDataFrame();
|
||||
break;
|
||||
|
||||
case 0x12:
|
||||
result = new MaxStreamsFrame();
|
||||
break;
|
||||
|
||||
case 0x13:
|
||||
result = new MaxStreamsFrame();
|
||||
break;
|
||||
|
||||
case 0x14:
|
||||
result = new DataBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
result = new StreamDataBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x16:
|
||||
result = new StreamsBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x17:
|
||||
result = new StreamsBlockedFrame();
|
||||
break;
|
||||
|
||||
case 0x18:
|
||||
result = new NewConnectionIdFrame();
|
||||
break;
|
||||
|
||||
case 0x19:
|
||||
result = new RetireConnectionIdFrame();
|
||||
break;
|
||||
|
||||
case 0x1a:
|
||||
result = new PathChallengeFrame();
|
||||
break;
|
||||
|
||||
case 0x1b:
|
||||
result = new PathResponseFrame();
|
||||
break;
|
||||
|
||||
case 0x1c:
|
||||
result = new ConnectionCloseFrame();
|
||||
break;
|
||||
|
||||
case 0x1d:
|
||||
result = new ConnectionCloseFrame();
|
||||
break;
|
||||
|
||||
default:
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
|
||||
result?.Decode(_array);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class AckFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x02;
|
||||
|
||||
public class AckFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x02;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,84 +1,82 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class ConnectionCloseFrame : Frame
|
||||
{
|
||||
// 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 class ConnectionCloseFrame : Frame
|
||||
public ConnectionCloseFrame()
|
||||
{
|
||||
public byte ActualType { get; set; }
|
||||
public override byte Type => 0x1c;
|
||||
public IntegerVar ErrorCode { get; set; }
|
||||
public IntegerVar FrameType { get; set; }
|
||||
public IntegerVar ReasonPhraseLength { get; set; }
|
||||
public string ReasonPhrase { get; set; }
|
||||
ErrorCode = 0;
|
||||
ReasonPhraseLength = new IntegerVar(0);
|
||||
}
|
||||
|
||||
public ConnectionCloseFrame()
|
||||
/// <summary>
|
||||
/// 0x1d not yet supported (Application Protocol Error)
|
||||
/// </summary>
|
||||
public ConnectionCloseFrame(ErrorCode error, byte frameType, string reason)
|
||||
{
|
||||
ActualType = 0x1c;
|
||||
|
||||
ErrorCode = (ulong)error;
|
||||
FrameType = new IntegerVar(frameType);
|
||||
if (!string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
ReasonPhraseLength = new IntegerVar((ulong)reason.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = 0;
|
||||
ReasonPhraseLength = new IntegerVar(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 0x1d not yet supported (Application Protocol Error)
|
||||
/// </summary>
|
||||
public ConnectionCloseFrame(ErrorCode error, byte frameType, string reason)
|
||||
ReasonPhrase = reason;
|
||||
}
|
||||
|
||||
public byte ActualType { get; set; }
|
||||
public override byte Type => 0x1c;
|
||||
public IntegerVar ErrorCode { get; set; }
|
||||
public IntegerVar FrameType { get; set; }
|
||||
public IntegerVar ReasonPhraseLength { get; set; }
|
||||
public string ReasonPhrase { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
ActualType = array.ReadByte();
|
||||
ErrorCode = array.ReadIntegerVar();
|
||||
if (ActualType == 0x1c)
|
||||
{
|
||||
ActualType = 0x1c;
|
||||
|
||||
ErrorCode = (ulong)error;
|
||||
FrameType = new IntegerVar(frameType);
|
||||
if (!string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
ReasonPhraseLength = new IntegerVar((ulong)reason.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReasonPhraseLength = new IntegerVar(0);
|
||||
}
|
||||
|
||||
ReasonPhrase = reason;
|
||||
FrameType = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
ReasonPhraseLength = array.ReadIntegerVar();
|
||||
|
||||
var rp = array.ReadBytes((int)ReasonPhraseLength.Value);
|
||||
ReasonPhrase = ByteHelpers.GetString(rp);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
ActualType = array.ReadByte();
|
||||
ErrorCode = array.ReadIntegerVar();
|
||||
if (ActualType == 0x1c)
|
||||
{
|
||||
FrameType = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
ReasonPhraseLength = array.ReadIntegerVar();
|
||||
|
||||
byte[] rp = array.ReadBytes((int)ReasonPhraseLength.Value);
|
||||
ReasonPhrase = ByteHelpers.GetString(rp);
|
||||
ActualType
|
||||
};
|
||||
result.AddRange(ErrorCode.ToByteArray());
|
||||
if (ActualType == 0x1c)
|
||||
{
|
||||
result.AddRange(FrameType.ToByteArray());
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
if (string.IsNullOrWhiteSpace(ReasonPhrase) == false)
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
ActualType
|
||||
};
|
||||
result.AddRange(ErrorCode.ToByteArray());
|
||||
if (ActualType == 0x1c)
|
||||
{
|
||||
result.AddRange(FrameType.ToByteArray());
|
||||
}
|
||||
byte[] rpl = new IntegerVar((ulong)ReasonPhrase.Length);
|
||||
result.AddRange(rpl);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ReasonPhrase) == false)
|
||||
{
|
||||
byte[] rpl = new IntegerVar((ulong)ReasonPhrase.Length);
|
||||
result.AddRange(rpl);
|
||||
|
||||
byte[] reasonPhrase = ByteHelpers.GetBytes(ReasonPhrase);
|
||||
result.AddRange(reasonPhrase);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
var reasonPhrase = ByteHelpers.GetBytes(ReasonPhrase);
|
||||
result.AddRange(reasonPhrase);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
|
||||
// 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 class CryptoFrame : Frame
|
||||
{
|
||||
// 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 class CryptoFrame : Frame
|
||||
public override byte Type => 0x06;
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x06;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,39 +1,38 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
|
||||
// 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 class DataBlockedFrame : Frame
|
||||
{
|
||||
// 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 class DataBlockedFrame : Frame
|
||||
public DataBlockedFrame()
|
||||
{
|
||||
public override byte Type => 0x14;
|
||||
public IntegerVar MaximumData { get; set; }
|
||||
}
|
||||
|
||||
public DataBlockedFrame()
|
||||
public DataBlockedFrame(ulong dataLimit)
|
||||
{
|
||||
MaximumData = dataLimit;
|
||||
}
|
||||
|
||||
public override byte Type => 0x14;
|
||||
public IntegerVar MaximumData { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
var type = array.ReadByte();
|
||||
MaximumData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumData.ToByteArray());
|
||||
|
||||
public DataBlockedFrame(ulong dataLimit)
|
||||
{
|
||||
MaximumData = dataLimit;
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
MaximumData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumData.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Data encapsulation unit for a Packet.
|
||||
/// </summary>
|
||||
public abstract class Frame
|
||||
{
|
||||
// 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 abstract byte Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Data encapsulation unit for a Packet.
|
||||
/// </summary>
|
||||
public abstract class Frame
|
||||
{
|
||||
public abstract byte Type { get; }
|
||||
public abstract byte[] Encode();
|
||||
|
||||
public abstract byte[] Encode();
|
||||
|
||||
public abstract void Decode(ByteArray array);
|
||||
}
|
||||
public abstract void Decode(ByteArray array);
|
||||
}
|
|
@ -1,32 +1,31 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
|
||||
public class MaxDataFrame : Frame
|
||||
{
|
||||
public class MaxDataFrame : Frame
|
||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// Copyright EonaCat (Jeroen Saey)
|
||||
// See file LICENSE or go to https://EonaCat.com/License for full license details.
|
||||
|
||||
public override byte Type => 0x10;
|
||||
public IntegerVar MaximumData { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// Copyright EonaCat (Jeroen Saey)
|
||||
// See file LICENSE or go to https://EonaCat.com/License for full license details.
|
||||
array.ReadByte();
|
||||
MaximumData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte Type => 0x10;
|
||||
public IntegerVar MaximumData { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
array.ReadByte();
|
||||
MaximumData = array.ReadIntegerVar();
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumData.ToByteArray());
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumData.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,46 +1,44 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class MaxStreamDataFrame : Frame
|
||||
{
|
||||
// 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 class MaxStreamDataFrame : Frame
|
||||
public MaxStreamDataFrame()
|
||||
{
|
||||
public override byte Type => 0x11;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar MaximumStreamData { get; set; }
|
||||
}
|
||||
|
||||
public StreamId ConvertedStreamId { get; set; }
|
||||
public MaxStreamDataFrame(ulong streamId, ulong maximumStreamData)
|
||||
{
|
||||
StreamId = streamId;
|
||||
MaximumStreamData = maximumStreamData;
|
||||
}
|
||||
|
||||
public MaxStreamDataFrame()
|
||||
public override byte Type => 0x11;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar MaximumStreamData { get; set; }
|
||||
|
||||
public StreamId ConvertedStreamId { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
var type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
MaximumStreamData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(MaximumStreamData.ToByteArray());
|
||||
|
||||
public MaxStreamDataFrame(ulong streamId, ulong maximumStreamData)
|
||||
{
|
||||
StreamId = streamId;
|
||||
MaximumStreamData = maximumStreamData;
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
MaximumStreamData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(MaximumStreamData.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,39 +1,38 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
|
||||
// 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 class MaxStreamsFrame : Frame
|
||||
{
|
||||
// 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 class MaxStreamsFrame : Frame
|
||||
public MaxStreamsFrame()
|
||||
{
|
||||
public override byte Type => 0x12;
|
||||
public IntegerVar MaximumStreams { get; set; }
|
||||
}
|
||||
|
||||
public MaxStreamsFrame()
|
||||
public MaxStreamsFrame(ulong maximumStreamId, StreamType appliesTo)
|
||||
{
|
||||
MaximumStreams = new IntegerVar(maximumStreamId);
|
||||
}
|
||||
|
||||
public override byte Type => 0x12;
|
||||
public IntegerVar MaximumStreams { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
var type = array.ReadByte();
|
||||
MaximumStreams = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumStreams.ToByteArray());
|
||||
|
||||
public MaxStreamsFrame(ulong maximumStreamId, StreamType appliesTo)
|
||||
{
|
||||
MaximumStreams = new IntegerVar(maximumStreamId);
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
MaximumStreams = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(MaximumStreams.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class NewConnectionIdFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x18;
|
||||
|
||||
public class NewConnectionIdFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x18;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class NewTokenFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x07;
|
||||
|
||||
public class NewTokenFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x07;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,28 +1,26 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class PaddingFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x00;
|
||||
|
||||
public class PaddingFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x00;
|
||||
var type = array.ReadByte();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var data = new List<byte>
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
}
|
||||
Type
|
||||
};
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> data = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class PathChallengeFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x1a;
|
||||
|
||||
public class PathChallengeFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x1a;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class PathResponseFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x1b;
|
||||
|
||||
public class PathResponseFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x1b;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,28 +1,26 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class PingFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x01;
|
||||
|
||||
public class PingFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x01;
|
||||
var type = array.ReadByte();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var data = new List<byte>
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
}
|
||||
Type
|
||||
};
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> data = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,37 +1,35 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class ResetStreamFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x04;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar ApplicationProtocolErrorCode { get; set; }
|
||||
public IntegerVar FinalSize { get; set; }
|
||||
|
||||
public class ResetStreamFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x04;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar ApplicationProtocolErrorCode { get; set; }
|
||||
public IntegerVar FinalSize { get; set; }
|
||||
var type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
ApplicationProtocolErrorCode = array.ReadIntegerVar();
|
||||
FinalSize = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
ApplicationProtocolErrorCode = array.ReadIntegerVar();
|
||||
FinalSize = array.ReadIntegerVar();
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(ApplicationProtocolErrorCode.ToByteArray());
|
||||
result.AddRange(FinalSize.ToByteArray());
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(ApplicationProtocolErrorCode.ToByteArray());
|
||||
result.AddRange(FinalSize.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class RetireConnectionIdFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x19;
|
||||
|
||||
public class RetireConnectionIdFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x19;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class StopSendingFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x05;
|
||||
|
||||
public class StopSendingFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x05;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,44 +1,42 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class StreamDataBlockedFrame : Frame
|
||||
{
|
||||
// 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 class StreamDataBlockedFrame : Frame
|
||||
public StreamDataBlockedFrame()
|
||||
{
|
||||
public override byte Type => 0x15;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar MaximumStreamData { get; set; }
|
||||
}
|
||||
|
||||
public StreamDataBlockedFrame()
|
||||
public StreamDataBlockedFrame(ulong streamId, ulong streamDataLimit)
|
||||
{
|
||||
StreamId = streamId;
|
||||
MaximumStreamData = streamDataLimit;
|
||||
}
|
||||
|
||||
public override byte Type => 0x15;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar MaximumStreamData { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
var type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
MaximumStreamData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var result = new List<byte>
|
||||
{
|
||||
}
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(MaximumStreamData.ToByteArray());
|
||||
|
||||
public StreamDataBlockedFrame(ulong streamId, ulong streamDataLimit)
|
||||
{
|
||||
StreamId = streamId;
|
||||
MaximumStreamData = streamDataLimit;
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
StreamId = array.ReadIntegerVar();
|
||||
MaximumStreamData = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
Type
|
||||
};
|
||||
result.AddRange(StreamId.ToByteArray());
|
||||
result.AddRange(MaximumStreamData.ToByteArray());
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,103 +1,101 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class StreamFrame : Frame
|
||||
{
|
||||
// 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 byte ActualType = 0x08;
|
||||
|
||||
public class StreamFrame : Frame
|
||||
public StreamFrame()
|
||||
{
|
||||
public byte ActualType = 0x08;
|
||||
}
|
||||
|
||||
public override byte Type => 0x08;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar Offset { get; set; }
|
||||
public IntegerVar Length { get; set; }
|
||||
public byte[] StreamData { get; set; }
|
||||
public bool EndOfStream { get; set; }
|
||||
public StreamFrame(ulong streamId, byte[] data, ulong offset, bool eos)
|
||||
{
|
||||
StreamId = streamId;
|
||||
StreamData = data;
|
||||
Offset = offset;
|
||||
Length = (ulong)data.Length;
|
||||
EndOfStream = eos;
|
||||
}
|
||||
|
||||
public StreamFrame()
|
||||
public override byte Type => 0x08;
|
||||
public IntegerVar StreamId { get; set; }
|
||||
public IntegerVar Offset { get; set; }
|
||||
public IntegerVar Length { get; set; }
|
||||
public byte[] StreamData { get; set; }
|
||||
public bool EndOfStream { get; set; }
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
var type = array.ReadByte();
|
||||
|
||||
var OFF_BIT = (byte)(type & 0x04);
|
||||
var LEN_BIT = (byte)(type & 0x02);
|
||||
var FIN_BIT = (byte)(type & 0x01);
|
||||
|
||||
StreamId = array.ReadIntegerVar();
|
||||
if (OFF_BIT > 0)
|
||||
{
|
||||
Offset = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public StreamFrame(ulong streamId, byte[] data, ulong offset, bool eos)
|
||||
if (LEN_BIT > 0)
|
||||
{
|
||||
StreamId = streamId;
|
||||
StreamData = data;
|
||||
Offset = offset;
|
||||
Length = (ulong)data.Length;
|
||||
EndOfStream = eos;
|
||||
Length = array.ReadIntegerVar();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
if (FIN_BIT > 0)
|
||||
{
|
||||
byte type = array.ReadByte();
|
||||
|
||||
byte OFF_BIT = (byte)(type & 0x04);
|
||||
byte LEN_BIT = (byte)(type & 0x02);
|
||||
byte FIN_BIT = (byte)(type & 0x01);
|
||||
|
||||
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);
|
||||
EndOfStream = true;
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
StreamData = array.ReadBytes((int)Length.Value);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
if (Offset != null && Offset.Value > 0)
|
||||
{
|
||||
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);
|
||||
byte FIN_BIT = (byte)(ActualType & 0x01);
|
||||
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
ActualType
|
||||
};
|
||||
byte[] streamId = StreamId;
|
||||
result.AddRange(streamId);
|
||||
|
||||
if (OFF_BIT > 0)
|
||||
{
|
||||
result.AddRange(Offset.ToByteArray());
|
||||
}
|
||||
|
||||
if (LEN_BIT > 0)
|
||||
{
|
||||
result.AddRange(Length.ToByteArray());
|
||||
}
|
||||
|
||||
result.AddRange(StreamData);
|
||||
|
||||
return result.ToArray();
|
||||
ActualType = (byte)(ActualType | 0x04);
|
||||
}
|
||||
|
||||
if (Length != null && Length.Value > 0)
|
||||
{
|
||||
ActualType = (byte)(ActualType | 0x02);
|
||||
}
|
||||
|
||||
if (EndOfStream)
|
||||
{
|
||||
ActualType = (byte)(ActualType | 0x01);
|
||||
}
|
||||
|
||||
var OFF_BIT = (byte)(ActualType & 0x04);
|
||||
var LEN_BIT = (byte)(ActualType & 0x02);
|
||||
var FIN_BIT = (byte)(ActualType & 0x01);
|
||||
|
||||
var result = new List<byte>
|
||||
{
|
||||
ActualType
|
||||
};
|
||||
byte[] streamId = StreamId;
|
||||
result.AddRange(streamId);
|
||||
|
||||
if (OFF_BIT > 0)
|
||||
{
|
||||
result.AddRange(Offset.ToByteArray());
|
||||
}
|
||||
|
||||
if (LEN_BIT > 0)
|
||||
{
|
||||
result.AddRange(Length.ToByteArray());
|
||||
}
|
||||
|
||||
result.AddRange(StreamData);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System;
|
||||
using System;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Frames
|
||||
namespace EonaCat.Quic.Infrastructure.Frames;
|
||||
// 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 class StreamsBlockedFrame : Frame
|
||||
{
|
||||
// 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 override byte Type => 0x16;
|
||||
|
||||
public class StreamsBlockedFrame : Frame
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
public override byte Type => 0x16;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(ByteArray array)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,36 +1,34 @@
|
|||
namespace EonaCat.Quic.Infrastructure
|
||||
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 class NumberSpace
|
||||
{
|
||||
// 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.
|
||||
private readonly uint _max = uint.MaxValue;
|
||||
private uint _n;
|
||||
|
||||
public class NumberSpace
|
||||
public NumberSpace()
|
||||
{
|
||||
private readonly uint _max = uint.MaxValue;
|
||||
private uint _n = 0;
|
||||
}
|
||||
|
||||
public NumberSpace()
|
||||
public NumberSpace(uint max)
|
||||
{
|
||||
_max = max;
|
||||
}
|
||||
|
||||
public bool IsMax()
|
||||
{
|
||||
return _n == _max;
|
||||
}
|
||||
|
||||
public uint Get()
|
||||
{
|
||||
if (_n >= _max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public NumberSpace(uint max)
|
||||
{
|
||||
_max = max;
|
||||
}
|
||||
|
||||
public bool IsMax()
|
||||
{
|
||||
return _n == _max;
|
||||
}
|
||||
|
||||
public uint Get()
|
||||
{
|
||||
if (_n >= _max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
_n++;
|
||||
return _n;
|
||||
}
|
||||
_n++;
|
||||
return _n;
|
||||
}
|
||||
}
|
|
@ -3,35 +3,30 @@ using EonaCat.Quic.Infrastructure.Frames;
|
|||
using EonaCat.Quic.Infrastructure.Packets;
|
||||
using EonaCat.Quic.Infrastructure.Settings;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.PacketProcessing
|
||||
namespace EonaCat.Quic.Infrastructure.PacketProcessing;
|
||||
// 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 class InitialPacketCreator
|
||||
{
|
||||
// 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 class InitialPacketCreator
|
||||
public InitialPacket CreateInitialPacket(IntegerParts sourceConnectionId, IntegerParts destinationConnectionId)
|
||||
{
|
||||
public InitialPacket CreateInitialPacket(IntegerParts sourceConnectionId, IntegerParts destinationConnectionId)
|
||||
{
|
||||
InitialPacket packet = new InitialPacket(destinationConnectionId, sourceConnectionId);
|
||||
packet.PacketNumber = 0;
|
||||
packet.SourceConnectionId = sourceConnectionId;
|
||||
packet.DestinationConnectionId = destinationConnectionId;
|
||||
packet.Version = QuicVersion.CurrentVersion;
|
||||
var packet = new InitialPacket(destinationConnectionId, sourceConnectionId);
|
||||
packet.PacketNumber = 0;
|
||||
packet.SourceConnectionId = sourceConnectionId;
|
||||
packet.DestinationConnectionId = destinationConnectionId;
|
||||
packet.Version = QuicVersion.CurrentVersion;
|
||||
|
||||
int length = packet.Encode().Length;
|
||||
int padding = QuicSettings.PMTU - length;
|
||||
var length = packet.Encode().Length;
|
||||
var padding = QuicSettings.PMTU - length;
|
||||
|
||||
for (int i = 0; i < padding; i++)
|
||||
{
|
||||
packet.AttachFrame(new PaddingFrame());
|
||||
}
|
||||
for (var i = 0; i < padding; i++) packet.AttachFrame(new PaddingFrame());
|
||||
|
||||
return packet;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public VersionNegotiationPacket CreateVersionNegotiationPacket()
|
||||
{
|
||||
return new VersionNegotiationPacket();
|
||||
}
|
||||
public VersionNegotiationPacket CreateVersionNegotiationPacket()
|
||||
{
|
||||
return new VersionNegotiationPacket();
|
||||
}
|
||||
}
|
|
@ -2,53 +2,51 @@
|
|||
using EonaCat.Quic.Infrastructure.Frames;
|
||||
using EonaCat.Quic.Infrastructure.Packets;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.PacketProcessing
|
||||
namespace EonaCat.Quic.Infrastructure.PacketProcessing;
|
||||
// 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 class PacketCreator
|
||||
{
|
||||
// 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.
|
||||
private readonly IntegerParts _connectionId;
|
||||
private readonly NumberSpace _ns;
|
||||
private readonly IntegerParts _peerConnectionId;
|
||||
|
||||
public class PacketCreator
|
||||
public PacketCreator(IntegerParts connectionId, IntegerParts peerConnectionId)
|
||||
{
|
||||
private readonly NumberSpace _ns;
|
||||
private readonly IntegerParts _connectionId;
|
||||
private readonly IntegerParts _peerConnectionId;
|
||||
_ns = new NumberSpace();
|
||||
|
||||
public PacketCreator(IntegerParts connectionId, IntegerParts peerConnectionId)
|
||||
{
|
||||
_ns = new NumberSpace();
|
||||
_connectionId = connectionId;
|
||||
_peerConnectionId = peerConnectionId;
|
||||
}
|
||||
|
||||
_connectionId = connectionId;
|
||||
_peerConnectionId = peerConnectionId;
|
||||
}
|
||||
public ShortHeaderPacket CreateConnectionClosePacket(ErrorCode code, byte frameType, string reason)
|
||||
{
|
||||
var packet = new ShortHeaderPacket(_peerConnectionId.Size);
|
||||
packet.PacketNumber = _ns.Get();
|
||||
packet.DestinationConnectionId = (byte)_peerConnectionId;
|
||||
packet.AttachFrame(new ConnectionCloseFrame(code, frameType, reason));
|
||||
|
||||
public ShortHeaderPacket CreateConnectionClosePacket(ErrorCode code, byte frameType, string reason)
|
||||
{
|
||||
ShortHeaderPacket packet = new ShortHeaderPacket(_peerConnectionId.Size);
|
||||
packet.PacketNumber = _ns.Get();
|
||||
packet.DestinationConnectionId = (byte)_peerConnectionId;
|
||||
packet.AttachFrame(new ConnectionCloseFrame(code, frameType, reason));
|
||||
return packet;
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
public ShortHeaderPacket CreateDataPacket(ulong streamId, byte[] data, ulong offset, bool eos)
|
||||
{
|
||||
var packet = new ShortHeaderPacket(_peerConnectionId.Size);
|
||||
packet.PacketNumber = _ns.Get();
|
||||
packet.DestinationConnectionId = (byte)_peerConnectionId;
|
||||
packet.AttachFrame(new StreamFrame(streamId, data, offset, eos));
|
||||
|
||||
public ShortHeaderPacket CreateDataPacket(ulong streamId, byte[] data, ulong offset, bool eos)
|
||||
{
|
||||
ShortHeaderPacket packet = new ShortHeaderPacket(_peerConnectionId.Size);
|
||||
packet.PacketNumber = _ns.Get();
|
||||
packet.DestinationConnectionId = (byte)_peerConnectionId;
|
||||
packet.AttachFrame(new StreamFrame(streamId, data, offset, eos));
|
||||
return packet;
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
public InitialPacket CreateServerBusyPacket()
|
||||
{
|
||||
return new InitialPacket(0, 0);
|
||||
}
|
||||
|
||||
public InitialPacket CreateServerBusyPacket()
|
||||
{
|
||||
return new InitialPacket(0, 0);
|
||||
}
|
||||
|
||||
public ShortHeaderPacket CreateShortHeaderPacket()
|
||||
{
|
||||
return new ShortHeaderPacket(0);
|
||||
}
|
||||
public ShortHeaderPacket CreateShortHeaderPacket()
|
||||
{
|
||||
return new ShortHeaderPacket(0);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
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.
|
||||
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 : ushort
|
||||
{
|
||||
Initial = 0x0,
|
||||
ZeroRTTProtected = 0x1,
|
||||
Handshake = 0x2,
|
||||
RetryPacket = 0x3
|
||||
}
|
||||
public enum PacketType : ushort
|
||||
{
|
||||
Initial = 0x0,
|
||||
ZeroRTTProtected = 0x1,
|
||||
Handshake = 0x2,
|
||||
RetryPacket = 0x3
|
||||
}
|
|
@ -1,103 +1,101 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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 class InitialPacket : Packet
|
||||
{
|
||||
// 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 class InitialPacket : Packet
|
||||
public InitialPacket()
|
||||
{
|
||||
public override byte Type => 0b1100_1100; //0xDC; // 1101 1100
|
||||
}
|
||||
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public byte SourceConnectionIdLength { get; set; }
|
||||
public IntegerParts SourceConnectionId { get; set; }
|
||||
public IntegerVar TokenLength { get; set; }
|
||||
public byte[] Token { get; set; }
|
||||
public IntegerVar Length { get; set; }
|
||||
public IntegerParts PacketNumber { get; set; }
|
||||
public InitialPacket(IntegerParts destinationConnectionId, IntegerParts sourceConnectionId)
|
||||
{
|
||||
DestinationConnectionIdLength = destinationConnectionId.Size;
|
||||
DestinationConnectionId = destinationConnectionId;
|
||||
|
||||
public InitialPacket()
|
||||
SourceConnectionIdLength = sourceConnectionId.Size;
|
||||
SourceConnectionId = sourceConnectionId;
|
||||
}
|
||||
|
||||
public override byte Type => 0b1100_1100; //0xDC; // 1101 1100
|
||||
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public byte SourceConnectionIdLength { get; set; }
|
||||
public IntegerParts SourceConnectionId { get; set; }
|
||||
public IntegerVar TokenLength { get; set; }
|
||||
public byte[] Token { get; set; }
|
||||
public IntegerVar Length { get; set; }
|
||||
public IntegerParts PacketNumber { get; set; }
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
var array = new ByteArray(packet);
|
||||
var type = array.ReadByte();
|
||||
// Size of the packet PacketNumber is determined by the last 2 bits of the Type.
|
||||
var pnSize = (type & 0x03) + 1;
|
||||
|
||||
Version = array.ReadUInt32();
|
||||
|
||||
DestinationConnectionIdLength = array.ReadByte();
|
||||
if (DestinationConnectionIdLength > 0)
|
||||
{
|
||||
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
|
||||
}
|
||||
|
||||
public InitialPacket(IntegerParts destinationConnectionId, IntegerParts sourceConnectionId)
|
||||
SourceConnectionIdLength = array.ReadByte();
|
||||
if (SourceConnectionIdLength > 0)
|
||||
{
|
||||
DestinationConnectionIdLength = destinationConnectionId.Size;
|
||||
DestinationConnectionId = destinationConnectionId;
|
||||
|
||||
SourceConnectionIdLength = sourceConnectionId.Size;
|
||||
SourceConnectionId = sourceConnectionId;
|
||||
SourceConnectionId = array.ReadGranularInteger(SourceConnectionIdLength);
|
||||
}
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
TokenLength = array.ReadIntegerVar();
|
||||
if (TokenLength > 0)
|
||||
{
|
||||
ByteArray array = new ByteArray(packet);
|
||||
byte type = array.ReadByte();
|
||||
// Size of the packet PacketNumber is determined by the last 2 bits of the Type.
|
||||
int pnSize = (type & 0x03) + 1;
|
||||
|
||||
Version = array.ReadUInt32();
|
||||
|
||||
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);
|
||||
|
||||
Length = Length - PacketNumber.Size;
|
||||
|
||||
this.DecodeFrames(array);
|
||||
Token = array.ReadBytes(TokenLength);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
Length = array.ReadIntegerVar();
|
||||
PacketNumber = array.ReadGranularInteger(pnSize);
|
||||
|
||||
Length = Length - PacketNumber.Size;
|
||||
|
||||
DecodeFrames(array);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var frames = EncodeFrames();
|
||||
|
||||
var result = new List<byte>
|
||||
{
|
||||
byte[] frames = EncodeFrames();
|
||||
(byte)(Type | (PacketNumber.Size - 1))
|
||||
};
|
||||
result.AddRange(ByteHelpers.GetBytes(Version));
|
||||
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
(byte)(Type | (PacketNumber.Size - 1))
|
||||
};
|
||||
result.AddRange(ByteHelpers.GetBytes(Version));
|
||||
|
||||
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 + (ulong)frames.Length);
|
||||
|
||||
result.AddRange(tokenLength);
|
||||
result.AddRange(length);
|
||||
result.AddRange(PacketNumber.ToByteArray());
|
||||
result.AddRange(frames);
|
||||
|
||||
return result.ToArray();
|
||||
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 + (ulong)frames.Length);
|
||||
|
||||
result.AddRange(tokenLength);
|
||||
result.AddRange(length);
|
||||
result.AddRange(PacketNumber.ToByteArray());
|
||||
result.AddRange(frames);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,99 +1,98 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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 class LongHeaderPacket : Packet
|
||||
{
|
||||
// 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 class LongHeaderPacket : Packet
|
||||
public LongHeaderPacket()
|
||||
{
|
||||
public override byte Type => 0b1100_0000; // 1100 0000
|
||||
}
|
||||
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public byte SourceConnectionIdLength { get; set; }
|
||||
public IntegerParts SourceConnectionId { get; set; }
|
||||
public LongHeaderPacket(PacketType packetType, IntegerParts destinationConnectionId,
|
||||
IntegerParts sourceConnectionId)
|
||||
{
|
||||
PacketType = packetType;
|
||||
DestinationConnectionIdLength = destinationConnectionId.Size;
|
||||
DestinationConnectionId = destinationConnectionId;
|
||||
|
||||
public PacketType PacketType { get; set; }
|
||||
SourceConnectionIdLength = sourceConnectionId.Size;
|
||||
SourceConnectionId = sourceConnectionId;
|
||||
}
|
||||
|
||||
public LongHeaderPacket()
|
||||
public override byte Type => 0b1100_0000; // 1100 0000
|
||||
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public byte SourceConnectionIdLength { get; set; }
|
||||
public IntegerParts SourceConnectionId { get; set; }
|
||||
|
||||
public PacketType PacketType { get; set; }
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
var array = new ByteArray(packet);
|
||||
|
||||
var type = array.ReadByte();
|
||||
PacketType = DecodeTypeFiled(type);
|
||||
|
||||
Version = array.ReadUInt32();
|
||||
|
||||
DestinationConnectionIdLength = array.ReadByte();
|
||||
if (DestinationConnectionIdLength > 0)
|
||||
{
|
||||
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
|
||||
}
|
||||
|
||||
public LongHeaderPacket(PacketType packetType, IntegerParts destinationConnectionId, IntegerParts sourceConnectionId)
|
||||
SourceConnectionIdLength = array.ReadByte();
|
||||
if (SourceConnectionIdLength > 0)
|
||||
{
|
||||
PacketType = packetType;
|
||||
DestinationConnectionIdLength = destinationConnectionId.Size;
|
||||
DestinationConnectionId = destinationConnectionId;
|
||||
|
||||
SourceConnectionIdLength = sourceConnectionId.Size;
|
||||
SourceConnectionId = sourceConnectionId;
|
||||
SourceConnectionId = array.ReadGranularInteger(SourceConnectionIdLength);
|
||||
}
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
DecodeFrames(array);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var frames = EncodeFrames();
|
||||
|
||||
var result = new List<byte>
|
||||
{
|
||||
ByteArray array = new ByteArray(packet);
|
||||
EncodeTypeField()
|
||||
};
|
||||
result.AddRange(ByteHelpers.GetBytes(Version));
|
||||
|
||||
byte type = array.ReadByte();
|
||||
PacketType = DecodeTypeFiled(type);
|
||||
|
||||
Version = array.ReadUInt32();
|
||||
|
||||
DestinationConnectionIdLength = array.ReadByte();
|
||||
if (DestinationConnectionIdLength > 0)
|
||||
{
|
||||
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
|
||||
}
|
||||
|
||||
SourceConnectionIdLength = array.ReadByte();
|
||||
if (SourceConnectionIdLength > 0)
|
||||
{
|
||||
SourceConnectionId = array.ReadGranularInteger(SourceConnectionIdLength);
|
||||
}
|
||||
|
||||
this.DecodeFrames(array);
|
||||
result.Add(DestinationConnectionId.Size);
|
||||
if (DestinationConnectionId.Size > 0)
|
||||
{
|
||||
result.AddRange(DestinationConnectionId.ToByteArray());
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
result.Add(SourceConnectionId.Size);
|
||||
if (SourceConnectionId.Size > 0)
|
||||
{
|
||||
byte[] frames = EncodeFrames();
|
||||
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
EncodeTypeField()
|
||||
};
|
||||
result.AddRange(ByteHelpers.GetBytes(Version));
|
||||
|
||||
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);
|
||||
|
||||
return result.ToArray();
|
||||
result.AddRange(SourceConnectionId.ToByteArray());
|
||||
}
|
||||
|
||||
private byte EncodeTypeField()
|
||||
{
|
||||
byte type = (byte)(Type | ((byte)PacketType << 4) & 0b0011_0000);
|
||||
result.AddRange(frames);
|
||||
|
||||
return type;
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private PacketType DecodeTypeFiled(byte type)
|
||||
{
|
||||
PacketType result = (PacketType)((type & 0b0011_0000) >> 4);
|
||||
private byte EncodeTypeField()
|
||||
{
|
||||
var type = (byte)(Type | (((byte)PacketType << 4) & 0b0011_0000));
|
||||
|
||||
return result;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private PacketType DecodeTypeFiled(byte type)
|
||||
{
|
||||
var result = (PacketType)((type & 0b0011_0000) >> 4);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,69 +1,67 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
using EonaCat.Quic.Infrastructure.Exceptions;
|
||||
using EonaCat.Quic.Infrastructure.Frames;
|
||||
using EonaCat.Quic.Infrastructure.Settings;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Base data transfer unit of QUIC Transport.
|
||||
/// </summary>
|
||||
public abstract class Packet
|
||||
{
|
||||
// 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.
|
||||
protected List<Frame> _frames = new();
|
||||
public abstract byte Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Base data transfer unit of QUIC Transport.
|
||||
/// </summary>
|
||||
public abstract class Packet
|
||||
public uint Version { get; set; }
|
||||
|
||||
public abstract byte[] Encode();
|
||||
|
||||
public abstract void Decode(byte[] packet);
|
||||
|
||||
public virtual void AttachFrame(Frame frame)
|
||||
{
|
||||
protected List<Frame> _frames = new List<Frame>();
|
||||
public abstract byte Type { get; }
|
||||
_frames.Add(frame);
|
||||
}
|
||||
|
||||
public uint Version { get; set; }
|
||||
public virtual List<Frame> GetFrames()
|
||||
{
|
||||
return _frames;
|
||||
}
|
||||
|
||||
public abstract byte[] Encode();
|
||||
|
||||
public abstract void Decode(byte[] packet);
|
||||
|
||||
public virtual void AttachFrame(Frame frame)
|
||||
public virtual void DecodeFrames(ByteArray array)
|
||||
{
|
||||
var factory = new FrameParser(array);
|
||||
Frame result;
|
||||
var frames = 0;
|
||||
while (array.HasData() && frames <= QuicSettings.PMTU)
|
||||
{
|
||||
_frames.Add(frame);
|
||||
}
|
||||
|
||||
public virtual List<Frame> GetFrames()
|
||||
{
|
||||
return _frames;
|
||||
}
|
||||
|
||||
public virtual void DecodeFrames(ByteArray array)
|
||||
{
|
||||
FrameParser factory = new FrameParser(array);
|
||||
Frame result;
|
||||
int frames = 0;
|
||||
while (array.HasData() && frames <= QuicSettings.PMTU)
|
||||
result = factory.GetFrame();
|
||||
if (result != null)
|
||||
{
|
||||
result = factory.GetFrame();
|
||||
if (result != null)
|
||||
{
|
||||
_frames.Add(result);
|
||||
}
|
||||
|
||||
frames++;
|
||||
_frames.Add(result);
|
||||
}
|
||||
|
||||
if (array.HasData())
|
||||
{
|
||||
throw new ProtocolException("Unexpected number of frames or possibly corrupted frame was sent.");
|
||||
}
|
||||
frames++;
|
||||
}
|
||||
|
||||
public virtual byte[] EncodeFrames()
|
||||
if (array.HasData())
|
||||
{
|
||||
List<byte> result = new List<byte>();
|
||||
foreach (Frame frame in _frames)
|
||||
{
|
||||
result.AddRange(frame.Encode());
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
throw new ProtocolException("Unexpected number of frames or possibly corrupted frame was sent.");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual byte[] EncodeFrames()
|
||||
{
|
||||
var result = new List<byte>();
|
||||
foreach (var frame in _frames)
|
||||
{
|
||||
result.AddRange(frame.Encode());
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,54 +1,53 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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 class ShortHeaderPacket : Packet
|
||||
{
|
||||
// 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 byte ActualType = 0b0100_0000;
|
||||
|
||||
public class ShortHeaderPacket : Packet
|
||||
public ShortHeaderPacket(byte destinationConnectionIdLength)
|
||||
{
|
||||
public byte ActualType = 0b0100_0000;
|
||||
public override byte Type => 0b0100_0000;
|
||||
DestinationConnectionIdLength = destinationConnectionIdLength;
|
||||
}
|
||||
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public IntegerParts PacketNumber { get; set; }
|
||||
public override byte Type => 0b0100_0000;
|
||||
|
||||
// Field not transferred! Only the connection knows about the length of the ConnectionId
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
public IntegerParts DestinationConnectionId { get; set; }
|
||||
public IntegerParts PacketNumber { get; set; }
|
||||
|
||||
public ShortHeaderPacket(byte destinationConnectionIdLength)
|
||||
// Field not transferred! Only the connection knows about the length of the ConnectionId
|
||||
public byte DestinationConnectionIdLength { get; set; }
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
var array = new ByteArray(packet);
|
||||
var type = array.ReadByte();
|
||||
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
|
||||
|
||||
var pnSize = (type & 0x03) + 1;
|
||||
PacketNumber = array.ReadBytes(pnSize);
|
||||
|
||||
DecodeFrames(array);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
var frames = EncodeFrames();
|
||||
|
||||
var result = new List<byte>
|
||||
{
|
||||
DestinationConnectionIdLength = destinationConnectionIdLength;
|
||||
}
|
||||
(byte)(Type | (PacketNumber.Size - 1))
|
||||
};
|
||||
result.AddRange(DestinationConnectionId.ToByteArray());
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
ByteArray array = new ByteArray(packet);
|
||||
byte type = array.ReadByte();
|
||||
DestinationConnectionId = array.ReadGranularInteger(DestinationConnectionIdLength);
|
||||
byte[] pnBytes = PacketNumber;
|
||||
result.AddRange(pnBytes);
|
||||
result.AddRange(frames);
|
||||
|
||||
int pnSize = (type & 0x03) + 1;
|
||||
PacketNumber = array.ReadBytes(pnSize);
|
||||
|
||||
DecodeFrames(array);
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
byte[] frames = EncodeFrames();
|
||||
|
||||
List<byte> result = new List<byte>
|
||||
{
|
||||
(byte)(Type | (PacketNumber.Size - 1))
|
||||
};
|
||||
result.AddRange(DestinationConnectionId.ToByteArray());
|
||||
|
||||
byte[] pnBytes = PacketNumber;
|
||||
result.AddRange(pnBytes);
|
||||
result.AddRange(frames);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,67 +1,65 @@
|
|||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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 class Unpacker
|
||||
{
|
||||
// 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 class Unpacker
|
||||
public Packet Unpack(byte[] data)
|
||||
{
|
||||
public Packet Unpack(byte[] data)
|
||||
Packet result = null;
|
||||
|
||||
var type = GetPacketType(data);
|
||||
switch (type)
|
||||
{
|
||||
Packet result = null;
|
||||
case QuicPacketType.Initial:
|
||||
result = new InitialPacket();
|
||||
break;
|
||||
|
||||
QuicPacketType type = GetPacketType(data);
|
||||
switch (type)
|
||||
{
|
||||
case QuicPacketType.Initial:
|
||||
result = new InitialPacket();
|
||||
break;
|
||||
|
||||
// Should be passed by the QuicConnection to the PacketWireTransfer -> Unpacker
|
||||
case QuicPacketType.ShortHeader:
|
||||
result = new ShortHeaderPacket(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
result.Decode(data);
|
||||
|
||||
return result;
|
||||
// Should be passed by the QuicConnection to the PacketWireTransfer -> Unpacker
|
||||
case QuicPacketType.ShortHeader:
|
||||
result = new ShortHeaderPacket(1);
|
||||
break;
|
||||
}
|
||||
|
||||
public QuicPacketType GetPacketType(byte[] data)
|
||||
if (result == null)
|
||||
{
|
||||
if (data == null || data.Length <= 0)
|
||||
{
|
||||
return QuicPacketType.Broken;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
byte type = data[0];
|
||||
result.Decode(data);
|
||||
|
||||
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 result;
|
||||
}
|
||||
|
||||
public QuicPacketType GetPacketType(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length <= 0)
|
||||
{
|
||||
return QuicPacketType.Broken;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Packets
|
||||
namespace EonaCat.Quic.Infrastructure.Packets;
|
||||
// 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 class VersionNegotiationPacket : Packet
|
||||
{
|
||||
// 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 override byte Type => throw new NotImplementedException();
|
||||
|
||||
public class VersionNegotiationPacket : Packet
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
public override byte Type => throw new NotImplementedException();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Decode(byte[] packet)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override byte[] Encode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
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.
|
||||
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 QuicPacketType
|
||||
{
|
||||
Initial,
|
||||
LongHeader,
|
||||
ShortHeader,
|
||||
VersionNegotiation,
|
||||
Broken
|
||||
}
|
||||
public enum QuicPacketType
|
||||
{
|
||||
Initial,
|
||||
LongHeader,
|
||||
ShortHeader,
|
||||
VersionNegotiation,
|
||||
Broken
|
||||
}
|
|
@ -1,55 +1,55 @@
|
|||
namespace EonaCat.Quic.Infrastructure.Settings
|
||||
namespace EonaCat.Quic.Infrastructure.Settings;
|
||||
// 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 class QuicSettings
|
||||
{
|
||||
// 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.
|
||||
/// <summary>
|
||||
/// Path Maximum Transmission Unit. Indicates the mandatory initial packet capacity, and the maximum UDP packet
|
||||
/// capacity.
|
||||
/// </summary>
|
||||
public const int PMTU = 1200;
|
||||
|
||||
public class QuicSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Path Maximum Transmission Unit. Indicates the mandatory initial packet capacity, and the maximum UDP packet capacity.
|
||||
/// </summary>
|
||||
public const int PMTU = 1200;
|
||||
/// <summary>
|
||||
/// Does the server want the first connected client to decide it's initial connection id?
|
||||
/// </summary>
|
||||
public const bool CanAcceptInitialClientConnectionId = false;
|
||||
|
||||
/// <summary>
|
||||
/// Does the server want the first connected client to decide it's initial connection id?
|
||||
/// </summary>
|
||||
public const bool CanAcceptInitialClientConnectionId = false;
|
||||
/// <summary>
|
||||
/// TBD. quic-transport 5.1.
|
||||
/// </summary>
|
||||
public const int MaximumConnectionIds = 8;
|
||||
|
||||
/// <summary>
|
||||
/// TBD. quic-transport 5.1.
|
||||
/// </summary>
|
||||
public const int MaximumConnectionIds = 8;
|
||||
/// <summary>
|
||||
/// Maximum number of streams that connection can handle.
|
||||
/// </summary>
|
||||
public const int MaximumStreamId = 128;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of streams that connection can handle.
|
||||
/// </summary>
|
||||
public const int MaximumStreamId = 128;
|
||||
/// <summary>
|
||||
/// Maximum packets that can be transferred before any data transfer (loss of packets, packet resent, infinite ack
|
||||
/// loop)
|
||||
/// </summary>
|
||||
public const int MaximumInitialPacketNumber = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum packets that can be transferred before any data transfer (loss of packets, packet resent, infinite ack loop)
|
||||
/// </summary>
|
||||
public const int MaximumInitialPacketNumber = 100;
|
||||
/// <summary>
|
||||
/// Should the server buffer packets that came before the initial packet?
|
||||
/// </summary>
|
||||
public const bool ShouldBufferPacketsBeforeConnection = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should the server buffer packets that came before the initial packet?
|
||||
/// </summary>
|
||||
public const bool ShouldBufferPacketsBeforeConnection = false;
|
||||
/// <summary>
|
||||
/// Limit the maximum number of frames a packet can carry.
|
||||
/// </summary>
|
||||
public const int MaximumFramesPerPacket = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Limit the maximum number of frames a packet can carry.
|
||||
/// </summary>
|
||||
public const int MaximumFramesPerPacket = 10;
|
||||
/// <summary>
|
||||
/// Maximum data that can be transferred for a Connection.
|
||||
/// Currently 10MB.
|
||||
/// </summary>
|
||||
public const int MaxData = 10 * 1000 * 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum data that can be transferred for a Connection.
|
||||
/// Currently 10MB.
|
||||
/// </summary>
|
||||
public const int MaxData = 10 * 1000 * 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum data that can be transferred for a Stream.
|
||||
/// Currently 0.078125 MB, which is MaxData / MaximumStreamId
|
||||
/// </summary>
|
||||
public const int MaxStreamData = 78125;
|
||||
}
|
||||
/// <summary>
|
||||
/// Maximum data that can be transferred for a Stream.
|
||||
/// Currently 0.078125 MB, which is MaxData / MaximumStreamId
|
||||
/// </summary>
|
||||
public const int MaxStreamData = 78125;
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.Quic.Infrastructure.Settings
|
||||
namespace EonaCat.Quic.Infrastructure.Settings;
|
||||
// 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 class QuicVersion
|
||||
{
|
||||
// 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 const int CurrentVersion = 16;
|
||||
|
||||
public class QuicVersion
|
||||
{
|
||||
public const int CurrentVersion = 16;
|
||||
|
||||
public static readonly List<uint> SupportedVersions = new List<uint>() { 15, 16 };
|
||||
}
|
||||
public static readonly List<uint> SupportedVersions = new() { 15, 16 };
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
using EonaCat.Quic.Helpers;
|
||||
|
||||
namespace EonaCat.Quic.InternalInfrastructure
|
||||
namespace EonaCat.Quic.InternalInfrastructure;
|
||||
// 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.
|
||||
|
||||
internal class ConnectionData
|
||||
{
|
||||
// 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.
|
||||
|
||||
internal class ConnectionData
|
||||
public ConnectionData(PacketWireTransfer pwt, IntegerParts connectionId, IntegerParts peerConnnectionId)
|
||||
{
|
||||
public PacketWireTransfer PWT { get; set; }
|
||||
public IntegerParts ConnectionId { get; set; }
|
||||
public IntegerParts PeerConnectionId { get; set; }
|
||||
|
||||
public ConnectionData(PacketWireTransfer pwt, IntegerParts connectionId, IntegerParts peerConnnectionId)
|
||||
{
|
||||
PWT = pwt;
|
||||
ConnectionId = connectionId;
|
||||
PeerConnectionId = peerConnnectionId;
|
||||
}
|
||||
PWT = pwt;
|
||||
ConnectionId = connectionId;
|
||||
PeerConnectionId = peerConnnectionId;
|
||||
}
|
||||
|
||||
public PacketWireTransfer PWT { get; set; }
|
||||
public IntegerParts ConnectionId { get; set; }
|
||||
public IntegerParts PeerConnectionId { get; set; }
|
||||
}
|
|
@ -1,54 +1,52 @@
|
|||
using EonaCat.Quic.Exceptions;
|
||||
using EonaCat.Quic.Infrastructure.Packets;
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EonaCat.Quic.Exceptions;
|
||||
using EonaCat.Quic.Infrastructure.Packets;
|
||||
|
||||
namespace EonaCat.Quic.InternalInfrastructure
|
||||
namespace EonaCat.Quic.InternalInfrastructure;
|
||||
// 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.
|
||||
|
||||
internal class PacketWireTransfer
|
||||
{
|
||||
// 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.
|
||||
private readonly UdpClient _client;
|
||||
|
||||
internal class PacketWireTransfer
|
||||
private readonly Unpacker _unpacker;
|
||||
private IPEndPoint _peerEndpoint;
|
||||
|
||||
public PacketWireTransfer(UdpClient client, IPEndPoint peerEndpoint)
|
||||
{
|
||||
private readonly UdpClient _client;
|
||||
private IPEndPoint _peerEndpoint;
|
||||
_client = client;
|
||||
_peerEndpoint = peerEndpoint;
|
||||
|
||||
private readonly Unpacker _unpacker;
|
||||
_unpacker = new Unpacker();
|
||||
}
|
||||
|
||||
public PacketWireTransfer(UdpClient client, IPEndPoint peerEndpoint)
|
||||
public Packet ReadPacket()
|
||||
{
|
||||
// Await response for sucessfull connection creation by the server
|
||||
var peerData = _client.Receive(ref _peerEndpoint);
|
||||
if (peerData == null)
|
||||
{
|
||||
_client = client;
|
||||
_peerEndpoint = peerEndpoint;
|
||||
|
||||
_unpacker = new Unpacker();
|
||||
throw new ConnectionException("Server did not respond properly.");
|
||||
}
|
||||
|
||||
public Packet ReadPacket()
|
||||
{
|
||||
// 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.");
|
||||
}
|
||||
var packet = _unpacker.Unpack(peerData);
|
||||
|
||||
Packet packet = _unpacker.Unpack(peerData);
|
||||
return packet;
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
public bool SendPacket(Packet packet)
|
||||
{
|
||||
var data = packet.Encode();
|
||||
|
||||
public bool SendPacket(Packet packet)
|
||||
{
|
||||
byte[] data = packet.Encode();
|
||||
var sent = _client.Send(data, data.Length, _peerEndpoint);
|
||||
|
||||
int sent = _client.Send(data, data.Length, _peerEndpoint);
|
||||
return sent > 0;
|
||||
}
|
||||
|
||||
return sent > 0;
|
||||
}
|
||||
|
||||
public IPEndPoint LastTransferEndpoint()
|
||||
{
|
||||
return _peerEndpoint;
|
||||
}
|
||||
public IPEndPoint LastTransferEndpoint()
|
||||
{
|
||||
return _peerEndpoint;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Exceptions;
|
||||
using EonaCat.Quic.Helpers;
|
||||
using EonaCat.Quic.Infrastructure.Frames;
|
||||
|
@ -6,113 +10,107 @@ 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
|
||||
namespace EonaCat.Quic;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Quic Client. Used for sending and receiving data from a Quic Server.
|
||||
/// </summary>
|
||||
public class QuicClient : QuicTransport
|
||||
{
|
||||
// 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.
|
||||
private readonly UdpClient _client;
|
||||
private readonly InitialPacketCreator _packetCreator;
|
||||
|
||||
private QuicConnection _connection;
|
||||
|
||||
private ulong _maximumStreams = QuicSettings.MaximumStreamId;
|
||||
private IPEndPoint _peerIp;
|
||||
private PacketWireTransfer _pwt;
|
||||
|
||||
public QuicClient()
|
||||
{
|
||||
_client = new UdpClient();
|
||||
_packetCreator = new InitialPacketCreator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quic Client. Used for sending and receiving data from a Quic Server.
|
||||
/// Connect to a remote server.
|
||||
/// </summary>
|
||||
public class QuicClient : QuicTransport
|
||||
/// <param name="ip">Ip Address</param>
|
||||
/// <param name="port">Port</param>
|
||||
/// <returns></returns>
|
||||
public QuicConnection Connect(string ip, int port)
|
||||
{
|
||||
private IPEndPoint _peerIp;
|
||||
private readonly UdpClient _client;
|
||||
|
||||
private QuicConnection _connection;
|
||||
private readonly InitialPacketCreator _packetCreator;
|
||||
|
||||
private ulong _maximumStreams = QuicSettings.MaximumStreamId;
|
||||
private PacketWireTransfer _pwt;
|
||||
|
||||
public QuicClient()
|
||||
// Establish socket connection
|
||||
var ipEntry = Uri.CheckHostName(ip);
|
||||
IPAddress ipAddress;
|
||||
if (ipEntry == UriHostNameType.Dns)
|
||||
{
|
||||
_client = new UdpClient();
|
||||
_packetCreator = new InitialPacketCreator();
|
||||
ipAddress = Dns.GetHostEntry(ip).AddressList?.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
ipAddress = IPAddress.Parse(ip);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to a remote server.
|
||||
/// </summary>
|
||||
/// <param name="ip">Ip Address</param>
|
||||
/// <param name="port">Port</param>
|
||||
/// <returns></returns>
|
||||
public QuicConnection Connect(string ip, int port)
|
||||
_peerIp = new IPEndPoint(ipAddress, port);
|
||||
|
||||
// Initialize packet reader
|
||||
_pwt = new PacketWireTransfer(_client, _peerIp);
|
||||
|
||||
// Start initial protocol process
|
||||
var connectionPacket = _packetCreator.CreateInitialPacket(0, 0);
|
||||
|
||||
// Send the initial packet
|
||||
_pwt.SendPacket(connectionPacket);
|
||||
|
||||
// Await response for sucessfull connection creation by the server
|
||||
var packet = (InitialPacket)_pwt.ReadPacket();
|
||||
|
||||
HandleInitialFrames(packet);
|
||||
EstablishConnection(packet.SourceConnectionId, packet.SourceConnectionId);
|
||||
|
||||
return _connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles initial packet's frames. (In most cases protocol frames)
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
private void HandleInitialFrames(Packet packet)
|
||||
{
|
||||
var frames = packet.GetFrames();
|
||||
for (var i = frames.Count - 1; i > 0; i--)
|
||||
{
|
||||
// Establish socket connection
|
||||
var ipEntry = Uri.CheckHostName(ip);
|
||||
IPAddress ipAddress;
|
||||
if (ipEntry == UriHostNameType.Dns)
|
||||
var frame = frames[i];
|
||||
if (frame is ConnectionCloseFrame ccf)
|
||||
{
|
||||
ipAddress = Dns.GetHostEntry(ip).AddressList?.FirstOrDefault();
|
||||
throw new ConnectionException(ccf.ReasonPhrase);
|
||||
}
|
||||
else
|
||||
|
||||
if (frame is MaxStreamsFrame msf)
|
||||
{
|
||||
ipAddress = IPAddress.Parse(ip);
|
||||
_maximumStreams = msf.MaximumStreams.Value;
|
||||
}
|
||||
_peerIp = new IPEndPoint(ipAddress, port);
|
||||
|
||||
// Initialize packet reader
|
||||
_pwt = new PacketWireTransfer(_client, _peerIp);
|
||||
|
||||
// Start initial protocol process
|
||||
InitialPacket connectionPacket = _packetCreator.CreateInitialPacket(0, 0);
|
||||
|
||||
// Send the initial packet
|
||||
_pwt.SendPacket(connectionPacket);
|
||||
|
||||
// Await response for sucessfull connection creation by the server
|
||||
InitialPacket packet = (InitialPacket)_pwt.ReadPacket();
|
||||
|
||||
HandleInitialFrames(packet);
|
||||
EstablishConnection(packet.SourceConnectionId, packet.SourceConnectionId);
|
||||
|
||||
return _connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles initial packet's frames. (In most cases protocol frames)
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
private void HandleInitialFrames(Packet packet)
|
||||
{
|
||||
List<Frame> frames = packet.GetFrames();
|
||||
for (int i = frames.Count - 1; i > 0; i--)
|
||||
// Break out if the first Padding Frame has been reached
|
||||
if (frame is PaddingFrame)
|
||||
{
|
||||
Frame frame = frames[i];
|
||||
if (frame is ConnectionCloseFrame ccf)
|
||||
{
|
||||
throw new ConnectionException(ccf.ReasonPhrase);
|
||||
}
|
||||
|
||||
if (frame is MaxStreamsFrame msf)
|
||||
{
|
||||
_maximumStreams = msf.MaximumStreams.Value;
|
||||
}
|
||||
|
||||
// Break out if the first Padding Frame has been reached
|
||||
if (frame is PaddingFrame)
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new connection
|
||||
/// </summary>
|
||||
/// <param name="connectionId"></param>
|
||||
/// <param name="peerConnectionId"></param>
|
||||
private void EstablishConnection(IntegerParts connectionId, IntegerParts peerConnectionId)
|
||||
{
|
||||
ConnectionData connection = new ConnectionData(_pwt, connectionId, peerConnectionId);
|
||||
_connection = new QuicConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new connection
|
||||
/// </summary>
|
||||
/// <param name="connectionId"></param>
|
||||
/// <param name="peerConnectionId"></param>
|
||||
private void EstablishConnection(IntegerParts connectionId, IntegerParts peerConnectionId)
|
||||
{
|
||||
var connection = new ConnectionData(_pwt, connectionId, peerConnectionId);
|
||||
_connection = new QuicConnection(connection);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Constants;
|
||||
using EonaCat.Quic.Events;
|
||||
using EonaCat.Quic.Helpers;
|
||||
|
@ -8,150 +12,146 @@ 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
|
||||
namespace EonaCat.Quic;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Quic Server - a Quic server that processes incoming connections and if possible sends back data on it's peers.
|
||||
/// </summary>
|
||||
public class QuicServer : QuicTransport
|
||||
{
|
||||
// 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.
|
||||
private readonly string _hostname;
|
||||
private readonly InitialPacketCreator _packetCreator;
|
||||
|
||||
private readonly int _port;
|
||||
private readonly Unpacker _unpacker;
|
||||
|
||||
private UdpClient _client;
|
||||
|
||||
private PacketWireTransfer _pwt;
|
||||
private bool _started;
|
||||
|
||||
/// <summary>
|
||||
/// Quic Server - a Quic server that processes incoming connections and if possible sends back data on it's peers.
|
||||
/// Create a new instance of QuicListener.
|
||||
/// </summary>
|
||||
public class QuicServer : QuicTransport
|
||||
/// <param name="port">The port that the server will listen on.</param>
|
||||
public QuicServer(string hostName, int port)
|
||||
{
|
||||
private readonly Unpacker _unpacker;
|
||||
private readonly InitialPacketCreator _packetCreator;
|
||||
_started = false;
|
||||
_port = port;
|
||||
_hostname = hostName;
|
||||
|
||||
private PacketWireTransfer _pwt;
|
||||
_unpacker = new Unpacker();
|
||||
_packetCreator = new InitialPacketCreator();
|
||||
}
|
||||
|
||||
private UdpClient _client;
|
||||
public event ClientConnectedEvent OnClientConnected;
|
||||
|
||||
private readonly int _port;
|
||||
private readonly string _hostname;
|
||||
private bool _started;
|
||||
|
||||
public event ClientConnectedEvent OnClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of QuicListener.
|
||||
/// </summary>
|
||||
/// <param name="port">The port that the server will listen on.</param>
|
||||
public QuicServer(string hostName, int port)
|
||||
/// <summary>
|
||||
/// Starts the listener.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
var ipEntry = Uri.CheckHostName(_hostname);
|
||||
IPAddress ipAddress;
|
||||
if (ipEntry == UriHostNameType.Dns)
|
||||
{
|
||||
_started = false;
|
||||
_port = port;
|
||||
_hostname = hostName;
|
||||
|
||||
_unpacker = new Unpacker();
|
||||
_packetCreator = new InitialPacketCreator();
|
||||
ipAddress = Dns.GetHostEntry(_hostname).AddressList?.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
ipAddress = IPAddress.Parse(_hostname);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
_client = new UdpClient(new IPEndPoint(ipAddress, _port));
|
||||
_started = true;
|
||||
_pwt = new PacketWireTransfer(_client, null);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var ipEntry = Uri.CheckHostName(_hostname);
|
||||
IPAddress ipAddress;
|
||||
if (ipEntry == UriHostNameType.Dns)
|
||||
var packet = _pwt.ReadPacket();
|
||||
if (packet is InitialPacket)
|
||||
{
|
||||
ipAddress = Dns.GetHostEntry(_hostname).AddressList?.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
ipAddress = IPAddress.Parse(_hostname);
|
||||
var connection = ProcessInitialPacket(packet, _pwt.LastTransferEndpoint());
|
||||
|
||||
OnClientConnected?.Invoke(connection);
|
||||
}
|
||||
|
||||
_client = new UdpClient(new IPEndPoint(ipAddress, _port));
|
||||
_started = true;
|
||||
_pwt = new PacketWireTransfer(_client, null);
|
||||
|
||||
while (true)
|
||||
if (packet is ShortHeaderPacket)
|
||||
{
|
||||
Packet packet = _pwt.ReadPacket();
|
||||
if (packet is InitialPacket)
|
||||
{
|
||||
QuicConnection connection = ProcessInitialPacket(packet, _pwt.LastTransferEndpoint());
|
||||
|
||||
OnClientConnected?.Invoke(connection);
|
||||
}
|
||||
|
||||
if (packet is ShortHeaderPacket)
|
||||
{
|
||||
ProcessShortHeaderPacket(packet);
|
||||
}
|
||||
ProcessShortHeaderPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the listener.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_client.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes incomming initial packet and creates or halts a connection.
|
||||
/// </summary>
|
||||
/// <param name="packet">Initial Packet</param>
|
||||
/// <param name="endPoint">Peer's endpoint</param>
|
||||
/// <returns></returns>
|
||||
private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint)
|
||||
{
|
||||
QuicConnection result = null;
|
||||
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))
|
||||
{
|
||||
VersionNegotiationPacket vnp = _packetCreator.CreateVersionNegotiationPacket();
|
||||
data = vnp.Encode();
|
||||
|
||||
_client.Send(data, data.Length, endPoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
InitialPacket cast = packet as InitialPacket;
|
||||
InitialPacket ip = _packetCreator.CreateInitialPacket(0, cast.SourceConnectionId);
|
||||
|
||||
// Protocol violation if the initial packet is smaller than the PMTU. (pt. 14 / 16th draft)
|
||||
if (cast.Encode().Length < QuicSettings.PMTU)
|
||||
{
|
||||
ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.PROTOCOL_VIOLATION, 0x00, ErrorConstants.PMTUNotReached));
|
||||
}
|
||||
else if (ConnectionPool.AddConnection(new ConnectionData(new PacketWireTransfer(_client, endPoint), cast.SourceConnectionId, 0), out ulong availableConnectionId) == true)
|
||||
{
|
||||
// Tell the peer the available connection id
|
||||
ip.SourceConnectionId = (byte)availableConnectionId;
|
||||
|
||||
// We're including the maximum possible stream id during the connection handshake. (4.5 / 16th draft)
|
||||
ip.AttachFrame(new MaxStreamsFrame(QuicSettings.MaximumStreamId, StreamType.ServerBidirectional));
|
||||
|
||||
// Set the return result
|
||||
result = ConnectionPool.Find(availableConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not accepting connections. Send initial packet with CONNECTION_CLOSE frame.
|
||||
// Maximum buffer size should be set in QuicSettings.
|
||||
ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.CONNECTION_REFUSED, 0x00, ErrorConstants.ServerTooBusy));
|
||||
}
|
||||
|
||||
data = ip.Encode();
|
||||
int dataSent = _client.Send(data, data.Length, endPoint);
|
||||
if (dataSent > 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the listener.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_client.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes incomming initial packet and creates or halts a connection.
|
||||
/// </summary>
|
||||
/// <param name="packet">Initial Packet</param>
|
||||
/// <param name="endPoint">Peer's endpoint</param>
|
||||
/// <returns></returns>
|
||||
private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint)
|
||||
{
|
||||
QuicConnection result = null;
|
||||
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))
|
||||
{
|
||||
var vnp = _packetCreator.CreateVersionNegotiationPacket();
|
||||
data = vnp.Encode();
|
||||
|
||||
_client.Send(data, data.Length, endPoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
var cast = packet as InitialPacket;
|
||||
var ip = _packetCreator.CreateInitialPacket(0, cast.SourceConnectionId);
|
||||
|
||||
// Protocol violation if the initial packet is smaller than the PMTU. (pt. 14 / 16th draft)
|
||||
if (cast.Encode().Length < QuicSettings.PMTU)
|
||||
{
|
||||
ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.PROTOCOL_VIOLATION, 0x00, ErrorConstants.PMTUNotReached));
|
||||
}
|
||||
else if (ConnectionPool.AddConnection(
|
||||
new ConnectionData(new PacketWireTransfer(_client, endPoint), cast.SourceConnectionId, 0),
|
||||
out var availableConnectionId))
|
||||
{
|
||||
// Tell the peer the available connection id
|
||||
ip.SourceConnectionId = (byte)availableConnectionId;
|
||||
|
||||
// We're including the maximum possible stream id during the connection handshake. (4.5 / 16th draft)
|
||||
ip.AttachFrame(new MaxStreamsFrame(QuicSettings.MaximumStreamId, StreamType.ServerBidirectional));
|
||||
|
||||
// Set the return result
|
||||
result = ConnectionPool.Find(availableConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not accepting connections. Send initial packet with CONNECTION_CLOSE frame.
|
||||
// Maximum buffer size should be set in QuicSettings.
|
||||
ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.CONNECTION_REFUSED, 0x00, ErrorConstants.ServerTooBusy));
|
||||
}
|
||||
|
||||
data = ip.Encode();
|
||||
var dataSent = _client.Send(data, data.Length, endPoint);
|
||||
if (dataSent > 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,30 +1,28 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Infrastructure.Packets;
|
||||
|
||||
namespace EonaCat.Quic
|
||||
namespace EonaCat.Quic;
|
||||
// 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 class QuicTransport
|
||||
{
|
||||
// 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 class QuicTransport
|
||||
/// <summary>
|
||||
/// Processes short header packet, by distributing the frames towards connections.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
protected void ProcessShortHeaderPacket(Packet packet)
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes short header packet, by distributing the frames towards connections.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
protected void ProcessShortHeaderPacket(Packet packet)
|
||||
var shp = (ShortHeaderPacket)packet;
|
||||
|
||||
var connection = ConnectionPool.Find(shp.DestinationConnectionId);
|
||||
|
||||
// No suitable connection found. Discard the packet.
|
||||
if (connection == null)
|
||||
{
|
||||
ShortHeaderPacket shp = (ShortHeaderPacket)packet;
|
||||
|
||||
QuicConnection connection = ConnectionPool.Find(shp.DestinationConnectionId);
|
||||
|
||||
// No suitable connection found. Discard the packet.
|
||||
if (connection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
connection.ProcessFrames(shp.GetFrames());
|
||||
return;
|
||||
}
|
||||
|
||||
connection.ProcessFrames(shp.GetFrames());
|
||||
}
|
||||
}
|
|
@ -1,217 +1,213 @@
|
|||
using EonaCat.Quic.Connections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EonaCat.Quic.Connections;
|
||||
using EonaCat.Quic.Constants;
|
||||
using EonaCat.Quic.Events;
|
||||
using EonaCat.Quic.Exceptions;
|
||||
using EonaCat.Quic.Helpers;
|
||||
using EonaCat.Quic.Infrastructure;
|
||||
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
|
||||
namespace EonaCat.Quic.Streams;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Virtual multiplexing channel.
|
||||
/// </summary>
|
||||
public class QuicStream
|
||||
{
|
||||
// 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.
|
||||
private readonly QuicConnection _connection;
|
||||
private readonly SortedList<ulong, byte[]> _data = new();
|
||||
private ulong _currentTransferRate;
|
||||
private ulong _maximumStreamData;
|
||||
private ulong _sendOffset;
|
||||
|
||||
public QuicStream(QuicConnection connection, StreamId streamId)
|
||||
{
|
||||
StreamId = streamId;
|
||||
Type = streamId.Type;
|
||||
|
||||
_maximumStreamData = QuicSettings.MaxStreamData;
|
||||
_currentTransferRate = 0;
|
||||
_sendOffset = 0;
|
||||
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public StreamState State { get; set; }
|
||||
public StreamType Type { get; set; }
|
||||
public StreamId StreamId { get; }
|
||||
|
||||
public StreamDataReceivedEvent OnStreamDataReceived { get; set; }
|
||||
|
||||
public byte[] Data => _data.SelectMany(v => v.Value).ToArray();
|
||||
|
||||
public bool Send(byte[] data)
|
||||
{
|
||||
if (Type == StreamType.ServerUnidirectional)
|
||||
{
|
||||
throw new StreamException("Cannot send data on unidirectional stream.");
|
||||
}
|
||||
|
||||
_connection.IncrementRate(data.Length);
|
||||
|
||||
var numberOfPackets = data.Length / QuicSettings.PMTU + 1;
|
||||
var leftoverCarry = data.Length % QuicSettings.PMTU;
|
||||
|
||||
for (var i = 0; i < numberOfPackets; i++)
|
||||
{
|
||||
var eos = false;
|
||||
var dataSize = QuicSettings.PMTU;
|
||||
if (i == numberOfPackets - 1)
|
||||
{
|
||||
eos = true;
|
||||
dataSize = leftoverCarry;
|
||||
}
|
||||
|
||||
var buffer = new byte[dataSize];
|
||||
Buffer.BlockCopy(data, (int)_sendOffset, buffer, 0, dataSize);
|
||||
|
||||
var packet = _connection.PacketCreator.CreateDataPacket(StreamId.IntegerValue, buffer, _sendOffset, eos);
|
||||
if (i == 0 && data.Length >= QuicSettings.MaxStreamData)
|
||||
{
|
||||
packet.AttachFrame(new MaxStreamDataFrame(StreamId.IntegerValue, (ulong)(data.Length + 1)));
|
||||
}
|
||||
|
||||
if (_connection.MaximumReached())
|
||||
{
|
||||
packet.AttachFrame(new StreamDataBlockedFrame(StreamId.IntegerValue, (ulong)data.Length));
|
||||
}
|
||||
|
||||
_sendOffset += (ulong)buffer.Length;
|
||||
|
||||
_connection.SendData(packet);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual multiplexing channel.
|
||||
/// Client only!
|
||||
/// </summary>
|
||||
public class QuicStream
|
||||
/// <returns></returns>
|
||||
public byte[] Receive()
|
||||
{
|
||||
private readonly SortedList<ulong, byte[]> _data = new SortedList<ulong, byte[]>();
|
||||
private readonly QuicConnection _connection;
|
||||
private ulong _maximumStreamData;
|
||||
private ulong _currentTransferRate;
|
||||
private ulong _sendOffset;
|
||||
|
||||
public StreamState State { get; set; }
|
||||
public StreamType Type { get; set; }
|
||||
public StreamId StreamId { get; }
|
||||
|
||||
public StreamDataReceivedEvent OnStreamDataReceived { get; set; }
|
||||
|
||||
public byte[] Data => _data.SelectMany(v => v.Value).ToArray();
|
||||
|
||||
public QuicStream(QuicConnection connection, StreamId streamId)
|
||||
if (Type == StreamType.ClientUnidirectional)
|
||||
{
|
||||
StreamId = streamId;
|
||||
Type = streamId.Type;
|
||||
|
||||
_maximumStreamData = QuicSettings.MaxStreamData;
|
||||
_currentTransferRate = 0;
|
||||
_sendOffset = 0;
|
||||
|
||||
_connection = connection;
|
||||
throw new StreamException("Cannot receive data on unidirectional stream.");
|
||||
}
|
||||
|
||||
public bool Send(byte[] data)
|
||||
while (!IsStreamFull() || State == StreamState.Receive) _connection.ReceivePacket();
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
public void ResetStream(ResetStreamFrame frame)
|
||||
{
|
||||
// Reset the state
|
||||
State = StreamState.ResetReceived;
|
||||
// Clear data
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
public void SetMaximumStreamData(ulong maximumData)
|
||||
{
|
||||
_maximumStreamData = maximumData;
|
||||
}
|
||||
|
||||
public bool CanSendData()
|
||||
{
|
||||
if (Type == StreamType.ServerUnidirectional || Type == StreamType.ClientUnidirectional)
|
||||
{
|
||||
if (Type == StreamType.ServerUnidirectional)
|
||||
{
|
||||
throw new StreamException("Cannot send data on unidirectional stream.");
|
||||
}
|
||||
|
||||
_connection.IncrementRate(data.Length);
|
||||
|
||||
int numberOfPackets = (data.Length / QuicSettings.PMTU) + 1;
|
||||
int leftoverCarry = data.Length % QuicSettings.PMTU;
|
||||
|
||||
for (int i = 0; i < numberOfPackets; i++)
|
||||
{
|
||||
bool eos = false;
|
||||
int dataSize = QuicSettings.PMTU;
|
||||
if (i == numberOfPackets - 1)
|
||||
{
|
||||
eos = true;
|
||||
dataSize = leftoverCarry;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[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, (ulong)(data.Length + 1)));
|
||||
}
|
||||
|
||||
if (_connection.MaximumReached())
|
||||
{
|
||||
packet.AttachFrame(new StreamDataBlockedFrame(StreamId.IntegerValue, (ulong)data.Length));
|
||||
}
|
||||
|
||||
_sendOffset += (ulong)buffer.Length;
|
||||
|
||||
_connection.SendData(packet);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client only!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] Receive()
|
||||
{
|
||||
if (Type == StreamType.ClientUnidirectional)
|
||||
{
|
||||
throw new StreamException("Cannot receive data on unidirectional stream.");
|
||||
}
|
||||
|
||||
while (!IsStreamFull() || State == StreamState.Receive)
|
||||
{
|
||||
_connection.ReceivePacket();
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
public void ResetStream(ResetStreamFrame frame)
|
||||
{
|
||||
// Reset the state
|
||||
State = StreamState.ResetReceived;
|
||||
// Clear data
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
public void SetMaximumStreamData(ulong maximumData)
|
||||
{
|
||||
_maximumStreamData = maximumData;
|
||||
}
|
||||
|
||||
public bool CanSendData()
|
||||
{
|
||||
if (Type == StreamType.ServerUnidirectional || Type == StreamType.ClientUnidirectional)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (State == StreamState.Receive || State == StreamState.SizeKnown)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsOpen()
|
||||
if (State == StreamState.Receive || State == StreamState.SizeKnown)
|
||||
{
|
||||
if (State == StreamState.DataReceived || State == StreamState.ResetReceived)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsOpen()
|
||||
{
|
||||
if (State == StreamState.DataReceived || State == StreamState.ResetReceived)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ProcessData(StreamFrame frame)
|
||||
{
|
||||
// Do not accept data if the stream is reset.
|
||||
if (State == StreamState.ResetReceived)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = frame.StreamData;
|
||||
if (frame.Offset != null)
|
||||
{
|
||||
_data.Add(frame.Offset.Value, frame.StreamData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.Add(0, frame.StreamData);
|
||||
}
|
||||
|
||||
// Either this frame marks the end of the stream,
|
||||
// or fin frame came before the data frames
|
||||
if (frame.EndOfStream)
|
||||
{
|
||||
State = StreamState.SizeKnown;
|
||||
}
|
||||
|
||||
_currentTransferRate += (ulong)data.Length;
|
||||
|
||||
// Terminate connection if maximum stream data is reached
|
||||
if (_currentTransferRate >= _maximumStreamData)
|
||||
{
|
||||
var errorPacket = _connection.PacketCreator.CreateConnectionClosePacket(ErrorCode.FLOW_CONTROL_ERROR,
|
||||
frame.ActualType, ErrorConstants.MaxDataTransfer);
|
||||
_connection.SendData(errorPacket);
|
||||
_connection.TerminateConnection();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (State == StreamState.SizeKnown && IsStreamFull())
|
||||
{
|
||||
State = StreamState.DataReceived;
|
||||
|
||||
OnStreamDataReceived?.Invoke(this, Data);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessStreamDataBlocked(StreamDataBlockedFrame frame)
|
||||
{
|
||||
State = StreamState.DataReceived;
|
||||
}
|
||||
|
||||
private bool IsStreamFull()
|
||||
{
|
||||
ulong length = 0;
|
||||
|
||||
foreach (var kvp in _data)
|
||||
{
|
||||
if (kvp.Key > 0 && kvp.Key != length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
length += (ulong)kvp.Value.Length;
|
||||
}
|
||||
|
||||
public void ProcessData(StreamFrame frame)
|
||||
{
|
||||
// Do not accept data if the stream is reset.
|
||||
if (State == StreamState.ResetReceived)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data = frame.StreamData;
|
||||
if (frame.Offset != null)
|
||||
{
|
||||
_data.Add(frame.Offset.Value, frame.StreamData);
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.Add(0, frame.StreamData);
|
||||
}
|
||||
|
||||
// Either this frame marks the end of the stream,
|
||||
// or fin frame came before the data frames
|
||||
if (frame.EndOfStream)
|
||||
{
|
||||
State = StreamState.SizeKnown;
|
||||
}
|
||||
|
||||
_currentTransferRate += (ulong)data.Length;
|
||||
|
||||
// Terminate connection if maximum stream data is reached
|
||||
if (_currentTransferRate >= _maximumStreamData)
|
||||
{
|
||||
ShortHeaderPacket errorPacket = _connection.PacketCreator.CreateConnectionClosePacket(Infrastructure.ErrorCode.FLOW_CONTROL_ERROR, frame.ActualType, ErrorConstants.MaxDataTransfer);
|
||||
_connection.SendData(errorPacket);
|
||||
_connection.TerminateConnection();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (State == StreamState.SizeKnown && IsStreamFull())
|
||||
{
|
||||
State = StreamState.DataReceived;
|
||||
|
||||
OnStreamDataReceived?.Invoke(this, Data);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessStreamDataBlocked(StreamDataBlockedFrame frame)
|
||||
{
|
||||
State = StreamState.DataReceived;
|
||||
}
|
||||
|
||||
private bool IsStreamFull()
|
||||
{
|
||||
ulong length = 0;
|
||||
|
||||
foreach (var kvp in _data)
|
||||
{
|
||||
if (kvp.Key > 0 && kvp.Key != length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
length += (ulong)kvp.Value.Length;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
namespace EonaCat.Quic.Streams
|
||||
{
|
||||
// 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.Quic.Streams;
|
||||
// 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 StreamState
|
||||
{
|
||||
Receive,
|
||||
SizeKnown,
|
||||
DataReceived,
|
||||
DataRead,
|
||||
ResetReceived
|
||||
}
|
||||
public enum StreamState
|
||||
{
|
||||
Receive,
|
||||
SizeKnown,
|
||||
DataReceived,
|
||||
DataRead,
|
||||
ResetReceived
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace EonaCat.Network
|
||||
{
|
||||
// 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;
|
||||
// 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 class RemoteInfo
|
||||
{
|
||||
public bool IsTcp { get; set; }
|
||||
public bool HasEndpoint => EndPoint != null;
|
||||
public EndPoint EndPoint { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public IPAddress Address => HasEndpoint ? ((IPEndPoint)EndPoint).Address : null;
|
||||
public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0;
|
||||
public Socket Socket { get; set; }
|
||||
public bool IsIpv6 { get; set; }
|
||||
public bool IsWebSocket { get; internal set; }
|
||||
public string ClientId { get; internal set; }
|
||||
public string ClientName { get; internal set; }
|
||||
}
|
||||
public class RemoteInfo
|
||||
{
|
||||
public bool IsTcp { get; set; }
|
||||
public bool HasEndpoint => EndPoint != null;
|
||||
public EndPoint EndPoint { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public IPAddress Address => HasEndpoint ? ((IPEndPoint)EndPoint).Address : null;
|
||||
public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0;
|
||||
public Socket Socket { get; set; }
|
||||
public bool IsIPv6 { get; set; }
|
||||
public bool IsWebSocket { get; internal set; }
|
||||
public string ClientId { get; internal set; }
|
||||
public string ClientName { get; internal set; }
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.Network
|
||||
|
@ -9,54 +13,53 @@ namespace EonaCat.Network
|
|||
{
|
||||
private const int BUFFER_SIZE = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// OnConnect event
|
||||
/// </summary>
|
||||
private Socket _socket;
|
||||
private bool _isIPv6;
|
||||
|
||||
public bool IsIPv6 => _isIPv6;
|
||||
|
||||
public event Action<RemoteInfo> OnConnect;
|
||||
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnReceive;
|
||||
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnDisconnect;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
private Socket socket;
|
||||
|
||||
/// <summary>
|
||||
/// Create TCP client
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public Task ConnectAsync(string ipAddress, int port)
|
||||
public async Task ConnectAsync(string ipAddress, int port, bool useSsl = false, SslOptions sslOptions = null)
|
||||
{
|
||||
if (!IPAddress.TryParse(ipAddress, out IPAddress ip))
|
||||
if (!IPAddress.TryParse(ipAddress, out var ip))
|
||||
{
|
||||
throw new Exception("EonaCat Network: Invalid ipAddress given");
|
||||
throw new ArgumentException("Invalid ipAddress given");
|
||||
}
|
||||
|
||||
return CreateSocketTcpClientAsync(ip, port);
|
||||
await CreateSocketTcpClientAsync(ip, port, useSsl, sslOptions);
|
||||
}
|
||||
|
||||
private async Task CreateSocketTcpClientAsync(IPAddress ipAddress, int port)
|
||||
private async Task CreateSocketTcpClientAsync(IPAddress ipAddress, int port, bool useSsl, SslOptions sslOptions)
|
||||
{
|
||||
IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
socket = new Socket(IsIp6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_isIPv6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
_socket = new Socket(_isIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(ipAddress, port).ConfigureAwait(false);
|
||||
OnConnect?.Invoke(new RemoteInfo { IsTcp = true, IsIpv6 = IsIp6 });
|
||||
_ = StartReceivingAsync();
|
||||
await _socket.ConnectAsync(ipAddress, port).ConfigureAwait(false);
|
||||
OnConnect?.Invoke(new RemoteInfo { IsTcp = true, IsIPv6 = _isIPv6 });
|
||||
|
||||
if (useSsl)
|
||||
{
|
||||
var sslStream = new SslStream(new NetworkStream(_socket), false, ValidateRemoteCertificate, null);
|
||||
try
|
||||
{
|
||||
await sslStream.AuthenticateAsClientAsync(ipAddress.ToString(), sslOptions.ClientCertificates, sslOptions.SslProtocol, sslOptions.CheckCertificateRevocation);
|
||||
_ = StartReceivingSslAsync(sslStream);
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"AuthenticationException: {ex.Message}");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = StartReceivingAsync();
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
|
@ -65,26 +68,24 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public bool IsIp6 { get; set; }
|
||||
|
||||
private async Task StartReceivingAsync()
|
||||
{
|
||||
byte[] buffer = new byte[BUFFER_SIZE]; // Increased buffer size for better performance
|
||||
while (socket.Connected)
|
||||
var buffer = new byte[BUFFER_SIZE];
|
||||
while (_socket.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
int received = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None).ConfigureAwait(false);
|
||||
var received = await _socket.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None).ConfigureAwait(false);
|
||||
if (received > 0)
|
||||
{
|
||||
byte[] data = new byte[received];
|
||||
var data = new byte[received];
|
||||
Buffer.BlockCopy(buffer, 0, data, 0, received);
|
||||
OnReceive?.Invoke(new RemoteInfo
|
||||
{
|
||||
IsTcp = true,
|
||||
Data = data,
|
||||
EndPoint = socket.RemoteEndPoint,
|
||||
IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6
|
||||
EndPoint = _socket.RemoteEndPoint,
|
||||
IsIPv6 = _isIPv6
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -98,25 +99,73 @@ namespace EonaCat.Network
|
|||
break;
|
||||
}
|
||||
}
|
||||
OnDisconnect?.Invoke(new RemoteInfo { IsTcp = true, EndPoint = socket.RemoteEndPoint, IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6 });
|
||||
|
||||
OnDisconnect?.Invoke(new RemoteInfo
|
||||
{
|
||||
IsTcp = true,
|
||||
EndPoint = _socket.RemoteEndPoint,
|
||||
IsIPv6 = _isIPv6
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public Task SendAsync(byte[] data)
|
||||
private async Task StartReceivingSslAsync(SslStream sslStream)
|
||||
{
|
||||
return socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
|
||||
var buffer = new byte[BUFFER_SIZE];
|
||||
while (_socket.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var received = await sslStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
if (received > 0)
|
||||
{
|
||||
var data = new byte[received];
|
||||
Buffer.BlockCopy(buffer, 0, data, 0, received);
|
||||
OnReceive?.Invoke(new RemoteInfo
|
||||
{
|
||||
IsTcp = true,
|
||||
Data = data,
|
||||
EndPoint = _socket.RemoteEndPoint,
|
||||
IsIPv6 = _isIPv6
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"IOException: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"AuthenticationException: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OnDisconnect?.Invoke(new RemoteInfo
|
||||
{
|
||||
IsTcp = true,
|
||||
EndPoint = _socket.RemoteEndPoint,
|
||||
IsIPv6 = _isIPv6
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SendAsync(byte[] data)
|
||||
{
|
||||
await _socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
socket.Close();
|
||||
_socket.Close();
|
||||
}
|
||||
|
||||
private bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
return sslPolicyErrors == SslPolicyErrors.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -10,61 +14,33 @@ namespace EonaCat.Network
|
|||
{
|
||||
private const int BUFFER_SIZE = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// OnConnect event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnConnect;
|
||||
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnReceive;
|
||||
|
||||
/// <summary>
|
||||
/// OnSend event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnSend;
|
||||
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnDisconnect;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
private readonly TcpListener _listener;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly X509Certificate2 _certificate;
|
||||
private readonly SslOptions _sslOptions;
|
||||
private Task _acceptTask;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Create TCP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
public SocketTcpServer(IPAddress ipAddress, int port)
|
||||
public SocketTcpServer(IPAddress ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null)
|
||||
{
|
||||
_listener = new TcpListener(ipAddress, port);
|
||||
_certificate = certificate;
|
||||
_sslOptions = sslOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create TCP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
public SocketTcpServer(string ipAddress, int port)
|
||||
public SocketTcpServer(string ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null)
|
||||
{
|
||||
IPAddress address = IPAddress.Parse(ipAddress);
|
||||
var address = IPAddress.Parse(ipAddress);
|
||||
_listener = new TcpListener(address, port);
|
||||
_certificate = certificate;
|
||||
_sslOptions = sslOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start TCP server
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public event Action<RemoteInfo> OnConnect;
|
||||
public event Action<RemoteInfo> OnReceive;
|
||||
public event Action<RemoteInfo> OnSend;
|
||||
public event Action<RemoteInfo> OnDisconnect;
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_listener.Start();
|
||||
|
@ -79,8 +55,8 @@ namespace EonaCat.Network
|
|||
{
|
||||
try
|
||||
{
|
||||
var socket = await _listener.AcceptSocketAsync().ConfigureAwait(false);
|
||||
_ = HandleConnectionAsync(socket, cancellationToken);
|
||||
var tcpClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
|
||||
_ = HandleConnectionAsync(tcpClient, cancellationToken);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
|
@ -89,69 +65,76 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
private async Task HandleConnectionAsync(Socket socket, CancellationToken cancellationToken)
|
||||
private async Task HandleConnectionAsync(TcpClient tcpClient, CancellationToken cancellationToken)
|
||||
{
|
||||
OnConnect?.Invoke(new RemoteInfo
|
||||
var remoteEndpoint = tcpClient.Client.RemoteEndPoint;
|
||||
using (tcpClient)
|
||||
{
|
||||
Socket = socket,
|
||||
IsTcp = true,
|
||||
IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
EndPoint = socket.RemoteEndPoint
|
||||
});
|
||||
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
OnConnect?.Invoke(new RemoteInfo
|
||||
{
|
||||
int received = await ReceiveAsync(socket, buffer, cancellationToken).ConfigureAwait(false);
|
||||
if (received > 0)
|
||||
Socket = tcpClient.Client,
|
||||
IsTcp = true,
|
||||
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
EndPoint = remoteEndpoint
|
||||
});
|
||||
|
||||
Stream stream = tcpClient.GetStream();
|
||||
if (_certificate != null)
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, ValidateRemoteCertificate, null);
|
||||
try
|
||||
{
|
||||
byte[] data = new byte[received];
|
||||
Buffer.BlockCopy(buffer, 0, data, 0, received);
|
||||
OnReceive?.Invoke(new RemoteInfo
|
||||
{
|
||||
Socket = socket,
|
||||
IsTcp = true,
|
||||
IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
EndPoint = socket.RemoteEndPoint,
|
||||
Data = data
|
||||
});
|
||||
await sslStream.AuthenticateAsServerAsync(_certificate, _sslOptions.ClientCertificateRequired, _sslOptions.SslProtocol, _sslOptions.CheckCertificateRevocation);
|
||||
stream = sslStream;
|
||||
}
|
||||
else
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
break;
|
||||
OnError?.Invoke(ex, $"AuthenticationException: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
|
||||
var buffer = new byte[BUFFER_SIZE];
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
|
||||
break;
|
||||
try
|
||||
{
|
||||
var received = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (received > 0)
|
||||
{
|
||||
var data = new byte[received];
|
||||
Buffer.BlockCopy(buffer, 0, data, 0, received);
|
||||
OnReceive?.Invoke(new RemoteInfo
|
||||
{
|
||||
Socket = tcpClient.Client,
|
||||
IsTcp = true,
|
||||
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
EndPoint = remoteEndpoint,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"IOException: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnDisconnect?.Invoke(new RemoteInfo
|
||||
{
|
||||
Socket = socket,
|
||||
Socket = tcpClient.Client,
|
||||
IsTcp = true,
|
||||
EndPoint = socket.RemoteEndPoint,
|
||||
IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6
|
||||
EndPoint = remoteEndpoint,
|
||||
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6
|
||||
});
|
||||
}
|
||||
|
||||
private Task<int> ReceiveAsync(Socket socket, byte[] buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task<int>.Factory.FromAsync(socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, null, socket),
|
||||
socket.EndReceive);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Send data to socket
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendToAsync(Socket socket, byte[] data)
|
||||
{
|
||||
await socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None).ConfigureAwait(false);
|
||||
|
@ -160,15 +143,11 @@ namespace EonaCat.Network
|
|||
Socket = socket,
|
||||
IsTcp = true,
|
||||
EndPoint = socket.RemoteEndPoint,
|
||||
IsIpv6 = socket.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
IsIPv6 = socket.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop TCP server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
@ -179,8 +158,19 @@ namespace EonaCat.Network
|
|||
{
|
||||
await _acceptTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (AggregateException) { }
|
||||
catch (AggregateException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (_sslOptions != null && _sslOptions.CertificateValidationCallback != null)
|
||||
{
|
||||
return _sslOptions.CertificateValidationCallback(sender, certificate, chain, sslPolicyErrors);
|
||||
}
|
||||
return sslPolicyErrors == SslPolicyErrors.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace EonaCat.Network;
|
||||
|
||||
public class SslOptions
|
||||
{
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
public bool CheckCertificateRevocation { get; set; } = true;
|
||||
public X509CertificateCollection ClientCertificates { get; set; }
|
||||
public RemoteCertificateValidationCallback CertificateValidationCallback { get; set; }
|
||||
public bool ClientCertificateRequired { get; set; }
|
||||
}
|
|
@ -4,112 +4,110 @@ using System.Net.Sockets;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.Network
|
||||
namespace EonaCat.Network;
|
||||
|
||||
public class SocketUdpClient
|
||||
{
|
||||
public class SocketUdpClient
|
||||
private UdpClient _udpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Create UDP client
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
public SocketUdpClient(IPAddress ipAddress, int port, CancellationToken cancellationToken = default)
|
||||
{
|
||||
/// <summary>
|
||||
/// OnConnect event
|
||||
/// </summary>
|
||||
public event Action<EndPoint> OnConnect;
|
||||
CreateUdpClient(ipAddress, port, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<byte[]> OnReceive;
|
||||
public bool IsMulticastGroupEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<EndPoint> OnDisconnect;
|
||||
public bool IsIp6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnSend event
|
||||
/// </summary>
|
||||
public event Action<EndPoint, byte[]> OnSend;
|
||||
/// <summary>
|
||||
/// OnConnect event
|
||||
/// </summary>
|
||||
public event Action<EndPoint> OnConnect;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<byte[]> OnReceive;
|
||||
|
||||
private UdpClient _udpClient;
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<EndPoint> OnDisconnect;
|
||||
|
||||
/// <summary>
|
||||
/// Create UDP client
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
public SocketUdpClient(IPAddress ipAddress, int port, CancellationToken cancellationToken = default)
|
||||
/// <summary>
|
||||
/// OnSend event
|
||||
/// </summary>
|
||||
public event Action<EndPoint, byte[]> OnSend;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
private void CreateUdpClient(IPAddress ipAddress, int port, CancellationToken cancellationToken = default)
|
||||
{
|
||||
IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
_udpClient = new UdpClient(ipAddress.AddressFamily);
|
||||
|
||||
if (IsIp6)
|
||||
{
|
||||
CreateUdpClient(ipAddress, port, cancellationToken);
|
||||
_udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
private void CreateUdpClient(IPAddress ipAddress, int port, CancellationToken cancellationToken = default)
|
||||
_udpClient.Client.Bind(new IPEndPoint(IsIp6 ? IPAddress.IPv6Any : IPAddress.Any, 0));
|
||||
|
||||
if (IsMulticastGroupEnabled)
|
||||
{
|
||||
IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
_udpClient = new UdpClient(ipAddress.AddressFamily);
|
||||
|
||||
if (IsIp6)
|
||||
{
|
||||
_udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
_udpClient.Client.Bind(new IPEndPoint(IsIp6 ? IPAddress.IPv6Any : IPAddress.Any, 0));
|
||||
|
||||
if (IsMulticastGroupEnabled)
|
||||
{
|
||||
_udpClient.JoinMulticastGroup(ipAddress);
|
||||
}
|
||||
|
||||
OnConnect?.Invoke(_udpClient.Client.RemoteEndPoint);
|
||||
_ = StartReceivingAsync(cancellationToken);
|
||||
_udpClient.JoinMulticastGroup(ipAddress);
|
||||
}
|
||||
|
||||
public bool IsMulticastGroupEnabled { get; set; }
|
||||
OnConnect?.Invoke(_udpClient.Client.RemoteEndPoint);
|
||||
_ = StartReceivingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public bool IsIp6 { get; private set; }
|
||||
|
||||
private async Task StartReceivingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
private async Task StartReceivingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpClient.ReceiveAsync().ConfigureAwait(false);
|
||||
OnReceive?.Invoke(result.Buffer);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
var result = await _udpClient.ReceiveAsync().ConfigureAwait(false);
|
||||
OnReceive?.Invoke(result.Buffer);
|
||||
}
|
||||
OnDisconnect?.Invoke(_udpClient.Client.RemoteEndPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to endPoint
|
||||
/// </summary>
|
||||
/// <param name="endPoint"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendTo(EndPoint endPoint, byte[] data)
|
||||
{
|
||||
if (endPoint is IPEndPoint ipEndPoint)
|
||||
catch (SocketException ex)
|
||||
{
|
||||
await _udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false);
|
||||
OnSend?.Invoke(endPoint, data);
|
||||
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect UDP client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
OnDisconnect?.Invoke(_udpClient.Client.RemoteEndPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to endPoint
|
||||
/// </summary>
|
||||
/// <param name="endPoint"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendTo(EndPoint endPoint, byte[] data)
|
||||
{
|
||||
if (endPoint is IPEndPoint ipEndPoint)
|
||||
{
|
||||
_udpClient.Close();
|
||||
await _udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false);
|
||||
OnSend?.Invoke(endPoint, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect UDP client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
_udpClient.Close();
|
||||
}
|
||||
}
|
|
@ -6,146 +6,144 @@ using System.Runtime.InteropServices;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.Network
|
||||
namespace EonaCat.Network;
|
||||
|
||||
public class SocketUdpServer
|
||||
{
|
||||
public class SocketUdpServer
|
||||
private UdpClient udpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Create UDP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
public SocketUdpServer(IPAddress ipAddress, int port)
|
||||
{
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnReceive;
|
||||
CreateUdpServer(ipAddress, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnSend event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnSend;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnDisconnect;
|
||||
|
||||
private UdpClient udpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the UDP server is IpV6
|
||||
/// </summary>
|
||||
public bool IsIp6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create UDP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
public SocketUdpServer(IPAddress ipAddress, int port)
|
||||
/// <summary>
|
||||
/// Create UDP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public SocketUdpServer(string ipAddress, int port)
|
||||
{
|
||||
if (!IPAddress.TryParse(ipAddress, out var ip))
|
||||
{
|
||||
CreateUdpServer(ipAddress, port);
|
||||
throw new Exception("EonaCat Network: Invalid ipAddress given");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create UDP server
|
||||
/// </summary>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public SocketUdpServer(string ipAddress, int port)
|
||||
{
|
||||
if (!IPAddress.TryParse(ipAddress, out IPAddress ip))
|
||||
{
|
||||
throw new Exception("EonaCat Network: Invalid ipAddress given");
|
||||
}
|
||||
CreateUdpServer(ip, port);
|
||||
}
|
||||
|
||||
CreateUdpServer(ip, port);
|
||||
/// <summary>
|
||||
/// Determines if the UDP server is IpV6
|
||||
/// </summary>
|
||||
public bool IsIp6 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// OnReceive event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnReceive;
|
||||
|
||||
/// <summary>
|
||||
/// OnSend event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnSend;
|
||||
|
||||
/// <summary>
|
||||
/// OnError event
|
||||
/// </summary>
|
||||
public event Action<Exception, string> OnError;
|
||||
|
||||
/// <summary>
|
||||
/// OnDisconnect event
|
||||
/// </summary>
|
||||
public event Action<RemoteInfo> OnDisconnect;
|
||||
|
||||
private static void SetConnectionReset(Socket socket)
|
||||
{
|
||||
if (socket.ProtocolType != ProtocolType.Udp && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private static void SetConnectionReset(Socket socket)
|
||||
// Disable ICMP packet shutdown (forcibly closed)
|
||||
const int SioUdpConnReset = -1744830452;
|
||||
|
||||
byte[] inValue = { 0, 0, 0, 0 };
|
||||
socket.IOControl(SioUdpConnReset, inValue, null);
|
||||
}
|
||||
|
||||
private void CreateUdpServer(IPAddress ipAddress, int port)
|
||||
{
|
||||
IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
udpClient = new UdpClient(ipAddress.AddressFamily);
|
||||
SetConnectionReset(udpClient.Client);
|
||||
|
||||
if (IsIp6)
|
||||
{
|
||||
if (socket.ProtocolType != ProtocolType.Udp && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable ICMP packet shutdown (forcibly closed)
|
||||
const int SioUdpConnReset = -1744830452;
|
||||
|
||||
byte[] inValue = { 0, 0, 0, 0 };
|
||||
socket.IOControl(SioUdpConnReset, inValue, null);
|
||||
udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
private void CreateUdpServer(IPAddress ipAddress, int port)
|
||||
{
|
||||
IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
udpClient = new UdpClient(ipAddress.AddressFamily);
|
||||
SetConnectionReset(udpClient.Client);
|
||||
udpClient.Client.Bind(new IPEndPoint(ipAddress, port));
|
||||
}
|
||||
|
||||
if (IsIp6)
|
||||
/// <summary>
|
||||
/// Start UDP server
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
|
||||
}
|
||||
|
||||
udpClient.Client.Bind(new IPEndPoint(ipAddress, port));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start UDP server
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
var result = await udpClient.ReceiveAsync().ConfigureAwait(false);
|
||||
OnReceive?.Invoke(new RemoteInfo
|
||||
{
|
||||
var result = await udpClient.ReceiveAsync().ConfigureAwait(false);
|
||||
OnReceive?.Invoke(new RemoteInfo()
|
||||
{
|
||||
EndPoint = result.RemoteEndPoint,
|
||||
IsIpv6 = result.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
Data = result.Buffer
|
||||
});
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
|
||||
}
|
||||
EndPoint = result.RemoteEndPoint,
|
||||
IsIPv6 = result.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6,
|
||||
Data = result.Buffer
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to endPoint
|
||||
/// </summary>
|
||||
/// <param name="endPoint"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendToAsync(EndPoint endPoint, byte[] data)
|
||||
{
|
||||
if (endPoint is IPEndPoint ipEndPoint)
|
||||
catch (SocketException ex)
|
||||
{
|
||||
await udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false);
|
||||
OnSend?.Invoke(new RemoteInfo() { EndPoint = endPoint, Data = data, IsIpv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6 });
|
||||
OnError?.Invoke(ex, $"SocketException: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to all clients
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendToAllAsync(byte[] data)
|
||||
/// <summary>
|
||||
/// Send data to endPoint
|
||||
/// </summary>
|
||||
/// <param name="endPoint"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendToAsync(EndPoint endPoint, byte[] data)
|
||||
{
|
||||
if (endPoint is IPEndPoint ipEndPoint)
|
||||
{
|
||||
// get all connected clients
|
||||
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
var endPoints = ipProperties.GetActiveUdpListeners();
|
||||
foreach (var endPoint in endPoints)
|
||||
{
|
||||
await udpClient.SendAsync(data, data.Length, endPoint).ConfigureAwait(false);
|
||||
}
|
||||
await udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false);
|
||||
OnSend?.Invoke(new RemoteInfo
|
||||
{ EndPoint = endPoint, Data = data, IsIPv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6 });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data to all clients
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public async Task SendToAllAsync(byte[] data)
|
||||
{
|
||||
// get all connected clients
|
||||
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
var endPoints = ipProperties.GetActiveUdpListeners();
|
||||
foreach (var endPoint in endPoints)
|
||||
{
|
||||
await udpClient.SendAsync(data, data.Length, endPoint).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
namespace EonaCat.WebSockets.Buffer;
|
||||
// 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 class BufferValidator
|
||||
{
|
||||
// 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 class BufferValidator
|
||||
public static void ValidateBuffer(byte[] buffer, int offset, int count,
|
||||
string bufferParameterName = null,
|
||||
string offsetParameterName = null,
|
||||
string countParameterName = null)
|
||||
{
|
||||
public static void ValidateBuffer(byte[] buffer, int offset, int count,
|
||||
string bufferParameterName = null,
|
||||
string offsetParameterName = null,
|
||||
string countParameterName = null)
|
||||
if (buffer == null)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(!string.IsNullOrEmpty(bufferParameterName) ? bufferParameterName : "buffer");
|
||||
}
|
||||
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(!string.IsNullOrEmpty(offsetParameterName) ? offsetParameterName : "offset");
|
||||
}
|
||||
|
||||
if (count < 0 || count > (buffer.Length - offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(!string.IsNullOrEmpty(countParameterName) ? countParameterName : "count");
|
||||
}
|
||||
throw new ArgumentNullException(!string.IsNullOrEmpty(bufferParameterName)
|
||||
? bufferParameterName
|
||||
: "buffer");
|
||||
}
|
||||
|
||||
public static void ValidateArraySegment<T>(ArraySegment<T> arraySegment, string arraySegmentParameterName = null)
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
if (arraySegment.Array == null)
|
||||
{
|
||||
throw new ArgumentNullException((!string.IsNullOrEmpty(arraySegmentParameterName) ? arraySegmentParameterName : "arraySegment") + ".Array");
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(!string.IsNullOrEmpty(offsetParameterName)
|
||||
? offsetParameterName
|
||||
: "offset");
|
||||
}
|
||||
|
||||
if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException((!string.IsNullOrEmpty(arraySegmentParameterName) ? arraySegmentParameterName : "arraySegment") + ".Offset");
|
||||
}
|
||||
|
||||
if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException((!string.IsNullOrEmpty(arraySegmentParameterName) ? arraySegmentParameterName : "arraySegment") + ".Count");
|
||||
}
|
||||
if (count < 0 || count > buffer.Length - offset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(!string.IsNullOrEmpty(countParameterName)
|
||||
? countParameterName
|
||||
: "count");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateArraySegment<T>(ArraySegment<T> arraySegment, string arraySegmentParameterName = null)
|
||||
{
|
||||
if (arraySegment.Array == null)
|
||||
{
|
||||
throw new ArgumentNullException((!string.IsNullOrEmpty(arraySegmentParameterName)
|
||||
? arraySegmentParameterName
|
||||
: "arraySegment") + ".Array");
|
||||
}
|
||||
|
||||
if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException((!string.IsNullOrEmpty(arraySegmentParameterName)
|
||||
? arraySegmentParameterName
|
||||
: "arraySegment") + ".Offset");
|
||||
}
|
||||
|
||||
if (arraySegment.Count < 0 || arraySegment.Count > arraySegment.Array.Length - arraySegment.Offset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException((!string.IsNullOrEmpty(arraySegmentParameterName)
|
||||
? arraySegmentParameterName
|
||||
: "arraySegment") + ".Count");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
{
|
||||
// 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.WebSockets.Buffer;
|
||||
// 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 interface ISegmentBufferManager
|
||||
{
|
||||
ArraySegment<byte> BorrowBuffer();
|
||||
IEnumerable<ArraySegment<byte>> BorrowBuffers(int count);
|
||||
void ReturnBuffer(ArraySegment<byte> buffer);
|
||||
void ReturnBuffers(IEnumerable<ArraySegment<byte>> buffers);
|
||||
void ReturnBuffers(params ArraySegment<byte>[] buffers);
|
||||
}
|
||||
}
|
||||
public interface ISegmentBufferManager
|
||||
{
|
||||
ArraySegment<byte> BorrowBuffer();
|
||||
IEnumerable<ArraySegment<byte>> BorrowBuffers(int count);
|
||||
void ReturnBuffer(ArraySegment<byte> buffer);
|
||||
void ReturnBuffers(IEnumerable<ArraySegment<byte>> buffers);
|
||||
void ReturnBuffers(params ArraySegment<byte>[] buffers);
|
||||
}
|
|
@ -1,93 +1,97 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
namespace EonaCat.WebSockets.Buffer;
|
||||
// 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 class SegmentBufferDeflector
|
||||
{
|
||||
// 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 class SegmentBufferDeflector
|
||||
public static void AppendBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
ref ArraySegment<byte> receiveBuffer,
|
||||
int receiveCount,
|
||||
ref ArraySegment<byte> sessionBuffer,
|
||||
ref int sessionBufferCount)
|
||||
{
|
||||
public static void AppendBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
ref ArraySegment<byte> receiveBuffer,
|
||||
int receiveCount,
|
||||
ref ArraySegment<byte> sessionBuffer,
|
||||
ref int sessionBufferCount)
|
||||
if (sessionBuffer.Count < sessionBufferCount + receiveCount)
|
||||
{
|
||||
if (sessionBuffer.Count < (sessionBufferCount + receiveCount))
|
||||
var autoExpandedBuffer = bufferManager.BorrowBuffer();
|
||||
if (autoExpandedBuffer.Count < (sessionBufferCount + receiveCount) * 2)
|
||||
{
|
||||
ArraySegment<byte> autoExpandedBuffer = bufferManager.BorrowBuffer();
|
||||
if (autoExpandedBuffer.Count < (sessionBufferCount + receiveCount) * 2)
|
||||
{
|
||||
bufferManager.ReturnBuffer(autoExpandedBuffer);
|
||||
autoExpandedBuffer = new ArraySegment<byte>(new byte[(sessionBufferCount + receiveCount) * 2]);
|
||||
}
|
||||
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset, autoExpandedBuffer.Array, autoExpandedBuffer.Offset, sessionBufferCount);
|
||||
|
||||
var discardBuffer = sessionBuffer;
|
||||
sessionBuffer = autoExpandedBuffer;
|
||||
bufferManager.ReturnBuffer(discardBuffer);
|
||||
bufferManager.ReturnBuffer(autoExpandedBuffer);
|
||||
autoExpandedBuffer = new ArraySegment<byte>(new byte[(sessionBufferCount + receiveCount) * 2]);
|
||||
}
|
||||
|
||||
Array.Copy(receiveBuffer.Array, receiveBuffer.Offset, sessionBuffer.Array, sessionBuffer.Offset + sessionBufferCount, receiveCount);
|
||||
sessionBufferCount = sessionBufferCount + receiveCount;
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset, autoExpandedBuffer.Array, autoExpandedBuffer.Offset,
|
||||
sessionBufferCount);
|
||||
|
||||
var discardBuffer = sessionBuffer;
|
||||
sessionBuffer = autoExpandedBuffer;
|
||||
bufferManager.ReturnBuffer(discardBuffer);
|
||||
}
|
||||
|
||||
public static void ShiftBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
int shiftStart,
|
||||
ref ArraySegment<byte> sessionBuffer,
|
||||
ref int sessionBufferCount)
|
||||
Array.Copy(receiveBuffer.Array, receiveBuffer.Offset, sessionBuffer.Array,
|
||||
sessionBuffer.Offset + sessionBufferCount, receiveCount);
|
||||
sessionBufferCount = sessionBufferCount + receiveCount;
|
||||
}
|
||||
|
||||
public static void ShiftBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
int shiftStart,
|
||||
ref ArraySegment<byte> sessionBuffer,
|
||||
ref int sessionBufferCount)
|
||||
{
|
||||
if (sessionBufferCount - shiftStart < shiftStart)
|
||||
{
|
||||
if ((sessionBufferCount - shiftStart) < shiftStart)
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset + shiftStart, sessionBuffer.Array,
|
||||
sessionBuffer.Offset, sessionBufferCount - shiftStart);
|
||||
sessionBufferCount = sessionBufferCount - shiftStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
var copyBuffer = bufferManager.BorrowBuffer();
|
||||
if (copyBuffer.Count < sessionBufferCount - shiftStart)
|
||||
{
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset + shiftStart, sessionBuffer.Array, sessionBuffer.Offset, sessionBufferCount - shiftStart);
|
||||
sessionBufferCount = sessionBufferCount - shiftStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
ArraySegment<byte> copyBuffer = bufferManager.BorrowBuffer();
|
||||
if (copyBuffer.Count < (sessionBufferCount - shiftStart))
|
||||
{
|
||||
bufferManager.ReturnBuffer(copyBuffer);
|
||||
copyBuffer = new ArraySegment<byte>(new byte[sessionBufferCount - shiftStart]);
|
||||
}
|
||||
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset + shiftStart, copyBuffer.Array, copyBuffer.Offset, sessionBufferCount - shiftStart);
|
||||
Array.Copy(copyBuffer.Array, copyBuffer.Offset, sessionBuffer.Array, sessionBuffer.Offset, sessionBufferCount - shiftStart);
|
||||
sessionBufferCount = sessionBufferCount - shiftStart;
|
||||
|
||||
bufferManager.ReturnBuffer(copyBuffer);
|
||||
copyBuffer = new ArraySegment<byte>(new byte[sessionBufferCount - shiftStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
ref ArraySegment<byte> receiveBuffer,
|
||||
ref int receiveBufferOffset,
|
||||
int receiveCount)
|
||||
{
|
||||
if ((receiveBufferOffset + receiveCount) < receiveBuffer.Count)
|
||||
{
|
||||
receiveBufferOffset = receiveBufferOffset + receiveCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
ArraySegment<byte> autoExpandedBuffer = bufferManager.BorrowBuffer();
|
||||
if (autoExpandedBuffer.Count < (receiveBufferOffset + receiveCount) * 2)
|
||||
{
|
||||
bufferManager.ReturnBuffer(autoExpandedBuffer);
|
||||
autoExpandedBuffer = new ArraySegment<byte>(new byte[(receiveBufferOffset + receiveCount) * 2]);
|
||||
}
|
||||
Array.Copy(sessionBuffer.Array, sessionBuffer.Offset + shiftStart, copyBuffer.Array, copyBuffer.Offset,
|
||||
sessionBufferCount - shiftStart);
|
||||
Array.Copy(copyBuffer.Array, copyBuffer.Offset, sessionBuffer.Array, sessionBuffer.Offset,
|
||||
sessionBufferCount - shiftStart);
|
||||
sessionBufferCount = sessionBufferCount - shiftStart;
|
||||
|
||||
Array.Copy(receiveBuffer.Array, receiveBuffer.Offset, autoExpandedBuffer.Array, autoExpandedBuffer.Offset, receiveBufferOffset + receiveCount);
|
||||
receiveBufferOffset = receiveBufferOffset + receiveCount;
|
||||
|
||||
var discardBuffer = receiveBuffer;
|
||||
receiveBuffer = autoExpandedBuffer;
|
||||
bufferManager.ReturnBuffer(discardBuffer);
|
||||
}
|
||||
bufferManager.ReturnBuffer(copyBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceBuffer(
|
||||
ISegmentBufferManager bufferManager,
|
||||
ref ArraySegment<byte> receiveBuffer,
|
||||
ref int receiveBufferOffset,
|
||||
int receiveCount)
|
||||
{
|
||||
if (receiveBufferOffset + receiveCount < receiveBuffer.Count)
|
||||
{
|
||||
receiveBufferOffset = receiveBufferOffset + receiveCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
var autoExpandedBuffer = bufferManager.BorrowBuffer();
|
||||
if (autoExpandedBuffer.Count < (receiveBufferOffset + receiveCount) * 2)
|
||||
{
|
||||
bufferManager.ReturnBuffer(autoExpandedBuffer);
|
||||
autoExpandedBuffer = new ArraySegment<byte>(new byte[(receiveBufferOffset + receiveCount) * 2]);
|
||||
}
|
||||
|
||||
Array.Copy(receiveBuffer.Array, receiveBuffer.Offset, autoExpandedBuffer.Array, autoExpandedBuffer.Offset,
|
||||
receiveBufferOffset + receiveCount);
|
||||
receiveBufferOffset = receiveBufferOffset + receiveCount;
|
||||
|
||||
var discardBuffer = receiveBuffer;
|
||||
receiveBuffer = autoExpandedBuffer;
|
||||
bufferManager.ReturnBuffer(discardBuffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,166 +3,148 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
namespace EonaCat.WebSockets.Buffer;
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// A manager to handle buffers for the socket connections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When used in an async call a buffer is pinned. Large numbers of pinned buffers
|
||||
/// cause problem with the GC (in particular it causes heap fragmentation).
|
||||
/// This class maintains a set of large segments and gives clients pieces of these
|
||||
/// segments that they can use for their buffers. The alternative to this would be to
|
||||
/// create many small arrays which it then maintained. This methodology should be slightly
|
||||
/// better than the many small array methodology because in creating only a few very
|
||||
/// large objects it will force these objects to be placed on the LOH. Since the
|
||||
/// objects are on the LOH they are at this time not subject to compacting which would
|
||||
/// require an update of all GC roots as would be the case with lots of smaller arrays
|
||||
/// that were in the normal heap.
|
||||
/// </remarks>
|
||||
public class SegmentBufferManager : ISegmentBufferManager
|
||||
{
|
||||
// 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.
|
||||
private const int TrialsCount = 100;
|
||||
|
||||
private static SegmentBufferManager _defaultBufferManager;
|
||||
private readonly bool _allowedToCreateMemory;
|
||||
|
||||
private readonly ConcurrentStack<ArraySegment<byte>> _buffers = new();
|
||||
private readonly object _creatingNewSegmentLock = new();
|
||||
|
||||
private readonly List<byte[]> _segments;
|
||||
private readonly int _segmentSize;
|
||||
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize)
|
||||
: this(segmentChunks, chunkSize, 1)
|
||||
{
|
||||
}
|
||||
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments)
|
||||
: this(segmentChunks, chunkSize, initialSegments, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A manager to handle buffers for the socket connections.
|
||||
/// Constructs a new <see cref="SegmentBufferManager"></see> object
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When used in an async call a buffer is pinned. Large numbers of pinned buffers
|
||||
/// cause problem with the GC (in particular it causes heap fragmentation).
|
||||
/// This class maintains a set of large segments and gives clients pieces of these
|
||||
/// segments that they can use for their buffers. The alternative to this would be to
|
||||
/// create many small arrays which it then maintained. This methodology should be slightly
|
||||
/// better than the many small array methodology because in creating only a few very
|
||||
/// large objects it will force these objects to be placed on the LOH. Since the
|
||||
/// objects are on the LOH they are at this time not subject to compacting which would
|
||||
/// require an update of all GC roots as would be the case with lots of smaller arrays
|
||||
/// that were in the normal heap.
|
||||
/// </remarks>
|
||||
public class SegmentBufferManager : ISegmentBufferManager
|
||||
/// <param name="segmentChunks">The number of chunks to create per segment</param>
|
||||
/// <param name="chunkSize">The size of a chunk in bytes</param>
|
||||
/// <param name="initialSegments">The initial number of segments to create</param>
|
||||
/// <param name="allowedToCreateMemory">If false when empty and checkout is called an exception will be thrown</param>
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments, bool allowedToCreateMemory)
|
||||
{
|
||||
private const int TrialsCount = 100;
|
||||
|
||||
private static SegmentBufferManager _defaultBufferManager;
|
||||
|
||||
private readonly int _segmentChunks;
|
||||
private readonly int _chunkSize;
|
||||
private readonly int _segmentSize;
|
||||
private readonly bool _allowedToCreateMemory;
|
||||
|
||||
private readonly ConcurrentStack<ArraySegment<byte>> _buffers = new ConcurrentStack<ArraySegment<byte>>();
|
||||
|
||||
private readonly List<byte[]> _segments;
|
||||
private readonly object _creatingNewSegmentLock = new object();
|
||||
|
||||
public static SegmentBufferManager Default
|
||||
if (segmentChunks <= 0)
|
||||
{
|
||||
get
|
||||
{
|
||||
// default to 1024 1kb buffers if people don't want to manage it on their own;
|
||||
if (_defaultBufferManager == null)
|
||||
{
|
||||
_defaultBufferManager = new SegmentBufferManager(1024, 1024, 1);
|
||||
}
|
||||
|
||||
return _defaultBufferManager;
|
||||
}
|
||||
throw new ArgumentException("segmentChunks");
|
||||
}
|
||||
|
||||
public static void SetDefaultBufferManager(SegmentBufferManager manager)
|
||||
if (chunkSize <= 0)
|
||||
{
|
||||
if (manager == null)
|
||||
throw new ArgumentException("chunkSize");
|
||||
}
|
||||
|
||||
if (initialSegments < 0)
|
||||
{
|
||||
throw new ArgumentException("initialSegments");
|
||||
}
|
||||
|
||||
SegmentChunksCount = segmentChunks;
|
||||
ChunkSize = chunkSize;
|
||||
_segmentSize = SegmentChunksCount * ChunkSize;
|
||||
|
||||
_segments = new List<byte[]>();
|
||||
|
||||
_allowedToCreateMemory = true;
|
||||
for (var i = 0; i < initialSegments; i++) CreateNewSegment(true);
|
||||
_allowedToCreateMemory = allowedToCreateMemory;
|
||||
}
|
||||
|
||||
public static SegmentBufferManager Default
|
||||
{
|
||||
get
|
||||
{
|
||||
// default to 1024 1kb buffers if people don't want to manage it on their own;
|
||||
if (_defaultBufferManager == null)
|
||||
{
|
||||
throw new ArgumentNullException("manager");
|
||||
_defaultBufferManager = new SegmentBufferManager(1024, 1024, 1);
|
||||
}
|
||||
|
||||
_defaultBufferManager = manager;
|
||||
return _defaultBufferManager;
|
||||
}
|
||||
}
|
||||
|
||||
public int ChunkSize
|
||||
public int ChunkSize { get; }
|
||||
|
||||
public int SegmentsCount => _segments.Count;
|
||||
|
||||
public int SegmentChunksCount { get; }
|
||||
|
||||
public int AvailableBuffers => _buffers.Count;
|
||||
|
||||
public int TotalBufferSize => _segments.Count * _segmentSize;
|
||||
|
||||
public ArraySegment<byte> BorrowBuffer()
|
||||
{
|
||||
var trial = 0;
|
||||
while (trial < TrialsCount)
|
||||
{
|
||||
get { return _chunkSize; }
|
||||
}
|
||||
|
||||
public int SegmentsCount
|
||||
{
|
||||
get { return _segments.Count; }
|
||||
}
|
||||
|
||||
public int SegmentChunksCount
|
||||
{
|
||||
get { return _segmentChunks; }
|
||||
}
|
||||
|
||||
public int AvailableBuffers
|
||||
{
|
||||
get { return _buffers.Count; }
|
||||
}
|
||||
|
||||
public int TotalBufferSize
|
||||
{
|
||||
get { return _segments.Count * _segmentSize; }
|
||||
}
|
||||
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize)
|
||||
: this(segmentChunks, chunkSize, 1) { }
|
||||
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments)
|
||||
: this(segmentChunks, chunkSize, initialSegments, true) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new <see cref="SegmentBufferManager"></see> object
|
||||
/// </summary>
|
||||
/// <param name="segmentChunks">The number of chunks to create per segment</param>
|
||||
/// <param name="chunkSize">The size of a chunk in bytes</param>
|
||||
/// <param name="initialSegments">The initial number of segments to create</param>
|
||||
/// <param name="allowedToCreateMemory">If false when empty and checkout is called an exception will be thrown</param>
|
||||
public SegmentBufferManager(int segmentChunks, int chunkSize, int initialSegments, bool allowedToCreateMemory)
|
||||
{
|
||||
if (segmentChunks <= 0)
|
||||
ArraySegment<byte> result;
|
||||
if (_buffers.TryPop(out result))
|
||||
{
|
||||
throw new ArgumentException("segmentChunks");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (chunkSize <= 0)
|
||||
{
|
||||
throw new ArgumentException("chunkSize");
|
||||
}
|
||||
|
||||
if (initialSegments < 0)
|
||||
{
|
||||
throw new ArgumentException("initialSegments");
|
||||
}
|
||||
|
||||
_segmentChunks = segmentChunks;
|
||||
_chunkSize = chunkSize;
|
||||
_segmentSize = _segmentChunks * _chunkSize;
|
||||
|
||||
_segments = new List<byte[]>();
|
||||
|
||||
_allowedToCreateMemory = true;
|
||||
for (int i = 0; i < initialSegments; i++)
|
||||
{
|
||||
CreateNewSegment(true);
|
||||
}
|
||||
_allowedToCreateMemory = allowedToCreateMemory;
|
||||
CreateNewSegment(false);
|
||||
trial++;
|
||||
}
|
||||
|
||||
private void CreateNewSegment(bool forceCreation)
|
||||
throw new UnableToAllocateBufferException();
|
||||
}
|
||||
|
||||
public IEnumerable<ArraySegment<byte>> BorrowBuffers(int count)
|
||||
{
|
||||
var result = new ArraySegment<byte>[count];
|
||||
var trial = 0;
|
||||
var totalReceived = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (!_allowedToCreateMemory)
|
||||
{
|
||||
throw new UnableToCreateMemoryException();
|
||||
}
|
||||
|
||||
lock (_creatingNewSegmentLock)
|
||||
{
|
||||
if (!forceCreation && _buffers.Count > _segmentChunks / 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = new byte[_segmentSize];
|
||||
_segments.Add(bytes);
|
||||
for (int i = 0; i < _segmentChunks; i++)
|
||||
{
|
||||
var chunk = new ArraySegment<byte>(bytes, i * _chunkSize, _chunkSize);
|
||||
_buffers.Push(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArraySegment<byte> BorrowBuffer()
|
||||
{
|
||||
int trial = 0;
|
||||
while (trial < TrialsCount)
|
||||
{
|
||||
ArraySegment<byte> result;
|
||||
if (_buffers.TryPop(out result))
|
||||
ArraySegment<byte> piece;
|
||||
while (totalReceived < count)
|
||||
{
|
||||
if (!_buffers.TryPop(out piece))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result[totalReceived] = piece;
|
||||
++totalReceived;
|
||||
}
|
||||
|
||||
if (totalReceived == count)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -170,104 +152,106 @@ namespace EonaCat.WebSockets.Buffer
|
|||
CreateNewSegment(false);
|
||||
trial++;
|
||||
}
|
||||
|
||||
throw new UnableToAllocateBufferException();
|
||||
}
|
||||
|
||||
public IEnumerable<ArraySegment<byte>> BorrowBuffers(int count)
|
||||
catch
|
||||
{
|
||||
var result = new ArraySegment<byte>[count];
|
||||
var trial = 0;
|
||||
var totalReceived = 0;
|
||||
|
||||
try
|
||||
if (totalReceived > 0)
|
||||
{
|
||||
while (trial < TrialsCount)
|
||||
{
|
||||
ArraySegment<byte> piece;
|
||||
while (totalReceived < count)
|
||||
{
|
||||
if (!_buffers.TryPop(out piece))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result[totalReceived] = piece;
|
||||
++totalReceived;
|
||||
}
|
||||
if (totalReceived == count)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
CreateNewSegment(false);
|
||||
trial++;
|
||||
}
|
||||
throw new UnableToAllocateBufferException();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (totalReceived > 0)
|
||||
{
|
||||
ReturnBuffers(result.Take(totalReceived));
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffer(ArraySegment<byte> buffer)
|
||||
{
|
||||
if (ValidateBuffer(buffer))
|
||||
{
|
||||
_buffers.Push(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffers(IEnumerable<ArraySegment<byte>> buffers)
|
||||
{
|
||||
if (buffers == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffers");
|
||||
ReturnBuffers(result.Take(totalReceived));
|
||||
}
|
||||
|
||||
foreach (var buf in buffers)
|
||||
{
|
||||
if (ValidateBuffer(buf))
|
||||
{
|
||||
_buffers.Push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffers(params ArraySegment<byte>[] buffers)
|
||||
{
|
||||
if (buffers == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffers");
|
||||
}
|
||||
|
||||
foreach (var buf in buffers)
|
||||
{
|
||||
if (ValidateBuffer(buf))
|
||||
{
|
||||
_buffers.Push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateBuffer(ArraySegment<byte> buffer)
|
||||
{
|
||||
if (buffer.Array == null || buffer.Count == 0 || buffer.Array.Length < buffer.Offset + buffer.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.Count != _chunkSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffer(ArraySegment<byte> buffer)
|
||||
{
|
||||
if (ValidateBuffer(buffer))
|
||||
{
|
||||
_buffers.Push(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffers(IEnumerable<ArraySegment<byte>> buffers)
|
||||
{
|
||||
if (buffers == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffers");
|
||||
}
|
||||
|
||||
foreach (var buf in buffers)
|
||||
{
|
||||
if (ValidateBuffer(buf))
|
||||
{
|
||||
_buffers.Push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnBuffers(params ArraySegment<byte>[] buffers)
|
||||
{
|
||||
if (buffers == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffers");
|
||||
}
|
||||
|
||||
foreach (var buf in buffers)
|
||||
{
|
||||
if (ValidateBuffer(buf))
|
||||
{
|
||||
_buffers.Push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDefaultBufferManager(SegmentBufferManager manager)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
throw new ArgumentNullException("manager");
|
||||
}
|
||||
|
||||
_defaultBufferManager = manager;
|
||||
}
|
||||
|
||||
private void CreateNewSegment(bool forceCreation)
|
||||
{
|
||||
if (!_allowedToCreateMemory)
|
||||
{
|
||||
throw new UnableToCreateMemoryException();
|
||||
}
|
||||
|
||||
lock (_creatingNewSegmentLock)
|
||||
{
|
||||
if (!forceCreation && _buffers.Count > SegmentChunksCount / 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = new byte[_segmentSize];
|
||||
_segments.Add(bytes);
|
||||
for (var i = 0; i < SegmentChunksCount; i++)
|
||||
{
|
||||
var chunk = new ArraySegment<byte>(bytes, i * ChunkSize, ChunkSize);
|
||||
_buffers.Push(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateBuffer(ArraySegment<byte> buffer)
|
||||
{
|
||||
if (buffer.Array == null || buffer.Count == 0 || buffer.Array.Length < buffer.Offset + buffer.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.Count != ChunkSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
{
|
||||
// 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.WebSockets.Buffer;
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public class UnableToAllocateBufferException : Exception
|
||||
[Serializable]
|
||||
public class UnableToAllocateBufferException : Exception
|
||||
{
|
||||
public UnableToAllocateBufferException()
|
||||
: base("Cannot allocate buffer after few trials.")
|
||||
{
|
||||
public UnableToAllocateBufferException()
|
||||
: base("Cannot allocate buffer after few trials.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Buffer
|
||||
{
|
||||
// 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.WebSockets.Buffer;
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public class UnableToCreateMemoryException : Exception
|
||||
[Serializable]
|
||||
public class UnableToCreateMemoryException : Exception
|
||||
{
|
||||
public UnableToCreateMemoryException()
|
||||
: base("All buffers were in use and acquiring more memory has been disabled.")
|
||||
{
|
||||
public UnableToCreateMemoryException()
|
||||
: base("All buffers were in use and acquiring more memory has been disabled.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -8,74 +8,72 @@ using EonaCat.WebSockets.Buffer;
|
|||
using EonaCat.WebSockets.Extensions;
|
||||
using EonaCat.WebSockets.SubProtocols;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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 sealed class AsyncWebSocketClientConfiguration
|
||||
{
|
||||
// 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 sealed class AsyncWebSocketClientConfiguration
|
||||
public AsyncWebSocketClientConfiguration()
|
||||
{
|
||||
public AsyncWebSocketClientConfiguration()
|
||||
BufferManager = new SegmentBufferManager(100, 8192, 1, true);
|
||||
ReceiveBufferSize = 8192;
|
||||
SendBufferSize = 8192;
|
||||
ReceiveTimeout = TimeSpan.Zero;
|
||||
SendTimeout = TimeSpan.Zero;
|
||||
NoDelay = true;
|
||||
LingerState = new LingerOption(false, 0); // The socket will linger for x seconds after Socket.Close is called.
|
||||
|
||||
SslTargetHost = null;
|
||||
SslClientCertificates = new X509CertificateCollection();
|
||||
SslEncryptionPolicy = EncryptionPolicy.RequireEncryption;
|
||||
SslEnabledProtocols = SslProtocols.Ssl3 | SslProtocols.Tls;
|
||||
SslCheckCertificateRevocation = false;
|
||||
SslPolicyErrorsBypassed = false;
|
||||
|
||||
ConnectTimeout = TimeSpan.FromSeconds(10);
|
||||
CloseTimeout = TimeSpan.FromSeconds(5);
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(30);
|
||||
KeepAliveTimeout = TimeSpan.FromSeconds(5);
|
||||
ReasonableFragmentSize = 4096;
|
||||
|
||||
EnabledExtensions = new Dictionary<string, IWebSocketExtensionNegotiator>
|
||||
{
|
||||
BufferManager = new SegmentBufferManager(100, 8192, 1, true);
|
||||
ReceiveBufferSize = 8192;
|
||||
SendBufferSize = 8192;
|
||||
ReceiveTimeout = TimeSpan.Zero;
|
||||
SendTimeout = TimeSpan.Zero;
|
||||
NoDelay = true;
|
||||
LingerState = new LingerOption(false, 0); // The socket will linger for x seconds after Socket.Close is called.
|
||||
{ PerMessageCompressionExtension.RegisteredToken, new PerMessageCompressionExtensionNegotiator() }
|
||||
};
|
||||
EnabledSubProtocols = new Dictionary<string, IWebSocketSubProtocolNegotiator>();
|
||||
|
||||
SslTargetHost = null;
|
||||
SslClientCertificates = new X509CertificateCollection();
|
||||
SslEncryptionPolicy = EncryptionPolicy.RequireEncryption;
|
||||
SslEnabledProtocols = SslProtocols.Ssl3 | SslProtocols.Tls;
|
||||
SslCheckCertificateRevocation = false;
|
||||
SslPolicyErrorsBypassed = false;
|
||||
|
||||
ConnectTimeout = TimeSpan.FromSeconds(10);
|
||||
CloseTimeout = TimeSpan.FromSeconds(5);
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(30);
|
||||
KeepAliveTimeout = TimeSpan.FromSeconds(5);
|
||||
ReasonableFragmentSize = 4096;
|
||||
|
||||
EnabledExtensions = new Dictionary<string, IWebSocketExtensionNegotiator>()
|
||||
{
|
||||
{ PerMessageCompressionExtension.RegisteredToken, new PerMessageCompressionExtensionNegotiator() },
|
||||
};
|
||||
EnabledSubProtocols = new Dictionary<string, IWebSocketSubProtocolNegotiator>();
|
||||
|
||||
OfferedExtensions = new List<WebSocketExtensionOfferDescription>()
|
||||
{
|
||||
new WebSocketExtensionOfferDescription(PerMessageCompressionExtension.RegisteredToken),
|
||||
};
|
||||
RequestedSubProtocols = new List<WebSocketSubProtocolRequestDescription>();
|
||||
}
|
||||
|
||||
public ISegmentBufferManager BufferManager { get; set; }
|
||||
public int ReceiveBufferSize { get; set; }
|
||||
public int SendBufferSize { get; set; }
|
||||
public TimeSpan ReceiveTimeout { get; set; }
|
||||
public TimeSpan SendTimeout { get; set; }
|
||||
public bool NoDelay { get; set; }
|
||||
public LingerOption LingerState { get; set; }
|
||||
|
||||
public string SslTargetHost { get; set; }
|
||||
public X509CertificateCollection SslClientCertificates { get; set; }
|
||||
public EncryptionPolicy SslEncryptionPolicy { get; set; }
|
||||
public SslProtocols SslEnabledProtocols { get; set; }
|
||||
public bool SslCheckCertificateRevocation { get; set; }
|
||||
public bool SslPolicyErrorsBypassed { get; set; }
|
||||
|
||||
public TimeSpan ConnectTimeout { get; set; }
|
||||
public TimeSpan CloseTimeout { get; set; }
|
||||
public TimeSpan KeepAliveInterval { get; set; }
|
||||
public TimeSpan KeepAliveTimeout { get; set; }
|
||||
public int ReasonableFragmentSize { get; set; }
|
||||
|
||||
public Dictionary<string, IWebSocketExtensionNegotiator> EnabledExtensions { get; set; }
|
||||
public Dictionary<string, IWebSocketSubProtocolNegotiator> EnabledSubProtocols { get; set; }
|
||||
|
||||
public List<WebSocketExtensionOfferDescription> OfferedExtensions { get; set; }
|
||||
public List<WebSocketSubProtocolRequestDescription> RequestedSubProtocols { get; set; }
|
||||
OfferedExtensions = new List<WebSocketExtensionOfferDescription>
|
||||
{
|
||||
new(PerMessageCompressionExtension.RegisteredToken)
|
||||
};
|
||||
RequestedSubProtocols = new List<WebSocketSubProtocolRequestDescription>();
|
||||
}
|
||||
}
|
||||
|
||||
public ISegmentBufferManager BufferManager { get; set; }
|
||||
public int ReceiveBufferSize { get; set; }
|
||||
public int SendBufferSize { get; set; }
|
||||
public TimeSpan ReceiveTimeout { get; set; }
|
||||
public TimeSpan SendTimeout { get; set; }
|
||||
public bool NoDelay { get; set; }
|
||||
public LingerOption LingerState { get; set; }
|
||||
|
||||
public string SslTargetHost { get; set; }
|
||||
public X509CertificateCollection SslClientCertificates { get; set; }
|
||||
public EncryptionPolicy SslEncryptionPolicy { get; set; }
|
||||
public SslProtocols SslEnabledProtocols { get; set; }
|
||||
public bool SslCheckCertificateRevocation { get; set; }
|
||||
public bool SslPolicyErrorsBypassed { get; set; }
|
||||
|
||||
public TimeSpan ConnectTimeout { get; set; }
|
||||
public TimeSpan CloseTimeout { get; set; }
|
||||
public TimeSpan KeepAliveInterval { get; set; }
|
||||
public TimeSpan KeepAliveTimeout { get; set; }
|
||||
public int ReasonableFragmentSize { get; set; }
|
||||
|
||||
public Dictionary<string, IWebSocketExtensionNegotiator> EnabledExtensions { get; set; }
|
||||
public Dictionary<string, IWebSocketSubProtocolNegotiator> EnabledSubProtocols { get; set; }
|
||||
|
||||
public List<WebSocketExtensionOfferDescription> OfferedExtensions { get; set; }
|
||||
public List<WebSocketSubProtocolRequestDescription> RequestedSubProtocols { get; set; }
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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 interface IAsyncWebSocketClientMessageDispatcher
|
||||
{
|
||||
// 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.
|
||||
Task OnServerConnected(AsyncWebSocketClient client);
|
||||
Task OnServerTextReceived(AsyncWebSocketClient client, string text);
|
||||
Task OnServerBinaryReceived(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerDisconnected(AsyncWebSocketClient client);
|
||||
|
||||
public interface IAsyncWebSocketClientMessageDispatcher
|
||||
{
|
||||
Task OnServerConnected(AsyncWebSocketClient client);
|
||||
Task OnServerTextReceived(AsyncWebSocketClient client, string text);
|
||||
Task OnServerBinaryReceived(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerDisconnected(AsyncWebSocketClient client);
|
||||
|
||||
Task OnServerFragmentationStreamOpened(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerFragmentationStreamContinued(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerFragmentationStreamClosed(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
}
|
||||
}
|
||||
Task OnServerFragmentationStreamOpened(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerFragmentationStreamContinued(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
Task OnServerFragmentationStreamClosed(AsyncWebSocketClient client, byte[] data, int offset, int count);
|
||||
}
|
|
@ -1,113 +1,112 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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.
|
||||
|
||||
internal class InternalAsyncWebSocketClientMessageDispatcherImplementation : IAsyncWebSocketClientMessageDispatcher
|
||||
{
|
||||
// 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.
|
||||
private readonly Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerBinaryReceived;
|
||||
private readonly Func<AsyncWebSocketClient, Task> _onServerConnected;
|
||||
private readonly Func<AsyncWebSocketClient, Task> _onServerDisconnected;
|
||||
private readonly Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamClosed;
|
||||
private readonly Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamContinued;
|
||||
|
||||
internal class InternalAsyncWebSocketClientMessageDispatcherImplementation : IAsyncWebSocketClientMessageDispatcher
|
||||
private readonly Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamOpened;
|
||||
private readonly Func<AsyncWebSocketClient, string, Task> _onServerTextReceived;
|
||||
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation()
|
||||
{
|
||||
private Func<AsyncWebSocketClient, string, Task> _onServerTextReceived;
|
||||
private Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerBinaryReceived;
|
||||
private Func<AsyncWebSocketClient, Task> _onServerConnected;
|
||||
private Func<AsyncWebSocketClient, Task> _onServerDisconnected;
|
||||
}
|
||||
|
||||
private Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamOpened;
|
||||
private Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamContinued;
|
||||
private Func<AsyncWebSocketClient, byte[], int, int, Task> _onServerFragmentationStreamClosed;
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation(
|
||||
Func<AsyncWebSocketClient, string, Task> onServerTextReceived,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerDataReceived,
|
||||
Func<AsyncWebSocketClient, Task> onServerConnected,
|
||||
Func<AsyncWebSocketClient, Task> onServerDisconnected)
|
||||
: this()
|
||||
{
|
||||
_onServerTextReceived = onServerTextReceived;
|
||||
_onServerBinaryReceived = onServerDataReceived;
|
||||
_onServerConnected = onServerConnected;
|
||||
_onServerDisconnected = onServerDisconnected;
|
||||
}
|
||||
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation()
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation(
|
||||
Func<AsyncWebSocketClient, string, Task> onServerTextReceived,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerDataReceived,
|
||||
Func<AsyncWebSocketClient, Task> onServerConnected,
|
||||
Func<AsyncWebSocketClient, Task> onServerDisconnected,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamOpened,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamContinued,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamClosed)
|
||||
: this()
|
||||
{
|
||||
_onServerTextReceived = onServerTextReceived;
|
||||
_onServerBinaryReceived = onServerDataReceived;
|
||||
_onServerConnected = onServerConnected;
|
||||
_onServerDisconnected = onServerDisconnected;
|
||||
|
||||
_onServerFragmentationStreamOpened = onServerFragmentationStreamOpened;
|
||||
_onServerFragmentationStreamContinued = onServerFragmentationStreamContinued;
|
||||
_onServerFragmentationStreamClosed = onServerFragmentationStreamClosed;
|
||||
}
|
||||
|
||||
public async Task OnServerConnected(AsyncWebSocketClient client)
|
||||
{
|
||||
if (_onServerConnected != null)
|
||||
{
|
||||
}
|
||||
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation(
|
||||
Func<AsyncWebSocketClient, string, Task> onServerTextReceived,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerDataReceived,
|
||||
Func<AsyncWebSocketClient, Task> onServerConnected,
|
||||
Func<AsyncWebSocketClient, Task> onServerDisconnected)
|
||||
: this()
|
||||
{
|
||||
_onServerTextReceived = onServerTextReceived;
|
||||
_onServerBinaryReceived = onServerDataReceived;
|
||||
_onServerConnected = onServerConnected;
|
||||
_onServerDisconnected = onServerDisconnected;
|
||||
}
|
||||
|
||||
public InternalAsyncWebSocketClientMessageDispatcherImplementation(
|
||||
Func<AsyncWebSocketClient, string, Task> onServerTextReceived,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerDataReceived,
|
||||
Func<AsyncWebSocketClient, Task> onServerConnected,
|
||||
Func<AsyncWebSocketClient, Task> onServerDisconnected,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamOpened,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamContinued,
|
||||
Func<AsyncWebSocketClient, byte[], int, int, Task> onServerFragmentationStreamClosed)
|
||||
: this()
|
||||
{
|
||||
_onServerTextReceived = onServerTextReceived;
|
||||
_onServerBinaryReceived = onServerDataReceived;
|
||||
_onServerConnected = onServerConnected;
|
||||
_onServerDisconnected = onServerDisconnected;
|
||||
|
||||
_onServerFragmentationStreamOpened = onServerFragmentationStreamOpened;
|
||||
_onServerFragmentationStreamContinued = onServerFragmentationStreamContinued;
|
||||
_onServerFragmentationStreamClosed = onServerFragmentationStreamClosed;
|
||||
}
|
||||
|
||||
public async Task OnServerConnected(AsyncWebSocketClient client)
|
||||
{
|
||||
if (_onServerConnected != null)
|
||||
{
|
||||
await _onServerConnected(client);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerTextReceived(AsyncWebSocketClient client, string text)
|
||||
{
|
||||
if (_onServerTextReceived != null)
|
||||
{
|
||||
await _onServerTextReceived(client, text);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerBinaryReceived(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerBinaryReceived != null)
|
||||
{
|
||||
await _onServerBinaryReceived(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerDisconnected(AsyncWebSocketClient client)
|
||||
{
|
||||
if (_onServerDisconnected != null)
|
||||
{
|
||||
await _onServerDisconnected(client);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamOpened(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamOpened != null)
|
||||
{
|
||||
await _onServerFragmentationStreamOpened(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamContinued(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamContinued != null)
|
||||
{
|
||||
await _onServerFragmentationStreamContinued(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamClosed(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamClosed != null)
|
||||
{
|
||||
await _onServerFragmentationStreamClosed(client, data, offset, count);
|
||||
}
|
||||
await _onServerConnected(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerTextReceived(AsyncWebSocketClient client, string text)
|
||||
{
|
||||
if (_onServerTextReceived != null)
|
||||
{
|
||||
await _onServerTextReceived(client, text);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerBinaryReceived(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerBinaryReceived != null)
|
||||
{
|
||||
await _onServerBinaryReceived(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerDisconnected(AsyncWebSocketClient client)
|
||||
{
|
||||
if (_onServerDisconnected != null)
|
||||
{
|
||||
await _onServerDisconnected(client);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamOpened(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamOpened != null)
|
||||
{
|
||||
await _onServerFragmentationStreamOpened(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamContinued(AsyncWebSocketClient client, byte[] data, int offset,
|
||||
int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamContinued != null)
|
||||
{
|
||||
await _onServerFragmentationStreamContinued(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnServerFragmentationStreamClosed(AsyncWebSocketClient client, byte[] data, int offset, int count)
|
||||
{
|
||||
if (_onServerFragmentationStreamClosed != null)
|
||||
{
|
||||
await _onServerFragmentationStreamClosed(client, data, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,391 +4,396 @@ using System.Linq;
|
|||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
using EonaCat.Logger.Extensions;
|
||||
using EonaCat.Logger.Managers;
|
||||
using EonaCat.Network;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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.
|
||||
|
||||
internal sealed class WebSocketClientHandshaker
|
||||
{
|
||||
// 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.
|
||||
private static readonly char[] _headerLineSplitter = { '\r', '\n' };
|
||||
|
||||
internal sealed class WebSocketClientHandshaker
|
||||
internal static byte[] CreateOpenningHandshakeRequest(AsyncWebSocketClient client, out string secWebSocketKey)
|
||||
{
|
||||
private static readonly char[] _headerLineSplitter = new char[] { '\r', '\n' };
|
||||
var sb = new StringBuilder();
|
||||
|
||||
internal static byte[] CreateOpenningHandshakeRequest(AsyncWebSocketClient client, out string secWebSocketKey)
|
||||
// The handshake MUST be a valid HTTP request as specified by [RFC2616].
|
||||
// The method of the request MUST be GET, and the HTTP version MUST be at least 1.1.
|
||||
// For example, if the WebSocket URI is "ws://example.com/chat",
|
||||
// the first line sent should be "GET /chat HTTP/1.1".
|
||||
sb.AppendFormatWithCrCf("GET {0} HTTP/{1}",
|
||||
!string.IsNullOrEmpty(client.Uri.PathAndQuery) ? client.Uri.PathAndQuery : "/",
|
||||
Consts.HttpVersion);
|
||||
|
||||
// The request MUST contain a |Host| header field whose value
|
||||
// contains /host/ plus optionally ":" followed by /port/ (when not
|
||||
// using the default port).
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Host, client.Uri.Host);
|
||||
|
||||
// The request MUST contain an |Upgrade| header field whose value
|
||||
// MUST include the "websocket" keyword.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Upgrade,
|
||||
Consts.WebSocketUpgradeToken);
|
||||
|
||||
// The request MUST contain a |Connection| header field whose value
|
||||
// MUST include the "Upgrade" token.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Connection,
|
||||
Consts.WebSocketConnectionToken);
|
||||
|
||||
// The request MUST include a header field with the name
|
||||
// |Sec-WebSocket-Key|. The value of this header field MUST be a
|
||||
// nonce consisting of a randomly selected 16-byte value that has
|
||||
// been base64-encoded (see Section 4 of [RFC4648]). The nonce
|
||||
// MUST be selected randomly for each connection.
|
||||
secWebSocketKey = Convert.ToBase64String(Encoding.ASCII.GetBytes(Guid.NewGuid().ToString().Substring(0, 16)));
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketKey, secWebSocketKey);
|
||||
|
||||
// The request MUST include a header field with the name
|
||||
// |Sec-WebSocket-Version|. The value of this header field MUST be 13.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketVersion,
|
||||
Consts.WebSocketVersion);
|
||||
|
||||
// The request MAY include a header field with the name
|
||||
// |Sec-WebSocket-Extensions|. If present, this value indicates
|
||||
// the protocol-level extension(s) the client wishes to speak. The
|
||||
// interpretation and format of this header field is described in Section 9.1.
|
||||
if (client.OfferedExtensions != null && client.OfferedExtensions.Any())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// The handshake MUST be a valid HTTP request as specified by [RFC2616].
|
||||
// The method of the request MUST be GET, and the HTTP version MUST be at least 1.1.
|
||||
// For example, if the WebSocket URI is "ws://example.com/chat",
|
||||
// the first line sent should be "GET /chat HTTP/1.1".
|
||||
sb.AppendFormatWithCrCf("GET {0} HTTP/{1}",
|
||||
!string.IsNullOrEmpty(client.Uri.PathAndQuery) ? client.Uri.PathAndQuery : "/",
|
||||
Consts.HttpVersion);
|
||||
|
||||
// The request MUST contain a |Host| header field whose value
|
||||
// contains /host/ plus optionally ":" followed by /port/ (when not
|
||||
// using the default port).
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Host, client.Uri.Host);
|
||||
|
||||
// The request MUST contain an |Upgrade| header field whose value
|
||||
// MUST include the "websocket" keyword.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Upgrade, Consts.WebSocketUpgradeToken);
|
||||
|
||||
// The request MUST contain a |Connection| header field whose value
|
||||
// MUST include the "Upgrade" token.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.Connection, Consts.WebSocketConnectionToken);
|
||||
|
||||
// The request MUST include a header field with the name
|
||||
// |Sec-WebSocket-Key|. The value of this header field MUST be a
|
||||
// nonce consisting of a randomly selected 16-byte value that has
|
||||
// been base64-encoded (see Section 4 of [RFC4648]). The nonce
|
||||
// MUST be selected randomly for each connection.
|
||||
secWebSocketKey = Convert.ToBase64String(Encoding.ASCII.GetBytes(Guid.NewGuid().ToString().Substring(0, 16)));
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketKey, secWebSocketKey);
|
||||
|
||||
// The request MUST include a header field with the name
|
||||
// |Sec-WebSocket-Version|. The value of this header field MUST be 13.
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketVersion, Consts.WebSocketVersion);
|
||||
|
||||
// The request MAY include a header field with the name
|
||||
// |Sec-WebSocket-Extensions|. If present, this value indicates
|
||||
// the protocol-level extension(s) the client wishes to speak. The
|
||||
// interpretation and format of this header field is described in Section 9.1.
|
||||
if (client.OfferedExtensions != null && client.OfferedExtensions.Any())
|
||||
foreach (var extension in client.OfferedExtensions)
|
||||
{
|
||||
foreach (var extension in client.OfferedExtensions)
|
||||
{
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketExtensions, extension.ExtensionNegotiationOffer);
|
||||
}
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketExtensions,
|
||||
extension.ExtensionNegotiationOffer);
|
||||
}
|
||||
}
|
||||
|
||||
// The request MAY include a header field with the name
|
||||
// |Sec-WebSocket-Protocol|. If present, this value indicates one
|
||||
// or more comma-separated subprotocol the client wishes to speak,
|
||||
// ordered by preference. The elements that comprise this value
|
||||
// MUST be non-empty strings with characters in the range U+0021 to
|
||||
// U+007E not including separator characters as defined in
|
||||
// [RFC2616] and MUST all be unique strings. The ABNF for the
|
||||
// value of this header field is 1#token, where the definitions of
|
||||
// constructs and rules are as given in [RFC2616].
|
||||
if (client.RequestedSubProtocols != null && client.RequestedSubProtocols.Any())
|
||||
// The request MAY include a header field with the name
|
||||
// |Sec-WebSocket-Protocol|. If present, this value indicates one
|
||||
// or more comma-separated subprotocol the client wishes to speak,
|
||||
// ordered by preference. The elements that comprise this value
|
||||
// MUST be non-empty strings with characters in the range U+0021 to
|
||||
// U+007E not including separator characters as defined in
|
||||
// [RFC2616] and MUST all be unique strings. The ABNF for the
|
||||
// value of this header field is 1#token, where the definitions of
|
||||
// constructs and rules are as given in [RFC2616].
|
||||
if (client.RequestedSubProtocols != null && client.RequestedSubProtocols.Any())
|
||||
{
|
||||
foreach (var description in client.RequestedSubProtocols)
|
||||
{
|
||||
foreach (var description in client.RequestedSubProtocols)
|
||||
{
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketProtocol, description.RequestedSubProtocol);
|
||||
}
|
||||
sb.AppendFormatWithCrCf(Consts.HttpHeaderLineFormat, HttpKnownHeaderNames.SecWebSocketProtocol,
|
||||
description.RequestedSubProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
// The request MUST include a header field with the name |Origin|
|
||||
// [RFC6454] if the request is coming from a browser client. If
|
||||
// the connection is from a non-browser client, the request MAY
|
||||
// include this header field if the semantics of that client match
|
||||
// the use-case described here for browser clients. The value of
|
||||
// this header field is the ASCII serialization of origin of the
|
||||
// context in which the code establishing the connection is
|
||||
// running. See [RFC6454] for the details of how this header field
|
||||
// value is constructed.
|
||||
// The request MUST include a header field with the name |Origin|
|
||||
// [RFC6454] if the request is coming from a browser client. If
|
||||
// the connection is from a non-browser client, the request MAY
|
||||
// include this header field if the semantics of that client match
|
||||
// the use-case described here for browser clients. The value of
|
||||
// this header field is the ASCII serialization of origin of the
|
||||
// context in which the code establishing the connection is
|
||||
// running. See [RFC6454] for the details of how this header field
|
||||
// value is constructed.
|
||||
|
||||
// The request MAY include any other header fields, for example,
|
||||
// cookies [RFC6265] and/or authentication-related header fields
|
||||
// such as the |Authorization| header field [RFC2616], which are
|
||||
// processed according to documents that define them.
|
||||
// The request MAY include any other header fields, for example,
|
||||
// cookies [RFC6265] and/or authentication-related header fields
|
||||
// such as the |Authorization| header field [RFC2616], which are
|
||||
// processed according to documents that define them.
|
||||
|
||||
sb.AppendWithCrCf();
|
||||
sb.AppendWithCrCf();
|
||||
|
||||
// GET /chat HTTP/1.1
|
||||
// Host: server.example.com
|
||||
// Upgrade: websocket
|
||||
// Connection: Upgrade
|
||||
// Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
|
||||
// Sec-WebSocket-Protocol: chat, superchat
|
||||
// Sec-WebSocket-Version: 13
|
||||
// Origin: http://example.com
|
||||
var request = sb.ToString();
|
||||
// GET /chat HTTP/1.1
|
||||
// Host: server.example.com
|
||||
// Upgrade: websocket
|
||||
// Connection: Upgrade
|
||||
// Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
|
||||
// Sec-WebSocket-Protocol: chat, superchat
|
||||
// Sec-WebSocket-Version: 13
|
||||
// Origin: http://example.com
|
||||
var request = sb.ToString();
|
||||
#if DEBUG
|
||||
NetworkHelper.Logger.Debug($"{client.RemoteEndPoint}{Environment.NewLine}{request}");
|
||||
#endif
|
||||
return Encoding.UTF8.GetBytes(request);
|
||||
return Encoding.UTF8.GetBytes(request);
|
||||
}
|
||||
|
||||
internal static bool VerifyOpenningHandshakeResponse(AsyncWebSocketClient client, byte[] buffer, int offset,
|
||||
int count, string secWebSocketKey)
|
||||
{
|
||||
BufferValidator.ValidateBuffer(buffer, offset, count, "buffer");
|
||||
if (string.IsNullOrEmpty(secWebSocketKey))
|
||||
{
|
||||
throw new ArgumentNullException("secWebSocketKey");
|
||||
}
|
||||
|
||||
internal static bool VerifyOpenningHandshakeResponse(AsyncWebSocketClient client, byte[] buffer, int offset, int count, string secWebSocketKey)
|
||||
{
|
||||
BufferValidator.ValidateBuffer(buffer, offset, count, "buffer");
|
||||
if (string.IsNullOrEmpty(secWebSocketKey))
|
||||
{
|
||||
throw new ArgumentNullException("secWebSocketKey");
|
||||
}
|
||||
|
||||
var response = Encoding.UTF8.GetString(buffer, offset, count);
|
||||
var response = Encoding.UTF8.GetString(buffer, offset, count);
|
||||
#if DEBUG
|
||||
NetworkHelper.Logger.Debug($"{client.RemoteEndPoint}{Environment.NewLine}{response}");
|
||||
#endif
|
||||
try
|
||||
try
|
||||
{
|
||||
// HTTP/1.1 101 Switching Protocols
|
||||
// Upgrade: websocket
|
||||
// Connection: Upgrade
|
||||
// Sec-WebSocket-Accept: 1tGBmA9p0DQDgmFll6P0/UcVS/E=
|
||||
// Sec-WebSocket-Protocol: chat
|
||||
Dictionary<string, string> headers;
|
||||
List<string> extensions;
|
||||
List<string> protocols;
|
||||
ParseOpenningHandshakeResponseHeaders(response, out headers, out extensions, out protocols);
|
||||
if (headers == null)
|
||||
{
|
||||
// HTTP/1.1 101 Switching Protocols
|
||||
// Upgrade: websocket
|
||||
// Connection: Upgrade
|
||||
// Sec-WebSocket-Accept: 1tGBmA9p0DQDgmFll6P0/UcVS/E=
|
||||
// Sec-WebSocket-Protocol: chat
|
||||
Dictionary<string, string> headers;
|
||||
List<string> extensions;
|
||||
List<string> protocols;
|
||||
ParseOpenningHandshakeResponseHeaders(response, out headers, out extensions, out protocols);
|
||||
if (headers == null)
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid headers.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
// If the status code received from the server is not 101, the
|
||||
// client handles the response per HTTP [RFC2616] procedures. In
|
||||
// particular, the client might perform authentication if it
|
||||
// receives a 401 status code; the server might redirect the client
|
||||
// using a 3xx status code (but clients are not required to follow them), etc.
|
||||
if (!headers.ContainsKey(Consts.HttpStatusCodeName))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to lack of status code.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (!headers.ContainsKey(Consts.HttpStatusCodeDescription))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to lack of status description.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (headers[Consts.HttpStatusCodeName] == ((int)HttpStatusCode.BadRequest).ToString())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to bad request [{1}].",
|
||||
client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
|
||||
}
|
||||
|
||||
if (headers[Consts.HttpStatusCodeName] != ((int)HttpStatusCode.SwitchingProtocols).ToString())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to expected 101 Switching Protocols but received [{1}].",
|
||||
client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
|
||||
}
|
||||
|
||||
// If the response lacks an |Upgrade| header field or the |Upgrade|
|
||||
// header field contains a value that is not an ASCII case-
|
||||
// insensitive match for the value "websocket", the client MUST
|
||||
// _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.Connection))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to lack of connection header item.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (headers[HttpKnownHeaderNames.Connection].ToLowerInvariant() != Consts.WebSocketConnectionToken.ToLowerInvariant())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid connection header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.Connection]));
|
||||
}
|
||||
|
||||
// If the response lacks a |Connection| header field or the
|
||||
// |Connection| header field doesn't contain a token that is an
|
||||
// ASCII case-insensitive match for the value "Upgrade", the client
|
||||
// MUST _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.Upgrade))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to lack of upgrade header item.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (headers[HttpKnownHeaderNames.Upgrade].ToLowerInvariant() != Consts.WebSocketUpgradeToken.ToLowerInvariant())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid upgrade header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.Upgrade]));
|
||||
}
|
||||
|
||||
// If the response lacks a |Sec-WebSocket-Accept| header field or
|
||||
// the |Sec-WebSocket-Accept| contains a value other than the
|
||||
// base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
|
||||
// Key| (as a string, not base64-decoded) with the string "258EAFA5-
|
||||
// E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
|
||||
// trailing whitespace, the client MUST _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketAccept))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to lack of Sec-WebSocket-Accept header item.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
string challenge = GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
if (!headers[HttpKnownHeaderNames.SecWebSocketAccept].Equals(challenge, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Accept header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketAccept]));
|
||||
}
|
||||
|
||||
// If the response includes a |Sec-WebSocket-Extensions| header
|
||||
// field and this header field indicates the use of an extension
|
||||
// that was not present in the client's handshake (the server has
|
||||
// indicated an extension not requested by the client), the client
|
||||
// MUST _Fail the WebSocket Connection_.
|
||||
if (extensions != null)
|
||||
{
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
// The empty string is not the same as the null value for these
|
||||
// purposes and is not a legal value for this field.
|
||||
if (string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to empty extension.", client.RemoteEndPoint));
|
||||
}
|
||||
}
|
||||
|
||||
client.AgreeExtensions(extensions);
|
||||
}
|
||||
|
||||
// If the response includes a |Sec-WebSocket-Protocol| header field
|
||||
// and this header field indicates the use of a subprotocol that was
|
||||
// not present in the client's handshake (the server has indicated a
|
||||
// subprotocol not requested by the client), the client MUST _Fail
|
||||
// the WebSocket Connection_.
|
||||
if (protocols != null)
|
||||
{
|
||||
if (!protocols.Any())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to empty sub-protocol.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (protocols.Count > 1)
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to suggest to use multiple sub-protocols.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
foreach (var protocol in protocols)
|
||||
{
|
||||
// The empty string is not the same as the null value for these
|
||||
// purposes and is not a legal value for this field.
|
||||
if (string.IsNullOrWhiteSpace(protocol))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to empty sub-protocol.", client.RemoteEndPoint));
|
||||
}
|
||||
}
|
||||
|
||||
var suggestedProtocols = protocols.First().Split(',')
|
||||
.Select(p => p.TrimStart().TrimEnd()).Where(p => !string.IsNullOrWhiteSpace(p));
|
||||
|
||||
if (!suggestedProtocols.Any())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid sub-protocol.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
if (suggestedProtocols.Count() > 1)
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to suggest to use multiple sub-protocols.", client.RemoteEndPoint));
|
||||
}
|
||||
|
||||
// The value chosen MUST be derived
|
||||
// from the client's handshake, specifically by selecting one of
|
||||
// the values from the |Sec-WebSocket-Protocol| field that the
|
||||
// server is willing to use for this connection (if any).
|
||||
client.UseSubProtocol(suggestedProtocols.First());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetworkHelper.Logger.Error($"{client.RemoteEndPoint}{Environment.NewLine}{ex.FormatExceptionToMessage()}");
|
||||
throw;
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to invalid headers.");
|
||||
}
|
||||
|
||||
return true;
|
||||
// If the status code received from the server is not 101, the
|
||||
// client handles the response per HTTP [RFC2616] procedures. In
|
||||
// particular, the client might perform authentication if it
|
||||
// receives a 401 status code; the server might redirect the client
|
||||
// using a 3xx status code (but clients are not required to follow them), etc.
|
||||
if (!headers.ContainsKey(Consts.HttpStatusCodeName))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to lack of status code.");
|
||||
}
|
||||
|
||||
if (!headers.ContainsKey(Consts.HttpStatusCodeDescription))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to lack of status description.");
|
||||
}
|
||||
|
||||
if (headers[Consts.HttpStatusCodeName] == ((int)HttpStatusCode.BadRequest).ToString())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to bad request [{1}].",
|
||||
client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
|
||||
}
|
||||
|
||||
if (headers[Consts.HttpStatusCodeName] != ((int)HttpStatusCode.SwitchingProtocols).ToString())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to expected 101 Switching Protocols but received [{1}].",
|
||||
client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
|
||||
}
|
||||
|
||||
// If the response lacks an |Upgrade| header field or the |Upgrade|
|
||||
// header field contains a value that is not an ASCII case-
|
||||
// insensitive match for the value "websocket", the client MUST
|
||||
// _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.Connection))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to lack of connection header item.");
|
||||
}
|
||||
|
||||
if (headers[HttpKnownHeaderNames.Connection].ToLowerInvariant() !=
|
||||
Consts.WebSocketConnectionToken.ToLowerInvariant())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid connection header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.Connection]));
|
||||
}
|
||||
|
||||
// If the response lacks a |Connection| header field or the
|
||||
// |Connection| header field doesn't contain a token that is an
|
||||
// ASCII case-insensitive match for the value "Upgrade", the client
|
||||
// MUST _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.Upgrade))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to lack of upgrade header item.");
|
||||
}
|
||||
|
||||
if (headers[HttpKnownHeaderNames.Upgrade].ToLowerInvariant() !=
|
||||
Consts.WebSocketUpgradeToken.ToLowerInvariant())
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid upgrade header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.Upgrade]));
|
||||
}
|
||||
|
||||
// If the response lacks a |Sec-WebSocket-Accept| header field or
|
||||
// the |Sec-WebSocket-Accept| contains a value other than the
|
||||
// base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
|
||||
// Key| (as a string, not base64-decoded) with the string "258EAFA5-
|
||||
// E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
|
||||
// trailing whitespace, the client MUST _Fail the WebSocket Connection_.
|
||||
if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketAccept))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to lack of Sec-WebSocket-Accept header item.");
|
||||
}
|
||||
|
||||
var challenge = GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
if (!headers[HttpKnownHeaderNames.SecWebSocketAccept].Equals(challenge, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new WebSocketHandshakeException(string.Format(
|
||||
"Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Accept header item value [{1}].",
|
||||
client.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketAccept]));
|
||||
}
|
||||
|
||||
// If the response includes a |Sec-WebSocket-Extensions| header
|
||||
// field and this header field indicates the use of an extension
|
||||
// that was not present in the client's handshake (the server has
|
||||
// indicated an extension not requested by the client), the client
|
||||
// MUST _Fail the WebSocket Connection_.
|
||||
if (extensions != null)
|
||||
{
|
||||
foreach (var extension in extensions)
|
||||
// The empty string is not the same as the null value for these
|
||||
// purposes and is not a legal value for this field.
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to empty extension.");
|
||||
}
|
||||
}
|
||||
|
||||
client.AgreeExtensions(extensions);
|
||||
}
|
||||
|
||||
// If the response includes a |Sec-WebSocket-Protocol| header field
|
||||
// and this header field indicates the use of a subprotocol that was
|
||||
// not present in the client's handshake (the server has indicated a
|
||||
// subprotocol not requested by the client), the client MUST _Fail
|
||||
// the WebSocket Connection_.
|
||||
if (protocols != null)
|
||||
{
|
||||
if (!protocols.Any())
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to empty sub-protocol.");
|
||||
}
|
||||
|
||||
if (protocols.Count > 1)
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to suggest to use multiple sub-protocols.");
|
||||
}
|
||||
|
||||
foreach (var protocol in protocols)
|
||||
// The empty string is not the same as the null value for these
|
||||
// purposes and is not a legal value for this field.
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(protocol))
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to empty sub-protocol.");
|
||||
}
|
||||
}
|
||||
|
||||
var suggestedProtocols = protocols.First().Split(',')
|
||||
.Select(p => p.TrimStart().TrimEnd()).Where(p => !string.IsNullOrWhiteSpace(p));
|
||||
|
||||
if (!suggestedProtocols.Any())
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to invalid sub-protocol.");
|
||||
}
|
||||
|
||||
if (suggestedProtocols.Count() > 1)
|
||||
{
|
||||
throw new WebSocketHandshakeException(
|
||||
$"Handshake with remote [{client.RemoteEndPoint}] failed due to suggest to use multiple sub-protocols.");
|
||||
}
|
||||
|
||||
// The value chosen MUST be derived
|
||||
// from the client's handshake, specifically by selecting one of
|
||||
// the values from the |Sec-WebSocket-Protocol| field that the
|
||||
// server is willing to use for this connection (if any).
|
||||
client.UseSubProtocol(suggestedProtocols.First());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetworkHelper.Logger.Error($"{client.RemoteEndPoint}{Environment.NewLine}{ex.FormatExceptionToMessage()}");
|
||||
throw;
|
||||
}
|
||||
|
||||
private static void ParseOpenningHandshakeResponseHeaders(string response,
|
||||
out Dictionary<string, string> headers,
|
||||
out List<string> extensions,
|
||||
out List<string> protocols)
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ParseOpenningHandshakeResponseHeaders(string response,
|
||||
out Dictionary<string, string> headers,
|
||||
out List<string> extensions,
|
||||
out List<string> protocols)
|
||||
{
|
||||
headers = new Dictionary<string, string>();
|
||||
|
||||
// The |Sec-WebSocket-Extensions| header field MAY appear multiple times
|
||||
// in an HTTP request (which is logically the same as a single
|
||||
// |Sec-WebSocket-Extensions| header field that contains all values.
|
||||
// However, the |Sec-WebSocket-Extensions| header field MUST NOT appear
|
||||
// more than once in an HTTP response.
|
||||
extensions = null;
|
||||
// The |Sec-WebSocket-Protocol| header field MAY appear multiple times
|
||||
// in an HTTP request (which is logically the same as a single
|
||||
// |Sec-WebSocket-Protocol| header field that contains all values).
|
||||
// However, the |Sec-WebSocket-Protocol| header field MUST NOT appear
|
||||
// more than once in an HTTP response.
|
||||
protocols = null;
|
||||
|
||||
var lines = response.Split(_headerLineSplitter).Where(l => l.Length > 0);
|
||||
foreach (var line in lines)
|
||||
// HTTP/1.1 101 Switching Protocols
|
||||
// HTTP/1.1 400 Bad Request
|
||||
{
|
||||
headers = new Dictionary<string, string>();
|
||||
|
||||
// The |Sec-WebSocket-Extensions| header field MAY appear multiple times
|
||||
// in an HTTP request (which is logically the same as a single
|
||||
// |Sec-WebSocket-Extensions| header field that contains all values.
|
||||
// However, the |Sec-WebSocket-Extensions| header field MUST NOT appear
|
||||
// more than once in an HTTP response.
|
||||
extensions = null;
|
||||
// The |Sec-WebSocket-Protocol| header field MAY appear multiple times
|
||||
// in an HTTP request (which is logically the same as a single
|
||||
// |Sec-WebSocket-Protocol| header field that contains all values).
|
||||
// However, the |Sec-WebSocket-Protocol| header field MUST NOT appear
|
||||
// more than once in an HTTP response.
|
||||
protocols = null;
|
||||
|
||||
var lines = response.Split(_headerLineSplitter).Where(l => l.Length > 0);
|
||||
foreach (var line in lines)
|
||||
if (line.StartsWith(@"HTTP/"))
|
||||
{
|
||||
// HTTP/1.1 101 Switching Protocols
|
||||
// HTTP/1.1 400 Bad Request
|
||||
if (line.StartsWith(@"HTTP/"))
|
||||
var segements = line.Split(' ');
|
||||
if (segements.Length > 1)
|
||||
{
|
||||
var segements = line.Split(' ');
|
||||
if (segements.Length > 1)
|
||||
{
|
||||
headers.Add(Consts.HttpStatusCodeName, segements[1]);
|
||||
headers.Add(Consts.HttpStatusCodeName, segements[1]);
|
||||
|
||||
if (segements.Length > 2)
|
||||
{
|
||||
headers.Add(Consts.HttpStatusCodeDescription, segements[2]);
|
||||
}
|
||||
if (segements.Length > 2)
|
||||
{
|
||||
headers.Add(Consts.HttpStatusCodeDescription, segements[2]);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var key in HttpKnownHeaderNames.All)
|
||||
{
|
||||
foreach (var key in HttpKnownHeaderNames.All)
|
||||
if (line.StartsWith(key + ":"))
|
||||
{
|
||||
if (line.StartsWith(key + ":"))
|
||||
var index = line.IndexOf(':');
|
||||
if (index != -1)
|
||||
{
|
||||
var index = line.IndexOf(':');
|
||||
if (index != -1)
|
||||
var value = line.Substring(index + 1);
|
||||
|
||||
if (key == HttpKnownHeaderNames.SecWebSocketExtensions)
|
||||
{
|
||||
var value = line.Substring(index + 1);
|
||||
|
||||
if (key == HttpKnownHeaderNames.SecWebSocketExtensions)
|
||||
if (extensions == null)
|
||||
{
|
||||
if (extensions == null)
|
||||
{
|
||||
extensions = new List<string>();
|
||||
}
|
||||
|
||||
extensions.Add(value.Trim());
|
||||
extensions = new List<string>();
|
||||
}
|
||||
else if (key == HttpKnownHeaderNames.SecWebSocketProtocol)
|
||||
{
|
||||
if (protocols == null)
|
||||
{
|
||||
protocols = new List<string>();
|
||||
}
|
||||
|
||||
protocols.Add(value.Trim());
|
||||
extensions.Add(value.Trim());
|
||||
}
|
||||
else if (key == HttpKnownHeaderNames.SecWebSocketProtocol)
|
||||
{
|
||||
if (protocols == null)
|
||||
{
|
||||
protocols = new List<string>();
|
||||
}
|
||||
|
||||
protocols.Add(value.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (headers.ContainsKey(key))
|
||||
{
|
||||
headers[key] = string.Join(",", headers[key], value.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (headers.ContainsKey(key))
|
||||
{
|
||||
headers[key] = string.Join(",", headers[key], value.Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.Add(key, value.Trim());
|
||||
}
|
||||
headers.Add(key, value.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -396,19 +401,19 @@ namespace EonaCat.WebSockets
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSecWebSocketAcceptString(string secWebSocketKey)
|
||||
{
|
||||
string retVal;
|
||||
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
string acceptString = string.Concat(secWebSocketKey, Consts.SecWebSocketKeyGuid);
|
||||
byte[] toHash = Encoding.UTF8.GetBytes(acceptString);
|
||||
retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSecWebSocketAcceptString(string secWebSocketKey)
|
||||
{
|
||||
string retVal;
|
||||
|
||||
using (var sha1 = SHA1.Create())
|
||||
{
|
||||
var acceptString = string.Concat(secWebSocketKey, Consts.SecWebSocketKeyGuid);
|
||||
var toHash = Encoding.UTF8.GetBytes(acceptString);
|
||||
retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public class WebSocketException : Exception
|
||||
{
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public class WebSocketException : Exception
|
||||
public WebSocketException(string message)
|
||||
: base(message)
|
||||
{
|
||||
public WebSocketException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public WebSocketException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public sealed class WebSocketHandshakeException : WebSocketException
|
||||
{
|
||||
// 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.
|
||||
|
||||
[Serializable]
|
||||
public sealed class WebSocketHandshakeException : WebSocketException
|
||||
public WebSocketHandshakeException(string message)
|
||||
: base(message)
|
||||
{
|
||||
public WebSocketHandshakeException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public WebSocketHandshakeException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketHandshakeException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 interface IWebSocketExtension
|
||||
{
|
||||
// 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.
|
||||
string Name { get; }
|
||||
|
||||
public interface IWebSocketExtension
|
||||
{
|
||||
string Name { get; }
|
||||
bool Rsv1BitOccupied { get; }
|
||||
bool Rsv2BitOccupied { get; }
|
||||
bool Rsv3BitOccupied { get; }
|
||||
|
||||
bool Rsv1BitOccupied { get; }
|
||||
bool Rsv2BitOccupied { get; }
|
||||
bool Rsv3BitOccupied { get; }
|
||||
string GetAgreedOffer();
|
||||
|
||||
string GetAgreedOffer();
|
||||
byte[] BuildExtensionData(byte[] payload, int offset, int count);
|
||||
|
||||
byte[] BuildExtensionData(byte[] payload, int offset, int count);
|
||||
|
||||
byte[] ProcessIncomingMessagePayload(byte[] payload, int offset, int count);
|
||||
byte[] ProcessOutgoingMessagePayload(byte[] payload, int offset, int count);
|
||||
}
|
||||
}
|
||||
byte[] ProcessIncomingMessagePayload(byte[] payload, int offset, int count);
|
||||
byte[] ProcessOutgoingMessagePayload(byte[] payload, int offset, int count);
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
namespace EonaCat.WebSockets.Extensions
|
||||
{
|
||||
// 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.WebSockets.Extensions;
|
||||
// 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 interface IWebSocketExtensionNegotiator
|
||||
{
|
||||
bool NegotiateAsServer(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension);
|
||||
bool NegotiateAsClient(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension);
|
||||
}
|
||||
}
|
||||
public interface IWebSocketExtensionNegotiator
|
||||
{
|
||||
bool NegotiateAsServer(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension);
|
||||
bool NegotiateAsClient(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension);
|
||||
}
|
|
@ -1,34 +1,27 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class AbsentableValueParameter<T> : ExtensionParameter
|
||||
{
|
||||
// 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 class AbsentableValueParameter<T> : ExtensionParameter
|
||||
public AbsentableValueParameter(string name, Func<string, bool> valueValidator, T defaultValue)
|
||||
: base(name)
|
||||
{
|
||||
public AbsentableValueParameter(string name, Func<string, bool> valueValidator, T defaultValue)
|
||||
: base(name)
|
||||
if (valueValidator == null)
|
||||
{
|
||||
if (valueValidator == null)
|
||||
{
|
||||
throw new ArgumentNullException("valueValidator");
|
||||
}
|
||||
|
||||
this.ValueValidator = valueValidator;
|
||||
this.DefaultValue = defaultValue;
|
||||
throw new ArgumentNullException("valueValidator");
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExtensionParameterType.Single | ExtensionParameterType.Valuable;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<string, bool> ValueValidator { get; private set; }
|
||||
|
||||
public T DefaultValue { get; private set; }
|
||||
ValueValidator = valueValidator;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType =>
|
||||
ExtensionParameterType.Single | ExtensionParameterType.Valuable;
|
||||
|
||||
public Func<string, bool> ValueValidator { get; private set; }
|
||||
|
||||
public T DefaultValue { get; private set; }
|
||||
}
|
|
@ -1,28 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 abstract class AgreedExtensionParameter
|
||||
{
|
||||
// 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 abstract class AgreedExtensionParameter
|
||||
public AgreedExtensionParameter(string name)
|
||||
{
|
||||
public AgreedExtensionParameter(string name)
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
this.Name = name;
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public abstract ExtensionParameterType ParameterType { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", this.Name);
|
||||
}
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public abstract ExtensionParameterType ParameterType { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}";
|
||||
}
|
||||
}
|
|
@ -1,21 +1,13 @@
|
|||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class AgreedSingleParameter : AgreedExtensionParameter
|
||||
{
|
||||
// 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 class AgreedSingleParameter : AgreedExtensionParameter
|
||||
public AgreedSingleParameter(string name)
|
||||
: base(name)
|
||||
{
|
||||
public AgreedSingleParameter(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExtensionParameterType.Single;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType => ExtensionParameterType.Single;
|
||||
}
|
|
@ -1,29 +1,21 @@
|
|||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class AgreedValuableParameter<T> : AgreedExtensionParameter
|
||||
{
|
||||
// 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 class AgreedValuableParameter<T> : AgreedExtensionParameter
|
||||
public AgreedValuableParameter(string name, T value)
|
||||
: base(name)
|
||||
{
|
||||
public AgreedValuableParameter(string name, T @value)
|
||||
: base(name)
|
||||
{
|
||||
this.Value = @value;
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExtensionParameterType.Valuable;
|
||||
}
|
||||
}
|
||||
|
||||
public T Value { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}={1}", this.Name, this.Value);
|
||||
}
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType => ExtensionParameterType.Valuable;
|
||||
|
||||
public T Value { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}={Value}";
|
||||
}
|
||||
}
|
|
@ -1,28 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 abstract class ExtensionParameter
|
||||
{
|
||||
// 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 abstract class ExtensionParameter
|
||||
public ExtensionParameter(string name)
|
||||
{
|
||||
public ExtensionParameter(string name)
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
this.Name = name;
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public abstract ExtensionParameterType ParameterType { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", this.Name);
|
||||
}
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public abstract ExtensionParameterType ParameterType { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}";
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
{
|
||||
// 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.WebSockets.Extensions;
|
||||
// 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.
|
||||
|
||||
[Flags]
|
||||
public enum ExtensionParameterType : byte
|
||||
{
|
||||
Single = 0x1,
|
||||
Valuable = 0x2,
|
||||
}
|
||||
}
|
||||
[Flags]
|
||||
public enum ExtensionParameterType : byte
|
||||
{
|
||||
Single = 0x1,
|
||||
Valuable = 0x2
|
||||
}
|
|
@ -1,21 +1,13 @@
|
|||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class SingleParameter : ExtensionParameter
|
||||
{
|
||||
// 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 class SingleParameter : ExtensionParameter
|
||||
public SingleParameter(string name)
|
||||
: base(name)
|
||||
{
|
||||
public SingleParameter(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExtensionParameterType.Single;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType => ExtensionParameterType.Single;
|
||||
}
|
|
@ -1,31 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class ValuableParameter<T> : ExtensionParameter
|
||||
{
|
||||
// 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 class ValuableParameter<T> : ExtensionParameter
|
||||
public ValuableParameter(string name, Func<string, bool> valueValidator)
|
||||
: base(name)
|
||||
{
|
||||
public ValuableParameter(string name, Func<string, bool> valueValidator)
|
||||
: base(name)
|
||||
if (valueValidator == null)
|
||||
{
|
||||
if (valueValidator == null)
|
||||
{
|
||||
throw new ArgumentNullException("valueValidator");
|
||||
}
|
||||
|
||||
this.ValueValidator = valueValidator;
|
||||
throw new ArgumentNullException("valueValidator");
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType
|
||||
{
|
||||
get
|
||||
{
|
||||
return ExtensionParameterType.Valuable;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<string, bool> ValueValidator { get; private set; }
|
||||
ValueValidator = valueValidator;
|
||||
}
|
||||
}
|
||||
|
||||
public override ExtensionParameterType ParameterType => ExtensionParameterType.Valuable;
|
||||
|
||||
public Func<string, bool> ValueValidator { get; private set; }
|
||||
}
|
|
@ -4,78 +4,75 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 class DeflateCompression
|
||||
{
|
||||
// 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.
|
||||
private readonly ISegmentBufferManager _bufferAllocator;
|
||||
|
||||
public class DeflateCompression
|
||||
public DeflateCompression(ISegmentBufferManager bufferAllocator)
|
||||
{
|
||||
private readonly ISegmentBufferManager _bufferAllocator;
|
||||
|
||||
public DeflateCompression(ISegmentBufferManager bufferAllocator)
|
||||
if (bufferAllocator == null)
|
||||
{
|
||||
if (bufferAllocator == null)
|
||||
throw new ArgumentNullException("bufferAllocator");
|
||||
}
|
||||
|
||||
_bufferAllocator = bufferAllocator;
|
||||
}
|
||||
|
||||
public byte[] Compress(byte[] raw)
|
||||
{
|
||||
return Compress(raw, 0, raw.Length);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
|
||||
public byte[] Compress(byte[] raw, int offset, int count)
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var deflate = new DeflateStream(memory, CompressionMode.Compress, true))
|
||||
{
|
||||
throw new ArgumentNullException("bufferAllocator");
|
||||
deflate.Write(raw, offset, count);
|
||||
}
|
||||
|
||||
_bufferAllocator = bufferAllocator;
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Compress(byte[] raw)
|
||||
{
|
||||
return Compress(raw, 0, raw.Length);
|
||||
}
|
||||
public byte[] Decompress(byte[] raw)
|
||||
{
|
||||
return Decompress(raw, 0, raw.Length);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
|
||||
public byte[] Compress(byte[] raw, int offset, int count)
|
||||
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
|
||||
public byte[] Decompress(byte[] raw, int offset, int count)
|
||||
{
|
||||
var buffer = _bufferAllocator.BorrowBuffer();
|
||||
|
||||
try
|
||||
{
|
||||
using (var input = new MemoryStream(raw, offset, count))
|
||||
using (var deflate = new DeflateStream(input, CompressionMode.Decompress, true))
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var deflate = new DeflateStream(memory, CompressionMode.Compress, leaveOpen: true))
|
||||
var readCount = 0;
|
||||
do
|
||||
{
|
||||
deflate.Write(raw, offset, count);
|
||||
}
|
||||
readCount = deflate.Read(buffer.Array, buffer.Offset, buffer.Count);
|
||||
if (readCount > 0)
|
||||
{
|
||||
memory.Write(buffer.Array, buffer.Offset, readCount);
|
||||
}
|
||||
} while (readCount > 0);
|
||||
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Decompress(byte[] raw)
|
||||
finally
|
||||
{
|
||||
return Decompress(raw, 0, raw.Length);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
|
||||
public byte[] Decompress(byte[] raw, int offset, int count)
|
||||
{
|
||||
var buffer = _bufferAllocator.BorrowBuffer();
|
||||
|
||||
try
|
||||
{
|
||||
using (var input = new MemoryStream(raw, offset, count))
|
||||
using (var deflate = new DeflateStream(input, CompressionMode.Decompress, leaveOpen: true))
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
int readCount = 0;
|
||||
do
|
||||
{
|
||||
readCount = deflate.Read(buffer.Array, buffer.Offset, buffer.Count);
|
||||
if (readCount > 0)
|
||||
{
|
||||
memory.Write(buffer.Array, buffer.Offset, readCount);
|
||||
}
|
||||
}
|
||||
while (readCount > 0);
|
||||
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_bufferAllocator.ReturnBuffer(buffer);
|
||||
}
|
||||
_bufferAllocator.ReturnBuffer(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,95 +3,93 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 sealed class PerMessageCompressionExtension : IWebSocketExtension
|
||||
{
|
||||
// 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.
|
||||
// Any extension-token used MUST be a registered token (see
|
||||
// Section 11.4). The parameters supplied with any given extension MUST
|
||||
// be defined for that extension. Note that the client is only offering
|
||||
// to use any advertised extensions and MUST NOT use them unless the
|
||||
// server indicates that it wishes to use the extension.
|
||||
public static readonly string RegisteredToken = @"permessage-deflate";
|
||||
private readonly SortedList<int, AgreedExtensionParameter> _agreedParameters;
|
||||
|
||||
public sealed class PerMessageCompressionExtension : IWebSocketExtension
|
||||
private readonly DeflateCompression _deflater;
|
||||
|
||||
public PerMessageCompressionExtension()
|
||||
{
|
||||
// Any extension-token used MUST be a registered token (see
|
||||
// Section 11.4). The parameters supplied with any given extension MUST
|
||||
// be defined for that extension. Note that the client is only offering
|
||||
// to use any advertised extensions and MUST NOT use them unless the
|
||||
// server indicates that it wishes to use the extension.
|
||||
public static readonly string RegisteredToken = @"permessage-deflate";
|
||||
|
||||
private readonly DeflateCompression _deflater;
|
||||
private SortedList<int, AgreedExtensionParameter> _agreedParameters;
|
||||
|
||||
public PerMessageCompressionExtension()
|
||||
{
|
||||
var bufferAllocator = new SegmentBufferManager(100, 8192, 1, true);
|
||||
_deflater = new DeflateCompression(bufferAllocator);
|
||||
}
|
||||
|
||||
public PerMessageCompressionExtension(SortedList<int, AgreedExtensionParameter> agreedParameters)
|
||||
: this()
|
||||
{
|
||||
_agreedParameters = agreedParameters;
|
||||
}
|
||||
|
||||
public string Name { get { return RegisteredToken; } }
|
||||
|
||||
// PMCEs use the RSV1 bit of the WebSocket frame header to indicate whether a
|
||||
// message is compressed or not so that an endpoint can choose not to
|
||||
// compress messages with incompressible contents.
|
||||
public bool Rsv1BitOccupied { get { return true; } }
|
||||
public bool Rsv2BitOccupied { get { return false; } }
|
||||
public bool Rsv3BitOccupied { get { return false; } }
|
||||
|
||||
public string GetAgreedOffer()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(this.Name);
|
||||
|
||||
if (_agreedParameters != null && _agreedParameters.Any())
|
||||
{
|
||||
foreach (var parameter in _agreedParameters.Values)
|
||||
{
|
||||
sb.Append("; ");
|
||||
sb.Append(parameter.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public byte[] BuildExtensionData(byte[] payload, int offset, int count)
|
||||
{
|
||||
// Payload data: (x+y) bytes
|
||||
//
|
||||
// The "Payload data" is defined as "Extension data" concatenated
|
||||
// with "Application data".
|
||||
//
|
||||
// Extension data: x bytes
|
||||
//
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload
|
||||
// length.
|
||||
//
|
||||
// Application data: y bytes
|
||||
//
|
||||
// Arbitrary "Application data", taking up the remainder of the frame
|
||||
// after any "Extension data". The length of the "Application data"
|
||||
// is equal to the payload length minus the length of the "Extension
|
||||
// data".
|
||||
return null; // PMCE doesn't have an extension data definition.
|
||||
}
|
||||
|
||||
public byte[] ProcessIncomingMessagePayload(byte[] payload, int offset, int count)
|
||||
{
|
||||
return _deflater.Decompress(payload, offset, count);
|
||||
}
|
||||
|
||||
public byte[] ProcessOutgoingMessagePayload(byte[] payload, int offset, int count)
|
||||
{
|
||||
return _deflater.Compress(payload, offset, count);
|
||||
}
|
||||
var bufferAllocator = new SegmentBufferManager(100, 8192, 1, true);
|
||||
_deflater = new DeflateCompression(bufferAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
public PerMessageCompressionExtension(SortedList<int, AgreedExtensionParameter> agreedParameters)
|
||||
: this()
|
||||
{
|
||||
_agreedParameters = agreedParameters;
|
||||
}
|
||||
|
||||
public string Name => RegisteredToken;
|
||||
|
||||
// PMCEs use the RSV1 bit of the WebSocket frame header to indicate whether a
|
||||
// message is compressed or not so that an endpoint can choose not to
|
||||
// compress messages with incompressible contents.
|
||||
public bool Rsv1BitOccupied => true;
|
||||
public bool Rsv2BitOccupied => false;
|
||||
public bool Rsv3BitOccupied => false;
|
||||
|
||||
public string GetAgreedOffer()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(Name);
|
||||
|
||||
if (_agreedParameters != null && _agreedParameters.Any())
|
||||
{
|
||||
foreach (var parameter in _agreedParameters.Values)
|
||||
{
|
||||
sb.Append("; ");
|
||||
sb.Append(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public byte[] BuildExtensionData(byte[] payload, int offset, int count)
|
||||
{
|
||||
// Payload data: (x+y) bytes
|
||||
//
|
||||
// The "Payload data" is defined as "Extension data" concatenated
|
||||
// with "Application data".
|
||||
//
|
||||
// Extension data: x bytes
|
||||
//
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload
|
||||
// length.
|
||||
//
|
||||
// Application data: y bytes
|
||||
//
|
||||
// Arbitrary "Application data", taking up the remainder of the frame
|
||||
// after any "Extension data". The length of the "Application data"
|
||||
// is equal to the payload length minus the length of the "Extension
|
||||
// data".
|
||||
return null; // PMCE doesn't have an extension data definition.
|
||||
}
|
||||
|
||||
public byte[] ProcessIncomingMessagePayload(byte[] payload, int offset, int count)
|
||||
{
|
||||
return _deflater.Decompress(payload, offset, count);
|
||||
}
|
||||
|
||||
public byte[] ProcessOutgoingMessagePayload(byte[] payload, int offset, int count)
|
||||
{
|
||||
return _deflater.Compress(payload, offset, count);
|
||||
}
|
||||
}
|
|
@ -1,135 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 sealed class PerMessageCompressionExtensionNegotiator : IWebSocketExtensionNegotiator
|
||||
{
|
||||
// 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.
|
||||
private static readonly char[] TrimableChars = { ' ', ';', '\r', '\n' };
|
||||
|
||||
public sealed class PerMessageCompressionExtensionNegotiator : IWebSocketExtensionNegotiator
|
||||
public bool NegotiateAsServer(string offer, out string invalidParameter,
|
||||
out IWebSocketExtension negotiatedExtension)
|
||||
{
|
||||
private static readonly char[] TrimableChars = new char[] { ' ', ';', '\r', '\n' };
|
||||
return Negotiate(offer, AgreeAsServer, out invalidParameter, out negotiatedExtension);
|
||||
}
|
||||
|
||||
public bool NegotiateAsServer(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension)
|
||||
public bool NegotiateAsClient(string offer, out string invalidParameter,
|
||||
out IWebSocketExtension negotiatedExtension)
|
||||
{
|
||||
return Negotiate(offer, AgreeAsClient, out invalidParameter, out negotiatedExtension);
|
||||
}
|
||||
|
||||
private bool Negotiate(string offer, Func<AgreedExtensionParameter, bool> agree, out string invalidParameter,
|
||||
out IWebSocketExtension negotiatedExtension)
|
||||
{
|
||||
invalidParameter = null;
|
||||
negotiatedExtension = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(offer))
|
||||
{
|
||||
return Negotiate(offer, AgreeAsServer, out invalidParameter, out negotiatedExtension);
|
||||
invalidParameter = offer;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool NegotiateAsClient(string offer, out string invalidParameter, out IWebSocketExtension negotiatedExtension)
|
||||
var segements = offer.Replace('\r', ' ').Replace('\n', ' ').TrimStart(TrimableChars).TrimEnd(TrimableChars)
|
||||
.Split(';');
|
||||
|
||||
var offeredExtensionName = segements[0].TrimStart(TrimableChars).TrimEnd(TrimableChars);
|
||||
if (string.IsNullOrEmpty(offeredExtensionName))
|
||||
{
|
||||
return Negotiate(offer, AgreeAsClient, out invalidParameter, out negotiatedExtension);
|
||||
invalidParameter = offer;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool Negotiate(string offer, Func<AgreedExtensionParameter, bool> agree, out string invalidParameter, out IWebSocketExtension negotiatedExtension)
|
||||
if (string.Compare(offeredExtensionName, PerMessageCompressionExtension.RegisteredToken,
|
||||
StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
invalidParameter = null;
|
||||
negotiatedExtension = null;
|
||||
invalidParameter = offeredExtensionName;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(offer))
|
||||
{
|
||||
invalidParameter = offer;
|
||||
return false;
|
||||
}
|
||||
|
||||
var segements = offer.Replace('\r', ' ').Replace('\n', ' ').TrimStart(TrimableChars).TrimEnd(TrimableChars).Split(';');
|
||||
|
||||
var offeredExtensionName = segements[0].TrimStart(TrimableChars).TrimEnd(TrimableChars);
|
||||
if (string.IsNullOrEmpty(offeredExtensionName))
|
||||
{
|
||||
invalidParameter = offer;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.Compare(offeredExtensionName, PerMessageCompressionExtension.RegisteredToken, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
invalidParameter = offeredExtensionName;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segements.Length == 1)
|
||||
{
|
||||
negotiatedExtension = new PerMessageCompressionExtension();
|
||||
return true;
|
||||
}
|
||||
|
||||
// This set of elements MAY include multiple PMCEs with the same extension
|
||||
// name to offer the possibility to use the same algorithm with
|
||||
// different configuration parameters.
|
||||
for (int i = 1; i < segements.Length; i++)
|
||||
{
|
||||
var offeredParameter = segements[i];
|
||||
if (!PerMessageCompressionExtensionParameters.ValidateParameter(offeredParameter))
|
||||
{
|
||||
invalidParameter = offeredParameter;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The order of elements is important as it specifies the client's preference.
|
||||
// An element preceding another element has higher preference. It is recommended
|
||||
// that a server accepts PMCEs with higher preference if the server supports them.
|
||||
var agreedSet = new SortedList<int, AgreedExtensionParameter>();
|
||||
|
||||
for (int i = 1; i < segements.Length; i++)
|
||||
{
|
||||
var offeredParameter = segements[i];
|
||||
var agreeingParameter = PerMessageCompressionExtensionParameters.ResolveParameter(offeredParameter);
|
||||
if (agree(agreeingParameter))
|
||||
{
|
||||
agreedSet.Add(i, agreeingParameter);
|
||||
}
|
||||
}
|
||||
|
||||
negotiatedExtension = new PerMessageCompressionExtension(agreedSet);
|
||||
if (segements.Length == 1)
|
||||
{
|
||||
negotiatedExtension = new PerMessageCompressionExtension();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AgreeAsServer(AgreedExtensionParameter parameter)
|
||||
// This set of elements MAY include multiple PMCEs with the same extension
|
||||
// name to offer the possibility to use the same algorithm with
|
||||
// different configuration parameters.
|
||||
for (var i = 1; i < segements.Length; i++)
|
||||
{
|
||||
if (parameter == null)
|
||||
var offeredParameter = segements[i];
|
||||
if (!PerMessageCompressionExtensionParameters.ValidateParameter(offeredParameter))
|
||||
{
|
||||
invalidParameter = offeredParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (parameter.Name)
|
||||
{
|
||||
case PerMessageCompressionExtensionParameters.ServerNoContextTakeOverParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case PerMessageCompressionExtensionParameters.ServerMaxWindowBitsParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter name.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool AgreeAsClient(AgreedExtensionParameter parameter)
|
||||
// The order of elements is important as it specifies the client's preference.
|
||||
// An element preceding another element has higher preference. It is recommended
|
||||
// that a server accepts PMCEs with higher preference if the server supports them.
|
||||
var agreedSet = new SortedList<int, AgreedExtensionParameter>();
|
||||
|
||||
for (var i = 1; i < segements.Length; i++)
|
||||
{
|
||||
if (parameter == null)
|
||||
var offeredParameter = segements[i];
|
||||
var agreeingParameter = PerMessageCompressionExtensionParameters.ResolveParameter(offeredParameter);
|
||||
if (agree(agreeingParameter))
|
||||
{
|
||||
agreedSet.Add(i, agreeingParameter);
|
||||
}
|
||||
}
|
||||
|
||||
negotiatedExtension = new PerMessageCompressionExtension(agreedSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AgreeAsServer(AgreedExtensionParameter parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (parameter.Name)
|
||||
{
|
||||
case PerMessageCompressionExtensionParameters.ServerNoContextTakeOverParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (parameter.Name)
|
||||
case PerMessageCompressionExtensionParameters.ServerMaxWindowBitsParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
case PerMessageCompressionExtensionParameters.ServerNoContextTakeOverParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case PerMessageCompressionExtensionParameters.ServerMaxWindowBitsParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter name.");
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter name.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool AgreeAsClient(AgreedExtensionParameter parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (parameter.Name)
|
||||
{
|
||||
case PerMessageCompressionExtensionParameters.ServerNoContextTakeOverParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case PerMessageCompressionExtensionParameters.ServerMaxWindowBitsParameterName:
|
||||
case PerMessageCompressionExtensionParameters.ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter name.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,202 +2,205 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 sealed class PerMessageCompressionExtensionParameters
|
||||
{
|
||||
// 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 const string ServerNoContextTakeOverParameterName = @"server_no_context_takeover";
|
||||
public const string ClientNoContextTakeOverParameterName = @"client_no_context_takeover";
|
||||
public const string ServerMaxWindowBitsParameterName = @"server_max_window_bits";
|
||||
public const string ClientMaxWindowBitsParameterName = @"client_max_window_bits";
|
||||
|
||||
public sealed class PerMessageCompressionExtensionParameters
|
||||
public static readonly SingleParameter ServerNoContextTakeOver = new(ServerNoContextTakeOverParameterName);
|
||||
public static readonly SingleParameter ClientNoContextTakeOver = new(ClientNoContextTakeOverParameterName);
|
||||
|
||||
public static readonly AbsentableValueParameter<byte> ServerMaxWindowBits =
|
||||
new(ServerMaxWindowBitsParameterName, ValidateServerMaxWindowBitsParameterValue, 15);
|
||||
|
||||
public static readonly AbsentableValueParameter<byte> ClientMaxWindowBits =
|
||||
new(ClientMaxWindowBitsParameterName, ValidateClientMaxWindowBitsParameterValue, 15);
|
||||
|
||||
public static readonly IEnumerable<ExtensionParameter> AllAvailableParameters = new List<ExtensionParameter>
|
||||
{
|
||||
public const string ServerNoContextTakeOverParameterName = @"server_no_context_takeover";
|
||||
public const string ClientNoContextTakeOverParameterName = @"client_no_context_takeover";
|
||||
public const string ServerMaxWindowBitsParameterName = @"server_max_window_bits";
|
||||
public const string ClientMaxWindowBitsParameterName = @"client_max_window_bits";
|
||||
ServerNoContextTakeOver,
|
||||
ClientNoContextTakeOver,
|
||||
ServerMaxWindowBits,
|
||||
ClientMaxWindowBits
|
||||
};
|
||||
|
||||
public static readonly SingleParameter ServerNoContextTakeOver = new SingleParameter(ServerNoContextTakeOverParameterName);
|
||||
public static readonly SingleParameter ClientNoContextTakeOver = new SingleParameter(ClientNoContextTakeOverParameterName);
|
||||
public static readonly AbsentableValueParameter<byte> ServerMaxWindowBits = new AbsentableValueParameter<byte>(ServerMaxWindowBitsParameterName, ValidateServerMaxWindowBitsParameterValue, 15);
|
||||
public static readonly AbsentableValueParameter<byte> ClientMaxWindowBits = new AbsentableValueParameter<byte>(ClientMaxWindowBitsParameterName, ValidateClientMaxWindowBitsParameterValue, 15);
|
||||
public static readonly IEnumerable<string> AllAvailableParameterNames = AllAvailableParameters.Select(p => p.Name);
|
||||
|
||||
public static readonly IEnumerable<ExtensionParameter> AllAvailableParameters = new List<ExtensionParameter>()
|
||||
private static bool ValidateServerMaxWindowBitsParameterValue(string value)
|
||||
{
|
||||
// A client MAY include the "server_max_window_bits" extension parameter
|
||||
// in an extension negotiation offer. This parameter has a decimal
|
||||
// integer value without leading zeroes between 8 to 15, inclusive,
|
||||
// indicating the base-2 logarithm of the LZ77 sliding window size, and
|
||||
// MUST conform to the ABNF below.
|
||||
// server-max-window-bits = 1*DIGIT
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
ServerNoContextTakeOver,
|
||||
ClientNoContextTakeOver,
|
||||
ServerMaxWindowBits,
|
||||
ClientMaxWindowBits,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
public static readonly IEnumerable<string> AllAvailableParameterNames = AllAvailableParameters.Select(p => p.Name);
|
||||
|
||||
private static bool ValidateServerMaxWindowBitsParameterValue(string @value)
|
||||
var paramValue = -1;
|
||||
if (int.TryParse(value, out paramValue))
|
||||
{
|
||||
// A client MAY include the "server_max_window_bits" extension parameter
|
||||
// in an extension negotiation offer. This parameter has a decimal
|
||||
// integer value without leading zeroes between 8 to 15, inclusive,
|
||||
// indicating the base-2 logarithm of the LZ77 sliding window size, and
|
||||
// MUST conform to the ABNF below.
|
||||
// server-max-window-bits = 1*DIGIT
|
||||
|
||||
if (string.IsNullOrWhiteSpace(@value))
|
||||
if (8 <= paramValue && paramValue <= 15)
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int paramValue = -1;
|
||||
if (int.TryParse(@value, out paramValue))
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateClientMaxWindowBitsParameterValue(string value)
|
||||
{
|
||||
// A client MAY include the "client_max_window_bits" extension parameter
|
||||
// in an extension negotiation offer. This parameter has no value or a
|
||||
// decimal integer value without leading zeroes between 8 to 15
|
||||
// inclusive indicating the base-2 logarithm of the LZ77 sliding window
|
||||
// size. If a value is specified for this parameter, the value MUST
|
||||
// conform to the ABNF below.
|
||||
// client-max-window-bits = 1*DIGIT
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var paramValue = -1;
|
||||
if (int.TryParse(value, out paramValue))
|
||||
{
|
||||
if (8 <= paramValue && paramValue <= 15)
|
||||
{
|
||||
if (8 <= paramValue && paramValue <= 15)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ValidateParameter(string parameter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parameter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var keyValuePair = parameter.TrimStart().TrimEnd().Split('=');
|
||||
var inputParameterName = keyValuePair[0].TrimStart().TrimEnd();
|
||||
ExtensionParameter matchedParameter = null;
|
||||
|
||||
foreach (var param in AllAvailableParameters)
|
||||
{
|
||||
if (string.Compare(inputParameterName, param.Name, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
matchedParameter = param;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedParameter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (matchedParameter.ParameterType)
|
||||
{
|
||||
case ExtensionParameterType.Single:
|
||||
{
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateClientMaxWindowBitsParameterValue(string @value)
|
||||
{
|
||||
// A client MAY include the "client_max_window_bits" extension parameter
|
||||
// in an extension negotiation offer. This parameter has no value or a
|
||||
// decimal integer value without leading zeroes between 8 to 15
|
||||
// inclusive indicating the base-2 logarithm of the LZ77 sliding window
|
||||
// size. If a value is specified for this parameter, the value MUST
|
||||
// conform to the ABNF below.
|
||||
// client-max-window-bits = 1*DIGIT
|
||||
|
||||
if (string.IsNullOrWhiteSpace(@value))
|
||||
break;
|
||||
case ExtensionParameterType.Valuable:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (keyValuePair.Length != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int paramValue = -1;
|
||||
if (int.TryParse(@value, out paramValue))
|
||||
{
|
||||
if (8 <= paramValue && paramValue <= 15)
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
if (((ValuableParameter<byte>)matchedParameter).ValueValidator.Invoke(inputParameterValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ValidateParameter(string parameter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parameter))
|
||||
break;
|
||||
case ExtensionParameterType.Single | ExtensionParameterType.Valuable:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var keyValuePair = parameter.TrimStart().TrimEnd().Split('=');
|
||||
var inputParameterName = keyValuePair[0].TrimStart().TrimEnd();
|
||||
ExtensionParameter matchedParameter = null;
|
||||
|
||||
foreach (var @param in AllAvailableParameters)
|
||||
{
|
||||
if (string.Compare(inputParameterName, @param.Name, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
matchedParameter = @param;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyValuePair.Length > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
if (((AbsentableValueParameter<byte>)matchedParameter).ValueValidator.Invoke(inputParameterValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedParameter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (matchedParameter.ParameterType)
|
||||
{
|
||||
case ExtensionParameterType.Single:
|
||||
{
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ExtensionParameterType.Valuable:
|
||||
{
|
||||
if (keyValuePair.Length != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
if (((ValuableParameter<byte>)matchedParameter).ValueValidator.Invoke(inputParameterValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ExtensionParameterType.Single | ExtensionParameterType.Valuable:
|
||||
{
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyValuePair.Length > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
if (((AbsentableValueParameter<byte>)matchedParameter).ValueValidator.Invoke(inputParameterValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter type.");
|
||||
}
|
||||
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter type.");
|
||||
}
|
||||
|
||||
public static AgreedExtensionParameter ResolveParameter(string parameter)
|
||||
return false;
|
||||
}
|
||||
|
||||
public static AgreedExtensionParameter ResolveParameter(string parameter)
|
||||
{
|
||||
if (!ValidateParameter(parameter))
|
||||
{
|
||||
if (!ValidateParameter(parameter))
|
||||
return null;
|
||||
}
|
||||
|
||||
var keyValuePair = parameter.TrimStart().TrimEnd().Split('=');
|
||||
var inputParameterName = keyValuePair[0].TrimStart().TrimEnd();
|
||||
ExtensionParameter matchedParameter = null;
|
||||
|
||||
foreach (var param in AllAvailableParameters)
|
||||
{
|
||||
if (string.Compare(inputParameterName, param.Name, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return null;
|
||||
matchedParameter = param;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var keyValuePair = parameter.TrimStart().TrimEnd().Split('=');
|
||||
var inputParameterName = keyValuePair[0].TrimStart().TrimEnd();
|
||||
ExtensionParameter matchedParameter = null;
|
||||
|
||||
foreach (var @param in AllAvailableParameters)
|
||||
switch (matchedParameter.Name)
|
||||
{
|
||||
case ServerNoContextTakeOverParameterName:
|
||||
case ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
if (string.Compare(inputParameterName, @param.Name, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
return new AgreedSingleParameter(matchedParameter.Name);
|
||||
}
|
||||
case ServerMaxWindowBitsParameterName:
|
||||
case ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
matchedParameter = @param;
|
||||
break;
|
||||
return new AgreedValuableParameter<byte>(matchedParameter.Name,
|
||||
((AbsentableValueParameter<byte>)matchedParameter).DefaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
switch (matchedParameter.Name)
|
||||
{
|
||||
case ServerNoContextTakeOverParameterName:
|
||||
case ClientNoContextTakeOverParameterName:
|
||||
{
|
||||
return new AgreedSingleParameter(matchedParameter.Name);
|
||||
}
|
||||
case ServerMaxWindowBitsParameterName:
|
||||
case ClientMaxWindowBitsParameterName:
|
||||
{
|
||||
if (keyValuePair.Length == 1)
|
||||
{
|
||||
return new AgreedValuableParameter<byte>(matchedParameter.Name, ((AbsentableValueParameter<byte>)matchedParameter).DefaultValue);
|
||||
}
|
||||
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
return new AgreedValuableParameter<byte>(matchedParameter.Name, byte.Parse(inputParameterValue));
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter type.");
|
||||
var inputParameterValue = keyValuePair[1].TrimStart().TrimEnd();
|
||||
return new AgreedValuableParameter<byte>(matchedParameter.Name, byte.Parse(inputParameterValue));
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException("Invalid parameter type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace EonaCat.WebSockets.Extensions
|
||||
namespace EonaCat.WebSockets.Extensions;
|
||||
// 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 sealed class WebSocketExtensionOfferDescription
|
||||
{
|
||||
// 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 sealed class WebSocketExtensionOfferDescription
|
||||
public WebSocketExtensionOfferDescription(string offer)
|
||||
{
|
||||
public WebSocketExtensionOfferDescription(string offer)
|
||||
if (string.IsNullOrWhiteSpace(offer))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(offer))
|
||||
{
|
||||
throw new ArgumentNullException("offer");
|
||||
}
|
||||
|
||||
this.ExtensionNegotiationOffer = offer;
|
||||
throw new ArgumentNullException("offer");
|
||||
}
|
||||
|
||||
public string ExtensionNegotiationOffer { get; private set; }
|
||||
ExtensionNegotiationOffer = offer;
|
||||
}
|
||||
}
|
||||
|
||||
public string ExtensionNegotiationOffer { get; private set; }
|
||||
}
|
|
@ -1,46 +1,40 @@
|
|||
using System;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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 sealed class BinaryFragmentationFrame : Frame
|
||||
{
|
||||
// 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 sealed class BinaryFragmentationFrame : Frame
|
||||
public BinaryFragmentationFrame(OpCode opCode, byte[] data, int offset, int count, bool isFin = false,
|
||||
bool isMasked = true)
|
||||
{
|
||||
private OpCode _opCode;
|
||||
BufferValidator.ValidateBuffer(data, offset, count, "data");
|
||||
|
||||
public BinaryFragmentationFrame(OpCode opCode, byte[] data, int offset, int count, bool isFin = false, bool isMasked = true)
|
||||
{
|
||||
BufferValidator.ValidateBuffer(data, offset, count, "data");
|
||||
|
||||
_opCode = opCode;
|
||||
this.Data = data;
|
||||
this.Offset = offset;
|
||||
this.Count = count;
|
||||
this.IsFin = isFin;
|
||||
this.IsMasked = isMasked;
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public int Offset { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public bool IsFin { get; private set; }
|
||||
public bool IsMasked { get; private set; }
|
||||
|
||||
public override OpCode OpCode
|
||||
{
|
||||
get { return _opCode; }
|
||||
}
|
||||
|
||||
public byte[] ToArray(IFrameBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.EncodeFrame(this);
|
||||
}
|
||||
OpCode = opCode;
|
||||
Data = data;
|
||||
Offset = offset;
|
||||
Count = count;
|
||||
IsFin = isFin;
|
||||
IsMasked = isMasked;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public int Offset { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public bool IsFin { get; private set; }
|
||||
public bool IsMasked { get; private set; }
|
||||
|
||||
public override OpCode OpCode { get; }
|
||||
|
||||
public byte[] ToArray(IFrameBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.EncodeFrame(this);
|
||||
}
|
||||
}
|
|
@ -1,51 +1,46 @@
|
|||
using System;
|
||||
using EonaCat.WebSockets.Buffer;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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 sealed class BinaryFrame : DataFrame
|
||||
{
|
||||
// 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 sealed class BinaryFrame : DataFrame
|
||||
public BinaryFrame(ArraySegment<byte> segment, bool isMasked = true)
|
||||
{
|
||||
public BinaryFrame(ArraySegment<byte> segment, bool isMasked = true)
|
||||
{
|
||||
BufferValidator.ValidateArraySegment(segment, "segment");
|
||||
BufferValidator.ValidateArraySegment(segment, "segment");
|
||||
|
||||
this.Data = segment.Array;
|
||||
this.Offset = segment.Offset;
|
||||
this.Count = segment.Count;
|
||||
this.IsMasked = isMasked;
|
||||
}
|
||||
|
||||
public BinaryFrame(byte[] data, int offset, int count, bool isMasked = true)
|
||||
{
|
||||
BufferValidator.ValidateBuffer(data, offset, count, "data");
|
||||
|
||||
this.Data = data;
|
||||
this.Offset = offset;
|
||||
this.Count = count;
|
||||
this.IsMasked = isMasked;
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public int Offset { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public bool IsMasked { get; private set; }
|
||||
|
||||
public override OpCode OpCode
|
||||
{
|
||||
get { return OpCode.Binary; }
|
||||
}
|
||||
|
||||
public byte[] ToArray(IFrameBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.EncodeFrame(this);
|
||||
}
|
||||
Data = segment.Array;
|
||||
Offset = segment.Offset;
|
||||
Count = segment.Count;
|
||||
IsMasked = isMasked;
|
||||
}
|
||||
}
|
||||
|
||||
public BinaryFrame(byte[] data, int offset, int count, bool isMasked = true)
|
||||
{
|
||||
BufferValidator.ValidateBuffer(data, offset, count, "data");
|
||||
|
||||
Data = data;
|
||||
Offset = offset;
|
||||
Count = count;
|
||||
IsMasked = isMasked;
|
||||
}
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public int Offset { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public bool IsMasked { get; private set; }
|
||||
|
||||
public override OpCode OpCode => OpCode.Binary;
|
||||
|
||||
public byte[] ToArray(IFrameBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.EncodeFrame(this);
|
||||
}
|
||||
}
|
|
@ -1,23 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using EonaCat.WebSockets.Extensions;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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 interface IFrameBuilder
|
||||
{
|
||||
// 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.
|
||||
SortedList<int, IWebSocketExtension> NegotiatedExtensions { get; set; }
|
||||
|
||||
public interface IFrameBuilder
|
||||
{
|
||||
SortedList<int, IWebSocketExtension> NegotiatedExtensions { get; set; }
|
||||
byte[] EncodeFrame(PingFrame frame);
|
||||
byte[] EncodeFrame(PongFrame frame);
|
||||
byte[] EncodeFrame(CloseFrame frame);
|
||||
byte[] EncodeFrame(TextFrame frame);
|
||||
byte[] EncodeFrame(BinaryFrame frame);
|
||||
byte[] EncodeFrame(BinaryFragmentationFrame frame);
|
||||
|
||||
byte[] EncodeFrame(PingFrame frame);
|
||||
byte[] EncodeFrame(PongFrame frame);
|
||||
byte[] EncodeFrame(CloseFrame frame);
|
||||
byte[] EncodeFrame(TextFrame frame);
|
||||
byte[] EncodeFrame(BinaryFrame frame);
|
||||
byte[] EncodeFrame(BinaryFragmentationFrame frame);
|
||||
bool TryDecodeFrameHeader(byte[] buffer, int offset, int count, out Header frameHeader);
|
||||
|
||||
bool TryDecodeFrameHeader(byte[] buffer, int offset, int count, out Header frameHeader);
|
||||
void DecodePayload(byte[] buffer, int offset, Header frameHeader, out byte[] payload, out int payloadOffset, out int payloadCount);
|
||||
}
|
||||
}
|
||||
void DecodePayload(byte[] buffer, int offset, Header frameHeader, out byte[] payload, out int payloadOffset,
|
||||
out int payloadCount);
|
||||
}
|
|
@ -4,434 +4,419 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using EonaCat.WebSockets.Extensions;
|
||||
|
||||
namespace EonaCat.WebSockets
|
||||
namespace EonaCat.WebSockets;
|
||||
// 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.
|
||||
|
||||
// http://tools.ietf.org/html/rfc6455
|
||||
// This wire format for the data transfer part is described by the ABNF
|
||||
// [RFC5234] given in detail in this section. (Note that, unlike in
|
||||
// other sections of this document, the ABNF in this section is
|
||||
// operating on groups of bits. The length of each group of bits is
|
||||
// indicated in a comment. When encoded on the wire, the most
|
||||
// significant bit is the leftmost in the ABNF). A high-level overview
|
||||
// of the framing is given in the following figure. In a case of
|
||||
// conflict between the figure below and the ABNF specified later in
|
||||
// this section, the figure is authoritative.
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
// | |1|2|3| |K| | |
|
||||
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
// | Extended payload length continued, if payload len == 127 |
|
||||
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
// | |Masking-key, if MASK set to 1 |
|
||||
// +-------------------------------+-------------------------------+
|
||||
// | Masking-key (continued) | Payload Data |
|
||||
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
// : Payload Data continued ... :
|
||||
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
public class WebSocketFrameBuilder : IFrameBuilder
|
||||
{
|
||||
// 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.
|
||||
private static readonly byte[] EmptyArray = new byte[0];
|
||||
private static readonly Random _rng = new(DateTime.UtcNow.Millisecond);
|
||||
private static readonly int MaskingKeyLength = 4;
|
||||
|
||||
// http://tools.ietf.org/html/rfc6455
|
||||
// This wire format for the data transfer part is described by the ABNF
|
||||
// [RFC5234] given in detail in this section. (Note that, unlike in
|
||||
// other sections of this document, the ABNF in this section is
|
||||
// operating on groups of bits. The length of each group of bits is
|
||||
// indicated in a comment. When encoded on the wire, the most
|
||||
// significant bit is the leftmost in the ABNF). A high-level overview
|
||||
// of the framing is given in the following figure. In a case of
|
||||
// conflict between the figure below and the ABNF specified later in
|
||||
// this section, the figure is authoritative.
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
// | |1|2|3| |K| | |
|
||||
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
// | Extended payload length continued, if payload len == 127 |
|
||||
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
// | |Masking-key, if MASK set to 1 |
|
||||
// +-------------------------------+-------------------------------+
|
||||
// | Masking-key (continued) | Payload Data |
|
||||
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
// : Payload Data continued ... :
|
||||
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
public class WebSocketFrameBuilder : IFrameBuilder
|
||||
public SortedList<int, IWebSocketExtension> NegotiatedExtensions { get; set; }
|
||||
|
||||
public byte[] EncodeFrame(PingFrame frame)
|
||||
{
|
||||
private static readonly byte[] EmptyArray = new byte[0];
|
||||
private static readonly Random _rng = new Random(DateTime.UtcNow.Millisecond);
|
||||
private static readonly int MaskingKeyLength = 4;
|
||||
|
||||
public WebSocketFrameBuilder()
|
||||
if (!string.IsNullOrEmpty(frame.Data))
|
||||
{
|
||||
}
|
||||
|
||||
public SortedList<int, IWebSocketExtension> NegotiatedExtensions { get; set; }
|
||||
|
||||
public byte[] EncodeFrame(PingFrame frame)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(frame.Data))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(frame.Data);
|
||||
if (data.Length > 125)
|
||||
{
|
||||
throw new WebSocketException("All control frames must have a payload length of 125 bytes or less.");
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, data, 0, data.Length, isMasked: frame.IsMasked);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, isMasked: frame.IsMasked);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(PongFrame frame)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(frame.Data))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(frame.Data);
|
||||
if (data.Length > 125)
|
||||
{
|
||||
throw new WebSocketException("All control frames must have a payload length of 125 bytes or less.");
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, data, 0, data.Length, isMasked: frame.IsMasked);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, isMasked: frame.IsMasked);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(CloseFrame frame)
|
||||
{
|
||||
// The Close frame MAY contain a body (the "Application data" portion of
|
||||
// the frame) that indicates a reason for closing, such as an endpoint
|
||||
// shutting down, an endpoint having received a frame too large, or an
|
||||
// endpoint having received a frame that does not conform to the format
|
||||
// expected by the endpoint. If there is a body, the first two bytes of
|
||||
// the body MUST be a 2-byte unsigned integer (in network byte order)
|
||||
// representing a status code with value /code/ defined in Section 7.4.
|
||||
// Following the 2-byte integer, the body MAY contain UTF-8-encoded data
|
||||
// with value /reason/, the interpretation of which is not defined by
|
||||
// this specification. This data is not necessarily human readable but
|
||||
// may be useful for debugging or passing information relevant to the
|
||||
// script that opened the connection. As the data is not guaranteed to
|
||||
// be human readable, clients MUST NOT show it to end users.
|
||||
int payloadLength = (string.IsNullOrEmpty(frame.CloseReason) ? 0 : Encoding.UTF8.GetMaxByteCount(frame.CloseReason.Length)) + 2;
|
||||
if (payloadLength > 125)
|
||||
var data = Encoding.UTF8.GetBytes(frame.Data);
|
||||
if (data.Length > 125)
|
||||
{
|
||||
throw new WebSocketException("All control frames must have a payload length of 125 bytes or less.");
|
||||
}
|
||||
|
||||
byte[] payload = new byte[payloadLength];
|
||||
return Encode(frame.OpCode, data, 0, data.Length, frame.IsMasked);
|
||||
}
|
||||
|
||||
int higherByte = (int)frame.CloseCode / 256;
|
||||
int lowerByte = (int)frame.CloseCode % 256;
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, frame.IsMasked);
|
||||
}
|
||||
|
||||
payload[0] = (byte)higherByte;
|
||||
payload[1] = (byte)lowerByte;
|
||||
|
||||
if (!string.IsNullOrEmpty(frame.CloseReason))
|
||||
public byte[] EncodeFrame(PongFrame frame)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(frame.Data))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(frame.Data);
|
||||
if (data.Length > 125)
|
||||
{
|
||||
int count = Encoding.UTF8.GetBytes(frame.CloseReason, 0, frame.CloseReason.Length, payload, 2);
|
||||
return Encode(frame.OpCode, payload, 0, 2 + count, isMasked: frame.IsMasked);
|
||||
throw new WebSocketException("All control frames must have a payload length of 125 bytes or less.");
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, data, 0, data.Length, frame.IsMasked);
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, frame.IsMasked);
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(CloseFrame frame)
|
||||
{
|
||||
// The Close frame MAY contain a body (the "Application data" portion of
|
||||
// the frame) that indicates a reason for closing, such as an endpoint
|
||||
// shutting down, an endpoint having received a frame too large, or an
|
||||
// endpoint having received a frame that does not conform to the format
|
||||
// expected by the endpoint. If there is a body, the first two bytes of
|
||||
// the body MUST be a 2-byte unsigned integer (in network byte order)
|
||||
// representing a status code with value /code/ defined in Section 7.4.
|
||||
// Following the 2-byte integer, the body MAY contain UTF-8-encoded data
|
||||
// with value /reason/, the interpretation of which is not defined by
|
||||
// this specification. This data is not necessarily human readable but
|
||||
// may be useful for debugging or passing information relevant to the
|
||||
// script that opened the connection. As the data is not guaranteed to
|
||||
// be human readable, clients MUST NOT show it to end users.
|
||||
var payloadLength = (string.IsNullOrEmpty(frame.CloseReason)
|
||||
? 0
|
||||
: Encoding.UTF8.GetMaxByteCount(frame.CloseReason.Length)) + 2;
|
||||
if (payloadLength > 125)
|
||||
{
|
||||
throw new WebSocketException("All control frames must have a payload length of 125 bytes or less.");
|
||||
}
|
||||
|
||||
var payload = new byte[payloadLength];
|
||||
|
||||
var higherByte = (int)frame.CloseCode / 256;
|
||||
var lowerByte = (int)frame.CloseCode % 256;
|
||||
|
||||
payload[0] = (byte)higherByte;
|
||||
payload[1] = (byte)lowerByte;
|
||||
|
||||
if (!string.IsNullOrEmpty(frame.CloseReason))
|
||||
{
|
||||
var count = Encoding.UTF8.GetBytes(frame.CloseReason, 0, frame.CloseReason.Length, payload, 2);
|
||||
return Encode(frame.OpCode, payload, 0, 2 + count, frame.IsMasked);
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, payload, 0, payload.Length, frame.IsMasked);
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(TextFrame frame)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(frame.Text))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(frame.Text);
|
||||
return Encode(frame.OpCode, data, 0, data.Length, frame.IsMasked);
|
||||
}
|
||||
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, frame.IsMasked);
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(BinaryFrame frame)
|
||||
{
|
||||
return Encode(frame.OpCode, frame.Data, frame.Offset, frame.Count, frame.IsMasked);
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(BinaryFragmentationFrame frame)
|
||||
{
|
||||
return Encode(frame.OpCode, frame.Data, frame.Offset, frame.Count, frame.IsMasked, frame.IsFin);
|
||||
}
|
||||
|
||||
public bool TryDecodeFrameHeader(byte[] buffer, int offset, int count, out Header frameHeader)
|
||||
{
|
||||
frameHeader = DecodeFrameHeader(buffer, offset, count);
|
||||
return frameHeader != null;
|
||||
}
|
||||
|
||||
public void DecodePayload(byte[] buffer, int offset, Header frameHeader, out byte[] payload, out int payloadOffset,
|
||||
out int payloadCount)
|
||||
{
|
||||
payload = buffer;
|
||||
payloadOffset = offset + frameHeader.Length;
|
||||
payloadCount = frameHeader.PayloadLength;
|
||||
|
||||
if (frameHeader.IsMasked)
|
||||
{
|
||||
payload = new byte[payloadCount];
|
||||
|
||||
for (var i = 0; i < payloadCount; i++)
|
||||
payload[i] = (byte)(buffer[payloadOffset + i] ^
|
||||
buffer[offset + frameHeader.MaskingKeyOffset + i % MaskingKeyLength]);
|
||||
|
||||
payloadOffset = 0;
|
||||
payloadCount = payload.Length;
|
||||
}
|
||||
|
||||
// Payload data: (x+y) bytes
|
||||
// Extension data: x bytes
|
||||
// Application data: y bytes
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload length.
|
||||
if (NegotiatedExtensions != null)
|
||||
{
|
||||
byte[] bakedBuffer = null;
|
||||
foreach (var extension in NegotiatedExtensions.Reverse().Select(e => e.Value))
|
||||
{
|
||||
if (bakedBuffer == null)
|
||||
{
|
||||
bakedBuffer = extension.ProcessIncomingMessagePayload(payload, payloadOffset, payloadCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
bakedBuffer = extension.ProcessIncomingMessagePayload(bakedBuffer, 0, bakedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
payload = bakedBuffer;
|
||||
payloadOffset = 0;
|
||||
payloadCount = payload.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] Encode(OpCode opCode, byte[] payload, int offset, int count, bool isMasked = true, bool isFin = true)
|
||||
{
|
||||
// Payload data: (x+y) bytes
|
||||
// Extension data: x bytes
|
||||
// Application data: y bytes
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload length.
|
||||
if (NegotiatedExtensions != null)
|
||||
{
|
||||
byte[] bakedBuffer = null;
|
||||
foreach (var extension in NegotiatedExtensions.Values)
|
||||
{
|
||||
if (bakedBuffer == null)
|
||||
{
|
||||
bakedBuffer = extension.ProcessOutgoingMessagePayload(payload, offset, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
bakedBuffer = extension.ProcessOutgoingMessagePayload(bakedBuffer, 0, bakedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
payload = bakedBuffer;
|
||||
offset = 0;
|
||||
count = payload.Length;
|
||||
}
|
||||
|
||||
byte[] fragment;
|
||||
|
||||
// Payload length: 7 bits, 7+16 bits, or 7+64 bits.
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the
|
||||
// payload length. If 126, the following 2 bytes interpreted as a
|
||||
// 16-bit unsigned integer are the payload length. If 127, the
|
||||
// following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||||
// most significant bit MUST be 0) are the payload length.
|
||||
if (count < 126)
|
||||
{
|
||||
fragment = new byte[2 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = (byte)count;
|
||||
}
|
||||
else if (count < 65536)
|
||||
{
|
||||
fragment = new byte[2 + 2 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = 126;
|
||||
fragment[2] = (byte)(count / 256);
|
||||
fragment[3] = (byte)(count % 256);
|
||||
}
|
||||
else
|
||||
{
|
||||
fragment = new byte[2 + 8 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = 127;
|
||||
|
||||
var left = count;
|
||||
for (var i = 9; i > 1; i--)
|
||||
{
|
||||
fragment[i] = (byte)(left % 256);
|
||||
left = left / 256;
|
||||
|
||||
if (left == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIN: 1 bit
|
||||
// Indicates that this is the final fragment in a message. The first
|
||||
// fragment MAY also be the final fragment.
|
||||
if (isFin)
|
||||
{
|
||||
fragment[0] = 0x80;
|
||||
}
|
||||
|
||||
// RSV1, RSV2, RSV3: 1 bit each
|
||||
// MUST be 0 unless an extension is negotiated that defines meanings
|
||||
// for non-zero values. If a nonzero value is received and none of
|
||||
// the negotiated extensions defines the meaning of such a nonzero
|
||||
// value, the receiving endpoint MUST _Fail the WebSocket
|
||||
// Connection_.
|
||||
if (NegotiatedExtensions != null)
|
||||
{
|
||||
foreach (var extension in NegotiatedExtensions.Values)
|
||||
{
|
||||
if (extension.Rsv1BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x40);
|
||||
}
|
||||
|
||||
if (extension.Rsv2BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x20);
|
||||
}
|
||||
|
||||
if (extension.Rsv3BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opcode: 4 bits
|
||||
// Defines the interpretation of the "Payload data". If an unknown
|
||||
// opcode is received, the receiving endpoint MUST _Fail the
|
||||
// WebSocket Connection_. The following values are defined.
|
||||
fragment[0] = (byte)(fragment[0] | (byte)opCode);
|
||||
|
||||
// Mask: 1 bit
|
||||
// Defines whether the "Payload data" is masked. If set to 1, a
|
||||
// masking key is present in masking-key, and this is used to unmask
|
||||
// the "Payload data" as per Section 5.3. All frames sent from
|
||||
// client to server have this bit set to 1.
|
||||
if (isMasked)
|
||||
{
|
||||
fragment[1] = (byte)(fragment[1] | 0x80);
|
||||
}
|
||||
|
||||
// Masking-key: 0 or 4 bytes
|
||||
// All frames sent from the client to the server are masked by a
|
||||
// 32-bit value that is contained within the frame.
|
||||
// The masking key is a 32-bit value chosen at random by the client.
|
||||
// When preparing a masked frame, the client MUST pick a fresh masking
|
||||
// key from the set of allowed 32-bit values. The masking key needs to
|
||||
// be unpredictable; thus, the masking key MUST be derived from a strong
|
||||
// source of entropy, and the masking key for a given frame MUST NOT
|
||||
// make it simple for a server/proxy to predict the masking key for a
|
||||
// subsequent frame. The unpredictability of the masking key is
|
||||
// essential to prevent authors of malicious applications from selecting
|
||||
// the bytes that appear on the wire. RFC 4086 [RFC4086] discusses what
|
||||
// entails a suitable source of entropy for security-sensitive applications.
|
||||
if (isMasked)
|
||||
{
|
||||
var maskingKeyIndex = fragment.Length - (MaskingKeyLength + count);
|
||||
for (var i = maskingKeyIndex; i < maskingKeyIndex + MaskingKeyLength; i++)
|
||||
fragment[i] = (byte)_rng.Next(0, 255);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
var payloadIndex = fragment.Length - count;
|
||||
for (var i = 0; i < count; i++)
|
||||
fragment[payloadIndex + i] =
|
||||
(byte)(payload[offset + i] ^ fragment[maskingKeyIndex + i % MaskingKeyLength]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
var payloadIndex = fragment.Length - count;
|
||||
Array.Copy(payload, offset, fragment, payloadIndex, count);
|
||||
}
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private Header DecodeFrameHeader(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse fixed header
|
||||
var header = new Header
|
||||
{
|
||||
IsFIN = (buffer[offset + 0] & 0x80) == 0x80,
|
||||
IsRSV1 = (buffer[offset + 0] & 0x40) == 0x40,
|
||||
IsRSV2 = (buffer[offset + 0] & 0x20) == 0x20,
|
||||
IsRSV3 = (buffer[offset + 0] & 0x10) == 0x10,
|
||||
OpCode = (OpCode)(buffer[offset + 0] & 0x0f),
|
||||
IsMasked = (buffer[offset + 1] & 0x80) == 0x80,
|
||||
PayloadLength = buffer[offset + 1] & 0x7f,
|
||||
Length = 2
|
||||
};
|
||||
|
||||
// parse extended payload length
|
||||
if (header.PayloadLength >= 126)
|
||||
{
|
||||
if (header.PayloadLength == 126)
|
||||
{
|
||||
header.Length += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encode(frame.OpCode, payload, 0, payload.Length, isMasked: frame.IsMasked);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(TextFrame frame)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(frame.Text))
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(frame.Text);
|
||||
return Encode(frame.OpCode, data, 0, data.Length, isMasked: frame.IsMasked);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encode(frame.OpCode, EmptyArray, 0, 0, isMasked: frame.IsMasked);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(BinaryFrame frame)
|
||||
{
|
||||
return Encode(frame.OpCode, frame.Data, frame.Offset, frame.Count, isMasked: frame.IsMasked);
|
||||
}
|
||||
|
||||
public byte[] EncodeFrame(BinaryFragmentationFrame frame)
|
||||
{
|
||||
return Encode(frame.OpCode, frame.Data, frame.Offset, frame.Count, isMasked: frame.IsMasked, isFin: frame.IsFin);
|
||||
}
|
||||
|
||||
private byte[] Encode(OpCode opCode, byte[] payload, int offset, int count, bool isMasked = true, bool isFin = true)
|
||||
{
|
||||
// Payload data: (x+y) bytes
|
||||
// Extension data: x bytes
|
||||
// Application data: y bytes
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload length.
|
||||
if (this.NegotiatedExtensions != null)
|
||||
{
|
||||
byte[] bakedBuffer = null;
|
||||
foreach (var extension in this.NegotiatedExtensions.Values)
|
||||
{
|
||||
if (bakedBuffer == null)
|
||||
{
|
||||
bakedBuffer = extension.ProcessOutgoingMessagePayload(payload, offset, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
bakedBuffer = extension.ProcessOutgoingMessagePayload(bakedBuffer, 0, bakedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
payload = bakedBuffer;
|
||||
offset = 0;
|
||||
count = payload.Length;
|
||||
header.Length += 8;
|
||||
}
|
||||
|
||||
byte[] fragment;
|
||||
|
||||
// Payload length: 7 bits, 7+16 bits, or 7+64 bits.
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the
|
||||
// payload length. If 126, the following 2 bytes interpreted as a
|
||||
// 16-bit unsigned integer are the payload length. If 127, the
|
||||
// following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||||
// most significant bit MUST be 0) are the payload length.
|
||||
if (count < 126)
|
||||
{
|
||||
fragment = new byte[2 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = (byte)count;
|
||||
}
|
||||
else if (count < 65536)
|
||||
{
|
||||
fragment = new byte[2 + 2 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = (byte)126;
|
||||
fragment[2] = (byte)(count / 256);
|
||||
fragment[3] = (byte)(count % 256);
|
||||
}
|
||||
else
|
||||
{
|
||||
fragment = new byte[2 + 8 + (isMasked ? MaskingKeyLength : 0) + count];
|
||||
fragment[1] = (byte)127;
|
||||
|
||||
int left = count;
|
||||
for (int i = 9; i > 1; i--)
|
||||
{
|
||||
fragment[i] = (byte)(left % 256);
|
||||
left = left / 256;
|
||||
|
||||
if (left == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIN: 1 bit
|
||||
// Indicates that this is the final fragment in a message. The first
|
||||
// fragment MAY also be the final fragment.
|
||||
if (isFin)
|
||||
{
|
||||
fragment[0] = 0x80;
|
||||
}
|
||||
|
||||
// RSV1, RSV2, RSV3: 1 bit each
|
||||
// MUST be 0 unless an extension is negotiated that defines meanings
|
||||
// for non-zero values. If a nonzero value is received and none of
|
||||
// the negotiated extensions defines the meaning of such a nonzero
|
||||
// value, the receiving endpoint MUST _Fail the WebSocket
|
||||
// Connection_.
|
||||
if (this.NegotiatedExtensions != null)
|
||||
{
|
||||
foreach (var extension in this.NegotiatedExtensions.Values)
|
||||
{
|
||||
if (extension.Rsv1BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x40);
|
||||
}
|
||||
|
||||
if (extension.Rsv2BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x20);
|
||||
}
|
||||
|
||||
if (extension.Rsv3BitOccupied)
|
||||
{
|
||||
fragment[0] = (byte)(fragment[0] | 0x10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opcode: 4 bits
|
||||
// Defines the interpretation of the "Payload data". If an unknown
|
||||
// opcode is received, the receiving endpoint MUST _Fail the
|
||||
// WebSocket Connection_. The following values are defined.
|
||||
fragment[0] = (byte)(fragment[0] | (byte)opCode);
|
||||
|
||||
// Mask: 1 bit
|
||||
// Defines whether the "Payload data" is masked. If set to 1, a
|
||||
// masking key is present in masking-key, and this is used to unmask
|
||||
// the "Payload data" as per Section 5.3. All frames sent from
|
||||
// client to server have this bit set to 1.
|
||||
if (isMasked)
|
||||
{
|
||||
fragment[1] = (byte)(fragment[1] | 0x80);
|
||||
}
|
||||
|
||||
// Masking-key: 0 or 4 bytes
|
||||
// All frames sent from the client to the server are masked by a
|
||||
// 32-bit value that is contained within the frame.
|
||||
// The masking key is a 32-bit value chosen at random by the client.
|
||||
// When preparing a masked frame, the client MUST pick a fresh masking
|
||||
// key from the set of allowed 32-bit values. The masking key needs to
|
||||
// be unpredictable; thus, the masking key MUST be derived from a strong
|
||||
// source of entropy, and the masking key for a given frame MUST NOT
|
||||
// make it simple for a server/proxy to predict the masking key for a
|
||||
// subsequent frame. The unpredictability of the masking key is
|
||||
// essential to prevent authors of malicious applications from selecting
|
||||
// the bytes that appear on the wire. RFC 4086 [RFC4086] discusses what
|
||||
// entails a suitable source of entropy for security-sensitive applications.
|
||||
if (isMasked)
|
||||
{
|
||||
int maskingKeyIndex = fragment.Length - (MaskingKeyLength + count);
|
||||
for (var i = maskingKeyIndex; i < maskingKeyIndex + MaskingKeyLength; i++)
|
||||
{
|
||||
fragment[i] = (byte)_rng.Next(0, 255);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
int payloadIndex = fragment.Length - count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
fragment[payloadIndex + i] = (byte)(payload[offset + i] ^ fragment[maskingKeyIndex + i % MaskingKeyLength]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
int payloadIndex = fragment.Length - count;
|
||||
Array.Copy(payload, offset, fragment, payloadIndex, count);
|
||||
}
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public bool TryDecodeFrameHeader(byte[] buffer, int offset, int count, out Header frameHeader)
|
||||
{
|
||||
frameHeader = DecodeFrameHeader(buffer, offset, count);
|
||||
return frameHeader != null;
|
||||
}
|
||||
|
||||
private Header DecodeFrameHeader(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count < 2)
|
||||
if (count < header.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse fixed header
|
||||
var header = new Header()
|
||||
if (header.PayloadLength == 126)
|
||||
{
|
||||
IsFIN = ((buffer[offset + 0] & 0x80) == 0x80),
|
||||
IsRSV1 = ((buffer[offset + 0] & 0x40) == 0x40),
|
||||
IsRSV2 = ((buffer[offset + 0] & 0x20) == 0x20),
|
||||
IsRSV3 = ((buffer[offset + 0] & 0x10) == 0x10),
|
||||
OpCode = (OpCode)(buffer[offset + 0] & 0x0f),
|
||||
IsMasked = ((buffer[offset + 1] & 0x80) == 0x80),
|
||||
PayloadLength = (buffer[offset + 1] & 0x7f),
|
||||
Length = 2,
|
||||
};
|
||||
|
||||
// parse extended payload length
|
||||
if (header.PayloadLength >= 126)
|
||||
{
|
||||
if (header.PayloadLength == 126)
|
||||
{
|
||||
header.Length += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Length += 8;
|
||||
}
|
||||
|
||||
if (count < header.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (header.PayloadLength == 126)
|
||||
{
|
||||
header.PayloadLength = buffer[offset + 2] * 256 + buffer[offset + 3];
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalLength = 0;
|
||||
int level = 1;
|
||||
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
totalLength += buffer[offset + i + 2] * level;
|
||||
level *= 256;
|
||||
}
|
||||
|
||||
header.PayloadLength = totalLength;
|
||||
}
|
||||
header.PayloadLength = buffer[offset + 2] * 256 + buffer[offset + 3];
|
||||
}
|
||||
|
||||
// parse masking key
|
||||
if (header.IsMasked)
|
||||
else
|
||||
{
|
||||
if (count < header.Length + MaskingKeyLength)
|
||||
var totalLength = 0;
|
||||
var level = 1;
|
||||
|
||||
for (var i = 7; i >= 0; i--)
|
||||
{
|
||||
return null;
|
||||
totalLength += buffer[offset + i + 2] * level;
|
||||
level *= 256;
|
||||
}
|
||||
|
||||
header.MaskingKeyOffset = header.Length;
|
||||
header.Length += MaskingKeyLength;
|
||||
header.PayloadLength = totalLength;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
public void DecodePayload(byte[] buffer, int offset, Header frameHeader, out byte[] payload, out int payloadOffset, out int payloadCount)
|
||||
// parse masking key
|
||||
if (header.IsMasked)
|
||||
{
|
||||
payload = buffer;
|
||||
payloadOffset = offset + frameHeader.Length;
|
||||
payloadCount = frameHeader.PayloadLength;
|
||||
|
||||
if (frameHeader.IsMasked)
|
||||
if (count < header.Length + MaskingKeyLength)
|
||||
{
|
||||
payload = new byte[payloadCount];
|
||||
|
||||
for (var i = 0; i < payloadCount; i++)
|
||||
{
|
||||
payload[i] = (byte)(buffer[payloadOffset + i] ^ buffer[offset + frameHeader.MaskingKeyOffset + i % MaskingKeyLength]);
|
||||
}
|
||||
|
||||
payloadOffset = 0;
|
||||
payloadCount = payload.Length;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Payload data: (x+y) bytes
|
||||
// Extension data: x bytes
|
||||
// Application data: y bytes
|
||||
// The "Extension data" is 0 bytes unless an extension has been
|
||||
// negotiated. Any extension MUST specify the length of the
|
||||
// "Extension data", or how that length may be calculated, and how
|
||||
// the extension use MUST be negotiated during the opening handshake.
|
||||
// If present, the "Extension data" is included in the total payload length.
|
||||
if (this.NegotiatedExtensions != null)
|
||||
{
|
||||
byte[] bakedBuffer = null;
|
||||
foreach (var extension in this.NegotiatedExtensions.Reverse().Select(e => e.Value))
|
||||
{
|
||||
if (bakedBuffer == null)
|
||||
{
|
||||
bakedBuffer = extension.ProcessIncomingMessagePayload(payload, payloadOffset, payloadCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
bakedBuffer = extension.ProcessIncomingMessagePayload(bakedBuffer, 0, bakedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
payload = bakedBuffer;
|
||||
payloadOffset = 0;
|
||||
payloadCount = payload.Length;
|
||||
}
|
||||
header.MaskingKeyOffset = header.Length;
|
||||
header.Length += MaskingKeyLength;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue