Просмотр исходного кода

WIP more real world scenarios working

Loïc Hoguin 1 год назад
Родитель
Сommit
9e798d3738
1 измененных файлов с 138 добавлено и 57 удалено
  1. 138 57
      src/cow_qpack.erl

+ 138 - 57
src/cow_qpack.erl

@@ -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];