private Frame GetFrameType(Frame preamble) { switch (preamble.FrameType) { case FrameType.GoAway: return new GoAwayFrame(preamble); case FrameType.Ping: return new PingFrame(preamble); case FrameType.RstStream: return new RstStreamFrame(preamble); case FrameType.Settings: return new SettingsFrame(preamble); case FrameType.Headers: return new HeadersFrame(preamble); case FrameType.WindowUpdate: return new WindowUpdateFrame(preamble); case FrameType.Data: return new DataFrame(preamble); default: throw new NotImplementedException("Frame type: " + preamble.FrameType); } }
// Queue up a fully rendered frame to send public Task WriteFrameAsync(Frame frame, Priority priority) { PriorityQueueEntry entry = new PriorityQueueEntry(frame, priority); Enqueue(entry); SignalDataAvailable(); return entry.Task; }
public async Task<Frame> ReadFrameAsync() { Frame preamble = new Frame(); if (!await TryFillAsync(preamble.Buffer, 0, preamble.Buffer.Length, _cancel)) { return null; } if (_validateFirstFrameIsControl) { if (!preamble.IsControl) { // Probably a HTTP/1.1 text formatted request. We could check if it starts with 'GET' // Is it sane to send a response here? What kind of response? 1.1 text, or 2.0 binary? throw new ProtocolViolationException("First frame is not a control frame."); } _validateFirstFrameIsControl = false; } // TODO: If this is the first frame, verify that it is in fact a control frame, and that it is not a HTTP/1.1 text request. // Not applicable after an HTTP/1.1->HTTP-01/2.0 upgrade handshake. if (preamble.IsControl && preamble.Version != Constants.CurrentProtocolVersion) { throw new NotSupportedException("This control frame uses an unsupported version: " + preamble.Version); } Frame wholeFrame = GetFrameType(preamble); if (!await TryFillAsync(wholeFrame.Buffer, Constants.FramePreambleSize, wholeFrame.Buffer.Length - Constants.FramePreambleSize, _cancel)) { return null; } return wholeFrame; }
// Queue up a fully rendered frame to send public void WriteFrame(Frame frame) { //Do not write to already closed stream if (frame.FrameType != FrameType.Settings && frame.FrameType != FrameType.GoAway && frame.FrameType != FrameType.Ping && _streams[frame.StreamId] == null) { return; } var priority = frame.StreamId != 0 ? _streams[frame.StreamId].Priority : Priority.Pri7; IQueueItem entry = null; if (IsPriorityTurnedOn) { entry = new PriorityQueueEntry(frame, priority); } else { entry = new QueueEntry(frame); } _messageQueue.Enqueue(entry); }
public Frame ReadFrame() { var preamble = new Frame(); if (!TryFill(preamble.Buffer, 0, preamble.Buffer.Length)) { return null; } var wholeFrame = GetFrameType(preamble); if (!TryFill(wholeFrame.Buffer, Constants.FramePreambleSize, wholeFrame.Buffer.Length - Constants.FramePreambleSize)) { return null; } return wholeFrame; }
private Frame GetFrameType(Frame preamble) { if (!preamble.IsControl) { return new DataFrame(preamble); } switch (preamble.FrameType) { case ControlFrameType.Credential: return new CredentialFrame(preamble); case ControlFrameType.GoAway: return new GoAwayFrame(preamble); case ControlFrameType.Headers: return new HeadersFrame(preamble); case ControlFrameType.Ping: return new PingFrame(preamble); case ControlFrameType.RstStream: return new RstStreamFrame(preamble); case ControlFrameType.Settings: return new SettingsFrame(preamble); case ControlFrameType.SynReply: return new SynReplyFrame(preamble); case ControlFrameType.SynStream: return new SynStreamFrame(preamble); case ControlFrameType.WindowUpdate: return new WindowUpdateFrame(preamble); default: throw new NotImplementedException("Frame type: " + preamble.FrameType); } }
public FrameReceivedEventArgs(Http2Stream stream, Frame frame) { Stream = stream; Frame = frame; }
// Incoming public SettingsFrame(Frame preamble) : base(preamble) { }
public PriorityQueueEntry(Frame frame, Priority priority) { _frame = frame; _priority = priority; _tcs = new TaskCompletionSource<object>(); }
public QueueEntry(Frame frame) { _frame = frame; }
// Create an incoming frame public SynStreamFrame(Frame preamble) : base(preamble) { }
/// <summary> /// Create an incoming frame /// </summary> /// <param name="preamble">Frame preamble</param> public HeadersFrame(Frame preamble) : base(preamble) { }
public PriorityQueueEntry(Frame frame, Priority priority) { _frame = frame; _priority = priority; }
// Incoming public RstStreamFrame(Frame preamble) : base(preamble) { }
// For incoming frames protected Frame(Frame preamble) : this(new byte[Constants.FramePreambleSize + preamble.FrameLength]) { System.Buffer.BlockCopy(preamble.Buffer, 0, Buffer, 0, Constants.FramePreambleSize); }
// Create an incoming frame public SynReplyFrame(Frame preamble) : base(preamble) { }
// For incoming public DataFrame(Frame preamble) : base(preamble) { }
public DataFrameSentEventArgs(Frame frame) { this.Id = frame.StreamId; this.DataAmount = frame.Buffer.Length - Constants.FramePreambleSize; }
// Incoming protected StreamControlFrame(Frame preamble) : base(preamble) { }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; //Spec 03 tells that frame with continues flag MUST be followed by a frame with the same type //and the same stread id. if (_toBeContinuedHeaders != null) { if (_toBeContinuedFrame.FrameType != frame.FrameType || _toBeContinuedFrame.StreamId != frame.StreamId) { //If not, we must close the session. Dispose(); return; } } try { switch (frame.FrameType) { case FrameType.Headers: Console.WriteLine("New headers with id = " + frame.StreamId); var headersFrame = (Headers) frame; var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders, frame.StreamId % 2 != 0); var headers = decompressedHeaders; if (!headersFrame.IsEndHeaders) { _toBeContinuedHeaders = decompressedHeaders; _toBeContinuedFrame = headersFrame; break; } if (_toBeContinuedHeaders != null) { headers.AddRange(_toBeContinuedHeaders); } //Remote side tries to open more streams than allowed if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { Dispose(); return; } string path; try { path = headers.GetValue(":path"); } catch (KeyNotFoundException) { path = !_handshakeHeaders.ContainsKey(":path") ? _handshakeHeaders[":path"] : @"\index.html"; headers.Add(new Tuple<string, string, IAdditionalHeaderInfo>(":path", path, null)); } stream = new Http2Stream(headers, headersFrame.StreamId, _writeQueue, _flowControlManager, _comprProc); ActiveStreams[stream.Id] = stream; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } }; _toBeContinuedFrame = null; _toBeContinuedHeaders = null; break; case FrameType.Priority: var priorityFrame = (PriorityFrame) frame; stream = GetStream(priorityFrame.StreamId); if (_usePriorities) { stream.Priority = priorityFrame.Priority; } break; case FrameType.RstStream: var resetFrame = (RstStreamFrame) frame; stream = GetStream(resetFrame.StreamId); Console.WriteLine("Got rst with code {0}", resetFrame.StatusCode); stream.Dispose(); break; case FrameType.Data: var dataFrame = (DataFrame) frame; stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(2000000); } break; case FrameType.Ping: var pingFrame = (PingFrame) frame; if (pingFrame.IsPong) { _wasPingReceived = true; _pingReceived.Set(); } else { var pingResonseFrame = new PingFrame(true); _writeQueue.WriteFrame(pingResonseFrame); } break; case FrameType.Settings: //Not first frame in the session. //Client initiates connection and sends settings before request. //It means that if server sent settings before it will not be a first frame, //because client initiates connection. if (_ourEnd == ConnectionEnd.Server && !_wasSettingsReceived && ActiveStreams.Count != 0) { Dispose(); return; } _wasSettingsReceived = true; _settingsManager.ProcessSettings((SettingsFrame) frame, this, _flowControlManager); break; case FrameType.WindowUpdate: if (_useFlowControl) { var windowFrame = (WindowUpdateFrame) frame; stream = GetStream(windowFrame.StreamId); stream.UpdateWindowSize(windowFrame.Delta); stream.PumpUnshippedFrames(); } break; case FrameType.GoAway: _goAwayReceived = true; Dispose(); break; default: throw new NotImplementedException(frame.FrameType.ToString()); } if (stream != null && frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Console.WriteLine("Final frame received for stream with id = " + stream.Id); stream.EndStreamReceived = true; } if (stream != null && OnFrameReceived != null) { OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); } } //Frame came for already closed stream. Ignore it. //Spec: //An endpoint that sends RST_STREAM MUST ignore //frames that it receives on closed streams if it sends RST_STREAM. // //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM or a frame //containing a END_STREAM flag on that stream MUST treat that as a //stream error (Section 5.4.2) of type PROTOCOL_ERROR. catch (Http2StreamNotFoundException) { if (stream != null) { stream.WriteRst(ResetStatusCode.ProtocolError); } else { //GoAway? } } }
public FrameSentArgs(Frame frame) { Frame = frame; }
/// <summary> /// Returns the StreamId from the given frame, or -1 if this frame type does not have one. /// </summary> /// <param name="id"></param> /// <returns></returns> public static int GetStreamId(Frame frame) { if (frame.IsControl) { ControlFrame controlFrame = (ControlFrame)frame; switch (controlFrame.FrameType) { case ControlFrameType.Headers: case ControlFrameType.RstStream: case ControlFrameType.SynReply: case ControlFrameType.SynStream: case ControlFrameType.WindowUpdate: StreamControlFrame streamFrame = (StreamControlFrame)controlFrame; return streamFrame.StreamId; case ControlFrameType.Credential: case ControlFrameType.GoAway: case ControlFrameType.Settings: case ControlFrameType.Ping: return -1; default: throw new NotImplementedException(controlFrame.FrameType.ToString()); } } else { DataFrame dataFrame = (DataFrame)frame; return dataFrame.StreamId; } }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; //Spec 03 tells that frame with continues flag MUST be followed by a frame with the same type //and the same stread id. if (_toBeContinuedHeaders.Count != 0) { if (_toBeContinuedFrame.FrameType != frame.FrameType || _toBeContinuedFrame.StreamId != frame.StreamId) { //If not, we must close the session. Dispose(); return; } } try { switch (frame.FrameType) { case FrameType.Headers: Http2Logger.LogDebug("New headers with id = " + frame.StreamId); var headersFrame = (HeadersFrame)frame; var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); if (!headersFrame.IsEndHeaders) { _toBeContinuedHeaders.AddRange(decompressedHeaders); _toBeContinuedFrame = headersFrame; break; } headers.AddRange(_toBeContinuedHeaders); _toBeContinuedHeaders.Clear(); _toBeContinuedFrame = null; headersFrame.Headers.AddRange(headers); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); } stream = GetStream(headersFrame.StreamId); if (stream == null) { if (_ourEnd == ConnectionEnd.Server) { string path = headers.GetValue(":path"); if (path == null) { path = _handshakeHeaders.ContainsKey(":path") ? _handshakeHeaders[":path"] : @"\index.html"; headers.Add(new KeyValuePair<string, string>(":path", path)); } } else { headers.AddRange(_handshakeHeaders); } stream = CreateStream(headers, frame.StreamId); } break; case FrameType.Priority: var priorityFrame = (PriorityFrame)frame; Http2Logger.LogDebug("Priority frame. StreamId: {0} Priority: {1}", priorityFrame.StreamId, priorityFrame.Priority); stream = GetStream(priorityFrame.StreamId); if (_usePriorities) { stream.Priority = priorityFrame.Priority; } break; case FrameType.RstStream: var resetFrame = (RstStreamFrame)frame; stream = GetStream(resetFrame.StreamId); if (stream != null) { Http2Logger.LogDebug("RST frame with code " + resetFrame.StatusCode); stream.Dispose(); } break; case FrameType.Data: var dataFrame = (DataFrame)frame; Http2Logger.LogDebug("Data frame. StreamId:{0} Length:{1}", dataFrame.StreamId, dataFrame.FrameLength); stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream != null && stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(InitialWindowSize); } break; case FrameType.Ping: var pingFrame = (PingFrame)frame; Http2Logger.LogDebug("Ping frame with StreamId:{0} Payload:{1}", pingFrame.StreamId, pingFrame.Payload.Count); if (pingFrame.FrameLength != PingFrame.PayloadLength) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Ping payload size is not equal to 8"); } if (pingFrame.IsPong) { _wasPingReceived = true; _pingReceived.Set(); } else { var pongFrame = new PingFrame(true, pingFrame.Payload.ToArray()); _writeQueue.WriteFrame(pongFrame); } break; case FrameType.Settings: //Not first frame in the session. //Client initiates connection and sends settings before request. //It means that if server sent settings before it will not be a first frame, //because client initiates connection. if (_ourEnd == ConnectionEnd.Server && !_wasSettingsReceived && (ActiveStreams.Count > 0)) { Dispose(); return; } var settingFrame = (SettingsFrame)frame; Http2Logger.LogDebug("Settings frame. Entry count: {0} StreamId: {1}", settingFrame.EntryCount, settingFrame.StreamId); _wasSettingsReceived = true; _settingsManager.ProcessSettings(settingFrame, this, _flowControlManager); if (_ourEnd == ConnectionEnd.Server && _sessionSocket.SecureProtocol == SecureProtocol.None) { //The HTTP/1.1 request that is sent prior to upgrade is associated with //stream 1 and is assigned the highest possible priority. Stream 1 is //implicitly half closed from the client toward the server, since the //request is completed as an HTTP/1.1 request. After commencing the //HTTP/2.0 connection, stream 1 is used for the response. stream = CreateStream(Priority.Pri0); stream.EndStreamReceived = true; stream.Headers.Add(new KeyValuePair<string, string>(":method", _handshakeHeaders[":method"])); stream.Headers.Add(new KeyValuePair<string, string>(":path", _handshakeHeaders[":path"])); OnFrameReceived(this, new FrameReceivedEventArgs(stream, new HeadersFrame(stream.Id, false))); } break; case FrameType.WindowUpdate: if (_useFlowControl) { var windowFrame = (WindowUpdateFrame)frame; Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowFrame.Delta, windowFrame.StreamId); stream = GetStream(windowFrame.StreamId); if (stream != null) { stream.UpdateWindowSize(windowFrame.Delta); stream.PumpUnshippedFrames(); } } break; case FrameType.GoAway: _goAwayReceived = true; Http2Logger.LogDebug("GoAway frame received"); Dispose(); break; default: throw new NotImplementedException(frame.FrameType.ToString()); } if (stream != null && frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Http2Logger.LogDebug("Final frame received for stream with id = " + stream.Id); stream.EndStreamReceived = true; } if (stream != null && OnFrameReceived != null) { OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); } } //Frame came for already closed stream. Ignore it. //Spec: //An endpoint that sends RST_STREAM MUST ignore //frames that it receives on closed streams if it sends RST_STREAM. // //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM or a frame //containing a END_STREAM flag on that stream MUST treat that as a //stream error (Section 5.4.2) of type PROTOCOL_ERROR. catch (Http2StreamNotFoundException) { if (stream != null) { stream.WriteRst(ResetStatusCode.ProtocolError); } else { //GoAway? } } catch (CompressionError ex) { //The endpoint is unable to maintain the compression context for the connection. Http2Logger.LogError("Compression error occurred: " + ex.Message); Close(ResetStatusCode.CompressionError); } catch (ProtocolError pEx) { Http2Logger.LogError("Protocol error occurred: " + pEx.Message); Close(pEx.Code); } }
// Incoming public CredentialFrame(Frame preamble) : base(preamble) { }