public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#RST_STREAM

            HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header);

            frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0);

            return(frame);
        }
        public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE

            HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header);

            frame.ReservedBit         = BufferHelper.ReadBit(header.Payload[0], 0);
            frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0);

            return(frame);
        }
        public static PooledBuffer HeaderAsBinary(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#FrameHeader

            var buffer = BufferPool.Get(9, true);

            BufferHelper.SetUInt24(buffer, 0, header.PayloadLength);
            buffer[3] = (byte)header.Type;
            buffer[4] = header.Flags;
            BufferHelper.SetUInt31(buffer, 5, header.StreamId);

            return(new PooledBuffer {
                Data = buffer, Length = 9
            });
        }
        public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode)
        {
            // https://httpwg.org/specs/rfc7540.html#RST_STREAM

            HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();

            frame.Type          = HTTP2FrameTypes.RST_STREAM;
            frame.Flags         = 0;
            frame.StreamId      = streamId;
            frame.Payload       = BufferPool.Get(4, true);
            frame.PayloadLength = 4;

            BufferHelper.SetUInt32(frame.Payload, 0, (UInt32)errorCode);

            return(frame);
        }
        public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#HEADERS

            HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);

            frame.HeaderBlockFragmentLength = header.PayloadLength;

            bool isPadded   = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
            bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;

            int payloadIdx = 0;

            if (isPadded)
            {
                frame.PadLength = header.Payload[payloadIdx++];

                uint subLength = (uint)(1 + (frame.PadLength ?? 0));
                if (subLength <= frame.HeaderBlockFragmentLength)
                {
                    frame.HeaderBlockFragmentLength -= subLength;
                }
                //else
                //    throw PROTOCOL_ERROR;
            }

            if (isPriority)
            {
                frame.IsExclusive      = BufferHelper.ReadBit(header.Payload[payloadIdx], 0);
                frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
                payloadIdx            += 4;
                frame.Weight           = header.Payload[payloadIdx++];

                uint subLength = 5;
                if (subLength <= frame.HeaderBlockFragmentLength)
                {
                    frame.HeaderBlockFragmentLength -= subLength;
                }
                //else
                //    throw PROTOCOL_ERROR;
            }

            frame.HeaderBlockFragmentIdx = (UInt32)payloadIdx;
            frame.HeaderBlockFragment    = header.Payload;

            return(frame);
        }
        public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error)
        {
            // https://httpwg.org/specs/rfc7540.html#GOAWAY

            HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();

            frame.Type          = HTTP2FrameTypes.GOAWAY;
            frame.Flags         = 0;
            frame.StreamId      = 0;
            frame.Payload       = BufferPool.Get(8, true);
            frame.PayloadLength = 8;

            BufferHelper.SetUInt31(frame.Payload, 0, lastStreamId);
            BufferHelper.SetUInt31(frame.Payload, 4, (UInt32)error);

            return(frame);
        }
        public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement)
        {
            // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE

            HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();

            frame.Type          = HTTP2FrameTypes.WINDOW_UPDATE;
            frame.Flags         = 0;
            frame.StreamId      = streamId;
            frame.Payload       = BufferPool.Get(4, true);
            frame.PayloadLength = 4;

            BufferHelper.SetBit(0, 0, 0);
            BufferHelper.SetUInt31(frame.Payload, 0, windowSizeIncrement);

            return(frame);
        }
        public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#PRIORITY

            if (header.PayloadLength != 5)
            {
                //throw FRAME_SIZE_ERROR
            }

            HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);

            frame.IsExclusive      = BufferHelper.ReadBit(header.Payload[0], 0);
            frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
            frame.Weight           = header.Payload[4];

            return(frame);
        }
        public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List <KeyValuePair <HTTP2Settings, UInt32> > settings)
        {
            HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();

            frame.Type          = HTTP2FrameTypes.SETTINGS;
            frame.Flags         = 0;
            frame.PayloadLength = (UInt32)settings.Count * 6;

            frame.Payload = BufferPool.Get(frame.PayloadLength, true);

            for (int i = 0; i < settings.Count; ++i)
            {
                BufferHelper.SetUInt16(frame.Payload, i * 6, (UInt16)settings[i].Key);
                BufferHelper.SetUInt32(frame.Payload, (i * 6) + 2, settings[i].Value);
            }

            return(frame);
        }
        public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header)
        {
            // https://httpwg.org/specs/rfc7540.html#GOAWAY

            HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header);

            frame.ReservedBit  = BufferHelper.ReadBit(header.Payload[0], 0);
            frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
            frame.ErrorCode    = BufferHelper.ReadUInt32(header.Payload, 4);

            frame.AdditionalDebugDataLength = header.PayloadLength - 8;
            if (frame.AdditionalDebugDataLength > 0)
            {
                frame.AdditionalDebugData = BufferPool.Get(frame.AdditionalDebugDataLength, true);
                Array.Copy(header.Payload, 0, frame.AdditionalDebugData, 0, frame.AdditionalDebugDataLength);
            }

            return(frame);
        }
        public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream)
        {
            byte[] buffer = BufferPool.Get(9, true);

            StreamRead(stream, buffer, 0, 9);

            HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();

            header.PayloadLength = BufferHelper.ReadUInt24(buffer, 0);
            header.Type          = (HTTP2FrameTypes)buffer[3];
            header.Flags         = buffer[4];
            header.StreamId      = BufferHelper.ReadUInt31(buffer, 5);

            BufferPool.Release(buffer);

            header.Payload = BufferPool.Get(header.PayloadLength, true);
            StreamRead(stream, header.Payload, 0, header.PayloadLength);

            return(header);
        }
        public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
        {
            HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);

            if (header.PayloadLength > 0)
            {
                int kvpCount = (int)(header.PayloadLength / 6);

                frame.Settings = new List <KeyValuePair <HTTP2Settings, uint> >(kvpCount);
                for (int i = 0; i < kvpCount; ++i)
                {
                    HTTP2Settings key   = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload, i * 6);
                    UInt32        value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2);

                    frame.Settings.Add(new KeyValuePair <HTTP2Settings, uint>(key, value));
                }
            }

            return(frame);
        }
