Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fpc-src / usr / share / fpcsrc / 3.2.0 / packages / fcl-web / src / hpack / uhpackimp.pp
Size: Mime:
unit uhpackimp;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, uhpacktables;

const
  HPACK_MAX_HEADER_SIZE = 8192;
  HPACK_MAX_HEADER_TABLE_SIZE = 4096;

(*
 * Tries to "inline" some specific and short time critical functions.
 *)
{$DEFINE USEINLINE}

(*
 * This setting applies a bit manual optimizations here and there, but most
 * of them could stop correctly working if static information is changed in a
 * future, like THPackStaticTable fields (count and order). This optimizations
 * only gain around 0.50% in speed time.
 *)
{$DEFINE MANUALOPTIMIZATIONS}

type

  THPackHeaderAddEvent = procedure (aName,aValue: RawByteString; aSensitive: Boolean) of object;
  THPACKException=class (Exception);

  THPackHeaderTextItem=record
    HeaderName: RawByteString;
    HeaderValue: RawByteString;
    IsSensitive: Boolean;
  end;
  PHPackHeaderTextItem=^THPackHeaderTextItem;

  { THPackHeaderTextList }

  THPackHeaderTextList=class(TObject)
  private
    function Get(Index: integer): PHPackHeaderTextItem; overload;
    function GetAsText: string;
    function GetCount: integer;
  protected
    FList: TFPList;
  public
    constructor Create;
    destructor  Destroy; override;
    function Add(const aName, aValue: RawByteString; const aSensitive: Boolean=false
      ): integer;
    procedure Clear;
    function  GetHeaderValue(const aName: String; out aValue: String): Boolean;
    property  Count: integer read GetCount;
    property  Item[Index: integer]: PHPackHeaderTextItem read Get; default;
    property  Text: string read GetAsText;
  end;

  { THPackHeaderField }

  THPackHeaderField=class
  private
  protected
    FName: RawByteString;
    FValue: RawByteString;
  public
    const
      HEADER_ENTRY_OVERHEAD = 32;
  public
    // Section 4.1. Calculating Table Size
    // The additional 32 octets account for an estimated
    // overhead associated with the structure.
    class function SizeOf(const aName, aValue: RawByteString): Integer;

    constructor Create(const aName,aValue: RawByteString);
    function Size: Integer;

  end;

  { THPackHuffmanEncoder }

  THPackHuffmanEncoder=class
  private
  protected
    FCodes: PDWORD;
    FLengths: PByte;
  public
    constructor Create;
    constructor Create(const aCodes: PDWORD; const aLengths: PByte);
    function  GetEncodedLength(aData: RawByteString): integer;
    procedure Encode(aOutputStream: TStream; aData: RawByteString);
    procedure Encode(aOutputStream: TStream; aData: RawByteString; aOff,aLen: integer);
  end;

  { THPackHuffmanNode }

  THPackHuffmanNode=class
  private
  protected
    FSymbol: integer;      // terminal nodes have a symbol
    FBits: integer;        // number of bits matched by the node
    FChildren: array of THPackHuffmanNode; // internal nodes have children
    class procedure Insert(aRoot: THPackHuffmanNode; aSymbol: integer; aCode: integer; aLength: BYTE);
    class function BuildTree(const aCodes: PDWORD; const aLengths: PByte): THPackHuffmanNode;
  public
    constructor Create;
    constructor Create(aSymbol: Integer; aBits: Integer);
    destructor Destroy; override;
    function  isTerminal: Boolean;{$IFDEF USEINLINE}inline;{$ENDIF}
  end;

  { THPackHuffmanDecoder }

  THPackHuffmanDecoder=class
  private
  protected
    FCodes: PDWORD;
    FLengths: PByte;
    FRoot: THPackHuffmanNode;
  public
    constructor Create;
    constructor Create(const aCodes: PDWORD; const aLengths: PByte);
    destructor Destroy; override;
    function Decode(aBuf: RawByteString): RawByteString;
  end;

  THPackHuffman=class
  private
  protected
    class var
    FRefCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    class var Encoder: THPackHuffmanEncoder;
    class var Decoder: THPackHuffmanDecoder;
  end;

  { THPackDynamicTable }

  THPackDynamicTable=class
  private
    FSize: integer;
    FCapacity: integer;
  protected
    // a circular queue of header fields
    FHeaderFields: array of THPackHeaderField;
    FHead: integer;
    FTail: integer;
    procedure SetCapacity(aCapacity: integer);
    procedure Clear;
    function  Remove: THPackHeaderField;
    function  EntriesCount: integer; {$IFDEF USEINLINE}inline;{$ENDIF}
  public
    constructor Create(aInitialCapacity: integer);
    destructor Destroy; override;
    procedure Add(aHeaderField: THPackHeaderField);
    function  GetEntry(aIndex: integer): THPackHeaderField; {$IFDEF USEINLINE}inline;{$ENDIF}
    function  GetNameIndex(const aName: RawByteString): integer;
    function  GetEntry(const aName, aValue: RawByteString): THPackHeaderField;
    function  GetEntryIndex(const aName, aValue: RawByteString): integer;
    function  GetEntryIndex(const aHeader: THPackHeaderField): integer;
    procedure EnsureCapacityToHold(const aHeaderSize: integer);
    property  Size: integer read FSize;
    property  Capacity: integer read FCapacity;
  end;

  { THPackStaticTable }

  THPackStaticTable=class
  private
    HPackStaticTable: array [1..61] of THPackHeaderField; static;
  protected
  public
    class procedure InitializeStaticTable;
    class procedure DestroyStaticTable;
    class function  GetEntry(aIndex: integer): THPackHeaderField;
    (**
     * Returns the lowest index value for the given header field name in the static table.
     * Returns -1 if the header field name is not in the static table.
     *)
    class function  GetIndex(const aName: RawByteString): Integer;
    (**
     * Returns the index value for the given header field in the static table.
     * Returns -1 if the header field is not in the static table.
     *)
    class function GetIndex(const aName,aValue: RawByteString): integer;
    class function TableLength: integer; {$IFDEF USEINLINE}inline;{$ENDIF}
  end;

  { THPackDecoder }

  THPackDecoder=class
  private
    DynamicTable: THPackDynamicTable;
    MaxHeaderSize: integer;
    MaxDynamicTableSize: integer;
    EncoderMaxDynamicTableSize: integer;
    MaxDynamicTableSizeChangeRequired: Boolean;
    HeaderSize: integer;
    State: THPackState;
    IndexType: THPackIndexType;
    FIndex: integer;
    HuffmanEncoded: Boolean;
    SkipLength: integer;
    NameLength: integer;
    ValueLength: integer;
    FName: RawByteString;
    FMustReset: Boolean;
  protected
    FHeaderListenerAddHeader: THPackHeaderAddEvent;
    FDecodedHeaders: THPackHeaderTextList;
    Huffman: THPackHuffman;

    procedure Reset;
    function  GetHeaderField(aIndex: integer): THPackHeaderField;
    procedure SetDynamicTableSize(aDynamicTableSize: integer);
    procedure ReadName (aIndex: integer);
    function  ReadStringLiteral(aStream: TStream; aLength: integer): RawByteString;
    procedure AddHeader(aName,aValue: RawByteString; aSensitive: Boolean);
    procedure InsertHeader(aName,aValue: RawByteString; aIndexType: THPackIndexType);
    function  DecodeULE128(aStream: TStream): integer;
    procedure IndexHeader(aIndex: integer);
    function  ExceedsMaxHeaderSize(aSize: integer): Boolean;

    procedure DoAddHeader(aName,aValue: RawByteString; aSensitive: Boolean); virtual;

  public
    constructor Create;
    constructor Create(aMaxHeaderSize, aMaxHeaderTableSize: integer);
    destructor Destroy; override;
    procedure Decode(aStream: TStream);
    procedure Decode(aString: RawByteString);
    function  GetMaxHeaderTableSize: Integer;
    procedure SetMaxHeaderTableSize(aMaxHeaderTableSize: integer);
    function  EndHeaderBlockTruncated: Boolean;
    property  OnAddHeader: THPackHeaderAddEvent read FHeaderListenerAddHeader write FHeaderListenerAddHeader;
    property  DecodedHeaders: THPackHeaderTextList read FDecodedHeaders;
  end;

  { THPackEncoder }

  THPackEncoder=class
  private
  private
    // Used for debugging purposes, modifies class behaviour using or not
    // indexing, or Huffman compression.
    UseIndexing: Boolean;
    ForceHuffmanOn: Boolean;
    ForceHuffmanOff: Boolean;

    procedure EncodeLiteral(aOutStream: TStream; const aName: RawByteString; const aValue: RawByteString; const aIndexType: THPackIndexType; const aNameIndex: Integer);
    procedure EncodeInteger(aOutStream: TStream; const aMask: integer; const n: integer; const i: integer);
    procedure EncodeStringLiteral(aOutStream: TStream; const aString: RawByteString);
    function  GetNameIndex(const aName: RawByteString): integer;
    procedure Add(const aName, aValue: RawByteString);
    procedure Clear;

  protected
    DynamicTable: THPackDynamicTable;
    Huffman: THPackHuffman;
  public
    constructor Create;
    constructor Create(const aMaxHeaderTableSize: Integer);
    constructor Create(const aMaxHeaderTableSize: Integer;
                       const aUseIndexing: Boolean;
                       const aForceHuffmanOn: Boolean;
                       const aForceHuffmanOff: Boolean);
    destructor Destroy; override;
    procedure EncodeHeader(aOutStream: TStream; const aName: RawByteString; const aValue: RawByteString; const aSensitive: Boolean);
    procedure SetMaxHeaderTableSize(aOutStream: TStream; const aNewMaxHeaderTableSize: Integer);

  end;

