protected internal override ValueTask ReadHeadersAsync(int version, IHttpHeadersSink headersSink, object?state, CancellationToken cancellationToken) { if (IsDisposed(version, out ValueTask task)) { return(task); } return(_request.ReadHeadersAsync(headersSink, state, cancellationToken)); }
private bool ReadHeadersImpl(Span <byte> buffer, IHttpHeadersSink headersSink, object?state, out int bytesConsumed) { if (Avx2.IsSupported) { return(ReadHeadersAvx2(buffer, headersSink, state, out bytesConsumed)); } return(ReadHeadersPortable(buffer, headersSink, state, out bytesConsumed)); }
private static void OnHeader(IHttpHeadersSink sink, object?state, ReadOnlySpan <byte> headerName, ReadOnlySpan <byte> headerValue, HttpHeaderFlags flags) { if (headerName.Length > 1 && headerName[0] == ':') { // TODO: check for pseudo headers. } sink.OnHeader(state, headerName, headerValue, flags); }
private static void ParseHeaderNameValue(ReadOnlySpan <byte> line, IHttpHeadersSink sink, object?state) { Debug.Assert(line.Length > 0); int pos = 0; while (line[pos] != (byte)':' && line[pos] != (byte)' ') { pos++; if (pos == line.Length) { // Invalid header line that doesn't contain ':'. throw new Exception(); } } if (pos == 0) { // Invalid empty header name. throw new Exception(); } ReadOnlySpan <byte> name = line.Slice(0, pos); // Eat any trailing whitespace while (line[pos] == (byte)' ') { pos++; if (pos == line.Length) { // Invalid header line that doesn't contain ':'. throw new Exception(); } } if (line[pos++] != ':') { // Invalid header line that doesn't contain ':'. throw new Exception(); } // Skip whitespace after colon while (pos < line.Length && (line[pos] == (byte)' ' || line[pos] == (byte)'\t')) { pos++; } // Note we ignore the return value from TryAddWithoutValidation. If the header can't be added, we silently drop it. ReadOnlySpan <byte> value = line.Slice(pos); sink.OnHeader(state, name, value); }
/// <summary> /// Reads headers, if any. Should be called when <see cref="ReadType"/> is <see cref="HttpReadType.Headers"/> or <see cref="HttpReadType.TrailingHeaders"/>. /// </summary> /// <param name="headersSink">A sink to retrieve headers.</param> /// <param name="state">User state to pass to <see cref="IHttpHeadersSink.OnHeader(object?, ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>.</param> /// <param name="cancellationToken">A cancellation token for the asynchronous operation.</param> /// <returns>A <see cref="HttpReadType"/> indicating the type of element read from the stream.</returns> public ValueTask ReadHeadersAsync(IHttpHeadersSink headersSink, object?state, CancellationToken cancellationToken = default) => _request.ReadHeadersAsync(_requestVersion, headersSink, state, cancellationToken);
internal unsafe bool ReadHeadersPortable(Span <byte> buffer, IHttpHeadersSink headersSink, object?state, out int bytesConsumed) { int originalBufferLength = buffer.Length; while (true) { if (buffer.Length == 0) { goto needMore; } int colIdx = buffer.IndexOfAny((byte)':', (byte)'\n'); if (colIdx == -1) { goto needMore; } if (buffer[colIdx] == '\n') { bytesConsumed = originalBufferLength - buffer.Length + colIdx + 1; return(true); } ReadOnlySpan <byte> headerName = buffer.Slice(0, colIdx); int lfIdx = colIdx; // Skip OWS. byte ch; do { if (++lfIdx == buffer.Length) { goto needMore; } ch = buffer[lfIdx]; } while (ch == ' ' || ch == '\t'); Span <byte> valueStart = buffer.Slice(lfIdx); Span <byte> valueIter = valueStart; while (true) { lfIdx = valueIter.IndexOf((byte)'\n'); if (lfIdx == -1 || (lfIdx + 1) == valueIter.Length) { goto needMore; } int crLfIdx = lfIdx != 0 && valueIter[lfIdx - 1] == '\r' ? lfIdx - 1 : lfIdx; if (lfIdx + 1 >= valueIter.Length) { goto needMore; } // Check if header continues on the next line. byte ht = valueIter[lfIdx + 1]; if (ht == '\t' || ht == ' ') { // Replace CRLFHT with SPSPSP and loop. valueIter[crLfIdx] = (byte)' '; valueIter[lfIdx] = (byte)' '; valueIter[lfIdx + 1] = (byte)' '; valueIter = valueIter.Slice(lfIdx + 2); continue; } ReadOnlySpan <byte> headerValue = valueStart.Slice(0, valueStart.Length - valueIter.Length + crLfIdx); headersSink.OnHeader(state, headerName, headerValue); ProcessKnownHeaders(headerName, headerValue); buffer = valueStart.Slice(valueStart.Length - valueIter.Length + lfIdx + 1); break; } } needMore: bytesConsumed = originalBufferLength - buffer.Length; return(false); }
/// <remarks> /// This method REQUIRES (32-1) bytes of valid address space after <paramref name="buffer"/>. /// It also assumes that it can always step backwards to a 32-byte aligned address. /// It is built to be used with VectorArrayBuffer, which does this. /// </remarks> internal unsafe bool ReadHeadersAvx2(Span <byte> buffer, IHttpHeadersSink headersSink, object?state, out int bytesConsumed) { if (buffer.Length == 0) { bytesConsumed = 0; return(false); } Vector256 <byte> maskCol = Vector256.Create((byte)':'); Vector256 <byte> maskLF = Vector256.Create((byte)'\n'); int nameStartIdx = 0; fixed(byte *vectorBegin = buffer) { // align to a 32 byte address for optimal loads. byte *vectorEnd = vectorBegin + buffer.Length; byte *vectorIter = (byte *)((nint)vectorBegin & ~(32 - 1)); Vector256 <byte> vector = Avx.LoadAlignedVector256(vectorIter); uint foundLF = (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskLF)); uint foundCol = foundLF | (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskCol)); while (true) { int colIdx; do { while ((colIdx = BitOperations.TrailingZeroCount(foundCol)) == Vector256 <byte> .Count) { vectorIter += Vector256 <byte> .Count; if (vectorIter >= vectorEnd) { bytesConsumed = nameStartIdx; return(false); } // vectorIter may be past the end of buffer[^1]. This is why padding is required. vector = Avx.LoadAlignedVector256(vectorIter); foundLF = (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskLF)); foundCol = foundLF | (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskCol)); } foundCol ^= 1u << colIdx; colIdx = (int)(vectorIter - vectorBegin) + colIdx; }while (colIdx < nameStartIdx); if (colIdx >= buffer.Length) { bytesConsumed = nameStartIdx; return(false); } if (buffer[colIdx] == '\n') { bytesConsumed = colIdx + 1; return(true); } // Skip OWS. int valueStartIdx = colIdx; byte ch; do { if (++valueStartIdx == buffer.Length) { bytesConsumed = nameStartIdx; return(false); } ch = buffer[valueStartIdx]; } while (ch == ' ' || ch == '\t'); int crlfIdx; int lfIdx; while (true) { do { while ((lfIdx = BitOperations.TrailingZeroCount(foundLF)) == Vector256 <byte> .Count) { vectorIter += Vector256 <byte> .Count; if (vectorIter >= vectorEnd) { bytesConsumed = nameStartIdx; return(false); } vector = Avx.LoadAlignedVector256(vectorIter); foundLF = (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskLF)); foundCol = foundLF | (uint)Avx2.MoveMask(Avx2.CompareEqual(vector, maskCol)); } uint clearMask = ~(1u << lfIdx); foundLF &= clearMask; foundCol &= clearMask; lfIdx = (int)(vectorIter - vectorBegin) + lfIdx; }while (lfIdx < colIdx); if (lfIdx >= buffer.Length) { bytesConsumed = nameStartIdx; return(false); } // Check if header continues on the next line. crlfIdx = lfIdx != 0 && buffer[lfIdx - 1] == '\r' ? lfIdx - 1 : lfIdx; if (lfIdx + 1 == buffer.Length) { bytesConsumed = nameStartIdx; return(false); } byte ht = buffer[lfIdx + 1]; if (ht == '\t' || ht == ' ') { buffer[crlfIdx] = (byte)' '; buffer[lfIdx] = (byte)' '; buffer[lfIdx + 1] = (byte)' '; continue; } break; } ; Span <byte> name = buffer[nameStartIdx..colIdx];
private static void OnHeader(IHttpHeadersSink sink, object?state, ulong headerNameIndex, ReadOnlySpan <byte> headerValue, HttpHeaderFlags flags) { PreparedHeader v = GetHeaderForIndex(headerNameIndex); OnHeader(sink, state, v._name._http2Encoded, headerValue, flags); }
public static int Decode(ReadOnlySpan <byte> buffer, IHttpHeadersSink sink, object?state) { int originalLength = buffer.Length; while (buffer.Length != 0) { HttpHeaderFlags flags; ulong nameIndex; int headerLength; byte prefixMask; byte firstByte = buffer[0]; switch (BitOperations.LeadingZeroCount(firstByte) - 24) { case 0: if (HPack.TryDecodeIndexedHeader(firstByte, buffer, out nameIndex, out headerLength)) { buffer = buffer.Slice(headerLength); OnHeader(sink, state, nameIndex); continue; } else { return(originalLength - buffer.Length); } case 1: prefixMask = HPack.IncrementalIndexingMask; flags = HttpHeaderFlags.None; break; case 2: // Dynamic table size update. if (HPack.TryDecodeDynamicTableSizeUpdate(firstByte, buffer, out nameIndex, out headerLength)) { buffer = buffer.Slice(headerLength); OnDynamicTableSizeUpdate(nameIndex); continue; } else { return(originalLength - buffer.Length); } case 3: // Literal header never indexed. prefixMask = HPack.WithoutIndexingOrNeverIndexMask; flags = HttpHeaderFlags.NeverCompressed; break; default: // Literal header without indexing. prefixMask = HPack.WithoutIndexingOrNeverIndexMask; flags = HttpHeaderFlags.None; break; } if (!HPack.TryDecodeHeader(prefixMask, flags, firstByte, buffer, out nameIndex, out ReadOnlySpan <byte> name, out ReadOnlySpan <byte> value, out flags, out headerLength)) { return(originalLength - buffer.Length); } buffer = buffer.Slice(headerLength); if (nameIndex != 0) { OnHeader(sink, state, nameIndex, value, flags); } else { OnHeader(sink, state, name, value, flags); } } return(originalLength - buffer.Length); }
private static void OnHeader(IHttpHeadersSink sink, object?state, ulong headerIndex) { PreparedHeader v = GetHeaderForIndex(headerIndex); OnHeader(sink, state, v._name._http2Encoded, v._value, HttpHeaderFlags.None); }
/// <summary> /// Reads headers, if any. Should be called when <see cref="ReadType"/> is <see cref="HttpReadType.Headers"/> or <see cref="HttpReadType.TrailingHeaders"/>. /// </summary> /// <param name="version">The version of the request to operate on. This must be validated by implementations.</param> /// <param name="headersSink">A sink to retrieve headers.</param> /// <param name="state">User state to pass to <see cref="IHttpHeadersSink.OnHeader(object?, ReadOnlySpan{byte}, ReadOnlySpan{byte})"/>.</param> /// <param name="cancellationToken">A cancellation token for the asynchronous operation.</param> /// <returns>A <see cref="HttpReadType"/> indicating the type of element read from the stream.</returns> protected internal abstract ValueTask ReadHeadersAsync(int version, IHttpHeadersSink headersSink, object?state, CancellationToken cancellationToken);