Beispiel #1
0
    private static bool Encode(Http3HeadersEnumerator headersEnumerator, Span <byte> buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length)
    {
        length = 0;

        do
        {
            var staticTableId = headersEnumerator.QPackStaticTableId;
            var name          = headersEnumerator.Current.Key;
            var value         = headersEnumerator.Current.Value;
            var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
                ? null : headersEnumerator.EncodingSelector(name);

            if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out var headerLength))
            {
                if (length == 0 && throwIfNoneEncoded)
                {
                    throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
                }
                return(false);
            }

            // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3
            totalHeaderSize += HeaderField.GetLength(name.Length, value.Length);
            length          += headerLength;
        } while (headersEnumerator.MoveNext());

        return(true);
    }
    static QPackDecoderBenchmark()
    {
        var headers    = new HttpResponseHeaders();
        var enumerator = new Http3HeadersEnumerator();

        AddLargeHeader(headers, 'a');
        enumerator.Initialize(headers);
        _headerFieldLine_LargeLiteralValue = GenerateHeaderBytes(enumerator);
        headers.Reset();

        AddLargeHeader(headers, 'a');
        AddLargeHeader(headers, 'b');
        AddLargeHeader(headers, 'c');
        AddLargeHeader(headers, 'd');
        AddLargeHeader(headers, 'e');
        enumerator.Initialize(headers);
        _headerFieldLine_LargeLiteralValue_Multiple = GenerateHeaderBytes(enumerator);
        headers.Reset();

        ((IHeaderDictionary)headers).ContentLength            = 0;
        ((IHeaderDictionary)headers).ContentType              = "application/json";
        ((IHeaderDictionary)headers).Age                      = "0";
        ((IHeaderDictionary)headers).AcceptRanges             = "bytes";
        ((IHeaderDictionary)headers).AccessControlAllowOrigin = "*";
        enumerator.Initialize(headers);
        _headerFieldLine_Static_Multiple = GenerateHeaderBytes(enumerator);
    public void CanIterateOverResponseHeaders()
    {
        var responseHeaders = (IHeaderDictionary) new HttpResponseHeaders();

        responseHeaders.ContentLength = 9;
        responseHeaders.AcceptRanges  = "AcceptRanges!";
        responseHeaders.Age           = new StringValues(new[] { "1", "2" });
        responseHeaders.Date          = "Date!";
        responseHeaders.GrpcEncoding  = "Identity!";

        responseHeaders.Append("Name1", "Value1");
        responseHeaders.Append("Name2", "Value2-1");
        responseHeaders.Append("Name2", "Value2-2");
        responseHeaders.Append("Name3", "Value3");

        var e = new Http3HeadersEnumerator();

        e.Initialize(responseHeaders);

        var headers = GetNormalizedHeaders(e);

        Assert.Equal(new[]
        {
            CreateHeaderResult(6, "Date", "Date!"),
            CreateHeaderResult(32, "Accept-Ranges", "AcceptRanges!"),
            CreateHeaderResult(2, "Age", "1"),
            CreateHeaderResult(2, "Age", "2"),
            CreateHeaderResult(-1, "Grpc-Encoding", "Identity!"),
            CreateHeaderResult(4, "Content-Length", "9"),
            CreateHeaderResult(-1, "Name1", "Value1"),
            CreateHeaderResult(-1, "Name2", "Value2-1"),
            CreateHeaderResult(-1, "Name2", "Value2-2"),
            CreateHeaderResult(-1, "Name3", "Value3"),
        }, headers);
    }
    public void CanIterateOverResponseTrailers()
    {
        var responseTrailers = (IHeaderDictionary) new HttpResponseTrailers();

        responseTrailers.ContentLength = 9;
        responseTrailers.ETag          = "ETag!";

        responseTrailers.Append("Name1", "Value1");
        responseTrailers.Append("Name2", "Value2-1");
        responseTrailers.Append("Name2", "Value2-2");
        responseTrailers.Append("Name3", "Value3");

        var e = new Http3HeadersEnumerator();

        e.Initialize(responseTrailers);

        var headers = GetNormalizedHeaders(e);

        Assert.Equal(new[]
        {
            CreateHeaderResult(7, "ETag", "ETag!"),
            CreateHeaderResult(-1, "Name1", "Value1"),
            CreateHeaderResult(-1, "Name2", "Value2-1"),
            CreateHeaderResult(-1, "Name2", "Value2-2"),
            CreateHeaderResult(-1, "Name3", "Value3"),
        }, headers);
    }
Beispiel #5
0
    public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource()
    {
        var responseHeaders = new HttpResponseHeaders();

        responseHeaders.Append("Name1", "Value1");
        responseHeaders.Append("Name2", "Value2-1");
        responseHeaders.Append("Name2", "Value2-2");

        var e = new Http3HeadersEnumerator();

        e.Initialize(responseHeaders);

        Assert.True(e.MoveNext());
        Assert.Equal("Name1", e.Current.Key);
        Assert.Equal("Value1", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.True(e.MoveNext());
        Assert.Equal("Name2", e.Current.Key);
        Assert.Equal("Value2-1", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.True(e.MoveNext());
        Assert.Equal("Name2", e.Current.Key);
        Assert.Equal("Value2-2", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        var responseTrailers = (IHeaderDictionary) new HttpResponseTrailers();

        responseTrailers.GrpcStatus = "1";

        responseTrailers.Append("Name1", "Value1");
        responseTrailers.Append("Name2", "Value2-1");
        responseTrailers.Append("Name2", "Value2-2");

        e.Initialize(responseTrailers);

        Assert.True(e.MoveNext());
        Assert.Equal("Grpc-Status", e.Current.Key);
        Assert.Equal("1", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.True(e.MoveNext());
        Assert.Equal("Name1", e.Current.Key);
        Assert.Equal("Value1", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.True(e.MoveNext());
        Assert.Equal("Name2", e.Current.Key);
        Assert.Equal("Value2-1", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.True(e.MoveNext());
        Assert.Equal("Name2", e.Current.Key);
        Assert.Equal("Value2-2", e.Current.Value);
        Assert.Equal(-1, e.QPackStaticTableId);

        Assert.False(e.MoveNext());
    }
Beispiel #6
0
        internal Http3HeadersEnumerator GetHeadersEnumerator(IEnumerable <KeyValuePair <string, string> > headers)
        {
            var dictionary = headers
                             .GroupBy(g => g.Key)
                             .ToDictionary(g => g.Key, g => new StringValues(g.Select(values => values.Value).ToArray()));

            var headersEnumerator = new Http3HeadersEnumerator();

            headersEnumerator.Initialize(dictionary);
            return(headersEnumerator);
        }
        static byte[] GenerateHeaderBytes(Http3HeadersEnumerator enumerator)
        {
            var buffer          = new byte[1024 * 1024];
            var totalHeaderSize = 0;
            var success         = QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length);

            if (!success)
            {
                throw new InvalidOperationException();
            }

            return(buffer[..length]);
Beispiel #8
0
        public async Task SendHeadersAsync(Http3HeadersEnumerator headers, bool endStream = false)
        {
            var headersTotalSize = 0;

            var buffer = _headerHandler.HeaderEncodingBuffer.AsMemory();
            var done   = QPackHeaderWriter.BeginEncode(headers, buffer.Span, ref headersTotalSize, out var length);

            if (!done)
            {
                throw new InvalidOperationException("Headers not sent.");
            }

            await SendFrameAsync(Http3FrameType.Headers, buffer.Slice(0, length), endStream);
        }
Beispiel #9
0
    public static bool BeginEncodeHeaders(Http3HeadersEnumerator enumerator, Span <byte> buffer, ref int totalHeaderSize, out int length)
    {
        bool hasValue = enumerator.MoveNext();

        Debug.Assert(hasValue == true);

        buffer[0] = 0;
        buffer[1] = 0;

        bool doneEncode = Encode(enumerator, buffer.Slice(2), ref totalHeaderSize, out length);

        // Add two for the first two bytes.
        length += 2;
        return(doneEncode);
    }
Beispiel #10
0
    public void BeginEncodeHeaders_StatusWithIndexedValue_ExpectedLength(int statusCode)
    {
        Span <byte> buffer = new byte[1024 * 16];

        var totalHeaderSize = 0;
        var headers         = new HttpResponseHeaders();
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(statusCode, enumerator, buffer, ref totalHeaderSize, out var length));
        length -= 2; // Remove prefix

        Assert.True(length <= 2, "Indexed header should be encoded into 1 or 2 bytes");
    }
Beispiel #11
0
    private static bool Encode(Http3HeadersEnumerator headersEnumerator, Span <byte> buffer, bool throwIfNoneEncoded, ref int totalHeaderSize, out int length)
    {
        length = 0;

        do
        {
            // Match the current header to the QPACK static table. Possible outcomes:
            // 1. Known header and value. Write index.
            // 2. Known header with custom value. Write name index and full value.
            // 3. Unknown header. Write full name and value.
            var(staticTableId, matchedValue) = headersEnumerator.GetQPackStaticTableId();
            var name  = headersEnumerator.Current.Key;
            var value = headersEnumerator.Current.Value;

            int headerLength;
            if (matchedValue)
            {
                if (!QPackEncoder.EncodeStaticIndexedHeaderField(staticTableId, buffer.Slice(length), out headerLength))
                {
                    if (length == 0 && throwIfNoneEncoded)
                    {
                        throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
                    }
                    return(false);
                }
            }
            else
            {
                var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector)
                    ? null : headersEnumerator.EncodingSelector(name);

                if (!EncodeHeader(buffer.Slice(length), staticTableId, name, value, valueEncoding, out headerLength))
                {
                    if (length == 0 && throwIfNoneEncoded)
                    {
                        throw new QPackEncodingException("TODO sync with corefx" /* CoreStrings.HPackErrorNotEnoughBuffer */);
                    }
                    return(false);
                }
            }

            // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.3
            totalHeaderSize += HeaderField.GetLength(name.Length, value.Length);
            length          += headerLength;
        } while (headersEnumerator.MoveNext());

        return(true);
    }
    public void BeginEncodeHeaders_StatusWithIndexedValue_WriteIndex()
    {
        Span <byte> buffer = new byte[1024 * 16];

        var totalHeaderSize = 0;
        var headers         = new HttpResponseHeaders();
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(200, enumerator, buffer, ref totalHeaderSize, out var length));

        var result = buffer.Slice(0, length).ToArray();
        var hex    = BitConverter.ToString(result);

        Assert.Equal("00-00-D9", hex);
    }
Beispiel #13
0
    internal async ValueTask <Http3RequestStream> CreateRequestStream(Http3HeadersEnumerator headers, Http3RequestHeaderHandler headerHandler = null, bool endStream = false, TaskCompletionSource tsc = null)
    {
        var stream = CreateRequestStreamCore(headerHandler);

        if (tsc is not null)
        {
            stream.StartStreamDisposeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        }

        await stream.SendHeadersAsync(headers, endStream);

        _runningStreams[stream.StreamId] = stream;

        MultiplexedConnectionContext.ToServerAcceptQueue.Writer.TryWrite(stream.StreamContext);

        return(stream);
    }
Beispiel #14
0
    public virtual void GlobalSetup()
    {
        _headerHandler            = new Http3RequestHeaderHandler();
        _requestHeadersEnumerator = new Http3HeadersEnumerator();
        _httpFrame = new Http3FrameWithPayload();

        _httpRequestHeaders = new HttpRequestHeaders();
        _httpRequestHeaders[InternalHeaderNames.Method]    = new StringValues("GET");
        _httpRequestHeaders[InternalHeaderNames.Path]      = new StringValues("/");
        _httpRequestHeaders[InternalHeaderNames.Scheme]    = new StringValues("http");
        _httpRequestHeaders[InternalHeaderNames.Authority] = new StringValues("localhost:80");

        var serviceContext = TestContextFactory.CreateServiceContext(
            serverOptions: new KestrelServerOptions(),
            dateHeaderValueManager: new DateHeaderValueManager(),
            systemClock: new MockSystemClock());

        serviceContext.DateHeaderValueManager.OnHeartbeat(default);
Beispiel #15
0
    public void BeginEncodeHeaders_NonStaticKey_WriteFullNameAndFullValue_CustomHeader()
    {
        Span <byte> buffer = new byte[1024 * 16];

        var headers = (IHeaderDictionary) new HttpResponseHeaders();

        headers["new-header"] = "value";

        var totalHeaderSize = 0;
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length));

        var result = buffer.Slice(2, length - 2).ToArray(); // trim prefix
        var hex    = BitConverter.ToString(result);

        Assert.Equal("37-03-6E-65-77-2D-68-65-61-64-65-72-05-76-61-6C-75-65", hex);
    }
Beispiel #16
0
    public void BeginEncodeHeaders_StaticKeyAndValue_WriteIndex()
    {
        Span <byte> buffer = new byte[1024 * 16];

        var headers = (IHeaderDictionary) new HttpResponseHeaders();

        headers.ContentType = "application/json";

        var totalHeaderSize = 0;
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length));

        var result = buffer.Slice(2, length - 2).ToArray(); // trim prefix
        var hex    = BitConverter.ToString(result);

        Assert.Equal("EE", hex);
    }
    public static bool BeginEncode(int statusCode, Http3HeadersEnumerator enumerator, Span <byte> buffer, ref int totalHeaderSize, out int length)
    {
        bool hasValue = enumerator.MoveNext();

        Debug.Assert(hasValue == true);

        // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#header-prefix
        buffer[0] = 0;
        buffer[1] = 0;

        int statusCodeLength = EncodeStatusCode(statusCode, buffer.Slice(2));

        totalHeaderSize += 42; // name (:status) + value (xxx) + overhead (32)

        bool done = Encode(enumerator, buffer.Slice(statusCodeLength + 2), throwIfNoneEncoded: false, ref totalHeaderSize, out int headersLength);

        length = statusCodeLength + headersLength + 2;

        return(done);
    }
    public void BeginEncodeHeaders_NoStatus_NonStaticKey_WriteFullNameAndFullValue()
    {
        Span <byte> buffer = new byte[1024 * 16];

        var headers = (IHeaderDictionary) new HttpResponseHeaders();

        headers.Translate = "private";

        var totalHeaderSize = 0;
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length));

        var result = buffer.Slice(2, length - 2).ToArray();
        var hex    = BitConverter.ToString(result);

        Assert.Equal("37-02-74-72-61-6E-73-6C-61-74-65-07-70-72-69-76-61-74-65", hex);
    }
Beispiel #19
0
    public void BeginEncodeHeaders_StaticKey_WriteStaticNameAndFullValue()
    {
        Span <byte> buffer = new byte[1024 * 16];

        var headers = (IHeaderDictionary) new HttpResponseHeaders();

        headers.ContentType = "application/custom";

        var totalHeaderSize = 0;
        var enumerator      = new Http3HeadersEnumerator();

        enumerator.Initialize(headers);

        Assert.True(QPackHeaderWriter.BeginEncodeHeaders(enumerator, buffer, ref totalHeaderSize, out var length));

        var result = buffer.Slice(2, length - 2).ToArray();
        var hex    = BitConverter.ToString(result);

        Assert.Equal("5F-1D-12-61-70-70-6C-69-63-61-74-69-6F-6E-2F-63-75-73-74-6F-6D", hex);
    }
Beispiel #20
0
 public static bool Encode(Http3HeadersEnumerator headersEnumerator, Span <byte> buffer, ref int totalHeaderSize, out int length)
 {
     return(Encode(headersEnumerator, buffer, throwIfNoneEncoded: true, ref totalHeaderSize, out length));
 }