implementation

const
    NOT_FOUND=-1;

{ THPackHeaderTextList }

function THPackHeaderTextList.Get(Index: integer): PHPackHeaderTextItem;
begin
  Result:=PHPackHeaderTextItem(FList[Index]);
end;

function THPackHeaderTextList.GetAsText: string;
const
  HEADER_SEPARATOR_MARK: char=':';
var
  j: integer;
  P: PHPackHeaderTextItem;
  O: string;
  w: integer;
  wl, lle: integer;
  Allocated: integer;
  LE : string;
  procedure EnsureSpace(const aNeeded: integer);{$IFDEF USEINLINE}inline;{$ENDIF}
  begin
    if (w+aNeeded)>Allocated then begin
      Allocated:=Allocated+aNeeded;
      SetLength(O,Allocated);
    end;
  end;
  
begin
  LE:=LineEnding;
  LLE:=Length(LE);
  Allocated:=HPACK_MAX_HEADER_SIZE*2;
  SetLength(O,Allocated);
  w:=1;
  for j := 0 to Pred(FList.Count) do begin
    P:=PHPackHeaderTextItem(FList[j]);
    wl:=Length(P^.HeaderName);
    if wl=0 then begin
      Raise THPACKException.Create('Header name is empty');
    end;
    EnsureSpace(wl);
    move(P^.HeaderName[1],O[w],wl);
    inc(w,wl);
    wl:=1;
    EnsureSpace(wl);
    move(HEADER_SEPARATOR_MARK,O[w],wl);
    inc(w,wl);
    wl:=Length(P^.HeaderValue);
    if wl>0 then begin
      EnsureSpace(wl);
      move(P^.HeaderValue[1],O[w],wl);
      inc(w,wl);
    end;
    EnsureSpace(lle);
    move(LE[1],O[w],lle);
    inc(w,lle);
  end;
  SetLength(O,w-1);
  Result:=O;
end;

function THPackHeaderTextList.GetCount: integer;
begin
  Result:=FList.Count;
end;

constructor THPackHeaderTextList.Create;
begin
  FList:=TFPList.Create;
end;

destructor THPackHeaderTextList.Destroy;
begin
  Clear;
  FList.Free;
end;

function THPackHeaderTextList.Add(const aName, aValue: RawByteString;
  const aSensitive: Boolean=false): integer;
var
  P: PHPackHeaderTextItem;
begin
  New(P);
  P^.HeaderName:=aName;
  P^.HeaderValue:=aValue;
  P^.IsSensitive:=aSensitive;
  Result:=FList.Add(P);
end;

procedure THPackHeaderTextList.Clear;
var
  j: integer;
begin
  for j := 0 to Pred(FList.Count) do begin
    Dispose(PHPackHeaderTextItem(FList[j]));
  end;
  FList.Clear;
end;

function THPackHeaderTextList.GetHeaderValue(const aName: String; out
  aValue: String): Boolean;
var
  j: integer;
  p: PHPackHeaderTextItem;
begin
  for j := 0 to Pred(FList.Count) do begin
    P:=PHPackHeaderTextItem(FList[j]);
    if p^.HeaderName=aName then begin
      aValue:=p^.HeaderValue;
      Result:=true;
      exit;
    end;
  end;
  aValue:='';
  Result:=false;
end;

{ THPackEncoder }

constructor THPackEncoder.Create(const aMaxHeaderTableSize: Integer);
begin
  Create(aMaxHeaderTableSize,true,false,false);
end;

constructor THPackEncoder.Create(const aMaxHeaderTableSize: Integer;
  const aUseIndexing: Boolean; const aForceHuffmanOn: Boolean;
  const aForceHuffmanOff: Boolean);
begin
  if aMaxHeaderTableSize < 0 then begin
    Raise THPACKException.CreateFmt('Illegal capacity: %d',[aMaxHeaderTableSize]);
  end;
  DynamicTable:=THPackDynamicTable.Create(aMaxHeaderTableSize);
  UseIndexing := aUseIndexing;
  ForceHuffmanOn := aForceHuffmanOn;
  ForceHuffmanOff := aForceHuffmanOff;
  Huffman:=THPackHuffman.Create;
end;

destructor THPackEncoder.Destroy;
begin
  Clear;
  FreeAndNil(Huffman);
  FreeAndNil(DynamicTable);
  inherited Destroy;
end;

procedure THPackEncoder.EncodeHeader(aOutStream: TStream;
  const aName: RawByteString; const aValue: RawByteString;
  const aSensitive: Boolean);
var
  NameIndex: integer;
  StaticTableIndex: integer;
  HeaderSize: integer;
  ThisHeaderField: THPackHeaderField;
  Index: integer;
  IndexType: THPackIndexType;
begin
  // If the header value is sensitive then it must never be indexed
  if aSensitive then begin
    NameIndex := GetNameIndex(aName);
    EncodeLiteral(aOutStream, aName, aValue, THPackIndexType.eHPackNEVER, NameIndex);
    exit;
  end;

  // If the peer will only use the static table
  if DynamicTable.Capacity = 0 then begin
    StaticTableIndex := THPackStaticTable.GetIndex(aName, aValue);
    if StaticTableIndex = NOT_FOUND then begin
      NameIndex := THPackStaticTable.GetIndex(aName);
      EncodeLiteral(aOutStream, aName, aValue, THPackIndexType.eHPackNONE, NameIndex);
    end else begin
      EncodeInteger(aOutStream, $80, 7, StaticTableIndex);
    end;
    exit;
  end;

  HeaderSize := THPackHeaderField.sizeOf(aName, aValue);

  // If the headerSize is greater than the max table size then it must be encoded literally
  if HeaderSize > DynamicTable.Capacity then begin
    NameIndex := GetNameIndex(aName);
    EncodeLiteral(aOutStream, aName, aValue, THPackIndexType.eHPackNONE, NameIndex);
    Exit;
  end;

  ThisHeaderField := DynamicTable.GetEntry(aName, aValue);
  if Assigned(ThisHeaderField) then begin
    Index := DynamicTable.GetEntryIndex(ThisHeaderField) + THPackStaticTable.TableLength;
    // Section 6.1. Indexed Header Field Representation
    EncodeInteger(aOutStream, $80, 7, Index);
  end else begin
    StaticTableIndex := THPackStaticTable.GetIndex(aName, aValue);
    if StaticTableIndex <> NOT_FOUND then begin
      // Section 6.1. Indexed Header Field Representation
      EncodeInteger(aOutStream, $80, 7, StaticTableIndex);
    end else begin
      NameIndex := GetNameIndex(aName);
      if UseIndexing then begin
        DynamicTable.EnsureCapacityToHold(HeaderSize);
      end;
      if UseIndexing then begin
        IndexType:=THPackIndexType.eHPackINCREMENTAL;
      end else begin
        IndexType:=THPackIndexType.eHPackNONE;
      end;
      EncodeLiteral(aOutStream, aName, aValue, IndexType, NameIndex);
      if UseIndexing then begin
        Add(aName, aValue);
      end;
    end;
  end;
