public async Task ReadWrite_Random_Success(int readSize, int writeSize) { byte[] testBuffer = new byte[8192]; Random.Shared.NextBytes(testBuffer); await RunClientServer( async clientConnection => { await using QuicStream clientStream = clientConnection.OpenUnidirectionalStream(); ReadOnlyMemory <byte> sendBuffer = testBuffer; while (sendBuffer.Length != 0) { ReadOnlyMemory <byte> chunk = sendBuffer.Slice(0, Math.Min(sendBuffer.Length, writeSize)); await clientStream.WriteAsync(chunk); sendBuffer = sendBuffer.Slice(chunk.Length); } clientStream.Shutdown(); await clientStream.ShutdownWriteCompleted(); }, async serverConnection => { await using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); byte[] receiveBuffer = new byte[testBuffer.Length]; int totalBytesRead = 0; while (totalBytesRead != receiveBuffer.Length) { int bytesRead = await serverStream.ReadAsync(receiveBuffer.AsMemory(totalBytesRead, Math.Min(receiveBuffer.Length - totalBytesRead, readSize))); if (bytesRead == 0) { break; } totalBytesRead += bytesRead; } Assert.Equal(testBuffer.Length, receiveBuffer.Length); Assert.Equal(testBuffer, receiveBuffer); }); }
public async Task BasicTest() { for (int i = 0; i < 100; i++) { Task listenTask = Task.Run(async() => { using QuicConnection connection = await DefaultListener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; int bytesRead = await stream.ReadAsync(buffer); Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); await stream.WriteAsync(s_data, endStream: true); byte[] memory = new byte[12]; int bytesRead = await stream.ReadAsync(memory); Assert.Equal(s_data.Length, bytesRead); // TODO this failed once... Assert.True(s_data.Span.SequenceEqual(memory)); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 10000); } }
private Task EnsureControlStreamAcceptedAsync() { if (_inboundControlStream != null) { return(Task.CompletedTask); } return(EnsureControlStreamAcceptedInternalAsync()); async Task EnsureControlStreamAcceptedInternalAsync() { Http3LoopbackStream controlStream; while (true) { QuicStream quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false); if (!quicStream.CanWrite) { // control stream accepted controlStream = new Http3LoopbackStream(quicStream); break; } // control streams are unidirectional, so this must be a request stream // keep it for later and wait for another stream _delayedStreams.Enqueue(quicStream); } long?streamType = await controlStream.ReadIntegerAsync(); Assert.Equal(Http3LoopbackStream.ControlStream, streamType); List <(long settingId, long settingValue)> settings = await controlStream.ReadSettingsAsync(); (long settingId, long settingValue) = Assert.Single(settings); Assert.Equal(Http3LoopbackStream.MaxHeaderListSize, settingId); MaxHeaderListSize = settingValue; _inboundControlStream = controlStream; } }
public Req send_on_quic_protocol(string content,int port) { Req request = new Req(); int ms = 0; int byteNumber = -1; request.Content = content; //Console.WriteLine("Starting client."); QuicClient client = new QuicClient(); request.timeSended = DateTime.Now.ToLocalTime(); QuicConnection connection = client.Connect("127.0.0.1", port); // Connect to peer (Server) QuicStream stream_quic = connection.CreateStream(QuickNet.Utilities.StreamType.ClientBidirectional); // Create a data stream byte[] messageBytes = null; string aaa = JsonConvert.SerializeObject(request); messageBytes = System.Text.Encoding.UTF8.GetBytes(aaa); //Console.WriteLine("Send 'Hello From Client!'"); stream_quic.Send(messageBytes); // Send Data //Console.WriteLine("Waiting for message from the server"); try { byte[] data = stream_quic.Receive(); byteNumber = data.Length; DateTime timeRecieved = DateTime.Now.ToLocalTime(); string message = cleanMessage(data); // Receive from server Req request_back = JsonConvert.DeserializeObject<Req>(message); // Deserialize request.timeRecieved = timeRecieved; TimeSpan span = request_back.timeRecieved - request_back.timeSended; request.number_of_bytes = data.Length; //request_back.timeRecieved.mili ms = (int)span.TotalMilliseconds; request.totalTime = ms; } catch (Exception e) { Console.WriteLine(e.Message); } return request; }
static void Main(string[] args) { Console.WriteLine("Starting client."); QuicClient client = new QuicClient(); 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(QuickNet.Utilities.StreamType.ClientBidirectional); // 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 stream = connection.CreateStream(QuickNet.Utilities.StreamType.ClientBidirectional); // Create a data stream stream.Send(Encoding.UTF8.GetBytes("Hello from Client2!")); Console.WriteLine("Waiting for message from the server"); try { byte[] data = stream.Receive(); // Receive from server Console.WriteLine("Received: " + Encoding.UTF8.GetString(data)); } catch (Exception e) { Console.WriteLine(e.Message); } try { byte[] data = stream.Receive(); // Receive from server Console.WriteLine("Received: " + Encoding.UTF8.GetString(data)); } catch (Exception e) { Console.WriteLine(e.Message); } Console.ReadKey(); }
protected override async Task <StreamPair> CreateConnectedStreamsAsync() { QuicImplementationProvider provider = Provider; var listener = new QuicListener( provider, new IPEndPoint(IPAddress.Loopback, 0), GetSslServerAuthenticationOptions()); byte[] buffer = new byte[1] { 42 }; QuicConnection connection1 = null, connection2 = null; QuicStream stream1 = null, stream2 = null; await WhenAllOrAnyFailed( Task.Run(async() => { connection1 = await listener.AcceptConnectionAsync(); stream1 = await connection1.AcceptStreamAsync(); Assert.Equal(1, await stream1.ReadAsync(buffer)); }), Task.Run(async() => { connection2 = new QuicConnection( provider, listener.ListenEndPoint, GetSslClientAuthenticationOptions()); await connection2.ConnectAsync(); stream2 = connection2.OpenBidirectionalStream(); // OpenBidirectionalStream only allocates ID. We will force stream opening // by Writing there and receiving data on the other side. await stream2.WriteAsync(buffer); })); var result = new StreamPairWithOtherDisposables(stream1, stream2); result.Disposables.Add(connection1); result.Disposables.Add(connection2); result.Disposables.Add(listener); return(result); }
// RunAsync starts the QuicListener and continually waits for new clients public override async Task RunAsync(CancellationToken Token) { // Start the QuicListener QuicListener listener = new QuicListener(this.ExternalPort); //QuicListener Listener = new QuicListener(IPAddress.Any, this.ExternalPort);; listener.Start(); // Continually wait for new client while (!Token.IsCancellationRequested) { // Handle the client asynchronously in a new thread QuicConnection client = listener.AcceptQuicClient(); _ = Task.Run(() => { //client.ReceiveTimeout = client.SendTimeout = 0; QuicStream stream = client.CreateStream(QuickNet.Utilities.StreamType.ClientBidirectional); //stream. //stream.ReadTimeout = stream.WriteTimeout = Timeout.Infinite; while (!Token.IsCancellationRequested) { // byte[] data; // Read from the implant string read = ""; client.OnDataReceived += (c) => { read += Encoding.UTF8.GetString(c.Data); }; //string read = Encoding.UTF8.GetString(data); //string read = await Utilities.ReadStreamAsync(stream); // Write to the Covenant server string guid = this.WriteToConnector(read); if (guid != null) { // Track this GUID -> client mapping, for use within the OnReadBridge function Clients.TryAdd(guid, stream); } } }); } }
public QuicStream ProcessFrames(List <Frame> frames) { QuicStream stream = null; foreach (Frame frame in frames) { if (frame.Type == 0x01) { OnRstStreamFrame(frame); } if (frame.Type == 0x04) { OnRstStreamFrame(frame); } if (frame.Type >= 0x08 && frame.Type <= 0x0f) { stream = OnStreamFrame(frame); } if (frame.Type == 0x10) { OnMaxDataFrame(frame); } if (frame.Type == 0x11) { OnMaxStreamDataFrame(frame); } if (frame.Type >= 0x12 && frame.Type <= 0x13) { OnMaxStreamFrame(frame); } if (frame.Type == 0x14) { OnDataBlockedFrame(frame); } if (frame.Type >= 0x1c && frame.Type <= 0x1d) { OnConnectionCloseFrame(frame); } } return(stream); }
public async Task Dispose_WithOpenLocalStream_LocalStreamFailsWithQuicOperationAbortedException(int writesBeforeClose) { if (IsMockProvider) { return; } // Set a short idle timeout so that after we dispose the connection, the peer will discover the connection is dead before too long. QuicListenerOptions listenerOptions = CreateQuicListenerOptions(); listenerOptions.IdleTimeout = TimeSpan.FromSeconds(1); using var sync = new SemaphoreSlim(0); await RunClientServer( async clientConnection => { using QuicStream clientStream = await clientConnection.OpenBidirectionalStreamAsync(); await DoWrites(clientStream, writesBeforeClose); // Wait for peer to receive data await sync.WaitAsync(); clientConnection.Dispose(); await Assert.ThrowsAsync <QuicOperationAbortedException>(async() => await clientStream.ReadAsync(new byte[1])); await Assert.ThrowsAsync <QuicOperationAbortedException>(async() => await clientStream.WriteAsync(new byte[1])); }, async serverConnection => { using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); await DoReads(serverStream, writesBeforeClose); sync.Release(); // The client has done an abortive shutdown of the connection, which means we are not notified that the connection has closed. // But the connection idle timeout should kick in and eventually we will get exceptions. await Assert.ThrowsAsync <QuicConnectionAbortedException>(async() => await serverStream.ReadAsync(new byte[1])); await Assert.ThrowsAsync <QuicConnectionAbortedException>(async() => await serverStream.WriteAsync(new byte[1])); }, listenerOptions : listenerOptions); }
/// <summary> /// Accepts unidirectional streams (control, QPack, ...) from the server. /// </summary> private async Task AcceptStreamsAsync() { try { while (true) { ValueTask <QuicStream> streamTask; lock (SyncObj) { if (ShuttingDown) { return; } // No cancellation token is needed here; we expect the operation to cancel itself when _connection is disposed. streamTask = _connection !.AcceptInboundStreamAsync(CancellationToken.None); } QuicStream stream = await streamTask.ConfigureAwait(false); // This process is cleaned up when _connection is disposed, and errors are observed via Abort(). _ = ProcessServerStreamAsync(stream); } } catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted) { // Shutdown initiated by us, no need to abort. } catch (QuicException ex) when(ex.QuicError == QuicError.ConnectionAborted) { Debug.Assert(ex.ApplicationErrorCode.HasValue); Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value; Abort(HttpProtocolException.CreateHttp3ConnectionException(code, SR.net_http_http3_connection_close)); } catch (Exception ex) { Abort(ex); } }
public void Start(bool isBlocking, IPEndPoint requiredLocalEP) { ValidateListenerDisposed(); QuicConnection serverConnection = AsyncUtil.RunSync <QuicConnection>(() => this.listener.AcceptConnectionAsync().AsTask()); QuicStream serverStream = AsyncUtil.RunSync <QuicStream>(() => serverConnection.AcceptStreamAsync().AsTask()); if (this.lspHooked) { IPEndPoint actualListenedLocalEP = this.listener.ListenEndPoint as IPEndPoint; LspConsole.Instance.InterceptTraffic(this.server.SocketConfig.Type, isBlocking, requiredLocalEP, actualListenedLocalEP); } this.lspHookedLocalEP = requiredLocalEP; this.thread.Start(); }
public void Initialize(QuicStream stream) { Debug.Assert(_stream == null); _stream = stream; _streamClosedTokenSource = null; _onClosedRegistrations?.Clear(); InitializeFeatures(); CanRead = _stream.CanRead; CanWrite = _stream.CanWrite; _error = null; StreamId = _stream.Id; PoolExpirationTicks = 0; Transport = _originalTransport; Application = _originalApplication; _transportPipeReader.Reset(); _transportPipeWriter.Reset(); _connectionId = null; _shutdownReason = null; _writeAbortException = null; _streamClosed = false; _serverAborted = false; _clientAbort = false; // Only reset pipes if the stream has been reused. if (CanReuse) { _inputPipe.Reset(); _outputPipe.Reset(); } CanReuse = false; }
private Task <HttpResponseMessage> SendWithoutWaitingAsync(HttpRequestMessage request, CancellationToken cancellationToken) { QuicStream quicStream = null; Http3RequestStream stream; try { // TODO: do less work in this lock, try to get rid of goto. lock (SyncObj) { if (_connection == null) { goto retryRequest; } quicStream = _connection.OpenBidirectionalStream(); if (_lastProcessedStreamId != -1 && quicStream.StreamId > _lastProcessedStreamId) { goto retryRequest; } stream = new Http3RequestStream(request, this, quicStream); _activeRequests.Add(quicStream.StreamId, stream); } return(stream.SendAsync(cancellationToken)); } catch (QuicConnectionAbortedException ex) { // This will happen if we aborted _connection somewhere. Abort(ex); } retryRequest: // We lost a race between GOAWAY/abort and our pool sending the request to this connection. quicStream?.Dispose(); return(Task.FromException <HttpResponseMessage>(new HttpRequestException(SR.net_http_request_aborted, null, RequestRetryType.RetryOnSameOrNextProxy))); }
public async Task CloseAsync_WithOpenStream_LocalAndPeerStreamsFailWithQuicOperationAbortedException(int writesBeforeClose) { if (IsMockProvider) { return; } using var sync = new SemaphoreSlim(0); await RunClientServer( async clientConnection => { using QuicStream clientStream = await clientConnection.OpenBidirectionalStreamAsync(); await DoWrites(clientStream, writesBeforeClose); // Wait for peer to receive data await sync.WaitAsync(); await clientConnection.CloseAsync(ExpectedErrorCode); await Assert.ThrowsAsync <QuicOperationAbortedException>(async() => await clientStream.ReadAsync(new byte[1])); await Assert.ThrowsAsync <QuicOperationAbortedException>(async() => await clientStream.WriteAsync(new byte[1])); }, async serverConnection => { using QuicStream serverStream = await serverConnection.AcceptStreamAsync(); await DoReads(serverStream, writesBeforeClose); sync.Release(); // Since the peer did the abort, we should receive the abort error code in the exception. QuicConnectionAbortedException ex; ex = await Assert.ThrowsAsync <QuicConnectionAbortedException>(async() => await serverStream.ReadAsync(new byte[1])); Assert.Equal(ExpectedErrorCode, ex.ErrorCode); ex = await Assert.ThrowsAsync <QuicConnectionAbortedException>(async() => await serverStream.WriteAsync(new byte[1])); Assert.Equal(ExpectedErrorCode, ex.ErrorCode); }); }
public static async Task HandleServerConnection(QuicConnection connection, CancellationToken token) { try { QuicStream stream = await connection.AcceptStreamAsync(token); byte[] buffer = new byte[4 * 1024]; int read; while ((read = await stream.ReadAsync(buffer, token)) > 0) { // echo the read data back await stream.WriteAsync(buffer, 0, read, token); await stream.FlushAsync(token); } } finally { // gracefully close the connection with 0 error code await connection.CloseAsync(0); } }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } if (_connection != null) { _connection.CloseAsync((long)Http3ErrorCode.NoError).GetAwaiter().GetResult(); // TODO: async... _connection.Dispose(); _connection = null; } }
private static async Task ServerQuicStreamTask(QuicConnection connection, QuicStream stream) { try { if (!await ServerStreamTask(stream).ConfigureAwait(false)) { await connection.CloseAsync(1).ConfigureAwait(false); } } catch (QuicStreamAbortedException e) when(e.ErrorCode == 0) { } catch (QuicConnectionAbortedException) { } catch (OperationCanceledException) { } finally { await stream.DisposeAsync().ConfigureAwait(false); } }
private void OnStreamFrame(Frame frame) { StreamFrame sf = (StreamFrame)frame; if (_streams.ContainsKey(sf.StreamId.Value) == false) { QuicStream stream = new QuicStream(this, sf.ConvertedStreamId); stream.ProcessData(sf); if ((UInt64)_streams.Count < MaxStreams) { _streams.Add(sf.StreamId.Value, stream); } else { SendMaximumStreamReachedError(); } } else { QuicStream stream = _streams[sf.StreamId]; stream.ProcessData(sf); } }
public async Task BasicTest() { using (QuicListener listener = new QuicListener(QuicImplementationProviders.Mock, new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null)) { listener.Start(); IPEndPoint listenEndPoint = listener.ListenEndPoint; await Task.WhenAll( Task.Run(async() => { // Client code using (QuicConnection connection = new QuicConnection(QuicImplementationProviders.Mock, listenEndPoint, sslClientAuthenticationOptions: null)) { await connection.ConnectAsync(); using (QuicStream stream = connection.OpenBidirectionalStream()) { await stream.WriteAsync(s_data); } } }), Task.Run(async() => { // Server code using (QuicConnection connection = await listener.AcceptConnectionAsync()) { using (QuicStream stream = await connection.AcceptStreamAsync()) { byte[] buffer = new byte[s_data.Length]; int bytesRead = await stream.ReadAsync(buffer); Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } } })); } }
/// <summary> /// Reads the server's control stream. /// </summary> private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer) { (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); if (frameType == null) { // Connection closed prematurely, expected SETTINGS frame. throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } if (frameType != Http3FrameType.Settings) { // Expected SETTINGS as first frame of control stream. throw new Http3ConnectionException(Http3ErrorCode.MissingSettings); } await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); while (true) { (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); switch (frameType) { case Http3FrameType.GoAway: await ProcessGoAwayFameAsync(payloadLength).ConfigureAwait(false); break; case Http3FrameType.Settings: // Only a single SETTINGS frame is supported. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.Headers: case Http3FrameType.Data: case Http3FrameType.MaxPushId: case Http3FrameType.DuplicatePush: // Servers should not send these frames to a control stream. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.PushPromise: case Http3FrameType.CancelPush: // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. throw new Http3ConnectionException(Http3ErrorCode.IdError); case null: // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). bool shuttingDown; lock (SyncObj) { shuttingDown = ShuttingDown; } if (!shuttingDown) { throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } return; default: await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); break; } } async ValueTask <(Http3FrameType?frameType, long payloadLength)> ReadFrameEnvelopeAsync() { long frameType, payloadLength; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out frameType, out payloadLength, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else if (buffer.ActiveLength == 0) { // End of stream. return(null, 0); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); return((Http3FrameType)frameType, payloadLength); } async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength) { if (settingsPayloadLength > MaximumSettingsPayloadLength) { if (NetEventSource.Log.IsEnabled()) { Trace($"Received SETTINGS frame with {settingsPayloadLength} byte payload exceeding the {MaximumSettingsPayloadLength} byte maximum."); } throw new Http3ConnectionException(Http3ErrorCode.ExcessiveLoad); } while (settingsPayloadLength != 0) { long settingId, settingValue; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out settingId, out settingValue, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } settingsPayloadLength -= bytesRead; // Only support this single setting. Skip others. if (settingId == (long)Http3SettingType.MaxHeaderListSize) { _maximumHeadersLength = (int)Math.Min(settingValue, int.MaxValue); } } } async ValueTask ProcessGoAwayFameAsync(long goawayPayloadLength) { long lastStreamId; int bytesRead; while (!VariableLengthIntegerHelper.TryRead(buffer.AvailableSpan, out lastStreamId, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); if (bytesRead != goawayPayloadLength) { // Frame contains unknown extra data after the integer. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } OnServerGoAway(lastStreamId); } async ValueTask SkipUnknownPayloadAsync(Http3FrameType frameType, long payloadLength) { if (payloadLength > MaximumUnknownFramePayloadLength) { Trace($"Received unknown frame type 0x{(long)frameType:x} with {payloadLength} byte payload exceeding the {MaximumUnknownFramePayloadLength} byte maximum."); throw new Http3ConnectionException(Http3ErrorCode.ExcessiveLoad); } while (payloadLength != 0) { if (buffer.ActiveLength == 0) { int bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } long readLength = Math.Min(payloadLength, buffer.ActiveLength); buffer.Discard((int)readLength); payloadLength -= readLength; } } }
/// <summary> /// Routes a stream to an appropriate stream-type-specific processor /// </summary> private async Task ProcessServerStreamAsync(QuicStream stream) { try { await using (stream.ConfigureAwait(false)) using (var buffer = new ArrayBuffer(initialSize: 32, usePool: true)) { if (stream.CanWrite) { // Server initiated bidirectional streams are either push streams or extensions, and we support neither. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } int bytesRead; try { bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); } catch (QuicStreamAbortedException) { // Treat identical to receiving 0. See below comment. bytesRead = 0; } if (bytesRead == 0) { // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-unidirectional-streams // A sender can close or reset a unidirectional stream unless otherwise specified. A receiver MUST // tolerate unidirectional streams being closed or reset prior to the reception of the unidirectional // stream header. return; } buffer.Commit(bytesRead); // Stream type is a variable-length integer, but we only check the first byte. There is no known type requiring more than 1 byte. switch (buffer.ActiveSpan[0]) { case (byte)Http3StreamType.Control: if (Interlocked.Exchange(ref _haveServerControlStream, 1) != 0) { // A second control stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // Discard the stream type header. buffer.Discard(1); await ProcessServerControlStreamAsync(stream, buffer).ConfigureAwait(false); return; case (byte)Http3StreamType.QPackDecoder: if (Interlocked.Exchange(ref _haveServerQpackDecodeStream, 1) != 0) { // A second QPack decode stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // The stream must not be closed, but we aren't using QPACK right now -- ignore. buffer.Dispose(); await stream.CopyToAsync(Stream.Null).ConfigureAwait(false); return; case (byte)Http3StreamType.QPackEncoder: if (Interlocked.Exchange(ref _haveServerQpackEncodeStream, 1) != 0) { // A second QPack encode stream has been received. throw new Http3ConnectionException(Http3ErrorCode.StreamCreationError); } // The stream must not be closed, but we aren't using QPACK right now -- ignore. buffer.Dispose(); await stream.CopyToAsync(Stream.Null).ConfigureAwait(false); return; case (byte)Http3StreamType.Push: // We don't support push streams. // Because no maximum push stream ID was negotiated via a MAX_PUSH_ID frame, server should not have sent this. Abort the connection with H3_ID_ERROR. throw new Http3ConnectionException(Http3ErrorCode.IdError); default: // Unknown stream type. Per spec, these must be ignored and aborted but not be considered a connection-level error. if (NetEventSource.Log.IsEnabled()) { // Read the rest of the integer, which might be more than 1 byte, so we can log it. long unknownStreamType; while (!VariableLengthIntegerHelper.TryRead(buffer.ActiveSpan, out unknownStreamType, out _)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead == 0) { unknownStreamType = -1; break; } buffer.Commit(bytesRead); } NetEventSource.Info(this, $"Ignoring server-initiated stream of unknown type {unknownStreamType}."); } stream.AbortWrite((long)Http3ErrorCode.StreamCreationError); return; } } } catch (Exception ex) { Abort(ex); } }
public async Task MultipleReadsAndWrites() { for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { // Connection isn't being accepted, interesting. using QuicConnection connection = await DefaultListener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); for (int i = 0; i < 5; i++) { await stream.WriteAsync(s_data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }
private static async Task SendAndReceiveDataAsync(ReadOnlyMemory <byte> data, QuicStream s1, QuicStream s2) { await s1.WriteAsync(data); await ReceiveDataAsync(data, s2); }
public async Task MultipleStreamsOnSingleConnection() { Task listenTask = Task.Run(async() => { { using QuicConnection connection = await DefaultListener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); await using QuicStream stream2 = await connection.AcceptStreamAsync(); byte[] buffer = new byte[s_data.Length]; while (true) { int bytesRead = await stream.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.Equal(s_data.Length, bytesRead); Assert.True(s_data.Span.SequenceEqual(buffer)); } while (true) { int bytesRead = await stream2.ReadAsync(buffer); if (bytesRead == 0) { break; } Assert.True(s_data.Span.SequenceEqual(buffer)); } await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); await connection.CloseAsync(); } }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(DefaultListener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); await using QuicStream stream2 = connection.OpenBidirectionalStream(); await stream.WriteAsync(s_data, endStream: true); await stream.ShutdownWriteCompleted(); await stream2.WriteAsync(s_data, endStream: true); await stream2.ShutdownWriteCompleted(); byte[] memory = new byte[12]; while (true) { int res = await stream.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } while (true) { int res = await stream2.ReadAsync(memory); if (res == 0) { break; } Assert.True(s_data.Span.SequenceEqual(memory)); } await connection.CloseAsync(); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000); }
public Http3LoopbackStream(QuicStream stream) { _stream = stream; }
/// <summary> /// Reads the server's control stream. /// </summary> private async Task ProcessServerControlStreamAsync(QuicStream stream, ArrayBuffer buffer) { using (buffer) { // Read the first frame of the control stream. Per spec: // A SETTINGS frame MUST be sent as the first frame of each control stream. (Http3FrameType? frameType, long payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); if (frameType == null) { // Connection closed prematurely, expected SETTINGS frame. throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } if (frameType != Http3FrameType.Settings) { throw new Http3ConnectionException(Http3ErrorCode.MissingSettings); } await ProcessSettingsFrameAsync(payloadLength).ConfigureAwait(false); // Read subsequent frames. while (true) { (frameType, payloadLength) = await ReadFrameEnvelopeAsync().ConfigureAwait(false); switch (frameType) { case Http3FrameType.GoAway: await ProcessGoAwayFameAsync(payloadLength).ConfigureAwait(false); break; case Http3FrameType.Settings: // If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.Headers: case Http3FrameType.Data: case Http3FrameType.MaxPushId: case Http3FrameType.DuplicatePush: // Servers should not send these frames to a control stream. throw new Http3ConnectionException(Http3ErrorCode.UnexpectedFrame); case Http3FrameType.PushPromise: case Http3FrameType.CancelPush: // Because we haven't sent any MAX_PUSH_ID frame, it is invalid to receive any push-related frames as they will all reference a too-large ID. throw new Http3ConnectionException(Http3ErrorCode.IdError); case null: // End of stream reached. If we're shutting down, stop looping. Otherwise, this is an error (this stream should not be closed for life of connection). bool shuttingDown; lock (SyncObj) { shuttingDown = ShuttingDown; } if (!shuttingDown) { throw new Http3ConnectionException(Http3ErrorCode.ClosedCriticalStream); } return; default: await SkipUnknownPayloadAsync(frameType.GetValueOrDefault(), payloadLength).ConfigureAwait(false); break; } } } async ValueTask <(Http3FrameType?frameType, long payloadLength)> ReadFrameEnvelopeAsync() { long frameType, payloadLength; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out frameType, out payloadLength, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else if (buffer.ActiveLength == 0) { // End of stream. return(null, 0); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); return((Http3FrameType)frameType, payloadLength); } async ValueTask ProcessSettingsFrameAsync(long settingsPayloadLength) { while (settingsPayloadLength != 0) { long settingId, settingValue; int bytesRead; while (!Http3Frame.TryReadIntegerPair(buffer.ActiveSpan, out settingId, out settingValue, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength * 2); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } settingsPayloadLength -= bytesRead; if (settingsPayloadLength < 0) { // An integer was encoded past the payload length. // A frame payload that contains additional bytes after the identified fields or a frame payload that terminates before the end of the identified fields MUST be treated as a connection error of type H3_FRAME_ERROR. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } buffer.Discard(bytesRead); // Only support this single setting. Skip others. if (settingId == (long)Http3SettingType.MaxHeaderListSize) { _maximumHeadersLength = (int)Math.Min(settingValue, int.MaxValue); } } } async ValueTask ProcessGoAwayFameAsync(long goawayPayloadLength) { long lastStreamId; int bytesRead; while (!VariableLengthIntegerHelper.TryRead(buffer.AvailableSpan, out lastStreamId, out bytesRead)) { buffer.EnsureAvailableSpace(VariableLengthIntegerHelper.MaximumEncodedLength); bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } buffer.Discard(bytesRead); if (bytesRead != goawayPayloadLength) { // Frame contains unknown extra data after the integer. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } OnServerGoAway(lastStreamId); } async ValueTask SkipUnknownPayloadAsync(Http3FrameType frameType, long payloadLength) { while (payloadLength != 0) { if (buffer.ActiveLength == 0) { int bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); if (bytesRead != 0) { buffer.Commit(bytesRead); } else { // Our buffer has partial frame data in it but not enough to complete the read: bail out. throw new Http3ConnectionException(Http3ErrorCode.FrameError); } } long readLength = Math.Min(payloadLength, buffer.ActiveLength); buffer.Discard((int)readLength); payloadLength -= readLength; } } }
public async Task LargeDataSentAndReceived() { byte[] data = Enumerable.Range(0, 64 * 1024).Select(x => (byte)x).ToArray(); const int NumberOfWrites = 256; // total sent = 16M using QuicListener listener = CreateQuicListener(); for (int j = 0; j < 100; j++) { Task listenTask = Task.Run(async() => { using QuicConnection connection = await listener.AcceptConnectionAsync(); await using QuicStream stream = await connection.AcceptStreamAsync(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); Task clientTask = Task.Run(async() => { using QuicConnection connection = CreateQuicConnection(listener.ListenEndPoint); await connection.ConnectAsync(); await using QuicStream stream = connection.OpenBidirectionalStream(); byte[] buffer = new byte[data.Length]; for (int i = 0; i < NumberOfWrites; i++) { await stream.WriteAsync(data); } await stream.WriteAsync(Memory <byte> .Empty, endStream: true); for (int i = 0; i < NumberOfWrites; i++) { int totalBytesRead = 0; while (totalBytesRead < data.Length) { int bytesRead = await stream.ReadAsync(buffer.AsMemory(totalBytesRead)); Assert.NotEqual(0, bytesRead); totalBytesRead += bytesRead; } Assert.Equal(data.Length, totalBytesRead); Assert.True(data.AsSpan().SequenceEqual(buffer)); } await stream.ShutdownWriteCompleted(); await connection.CloseAsync(errorCode: 0); }); await(new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 1000000); } }
public async Task WriteTests(int[][] writes, WriteType writeType) { await RunClientServer( async clientConnection => { await using QuicStream stream = clientConnection.OpenUnidirectionalStream(); foreach (int[] bufferLengths in writes) { switch (writeType) { case WriteType.SingleBuffer: foreach (int bufferLength in bufferLengths) { await stream.WriteAsync(new byte[bufferLength]); } break; case WriteType.GatheredBuffers: var buffers = bufferLengths .Select(bufferLength => new ReadOnlyMemory <byte>(new byte[bufferLength])) .ToArray(); await stream.WriteAsync(buffers); break; case WriteType.GatheredSequence: var firstSegment = new BufferSegment(new byte[bufferLengths[0]]); BufferSegment lastSegment = firstSegment; foreach (int bufferLength in bufferLengths.Skip(1)) { lastSegment = lastSegment.Append(new byte[bufferLength]); } var buffer = new ReadOnlySequence <byte>(firstSegment, 0, lastSegment, lastSegment.Memory.Length); await stream.WriteAsync(buffer); break; default: Debug.Fail("Unknown write type."); break; } } stream.Shutdown(); await stream.ShutdownCompleted(); }, async serverConnection => { await using QuicStream stream = await serverConnection.AcceptStreamAsync(); var buffer = new byte[4096]; int receivedBytes = 0, totalBytes = 0; while ((receivedBytes = await stream.ReadAsync(buffer)) != 0) { totalBytes += receivedBytes; } int expectedTotalBytes = writes.SelectMany(x => x).Sum(); Assert.Equal(expectedTotalBytes, totalBytes); stream.Shutdown(); await stream.ShutdownCompleted(); }); }
public StreamPair(QuicStream stream1, QuicStream stream2) { Stream1 = stream1; Stream2 = stream2; }
protected static async Task <StreamPair> CreateConnectedStreamsAsync() { QuicImplementationProvider provider = ImplementationProvider; var protocol = new SslApplicationProtocol("quictest"); QuicListener listener = new QuicListener(provider, new QuicListenerOptions() { ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ServerAuthenticationOptions = new SslServerAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol> { protocol } }, CertificateFilePath = "Certs/cert.crt", PrivateKeyFilePath = "Certs/cert.key" }); listener.Start(); QuicConnection connection1 = null, connection2 = null; QuicStream stream1 = null, stream2 = null; await Task.WhenAll( Task.Run(async() => { connection1 = await listener.AcceptConnectionAsync(); stream1 = await connection1.AcceptStreamAsync(); // Hack to force stream creation byte[] buffer = new byte[1]; await stream1.ReadAsync(buffer); }), Task.Run(async() => { connection2 = new QuicConnection( provider, listener.ListenEndPoint, new SslClientAuthenticationOptions() { ApplicationProtocols = new List <SslApplicationProtocol>() { protocol } }); await connection2.ConnectAsync(); stream2 = connection2.OpenBidirectionalStream(); // Hack to force stream creation byte[] buffer = new byte[1]; await stream2.WriteAsync(buffer); await stream2.FlushAsync(); })); var result = new StreamPair(stream1, stream2); result.Disposables.Add(connection1); result.Disposables.Add(connection2); result.Disposables.Add(listener); return(result); }