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); }
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()); }
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]);
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); }
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); }
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"); }
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); }
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); }
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);
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); }
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); }
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); }
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)); }