|
@@ -143,11 +143,10 @@ decode(<<2#001:3,N:1,NameH:1,Rest0/bits>>, State, Base, Acc) ->
|
|
|
%% @todo N=1 the encoded field line MUST be encoded as literal, need to return metadata about this?
|
|
|
{NameLen, Rest1} = dec_int3(Rest0),
|
|
|
<<NameStr:NameLen/binary,ValueH:1,Rest2/bits>> = Rest1,
|
|
|
- %% @todo huffman decode when NameH=1
|
|
|
+ {Name, <<>>} = maybe_dec_huffman(NameStr, NameLen, NameH),
|
|
|
{ValueLen, Rest3} = dec_int7(Rest2),
|
|
|
- <<ValueStr:ValueLen/binary,Rest/bits>> = Rest3,
|
|
|
- %% @todo huffman decode when ValueH=1
|
|
|
- todo.
|
|
|
+ {Value, Rest} = maybe_dec_huffman(Rest3, ValueLen, ValueH),
|
|
|
+ decode(Rest, State, Base, [{Name, Value}|Acc]).
|
|
|
|
|
|
-spec execute_encoder_instructions(binary(), State)
|
|
|
-> {ok, State} | {error, qpack_encoder_stream_error, atom()}
|
|
@@ -246,9 +245,11 @@ encode_field_section(Headers, StreamID, State0=#state{max_table_capacity=MaxTabl
|
|
|
{ok, [enc_big_int(EncInsertCount, <<>>), enc_int7(DeltaBase, S)|Data], EncData, State}
|
|
|
end.
|
|
|
|
|
|
-encode([], _, State, HuffmanOpt, ReqInsertCount, _, EncAcc, Acc) ->
|
|
|
+encode([], _StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, _Base, EncAcc, Acc) ->
|
|
|
{ReqInsertCount, lists:reverse(EncAcc), lists:reverse(Acc), State};
|
|
|
-encode([{Name, Value0}|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc) ->
|
|
|
+encode([{Name, Value0}|Tail], StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, Base, EncAcc, Acc) ->
|
|
|
%% We conditionally call iolist_to_binary/1 because a small
|
|
|
%% but noticeable speed improvement happens when we do this.
|
|
|
%% (Or at least it did for cow_hpack.)
|
|
@@ -258,63 +259,129 @@ encode([{Name, Value0}|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Bas
|
|
|
end,
|
|
|
Entry = {Name, Value},
|
|
|
DrainIndex = 1, %% @todo
|
|
|
+ encode_static([Entry|Tail], StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, Base, EncAcc, Acc, DrainIndex).
|
|
|
+
|
|
|
+encode_static([Entry|Tail], StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, Base, EncAcc, Acc, DrainIndex) ->
|
|
|
case table_find_static(Entry) of
|
|
|
not_found ->
|
|
|
- case table_find_dyn(Entry, State0) of
|
|
|
- not_found ->
|
|
|
- case table_find_name_static(Name) of
|
|
|
- not_found ->
|
|
|
- todo;
|
|
|
- StaticNameIndex ->
|
|
|
- case table_can_insert(Entry, State0) of
|
|
|
- true ->
|
|
|
- State = table_insert(Entry, State0),
|
|
|
- #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
- ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
- PostBaseIndex = length(EncAcc),
|
|
|
- encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
- [[enc_int6(StaticNameIndex, 2#11)|enc_str(Value, HuffmanOpt)]|EncAcc],
|
|
|
- [enc_int4(PostBaseIndex, 2#0001)|Acc]);
|
|
|
- false ->
|
|
|
- encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
- [[enc_int4(StaticNameIndex, 2#0101)|enc_str(Value, HuffmanOpt)]|Acc])
|
|
|
- end
|
|
|
- end;
|
|
|
- %% When the index is below the drain index and there is enough
|
|
|
- %% space in the table for duplicating the value, we do that
|
|
|
- %% and use the duplicated index. If we can't then we must not
|
|
|
- %% use the dynamic index for the field.
|
|
|
- DynIndex when DynIndex =< DrainIndex ->
|
|
|
- case table_can_insert(Entry, State0) of
|
|
|
- true ->
|
|
|
- State = table_insert(Entry, State0),
|
|
|
- #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
- ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
- %% - 1 because we already inserted the new entry in the table.
|
|
|
- DynIndexRel = ReqInsertCount - DynIndex - 1,
|
|
|
- PostBaseIndex = length(EncAcc),
|
|
|
- encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
- [enc_int5(DynIndexRel, 2#000)|EncAcc],
|
|
|
- [enc_int6(Base - ReqInsertCount, 2#10)|Acc]);
|
|
|
- false ->
|
|
|
- todo %% @todo Same as not_found.
|
|
|
- end;
|
|
|
- DynIndex ->
|
|
|
- %% @todo We should check whether the value is below the drain index
|
|
|
- %% and if that's the case we either duplicate or not use the index
|
|
|
- %% depending on capacity.
|
|
|
- ReqInsertCount = if
|
|
|
- ReqInsertCount0 > DynIndex -> ReqInsertCount0;
|
|
|
- true -> DynIndex
|
|
|
- end,
|
|
|
- encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
- [enc_int6(Base - DynIndex, 2#10)|Acc])
|
|
|
- end;
|
|
|
+ encode_dyn([Entry|Tail], StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, Base, EncAcc, Acc, DrainIndex);
|
|
|
StaticIndex ->
|
|
|
- encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
+ encode(Tail, StreamID, State, HuffmanOpt,
|
|
|
+ ReqInsertCount, Base, EncAcc,
|
|
|
+ %% Indexed Field Line. T=1 (static).
|
|
|
[enc_int6(StaticIndex, 2#11)|Acc])
|
|
|
end.
|
|
|
|
|
|
+encode_dyn([Entry|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex) ->
|
|
|
+ case table_find_dyn(Entry, State0) of
|
|
|
+ not_found ->
|
|
|
+ encode_static_name([Entry|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex);
|
|
|
+ %% When the index is below the drain index and there is enough
|
|
|
+ %% space in the table for duplicating the value, we do that
|
|
|
+ %% and use the duplicated index. If we can't then we must not
|
|
|
+ %% use the dynamic index for the field.
|
|
|
+ DynIndex when DynIndex =< DrainIndex ->
|
|
|
+ case table_can_insert(Entry, State0) of
|
|
|
+ true ->
|
|
|
+ State = table_insert(Entry, State0),
|
|
|
+ #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
+ ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
+ %% - 1 because we already inserted the new entry in the table.
|
|
|
+ DynIndexRel = ReqInsertCount - DynIndex - 1,
|
|
|
+ encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
+ %% Duplicate.
|
|
|
+ [enc_int5(DynIndexRel, 2#000)|EncAcc],
|
|
|
+ %% Indexed Field Line. T=0 (dynamic).
|
|
|
+ [enc_int6(Base - ReqInsertCount, 2#10)|Acc]);
|
|
|
+ false ->
|
|
|
+ encode_static_name([Entry|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex)
|
|
|
+ end;
|
|
|
+ DynIndex ->
|
|
|
+ ReqInsertCount = if
|
|
|
+ ReqInsertCount0 > DynIndex -> ReqInsertCount0;
|
|
|
+ true -> DynIndex
|
|
|
+ end,
|
|
|
+ encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
+ %% Indexed Field Line. T=0 (dynamic).
|
|
|
+ [enc_int6(Base - DynIndex, 2#10)|Acc])
|
|
|
+ end.
|
|
|
+
|
|
|
+encode_static_name([Entry = {Name, Value}|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex) ->
|
|
|
+ case table_find_name_static(Name) of
|
|
|
+ not_found ->
|
|
|
+ encode_dyn_name([Entry|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex);
|
|
|
+ StaticNameIndex ->
|
|
|
+ case table_can_insert(Entry, State0) of
|
|
|
+ true ->
|
|
|
+ State = table_insert(Entry, State0),
|
|
|
+ #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
+ ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
+ PostBaseIndex = length(EncAcc),
|
|
|
+ encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
+ %% Insert with Name Reference. T=1 (static).
|
|
|
+ [[enc_int6(StaticNameIndex, 2#11)|enc_str(Value, HuffmanOpt)]|EncAcc],
|
|
|
+ %% Indexed Field Line with Post-Base Index.
|
|
|
+ [enc_int4(PostBaseIndex, 2#0001)|Acc]);
|
|
|
+ false ->
|
|
|
+ encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
+ %% Literal Field Line with Name Reference. N=0. T=1 (static).
|
|
|
+ [[enc_int4(StaticNameIndex, 2#0101)|enc_str(Value, HuffmanOpt)]|Acc])
|
|
|
+ end
|
|
|
+ end.
|
|
|
+
|
|
|
+encode_dyn_name([Entry = {Name, Value}|Tail], StreamID, State0, HuffmanOpt,
|
|
|
+ ReqInsertCount0, Base, EncAcc, Acc, DrainIndex) ->
|
|
|
+ case table_find_name_dyn(Name, State0) of
|
|
|
+ %% We can reference the dynamic name.
|
|
|
+ DynIndex when is_integer(DynIndex), DynIndex > DrainIndex ->
|
|
|
+ case table_can_insert(Entry, State0) of
|
|
|
+ true ->
|
|
|
+ State = table_insert(Entry, State0),
|
|
|
+ #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
+ ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
+ %% - 1 because we already inserted the new entry in the table.
|
|
|
+ DynIndexRel = ReqInsertCount - DynIndex - 1,
|
|
|
+ PostBaseIndex = length(EncAcc),
|
|
|
+ encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
+ %% Insert with Name Reference. T=0 (dynamic).
|
|
|
+ [[enc_int6(DynIndexRel, 2#10)|enc_str(Value, HuffmanOpt)]|EncAcc],
|
|
|
+ %% Indexed Field Line with Post-Base Index.
|
|
|
+ [enc_int4(PostBaseIndex, 2#0001)|Acc]);
|
|
|
+ false ->
|
|
|
+ encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
+ %% Literal Field Line with Name Reference. N=0. T=0 (dynamic).
|
|
|
+ [[enc_int4(DynIndex, 2#0100)|enc_str(Value, HuffmanOpt)]|Acc])
|
|
|
+ end;
|
|
|
+ %% When there are no name to reference, or the name
|
|
|
+ %% is found below the drain index, we do not attempt
|
|
|
+ %% to refer to it.
|
|
|
+ _ ->
|
|
|
+ case table_can_insert(Entry, State0) of
|
|
|
+ true ->
|
|
|
+ State = table_insert(Entry, State0),
|
|
|
+ #state{num_dropped=NumDropped, dyn_table=DynamicTable} = State,
|
|
|
+ ReqInsertCount = NumDropped + length(DynamicTable),
|
|
|
+ PostBaseIndex = length(EncAcc),
|
|
|
+ encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base,
|
|
|
+ %% Insert with Literal Name.
|
|
|
+ [[enc_str6(Name, HuffmanOpt, 2#01)|enc_str(Value, HuffmanOpt)]|EncAcc],
|
|
|
+ %% Indexed Field Line with Post-Base Index.
|
|
|
+ [enc_int4(PostBaseIndex, 2#0001)|Acc]);
|
|
|
+ false ->
|
|
|
+ encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc,
|
|
|
+ %% Literal Field Line with Literal Name. N=0.
|
|
|
+ [[enc_str4(Name, HuffmanOpt, 2#0010)|enc_str(Value, HuffmanOpt)]|Acc])
|
|
|
+ end
|
|
|
+ end.
|
|
|
+
|
|
|
-spec execute_decoder_instructions(binary(), State)
|
|
|
-> {ok, State} | {error, qpack_decoder_stream_error, atom()}
|
|
|
when State::state().
|
|
@@ -338,6 +405,7 @@ execute_decoder_instructions(<<2#00:2,Rest0/bits>>, State) ->
|
|
|
|
|
|
%% @todo spec
|
|
|
encoder_set_table_capacity(Capacity, State) ->
|
|
|
+ %% Set Dynamic Table Capacity.
|
|
|
{ok, enc_int5(Capacity, 2#001), State#state{max_table_capacity=Capacity}}.
|
|
|
|
|
|
%% @todo spec
|
|
@@ -348,10 +416,12 @@ encoder_insert_entry(Entry={Name, Value}, State0, Opts) ->
|
|
|
not_found ->
|
|
|
case table_find_name_dyn(Name, State0) of
|
|
|
not_found ->
|
|
|
+ %% Insert with Literal Name.
|
|
|
{ok, [enc_str6(Name, HuffmanOpt, 2#01)|enc_str(Value, HuffmanOpt)], State};
|
|
|
DynNameIndex ->
|
|
|
#state{num_dropped=NumDropped0, dyn_table=DynamicTable0} = State0,
|
|
|
DynNameIndexRel = NumDropped0 + length(DynamicTable0) - DynNameIndex,
|
|
|
+ %% Insert with Name Reference. T=0 (dynamic).
|
|
|
{ok, [enc_int6(DynNameIndexRel, 2#10)|enc_str(Value, HuffmanOpt)], State}
|
|
|
end;
|
|
|
StaticNameIndex ->
|
|
@@ -361,11 +431,22 @@ encoder_insert_entry(Entry={Name, Value}, State0, Opts) ->
|
|
|
huffman_opt(#{huffman := false}) -> no_huffman;
|
|
|
huffman_opt(_) -> huffman.
|
|
|
|
|
|
+enc_int3(Int, Prefix) when Int < 7 ->
|
|
|
+ <<Prefix:5, Int:3>>;
|
|
|
+enc_int3(Int, Prefix) ->
|
|
|
+ enc_big_int(Int - 7, <<Prefix:5, 2#111:3>>).
|
|
|
+
|
|
|
enc_int4(Int, Prefix) when Int < 15 ->
|
|
|
<<Prefix:4, Int:4>>;
|
|
|
enc_int4(Int, Prefix) ->
|
|
|
enc_big_int(Int - 15, <<Prefix:4, 2#1111:4>>).
|
|
|
|
|
|
+enc_str4(Str, huffman, Prefix) ->
|
|
|
+ Str2 = enc_huffman(Str, <<>>),
|
|
|
+ [enc_int3(byte_size(Str2), Prefix * 2 + 2#1)|Str2];
|
|
|
+enc_str4(Str, no_huffman, Prefix) ->
|
|
|
+ [enc_int3(byte_size(Str), Prefix * 2 + 2#0)|Str].
|
|
|
+
|
|
|
enc_str6(Str, huffman, Prefix) ->
|
|
|
Str2 = enc_huffman(Str, <<>>),
|
|
|
[enc_int5(byte_size(Str2), Prefix * 2 + 2#1)|Str2];
|