end;

procedure THPackEncoder.SetMaxHeaderTableSize(aOutStream: TStream;
  const aNewMaxHeaderTableSize: Integer);
begin
  if aNewMaxHeaderTableSize < 0 then begin
    Raise THPACKException.CreateFmt('Illegal Capacity %d',[aNewMaxHeaderTableSize]);
  end;
  if DynamicTable.Capacity = aNewMaxHeaderTableSize then begin
    //No change needed
    exit;
  end;
  DynamicTable.SetCapacity(aNewMaxHeaderTableSize);
  //DynamicTable.EnsureCapacityToHold(0);
  EncodeInteger(aOutStream, $20, 5, aNewMaxHeaderTableSize);
end;

procedure THPackEncoder.EncodeLiteral(aOutStream: TStream;
  const aName: RawByteString; const aValue: RawByteString;
  const aIndexType: THPackIndexType; const aNameIndex: Integer);
(**
 * Encode literal header field according to Section 6.2.
 *)
var
  Mask: Integer;
  PrefixBits: Integer;
  v: integer;
begin
  case aIndexType of
    eHPackINCREMENTAL:
      begin
        Mask := $40;
        PrefixBits := 6;
      end;
    eHPackNONE:
      begin
        Mask := $00;
        PrefixBits := 4;
      end;
    eHPackNEVER:
      begin
        Mask := $10;
        PrefixBits := 4;
      end;
    else
      Raise THPACKException.Create('Should not reach here');
  end;
  if aNameIndex=NOT_FOUND then begin
    v:=0;
  end else begin
    v:=aNameIndex;
  end;
  EncodeInteger(aOutStream, Mask, PrefixBits, v);
  if aNameIndex = NOT_FOUND then begin
    EncodeStringLiteral(aOutStream, aName);
  end;
  EncodeStringLiteral(aOutStream, aValue);
end;

procedure THPackEncoder.EncodeInteger(aOutStream: TStream; const aMask: integer;
  const n: integer; const i: integer);
var
  nBits: Integer;
  Len: DWORD;
begin
  if (n < 0) or (n > 8) then begin
    Raise THPACKException.CreateFmt('Encode Integer Illegal Argument Exception ("N<0|N>8": %d)',[n]);
  end;
  nBits := $FF shr (8 - n);
  if i < nBits then begin
    aOutStream.WriteByte(BYTE(aMask or i));
  end else begin
    aOutStream.WriteByte(BYTE(aMask or nBits));
    Len := i - nBits;
    while (true) do begin
      if (Len and  (not $7F)) = 0 then begin
        aOutStream.WriteByte(BYTE(Len));
        exit;
      end else begin
        aOutStream.WriteByte(Byte((Len and $7F) or $80));
        Len:=Len shr 7;
      end;
    end;
  end;
end;

procedure THPackEncoder.EncodeStringLiteral(aOutStream: TStream;
  const aString: RawByteString);
var
  HuffmanLength: integer;
begin
  if Length(aString)=0 then begin
    EncodeInteger(aOutStream, $00, 7, 0);
    exit;
  end;
  HuffmanLength := THpackHuffman.Encoder.GetEncodedLength(aString);
  if ((HuffmanLength < Length(aString)) and not forceHuffmanOff) or forceHuffmanOn then begin
    EncodeInteger(aOutStream, $80, 7, HuffmanLength);
    if Length(aString)>0 then begin
      THPackHuffman.Encoder.Encode(aOutStream, aString);
    end;
  end else begin
    EncodeInteger(aOutStream, $00, 7, Length(aString));
    if Length(aString)>0 then begin
      aOutStream.Write(aString[1],Length(aString));
    end;
  end;
end;

function THPackEncoder.GetNameIndex(const aName: RawByteString): integer;
var
  Index: integer;
begin
  Index := THPackStaticTable.GetIndex(aName);
  if Index = NOT_FOUND then begin
    Index := DynamicTable.GetNameIndex(aName);
    if Index >= 0 then begin
      inc(Index,THPackStaticTable.TableLength);
    end;
  end;
  Result:=Index;
end;

procedure THPackEncoder.Add(const aName, aValue: RawByteString);
(**
 * Add the header field to the dynamic table.
 * Entries are evicted from the dynamic table until the size of the table
 * and the new header field is less than the table's capacity.
 * If the size of the new entry is larger than the table's capacity,
 * the dynamic table will be cleared.
 *)
var
//  HeaderSize: Integer;
  Header: THPackHeaderField;
begin
  (*
  HeaderSize := THPackHeaderField.SizeOf(aName, aValue);

  // Clear the table if the header field size is larger than the capacity.
  if HeaderSize > Capacity then begin
    Clear;
    // Do not add this entry to the DynamicTable
    exit;
  end;

  // Evict oldest entries until we have enough capacity.
  while (Size + HeaderSize) > Capacity do begin
    Remove().Free;
  end;
  *)
  // Copy name and value that modifications of original do not affect the dynamic table.

  Header:=THPackHeaderField.Create(aName,aValue);
  DynamicTable.Add(Header);

  //Inc(Size,HeaderSize);
end;

procedure THPackEncoder.Clear;
(**
 * Remove all entries from the dynamic table.
 *)
begin
  DynamicTable.Clear;
end;

constructor THPackEncoder.Create;
begin
  Create(HPACK_MAX_HEADER_TABLE_SIZE,true,false,false);
end;

{ THPackHuffman }

constructor THPackHuffman.Create;
begin
  if FRefCount=0 then begin
    Encoder:=THPackHuffmanEncoder.Create;
    Decoder:=THPackHuffmanDecoder.Create;
  end;
  inc(FRefCount);
end;

destructor THPackHuffman.Destroy;
begin
  dec(FRefCount);
  if FRefCount=0 then begin
    FreeAndNil(Encoder);
    FreeAndNil(Decoder);
  end;
  inherited Destroy;
end;

{ THPackStaticTable }

class function THPackStaticTable.TableLength: integer;
begin
  Result:=Length(HPackStaticTable);
end;

class procedure THPackStaticTable.InitializeStaticTable;
const
  EMPTY='';
