public void WriteWindowUpdate(Int32 windowSize) { if (windowSize <= 0) { throw new ArgumentOutOfRangeException("windowSize should be greater than 0"); } if (windowSize > Constants.MaxWindowSize) { throw new ProtocolError(ResetStatusCode.FlowControlError, "window size is too large"); } //09 -> 6.9.4. Ending Flow Control //After a receiver reads in a frame that marks the end of a stream (for //example, a data stream with a END_STREAM flag set), it MUST cease //transmission of WINDOW_UPDATE frames for that stream. if (Closed) { return; } //TODO handle idle state var frame = new WindowUpdateFrame(_id, windowSize); _writeQueue.WriteFrame(frame); Http2Logger.LogDebug("Sending WINDOW_UPDATE: stream id={0}, delta={1}", frame.StreamId, frame.Delta); if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(frame)); } }
private void HandleRstFrame(RstStreamFrame resetFrame, out Http2Stream stream) { Http2Logger.LogDebug("RST_STREAM frame: stream id={0}, status code={1}", resetFrame.StreamId, resetFrame.StatusCode); /* 12 -> 6.4 * RST_STREAM frames MUST be associated with a stream. If a RST_STREAM * frame is received with a stream identifier of 0x0, the recipient MUST * treat this as a connection error of type PROTOCOL_ERROR. */ if (resetFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Rst frame with stream id=0"); } stream = GetStream(resetFrame.StreamId); if (stream.Closed) { /* 12 -> 5.4.2 * An endpoint MUST NOT send a RST_STREAM in response to an RST_STREAM * frame, to avoid looping. */ if (!stream.WasRstSent) { throw new Http2StreamNotFoundException(resetFrame.StreamId); } return; } if (!(stream.ReservedRemote || stream.Opened || stream.HalfClosedLocal)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Rst for non opened or reserved stream"); } stream.Close(ResetStatusCode.None); }
public void UpdateWindowSize(Int32 delta) { if (IsFlowControlEnabled) { //09 -> 6.9.1. The Flow Control Window //A sender MUST NOT allow a flow control window to exceed 2^31 - 1 //bytes. If a sender receives a WINDOW_UPDATE that causes a flow //control window to exceed this maximum it MUST terminate either the //stream or the connection, as appropriate. For streams, the sender //sends a RST_STREAM with the error code of FLOW_CONTROL_ERROR code; //for the connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code. WindowSize += delta; if (WindowSize > Constants.MaxWindowSize) { Http2Logger.LogDebug("Incorrect window size : {0}", WindowSize); throw new ProtocolError(ResetStatusCode.FlowControlError, String.Format("Incorrect window size : {0}", WindowSize)); } } //Unblock stream if it was blocked by flowCtrlManager if (WindowSize > 0 && IsFlowControlBlocked) { IsFlowControlBlocked = false; } }
/// <summary> /// Writes the data frame. Method is used for pushing unshipped frames. /// If flow control manager has blocked stream, frames are adding to the unshippedFrames collection. /// After window update for that stream they will be delivered. /// </summary> /// <param name="dataFrame">The data frame.</param> private void WriteDataFrame(DataFrame dataFrame) { if (dataFrame == null) { throw new ArgumentNullException("dataFrame is null"); } if (Closed) { return; } if (!IsFlowControlBlocked) { _writeQueue.WriteFrame(dataFrame); SentDataAmount += dataFrame.Data.Count; _flowCrtlManager.DataFrameSentHandler(this, new DataFrameSentEventArgs(dataFrame)); if (dataFrame.IsEndStream) { Http2Logger.LogDebug("Bytes sent: {0}", SentDataAmount); HalfClosedLocal = true; } if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(dataFrame)); } } else { _unshippedFrames.Enqueue(dataFrame); } }
public void Close(ResetStatusCode code) { if (Closed || Idle) { return; } OnFrameSent = null; Http2Logger.LogDebug("Total outgoing data frames volume " + SentDataAmount); Http2Logger.LogDebug("Total frames sent: {0}", FramesSent); Http2Logger.LogDebug("Total frames received: {0}", FramesReceived); if (code == ResetStatusCode.Cancel || code == ResetStatusCode.InternalError) { WriteRst(code); } _flowCrtlManager.StreamClosedHandler(this); Closed = true; if (OnClose != null) { OnClose(this, new StreamClosedEventArgs(_id)); } OnClose = null; Http2Logger.LogDebug("Stream closed " + _id); }
/// <summary> /// Writes the SETTINGS frame. /// </summary> /// <param name="settings">The settings pairs.</param> /// <param name="isAck">The ACK flag.</param> public void WriteSettings(SettingsPair[] settings, bool isAck) { if (settings == null) { throw new ArgumentNullException("settings"); } var frame = new SettingsFrame(new List <SettingsPair>(settings), isAck); Http2Logger.LogDebug("Sending SETTINGS frame: stream id={0}, payload len={1}, is ack={2}, count={3}", frame.StreamId, frame.PayloadLength, frame.IsAck, frame.EntryCount); foreach (var s in settings) { Http2Logger.LogDebug("{0}: {1}", s.Id.ToString(), s.Value); } _writeQueue.WriteFrame(frame); if (!isAck && !_settingsAckReceived.WaitOne(60000)) { WriteGoAway(ResetStatusCode.SettingsTimeout); Dispose(); } _settingsAckReceived.Reset(); if (OnSettingsSent != null) { OnSettingsSent(this, new SettingsSentEventArgs(frame)); } }
/// <summary> /// Writes the data frame. /// If flow control manager has blocked stream, frames are adding to the unshippedFrames collection. /// After window update for that stream they will be delivered. /// </summary> /// <param name="dataFrame">The data frame.</param> public void WriteDataFrame(DataFrame dataFrame) { if (IsFlowControlBlocked == false) { _writeQueue.WriteFrame(dataFrame); SentDataAmount += dataFrame.FrameLength; _flowCrtlManager.DataFrameSentHandler(this, new DataFrameSentEventArgs(dataFrame)); if (dataFrame.IsEndStream) { Http2Logger.LogDebug("Transfer end"); EndStreamSent = true; } if (OnFrameSent != null) { OnFrameSent(this, new FrameSentArgs(dataFrame)); } } else { _unshippedFrames.Enqueue(dataFrame); } }
/// <summary> /// Starts session. /// </summary> /// <returns></returns> public Task Start() { Http2Logger.LogDebug("Session start"); //Write settings. Settings must be the first frame in session. if (_useFlowControl) { WriteSettings(new[] { new SettingsPair(SettingsFlags.None, SettingsIds.InitialWindowSize, 200000) }); } else { WriteSettings(new[] { new SettingsPair(SettingsFlags.None, SettingsIds.InitialWindowSize, 200000), new SettingsPair(SettingsFlags.None, SettingsIds.FlowControlOptions, (byte)FlowControlOptions.DontUseFlowControl) }); } // Listen for incoming Http/2.0 frames var incomingTask = new Task(PumpIncommingData); // Send outgoing Http/2.0 frames var outgoingTask = new Task(() => PumpOutgoingData()); incomingTask.Start(); outgoingTask.Start(); return(Task.WhenAll(incomingTask, outgoingTask)); }
private void HandlePingFrame(PingFrame pingFrame) { Http2Logger.LogDebug("PING frame: stream id={0}, payload={1}", pingFrame.StreamId, pingFrame.Payload.Count); /* 12 -> 6.7 * PING frames are not associated with any individual stream. If a PING * frame is received with a stream identifier field value other than * 0x0, the recipient MUST respond with a connection error of type PROTOCOL_ERROR. */ if (pingFrame.StreamId != 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming ping frame with stream id != 0"); } if (pingFrame.PayloadLength != PingFrame.DefPayloadLength) { throw new ProtocolError(ResetStatusCode.FrameSizeError, "Ping payload size is not equal to 8"); } if (pingFrame.IsAck) { _pingReceived.Set(); } else { var pingAckFrame = new PingFrame(true, pingFrame.Payload.ToArray()); _writeQueue.WriteFrame(pingAckFrame); } }
private void StartResponse() { Debug.Assert(!_responseStarted, "Response started more than once"); _responseStarted = true; Http2Logger.LogDebug("Transfer begin"); SendHeaders(final: false); }
/// <summary> /// Writes the data frame. /// If flow control manager has blocked stream, frames are adding to the unshippedFrames collection. /// After window update for that stream they will be delivered. /// </summary> /// <param name="data">The data.</param> /// <param name="isEndStream">if set to <c>true</c> [is fin].</param> public void WriteDataFrame(ArraySegment <byte> data, bool isEndStream) { if (data.Array == null) { throw new ArgumentNullException("data is null"); } if (Closed) { return; } var dataFrame = new DataFrame(_id, data, isEndStream, true); Http2Logger.LogDebug( "Sending DATA frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " + "end stream={5}", dataFrame.StreamId, dataFrame.PayloadLength, dataFrame.HasPadding, dataFrame.PadHigh, dataFrame.PadLow, dataFrame.IsEndStream); //We cant let lesser frame that were passed through flow control window //be sent before greater frames that were not passed through flow control window //09 -> 6.9.1. The Flow Control Window //The sender MUST NOT //send a flow controlled frame with a length that exceeds the space //available in either of the flow control windows advertised by the receiver. if (_unshippedFrames.Count != 0 || WindowSize - dataFrame.Data.Count < 0) { _unshippedFrames.Enqueue(dataFrame); return; } if (!IsFlowControlBlocked) { _writeQueue.WriteFrame(dataFrame); SentDataAmount += dataFrame.Data.Count; _flowCrtlManager.DataFrameSentHandler(this, new DataFrameSentEventArgs(dataFrame)); if (dataFrame.IsEndStream) { Http2Logger.LogDebug("Transfer end"); Http2Logger.LogDebug("Sent bytes: {0}", SentDataAmount); HalfClosedLocal = true; } if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(dataFrame)); } } else { _unshippedFrames.Enqueue(dataFrame); } }
/// <summary> /// Accepts client and deals handshake with it. /// </summary> internal void Accept() { SecureSocket incomingClient; using (var monitor = new ALPNExtensionMonitor()) { incomingClient = _server.AcceptSocket(monitor); } Http2Logger.LogDebug("New connection accepted"); Task.Run(() => HandleAcceptedClient(incomingClient)); }
private void HandleWindowUpdateFrame(WindowUpdateFrame windowUpdateFrame, out Http2Stream stream) { Http2Logger.LogDebug("WINDOW_UPDATE frame: stream id={0}, delta={1}", windowUpdateFrame.StreamId, windowUpdateFrame.Delta); if (!_useFlowControl) { stream = null; return; } // TODO Remove this hack /* The WINDOW_UPDATE frame can be specific to a stream or to the entire * connection. In the former case, the frame's stream identifier * indicates the affected stream; in the latter, the value "0" indicates * that the _entire connection_ is the subject of the frame. */ if (windowUpdateFrame.StreamId == 0) { _flowControlManager.StreamsInitialWindowSize += windowUpdateFrame.Delta; stream = null; return; } stream = GetStream(windowUpdateFrame.StreamId); /* 12 -> 6.9 * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the * END_STREAM flag. This means that a receiver could receive a * WINDOW_UPDATE frame on a "half closed (remote)" or "closed" stream. * A receiver MUST NOT treat this as an error. */ if (!(stream.Opened || stream.HalfClosedRemote || stream.HalfClosedLocal || stream.Closed)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "window update in incorrect state"); } //09 -> 6.9. WINDOW_UPDATE //The payload of a WINDOW_UPDATE frame is one reserved bit, plus an //unsigned 31-bit integer indicating the number of bytes that the //sender can transmit in addition to the existing flow control window. //The legal range for the increment to the flow control window is 1 to //2^31 - 1 (0x7fffffff) bytes. if (!(0 < windowUpdateFrame.Delta && windowUpdateFrame.Delta <= Constants.MaxPriority)) { Http2Logger.LogDebug("Incorrect window update delta : {0}", windowUpdateFrame.Delta); throw new ProtocolError(ResetStatusCode.FlowControlError, String.Format("Incorrect window update delta : {0}", windowUpdateFrame.Delta)); } stream.UpdateWindowSize(windowUpdateFrame.Delta); stream.PumpUnshippedFrames(); }
public static void Http11SendResponse(SecureSocket socket) { string[] headers = GetHttp11Headers(socket); string filename = GetFileName(headers); if (headers.Length == 0) { Http2Logger.LogError("Request headers empty!"); } string path = Path.GetFullPath(AssemblyPath + @"\root" + filename); string contentType = ContentTypes.GetTypeFromFileName(filename); if (!File.Exists(path)) { Http2Logger.LogError("File " + filename + " not found"); SendResponse(socket, new byte[0], StatusCode.Code404NotFound, contentType); socket.Close(); return; } try { using (var sr = new StreamReader(path)) { string file = sr.ReadToEnd(); var fileBytes = Encoding.UTF8.GetBytes(file); int sent = SendResponse(socket, fileBytes, StatusCode.Code200Ok, contentType); Http2Logger.LogDebug(string.Format("Sent: {0} bytes", sent)); Http2Logger.LogInfo("File sent: " + filename); socket.Close(); if (OnSocketClosed != null) { OnSocketClosed(null, new SocketCloseEventArgs()); } } } catch (Exception ex) { var msgBytes = Encoding.UTF8.GetBytes(ex.Message); SendResponse(socket, msgBytes, StatusCode.Code500InternalServerError, contentType); Http2Logger.LogError(ex.Message); } }
private void HandleRequest(Stream incomingClient, string alpnSelectedProtocol, bool backToHttp11) { //Server checks selected protocol and calls http2 or http11 layer if (backToHttp11 || alpnSelectedProtocol == Protocols.Http1) { Http2Logger.LogDebug("Selected protocol: HTTP/1.1"); new Http11ProtocolOwinAdapter(incomingClient, SslProtocols.Tls, _next.Invoke).ProcessRequest(); return; } //ALPN selected http2. No need to perform upgrade handshake. Http2Logger.LogDebug("Selected protocol (ALPN): http/2"); OpenHttp2Session(incomingClient); }
/// <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); }
private async void OpenHttp2Session(SecureSocket incomingClient, IDictionary <string, object> handshakeResult) { Http2Logger.LogDebug("Handshake successful"); _session = new Http2Session(incomingClient, ConnectionEnd.Server, _usePriorities, _useFlowControl, handshakeResult); _session.OnFrameReceived += FrameReceivedHandler; try { await _session.Start(); } catch (Exception) { Http2Logger.LogError("Client was disconnected"); } }
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(); }
public void HandleHttp11Response(byte[] responseBinaryHeaders, int offset, int length) { var bytes = new byte[offset + length]; Buffer.BlockCopy(responseBinaryHeaders, 0, bytes, offset, length); var response = ParseHeadersAndReadResponseBody(bytes); //TODO Handle headers somehow if it's needed using (var stream = new FileStream(AssemblyName + _path, FileMode.Create)) { stream.Write(response.Value, 0, response.Value.Length); } Http2Logger.LogDebug("Response was saved as {0}", _path); }
// If no response headers have been sent yet, send them. internal void FinishResponse() { if (!_responseStarted) { Http2Logger.LogDebug("Transfer begin"); SendHeaders(final: true); Http2Logger.LogDebug("Transfer end"); } else { // End the data stream. _protocolStream.WriteDataFrame(new ArraySegment <byte>(new byte[0]), isEndStream: true); } }
private void HandleBlockedFrame(Frame blockedFrame) { Http2Logger.LogDebug("BLOCKED frame: stream id={0}, payload len={1}", blockedFrame.StreamId, blockedFrame.PayloadLength); /* 12 -> 6.12 * The BLOCKED frame defines no flags and contains no payload. A * receiver MUST treat the receipt of a BLOCKED frame with a payload as * a connection error of type FRAME_SIZE_ERROR. */ if (blockedFrame.PayloadLength > 0) { throw new ProtocolError(ResetStatusCode.FrameSizeError, "Blocked frame with non-zero payload"); } // TODO: receiving BLCOKED frame is not implemented. // see https://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-6.12 }
public virtual void Dispose() { if (_isDisposed) { return; } if (_session != null) { _session.Dispose(); } OnFirstSettingsSent = null; _isDisposed = true; Http2Logger.LogDebug("Adapter disposed"); }
private void Close(ResetStatusCode status) { if (_disposed) { return; } Http2Logger.LogDebug("Session closing"); _disposed = true; // Dispose of all streams foreach (Http2Stream stream in ActiveStreams.Values) { //Cancel all opened streams stream.WriteRst(ResetStatusCode.Cancel); stream.Dispose(); } OnSettingsSent = null; OnFrameReceived = null; OnFrameSent = null; if (!_goAwayReceived) { WriteGoAway(status); if (_writeQueue != null) { _writeQueue.Flush(); _writeQueue.Dispose(); } } _comprProc.Dispose(); _sessionSocket.Close(); if (OnSessionDisposed != null) { OnSessionDisposed(this, null); } OnSessionDisposed = null; Http2Logger.LogDebug("Session closed"); }
private void HandleDataFrame(DataFrame dataFrame, out Http2Stream stream) { Http2Logger.LogDebug("DATA frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " + "end stream={5}", dataFrame.StreamId, dataFrame.PayloadLength, dataFrame.HasPadding, dataFrame.PadHigh, dataFrame.PadLow, dataFrame.IsEndStream); /* 12 -> 6.1 * DATA frames MUST be associated with a stream. If a DATA frame is * received whose stream identifier field is 0x0, the recipient MUST * respond with a connection error of type PROTOCOL_ERROR. */ if (dataFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with stream id=0"); } /* 12 -> 6.1 * An endpoint that has not enabled DATA frame compression MUST * treat the receipt of a DATA frame with the COMPRESSED flag set as a * connection error of type PROTOCOL_ERROR. */ if (dataFrame.IsCompressed) { throw new ProtocolError(ResetStatusCode.ProtocolError, "GZIP compression is not enabled"); } stream = GetStream(dataFrame.StreamId); if (stream.Closed) { throw new Http2StreamNotFoundException(dataFrame.StreamId); } if (!(stream.Opened || stream.HalfClosedLocal)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "data in non opened or half closed local stream"); } if (stream.IsFlowControlEnabled && !dataFrame.IsEndStream) { stream.WriteWindowUpdate(Constants.MaxFramePayloadSize); } }
/// <summary> /// Pumps the incomming data and calls dispatch for it /// </summary> private void PumpIncommingData() { while (!_goAwayReceived && !_disposed) { Frame frame; try { frame = _frameReader.ReadFrame(); if (!_wasResponseReceived) { _wasResponseReceived = true; } } catch (IOException) { //Connection was closed by the remote endpoint Http2Logger.LogInfo("Connection was closed by the remote endpoint"); Dispose(); break; } catch (Exception) { // Read failure, abort the connection/session. Http2Logger.LogInfo("Read failure, abort the connection/session"); Dispose(); break; } if (frame != null) { DispatchIncomingFrame(frame); } else { // Looks like connection was lost Dispose(); break; } } Http2Logger.LogDebug("Read thread finished"); }
/// <summary> /// Pumps the outgoing data to write queue /// </summary> /// <returns></returns> private void PumpOutgoingData() { try { _writeQueue.PumpToStream(_cancelSessionToken); } catch (OperationCanceledException) { Http2Logger.LogError("Handling session was cancelled"); Dispose(); } catch (Exception) { Http2Logger.LogError("Sending frame was cancelled because connection was lost"); Dispose(); } Http2Logger.LogDebug("Write thread finished"); }
/// <summary> /// Pings session. /// </summary> /// <returns></returns> public TimeSpan Ping() { var pingFrame = new PingFrame(false); _writeQueue.WriteFrame(pingFrame); var now = DateTime.UtcNow; if (!_pingReceived.WaitOne(3000)) { //Remote endpoint was not answer at time. Dispose(); } _pingReceived.Reset(); var newNow = DateTime.UtcNow; Http2Logger.LogDebug("Ping: " + (newNow - now).Milliseconds); return(newNow - now); }
private void Listen() { Http2Logger.LogInfo("Server running at port " + _port); _server.Start(); while (!_disposed) { try { var client = new HttpConnectingClient(_server, _next.Invoke, _serverCert, _isSecure, _useHandshake, _usePriorities, _useFlowControl); client.Accept(_cancelAccept.Token); } catch (Exception ex) { Http2Logger.LogError("Unhandled exception was caught: " + ex.Message); } } Http2Logger.LogDebug("Listen thread was finished"); }
private async void OpenHttp2Session(Stream incomingClientStream) { Http2Logger.LogDebug("Handshake successful"); using (var messageHandler = new Http2OwinMessageHandler(incomingClientStream, ConnectionEnd.Server, incomingClientStream is SslStream, _next, _cancelClientHandling.Token)) { try { await messageHandler.StartSessionAsync(); } catch (Exception) { Http2Logger.LogError("Client was disconnected"); } } // GC.Collect(); }
public void Dispose() { if (Disposed) { return; } Http2Logger.LogDebug("Total outgoing data frames volume " + SentDataAmount); if (OnClose != null) { OnClose(this, new StreamClosedEventArgs(_id)); } Http2Logger.LogDebug("Stream closed " + _id); OnClose = null; _flowCrtlManager.StreamClosedHandler(this); Disposed = true; }