From b98566efb3bb16ac05a721afbd67ae6b0a7776d4 Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 00:27:42 +0200 Subject: [PATCH 1/7] AcceptQuicClient --- QuicNet/Connections/QuicConnection.cs | 2 + QuicNet/QuicListener.cs | 55 ++++++++++++++++--------- QuickNet.Tests.ConsoleServer/Program.cs | 12 +++++- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/QuicNet/Connections/QuicConnection.cs b/QuicNet/Connections/QuicConnection.cs index df36de9..3dc96a7 100644 --- a/QuicNet/Connections/QuicConnection.cs +++ b/QuicNet/Connections/QuicConnection.cs @@ -6,6 +6,8 @@ using QuicNet.Streams; using System; using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; namespace QuicNet.Connections { diff --git a/QuicNet/QuicListener.cs b/QuicNet/QuicListener.cs index b7f9aef..1d57b73 100644 --- a/QuicNet/QuicListener.cs +++ b/QuicNet/QuicListener.cs @@ -27,8 +27,6 @@ public class QuicListener private int _port; private bool _started; - public event Action OnClientConnected; - public QuicListener(int port) { _started = false; @@ -42,7 +40,6 @@ public void Start() { _client = new UdpClient(_port); _started = true; - Receive(); } public void Close() @@ -50,11 +47,33 @@ public void Close() _client.Close(); } - private void Receive() + public QuicConnection AcceptQuicClient() { if (!_started) throw new QuicListenerNotStartedException("Please call the Start() method before receving data."); - + + /* + * Wait until there is initial packet incomming. + * Otherwise we still need to orchestrate any other protocol or data pakcets. + * */ + while (true) + { + IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, _port); + byte[] data = _client.Receive(ref endpoint); + + Packet packet = _unpacker.Unpack(data); + if (packet is InitialPacket) + { + QuicConnection connection = ProcessInitialPacket(packet, endpoint); + return connection; + } + + OrchestratePacket(packet); + } + } + + private void Receive() + { while (true) { IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, _port); @@ -67,24 +86,21 @@ private void Receive() continue; // TODO: Validate packet before dispatching - ProcessPacket(packet, endpoint); + OrchestratePacket(packet); } } - private void ProcessPacket(Packet packet, IPEndPoint endPoint) + private void OrchestratePacket(Packet packet) { - if (packet is InitialPacket) - { - ProcessInitialPacket(packet, endPoint); - } - else if (packet is ShortHeaderPacket) + if (packet is ShortHeaderPacket) { ProcessShortHeaderPacket(packet); } } - private void ProcessInitialPacket(Packet packet, IPEndPoint endPoint) + private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint) { + QuicConnection result = null; UInt32 availableConnectionId; byte[] data; // Unsupported version. Version negotiation packet is sent only on initial connection. All other packets are dropped. (5.2.2 / 16th draft) @@ -94,7 +110,7 @@ private void ProcessInitialPacket(Packet packet, IPEndPoint endPoint) data = vnp.Encode(); _client.Send(data, data.Length, endPoint); - return; + return null; } InitialPacket cast = packet as InitialPacket; @@ -112,6 +128,9 @@ private void ProcessInitialPacket(Packet packet, IPEndPoint endPoint) // 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 { @@ -124,13 +143,9 @@ private void ProcessInitialPacket(Packet packet, IPEndPoint endPoint) data = ip.Encode(); int dataSent = _client.Send(data, data.Length, endPoint); if (dataSent > 0) - { - // Create a QuicContext to represent the connected client. - QuicContext context = new QuicContext(_client, endPoint); - ConnectionPool.AttachContext(ip.SourceConnectionId, context); + return result; - OnClientConnected?.Invoke(context); - } + return null; } private void ProcessShortHeaderPacket(Packet packet) diff --git a/QuickNet.Tests.ConsoleServer/Program.cs b/QuickNet.Tests.ConsoleServer/Program.cs index 3ba4226..e405933 100644 --- a/QuickNet.Tests.ConsoleServer/Program.cs +++ b/QuickNet.Tests.ConsoleServer/Program.cs @@ -1,5 +1,6 @@ using QuickNet.Utilities; using QuicNet; +using QuicNet.Connections; using QuicNet.Context; using QuicNet.Infrastructure; using QuicNet.Infrastructure.Frames; @@ -15,6 +16,15 @@ namespace QuickNet.Tests.ConsoleServer { class Program { + static void Example() + { + QuicListener listener = new QuicListener(11000); + while (true) + { + QuicConnection client = listener.AcceptQuicClient(); + } + } + static void Main(string[] args) { byte[] bytes = new VariableInteger(12345); @@ -62,7 +72,7 @@ static void Main(string[] args) StreamId streamId = streamIdData; QuicListener listener = new QuicListener(11000); - listener.OnClientConnected += Listener_OnClientConnected; + // listener.OnClientConnected += Listener_OnClientConnected; listener.Start(); } From b15645ac6b381a649be39276d814cbcae41b19fa Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 18:05:51 +0200 Subject: [PATCH 2/7] Major changes on the API. --- QuicNet.Tests.ConsoleClient/Program.cs | 19 ++- QuicNet/Connections/ConnectionPool.cs | 20 +--- QuicNet/Connections/QuicConnection.cs | 111 ++++++++++++++---- QuicNet/Context/QuicContext.cs | 83 ------------- QuicNet/Context/QuicStreamContext.cs | 13 +- .../InternalInfrastructure/ConnectionData.cs | 24 ++++ .../PacketWireTransfer.cs | 54 +++++++++ QuicNet/QuicClient.cs | 40 ++----- QuicNet/QuicListener.cs | 18 ++- QuicNet/QuicNet.csproj | 3 +- QuicNet/Streams/QuicStream.cs | 35 ++++-- QuickNet.Tests.ConsoleServer/Program.cs | 25 +++- 12 files changed, 264 insertions(+), 181 deletions(-) delete mode 100644 QuicNet/Context/QuicContext.cs create mode 100644 QuicNet/InternalInfrastructure/ConnectionData.cs create mode 100644 QuicNet/InternalInfrastructure/PacketWireTransfer.cs diff --git a/QuicNet.Tests.ConsoleClient/Program.cs b/QuicNet.Tests.ConsoleClient/Program.cs index 78eca92..64897df 100644 --- a/QuicNet.Tests.ConsoleClient/Program.cs +++ b/QuicNet.Tests.ConsoleClient/Program.cs @@ -1,4 +1,6 @@ +using QuicNet.Connections; using QuicNet.Context; +using QuicNet.Streams; using System; using System.Collections.Generic; using System.Linq; @@ -11,12 +13,21 @@ class Program { static void Main(string[] args) { + Console.WriteLine("Starting client."); QuicClient client = new QuicClient(); - QuicContext context = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) - QuicStreamContext sc = client.CreateStream(); // Create a data stream - sc.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data + Console.WriteLine("Connecting to server."); + QuicConnection connection = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) + Console.WriteLine("Connected"); + + QuicStream stream = connection.CreateStream(); // Create a data stream + Console.WriteLine("Create stream with id: " + stream.StreamId.Value.ToString()); - sc.Close(); // Close the stream after processing + Console.WriteLine("Send 'Hello From Client!'"); + stream.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data + Console.WriteLine("Waiting for message from the server"); + byte[] data = stream.Receive(); // Receive from server + Console.WriteLine("Received: " + Encoding.UTF8.GetString(data)); + Console.ReadKey(); } } } diff --git a/QuicNet/Connections/ConnectionPool.cs b/QuicNet/Connections/ConnectionPool.cs index 3568772..2750398 100644 --- a/QuicNet/Connections/ConnectionPool.cs +++ b/QuicNet/Connections/ConnectionPool.cs @@ -1,6 +1,7 @@ using QuicNet.Context; using QuicNet.Infrastructure; using QuicNet.Infrastructure.Settings; +using QuicNet.InternalInfrastructure; using System; using System.Collections.Generic; using System.Text; @@ -11,7 +12,7 @@ namespace QuicNet.Connections /// Since UDP is a stateless protocol, the ConnectionPool is used as a Conenction Manager to /// route packets to the right "Connection". /// - public static class ConnectionPool + internal static class ConnectionPool { /// /// Starting point for connection identifiers. @@ -30,11 +31,11 @@ public static class ConnectionPool /// /// Connection Id /// - public static bool AddConnection(UInt32 id, out UInt32 availableConnectionId) + public static bool AddConnection(ConnectionData connection, out UInt32 availableConnectionId) { availableConnectionId = 0; - if (_pool.ContainsKey(id)) + if (_pool.ContainsKey(connection.ConnectionId)) return false; if (_pool.Count > QuicSettings.MaximumConnectionIds) @@ -42,7 +43,8 @@ public static bool AddConnection(UInt32 id, out UInt32 availableConnectionId) availableConnectionId = _ns.Get(); - _pool.Add(availableConnectionId, new QuicConnection(availableConnectionId, availableConnectionId)); + connection.PeerConnectionId = connection.ConnectionId; + _pool.Add(availableConnectionId, new QuicConnection(connection)); return true; } @@ -54,15 +56,5 @@ public static QuicConnection Find(UInt32 id) return _pool[id]; } - - public static bool AttachContext(UInt32 id, QuicContext context) - { - if (_pool.ContainsKey(id) == false) - return false; - - _pool[id].AttachContext(context); - - return true; - } } } diff --git a/QuicNet/Connections/QuicConnection.cs b/QuicNet/Connections/QuicConnection.cs index 3dc96a7..f0b8c85 100644 --- a/QuicNet/Connections/QuicConnection.cs +++ b/QuicNet/Connections/QuicConnection.cs @@ -3,6 +3,7 @@ using QuicNet.Infrastructure.PacketProcessing; using QuicNet.Infrastructure.Packets; using QuicNet.Infrastructure.Settings; +using QuicNet.InternalInfrastructure; using QuicNet.Streams; using System; using System.Collections.Generic; @@ -13,35 +14,27 @@ namespace QuicNet.Connections { public class QuicConnection { + private UInt64 _currentTransferRate; + private ConnectionState _state; + private Dictionary _streams; + + private PacketWireTransfer _pwt; + public UInt32 ConnectionId { get; private set; } public UInt32 PeerConnectionId { get; private set; } - public QuicContext Context { get; private set; } + public PacketCreator PacketCreator { get; private set; } public UInt64 MaxData { get; private set; } public UInt64 MaxStreams { get; private set; } - private UInt64 _currentTransferRate; - private ConnectionState _state; - private Dictionary _streams; - - public QuicConnection(UInt32 id, UInt32 peerConnectionId) - { - _currentTransferRate = 0; - _state = ConnectionState.Open; - _streams = new Dictionary(); - - ConnectionId = id; - PeerConnectionId = peerConnectionId; - // Also creates a new number space - PacketCreator = new PacketCreator(ConnectionId, PeerConnectionId); - MaxData = QuicSettings.MaxData; - MaxStreams = QuicSettings.MaximumStreamId; - } + public event Action OnDataReceived; - public void AttachContext(QuicContext context) + public QuicStream CreateStream() { - Context = context; - Context.Connection = this; + QuicStream stream = new QuicStream(this, new QuickNet.Utilities.StreamId(0, QuickNet.Utilities.StreamType.ClientBidirectional)); + _streams.Add(0, stream); + + return stream; } public void ProcessFrames(List frames) @@ -129,6 +122,48 @@ private void OnMaxStreamFrame(Frame frame) MaxStreams = msf.MaximumStreams.Value; } + #region Internal + + internal QuicConnection(ConnectionData connection) + { + _currentTransferRate = 0; + _state = ConnectionState.Open; + _streams = new Dictionary(); + _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; + } + + /// + /// Client only! + /// + /// + internal void ReceivePacket() + { + Packet packet = _pwt.ReadPacket(); + + if (packet is ShortHeaderPacket) + { + ShortHeaderPacket shp = (ShortHeaderPacket)packet; + ProcessFrames(shp.GetFrames()); + } + } + + internal bool SendData(Packet packet) + { + return _pwt.SendPacket(packet); + } + + internal void DataReceived(QuicStream context) + { + OnDataReceived?.Invoke(context); + } + internal void TerminateConnection() { _state = ConnectionState.Draining; @@ -137,7 +172,39 @@ internal void TerminateConnection() internal void SendMaximumStreamReachedError() { ShortHeaderPacket packet = PacketCreator.CreateConnectionClosePacket(Infrastructure.ErrorCode.STREAM_LIMIT_ERROR, "Maximum number of streams reached."); - Context.Send(packet); + Send(packet); + } + + /// + /// Used to send protocol packets to the peer. + /// + /// + /// + 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, "Maximum data transfer reached."); + + TerminateConnection(); + } + + // Ignore empty packets + if (data == null || data.Length <= 0) + return true; + + bool result = _pwt.SendPacket(packet); + + return result; } + + #endregion } } diff --git a/QuicNet/Context/QuicContext.cs b/QuicNet/Context/QuicContext.cs deleted file mode 100644 index 2e81fdf..0000000 --- a/QuicNet/Context/QuicContext.cs +++ /dev/null @@ -1,83 +0,0 @@ -using QuickNet.Utilities; -using QuicNet.Connections; -using QuicNet.Infrastructure.Packets; -using QuicNet.Streams; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; - -namespace QuicNet.Context -{ - /// - /// Wrapper of the UdpClient to represent the Connection. - /// - public class QuicContext - { - private UdpClient _client; - - public IPEndPoint Endpoint { get; } - public bool IsConnectionClosed { get; private set; } - public event Action OnDataReceived; - - #region Internal - - internal QuicConnection Connection { get; set; } - - /// - /// Internal constructor to prevent creating the context outside the scope of Quic. - /// - /// - /// - internal QuicContext(UdpClient client, IPEndPoint endpoint) - { - _client = client; - Endpoint = endpoint; - } - - internal void DataReceived(QuicStreamContext context) - { - OnDataReceived?.Invoke(context); - } - - /// - /// Used to send protocol packets to the peer. - /// - /// - /// - internal bool Send(Packet packet) - { - // Encode the packet - byte[] data = packet.Encode(); - - // Increment the connection transfer rate - Connection.IncrementRate(data.Length); - - // If the maximum transfer rate is reached, send FLOW_CONTROL_ERROR - if (Connection.MaximumReached()) - { - packet = Connection.PacketCreator.CreateConnectionClosePacket(Infrastructure.ErrorCode.FLOW_CONTROL_ERROR, "Maximum data transfer reached."); - data = packet.Encode(); - - Connection.TerminateConnection(); - - return false; - } - - // Ignore empty packets - if (data == null || data.Length <= 0) - return true; - - int result = _client.Send(data, data.Length, Endpoint); - if (result <= 0) - return false; - - return true; - } - - #endregion - } -} diff --git a/QuicNet/Context/QuicStreamContext.cs b/QuicNet/Context/QuicStreamContext.cs index a7b9af0..563a699 100644 --- a/QuicNet/Context/QuicStreamContext.cs +++ b/QuicNet/Context/QuicStreamContext.cs @@ -17,7 +17,7 @@ public class QuicStreamContext /// /// The connection's context. /// - public QuicContext ConnectionContext { get; set; } + // public QuicContext ConnectionContext { get; set; } /// /// Data received @@ -43,11 +43,13 @@ public bool Send(byte[] data) if (data == null || data.Length <= 0) return true; - Packet packet = ConnectionContext.Connection.PacketCreator.CreateDataPacket(StreamId, data); + // Packet packet = ConnectionContext.Connection.PacketCreator.CreateDataPacket(StreamId, data); - bool result = ConnectionContext.Send(packet); + // bool result = ConnectionContext.Send(packet); - return result; + //return result; + + return false; } public void Close() @@ -65,11 +67,10 @@ public void Close() /// /// /// - internal QuicStreamContext(QuicStream stream, QuicContext context) + internal QuicStreamContext(QuicStream stream) { Stream = stream; StreamId = stream.StreamId; - ConnectionContext = context; } internal void SetData(byte[] data) diff --git a/QuicNet/InternalInfrastructure/ConnectionData.cs b/QuicNet/InternalInfrastructure/ConnectionData.cs new file mode 100644 index 0000000..a2bc012 --- /dev/null +++ b/QuicNet/InternalInfrastructure/ConnectionData.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace QuicNet.InternalInfrastructure +{ + internal class ConnectionData + { + public PacketWireTransfer PWT { get; set; } + public UInt32 ConnectionId { get; set; } + public UInt32 PeerConnectionId { get; set; } + + public ConnectionData(PacketWireTransfer pwt, UInt32 connectionId, UInt32 peerConnnectionId) + { + PWT = pwt; + ConnectionId = connectionId; + PeerConnectionId = peerConnnectionId; + } + } +} diff --git a/QuicNet/InternalInfrastructure/PacketWireTransfer.cs b/QuicNet/InternalInfrastructure/PacketWireTransfer.cs new file mode 100644 index 0000000..3b68e77 --- /dev/null +++ b/QuicNet/InternalInfrastructure/PacketWireTransfer.cs @@ -0,0 +1,54 @@ +using QuicNet.Exceptions; +using QuicNet.Infrastructure.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace QuicNet.InternalInfrastructure +{ + internal class PacketWireTransfer + { + private UdpClient _client; + private IPEndPoint _peerEndpoint; + + private Unpacker _unpacker; + + public PacketWireTransfer(UdpClient client, IPEndPoint peerEndpoint) + { + _client = client; + _peerEndpoint = peerEndpoint; + + _unpacker = new Unpacker(); + } + + public Packet ReadPacket() + { + // Await response for sucessfull connection creation by the server + byte[] peerData = _client.Receive(ref _peerEndpoint); + if (peerData == null) + throw new QuicConnectivityException("Server did not respond properly."); + + Packet packet = _unpacker.Unpack(peerData); + + return packet; + } + + public bool SendPacket(Packet packet) + { + byte[] data = packet.Encode(); + + int sent = _client.Send(data, data.Length, _peerEndpoint); + + return sent > 0; + } + + public IPEndPoint LastTransferEndpoint() + { + return _peerEndpoint; + } + } +} diff --git a/QuicNet/QuicClient.cs b/QuicNet/QuicClient.cs index f36565f..e633d81 100644 --- a/QuicNet/QuicClient.cs +++ b/QuicNet/QuicClient.cs @@ -6,6 +6,7 @@ using QuicNet.Infrastructure.PacketProcessing; using QuicNet.Infrastructure.Packets; using QuicNet.Infrastructure.Settings; +using QuicNet.InternalInfrastructure; using QuicNet.Streams; using System; using System.Collections.Generic; @@ -29,6 +30,7 @@ public class QuicClient private InitialPacketCreator _packetCreator; private UInt64 _maximumStreams = QuicSettings.MaximumStreamId; + private PacketWireTransfer _pwt; public QuicClient() { @@ -37,46 +39,27 @@ public QuicClient() _packetCreator = new InitialPacketCreator(); } - public QuicContext Connect(string ip, int port) + public QuicConnection Connect(string ip, int port) { // Establish socket connection _peerIp = new IPEndPoint(IPAddress.Parse(ip), port); + // Initialize packet reader + _pwt = new PacketWireTransfer(_client, _peerIp); + // Start initial protocol process InitialPacket connectionPacket = _packetCreator.CreateInitialPacket(0, 0); - byte[] data = connectionPacket.Encode(); // Send the initial packet - _client.Send(data, data.Length, _peerIp); + _pwt.SendPacket(connectionPacket); // Await response for sucessfull connection creation by the server - byte[] peerData = _client.Receive(ref _peerIp); - if (peerData == null) - throw new QuicConnectivityException("Server did not respond properly."); - - Packet packet = _unpacker.Unpack(peerData); - if ((packet is InitialPacket) == false) - throw new QuicConnectivityException("Server did not respond properly."); - - InitialPacket ini = (InitialPacket)packet; + InitialPacket packet = (InitialPacket)_pwt.ReadPacket(); HandleInitialFrames(packet); - EstablishConnection(ini.SourceConnectionId, ini.SourceConnectionId); - - // Create the QuicContext - QuicContext context = new QuicContext(_client, _peerIp); + EstablishConnection(packet.SourceConnectionId, packet.SourceConnectionId); - // Cross reference with Connection - _connection.AttachContext(context); - - return context; - } - - public QuicStreamContext CreateStream() - { - QuicStream stream = new QuicStream(_connection, new StreamId(1, StreamType.ClientBidirectional)); - - return stream.Context; + return _connection; } private void HandleInitialFrames(Packet packet) @@ -106,7 +89,8 @@ private void HandleInitialFrames(Packet packet) private void EstablishConnection(UInt32 connectionId, UInt32 peerConnectionId) { - _connection = new QuicConnection(connectionId, peerConnectionId); + ConnectionData connection = new ConnectionData(_pwt, connectionId, peerConnectionId); + _connection = new QuicConnection(connection); } } } diff --git a/QuicNet/QuicListener.cs b/QuicNet/QuicListener.cs index 1d57b73..88f55bb 100644 --- a/QuicNet/QuicListener.cs +++ b/QuicNet/QuicListener.cs @@ -7,6 +7,7 @@ using QuicNet.Infrastructure.PacketProcessing; using QuicNet.Infrastructure.Packets; using QuicNet.Infrastructure.Settings; +using QuicNet.InternalInfrastructure; using System; using System.Collections.Generic; using System.Linq; @@ -24,6 +25,8 @@ public class QuicListener private Unpacker _unpacker; private InitialPacketCreator _packetCreator; + private PacketWireTransfer _pwt; + private int _port; private bool _started; @@ -40,6 +43,7 @@ public void Start() { _client = new UdpClient(_port); _started = true; + _pwt = new PacketWireTransfer(_client, null); } public void Close() @@ -58,13 +62,10 @@ public QuicConnection AcceptQuicClient() * */ while (true) { - IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, _port); - byte[] data = _client.Receive(ref endpoint); - - Packet packet = _unpacker.Unpack(data); + Packet packet = _pwt.ReadPacket(); if (packet is InitialPacket) { - QuicConnection connection = ProcessInitialPacket(packet, endpoint); + QuicConnection connection = ProcessInitialPacket(packet, _pwt.LastTransferEndpoint()); return connection; } @@ -76,10 +77,7 @@ private void Receive() { while (true) { - IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, _port); - byte[] data = _client.Receive(ref endpoint); - - Packet packet = _unpacker.Unpack(data); + Packet packet = _pwt.ReadPacket(); // Discard unknown packets if (packet == null) @@ -121,7 +119,7 @@ private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint) { ip.AttachFrame(new ConnectionCloseFrame(ErrorCode.PROTOCOL_VIOLATION, "PMTU have not been reached.")); } - else if (ConnectionPool.AddConnection(cast.SourceConnectionId, out availableConnectionId) == true) + else if (ConnectionPool.AddConnection(new ConnectionData(_pwt, cast.SourceConnectionId, 0), out availableConnectionId) == true) { // Tell the peer the available connection id ip.SourceConnectionId = (byte)availableConnectionId; diff --git a/QuicNet/QuicNet.csproj b/QuicNet/QuicNet.csproj index a0d92af..4042e72 100644 --- a/QuicNet/QuicNet.csproj +++ b/QuicNet/QuicNet.csproj @@ -43,10 +43,11 @@ - + + diff --git a/QuicNet/Streams/QuicStream.cs b/QuicNet/Streams/QuicStream.cs index ab705f4..bf18687 100644 --- a/QuicNet/Streams/QuicStream.cs +++ b/QuicNet/Streams/QuicStream.cs @@ -2,6 +2,7 @@ using QuicNet.Connections; using QuicNet.Context; using QuicNet.Infrastructure.Frames; +using QuicNet.Infrastructure.Packets; using System; using System.Collections.Generic; using System.Linq; @@ -17,19 +18,41 @@ public class QuicStream private SortedList _data = new SortedList(); private QuicConnection _connection; - public QuicStreamContext Context; - public StreamState State { get; set; } public StreamType Type { get; set; } public StreamId StreamId { get; } + public byte[] Data + { + get + { + return _data.SelectMany(v => v.Value).ToArray(); + } + } + public QuicStream(QuicConnection connection, StreamId streamId) { StreamId = streamId; Type = streamId.Type; _connection = connection; - Context = new QuicStreamContext(this, _connection.Context); + } + + public bool Send(byte[] data) + { + ShortHeaderPacket packet = _connection.PacketCreator.CreateDataPacket(this.StreamId.Value, data); + + return _connection.SendData(packet); + } + + public byte[] Receive() + { + while (!IsStreamFull() || State == StreamState.Recv) + { + _connection.ReceivePacket(); + } + + return Data; } public void ResetStream(ResetStreamFrame frame) @@ -75,14 +98,10 @@ public void ProcessData(StreamFrame frame) if (State == StreamState.SizeKnown && IsStreamFull()) { - byte[] aggregatedData = _data.SelectMany(v => v.Value).ToArray(); - Context.SetData(data); - - _connection.Context.DataReceived(Context); + _connection.DataReceived(this); State = StreamState.DataRecvd; } - } private bool IsStreamFull() diff --git a/QuickNet.Tests.ConsoleServer/Program.cs b/QuickNet.Tests.ConsoleServer/Program.cs index e405933..c47fdff 100644 --- a/QuickNet.Tests.ConsoleServer/Program.cs +++ b/QuickNet.Tests.ConsoleServer/Program.cs @@ -19,14 +19,29 @@ class Program static void Example() { QuicListener listener = new QuicListener(11000); + listener.Start(); + while (true) { + // Blocks while waiting for a connection QuicConnection client = listener.AcceptQuicClient(); + + // Assign an action when a data is received from that client. + client.OnDataReceived += (c) => { + + byte[] data = c.Data; + + Console.WriteLine("Data received: " + Encoding.UTF8.GetString(data)); + + c.Send(Encoding.UTF8.GetBytes("Echo!")); + }; } } static void Main(string[] args) { + Example(); + byte[] bytes = new VariableInteger(12345); VariableInteger integer = bytes; UInt64 uinteger = integer; @@ -76,11 +91,11 @@ static void Main(string[] args) listener.Start(); } - private static void Listener_OnClientConnected(QuicContext obj) - { - System.Console.WriteLine("Client connected."); - obj.OnDataReceived += Obj_OnDataReceived; - } + //private static void Listener_OnClientConnected(QuicContext obj) + //{ + // System.Console.WriteLine("Client connected."); + // obj.OnDataReceived += Obj_OnDataReceived; + //} private static void Obj_OnDataReceived(QuicStreamContext obj) { From 64a259e6523eb0b8025d1d34b3778f139612b697 Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 18:12:24 +0200 Subject: [PATCH 3/7] Fixed an issue with ConvertedStreamId. --- QuicNet.Infrastructure/Frames/StreamFrame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuicNet.Infrastructure/Frames/StreamFrame.cs b/QuicNet.Infrastructure/Frames/StreamFrame.cs index f9abbca..7e7bbf6 100644 --- a/QuicNet.Infrastructure/Frames/StreamFrame.cs +++ b/QuicNet.Infrastructure/Frames/StreamFrame.cs @@ -50,7 +50,7 @@ public override void Decode(ByteArray array) EndOfStream = true; StreamData = array.ReadBytes((int)Length.Value); - ConvertedStreamId = QuickNet.Utilities.StreamId.Decode(ByteUtilities.GetBytes(Length.Value)); + ConvertedStreamId = QuickNet.Utilities.StreamId.Decode(ByteUtilities.GetBytes(StreamId.Value)); } public override byte[] Encode() From 75f2332c0deb0aa3d32dae3fc4b75c4b43b8a9dc Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 18:15:13 +0200 Subject: [PATCH 4/7] Updated examples --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8655a84..508f598 100644 --- a/README.md +++ b/README.md @@ -52,23 +52,21 @@ namespace QuickNet.Tests.ConsoleServer { static void Main(string[] args) { - QuicListener listener = new QuicListener(1234); // Start a new listener on port 1234 - listener.OnClientConnected += OnClientConnected; // Attach callback when a new client is connected - listener.Start(); // Start listening - } - static void OnClientConnected(QuicContext context) - { - System.Console.WriteLine("Client connected."); - context.OnDataReceived += OnDataReceived; // Attach a callback when the server reveices data - } - static void OnDataReceived(QuicStreamContext context) - { - string result = Convert.ToBase64String(context.Data); // Data - System.Console.WriteLine("Received data: {0}", result); - - // Send data back to the client - byte[] echoData = Encoding.UTF8.GetBytes("Echo!"); - context.Send(echoData); + QuicListener listener = new QuicListener(11000); + listener.Start(); + while (true) + { + // Blocks while waiting for a connection + QuicConnection client = listener.AcceptQuicClient(); + + // Assign an action when a data is received from that client. + client.OnDataReceived += (c) => { + byte[] data = c.Data; + Console.WriteLine("Data received: " + Encoding.UTF8.GetString(data)); + // Echo back data to the client + c.Send(Encoding.UTF8.GetBytes("Echo!")); + }; + } } } } @@ -85,11 +83,13 @@ namespace QuicNet.Tests.ConsoleClient static void Main(string[] args) { QuicClient client = new QuicClient(); - QuicContext context = client.Connect("127.0.0.1", 1234); // Connect to peer (Server) - QuicStreamContext sc = client.CreateStream(); // Create a data stream - sc.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data - - sc.Close(); // Close the stream after processing + QuicConnection connection = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) + + QuicStream stream = connection.CreateStream(); // Create a data stream + stream.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data + + byte[] data = stream.Receive(); // Receive from server (Blocks) + Console.ReadKey(); } } } From 73575689b4f53fd5a1d7d6b7d74b21a02e824352 Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 20:10:10 +0200 Subject: [PATCH 5/7] Fixes on StreamId. Stream types are now sent properly. --- QuicNet.Tests.ConsoleClient/Program.cs | 8 ++++---- QuicNet/Connections/QuicConnection.cs | 7 ++++--- .../QuicListenerNotStartedException.cs | 4 +--- QuicNet/Exceptions/StreamException.cs | 18 ++++++++++++++++++ QuicNet/QuicNet.csproj | 1 + QuicNet/Streams/QuicStream.cs | 11 +++++++++-- QuickNet.Utilities/StreamId.cs | 17 ++++++++--------- 7 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 QuicNet/Exceptions/StreamException.cs diff --git a/QuicNet.Tests.ConsoleClient/Program.cs b/QuicNet.Tests.ConsoleClient/Program.cs index 64897df..9d7683a 100644 --- a/QuicNet.Tests.ConsoleClient/Program.cs +++ b/QuicNet.Tests.ConsoleClient/Program.cs @@ -19,14 +19,14 @@ static void Main(string[] args) QuicConnection connection = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) Console.WriteLine("Connected"); - QuicStream stream = connection.CreateStream(); // Create a data stream - Console.WriteLine("Create stream with id: " + stream.StreamId.Value.ToString()); + QuicStream stream = connection.CreateStream(QuickNet.Utilities.StreamType.ClientUnidirectional); // Create a data stream + Console.WriteLine("Create stream with id: " + stream.StreamId.IntegerValue.ToString()); Console.WriteLine("Send 'Hello From Client!'"); stream.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data Console.WriteLine("Waiting for message from the server"); - byte[] data = stream.Receive(); // Receive from server - Console.WriteLine("Received: " + Encoding.UTF8.GetString(data)); + // byte[] data = stream.Receive(); // Receive from server + // Console.WriteLine("Received: " + Encoding.UTF8.GetString(data)); Console.ReadKey(); } } diff --git a/QuicNet/Connections/QuicConnection.cs b/QuicNet/Connections/QuicConnection.cs index f0b8c85..ee3e0cc 100644 --- a/QuicNet/Connections/QuicConnection.cs +++ b/QuicNet/Connections/QuicConnection.cs @@ -1,4 +1,5 @@ -using QuicNet.Context; +using QuickNet.Utilities; +using QuicNet.Context; using QuicNet.Infrastructure.Frames; using QuicNet.Infrastructure.PacketProcessing; using QuicNet.Infrastructure.Packets; @@ -29,9 +30,9 @@ public class QuicConnection public event Action OnDataReceived; - public QuicStream CreateStream() + public QuicStream CreateStream(StreamType type) { - QuicStream stream = new QuicStream(this, new QuickNet.Utilities.StreamId(0, QuickNet.Utilities.StreamType.ClientBidirectional)); + QuicStream stream = new QuicStream(this, new QuickNet.Utilities.StreamId(0, type)); _streams.Add(0, stream); return stream; diff --git a/QuicNet/Exceptions/QuicListenerNotStartedException.cs b/QuicNet/Exceptions/QuicListenerNotStartedException.cs index 4d27672..2c9add0 100644 --- a/QuicNet/Exceptions/QuicListenerNotStartedException.cs +++ b/QuicNet/Exceptions/QuicListenerNotStartedException.cs @@ -8,9 +8,7 @@ namespace QuicNet.Exceptions { public class QuicListenerNotStartedException : Exception { - public QuicListenerNotStartedException() - { - } + public QuicListenerNotStartedException() { } public QuicListenerNotStartedException(string message) : base(message) { diff --git a/QuicNet/Exceptions/StreamException.cs b/QuicNet/Exceptions/StreamException.cs new file mode 100644 index 0000000..bfaf2ad --- /dev/null +++ b/QuicNet/Exceptions/StreamException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuicNet.Exceptions +{ + public class StreamException : Exception + { + public StreamException() { } + + public StreamException(string message) : base(message) + { + + } + } +} diff --git a/QuicNet/QuicNet.csproj b/QuicNet/QuicNet.csproj index 4042e72..7b0431c 100644 --- a/QuicNet/QuicNet.csproj +++ b/QuicNet/QuicNet.csproj @@ -46,6 +46,7 @@ + diff --git a/QuicNet/Streams/QuicStream.cs b/QuicNet/Streams/QuicStream.cs index bf18687..9512f6a 100644 --- a/QuicNet/Streams/QuicStream.cs +++ b/QuicNet/Streams/QuicStream.cs @@ -1,6 +1,7 @@ using QuickNet.Utilities; using QuicNet.Connections; using QuicNet.Context; +using QuicNet.Exceptions; using QuicNet.Infrastructure.Frames; using QuicNet.Infrastructure.Packets; using System; @@ -40,13 +41,19 @@ public QuicStream(QuicConnection connection, StreamId streamId) public bool Send(byte[] data) { - ShortHeaderPacket packet = _connection.PacketCreator.CreateDataPacket(this.StreamId.Value, data); + if (Type == StreamType.ServerUnidirectional) + throw new StreamException("Cannot send data on unidirectional stream."); + + ShortHeaderPacket packet = _connection.PacketCreator.CreateDataPacket(this.StreamId.IntegerValue, data); return _connection.SendData(packet); } public byte[] Receive() { + if (Type == StreamType.ClientUnidirectional) + throw new StreamException("Cannot receive data on unidirectional stream."); + while (!IsStreamFull() || State == StreamState.Recv) { _connection.ReceivePacket(); @@ -65,7 +72,7 @@ public void ResetStream(ResetStreamFrame frame) public bool CanSendData() { - if (Type == StreamType.ServerUnidirectional) + if (Type == StreamType.ServerUnidirectional || Type == StreamType.ClientUnidirectional) return false; if (State == StreamState.Recv || State == StreamState.SizeKnown) diff --git a/QuickNet.Utilities/StreamId.cs b/QuickNet.Utilities/StreamId.cs index 9bc1beb..67c734d 100644 --- a/QuickNet.Utilities/StreamId.cs +++ b/QuickNet.Utilities/StreamId.cs @@ -16,21 +16,20 @@ public enum StreamType public class StreamId { - private UInt64 _id; - private StreamType _type; - - public UInt64 Value { get { return _id; } } - public StreamType Type { get { return _type; } } + public UInt64 Id { get; } + public UInt64 IntegerValue { get; } + public StreamType Type { get; private set; } public StreamId(UInt64 id, StreamType type) { - _id = id; - _type = type; + Id = id; + Type = type; + IntegerValue = id << 2 | (UInt64)type; } public static implicit operator byte[](StreamId id) { - return Encode(id.Value, id.Type); + return Encode(id.Id, id.Type); } public static implicit operator StreamId(byte[] data) @@ -40,7 +39,7 @@ public static implicit operator StreamId(byte[] data) public static implicit operator UInt64(StreamId streamId) { - return streamId.Value; + return streamId.Id; } public static byte[] Encode(UInt64 id, StreamType type) From 237c6a2943c540144cce64c8bcd1e6c394e6f885 Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 20:12:48 +0200 Subject: [PATCH 6/7] Updated examples --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 508f598..3c0ad39 100644 --- a/README.md +++ b/README.md @@ -85,10 +85,14 @@ namespace QuicNet.Tests.ConsoleClient QuicClient client = new QuicClient(); QuicConnection connection = client.Connect("127.0.0.1", 11000); // Connect to peer (Server) - QuicStream stream = connection.CreateStream(); // Create a data stream - stream.Send(Encoding.UTF8.GetBytes("Hello from Client!")); // Send Data + // Create a Bidirectional data stream + QuicStream stream = connection.CreateStream(QuickNet.Utilities.StreamType.ClientBidirectional); - byte[] data = stream.Receive(); // Receive from server (Blocks) + // Send Data + stream.Send(Encoding.UTF8.GetBytes("Hello from Client!")); + + // Receive from server (Blocks) + byte[] data = stream.Receive(); Console.ReadKey(); } } From 772d93614f9d094dc05ae237c7ad5cb80db1da62 Mon Sep 17 00:00:00 2001 From: Chris Dimitrov Date: Sun, 6 Jan 2019 20:27:33 +0200 Subject: [PATCH 7/7] Documentation for QuicClient and QuicListener. --- QuicNet/QuicClient.cs | 18 ++++++++++++++++++ QuicNet/QuicListener.cs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/QuicNet/QuicClient.cs b/QuicNet/QuicClient.cs index e633d81..e5e920b 100644 --- a/QuicNet/QuicClient.cs +++ b/QuicNet/QuicClient.cs @@ -18,6 +18,9 @@ namespace QuicNet { + /// + /// Quic Client. Used for sending and receiving data from a Quic Server. + /// public class QuicClient { private IPEndPoint _peerIp; @@ -39,6 +42,12 @@ public QuicClient() _packetCreator = new InitialPacketCreator(); } + /// + /// Connect to a remote server. + /// + /// Ip Address + /// Port + /// public QuicConnection Connect(string ip, int port) { // Establish socket connection @@ -62,6 +71,10 @@ public QuicConnection Connect(string ip, int port) return _connection; } + /// + /// Handles initial packet's frames. (In most cases protocol frames) + /// + /// private void HandleInitialFrames(Packet packet) { List frames = packet.GetFrames(); @@ -87,6 +100,11 @@ private void HandleInitialFrames(Packet packet) } } + /// + /// Create a new connection + /// + /// + /// private void EstablishConnection(UInt32 connectionId, UInt32 peerConnectionId) { ConnectionData connection = new ConnectionData(_pwt, connectionId, peerConnectionId); diff --git a/QuicNet/QuicListener.cs b/QuicNet/QuicListener.cs index 88f55bb..e00464c 100644 --- a/QuicNet/QuicListener.cs +++ b/QuicNet/QuicListener.cs @@ -18,6 +18,9 @@ namespace QuicNet { + /// + /// Quic Listener - a Quic server that processes incomming connections and if possible sends back data on it's peers. + /// public class QuicListener { private UdpClient _client; @@ -30,6 +33,10 @@ public class QuicListener private int _port; private bool _started; + /// + /// Create a new instance of QuicListener. + /// + /// The port that the server will listen on. public QuicListener(int port) { _started = false; @@ -39,6 +46,9 @@ public QuicListener(int port) _packetCreator = new InitialPacketCreator(); } + /// + /// Starts the listener. + /// public void Start() { _client = new UdpClient(_port); @@ -46,11 +56,18 @@ public void Start() _pwt = new PacketWireTransfer(_client, null); } + /// + /// Stops the listener. + /// public void Close() { _client.Close(); } + /// + /// Blocks and waits for incomming connection. Does NOT block additional incomming packets. + /// + /// Returns an instance of QuicConnection. public QuicConnection AcceptQuicClient() { if (!_started) @@ -73,6 +90,9 @@ public QuicConnection AcceptQuicClient() } } + /// + /// Starts receiving data from clients. + /// private void Receive() { while (true) @@ -88,6 +108,10 @@ private void Receive() } } + /// + /// Orchestrates packets to connections, depending on the packet type. + /// + /// private void OrchestratePacket(Packet packet) { if (packet is ShortHeaderPacket) @@ -96,6 +120,12 @@ private void OrchestratePacket(Packet packet) } } + /// + /// Processes incomming initial packet and creates or halts a connection. + /// + /// Initial Packet + /// Peer's endpoint + /// private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint) { QuicConnection result = null; @@ -146,6 +176,10 @@ private QuicConnection ProcessInitialPacket(Packet packet, IPEndPoint endPoint) return null; } + /// + /// Processes short header packet, by distributing the frames towards connections. + /// + /// private void ProcessShortHeaderPacket(Packet packet) { ShortHeaderPacket shp = (ShortHeaderPacket)packet;