Browse Source

WIP Optimize HPACK (de)compression speed

Loïc Hoguin 5 years ago
parent
commit
3b89df7175
1 changed files with 365 additions and 262 deletions
  1. 365 262
      src/cow_hpack.erl

+ 365 - 262
src/cow_hpack.erl

@@ -34,7 +34,16 @@
 	size = 0 :: non_neg_integer(),
 	max_size = 4096 :: non_neg_integer(),
 	configured_max_size = 4096 :: non_neg_integer(),
-	dyn_table = [] :: [{pos_integer(), {binary(), binary()}}]
+
+	next_index = 1 :: pos_integer(),
+	offset_index = 0 :: non_neg_integer(),
+	%% #{Index => {EntrySize, Header}}.
+	dyn_table = #{} :: #{pos_integer() => {pos_integer(), {binary(), binary()}}},
+%	dyn_table = [] :: [{pos_integer(), {binary(), binary()}}]
+	%% #{Name => {HighestIndex, #{Value => Index}}}.
+	reverse_dyn_table = #{} :: #{binary() => {pos_integer(), #{binary() => pos_integer()}}}
+
+%	dyn_table = [] :: [{pos_integer(), {binary(), binary()}}]
 }).
 
 -opaque state() :: #state{}.
@@ -484,7 +493,10 @@ req_decode_test() ->
 		{<<":path">>, <<"/">>},
 		{<<":authority">>, <<"www.example.com">>}
 	],