begin
  // Appendix A: Static Table
  // http://tools.ietf.org/html/rfc7541#appendix-A
  HPackStaticTable[01]:=THPackHeaderField.Create(':authority',EMPTY);
  HPackStaticTable[02]:=THPackHeaderField.Create(':method', 'GET');
  HPackStaticTable[03]:=THPackHeaderField.Create(':method', 'POST');
  HPackStaticTable[04]:=THPackHeaderField.Create(':path', '/');
  HPackStaticTable[05]:=THPackHeaderField.Create(':path', '/index.html');
  HPackStaticTable[06]:=THPackHeaderField.Create(':scheme', 'http');
  HPackStaticTable[07]:=THPackHeaderField.Create(':scheme', 'https');
  HPackStaticTable[08]:=THPackHeaderField.Create(':status', '200');
  HPackStaticTable[09]:=THPackHeaderField.Create(':status', '204');
  HPackStaticTable[10]:=THPackHeaderField.Create(':status', '206');
  HPackStaticTable[11]:=THPackHeaderField.Create(':status', '304');
  HPackStaticTable[12]:=THPackHeaderField.Create(':status', '400');
  HPackStaticTable[13]:=THPackHeaderField.Create(':status', '404');
  HPackStaticTable[14]:=THPackHeaderField.Create(':status', '500');
  HPackStaticTable[15]:=THPackHeaderField.Create('accept-charset', EMPTY);
  HPackStaticTable[16]:=THPackHeaderField.Create('accept-encoding', 'gzip, deflate');
  HPackStaticTable[17]:=THPackHeaderField.Create('accept-language', EMPTY);
  HPackStaticTable[18]:=THPackHeaderField.Create('accept-ranges', EMPTY);
  HPackStaticTable[19]:=THPackHeaderField.Create('accept', EMPTY);
  HPackStaticTable[20]:=THPackHeaderField.Create('access-control-allow-origin', EMPTY);
  HPackStaticTable[21]:=THPackHeaderField.Create('age', EMPTY);
  HPackStaticTable[22]:=THPackHeaderField.Create('allow', EMPTY);
  HPackStaticTable[23]:=THPackHeaderField.Create('authorization', EMPTY);
  HPackStaticTable[24]:=THPackHeaderField.Create('cache-control', EMPTY);
  HPackStaticTable[25]:=THPackHeaderField.Create('content-disposition', EMPTY);
  HPackStaticTable[26]:=THPackHeaderField.Create('content-encoding', EMPTY);
  HPackStaticTable[27]:=THPackHeaderField.Create('content-language', EMPTY);
  HPackStaticTable[28]:=THPackHeaderField.Create('content-length', EMPTY);
  HPackStaticTable[29]:=THPackHeaderField.Create('content-location', EMPTY);
  HPackStaticTable[30]:=THPackHeaderField.Create('content-range', EMPTY);
  HPackStaticTable[31]:=THPackHeaderField.Create('content-type', EMPTY);
  HPackStaticTable[32]:=THPackHeaderField.Create('cookie', EMPTY);
  HPackStaticTable[33]:=THPackHeaderField.Create('date', EMPTY);
  HPackStaticTable[34]:=THPackHeaderField.Create('etag', EMPTY);
  HPackStaticTable[35]:=THPackHeaderField.Create('expect', EMPTY);
  HPackStaticTable[36]:=THPackHeaderField.Create('expires', EMPTY);
  HPackStaticTable[37]:=THPackHeaderField.Create('from', EMPTY);
  HPackStaticTable[38]:=THPackHeaderField.Create('host', EMPTY);
  HPackStaticTable[39]:=THPackHeaderField.Create('if-match', EMPTY);
  HPackStaticTable[40]:=THPackHeaderField.Create('if-modified-since', EMPTY);
  HPackStaticTable[41]:=THPackHeaderField.Create('if-none-match', EMPTY);
  HPackStaticTable[42]:=THPackHeaderField.Create('if-range', EMPTY);
  HPackStaticTable[43]:=THPackHeaderField.Create('if-unmodified-since', EMPTY);
  HPackStaticTable[44]:=THPackHeaderField.Create('last-modified', EMPTY);
  HPackStaticTable[45]:=THPackHeaderField.Create('link', EMPTY);
  HPackStaticTable[46]:=THPackHeaderField.Create('location', EMPTY);
  HPackStaticTable[47]:=THPackHeaderField.Create('max-forwards', EMPTY);
  HPackStaticTable[48]:=THPackHeaderField.Create('proxy-authenticate', EMPTY);
  HPackStaticTable[49]:=THPackHeaderField.Create('proxy-authorization', EMPTY);
  HPackStaticTable[50]:=THPackHeaderField.Create('range', EMPTY);
  HPackStaticTable[51]:=THPackHeaderField.Create('referer', EMPTY);
  HPackStaticTable[52]:=THPackHeaderField.Create('refresh', EMPTY);
  HPackStaticTable[53]:=THPackHeaderField.Create('retry-after', EMPTY);
  HPackStaticTable[54]:=THPackHeaderField.Create('server', EMPTY);
  HPackStaticTable[55]:=THPackHeaderField.Create('set-cookie', EMPTY);
  HPackStaticTable[56]:=THPackHeaderField.Create('strict-transport-security', EMPTY);
  HPackStaticTable[57]:=THPackHeaderField.Create('transfer-encoding', EMPTY);
  HPackStaticTable[58]:=THPackHeaderField.Create('user-agent', EMPTY);
  HPackStaticTable[59]:=THPackHeaderField.Create('vary', EMPTY);
  HPackStaticTable[60]:=THPackHeaderField.Create('via', EMPTY);
  HPackStaticTable[61]:=THPackHeaderField.Create('www-authenticate', EMPTY);
end;

class procedure THPackStaticTable.DestroyStaticTable;
var
  j: integer;
begin
  for j := Low(HPackStaticTable) to High(HPackStaticTable) do begin
    HPackStaticTable[j].Free;
    HPackStaticTable[j]:=nil;
  end;
end;

class function THPackStaticTable.GetEntry(aIndex: integer): THPackHeaderField;
begin
  Result:=HPackStaticTable[aIndex];
end;

class function THPackStaticTable.GetIndex(const aName: RawByteString
  ): Integer;
var
  lLeft,lRight: integer;
  Half: integer;
  c: integer;
begin
  lLeft:=Low(HPackStaticTable);
  lRight:=High(HPackStaticTable);
  {$IFDEF MANUALOPTIMIZATIONS}
  // Manual optimization
  if aName[1]>'c' then begin
    lLeft:=33;
  end;
  {$ENDIF}
  while lLeft<=lRight do begin
    Half:=(lLeft+lRight) div 2; // No overflow problem, low amount of elements
    c:=CompareStr(aName,HPackStaticTable[Half].FName);
    if c=0 then begin
      dec(Half);
      while Half>=lLeft do begin
        if HPackStaticTable[Half].FName<>aName then begin
          break;
        end;
        dec(Half);
      end;
      Result:=Half+1;
      exit;
    end else if c<0 then begin
      lRight:=Half-1;
    end else begin
      // c > 0
      lLeft:=Half+1;
    end;
  end;
  Result:=NOT_FOUND;
end;

class function THPackStaticTable.GetIndex(const aName, aValue: RawByteString): Integer;
var
  lLeft,lRight: integer;
  Half: integer;
  c: integer;
begin
  lLeft:=Low(HPackStaticTable);
  lRight:=High(HPackStaticTable);
  {$IFDEF MANUALOPTIMIZATIONS}
  // Manual optimization
  if aName[1]>'c' then begin
    lLeft:=33;
  end;
  {$ENDIF}
  while lLeft<=lRight do begin
    Half:=(lLeft+lRight) div 2; // No overflow problem, low amount of elements
    c:=CompareStr(aName,HPackStaticTable[Half].FName);
    if c=0 then begin
      c:=CompareStr(aValue,HPackStaticTable[Half].FValue);
      if c=0 then begin
        Result:=Half;
        exit;
      end else if c<0 then begin
        lRight:=Half-1;
      end else begin
        // c > 0
        lLeft:=Half+1;
      end;
    end else if c<0 then begin
      lRight:=Half-1;
    end else begin
      // c > 0
      lLeft:=Half+1;
    end;
  end;
  Result:=NOT_FOUND;
end;

{ THPackDecoder }

procedure THPackDecoder.Reset;
begin
  HeaderSize := 0;
  State := THPackState.READ_HEADER_REPRESENTATION;
  IndexType := THPackIndexType.eHPackNONE;
  FDecodedHeaders.Clear;
  FMustReset:=false;
end;

function THPackDecoder.EndHeaderBlockTruncated: Boolean;
begin
  Result:= HeaderSize > MaxHeaderSize;
  FMustReset:=true;
end;

procedure THPackDecoder.SetMaxHeaderTableSize(aMaxHeaderTableSize: integer);
begin
  if FMustReset then Reset;
  MaxDynamicTableSize := aMaxHeaderTableSize;
  if (MaxDynamicTableSize < EncoderMaxDynamicTableSize) then begin
    // decoder requires less space than encoder
    // encoder MUST signal this change
    MaxDynamicTableSizeChangeRequired := true;
    DynamicTable.SetCapacity(MaxDynamicTableSize);
  end;
end;

function THPackDecoder.GetHeaderField(aIndex: integer): THPackHeaderField;
begin
  Result:=DynamicTable.GetEntry(aIndex + 1);
end;

