// Called when we have complete header with name and value. private void OnHeaderComplete(HeaderCallback onHeader, object onHeaderState, ReadOnlySpan <byte> headerName, ReadOnlySpan <byte> headerValue) { // Call provided callback. onHeader(onHeaderState, headerName, headerValue); if (_index) { _dynamicTable.Insert(headerName, headerValue); } }
public void Decode(ReadOnlySpan <byte> data, HeaderCallback onHeader, object onHeaderState) { for (int i = 0; i < data.Length; i++) { byte b = data[i]; switch (_state) { case State.Ready: // TODO: Instead of masking and comparing each prefix value, // consider doing a 16-way switch on the first four bits (which is the max prefix size). // Look at this once we have more concrete perf data. if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation) { int val = b & ~IndexedHeaderFieldMask; if (_integerDecoder.StartDecode((byte)val, IndexedHeaderFieldPrefix)) { OnIndexedHeaderField(_integerDecoder.Value, onHeader, onHeaderState); } else { _state = State.HeaderFieldIndex; } } else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation) { _index = true; int val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask; if (val == 0) { _state = State.HeaderNameLength; } else if (_integerDecoder.StartDecode((byte)val, LiteralHeaderFieldWithIncrementalIndexingPrefix)) { OnIndexedHeaderName(_integerDecoder.Value); } else { _state = State.HeaderNameIndex; } } else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation) { _index = false; int val = b & ~LiteralHeaderFieldWithoutIndexingMask; if (val == 0) { _state = State.HeaderNameLength; } else if (_integerDecoder.StartDecode((byte)val, LiteralHeaderFieldWithoutIndexingPrefix)) { OnIndexedHeaderName(_integerDecoder.Value); } else { _state = State.HeaderNameIndex; } } else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation) { _index = false; int val = b & ~LiteralHeaderFieldNeverIndexedMask; if (val == 0) { _state = State.HeaderNameLength; } else if (_integerDecoder.StartDecode((byte)val, LiteralHeaderFieldNeverIndexedPrefix)) { OnIndexedHeaderName(_integerDecoder.Value); } else { _state = State.HeaderNameIndex; } } else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation) { if (_integerDecoder.StartDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix)) { // TODO: validate that it's less than what's defined via SETTINGS _dynamicTable.Resize(_integerDecoder.Value); } else { _state = State.DynamicTableSizeUpdate; } } else { // Can't happen Debug.Fail("Unreachable code"); throw new InternalException(); } break; case State.HeaderFieldIndex: if (_integerDecoder.Decode(b)) { OnIndexedHeaderField(_integerDecoder.Value, onHeader, onHeaderState); } break; case State.HeaderNameIndex: if (_integerDecoder.Decode(b)) { OnIndexedHeaderName(_integerDecoder.Value); } break; case State.HeaderNameLength: _huffman = (b & HuffmanMask) != 0; if (_integerDecoder.StartDecode((byte)(b & ~HuffmanMask), StringLengthPrefix)) { OnStringLength(_integerDecoder.Value, nextState: State.HeaderName); } else { _state = State.HeaderNameLengthContinue; } break; case State.HeaderNameLengthContinue: if (_integerDecoder.Decode(b)) { OnStringLength(_integerDecoder.Value, nextState: State.HeaderName); } break; case State.HeaderName: _stringOctets[_stringIndex++] = b; if (_stringIndex == _stringLength) { OnString(nextState: State.HeaderValueLength); } break; case State.HeaderValueLength: _huffman = (b & HuffmanMask) != 0; if (_integerDecoder.StartDecode((byte)(b & ~HuffmanMask), StringLengthPrefix)) { OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue); } else { _state = State.HeaderValueLengthContinue; } break; case State.HeaderValueLengthContinue: if (_integerDecoder.Decode(b)) { OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue); } break; case State.HeaderValue: _stringOctets[_stringIndex++] = b; if (_stringIndex == _stringLength) { OnString(nextState: State.Ready); var headerNameSpan = new ReadOnlySpan <byte>(_headerName, 0, _headerNameLength); var headerValueSpan = new ReadOnlySpan <byte>(_headerValueOctets, 0, _headerValueLength); onHeader(onHeaderState, headerNameSpan, headerValueSpan); if (_index) { _dynamicTable.Insert(headerNameSpan, headerValueSpan); } } break; case State.DynamicTableSizeUpdate: if (_integerDecoder.Decode(b)) { if (_integerDecoder.Value > _maxDynamicTableSize) { // Dynamic table size update is too large. throw new HPackDecodingException(); } _dynamicTable.Resize(_integerDecoder.Value); _state = State.Ready; } break; default: // Can't happen Debug.Fail("HPACK decoder reach an invalid state"); throw new InternalException(_state); } } }