public void Remove(HeadersSequence item) { lock (_modificationLock) { _collection.Remove(item); } }
public void Add(HeadersSequence item) { lock (_modificationLock) { _collection.Add(item); } }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); Http2Logger.LogDebug("New continuation with id = " + contFrame.StreamId); if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with id = 0"); } var serHeaders = new byte[contFrame.CompressedHeaders.Count]; Buffer.BlockCopy(contFrame.CompressedHeaders.Array, contFrame.CompressedHeaders.Offset, serHeaders, 0, serHeaders.Length); var decomprHeaders = _comprProc.Decompress(serHeaders); var contHeaders = new HeadersList(decomprHeaders); foreach (var header in contHeaders) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value); } contFrame.Headers.AddRange(contHeaders); var sequence = _headersSequences.Find(seq => seq.StreamId == contFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(contFrame.StreamId, contFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(contFrame); } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(contFrame.StreamId); if (stream == null) { stream = CreateStream(sequence.Headers, contFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
/// <summary> /// Creates new http2 stream. /// </summary> /// <param name="priority">The stream priority.</param> /// <returns></returns> /// <exception cref="System.InvalidOperationException">Thrown when trying to create more streams than allowed by the remote side</exception> public Http2Stream CreateStream(int priority) { if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_ourEnd) + 1 > RemoteMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var id = GetNextId(); if (_usePriorities) { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager, priority); } else { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager); } var streamSequence = new HeadersSequence(id, (new HeadersFrame(id, priority))); _headersSequences.Add(streamSequence); ActiveStreams[id].OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; ActiveStreams[id].OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Can't remove stream from ActiveStreams."); } var streamSeq = _headersSequences.Find(id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return ActiveStreams[id]; }
internal Http2Stream CreateStream(HeadersSequence sequence) { if (sequence == null) throw new ArgumentNullException("sequence is null"); if (sequence.Priority < 0 || sequence.Priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } int id = sequence.StreamId; int priority = sequence.Priority; var headers = sequence.Headers; var stream = new Http2Stream(headers, id, _writeQueue, _flowControlManager, priority); if (sequence.WasEndStreamReceived) stream.EndStreamReceived = sequence.WasEndStreamReceived; ActiveStreams[stream.Id] = stream; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return stream; }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <param name="priority"></param> /// <returns></returns> public Http2Stream CreateStream(HeadersList headers, int streamId, int priority = -1) { if (headers == null) throw new ArgumentNullException("pairs is null"); if (priority == -1) priority = Constants.DefaultStreamPriority; if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var stream = new Http2Stream(headers, streamId, _writeQueue, _flowControlManager, priority); var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, priority){Headers = headers})); _headersSequences.Add(streamSequence); ActiveStreams[stream.Id] = stream; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return stream; }
private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream) { Http2Logger.LogDebug("New headers with id = " + headersFrame.StreamId); //spec 06: //If a HEADERS frame //is received whose stream identifier field is 0x0, the recipient MUST //respond with a connection error (Section 5.4.1) of type //PROTOCOL_ERROR [PROTOCOL_ERROR]. if (headersFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with id = 0"); } 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); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", headersFrame.StreamId, header.Key, header.Value); } headersFrame.Headers.AddRange(headers); var sequence = _headersSequences.Find(seq => seq.StreamId == headersFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(headersFrame.StreamId, headersFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(headersFrame); } if (headersFrame.HasPriority) { sequence.Priority = headersFrame.Priority; } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(headersFrame.StreamId); if (stream == null) { stream = CreateStream(sequence.Headers, headersFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); Http2Logger.LogDebug("New continuation with id = " + contFrame.StreamId); //09 -> 6.10. CONTINUATION //CONTINUATION frames MUST be associated with a stream. If a //CONTINUATION frame is received whose stream identifier field is 0x0, //the recipient MUST respond with a connection error (Section 5.4.1) of //type PROTOCOL_ERROR. if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with id = 0"); } var serHeaders = new byte[contFrame.CompressedHeaders.Count]; Buffer.BlockCopy(contFrame.CompressedHeaders.Array, contFrame.CompressedHeaders.Offset, serHeaders, 0, serHeaders.Length); var decomprHeaders = _comprProc.Decompress(serHeaders); var contHeaders = new HeadersList(decomprHeaders); foreach (var header in contHeaders) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value); } contFrame.Headers.AddRange(contHeaders); var sequence = _headersSequences.Find(contFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(contFrame.StreamId, contFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(contFrame); } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(contFrame.StreamId); if (stream == null) { stream = CreateStream(sequence); ValidateHeaders(stream); } else { stream.Headers = sequence.Headers; ValidateHeaders(stream); } }
/// <summary> /// Creates new http2 stream. /// </summary> /// <param name="priority">The stream priority.</param> /// <returns></returns> /// <exception cref="System.InvalidOperationException">Thrown when trying to create more streams than allowed by the remote side</exception> public Http2Stream CreateStream(int priority) { if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (StreamDictionary.GetOpenedStreamsBy(_ourEnd) + 1 > RemoteMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } int nextId = GetNextId(); var stream = StreamDictionary[nextId]; var streamSequence = new HeadersSequence(nextId, (new HeadersFrame(nextId, true))); _headersSequences.Add(streamSequence); stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(nextId); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; stream.Priority = priority; stream.Opened = true; return stream; }
private void HandlePushPromiseFrame(PushPromiseFrame frame, out Http2Stream stream) { Http2Logger.LogDebug("New push_promise with id = " + frame.StreamId); Http2Logger.LogDebug("Promised id = " + frame.PromisedStreamId); //09 -> 6.6. PUSH_PROMISE //PUSH_PROMISE frames MUST be associated with an existing, peer- //initiated stream. If the stream identifier field specifies the value //0x0, a recipient MUST respond with a connection error (Section 5.4.1) //of type PROTOCOL_ERROR. //... a receiver MUST //treat the receipt of a PUSH_PROMISE that promises an illegal stream //identifier (Section 5.1.1) (that is, an identifier for a stream that //is not currently in the "idle" state) as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver recently //sent a RST_STREAM frame to cancel the associated stream (see //Section 5.1). if (frame.StreamId == 0 || frame.PromisedStreamId == 0 || (frame.PromisedStreamId % 2) != 0 || ActiveStreams.Any(str => (str.Key % 2) == 0 && str.Key > frame.PromisedStreamId)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incorrect promised stream id"); } var serializedHeaders = new byte[frame.CompressedHeaders.Count]; Buffer.BlockCopy(frame.CompressedHeaders.Array, frame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); frame.Headers.Add(header); } var sequence = _headersSequences.Find(frame.PromisedStreamId); if (sequence == null) { sequence = new HeadersSequence(frame.PromisedStreamId, frame); _headersSequences.Add(sequence); } else { //09 -> 6.6. PUSH_PROMISE //A receiver MUST //treat the receipt of a PUSH_PROMISE on a stream that is neither //"open" nor "half-closed (local)" as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR. //This means that we already got push_promise with the same PromisedId. //Hence Stream is in the reserved state. throw new ProtocolError(ResetStatusCode.ProtocolError, "Got multiple push_promises with same Promised id's"); } //09 -> 6.6. PUSH_PROMISE //A PUSH_PROMISE frame without the END_PUSH_PROMISE flag set MUST be //followed by a CONTINUATION frame for the same stream. A receiver //MUST treat the receipt of any other type of frame or a frame on a //different stream as a connection error (Section 5.4.1) of type //PROTOCOL_ERROR. if (!sequence.IsComplete) { stream = null; return; } //09 -> 8.2.1. Push Requests //The server MUST include a method in the ":method" //header field that is safe (see [HTTP-p2], Section 4.2.1). If a //client receives a PUSH_PROMISE that does not include a complete and // valid set of header fields, or the ":method" header field identifies //a method that is not safe, it MUST respond with a stream error //(Section 5.4.2) of type PROTOCOL_ERROR. //Lets think that only GET method is safe for now var method = sequence.Headers.GetValue(CommonHeaders.Method); if (method == null || !method.Equals(Verbs.Get, StringComparison.OrdinalIgnoreCase)) { var frameReceiveStream = GetStream(frame.StreamId); frameReceiveStream.WriteRst(ResetStatusCode.ProtocolError); frameReceiveStream.Dispose(ResetStatusCode.None); stream = null; return; } stream = GetStream(frame.PromisedStreamId); if (stream == null) { stream = CreateStream(sequence); stream.EndStreamSent = true; ValidateHeaders(stream); } else { //09 -> 6.6. PUSH_PROMISE //Similarly, a receiver MUST //treat the receipt of a PUSH_PROMISE that promises an illegal stream //identifier (Section 5.1.1) (that is, an identifier for a stream that //is not currently in the "idle" state) as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR throw new ProtocolError(ResetStatusCode.ProtocolError, "Remote ep tried to promise incorrect stream id"); } }
internal Http2Stream CreateStream(HeadersSequence sequence) { if (sequence == null) throw new ArgumentNullException("sequence is null"); if (sequence.Priority < 0 || sequence.Priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (StreamDictionary.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } int id = sequence.StreamId; int priority = sequence.Priority; var headers = sequence.Headers; var stream = StreamDictionary[id]; if (sequence.WasEndStreamReceived) stream.HalfClosedLocal = sequence.WasEndStreamReceived; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; stream.Headers = headers; stream.Priority = priority; stream.Opened = true; return stream; }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <param name="priority"></param> /// <returns></returns> public Http2Stream CreateStream(HeadersList headers, int streamId, int priority = -1) { if (headers == null) throw new ArgumentNullException("pairs is null"); if (priority == -1) priority = Constants.DefaultStreamPriority; if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (StreamDictionary.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, true){Headers = headers})); _headersSequences.Add(streamSequence); var stream = StreamDictionary[streamId]; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; stream.Priority = priority; stream.Headers = headers; stream.Opened = true; return stream; }
private void HandlePushPromiseFrame(PushPromiseFrame frame, out Http2Stream stream) { Http2Logger.LogDebug("PUSH_PROMISE frame: stream id={0}, payload len={1}, promised id={2}, " + "has pad={3}, pad high={4}, pad low={5}, end headers={6}", frame.StreamId, frame.PayloadLength, frame.PromisedStreamId, frame.HasPadding, frame.PadHigh, frame.PadLow, frame.IsEndHeaders); /* 12 -> 6.6. PUSH_PROMISE frames MUST be associated with an existing, peer- initiated stream. If the stream identifier field specifies the value 0x0, a recipient MUST respond with a connection error of type PROTOCOL_ERROR. */ if (frame.StreamId == 0) throw new ProtocolError(ResetStatusCode.ProtocolError, "push promise frame with stream id=0"); /* 12 -> 5.1 An endpoint that receives any frame after receiving a RST_STREAM MUST treat that as a stream error of type STREAM_CLOSED. */ if (StreamDictionary[frame.StreamId].Closed) throw new Http2StreamNotFoundException(frame.StreamId); //... a receiver MUST //treat the receipt of a PUSH_PROMISE that promises an illegal stream //identifier (Section 5.1.1) (that is, an identifier for a stream that //is not currently in the "idle" state) as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver recently //sent a RST_STREAM frame to cancel the associated stream (see //Section 5.1). if (frame.StreamId % 2 == 0 || frame.PromisedStreamId == 0 || (frame.PromisedStreamId % 2) != 0 || frame.PromisedStreamId < _lastPromisedId || !((StreamDictionary[frame.StreamId].Opened || StreamDictionary[frame.StreamId].HalfClosedLocal))) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incorrect Promised Stream id"); } var serializedHeaders = new byte[frame.CompressedHeaders.Count]; Buffer.BlockCopy(frame.CompressedHeaders.Array, frame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); frame.Headers.Add(header); } var sequence = _headersSequences.Find(frame.PromisedStreamId); if (sequence == null) { sequence = new HeadersSequence(frame.PromisedStreamId, frame); _headersSequences.Add(sequence); } else { //09 -> 6.6. PUSH_PROMISE //A receiver MUST //treat the receipt of a PUSH_PROMISE on a stream that is neither //"open" nor "half-closed (local)" as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR. //This means that we already got push_promise with the same PromisedId. //Hence Stream is in the reserved state. throw new ProtocolError(ResetStatusCode.ProtocolError, "Got multiple push promises with same Promised Stream id's"); } //09 -> 6.6. PUSH_PROMISE //A PUSH_PROMISE frame without the END_PUSH_PROMISE flag set MUST be //followed by a CONTINUATION frame for the same stream. A receiver //MUST treat the receipt of any other type of frame or a frame on a //different stream as a connection error (Section 5.4.1) of type //PROTOCOL_ERROR. if (!sequence.IsComplete) { stream = null; return; } //09 -> 8.2.1. Push Requests //The server MUST include a method in the ":method" //header field that is safe (see [HTTP-p2], Section 4.2.1). If a //client receives a PUSH_PROMISE that does not include a complete and // valid set of header fields, or the ":method" header field identifies //a method that is not safe, it MUST respond with a stream error //(Section 5.4.2) of type PROTOCOL_ERROR. //Lets think that only GET method is safe for now var method = sequence.Headers.GetValue(CommonHeaders.Method); if (method == null || !method.Equals(Verbs.Get, StringComparison.OrdinalIgnoreCase)) { var frameReceiveStream = GetStream(frame.StreamId); frameReceiveStream.WriteRst(ResetStatusCode.ProtocolError); frameReceiveStream.Close(ResetStatusCode.None); stream = null; return; } stream = GetStream(frame.PromisedStreamId); if (stream.Idle) { stream = CreateStream(sequence); stream.ReservedRemote = true; ValidateHeaders(stream); _lastPromisedId = stream.Id; } else { /* 12 -> 6.6 Similarly, a receiver MUST treat the receipt of a PUSH_PROMISE that promises an illegal stream identifier (that is, an identifier for a stream that is not currently in the "idle" state) as a connection error of type PROTOCOL_ERROR. */ throw new ProtocolError(ResetStatusCode.ProtocolError, "Remote endpoint tried to Promise incorrect Stream id"); } }
private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream) { Http2Logger.LogDebug("HEADERS frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " + "end stream={5}, has priority={6}, exclusive={7}, dependency={8}, weight={9}", headersFrame.StreamId, headersFrame.PayloadLength, headersFrame.HasPadding, headersFrame.PadHigh, headersFrame.PadLow, headersFrame.IsEndStream, headersFrame.HasPriority, headersFrame.Exclusive, headersFrame.StreamDependency, headersFrame.Weight); /* 12 -> 6.2 HEADERS frames MUST be associated with a stream. If a HEADERS frame is received whose stream identifier field is 0x0, the recipient MUST respond with a connection error of type PROTOCOL_ERROR. */ if (headersFrame.StreamId == 0) throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with stream id=0"); 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); foreach (var header in headers) { Http2Logger.LogDebug("{1}={2}", headersFrame.StreamId, header.Key, header.Value); } headersFrame.Headers.AddRange(headers); var sequence = _headersSequences.Find(headersFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(headersFrame.StreamId, headersFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(headersFrame); } if (headersFrame.HasPriority) { //TODO: Priority was deprecated, now we need to use Dependency and Weight //sequence.Priority = headersFrame.Priority; } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(headersFrame.StreamId); if (stream.Idle) { stream = CreateStream(sequence); ValidateHeaders(stream); } else if (stream.ReservedRemote) { stream = CreateStream(sequence); stream.HalfClosedLocal = true; ValidateHeaders(stream); } else if(stream.Opened || stream.HalfClosedLocal) { stream.Headers = sequence.Headers;//Modify by the last accepted frame ValidateHeaders(stream); } else if (stream.HalfClosedRemote) { throw new ProtocolError(ResetStatusCode.ProtocolError, "headers for half closed remote stream"); } else { throw new Http2StreamNotFoundException(headersFrame.StreamId); } }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { Http2Logger.LogDebug("CONTINUATION frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}," + " pad low={4}, end headers={5}", contFrame.StreamId, contFrame.PayloadLength, contFrame.HasPadding, contFrame.PadHigh, contFrame.PadLow, contFrame.IsEndHeaders); if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); /* 12 -> 6.10 CONTINUATION frames MUST be associated with a stream. If a CONTINUATION frame is received whose stream identifier field is 0x0, the recipient MUST respond with a connection error of type PROTOCOL_ERROR. */ if (contFrame.StreamId == 0) throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with stream id=0"); var serHeaders = new byte[contFrame.CompressedHeaders.Count]; Buffer.BlockCopy(contFrame.CompressedHeaders.Array, contFrame.CompressedHeaders.Offset, serHeaders, 0, serHeaders.Length); var decomprHeaders = _comprProc.Decompress(serHeaders); var contHeaders = new HeadersList(decomprHeaders); foreach (var header in contHeaders) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value); } contFrame.Headers.AddRange(contHeaders); var sequence = _headersSequences.Find(contFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(contFrame.StreamId, contFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(contFrame); } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(contFrame.StreamId); if (stream.Idle || stream.ReservedRemote) { stream = CreateStream(sequence); ValidateHeaders(stream); } else if (stream.Opened || stream.HalfClosedLocal) { stream.Headers = sequence.Headers;//Modify by the last accepted frame ValidateHeaders(stream); } else if (stream.HalfClosedRemote) { throw new ProtocolError(ResetStatusCode.ProtocolError, "continuation for half closed remote stream"); } else { throw new Http2StreamNotFoundException(contFrame.StreamId); } }