procedure THPackDecoder.SetDynamicTableSize(aDynamicTableSize: integer);
begin
  if aDynamicTableSize > MaxDynamicTableSize then begin
    Raise THPACKException.Create('Invalid MAX_DYNAMIC_TABLE_SIZE');
  end;
  EncoderMaxDynamicTableSize := aDynamicTableSize;
  MaxDynamicTableSizeChangeRequired := false;
  DynamicTable.SetCapacity(aDynamicTableSize);
end;

procedure THPackDecoder.ReadName(aIndex: integer);
var
  HeaderField: THPackHeaderField;
begin
  if aIndex <= THPackStaticTable.TableLength then begin
    HeaderField:= THPackStaticTable.GetEntry(aIndex);
    FName := HeaderField.FName;
  end else if (aIndex - THPackStaticTable.TableLength <= DynamicTable.EntriesCount) then begin
    HeaderField := DynamicTable.GetEntry(aIndex - THPackStaticTable.TableLength);
    FName := HeaderField.FName;
  end else begin
    Raise THPACKException.Create('Illegal index value');
  end;
end;

function THPackDecoder.ReadStringLiteral(aStream: TStream; aLength: integer
  ): RawByteString;
var
  buf: RawByteString;
begin
  SetLength(buf,aLength);
  if (aStream.Read(buf[1],aLength) <> aLength) then begin
    Raise THPACKException.Create('Decompression exception in ReadStringLiteral');
  end;

  if (HuffmanEncoded) then begin
    Result:=Huffman.Decoder.Decode(buf);
  end else begin
    Result:=buf;
  end;
end;

procedure THPackDecoder.AddHeader(aName, aValue: RawByteString; aSensitive: Boolean);
var
  NewSize: Integer;
begin
  if aName='' then begin
    Raise THPACKException.Create('Header name is empty');
  end;
  NewSize := HeaderSize + Length(aName) + Length(aValue);
  if NewSize <= MaxHeaderSize then begin
    DoAddHeader(aName, aValue, aSensitive);
    HeaderSize := newSize;
  end else begin
    // truncation will be reported during EndHeaderBlockTruncated
    HeaderSize := MaxHeaderSize + 1;
  end;
end;

procedure THPackDecoder.InsertHeader(aName, aValue: RawByteString;
  aIndexType: THPackIndexType);
begin
  AddHeader(aName, aValue, aIndexType = THPackIndexType.eHPackNEVER);

  case (aIndexType) of
    eHPackNONE,
    eHPackNEVER: exit;
    eHPackINCREMENTAL: begin
        DynamicTable.Add(THPackHeaderField.Create(aName, aValue));
      end;
    else
      Raise THPACKException.Create('Should not reach here');
  end;
end;

function THPackDecoder.DecodeULE128(aStream: TStream): integer;
var
  EntryMark: int64;
  Shift: integer;
  b: BYTE;
  function InAvailable(): int64; inline;
  begin
    Result:=aStream.Size-aStream.Position;
  end;
begin
  Shift:=0;
  Result:=0;
  EntryMark:=aStream.Position;
  while (Shift < 32) do begin
    if (InAvailable() = 0) then begin
      // Buffer does not contain entire integer,
      // reset reader index and return -1.
      aStream.Position:=EntryMark;
      exit(-1);
    end;
    b := aStream.ReadByte;
    if ((Shift = 28) and ((b and $F8) <> 0)) then begin
      break;
    end;

    Result:=Result or ((b and $7F) shl Shift);

    if ((b and $80) = 0) then begin
      exit;
    end;
    Inc(shift,7);
  end;

  // Value exceeds Integer.MAX_VALUE
  aStream.Position:=EntryMark;
  Raise THPACKException.Create('Decompression error DecodeULE128');
end;

procedure THPackDecoder.IndexHeader(aIndex: integer);
var
  HeaderField: THPackHeaderField;
begin
  if (aIndex <= THPackStaticTable.TableLength) then begin
    HeaderField := THPackStaticTable.GetEntry(aIndex);
    //addHeader(headerListener, headerField.name, headerField.value, false);
    AddHeader(HeaderField.FName, HeaderField.FValue, False);
  end else if (aIndex - THPackStaticTable.TableLength <= DynamicTable.EntriesCount) then begin
    HeaderField := DynamicTable.GetEntry(aIndex - THPackStaticTable.TableLength);
    //addHeader(headerListener, headerField.name, headerField.value, false);
    AddHeader(HeaderField.FName, HeaderField.FValue, False);
  end else begin
    Raise THPACKException.Create('Illegal index value');
  end;
end;

function THPackDecoder.ExceedsMaxHeaderSize(aSize: integer): Boolean;
begin
  // Check new header size against max header size
  if aSize + HeaderSize <= MaxHeaderSize then begin
    exit(False);
  end;
  // truncation will be reported during EndHeaderBlockTruncated
  HeaderSize := MaxHeaderSize + 1;
  Result:=true;
end;

procedure THPackDecoder.DoAddHeader(aName, aValue: RawByteString; aSensitive: Boolean);
begin
  if Assigned(FHeaderListenerAddHeader) then begin
    FHeaderListenerAddHeader(aName,aValue,aSensitive);
  end;
  FDecodedHeaders.Add(aName,aValue,aSensitive);
end;

constructor THPackDecoder.Create;
begin
  Create(HPACK_MAX_HEADER_SIZE,HPACK_MAX_HEADER_TABLE_SIZE);
end;

constructor THPackDecoder.Create(aMaxHeaderSize, aMaxHeaderTableSize: integer);
begin
  Huffman:=THPackHuffman.Create;
  DynamicTable := THPackDynamicTable.Create(aMaxHeaderTableSize);
  MaxHeaderSize := aMaxHeaderSize;
  MaxDynamicTableSize := aMaxHeaderTableSize;
  EncoderMaxDynamicTableSize := aMaxHeaderTableSize;
  MaxDynamicTableSizeChangeRequired := false;
  FDecodedHeaders:=THPackHeaderTextList.Create;
  Reset();
end;

destructor THPackDecoder.Destroy;
begin
  FreeAndNil(Huffman);
  FreeAndNil(DynamicTable);
  FreeAndNil(FDecodedHeaders);
  inherited Destroy;
end;

procedure THPackDecoder.Decode(aStream: TStream);
  function InAvailable(): int64; inline;
  begin
    Result:=aStream.Size-aStream.Position;
  end;
var
  b: BYTE;
  MaxSize: integer;
  HeaderIndex: integer;
  NameIndex: integer;
  NewHeaderSize: integer;
  tmpbuffer: RawByteString;
  Value: RawByteString;
