| File: Internal\Http3\QPack\EncoderStreamReader.cs | Web Access |
| Project: src\aspnetcore\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj (Microsoft.AspNetCore.Server.Kestrel.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Net.Http.HPack; using System.Net.Http.QPack; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.QPack; internal sealed class EncoderStreamReader { private enum State { Ready, DynamicTableCapcity, NameIndex, NameLength, Name, ValueLength, ValueLengthContinue, Value, Duplicate } //0 1 2 3 4 5 6 7 //+---+---+---+---+---+---+---+---+ //| 0 | 0 | 1 | Capacity(5+) | //+---+---+---+-------------------+ private const byte DynamicTableCapacityMask = 0xE0; private const byte DynamicTableCapacityRepresentation = 0x20; private const byte DynamicTableCapacityPrefixMask = 0x1F; private const int DynamicTableCapacityPrefix = 5; //0 1 2 3 4 5 6 7 //+---+---+---+---+---+---+---+---+ //| 1 | S | Name Index(6+) | //+---+---+-----------------------+ //| H | Value Length(7+) | //+---+---------------------------+ //| Value String(Length bytes) | //+-------------------------------+ private const byte InsertWithNameReferenceMask = 0x80; private const byte InsertWithNameReferenceRepresentation = 0x80; private const byte InsertWithNameReferencePrefixMask = 0x3F; private const byte InsertWithNameReferenceStaticMask = 0x40; private const int InsertWithNameReferencePrefix = 6; //0 1 2 3 4 5 6 7 //+---+---+---+---+---+---+---+---+ //| 0 | 1 | H | Name Length(5+) | //+---+---+---+-------------------+ //| Name String(Length bytes) | //+---+---------------------------+ //| H | Value Length(7+) | //+---+---------------------------+ //| Value String(Length bytes) | //+-------------------------------+ private const byte InsertWithoutNameReferenceMask = 0xC0; private const byte InsertWithoutNameReferenceRepresentation = 0x40; private const byte InsertWithoutNameReferencePrefixMask = 0x1F; private const byte InsertWithoutNameReferenceHuffmanMask = 0x20; private const int InsertWithoutNameReferencePrefix = 5; //0 1 2 3 4 5 6 7 //+---+---+---+---+---+---+---+---+ //| 0 | 0 | 0 | Index(5+) | //+---+---+---+-------------------+ private const byte DuplicateMask = 0xE0; private const byte DuplicateRepresentation = 0x00; private const byte DuplicatePrefixMask = 0x1F; private const int DuplicatePrefix = 5; private const int StringLengthPrefix = 7; private const byte HuffmanMask = 0x80; private bool _s; private readonly byte[] _stringOctets; private readonly byte[] _headerNameOctets; private readonly byte[] _headerValueOctets; private byte[]? _headerName; private int _headerNameLength; private int _headerValueLength; private int _stringLength; private int _stringIndex; private readonly DynamicTable _dynamicTable = new DynamicTable(1000); // TODO figure out architecture. private IntegerDecoder _integerDecoder; private State _state = State.Ready; private bool _huffman; public EncoderStreamReader(int maxRequestHeaderFieldSize) { // TODO how to propagate dynamic table around. _stringOctets = new byte[maxRequestHeaderFieldSize]; _headerNameOctets = new byte[maxRequestHeaderFieldSize]; _headerValueOctets = new byte[maxRequestHeaderFieldSize]; } public void Read(ReadOnlySequence<byte> data) { foreach (var segment in data) { var span = segment.Span; for (var i = 0; i < span.Length; i++) { OnByte(span[i]); } } } private void OnByte(byte b) { int intResult; int prefixInt; switch (_state) { case State.Ready: if ((b & DynamicTableCapacityMask) == DynamicTableCapacityRepresentation) { prefixInt = DynamicTableCapacityPrefixMask & b; if (_integerDecoder.BeginTryDecode((byte)prefixInt, DynamicTableCapacityPrefix, out intResult)) { OnDynamicTableCapacity(intResult); } else { _state = State.DynamicTableCapcity; } } else if ((b & InsertWithNameReferenceMask) == InsertWithNameReferenceRepresentation) { prefixInt = InsertWithNameReferencePrefixMask & b; _s = (InsertWithNameReferenceStaticMask & b) == InsertWithNameReferenceStaticMask; if (_integerDecoder.BeginTryDecode((byte)prefixInt, InsertWithNameReferencePrefix, out intResult)) { OnNameIndex(intResult); } else { _state = State.NameIndex; } } else if ((b & InsertWithoutNameReferenceMask) == InsertWithoutNameReferenceRepresentation) { prefixInt = InsertWithoutNameReferencePrefixMask & b; _huffman = (InsertWithoutNameReferenceHuffmanMask & b) == InsertWithoutNameReferenceHuffmanMask; if (_integerDecoder.BeginTryDecode((byte)prefixInt, InsertWithoutNameReferencePrefix, out intResult)) { OnStringLength(intResult, State.Name); } else { _state = State.NameIndex; } } else if ((b & DuplicateMask) == DuplicateRepresentation) { prefixInt = DuplicatePrefixMask & b; if (_integerDecoder.BeginTryDecode((byte)prefixInt, DuplicatePrefix, out intResult)) { OnDuplicate(intResult); } else { _state = State.Duplicate; } } break; case State.DynamicTableCapcity: if (_integerDecoder.TryDecode(b, out intResult)) { OnDynamicTableCapacity(intResult); } break; case State.NameIndex: if (_integerDecoder.TryDecode(b, out intResult)) { OnNameIndex(intResult); } break; case State.NameLength: if (_integerDecoder.TryDecode(b, out intResult)) { OnStringLength(intResult, nextState: State.Name); } break; case State.Name: _stringOctets[_stringIndex++] = b; if (_stringIndex == _stringLength) { OnString(nextState: State.ValueLength); } break; case State.ValueLength: _huffman = (b & HuffmanMask) != 0; // TODO confirm this. if (_integerDecoder.BeginTryDecode((byte)(b & ~HuffmanMask), StringLengthPrefix, out intResult)) { OnStringLength(intResult, nextState: State.Value); if (intResult == 0) { ProcessValue(); } } else { _state = State.ValueLengthContinue; } break; case State.ValueLengthContinue: if (_integerDecoder.TryDecode(b, out intResult)) { OnStringLength(intResult, nextState: State.Value); if (intResult == 0) { ProcessValue(); } } break; case State.Value: _stringOctets[_stringIndex++] = b; if (_stringIndex == _stringLength) { ProcessValue(); } break; case State.Duplicate: if (_integerDecoder.TryDecode(b, out intResult)) { OnDuplicate(intResult); } break; } } private void OnStringLength(int length, State nextState) { if (length > _stringOctets.Length) { throw new QPackDecodingException(/*CoreStrings.FormatQPackStringLengthTooLarge(length, _stringOctets.Length)*/); } _stringLength = length; _stringIndex = 0; _state = nextState; } private void ProcessValue() { OnString(nextState: State.Ready); var headerNameSpan = new Span<byte>(_headerName, 0, _headerNameLength); var headerValueSpan = new Span<byte>(_headerValueOctets, 0, _headerValueLength); DynamicTable.Insert(headerNameSpan, headerValueSpan); } private void OnString(State nextState) { int Decode(byte[] dst) { if (_huffman) { return Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst); } else { Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength); return _stringLength; } } try { if (_state == State.Name) { _headerName = _headerNameOctets; _headerNameLength = Decode(_headerNameOctets); } else { _headerValueLength = Decode(_headerValueOctets); } } catch (HuffmanDecodingException ex) { throw new QPackDecodingException(""/*CoreStrings.QPackHuffmanError*/, ex); } _state = nextState; } private void OnNameIndex(int index) { var header = GetHeader(index); _headerName = header.Name; _headerNameLength = header.Name.Length; _state = State.ValueLength; } private void OnDynamicTableCapacity(int dynamicTableSize) { // Call Decoder to update the table size. DynamicTable.Resize(dynamicTableSize); _state = State.Ready; } private void OnDuplicate(int index) { _dynamicTable.Duplicate(index); _state = State.Ready; } private System.Net.Http.QPack.HeaderField GetHeader(int index) { try { return _s ? H3StaticTable.Get(index) : _dynamicTable[index]; } catch (IndexOutOfRangeException ex) { throw new QPackDecodingException("" /*CoreStrings.FormatQPackErrorIndexOutOfRange(index)*/, ex); } } }