/// <summary> /// Proess close goaway frame. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="errorCode">Error code registry.</param> /// <param name="lastStreamID">The last good stream id.</param> private static void ProcessCloseGoAwayFrame(Nequeo.Net.Http2.HttpContext httpContext, ErrorCodeRegistry errorCode, int lastStreamID) { // Create the goaway frame response. GoAwayFrame frame = new GoAwayFrame(lastStreamID, errorCode); // Write the frame. httpContext.ResponseWrite(frame.Buffer); }
/// <summary> /// Process Protocol control frame. /// </summary> /// <param name="frame">The control frame.</param> private void ProcessControlFrame(ControlFrame frame) { if (frame.Version != Version) { throw new ProtocolExeption(StatusCode.UnsupportedVersion); } if (frame.Type == FrameType.GoAway) { GoAwayFrame goAway = (GoAwayFrame)frame; CloseInternal(goAway.Status, goAway.LastSeenGoodStreamId, false); } else { Http2Stream stream = this.streamsStore.GetStreamById(frame.StreamId); // if this is rst frame - don't send error or it will go in rst loop if (stream == null && frame.Type != FrameType.RTS && frame.Type != FrameType.Settings && frame.Type != FrameType.SynStream) { this.SendRST(frame.StreamId, StatusCode.InvalidStream); return; } switch (frame.Type) { case FrameType.SynStream: //TODO validate syn_stream and send syn_reply or rst this.OnSessionFrame(this, new ControlFrameEventArgs(frame)); break; case FrameType.SynReply: this.OnSessionFrame(this, new ControlFrameEventArgs(frame)); break; case FrameType.Headers: this.OnStreamFrame(this, new HeadersEventArgs(this.streamsStore.GetStreamById(frame.StreamId), frame.Headers)); break; case FrameType.RTS: this.OnStreamFrame(this, new RSTEventArgs(this.streamsStore.GetStreamById(frame.StreamId), frame.StatusCode)); break; case FrameType.Ping: this.OnSocketPing(this, new PingEventArgs(frame.StreamId)); break; case FrameType.Settings: OnSessionFrame(this, new SettingsEventArgs(frame)); break; case FrameType.WindowUpdate: Http2Stream http2Stream = this.streamsStore.GetStreamById(frame.StreamId); http2Stream.UpdateWindowBalance(frame.DeltaWindowSize); break; } } }
/// <summary> /// Writes the go away frame. /// </summary> /// <param name="code">The code.</param> public void WriteGoAway(ResetStatusCode code) { //if there were no streams opened if (_lastId == -1) { _lastId = 0; //then set lastId to 0 as spec tells. (See GoAway chapter) } var frame = new GoAwayFrame(_lastId, code); _writeQueue.WriteFrame(frame); }
private void HandleGoAwayFrame(GoAwayFrame goAwayFrame) { Http2Logger.LogDebug("GOAWAY frame: stream id={0}, status code={1}", goAwayFrame.StreamId, goAwayFrame.StatusCode); if (goAwayFrame.StreamId != 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "GoAway Stream id should always be null"); } _goAwayReceived = true; Http2Logger.LogDebug("last successful id = {0}", goAwayFrame.LastGoodStreamId); Dispose(); }
/// <summary> /// Writes the GOAWAY frame. /// </summary> /// <param name="code">The Reset Status code.</param> public void WriteGoAway(ResetStatusCode code) { //if there were no streams opened if (_lastId == -1) { _lastId = 0; //then set lastId to 0 as spec tells. (See GoAway chapter) } var frame = new GoAwayFrame(_lastId, code); Http2Logger.LogDebug("Sending GOAWAY frame: stream id={0}, code={1}, last good id={2}", frame.StreamId, frame.StatusCode, frame.LastGoodStreamId); _writeQueue.WriteFrame(frame); }
public async Task GoAwayFrame_NoPendingStreams_ConnectionClosed() { HttpClientHandler handler = CreateHttpClientHandler(); handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; using (var server = Http2LoopbackServer.CreateServer()) using (var client = new HttpClient(handler)) { int streamId = await EstablishConnectionAndProcessOneRequestAsync(client, server); // Send GOAWAY. GoAwayFrame goAwayFrame = new GoAwayFrame(streamId, 0, new byte[0], 0); await server.WriteFrameAsync(goAwayFrame); // The client should close the connection. await server.WaitForConnectionShutdownAsync(); // New request should cause a new connection await EstablishConnectionAndProcessOneRequestAsync(client, server); } }
public async Task GoAwayFrame_NonzeroStream_ConnectionError() { HttpClientHandler handler = CreateHttpClientHandler(); handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; using (var server = Http2LoopbackServer.CreateServer()) using (var client = new HttpClient(handler)) { Task sendTask = client.GetAsync(server.Address); await server.EstablishConnectionAsync(); await server.ReadRequestHeaderAsync(); // Send a GoAway frame on stream 1. GoAwayFrame invalidFrame = new GoAwayFrame(0, 0, new byte[0], 1); await server.WriteFrameAsync(invalidFrame); // As this is a connection level error, the client should see the request fail. await Assert.ThrowsAsync <HttpRequestException>(async() => await sendTask); } }
public async Task GoAwayFrame_AllPendingStreamsValid_RequestsSucceedAndConnectionClosed() { HttpClientHandler handler = CreateHttpClientHandler(); handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; using (var server = Http2LoopbackServer.CreateServer()) using (var client = new HttpClient(handler)) { await EstablishConnectionAndProcessOneRequestAsync(client, server); // Issue three requests Task <HttpResponseMessage> sendTask1 = client.GetAsync(server.Address); Task <HttpResponseMessage> sendTask2 = client.GetAsync(server.Address); Task <HttpResponseMessage> sendTask3 = client.GetAsync(server.Address); // Receive three requests int streamId1 = await server.ReadRequestHeaderAsync(); int streamId2 = await server.ReadRequestHeaderAsync(); int streamId3 = await server.ReadRequestHeaderAsync(); Assert.True(streamId1 < streamId2); Assert.True(streamId2 < streamId3); // Send various partial responses // First response: Don't send anything yet // Second response: Send headers, no body yet await server.SendDefaultResponseHeadersAsync(streamId2); // Third response: Send headers, partial body await server.SendDefaultResponseHeadersAsync(streamId3); await server.SendResponseDataAsync(streamId3, new byte[5], endStream : false); // Send a GOAWAY frame that indicates that we will process all three streams GoAwayFrame goAwayFrame = new GoAwayFrame(streamId3, 0, new byte[0], 0); await server.WriteFrameAsync(goAwayFrame); // Finish sending responses await server.SendDefaultResponseHeadersAsync(streamId1); await server.SendResponseDataAsync(streamId1, new byte[10], endStream : true); await server.SendResponseDataAsync(streamId2, new byte[10], endStream : true); await server.SendResponseDataAsync(streamId3, new byte[5], endStream : true); // We will not send any more frames, so send EOF now, and ensure the client handles this properly. server.ShutdownSend(); // Receive all responses HttpResponseMessage response1 = await sendTask1; Assert.Equal(HttpStatusCode.OK, response1.StatusCode); Assert.Equal(10, (await response1.Content.ReadAsByteArrayAsync()).Length); HttpResponseMessage response2 = await sendTask2; Assert.Equal(HttpStatusCode.OK, response2.StatusCode); Assert.Equal(10, (await response2.Content.ReadAsByteArrayAsync()).Length); HttpResponseMessage response3 = await sendTask3; Assert.Equal(HttpStatusCode.OK, response3.StatusCode); Assert.Equal(10, (await response3.Content.ReadAsByteArrayAsync()).Length); // Now that all pending responses have been sent, the client should close the connection. await server.WaitForConnectionShutdownAsync(); // New request should cause a new connection await EstablishConnectionAndProcessOneRequestAsync(client, server); } }
/// <summary> /// Deserializes the data into control frame. /// </summary> /// <param name="data">The data.</param> /// <returns>Deserialized frame.</returns> private BaseFrame DeserializeControlFrame(byte[] data) { FrameType type = GetFrameType(data); ControlFrame frame = new ControlFrame(); switch (type) { case FrameType.RTS: frame.StreamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); frame.StatusCode = (StatusCode)BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 12, 4)); break; case FrameType.Headers: case FrameType.SynReply: ParseControlFrameHeader(ref frame, data); frame.StreamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); ParseControlFrameHeaders(ref frame, data, 12); break; case FrameType.SynStream: ParseControlFrameHeader(ref frame, data); frame.StreamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); frame.AssociatedToStreamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 12, 4)); frame.Priority = (byte)(data[16] >> 5); frame.Slot = data[17]; ParseControlFrameHeaders(ref frame, data, 18); break; case FrameType.Settings: int numberOfEntries = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); frame = new SettingsFrame(numberOfEntries); int headersOffset = 12; for (int i = 0; i < numberOfEntries; i++) { int key = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, headersOffset, 4)); headersOffset += 4; int value = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, headersOffset, 4)); headersOffset += 4; frame.SettingsHeaders.Add(key, value); } ParseControlFrameHeader(ref frame, data); break; case FrameType.GoAway: int lastSeenGoodStreamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); StatusCode status = StatusCode.Success; if (data.Length > 12) { status = (StatusCode)BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 12, 4)); } frame = new GoAwayFrame(lastSeenGoodStreamId, status); ParseControlFrameHeader(ref frame, data); break; case FrameType.Ping: int streamID = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 4, 4)); frame = new ControlFrame { StreamId = streamID }; ParseControlFrameHeader(ref frame, data); break; case FrameType.WindowUpdate: int streamId = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 4, 4)); int deltaWindowSize = BinaryHelper.Int32FromBytes(new ArraySegment <byte>(data, 8, 4)); frame = new WindowUpdateFrame(streamId, deltaWindowSize); ParseControlFrameHeader(ref frame, data); break; } frame.Type = type; return(frame); }
/// <summary> /// Process a goaway frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="goAwayFrame">The goaway frame.</param> private static void ProcessGoAwayFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, GoAwayFrame goAwayFrame) { if (goAwayFrame.StreamId != 0) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "GoAway frame stream id is not equal to 0."); } // Close the connection. throw new ProtocolError(ErrorCodeRegistry.No_Error, "Close the session and connection."); }