begin
  if FMustReset then Reset;
  while InAvailable() > 0 do begin
    case State of
    READ_HEADER_REPRESENTATION: begin
        b := aStream.ReadByte;
        if MaxDynamicTableSizeChangeRequired and ((b and $E0) <> $20) then begin
          // Encoder MUST signal maximum dynamic table size change
          Raise THPACKException.Create('Max dynamic table size change not notified');
        end;
        if (b > 127) then begin
          // Indexed Header Field
          FIndex := b and $7F;
          if (Findex = 0) then begin
            Raise THPACKException.Create('Illegal index value in Decode');
          end else if (Findex = $7F) then begin
            State := THPackState.READ_INDEXED_HEADER;
          end else begin
            IndexHeader(Findex);
          end;
        end else if ((b and $40) = $40) then begin
          // Literal Header Field with Incremental Indexing
          IndexType := THPackIndexType.eHPackINCREMENTAL;
          FIndex := b and $3F;
          if (Findex = 0) then begin
            State := THPackState.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
          end else if (findex = $3F) then begin
            State := THPackState.READ_INDEXED_HEADER_NAME;
          end else begin
            // Index was stored as the prefix
            ReadName(Findex);
            State := THPackState.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
          end;
        end else if ((b and $20) = $20) then begin
          // Dynamic Table Size Update
          Findex := b and $1F;
          if (Findex = $1F) then begin
            State := THPackState.READ_MAX_DYNAMIC_TABLE_SIZE;
          end else begin
            SetDynamicTableSize(Findex);
            State := THPackState.READ_HEADER_REPRESENTATION;
          end;
        end else begin
          // Literal Header Field without Indexing / never Indexed
          if (b and $10) = $10 then begin
            IndexType:=THPackIndexType.eHPackNEVER;
          end else begin
            IndexType:=THPackIndexType.eHPackNONE;
          end;
          Findex := b and $0F;
          if (Findex = 0) then begin
            State := THPackState.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
          end else if (Findex = $0F) then begin
            State := THpackState.READ_INDEXED_HEADER_NAME;
          end else begin
            // Index was stored as the prefix
            ReadName(FIndex);
            State := THpackState.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
          end;
        end;
      end;
    READ_MAX_DYNAMIC_TABLE_SIZE: begin
        MaxSize := decodeULE128(aStream);
        if (MaxSize = -1) then begin
          exit;
        end;
        // Check for numerical overflow
        if (MaxSize > High(Integer) - Findex) then begin
          Raise THPACKException.Create('Decompression exception in Decode-READ_MAX_DYNAMIC_TABLE_SIZE');
        end;
        SetDynamicTableSize(Findex + MaxSize);
        State := THPackState.READ_HEADER_REPRESENTATION;
      end;
    READ_INDEXED_HEADER: begin
        HeaderIndex := decodeULE128(aStream);
        if (HeaderIndex = -1) then begin
          exit;
        end;
        // Check for numerical overflow
        if (HeaderIndex > High(Integer) - Findex) then begin
          Raise THPACKException.Create('Decompression exception in Decode-READ_INDEXED_HEADER');
        end;

        IndexHeader(Findex + HeaderIndex);
        State := THPackState.READ_HEADER_REPRESENTATION;
      end;
    READ_INDEXED_HEADER_NAME: begin
        // Header Name matches an entry in the Header Table
        NameIndex := decodeULE128(aStream);
        if (NameIndex = -1) then begin
          Exit;
        end;

        // Check for numerical overflow
        if (NameIndex > High(Integer) - Findex) then begin
          Raise THPACKException.Create('Decompression exception in Decode-READ_INDEXED_HEADER_NAME');
        end;

        ReadName(Findex + NameIndex);
        State := THPackState.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
      end;
    READ_LITERAL_HEADER_NAME_LENGTH_PREFIX: begin
        b := aStream.ReadByte;
        HuffmanEncoded := (b and $80) = $80;
        Findex := b and $7F;
        if (Findex = $7f) then begin
          State := THPackState.READ_LITERAL_HEADER_NAME_LENGTH;
        end else begin
          NameLength := Findex;

          // Disallow empty names -- they cannot be represented in HTTP/1.x
          if (NameLength = 0) then begin
            Raise THPACKException.Create('Empty name');
          end;

          // Check name length against max header size
          if ExceedsMaxHeaderSize(NameLength) then begin
            if (IndexType = THPackIndexType.eHPackNONE) then begin
              // Name is unused so skip bytes
              FName := '';
              SkipLength := NameLength;
              State := THPackState.SKIP_LITERAL_HEADER_NAME;
              break;
            end;

            // Check name length against max dynamic table size
            if (NameLength + HPACK_HEADER_ENTRY_OVERHEAD > DynamicTable.Capacity) then begin
              DynamicTable.Clear();
              Fname := '';
              SkipLength := NameLength;
              State := THPackState.SKIP_LITERAL_HEADER_NAME;
              break;
            end;
          end;
          State := THPackState.READ_LITERAL_HEADER_NAME;
        end;
      end;
    READ_LITERAL_HEADER_NAME_LENGTH: begin
        // Header Name is a Literal String
        NameLength := decodeULE128(aStream);
        if (NameLength = -1) then begin
          exit;
        end;

        // Check for numerical overflow
        if (NameLength > High(Integer) - Findex) then begin
          Raise THPACKException.Create('Decompression exception in Decode-READ_LITERAL_HEADER_NAME_LENGTH');
        end;
        inc(NameLength,Findex);

        // Check name length against max header size
        if ExceedsMaxHeaderSize(NameLength) then begin
          if (IndexType = THPackIndexType.eHPackNONE) then begin
            // Name is unused so skip bytes
            Fname := '';
            SkipLength := NameLength;
            State := THPackState.SKIP_LITERAL_HEADER_NAME;
            break;
          end;

          // Check name length against max dynamic table size
          if (NameLength + HPACK_HEADER_ENTRY_OVERHEAD > DynamicTable.Capacity) then begin
            DynamicTable.Clear();
            Fname := '';
            SkipLength := NameLength;
            State := THPackState.SKIP_LITERAL_HEADER_NAME;
            break;
          end;
          State := THPackState.READ_LITERAL_HEADER_NAME;
        end;
      end;
    READ_LITERAL_HEADER_NAME: begin
        // Wait until entire name is readable
        if (InAvailable() < NameLength) then begin
          exit;
        end;

        FName := ReadStringLiteral(aStream, NameLength);

        State := THPackState.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
      end;
    SKIP_LITERAL_HEADER_NAME: begin
        SetLength(tmpbuffer,SkipLength);
        dec(SkipLength, aStream.Read(tmpbuffer[1],SkipLength));

        if (SkipLength = 0) then begin
          State := THPackState.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
        end;
      end;
    READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX: begin
        b := aStream.ReadByte;
        HuffmanEncoded := (b and $80) = $80;
        Findex := b and $7F;
        if (Findex = $7f) then begin
          State := THPackState.READ_LITERAL_HEADER_VALUE_LENGTH;
        end else begin
          ValueLength := Findex;

          // Check new header size against max header size
          NewHeaderSize := NameLength + ValueLength;
          if ExceedsMaxHeaderSize(NewHeaderSize) then begin
            // truncation will be reported during EndHeaderBlockTruncated
            HeaderSize := MaxHeaderSize + 1;

            if (IndexType = THPackIndexType.eHPackNONE) then begin
              // Value is unused so skip bytes
              State := THPackState.SKIP_LITERAL_HEADER_VALUE;
              break;
            end;

            // Check new header size against max dynamic table size
            if NewHeaderSize + HPACK_HEADER_ENTRY_OVERHEAD > DynamicTable.Capacity then begin
              DynamicTable.Clear();
              State := THPackState.SKIP_LITERAL_HEADER_VALUE;
              break;
            end;
          end;

          if (ValueLength = 0) then begin
            InsertHeader(FName, '', IndexType);
            State := THPackState.READ_HEADER_REPRESENTATION;
          end else begin
            State := THPackState.READ_LITERAL_HEADER_VALUE;
          end;
        end;
      end;
    READ_LITERAL_HEADER_VALUE_LENGTH: begin
        // Header Value is a Literal String
        ValueLength := decodeULE128(aStream);
        if (ValueLength = -1) then begin
          Exit;
        end;

        // Check for numerical overflow
        if (ValueLength > High(Integer) - Findex) then begin
          Raise THPACKException.Create('Decompression exception in Decode-READ_LITERAL_HEADER_VALUE_LENGTH');
        end;

        inc(ValueLength,Findex);

        // Check new header size against max header size
        NewHeaderSize := NameLength + ValueLength;
        if (NewHeaderSize + HeaderSize > MaxHeaderSize) then begin
          // truncation will be reported during EndHeaderBlockTruncated
          HeaderSize := MaxHeaderSize + 1;

          if (IndexType = THPackIndexType.eHPackNONE) then begin
            // Value is unused so skip bytes
            State := THPackState.SKIP_LITERAL_HEADER_VALUE;
            break;
          end;

          // Check new header size against max dynamic table size
          if (NewHeaderSize + HPACK_HEADER_ENTRY_OVERHEAD > DynamicTable.Capacity) then begin
            DynamicTable.Clear();
            State := THPackState.SKIP_LITERAL_HEADER_VALUE;
            break;
          end;
        end;
        State := THPackState.READ_LITERAL_HEADER_VALUE;
      end;
    READ_LITERAL_HEADER_VALUE: begin
        // Wait until entire value is readable
        if (InAvailable() < ValueLength) then begin
          Exit;
        end;

        Value := ReadStringLiteral(aStream, ValueLength);
        InsertHeader(FName, value, IndexType);
        State := THPackState.READ_HEADER_REPRESENTATION;
      end;
    SKIP_LITERAL_HEADER_VALUE: begin
        SetLength(tmpbuffer,ValueLength);
        dec(ValueLength, aStream.Read(tmpbuffer[1],ValueLength));

        if (ValueLength = 0) then begin
          State := THPackState.READ_HEADER_REPRESENTATION;
        end;
      end;
    end;
  end;