Пример #13
0
        // This method encodes a string without huffman encoding
        private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset)
        {
            uint requiredBytesForStr          = (uint)System.Text.Encoding.UTF8.GetByteCount(str);
            int  requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);

            UInt32 originalOffset = offset;

            buffer[offset] = 0;
            EncodeInteger(requiredBytesForStr, 7, buffer, ref offset);

            // Zero out the huffman flag
            buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false);

            if (offset != originalOffset + requiredBytesForLengthPrefix)
            {
                throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix));
            }

            System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset);
            offset += requiredBytesForStr;
        }
Пример #14
0
        private static void AddCodePointToBuffer(HuffmanEncoder.TableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false)
        {
            for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx)
            {
                byte bit = code.GetBitAtIdx(codeBitIdx);
                buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit);

                // octet boundary reached, proceed to the next octet
                if (++bufferBitIdx == 8)
                {
                    if (++offset < buffer.Length)
                    {
                        buffer[offset] = 0;
                    }

                    if (finishOnBoundary)
                    {
                        return;
                    }

                    bufferBitIdx = 0;
                }
            }
        }
Пример #15
0
        public void Decode(HTTP2Stream context, Stream stream, List <KeyValuePair <string, string> > to)
        {
            int headerType = stream.ReadByte();

            while (headerType != -1)
            {
                byte firstDataByte = (byte)headerType;

                // https://http2.github.io/http2-spec/compression.html#indexed.header.representation
                if (BufferHelper.ReadBit(firstDataByte, 0) == 1)
                {
                    var header = ReadIndexedHeader(firstDataByte, stream);

                    if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                    {
                        HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()));
                    }

                    to.Add(header);
                }
                else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1)
                {
                    // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing

                    if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0)
                    {
                        // Literal Header Field with Incremental Indexing — New Name
                        var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()));
                        }

                        this.responseTable.Add(header);
                        to.Add(header);
                    }
                    else
                    {
                        // Literal Header Field with Incremental Indexing — Indexed Name
                        var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()));
                        }

                        this.responseTable.Add(header);
                        to.Add(header);
                    }
                }
                else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0)
                {
                    // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing

                    if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
                    {
                        // Literal Header Field without Indexing — New Name
                        var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()));
                        }

                        to.Add(header);
                    }
                    else
                    {
                        // Literal Header Field without Indexing — Indexed Name
                        var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()));
                        }

                        to.Add(header);
                    }
                }
                else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1)
                {
                    // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed

                    if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
                    {
                        // Literal Header Field Never Indexed — New Name
                        var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()));
                        }

                        to.Add(header);
                    }
                    else
                    {
                        // Literal Header Field Never Indexed — Indexed Name
                        var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                        {
                            HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()));
                        }

                        to.Add(header);
                    }
                }
                else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1)
                {
                    // https://http2.github.io/http2-spec/compression.html#encoding.context.update

                    UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream);

                    if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
                    {
                        HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize));
                    }

                    //this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize;
                    this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize;
                }
                else
                {
                    // ERROR
                }

                headerType = stream.ReadByte();
            }
        }
        private void ReadThread()
        {
            try
            {
                Thread.CurrentThread.Name = "HTTP2 Read";
                HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!");

                using (ReadOnlyBufferedStream bufferedStream = new ReadOnlyBufferedStream(this.conn.connector.Stream, 32 * 1024))
                {
                    while (this.isRunning)
                    {
                        // TODO:
                        //  1. Set the local window to a reasonable size
                        //  2. stop reading when the local window is about to be 0.
                        //  3.
                        HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(bufferedStream);

                        if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
                        {
                            HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString());
                        }

                        // Add the new frame to the queue. Processing it on the write thread gives us the advantage that
                        //  we don't have to deal with too much locking.
                        this.newFrames.Enqueue(header);

                        // ping write thread to process the new frame
                        this.newFrameSignal.Set();

                        switch (header.Type)
                        {
                        // Handle pongs on the read thread, so no additional latency is added to the rtt calculation.
                        case HTTP2FrameTypes.PING:
                            var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);

                            if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
                            {
                                // it was an ack, payload must contain what we sent

                                var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);

                                // the difference between the current time and the time when the ping message is sent
                                TimeSpan diff = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - ticks);

                                // add it to the buffer
                                this.rtts.Add(diff.TotalMilliseconds);

                                // and calculate the new latency
                                this.Latency = CalculateLatency();

                                HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()));
                            }
                            break;

                        case HTTP2FrameTypes.GOAWAY:
                            // Just exit from this thread. The processing thread will handle the frame too.
                            return;
                        }
                    }
                }
            }
            catch //(Exception ex)
            {
                //HTTPManager.Logger.Exception("HTTP2Handler", "", ex);

                this.isRunning = false;
                this.newFrameSignal.Set();
            }
            finally
            {
                // First thread closing notifies the ConnectionEventHelper
                if (Interlocked.Increment(ref this.threadExitCount) == 1)
                {
                    ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
                }

                HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing");
            }
        }
        public void RunHandler()
        {
            HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!");

            Thread.CurrentThread.Name = "HTTP2 Process";

            PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);

            try
            {
                bool atLeastOneStreamHasAFrameToSend = true;

                this.HPACKEncoder = new HPACKEncoder(this.settings);

                // https://httpwg.org/specs/rfc7540.html#InitialWindowSize
                // The connection flow-control window is also 65,535 octets.
                this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];

                // we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
                //  we might keep data too long and send them in bursts instead of in a steady stream.
                // Keeping it too low might result in a full tcp segment and one with very low payload
                // Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
                // It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
                // but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
                // On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
                // of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
                // a well saturated network might serve us better.
                using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/))
                {
                    // The client connection preface starts with a sequence of 24 octets
                    bufferedStream.Write(MAGIC, 0, MAGIC.Length);

                    // This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
                    // The client sends the client connection preface immediately upon receipt of a
                    // 101 (Switching Protocols) response (indicating a successful upgrade)
                    // or as the first application data octets of a TLS connection

                    // Set streams' initial window size to its maximum.
                    this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE]    = HTTPManager.HTTP2Settings.InitialStreamWindowSize;
                    this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams;
                    this.settings.SendChanges(this.outgoingFrames);

                    // The default window size for the whole connection is 65535 bytes,
                    // but we want to set it to the maximum possible value.
                    Int64 diff = HTTPManager.HTTP2Settings.InitialConnectionWindowSize - 65535;
                    if (diff > 0)
                    {
                        this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
                    }

                    this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;

                    while (this.isRunning)
                    {
                        if (!atLeastOneStreamHasAFrameToSend)
                        {
                            // buffered stream will call flush automatically if its internal buffer is full.
                            // But we have to make it sure that we flush remaining data before we go to sleep.
                            bufferedStream.Flush();

                            // Wait until we have to send the next ping, OR a new frame is received on the read thread.
                            //   Sent      Now  Sent+frequency
                            //-----|--------|----|------------
                            int wait = (int)((this.lastPingSent + this.pingFrequency) - DateTime.UtcNow).TotalMilliseconds;

                            wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);

                            if (wait >= 1)
                            {
                                if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
                                {
                                    HTTPManager.Logger.Information("HTTP2Handler", string.Format("Sleeping for {0:N0}ms", wait));
                                }
                                this.newFrameSignal.WaitOne(wait);
                            }
                        }

                        DateTime now = DateTime.UtcNow;

                        if (now - this.lastPingSent >= this.pingFrequency)
                        {
                            this.lastPingSent = now;

                            var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None);
                            BufferHelper.SetLong(frame.Payload, 0, now.Ticks);

                            this.outgoingFrames.Add(frame);
                        }

                        // Process received frames
                        HTTP2FrameHeaderAndPayload header;
                        while (this.newFrames.TryDequeue(out header))
                        {
                            if (header.StreamId > 0)
                            {
                                HTTP2Stream http2Stream = FindStreamById(header.StreamId);

                                // Add frame to the stream, so it can process it when its Process function is called
                                if (http2Stream != null)
                                {
                                    http2Stream.AddFrame(header, this.outgoingFrames);
                                }
                                else
                                {
                                    // Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
                                    //HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header));
                                }
                            }
                            else
                            {
                                switch (header.Type)
                                {
                                case HTTP2FrameTypes.SETTINGS:
                                    this.settings.Process(header, this.outgoingFrames);
                                    break;

                                case HTTP2FrameTypes.PING:
                                    var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);

                                    // if it wasn't an ack for our ping, we have to send one
                                    if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
                                    {
                                        var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK);
                                        Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength);

                                        this.outgoingFrames.Add(frame);
                                    }
                                    break;

                                case HTTP2FrameTypes.WINDOW_UPDATE:
                                    var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
                                    this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
                                    break;

                                case HTTP2FrameTypes.GOAWAY:
                                    // parse the frame, so we can print out detailed information
                                    HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);

                                    HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString());

                                    string msg = string.Format("Server closing the connection! Error code: {0} ({1})", goAwayFrame.Error, goAwayFrame.ErrorCode);
                                    for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
                                    {
                                        this.clientInitiatedStreams[i].Abort(msg);
                                    }

                                    // set the running flag to false, so the thread can exit
                                    this.isRunning = false;

                                    this.conn.State = HTTPConnectionStates.Closed;
                                    break;

                                case HTTP2FrameTypes.ALT_SVC:
                                    //HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);

                                    // Implement
                                    //HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
                                    break;
                                }
                            }
                        }

                        UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]);

                        // pre-test stream count to lock only when truly needed.
                        if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning)
                        {
                            // grab requests from queue
                            HTTPRequest request;
                            while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request))
                            {
                                // create a new stream
                                var newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this.settings, this.HPACKEncoder);

                                // process the request
                                newStream.Assign(request);

                                this.clientInitiatedStreams.Add(newStream);
                            }
                        }

                        // send any settings changes
                        this.settings.SendChanges(this.outgoingFrames);

                        atLeastOneStreamHasAFrameToSend = false;

                        // process other streams
                        // Room for improvement Streams should be processed by their priority!
                        for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
                        {
                            var stream = this.clientInitiatedStreams[i];
                            stream.Process(this.outgoingFrames);

                            // remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
                            if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
                            {
                                this.clientInitiatedStreams.RemoveAt(i--);
                                stream.Removed();
                            }

                            atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;

                            this.lastInteraction = DateTime.UtcNow;
                        }

                        // If we encounter a data frame that too large for the current remote window, we have to stop
                        // sending all data frames as we could send smaller data frames before the large ones.
                        // Room for improvement: An improvement would be here to stop data frame sending per-stream.
                        bool haltDataSending = false;

                        if (this.ShutdownType == ShutdownTypes.Running && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime)
                        {
                            this.lastInteraction = DateTime.UtcNow;
                            HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!");
                            this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
                            this.goAwaySentAt = DateTime.UtcNow;
                        }

                        // https://httpwg.org/specs/rfc7540.html#GOAWAY
                        // Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
                        if (this.ShutdownType == ShutdownTypes.Gentle)
                        {
                            HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!");

                            this.outgoingFrames.Clear();
                            this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
                            this.goAwaySentAt = DateTime.UtcNow;
                        }

                        if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
                        {
                            HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!");
                            this.isRunning = false;
                            conn.State     = HTTPConnectionStates.Closed;
                        }

                        uint streamWindowUpdates = 0;

                        // Go through all the collected frames and send them.
                        for (int i = 0; i < this.outgoingFrames.Count; ++i)
                        {
                            var frame = this.outgoingFrames[i];

                            if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
                            {
                                HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString());
                            }

                            // post process frames
                            switch (frame.Type)
                            {
                            case HTTP2FrameTypes.DATA:
                                if (haltDataSending)
                                {
                                    continue;
                                }

                                // if the tracked remoteWindow is smaller than the frame's payload, we stop sending
                                // data frames until we receive window-update frames
                                if (frame.PayloadLength > this.remoteWindow)
                                {
                                    haltDataSending = true;
                                    HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()));
                                    continue;
                                }

                                break;

                            case HTTP2FrameTypes.WINDOW_UPDATE:
                                if (frame.StreamId > 0)
                                {
                                    streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0);
                                }
                                break;
                            }

                            this.outgoingFrames.RemoveAt(i--);

                            using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
                                bufferedStream.Write(buffer.Data, 0, buffer.Length);

                            if (frame.PayloadLength > 0)
                            {
                                bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);

                                if (!frame.DontUseMemPool)
                                {
                                    BufferPool.Release(frame.Payload);
                                }
                            }

                            if (frame.Type == HTTP2FrameTypes.DATA)
                            {
                                this.remoteWindow -= frame.PayloadLength;
                            }
                        }

                        if (streamWindowUpdates > 0)
                        {
                            var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates);

                            if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
                            {
                                HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString());
                            }

                            using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
                                bufferedStream.Write(buffer.Data, 0, buffer.Length);

                            bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
                        }
                    } // while (this.isRunning)

                    bufferedStream.Flush();
                }
            }
            catch (Exception ex)
            {
                // Log out the exception if it's a non-expected one.
                if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue)
                {
                    HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex);
                }
            }
            finally
            {
                // First thread closing notifies the ConnectionEventHelper
                if (Interlocked.Increment(ref this.threadExitCount) == 1)
                {
                    ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
                }

                HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...");

                for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
                {
                    this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly");
                }
                this.clientInitiatedStreams.Clear();

                HTTPRequest request = null;
                while (this.requestQueue.TryDequeue(out request))
                {
                    HTTPManager.Logger.Information("HTTP2Handler", string.Format("Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()));
                    if (request.IsCancellationRequested)
                    {
                        request.Response = null;
                        request.State    = HTTPRequestStates.Aborted;
                    }
                    else
                    {
                        RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
                    }
                }

                HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing");
            }

            try
            {
                // Works in the new runtime
                if (this.conn.connector.TopmostStream != null)
                {
                    using (this.conn.connector.TopmostStream) { }
                }

                // Works in the old runtime
                if (this.conn.connector.Stream != null)
                {
                    using (this.conn.connector.Stream) { }
                }
            }
            catch
            { }
        }
Пример #18
0
        private string DecodeString(Stream stream)
        {
            byte   start        = (byte)stream.ReadByte();
            bool   rawString    = BufferHelper.ReadBit(start, 0) == 0;
            UInt32 stringLength = DecodeInteger(7, start, stream);

            if (rawString)
            {
                byte[] buffer = BufferPool.Get(stringLength, true);

                stream.Read(buffer, 0, (int)stringLength);

                BufferPool.Release(buffer);

                return(System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength));
            }
            else
            {
                var  node        = HuffmanEncoder.GetRoot();
                byte currentByte = (byte)stream.ReadByte();
                byte bitIdx      = 0; // 0..7

                using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream())
                {
                    do
                    {
                        byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx);

                        if (++bitIdx > 7)
                        {
                            stringLength--;

                            if (stringLength > 0)
                            {
                                bitIdx      = 0;
                                currentByte = (byte)stream.ReadByte();
                            }
                        }

                        node = HuffmanEncoder.GetNext(node, bitValue);

                        if (node.Value != 0)
                        {
                            if (node.Value != HuffmanEncoder.EOS)
                            {
                                bufferStream.WriteByte((byte)node.Value);
                            }

                            node = HuffmanEncoder.GetRoot();
                        }
                    } while (stringLength > 0);

                    byte[] buffer = bufferStream.ToArray(true);

                    string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);

                    BufferPool.Release(buffer);

                    return(result);
                }
            }
        }