-	#state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+	#state{size=57, next_index=2, offset_index=0,
+		dyn_table=#{1 := {57,{<<":authority">>, <<"www.example.com">>}}},
+		reverse_dyn_table=#{<<":authority">> := {1, #{<<"www.example.com">> := 1}}}
+	} = State1,
 	%% Second request (raw then huffman).
 	{Headers2, State2} = decode(<< 16#828684be58086e6f2d6361636865:112 >>, State1),
 	{Headers2, State2} = decode(<< 16#828684be5886a8eb10649cbf:96 >>, State1),
@@ -495,12 +507,20 @@ req_decode_test() ->
 		{<<":authority">>, <<"www.example.com">>},
 		{<<"cache-control">>, <<"no-cache">>}
 	],
-	#state{size=110, dyn_table=[
-		{53,{<<"cache-control">>, <<"no-cache">>}},
-		{57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+	#state{size=110, next_index=3, offset_index=0,
+		dyn_table=#{
+			1 := {57,{<<":authority">>, <<"www.example.com">>}},
+			2 := {53,{<<"cache-control">>, <<"no-cache">>}}
+		},
+		reverse_dyn_table=#{
+			<<":authority">> := {1, #{<<"www.example.com">> := 1}},
+			<<"cache-control">> := {2, #{<<"no-cache">> := 2}}
+		}
+	} = State2,
 	%% Third request (raw then huffman).
 	{Headers3, State3} = decode(<< 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >>, State2),
 	{Headers3, State3} = decode(<< 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >>, State2),
+	io:format("~p~n~p~n", [Headers3, State3]),
 	Headers3 = [
 		{<<":method">>, <<"GET">>},
 		{<<":scheme">>, <<"https">>},
@@ -508,10 +528,18 @@ req_decode_test() ->
 		{<<":authority">>, <<"www.example.com">>},
 		{<<"custom-key">>, <<"custom-value">>}
 	],
-	#state{size=164, dyn_table=[
-		{54,{<<"custom-key">>, <<"custom-value">>}},
-		{53,{<<"cache-control">>, <<"no-cache">>}},
-		{57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+	#state{size=164, next_index=4, offset_index=0,
+		dyn_table=#{
+			1 := {57,{<<":authority">>, <<"www.example.com">>}},
+			2 := {53,{<<"cache-control">>, <<"no-cache">>}},
+			3 := {54,{<<"custom-key">>, <<"custom-value">>}}
+		},
+		reverse_dyn_table=#{
+			<<":authority">> := {1, #{<<"www.example.com">> := 1}},
+			<<"cache-control">> := {2, #{<<"no-cache">> := 2}},
+			<<"custom-key">> := {3, #{<<"custom-value">> := 3}}
+		}
+	} = State3,
 	ok.
 
 resp_decode_test() ->
@@ -526,11 +554,20 @@ resp_decode_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+	#state{size=222, next_index=5, offset_index=0,
+		dyn_table=#{
+			1 := {42,{<<":status">>, <<"302">>}},
+			2 := {52,{<<"cache-control">>, <<"private">>}},
+			3 := {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+			4 := {63,{<<"location">>, <<"https://www.example.com">>}}
+		},
+		reverse_dyn_table=#{
+			<<":status">> := {1, #{<<"302">> := 1}},
+			<<"cache-control">> := {2, #{<<"private">> := 2}},
+			<<"date">> := {3, #{<<"Mon, 21 Oct 2013 20:13:21 GMT">> := 3}},
+			<<"location">> := {4, #{<<"https://www.example.com">> := 4}}
+		}
+	} = State1,
 	%% Second response (raw then huffman).
 	{Headers2, State2} = decode(<< 16#4803333037c1c0bf:64 >>, State1),
 	{Headers2, State2} = decode(<< 16#4883640effc1c0bf:64 >>, State1),
@@ -540,11 +577,20 @@ resp_decode_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}}]} = State2,
+	#state{size=222, next_index=6, offset_index=1,
+		dyn_table=#{
+			2 := {52,{<<"cache-control">>, <<"private">>}},
+			3 := {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+			4 := {63,{<<"location">>, <<"https://www.example.com">>}},
+			5 := {42,{<<":status">>, <<"307">>}}
+		},
+		reverse_dyn_table=#{
+			<<"cache-control">> := {2, #{<<"private">> := 2}},
+			<<"date">> := {3, #{<<"Mon, 21 Oct 2013 20:13:21 GMT">> := 3}},
+			<<"location">> := {4, #{<<"https://www.example.com">> := 4}},
+			<<":status">> := {5, #{<<"307">> := 5}}
+		}
+	} = State2,
 	%% Third response (raw then huffman).
 	{Headers3, State3} = decode(<< 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >>, State2),
 	{Headers3, State3} = decode(<< 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >>, State2),
@@ -556,10 +602,18 @@ resp_decode_test() ->
 		{<<"content-encoding">>, <<"gzip">>},
 		{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}
 	],
-	#state{size=215, dyn_table=[
-		{98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
-		{52,{<<"content-encoding">>, <<"gzip">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+	#state{size=215, next_index=9, offset_index=5,
+		dyn_table=#{
+			6 := {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}},
+			7 := {52,{<<"content-encoding">>, <<"gzip">>}},
+			8 := {98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}}
+		},
+		reverse_dyn_table=#{
+			<<"date">> := {6, #{<<"Mon, 21 Oct 2013 20:13:22 GMT">> := 6}},
+			<<"content-encoding">> := {7, #{<<"gzip">> := 7}},
+			<<"set-cookie">> := {8, #{<<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">> := 8}}
+		}
+	} = State3,
 	ok.
 
 table_update_decode_test() ->
@@ -575,11 +629,11 @@ table_update_decode_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State1,
 	%% Set a new configured max_size to avoid header evictions.
 	State2 = set_max_size(512, State1),
 	%% Second response with the table size update (raw then huffman).
@@ -596,12 +650,12 @@ table_update_decode_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=264, configured_max_size=512, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State3,
+%	#state{size=264, configured_max_size=512, dyn_table=[
+%		{42,{<<":status">>, <<"307">>}},
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State3,
 	ok.
 
 table_update_decode_smaller_test() ->
@@ -617,11 +671,11 @@ table_update_decode_smaller_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State1,
 	%% Set a new configured max_size to avoid header evictions.
 	State2 = set_max_size(512, State1),
 	%% Second response with the table size update smaller than the limit (raw then huffman).
@@ -638,12 +692,12 @@ table_update_decode_smaller_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=264, configured_max_size=512, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State3,
+%	#state{size=264, configured_max_size=512, dyn_table=[
+%		{42,{<<":status">>, <<"307">>}},
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State3,
 	ok.
 
 table_update_decode_too_large_test() ->
@@ -659,11 +713,11 @@ table_update_decode_too_large_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State1,
 	%% Set a new configured max_size to avoid header evictions.
 	State2 = set_max_size(512, State1),
 	%% Second response with the table size update (raw then huffman).
@@ -687,11 +741,11 @@ table_update_decode_zero_test() ->
 		{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
 		{<<"location">>, <<"https://www.example.com">>}
 	],
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State1,
 	%% Set a new configured max_size to avoid header evictions.
 	State2 = set_max_size(512, State1),
 	%% Second response with the table size update (raw then huffman).
@@ -706,11 +760,11 @@ table_update_decode_zero_test() ->
 		<<2#00100000, 2#00111111>>, MaxSize,
 		<<16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432>>]),
 		State2),
-	#state{size=222, configured_max_size=512, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State3,
+%	#state{size=222, configured_max_size=512, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State3,
 	ok.
 -endif.
 
@@ -1068,7 +1122,7 @@ req_encode_test() ->
 	<< 16#828684410f7777772e6578616d706c652e636f6d:160 >> = iolist_to_binary(Raw1),
 	{Huff1, State1} = encode(Headers1),
 	<< 16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136 >> = iolist_to_binary(Huff1),
-	#state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+%	#state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
 	%% Second request (raw then huffman).
 	Headers2 = [
 		{<<":method">>, <<"GET">>},
@@ -1081,9 +1135,9 @@ req_encode_test() ->
 	<< 16#828684be58086e6f2d6361636865:112 >> = iolist_to_binary(Raw2),
 	{Huff2, State2} = encode(Headers2, State1),
 	<< 16#828684be5886a8eb10649cbf:96 >> = iolist_to_binary(Huff2),
-	#state{size=110, dyn_table=[
-		{53,{<<"cache-control">>, <<"no-cache">>}},
-		{57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+%	#state{size=110, dyn_table=[
+%		{53,{<<"cache-control">>, <<"no-cache">>}},
+%		{57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
 	%% Third request (raw then huffman).
 	Headers3 = [
 		{<<":method">>, <<"GET">>},
@@ -1096,10 +1150,10 @@ req_encode_test() ->
 	<< 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >> = iolist_to_binary(Raw3),
 	{Huff3, State3} = encode(Headers3, State2),
 	<< 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >> = iolist_to_binary(Huff3),
-	#state{size=164, dyn_table=[
-		{54,{<<"custom-key">>, <<"custom-value">>}},
-		{53,{<<"cache-control">>, <<"no-cache">>}},
-		{57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+%	#state{size=164, dyn_table=[
+%		{54,{<<"custom-key">>, <<"custom-value">>}},
+%		{53,{<<"cache-control">>, <<"no-cache">>}},
+%		{57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
 	ok.
 
 resp_encode_test() ->
@@ -1116,11 +1170,11 @@ resp_encode_test() ->
 	<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >> = iolist_to_binary(Raw1),
 	{Huff1, State1} = encode(Headers1, State0),
 	<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >> = iolist_to_binary(Huff1),
-	#state{size=222, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = State1,
+%	#state{size=222, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = State1,
 	%% Second response (raw then huffman).
 	Headers2 = [
 		{<<":status">>, <<"307">>},
@@ -1132,11 +1186,11 @@ resp_encode_test() ->
 	<< 16#4803333037c1c0bf:64 >> = iolist_to_binary(Raw2),
 	{Huff2, State2} = encode(Headers2, State1),
 	<< 16#4883640effc1c0bf:64 >> = iolist_to_binary(Huff2),
-	#state{size=222, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}}]} = State2,
+%	#state{size=222, dyn_table=[
+%		{42,{<<":status">>, <<"307">>}},
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}}]} = State2,
 	%% Third response (raw then huffman).
 	Headers3 = [
 		{<<":status">>, <<"200">>},
@@ -1150,10 +1204,10 @@ resp_encode_test() ->
 	<< 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >> = iolist_to_binary(Raw3),
 	{Huff3, State3} = encode(Headers3, State2),
 	<< 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >> = iolist_to_binary(Huff3),
-	#state{size=215, dyn_table=[
-		{98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
-		{52,{<<"content-encoding">>, <<"gzip">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+%	#state{size=215, dyn_table=[
+%		{98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
+%		{52,{<<"content-encoding">>, <<"gzip">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
 	ok.
 
 %% This test assumes that table updates work correctly when decoding.
@@ -1170,16 +1224,16 @@ table_update_encode_test() ->
 	],
 	{Encoded1, EncState1} = encode(Headers1, EncState0),
 	{Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0),
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = DecState1,
-	#state{size=222, configured_max_size=256, dyn_table=[
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = EncState1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = DecState1,
+%	#state{size=222, configured_max_size=256, dyn_table=[
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = EncState1,
 	%% Set a new configured max_size to avoid header evictions.
 	DecState2 = set_max_size(512, DecState1),
 	EncState2 = set_max_size(512, EncState1),
@@ -1192,18 +1246,18 @@ table_update_encode_test() ->
 	],
 	{Encoded2, EncState3} = encode(Headers2, EncState2),
 	{Headers2, DecState3} = decode(iolist_to_binary(Encoded2), DecState2),
-	#state{size=264, max_size=512, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = DecState3,
-	#state{size=264, max_size=512, dyn_table=[
-		{42,{<<":status">>, <<"307">>}},
-		{63,{<<"location">>, <<"https://www.example.com">>}},
-		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
-		{52,{<<"cache-control">>, <<"private">>}},
-		{42,{<<":status">>, <<"302">>}}]} = EncState3,
+%	#state{size=264, max_size=512, dyn_table=[
+%		{42,{<<":status">>, <<"307">>}},
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = DecState3,
+%	#state{size=264, max_size=512, dyn_table=[
+%		{42,{<<":status">>, <<"307">>}},
+%		{63,{<<"location">>, <<"https://www.example.com">>}},
+%		{65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+%		{52,{<<"cache-control">>, <<"private">>}},
+%		{42,{<<":status">>, <<"302">>}}]} = EncState3,
 	ok.
 
 encode_iolist_test() ->
@@ -1221,145 +1275,160 @@ encode_iolist_test() ->
 %% Static and dynamic tables.
 
 %% @todo There must be a more efficient way.
-table_find(Header = {Name, _}, State) ->
-	case table_find_field(Header, State) of
-		not_found ->
-			case table_find_name(Name, State) of
-				NotFound = not_found ->
-					NotFound;
-				Found ->
-					{name, Found}
-			end;
-		Found ->
-			{field, Found}
+%table_find(Header = {Name, _}, State) ->
+%	case table_find_field(Header, State) of
+%		not_found ->
+%			case table_find_name(Name, State) of
+%				NotFound = not_found ->
+%					NotFound;
+%				Found ->
+%					{name, Found}
+%			end;
+%		Found ->
+%			{field, Found}
+%	end.
+
+%table_find_field({<<":authority">>, <<>>}, _) -> 1;
+table_find({<<":method">>, <<"GET">>}, _) -> {field, 2};
+table_find({<<":method">>, <<"POST">>}, _) -> {field, 3};
+table_find({<<":path">>, <<"/">>}, _) -> {field, 4};
+table_find({<<":path">>, <<"/index.html">>}, _) -> {field, 5};
+table_find({<<":scheme">>, <<"http">>}, _) -> {field, 6};
+table_find({<<":scheme">>, <<"https">>}, _) -> {field, 7};
+table_find({<<":status">>, <<"200">>}, _) -> {field, 8};
+table_find({<<":status">>, <<"204">>}, _) -> {field, 9};
+table_find({<<":status">>, <<"206">>}, _) -> {field, 10};
+table_find({<<":status">>, <<"304">>}, _) -> {field, 11};
+table_find({<<":status">>, <<"400">>}, _) -> {field, 12};
+table_find({<<":status">>, <<"404">>}, _) -> {field, 13};
+table_find({<<":status">>, <<"500">>}, _) -> {field, 14};
+%table_find_field({<<"accept-charset">>, <<>>}, _) -> 15;
+table_find({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> {field, 16};
+%table_find_field({<<"accept-language">>, <<>>}, _) -> 17;
+%table_find_field({<<"accept-ranges">>, <<>>}, _) -> 18;
+%table_find_field({<<"accept">>, <<>>}, _) -> 19;
+%table_find_field({<<"access-control-allow-origin">>, <<>>}, _) -> 20;
+%table_find_field({<<"age">>, <<>>}, _) -> 21;
+%table_find_field({<<"allow">>, <<>>}, _) -> 22;
+%table_find_field({<<"authorization">>, <<>>}, _) -> 23;
+%table_find_field({<<"cache-control">>, <<>>}, _) -> 24;
+%table_find_field({<<"content-disposition">>, <<>>}, _) -> 25;
+%table_find_field({<<"content-encoding">>, <<>>}, _) -> 26;
+%table_find_field({<<"content-language">>, <<>>}, _) -> 27;
+%table_find_field({<<"content-length">>, <<>>}, _) -> 28;
+%table_find_field({<<"content-location">>, <<>>}, _) -> 29;
+%table_find_field({<<"content-range">>, <<>>}, _) -> 30;
+%table_find_field({<<"content-type">>, <<>>}, _) -> 31;
+%table_find_field({<<"cookie">>, <<>>}, _) -> 32;
+%table_find_field({<<"date">>, <<>>}, _) -> 33;
+%table_find_field({<<"etag">>, <<>>}, _) -> 34;
+%table_find_field({<<"expect">>, <<>>}, _) -> 35;
+%table_find_field({<<"expires">>, <<>>}, _) -> 36;
+%table_find_field({<<"from">>, <<>>}, _) -> 37;
+%table_find_field({<<"host">>, <<>>}, _) -> 38;
+%table_find_field({<<"if-match">>, <<>>}, _) -> 39;
+%table_find_field({<<"if-modified-since">>, <<>>}, _) -> 40;
+%table_find_field({<<"if-none-match">>, <<>>}, _) -> 41;
+%table_find_field({<<"if-range">>, <<>>}, _) -> 42;
+%table_find_field({<<"if-unmodified-since">>, <<>>}, _) -> 43;
+%table_find_field({<<"last-modified">>, <<>>}, _) -> 44;
+%table_find_field({<<"link">>, <<>>}, _) -> 45;
+%table_find_field({<<"location">>, <<>>}, _) -> 46;
+%table_find_field({<<"max-forwards">>, <<>>}, _) -> 47;
+%table_find_field({<<"proxy-authenticate">>, <<>>}, _) -> 48;
+%table_find_field({<<"proxy-authorization">>, <<>>}, _) -> 49;
+%table_find_field({<<"range">>, <<>>}, _) -> 50;
+%table_find_field({<<"referer">>, <<>>}, _) -> 51;
+%table_find_field({<<"refresh">>, <<>>}, _) -> 52;
+%table_find_field({<<"retry-after">>, <<>>}, _) -> 53;
+%table_find_field({<<"server">>, <<>>}, _) -> 54;
+%table_find_field({<<"set-cookie">>, <<>>}, _) -> 55;
+%table_find_field({<<"strict-transport-security">>, <<>>}, _) -> 56;
+%table_find_field({<<"transfer-encoding">>, <<>>}, _) -> 57;
+%table_find_field({<<"user-agent">>, <<>>}, _) -> 58;
+%table_find_field({<<"vary">>, <<>>}, _) -> 59;
+%table_find_field({<<"via">>, <<>>}, _) -> 60;
+%table_find_field({<<"www-authenticate">>, <<>>}, _) -> 61;
+table_find({Name, Value}, State=#state{offset_index=Offset,
+		dyn_table=DynTable, reverse_dyn_table=ReverseTable}) ->
+	case ReverseTable of
+		#{Name := {_, #{Value := Index}}} ->
+			{field, map_size(DynTable) + Offset + 62 - Index};
+		_ ->
+			table_find_name(Name, State)
+	end.
+
+%	table_find_field_dyn(Header, DynamicTable, 62).
+
+%table_find_field_dyn(_, [], _) -> not_found;
+%table_find_field_dyn(Header, [{_, Header}|_], Index) -> Index;
+%table_find_field_dyn(Header, [_|Tail], Index) -> table_find_field_dyn(Header, Tail, Index + 1).
+
+table_find_name(<<":authority">>, _) -> {name, 1};
+table_find_name(<<":method">>, _) -> {name, 2};
+table_find_name(<<":path">>, _) -> {name, 4};
+table_find_name(<<":scheme">>, _) -> {name, 6};
+table_find_name(<<":status">>, _) -> {name, 8};
+table_find_name(<<"accept-charset">>, _) -> {name, 15};
+table_find_name(<<"accept-encoding">>, _) -> {name, 16};
+table_find_name(<<"accept-language">>, _) -> {name, 17};
+table_find_name(<<"accept-ranges">>, _) -> {name, 18};
+table_find_name(<<"accept">>, _) -> {name, 19};
+table_find_name(<<"access-control-allow-origin">>, _) -> {name, 20};
+table_find_name(<<"age">>, _) -> {name, 21};
+table_find_name(<<"allow">>, _) -> {name, 22};
+table_find_name(<<"authorization">>, _) -> {name, 23};
+table_find_name(<<"cache-control">>, _) -> {name, 24};
+table_find_name(<<"content-disposition">>, _) -> {name, 25};
+table_find_name(<<"content-encoding">>, _) -> {name, 26};
+table_find_name(<<"content-language">>, _) -> {name, 27};
+table_find_name(<<"content-length">>, _) -> {name, 28};
+table_find_name(<<"content-location">>, _) -> {name, 29};
+table_find_name(<<"content-range">>, _) -> {name, 30};
+table_find_name(<<"content-type">>, _) -> {name, 31};
+table_find_name(<<"cookie">>, _) -> {name, 32};
+table_find_name(<<"date">>, _) -> {name, 33};
+table_find_name(<<"etag">>, _) -> {name, 34};
+table_find_name(<<"expect">>, _) -> {name, 35};
+table_find_name(<<"expires">>, _) -> {name, 36};
+table_find_name(<<"from">>, _) -> {name, 37};
+table_find_name(<<"host">>, _) -> {name, 38};
+table_find_name(<<"if-match">>, _) -> {name, 39};
+table_find_name(<<"if-modified-since">>, _) -> {name, 40};
+table_find_name(<<"if-none-match">>, _) -> {name, 41};
+table_find_name(<<"if-range">>, _) -> {name, 42};
+table_find_name(<<"if-unmodified-since">>, _) -> {name, 43};
+table_find_name(<<"last-modified">>, _) -> {name, 44};
+table_find_name(<<"link">>, _) -> {name, 45};
+table_find_name(<<"location">>, _) -> {name, 46};
+table_find_name(<<"max-forwards">>, _) -> {name, 47};
+table_find_name(<<"proxy-authenticate">>, _) -> {name, 48};
+table_find_name(<<"proxy-authorization">>, _) -> {name, 49};
+table_find_name(<<"range">>, _) -> {name, 50};
+table_find_name(<<"referer">>, _) -> {name, 51};
+table_find_name(<<"refresh">>, _) -> {name, 52};
+table_find_name(<<"retry-after">>, _) -> {name, 53};
+table_find_name(<<"server">>, _) -> {name, 54};
+table_find_name(<<"set-cookie">>, _) -> {name, 55};
+table_find_name(<<"strict-transport-security">>, _) -> {name, 56};
+table_find_name(<<"transfer-encoding">>, _) -> {name, 57};
+table_find_name(<<"user-agent">>, _) -> {name, 58};
+table_find_name(<<"vary">>, _) -> {name, 59};
+table_find_name(<<"via">>, _) -> {name, 60};
+table_find_name(<<"www-authenticate">>, _) -> {name, 61};
+table_find_name(Name, #state{offset_index=Offset, dyn_table=DynTable, reverse_dyn_table=ReverseTable}) ->
+	case ReverseTable of
+		#{Name := {Index, _}} ->
+			{name, map_size(DynTable) + Offset + 62 - Index};
+		_ ->
+			not_found
 	end.
 
-table_find_field({<<":authority">>, <<>>}, _) -> 1;
-table_find_field({<<":method">>, <<"GET">>}, _) -> 2;
-table_find_field({<<":method">>, <<"POST">>}, _) -> 3;
-table_find_field({<<":path">>, <<"/">>}, _) -> 4;
-table_find_field({<<":path">>, <<"/index.html">>}, _) -> 5;
-table_find_field({<<":scheme">>, <<"http">>}, _) -> 6;
-table_find_field({<<":scheme">>, <<"https">>}, _) -> 7;
-table_find_field({<<":status">>, <<"200">>}, _) -> 8;
-table_find_field({<<":status">>, <<"204">>}, _) -> 9;
-table_find_field({<<":status">>, <<"206">>}, _) -> 10;
-table_find_field({<<":status">>, <<"304">>}, _) -> 11;
-table_find_field({<<":status">>, <<"400">>}, _) -> 12;
-table_find_field({<<":status">>, <<"404">>}, _) -> 13;
-table_find_field({<<":status">>, <<"500">>}, _) -> 14;
-table_find_field({<<"accept-charset">>, <<>>}, _) -> 15;
-table_find_field({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> 16;
-table_find_field({<<"accept-language">>, <<>>}, _) -> 17;
-table_find_field({<<"accept-ranges">>, <<>>}, _) -> 18;
-table_find_field({<<"accept">>, <<>>}, _) -> 19;
-table_find_field({<<"access-control-allow-origin">>, <<>>}, _) -> 20;
-table_find_field({<<"age">>, <<>>}, _) -> 21;
-table_find_field({<<"allow">>, <<>>}, _) -> 22;
-table_find_field({<<"authorization">>, <<>>}, _) -> 23;
-table_find_field({<<"cache-control">>, <<>>}, _) -> 24;
-table_find_field({<<"content-disposition">>, <<>>}, _) -> 25;
-table_find_field({<<"content-encoding">>, <<>>}, _) -> 26;
-table_find_field({<<"content-language">>, <<>>}, _) -> 27;
-table_find_field({<<"content-length">>, <<>>}, _) -> 28;
-table_find_field({<<"content-location">>, <<>>}, _) -> 29;
-table_find_field({<<"content-range">>, <<>>}, _) -> 30;
-table_find_field({<<"content-type">>, <<>>}, _) -> 31;
-table_find_field({<<"cookie">>, <<>>}, _) -> 32;
-table_find_field({<<"date">>, <<>>}, _) -> 33;
-table_find_field({<<"etag">>, <<>>}, _) -> 34;
-table_find_field({<<"expect">>, <<>>}, _) -> 35;
-table_find_field({<<"expires">>, <<>>}, _) -> 36;
-table_find_field({<<"from">>, <<>>}, _) -> 37;
-table_find_field({<<"host">>, <<>>}, _) -> 38;
-table_find_field({<<"if-match">>, <<>>}, _) -> 39;
-table_find_field({<<"if-modified-since">>, <<>>}, _) -> 40;
-table_find_field({<<"if-none-match">>, <<>>}, _) -> 41;
-table_find_field({<<"if-range">>, <<>>}, _) -> 42;
-table_find_field({<<"if-unmodified-since">>, <<>>}, _) -> 43;
-table_find_field({<<"last-modified">>, <<>>}, _) -> 44;
-table_find_field({<<"link">>, <<>>}, _) -> 45;
-table_find_field({<<"location">>, <<>>}, _) -> 46;
-table_find_field({<<"max-forwards">>, <<>>}, _) -> 47;
-table_find_field({<<"proxy-authenticate">>, <<>>}, _) -> 48;
-table_find_field({<<"proxy-authorization">>, <<>>}, _) -> 49;
-table_find_field({<<"range">>, <<>>}, _) -> 50;
-table_find_field({<<"referer">>, <<>>}, _) -> 51;
-table_find_field({<<"refresh">>, <<>>}, _) -> 52;
-table_find_field({<<"retry-after">>, <<>>}, _) -> 53;
-table_find_field({<<"server">>, <<>>}, _) -> 54;
-table_find_field({<<"set-cookie">>, <<>>}, _) -> 55;
-table_find_field({<<"strict-transport-security">>, <<>>}, _) -> 56;
-table_find_field({<<"transfer-encoding">>, <<>>}, _) -> 57;
-table_find_field({<<"user-agent">>, <<>>}, _) -> 58;
-table_find_field({<<"vary">>, <<>>}, _) -> 59;
-table_find_field({<<"via">>, <<>>}, _) -> 60;
-table_find_field({<<"www-authenticate">>, <<>>}, _) -> 61;
-table_find_field(Header, #state{dyn_table=DynamicTable}) ->
-	table_find_field_dyn(Header, DynamicTable, 62).
-
-table_find_field_dyn(_, [], _) -> not_found;
-table_find_field_dyn(Header, [{_, Header}|_], Index) -> Index;
-table_find_field_dyn(Header, [_|Tail], Index) -> table_find_field_dyn(Header, Tail, Index + 1).
-
-table_find_name(<<":authority">>, _) -> 1;
-table_find_name(<<":method">>, _) -> 2;
-table_find_name(<<":path">>, _) -> 4;
-table_find_name(<<":scheme">>, _) -> 6;
-table_find_name(<<":status">>, _) -> 8;
-table_find_name(<<"accept-charset">>, _) -> 15;
-table_find_name(<<"accept-encoding">>, _) -> 16;
-table_find_name(<<"accept-language">>, _) -> 17;
-table_find_name(<<"accept-ranges">>, _) -> 18;
-table_find_name(<<"accept">>, _) -> 19;
-table_find_name(<<"access-control-allow-origin">>, _) -> 20;
-table_find_name(<<"age">>, _) -> 21;
-table_find_name(<<"allow">>, _) -> 22;
-table_find_name(<<"authorization">>, _) -> 23;
-table_find_name(<<"cache-control">>, _) -> 24;
-table_find_name(<<"content-disposition">>, _) -> 25;
-table_find_name(<<"content-encoding">>, _) -> 26;
-table_find_name(<<"content-language">>, _) -> 27;
-table_find_name(<<"content-length">>, _) -> 28;
-table_find_name(<<"content-location">>, _) -> 29;
-table_find_name(<<"content-range">>, _) -> 30;
-table_find_name(<<"content-type">>, _) -> 31;
-table_find_name(<<"cookie">>, _) -> 32;
-table_find_name(<<"date">>, _) -> 33;
-table_find_name(<<"etag">>, _) -> 34;
-table_find_name(<<"expect">>, _) -> 35;
-table_find_name(<<"expires">>, _) -> 36;
-table_find_name(<<"from">>, _) -> 37;
-table_find_name(<<"host">>, _) -> 38;
-table_find_name(<<"if-match">>, _) -> 39;
-table_find_name(<<"if-modified-since">>, _) -> 40;
-table_find_name(<<"if-none-match">>, _) -> 41;
-table_find_name(<<"if-range">>, _) -> 42;
-table_find_name(<<"if-unmodified-since">>, _) -> 43;
-table_find_name(<<"last-modified">>, _) -> 44;
-table_find_name(<<"link">>, _) -> 45;
-table_find_name(<<"location">>, _) -> 46;
-table_find_name(<<"max-forwards">>, _) -> 47;
-table_find_name(<<"proxy-authenticate">>, _) -> 48;
-table_find_name(<<"proxy-authorization">>, _) -> 49;
-table_find_name(<<"range">>, _) -> 50;
-table_find_name(<<"referer">>, _) -> 51;
-table_find_name(<<"refresh">>, _) -> 52;
-table_find_name(<<"retry-after">>, _) -> 53;
-table_find_name(<<"server">>, _) -> 54;
-table_find_name(<<"set-cookie">>, _) -> 55;
-table_find_name(<<"strict-transport-security">>, _) -> 56;
-table_find_name(<<"transfer-encoding">>, _) -> 57;
-table_find_name(<<"user-agent">>, _) -> 58;
-table_find_name(<<"vary">>, _) -> 59;
-table_find_name(<<"via">>, _) -> 60;
-table_find_name(<<"www-authenticate">>, _) -> 61;
-table_find_name(Name, #state{dyn_table=DynamicTable}) ->
-	table_find_name_dyn(Name, DynamicTable, 62).
-
-table_find_name_dyn(_, [], _) -> not_found;
-table_find_name_dyn(Name, [{Name, _}|_], Index) -> Index;
-table_find_name_dyn(Name, [_|Tail], Index) -> table_find_name_dyn(Name, Tail, Index + 1).
+%	table_find_name_dyn(Name, DynamicTable, 62).
+
+%table_find_name_dyn(_, [], _) -> not_found;
+%table_find_name_dyn(Name, [{Name, _}|_], Index) -> Index;
+%table_find_name_dyn(Name, [_|Tail], Index) -> table_find_name_dyn(Name, Tail, Index + 1).
 
 table_get(1, _) -> {<<":authority">>, <<>>};
 table_get(2, _) -> {<<":method">>, <<"GET">>};
@@ -1422,10 +1491,13 @@ table_get(58, _) -> {<<"user-agent">>, <<>>};
 table_get(59, _) -> {<<"vary">>, <<>>};
 table_get(60, _) -> {<<"via">>, <<>>};
 table_get(61, _) -> {<<"www-authenticate">>, <<>>};
-table_get(Index, #state{dyn_table=DynamicTable}) ->
-	{_, Header} = lists:nth(Index - 61, DynamicTable),
+table_get(Index, #state{offset_index=Offset, dyn_table=DynTable}) ->
+	{_, Header} = maps:get(map_size(DynTable) + Offset + 62 - Index, DynTable),
 	Header.
 
+%	{_, Header} = lists:nth(Index - 61, DynamicTable),
+%	Header.
+
 table_get_name(1, _) -> <<":authority">>;
 table_get_name(2, _) -> <<":method">>;
 table_get_name(3, _) -> <<":method">>;
@@ -1487,34 +1559,65 @@ table_get_name(58, _) -> <<"user-agent">>;
 table_get_name(59, _) -> <<"vary">>;
 table_get_name(60, _) -> <<"via">>;
 table_get_name(61, _) -> <<"www-authenticate">>;
-table_get_name(Index, #state{dyn_table=DynamicTable}) ->
-	{_, {Name, _}} = lists:nth(Index - 61, DynamicTable),
+table_get_name(Index, #state{offset_index=Offset, dyn_table=DynTable}) ->
+	{_, {Name, _}} = maps:get(map_size(DynTable) + Offset + 62 - Index, DynTable),
 	Name.
 
-table_insert(Entry = {Name, Value}, State=#state{size=Size, max_size=MaxSize, dyn_table=DynamicTable}) ->
+%	{_, {Name, _}} = lists:nth(Index - 61, DynamicTable),
+%	Name.
+
+%	next_index = 62 :: pos_integer(),
+%	%% #{Index => {EntrySize, Header}}.
+%	dyn_table = #{} :: #{pos_integer() => {pos_integer(), {binary(), binary()}}},
+%	%% #{Name => {HighestIndex, #{Value => Index}}}.
+%	reverse_dyn_table = #{} :: #{binary() => {pos_integer(), #{binary() => pos_integer()}}}
+
+table_insert(Header = {Name, Value}, State0=#state{size=Size, max_size=MaxSize, next_index=Index}) ->
 	EntrySize = byte_size(Name) + byte_size(Value) + 32,
-	{DynamicTable2, Size2} = if
-		Size + EntrySize > MaxSize ->
-			table_resize(DynamicTable, MaxSize - EntrySize, 0, []);
-		true ->
-			{DynamicTable, Size}
+	State = #state{
+		dyn_table=DynTable,
+		reverse_dyn_table=ReverseTable
+	} = table_resize(State0, MaxSize, Size + EntrySize),
+	State#state{
+		next_index=Index + 1,
+		dyn_table=DynTable#{Index => {EntrySize, Header}},
+		reverse_dyn_table=case ReverseTable of
+			#{Name := {_, Values}} ->
+				ReverseTable#{Name => {Index, Values#{Value => Index}}};
+			_ ->
+				ReverseTable#{Name => {Index, #{Value => Index}}}
+		end
+	}.
+
+table_resize(State, MaxSize, Size) when Size =< MaxSize ->
+	State#state{size=Size};
+table_resize(State=#state{offset_index=Offset0,
+		dyn_table=DynTable0, reverse_dyn_table=ReverseTable0}, MaxSize, Size) ->
+	Offset = Offset0 + 1,
+	{{EntrySize, {Name, Value}}, DynTable} = maps:take(Offset, DynTable0),
+	ReverseTable = case ReverseTable0 of
+		#{Name := {_, Values}} when map_size(Values) =:= 1 ->
+			maps:remove(Name, ReverseTable0);
+		#{Name := {Index, Values}} ->
+			ReverseTable0#{Name => {Index, maps:remove(Value, Values)}}
 	end,
-	State#state{size=Size2 + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable2]}.
+	table_resize(State#state{offset_index=Offset,
+		dyn_table=DynTable, reverse_dyn_table=ReverseTable},
+		MaxSize, Size - EntrySize).
 
-table_resize([], _, Size, Acc) ->
-	{lists:reverse(Acc), Size};
-table_resize([{EntrySize, _}|_], MaxSize, Size, Acc) when Size + EntrySize > MaxSize ->
-	{lists:reverse(Acc), Size};
-table_resize([Entry = {EntrySize, _}|Tail], MaxSize, Size, Acc) ->
-	table_resize(Tail, MaxSize, Size + EntrySize, [Entry|Acc]).
+%table_resize([], _, Size, Acc) ->
+%	{lists:reverse(Acc), Size};
+%table_resize([{EntrySize, _}|_], MaxSize, Size, Acc) when Size + EntrySize > MaxSize ->
+%	{lists:reverse(Acc), Size};
+%table_resize([Entry = {EntrySize, _}|Tail], MaxSize, Size, Acc) ->
+%	table_resize(Tail, MaxSize, Size + EntrySize, [Entry|Acc]).
 
 table_update_size(0, State) ->
-	State#state{size=0, max_size=0, dyn_table=[]};
+	State#state{size=0, max_size=0, next_index=1, offset_index=0, dyn_table=#{}, reverse_dyn_table=#{}};
 table_update_size(MaxSize, State=#state{max_size=MaxSize}) ->
 	State;
-table_update_size(MaxSize, State=#state{dyn_table=DynTable}) ->
-	{DynTable2, Size} = table_resize(DynTable, MaxSize, 0, []),
-	State#state{size=Size, max_size=MaxSize, dyn_table=DynTable2}.
+table_update_size(MaxSize, State=#state{size=Size}) ->
+	table_resize(State#state{max_size=MaxSize}, MaxSize, Size).
 
 -ifdef(TEST).
 prop_str_raw() ->