end;

procedure THPackDecoder.Decode(aString: RawByteString);
var
  Stream: TStringStream;
begin
  Stream:=TStringStream.Create(aString);
  try
    Decode(Stream);
  finally
    Stream.Free;
  end;
end;

function THPackDecoder.GetMaxHeaderTableSize: Integer;
begin
  Result:=DynamicTable.Capacity;
end;

{ THPackDynamicTable }

procedure THPackDynamicTable.SetCapacity(aCapacity: integer);
(*
 * Set the maximum Size of the dynamic table.
 * Entries are evicted from the dynamic table until the Size of the table
 * is less than or equal to the maximum Size.
 *)
var
  tmp: array of THPackHeaderField;
  MaxEntries: integer;
  Len,Cursor, i: integer;
  Entry: THPackHeaderField;
begin
  if aCapacity < 0 then begin
    Raise THPACKException.Create('Illegal Capacity: '+ inttostr(acapacity));
  end;

  // initially FCapacity will be -1 so init won't return here
  if FCapacity = aCapacity then begin
    exit;
  end;
  FCapacity := aCapacity;

  if FCapacity = 0 then begin
    Clear;
  end else begin
    // initially FSize will be 0 so remove won't be called
    while (FSize > FCapacity) do begin
      Remove().Free;
    end;
  end;
  MaxEntries := aCapacity div HPACK_HEADER_ENTRY_OVERHEAD;
  if (Acapacity mod HPACK_HEADER_ENTRY_OVERHEAD <> 0) then begin
    inc(MaxEntries);
  end;

  // check if FCapacity change requires us to reallocate the array
  if (Length(FHeaderFields)<>0) and (Length(FHeaderFields) = MaxEntries) then begin
    exit;
  end;

  SetLength(tmp,MaxEntries);

  // initially length will be 0 so there will be no copy
  Len := EntriesCount();
  Cursor := Ftail;
  for i := 0 to Pred(Len) do begin
    Entry:=FHeaderFields[Cursor];
    inc(Cursor);
    tmp[i]:=Entry;
    if Cursor=Length(FHeaderFields) then begin
      Cursor:=0;
    end;
  end;

  Ftail := 0;
  Fhead := Ftail + Len;
  FheaderFields := tmp;

end;

procedure THPackDynamicTable.Clear;
begin
  while (FTail <> FHead) do begin
    FHeaderFields[Ftail].Free;
    FHeaderFields[Ftail]:=nil;
    inc(FTail);
    if FTail = Length(FheaderFields) then begin
      FTail := 0;
    end;
  end;
  FHead := 0;
  FTail := 0;
  FSize := 0;
end;

function THPackDynamicTable.Remove: THPackHeaderField;
var
  Removed: THPackHeaderField;
begin
  Removed := FHeaderFields[Ftail];
  if (Removed = nil) then begin
    exit(nil);
  end;
  dec(FSize,Removed.Size());
  FHeaderFields[Ftail] := nil;
  inc(FTail);
  if FTail = Length(FheaderFields) then begin
    FTail := 0;
  end;
  Result:= Removed;
end;

function THPackDynamicTable.EntriesCount: integer;{$IFDEF USEINLINE}inline;{$ENDIF}
begin
  if FHead < FTail then begin
    Result:= Length(FHeaderFields) - FTail + FHead;
  end else begin
    Result:= FHead - FTail;
  end;
end;

constructor THPackDynamicTable.Create(aInitialCapacity: integer);
begin
  SetCapacity(aInitialCapacity);
end;

destructor THPackDynamicTable.Destroy;
var
  j: integer;
begin
  for j := Low(FHeaderFields) to High(FHeaderFields) do begin
    FHeaderFields[j].Free;
    FHeaderFields[j]:=nil;
  end;
  inherited Destroy;
end;

procedure THPackDynamicTable.Add(aHeaderField: THPackHeaderField);
var
  HeaderSize: integer;
begin
  HeaderSize := aHeaderField.size;
  if HeaderSize > Fcapacity then begin
    Clear;
    exit;
  end;
  while (Fsize + HeaderSize > FCapacity) do begin
    Remove().Free;
  end;
  FHeaderFields[FHead] := aHeaderField;
  inc(FHead);
  inc(FSize,aHeaderField.Size);
  if FHead = Length(FHeaderFields) then begin
    FHead := 0;
  end;
end;

function THPackDynamicTable.GetEntry(aIndex: integer): THPackHeaderField; {$IFDEF USEINLINE}inline;{$ENDIF}
var
  i: integer;
begin
  if (aIndex <= 0) or (aIndex > EntriesCount()) then begin
    Raise THPACKException.Create('Index out of bounds in GetEntry');
  end;
  i := FHead - aIndex;
  if i < 0 then begin
    Result:= FHeaderFields[i + Length(FHeaderFields)];
  end else begin
    Result:= FHeaderFields[i];
  end;
end;

function THPackDynamicTable.GetNameIndex(const aName: RawByteString): integer;
var
  j: integer;
  H: THPackHeaderField;
begin
  for j := 1 to Pred(EntriesCount()) do begin
    H:=GetEntry(j);
    if H.FName=aName then begin
      Result:=j;
      Exit;
    end;
  end;
  Result:=NOT_FOUND;
end;

function THPackDynamicTable.GetEntry(const aName, aValue: RawByteString
  ): THPackHeaderField;
var
  j: integer;
  H: THPackHeaderField;
begin
  for j := 1 to Pred(EntriesCount()) do begin
    H:=GetEntry(j);
    if (H.FName=aName) and (H.FValue=aValue) then begin
      Result:=H;
      Exit;
    end;
  end;
  Result:=nil;
end;

function THPackDynamicTable.GetEntryIndex(const aName, aValue: RawByteString
  ): integer;
var
  j: integer;
  H: THPackHeaderField;
begin
  for j := 1 to Pred(EntriesCount()) do begin
    H:=GetEntry(j);
    if (H.FName=aName) and (H.FValue=aValue) then begin
      Result:=j;
      Exit;
    end;
  end;
  Result:=NOT_FOUND;
end;

function THPackDynamicTable.GetEntryIndex(const aHeader: THPackHeaderField
  ): integer;
var
  j: integer;
  H: THPackHeaderField;
begin
  for j := 1 to Pred(EntriesCount()) do begin
    H:=GetEntry(j);
    if H=aHeader then begin
      Result:=j;
      Exit;
    end;
  end;
  Result:=NOT_FOUND;
end;

procedure THPackDynamicTable.EnsureCapacityToHold(const aHeaderSize: integer);
var
  Index: integer;
begin
  while (Size + aHeaderSize > FCapacity) do begin
    Index := EntriesCount;
    if Index = 0 then begin
      break;
    end;
    Remove.Free;
  end;
end;

{ THPackHuffmanNode }

class procedure THPackHuffmanNode.Insert(aRoot: THPackHuffmanNode;
  aSymbol: integer; aCode: integer; aLength: BYTE);
var
  Current: THPackHuffmanNode;
  Terminal: THPackHuffmanNode;
  i: integer;
  Shift,Start,iEnd: integer;
begin
  // traverse tree using the most significant bytes of code
  Current:=aRoot;
  while (aLength > 8) do begin
    if (Current.isTerminal) then begin
      Raise THPACKException.Create('Invalid Huffman code: prefix not unique');
    end;
    dec(aLength,8);
    i := integer((DWORD(aCode) {Unsigned Shift} shr aLength) and DWORD($FF));
    if (Current.FChildren[i] = Nil) then begin
      Current.FChildren[i] := THPackHuffmanNode.Create;
    end;
    Current := Current.FChildren[i];
  end;

  Terminal := THPackHuffmanNode.Create(aSymbol, aLength);
  Shift := 8 - aLength;
  Start := (aCode shl Shift) and $FF;
  iEnd := 1 << Shift;
  for i := Start to Pred(Start + iEnd) do begin
    Current.FChildren[i]:=Terminal;
  end;
end;

class function THPackHuffmanNode.BuildTree(const aCodes: PDWORD;
  const aLengths: PByte): THPackHuffmanNode;
var
  Root: THPackHuffmanNode;
  i: integer;
begin
    Root := THPackHuffmanNode.Create;
    for i := 0 to Pred(HPACK_HUFFMAN_CODES_LENGTH) do begin
      Insert(Root,i,aCodes[i],aLengths[i]);
    end;
    Result:=Root;
end;

constructor THPackHuffmanNode.Create;
begin
  FSymbol := 0;
  FBits := 8;
  SetLength(FChildren,256);
end;

constructor THPackHuffmanNode.Create(aSymbol: integer; aBits: integer);
begin
  //assert(FBits > 0 && FBits <= 8);
  if (aBits<1) or (aBits > 8) then begin
    Raise THPACKException.Create('BUG');
  end;
  FSymbol := aSymbol;
  FBits := aBits;
  SetLength(FChildren,0);
end;

destructor THPackHuffmanNode.Destroy;
var
  j,i: Integer;
  Node: THPackHuffmanNode;
begin
  for j := Low(FChildren) to high(FChildren) do begin
    Node:=FChildren[j];
    if Assigned(Node) then begin
      for i := j to High(FChildren) do begin
        if Node=FChildren[i] then begin
          FChildren[i]:=nil;
        end;
      end;
    end;
    Node.Free;
  end;
  inherited Destroy;
end;

function THPackHuffmanNode.isTerminal: Boolean;
begin
  if Length(FChildren)=0 then begin
    Result:=true;
  end else begin
    Result:=false;
  end;
end;

{ THPackHuffmanDecoder }

constructor THPackHuffmanDecoder.Create;
begin
  Create(HPackHuffmanCodes,HPackHuffmanCodeLength);
end;

constructor THPackHuffmanDecoder.Create(const aCodes: PDWORD;
  const aLengths: PByte);
begin
  FCodes:=aCodes;
  FLengths:=aLengths;
  FRoot:=THPackHuffmanNode.BuildTree(aCodes,aLengths);
end;

destructor THPackHuffmanDecoder.Destroy;
begin
  FreeAndNil(FRoot);
  inherited Destroy;
end;

function THPackHuffmanDecoder.Decode(aBuf: RawByteString): RawByteString;
var
  WritePoint: integer;
  RealSize: integer;
  Node: THPackHuffmanNode;
  Current: integer;
  Bits: Integer;
  i,b,c: Integer;
  Mask: integer;
  OutputBuffer: RawByteString;
  procedure WriteByte(const aByte: Byte); {$IFDEF USEINLINE}inline;{$ENDIF}
  begin
    if WritePoint>RealSize then begin
      SetLength(OutputBuffer,RealSize*2);
      RealSize:=RealSize*2;
    end;
    Byte(OutputBuffer[WritePoint]):=aByte;
    inc(WritePoint);
  end;
begin
  if aBuf='' then begin
    Result:='';
    exit;
  end;
  WritePoint:=1;
  RealSize:=Length(aBuf)*2; //Huffman usually reach a 50% compress at best.
  SetLength(OutputBuffer,RealSize);
  Node := FRoot;
  Current := 0;
  Bits := 0;
  for i := 0 to Pred(Length(aBuf)) do begin
    b := Byte(aBuf[i+1]);
    Current := (current shl 8) or b;
    inc(Bits,8);
    while (Bits >= 8) do begin
      c := integer((DWORD(Current) {unsigned shift} shr (Bits - 8)) and DWORD($FF));
      Node := Node.FChildren[c];
      dec(Bits,Node.FBits);
      if (Node.isTerminal) then begin
        if (Node.FSymbol = HPACK_HUFFMAN_EOS) then begin
          Raise THPACKException.Create('EOS_DECODED');
        end;
        WriteByte(Byte(Node.FSymbol));
        Node := Froot;
      end;
    end;
  end;
  while (Bits > 0) do begin
    c := (current shl (8 - Bits)) and $FF;
    Node := Node.FChildren[c];
    if (Node.isTerminal and (Node.FBits <= Bits)) then begin
      dec(Bits,Node.FBits);
      WriteByte(Byte(Node.FSymbol));
      Node := Froot;
    end else begin
      break;
    end;
  end;

  // Section 5.2. String Literal Representation
  // Padding not corresponding to the most significant Bits of the code
  // for the EOS symbol (0xFF) MUST be treated as a decoding error.
  Mask := (1 shl Bits) - 1;
  if (current and Mask) <> Mask then begin
    Raise THPACKException.Create('INVALID_PADDING');
  end;
  SetLength(OutputBuffer,WritePoint-1);
  Result:=OutputBuffer;
end;

{ THPackHuffmanEncoder }

constructor THPackHuffmanEncoder.Create;
begin
  Create(HPackHuffmanCodes,HPackHuffmanCodeLength);
end;

constructor THPackHuffmanEncoder.Create(const aCodes: PDWORD;
  const aLengths: PByte);
begin
  FCodes:=aCodes;
  FLengths:=aLengths;
end;

procedure THPackHuffmanEncoder.Encode(aOutputStream: TStream; aData: RawByteString);
begin
  Encode(aOutputStream, aData, 0, Length(aData));
end;

procedure THPackHuffmanEncoder.Encode(aOutputStream: TStream;
  aData: RawByteString; aOff, aLen: integer);
var
  Current: DWORD=0;
  n: integer=0;
  i: integer;
  Code: DWORD;
  b,nBits: integer;
  v: DWORD;
begin
  if not Assigned(aOutputStream) then begin
    Raise THPACKException.Create('Output stream is nil');
  end else if aData = '' then begin
    Raise THPACKException.Create('Data is empty');
  end else if ((aOff < 0) or (aLen < 0) or ((aOff + aLen) < 0) or (aOff > Length(aData)) or ((aOff + aLen) > Length(aData))) then begin
    Raise THPACKException.Create('Index out of bounds');
  end else if aLen = 0 then begin
    exit;
  end;

  for i := 0 to Pred(aLen) do begin
    b := BYTE(aData[aOff + i + 1]) and $FF;
    Code := FCodes[b];
    nBits := FLengths[b];

    Current := Current shl nBits;
    Current := Current or Code;
    inc(n,nBits);

    while (n >= 8) do begin
      dec(n,8);
      v:=Current shr n;
      aOutputStream.WriteByte(Byte(v));
    end;
  end;

  if (n > 0) then begin
    Current := Current shl (8-n);
    Current:=Current or (DWORD($FF) {unsigned shift} shr n); // this should be EOS symbol
    aOutputStream.WriteByte(Byte(Current));
  end;
end;

function THPackHuffmanEncoder.GetEncodedLength(aData: RawByteString
  ): integer;
var
  Len: integer;
  i: integer;
begin
  if aData = '' then begin
    Raise THPACKException.Create('Data is empty');
  end;

  Len := 0;
  for i := 1 to Length(aData) do begin
    inc(Len,FLengths[BYTE(aData[i])]);
  end;
  Result:=(Len + 7) shr 3;
end;

{ THPackHeaderField }

class function THPackHeaderField.SizeOf(const aName, aValue: RawByteString
  ): Integer;
begin
  Result:=Length(aName) + Length(aValue) + HEADER_ENTRY_OVERHEAD;
end;

constructor THPackHeaderField.Create(const aName, aValue: RawByteString);
begin
  FName:=aName;
  FValue:=aValue;
end;

function THPackHeaderField.Size: Integer;
begin
  Result:=Length(FName) + Length(FValue) + HEADER_ENTRY_OVERHEAD;
end;

initialization
  THPackStaticTable.InitializeStaticTable;
finalization;
  THPackStaticTable.DestroyStaticTable;

end.