diff --git a/Makefile b/Makefile
index 06e5bac..5e6ba1e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,3 @@
-REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3
# If there is a rebar in the current directory, use it
ifeq ($(wildcard rebar3),rebar3)
@@ -8,21 +7,19 @@ endif
# Fallback to rebar on PATH
REBAR3 ?= $(shell which rebar3)
-# And finally, prep to download rebar if all else fails
-ifeq ($(REBAR3),)
-REBAR3 = $(CURDIR)/rebar3
-endif
-
-clean: $(REBAR3)
- @$(REBAR3) clean
- rm -rf _build
all: $(REBAR3)
- @$(REBAR3) do clean, compile, eunit, ct, dialyzer
+ @$(REBAR3) do compile, eunit, cover, dialyzer
+
rel: all
@$(REBAR3) release
-$(REBAR3):
- curl -Lo rebar3 $(REBAR3_URL) || wget $(REBAR3_URL)
- chmod a+x rebar3
+
+nginx-tests:
+ ./tools/gen_nginx_tests.py > test/nginx.data
+ @rm -rf test/hpack-test-case
+
+clean: $(REBAR3)
+ @$(REBAR3) clean
+ rm -rf _build
diff --git a/rebar.config b/rebar.config
index 8c929fd..0e5c35c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,21 +1,14 @@
-%% -*- mode: erlang -*-
-%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
-%% ex: ts=4 sw=4 ft=erlang et
{erl_opts, [
warnings_as_errors,
debug_info
]}.
{deps, []}.
-
-{cover_enabled, true}.
-
{ct_opts, [{verbose,true}]}.
-{profiles, [
- {test, [{erl_opts, [
- {i,["include"]}
- ]}
- ]
- }]
-}.
+{eunit_opts, [
+ verbose
+]}.
+
+{cover_enabled, true}.
+{cover_print_enabled, true}.
diff --git a/src/hpack.app.src b/src/hpack.app.src
index 0a4ebd3..92313d7 100644
--- a/src/hpack.app.src
+++ b/src/hpack.app.src
@@ -1,18 +1,14 @@
-%% -*- mode: erlang -*-
-{application, hpack,
- [
- {description, "HPACK Implementation"},
- {vsn, git},
- {registered, []},
- {applications, [
- kernel,
- stdlib
- ]},
- {env, []},
-
- {pkg_name, 'hpack_erl'},
- {maintainers, ["Joe DeVivo"]},
- {licenses, ["MIT"]},
- {links, [{"Github", "https://github.com/joedevivo/hpack"}]}
- ]}.
-%% vim: set filetype=erlang tabstop=2
+{application, hpack, [
+ {description, "HPACK Implementation"},
+ {vsn, git},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []},
+ {pkg_name, 'hpack_erl'},
+ {maintainers, ["Joe DeVivo"]},
+ {licenses, ["MIT"]},
+ {links, [{"Github", "https://github.com/joedevivo/hpack"}]}
+]}.
\ No newline at end of file
diff --git a/src/hpack.erl b/src/hpack.erl
index eddc031..8df6101 100644
--- a/src/hpack.erl
+++ b/src/hpack.erl
@@ -1,270 +1,319 @@
%% @doc
%% The `hpack' module provides functions for working with HPACK as described in
%% RFC 7541.
-
+%%
%% @reference RFC 7541
-module(hpack).
-%% API Exports
+
-export([
- new_context/0,
- new_context/1,
- decode/2,
- encode/2,
- new_max_table_size/2
- ]).
-
-%% Datatypes for the real world:
--record(hpack_context,
- {
- dynamic_table = hpack_index:new(),
- connection_max_table_size = 4096 :: non_neg_integer()
- }).
--type context() :: #hpack_context{}.
-
--export_type([context/0]).
-
--type header_name() :: binary().
--type header_value() :: binary().
--type header() :: {header_name(), header_value()}.
--type headers() :: [header()].
+ new/0,
+ new/1,
+
+ resize/2,
+
+ decode/2,
+ encode/2
+]).
+
+
+-include("hpack.hrl").
+
-export_type([
- header_name/0,
- header_value/0,
- header/0,
- headers/0
- ]).
+ context/0,
+
+ header_name/0,
+ header_value/0,
+ header_opt/0,
+ header/0,
+ headers/0,
+
+ encode_error/0,
+ decode_error/0
+]).
+
+
+%% @equiv new(4096)
+-spec new() -> context().
+new() ->
+ #hpack_ctx{}.
-%% @equiv new_context(4096)
--spec new_context() -> context().
-new_context() -> #hpack_context{}.
%% @doc
-%% Returns a new HPACK context with the given `MaxTableSize' as the max table size.
--spec new_context(non_neg_integer()) -> context().
-new_context(MaxTableSize) ->
- #hpack_context{
- connection_max_table_size=MaxTableSize
- }.
+%% Returns a new HPACK context with the given `ConnMaxTableSize'
+-spec new(non_neg_integer()) -> context().
+new(ConnMaxTableSize) ->
+ #hpack_ctx{
+ max_size = min(ConnMaxTableSize, 4096),
+ conn_max_size = ConnMaxTableSize
+ }.
+
%% @doc
-%% Updates the max table size of the given HPACK context (`Context') to the given
-%% `NewSize'.
-%%
-%% Useful when HTTP/2 settings are renegotiated.
--spec new_max_table_size(non_neg_integer(), context())
- -> context().
-new_max_table_size(NewSize,
- #hpack_context{
- dynamic_table=T,
- connection_max_table_size=OldSize
- }=Context) ->
- NewT = case OldSize > NewSize of
- true ->
- hpack_index:resize(NewSize, T);
- _ ->
- T
- end,
- Context#hpack_context{
- dynamic_table=NewT,
- connection_max_table_size=NewSize
- }.
+%% Updates the max table size of the given HPACK
+%% context to the given `NewSize'. `NewSize` must
+%% not exceed the size specified when creating
+%% the context.
+-spec resize(context(), non_neg_integer()) -> context().
+resize(#hpack_ctx{} = Ctx, NewSize) when is_integer(NewSize) ->
+ #hpack_ctx{
+ max_size = OldSize,
+ conn_max_size = ConnMaxSize
+ } = Ctx,
+ if NewSize =< ConnMaxSize -> ok; true ->
+ ?ERROR({invalid_table_size, NewSize})
+ end,
+ case NewSize > OldSize of
+ true -> Ctx#hpack_ctx{max_size = NewSize};
+ false -> hpack_index:resize(Ctx, NewSize)
+ end.
+
%% @doc
-%% Encodes the given `Headers' using the given `Context'.
+%% Encodes the given `Headers' using the given `Ctx'.
%%
-%% When successful, returns a `{ok, {EncodedHeaders, NewContext}}' tuple where
-%% `EncodedHeaders' is a binary representing the encoded headers and `NewContext'
-%% is the new HPACK context.
+%% When successful, returns a `{ok, NewContext, EncodedHeaders}'
+%% tuple where `EncodedHeaders' is a binary representing the
+%% encoded headers and `NewContext' is the new HPACK context.
%%
%% For example:
%% ```
%% Headers = [{<<":method">>, <<"GET">>}],
-%% {ok, {EncodedHeaders, NewContext}} = hpack:encode(Headers, hpack:new_context()).
+%% {ok, NewCtx EncodedHeaders} = hpack:encode(hpack:new(), Headers).
%% '''
--spec encode(headers(),
- context())
- -> {ok, {binary(), context()}}
- | {error, term()}.
-encode(Headers, Context) ->
- encode(Headers, <<>>, Context).
+-spec encode(context(), headers()) ->
+ {ok, context(), binary()} |
+ {error, encode_error()}.
+encode(#hpack_ctx{} = Ctx, Headers) when is_list(Headers) ->
+ try
+ encode(Ctx, Headers, [])
+ catch throw:{hpack_error, Error} ->
+ {error, Error}
+ end.
+
%% @doc
-%% Decodes the given binary into a list of headers using the given HPACK
-%% context.
+%% Decodes the provided binary `Bin` using the HPACK
+%% context `Ctx`.
%%
-%% If successful, returns a `{ok, {Headers, NewContext}}' tuple where `Headers'
-%% are the decoded headers and `NewContext' is the new HPACK context.
+%% If successful, returns `{ok, NewCtx, Headers}'
%%
%% For example:
%% ```
-%% {Headers, NewContext} = hpack:decode(Binary, OldContext).
+%% {ok, NewCtx, Headers} = hpack:decode(OldCtx, Binary)
%% '''
--spec decode(binary(), context()) ->
- {ok, {headers(), context()}}
- | {error, compression_error}
- | {error, {compression_error, {bad_header_packet, binary()}}}.
-decode(Bin, Context) ->
- decode(Bin, [], Context).
-
-%% Private Functions
-
--spec decode(binary(), headers(), context())
- ->
- {ok, {headers(), context()}}
- | {error, compression_error}
- | {error, {compression_error, {bad_header_packet, binary()}}}.
-%% We're done decoding, return headers
-decode(<<>>, HeadersAcc, C) ->
- {ok, {HeadersAcc, C}};
-%% First bit is '1', so it's an 'Indexed Header Feild'
-%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.1
-decode(<<2#1:1,_/bits>>=B, HeaderAcc, Context) ->
- decode_indexed_header(B, HeaderAcc, Context);
-%% First two bits are '01' so it's a 'Literal Header Field with Incremental Indexing'
-%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.1
-decode(<<2#01:2,_/bits>>=B, HeaderAcc, Context) ->
- decode_literal_header_with_indexing(B, HeaderAcc, Context);
-
-%% First four bits are '0000' so it's a 'Literal Header Field without Indexing'
-%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.2
-decode(<<2#0000:4,_/bits>>=B, HeaderAcc, Context) ->
- decode_literal_header_without_indexing(B, HeaderAcc, Context);
-
-%% First four bits are '0001' so it's a 'Literal Header Field never Indexed'
-%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.3
-decode(<<2#0001:4,_/bits>>=B, HeaderAcc, Context) ->
- decode_literal_header_never_indexed(B, HeaderAcc, Context);
-
-%% First three bits are '001' so it's a 'Dynamic Table Size Update'
-%% http://http2.github.io/http2-spec/compression.html#rfc.section.6.3
-decode(<<2#001:3,_/bits>>=B, HeaderAcc, Context) ->
- decode_dynamic_table_size_update(B, HeaderAcc, Context);
-
-%% Oops!
-decode(<>, _HeaderAcc, _Context) ->
- {error, {compression_error, {bad_header_packet, B}}}.
-
-decode_indexed_header(<<2#1:1,B1/bits>>,
- Acc,
- Context = #hpack_context{dynamic_table=T}) ->
- {Index, B2} = hpack_integer:decode(B1, 7),
- decode(B2, Acc ++ [hpack_index:lookup(Index, T)], Context).
-
-%% The case where the field isn't indexed yet, but should be.
-decode_literal_header_with_indexing(<<2#01:2,2#000000:6>>, _Acc, _Context) ->
- {error, compression_error};
-decode_literal_header_with_indexing(<<2#01:2,2#000000:6,B1/bits>>, Acc,
- #hpack_context{
- dynamic_table=T
- }=Context) ->
- {Str, B2} = hpack_string:decode(B1),
+-spec decode(context(), binary()) ->
+ {ok, {headers(), context()}} |
+ {error, decode_error()}.
+decode(#hpack_ctx{} = Ctx, Bin) when is_binary(Bin) ->
+ try
+ decode(Ctx, Bin, [])
+ catch throw:{hpack_error, Error} ->
+ {error, Error}
+ end.
+
+
+encode(Ctx, [], Acc) ->
+ {ok, Ctx, iolist_to_binary(lists:reverse(Acc))};
+
+encode(Ctx, [{Name, Value} | Tail], Acc) ->
+ encode(Ctx, [{Name, Value, []} | Tail], Acc);
+
+encode(Ctx, [{Name, Value, Opts} | Tail], Acc)
+ when is_binary(Name), is_binary(Value), is_list(Opts) ->
+ NeverIndex = lists:member(never_index, Opts),
+ NoIndex = lists:member(no_index, Opts),
+ NoNameIndex = lists:member(no_name_index, Opts),
+
+ {NewCtx, Encoded} = if
+ NeverIndex and NoNameIndex ->
+ {Ctx, encode_never_index(Name, Value, Opts)};
+ NeverIndex ->
+ {Ctx, encode_never_index(name_index(Ctx, Name), Value, Opts)};
+ NoIndex and NoNameIndex ->
+ {Ctx, encode_no_index(Name, Value, Opts)};
+ NoIndex ->
+ {Ctx, encode_no_index(name_index(Ctx, Name), Value, Opts)};
+ true ->
+ case hpack_index:match(Ctx, {Name, Value}) of
+ {hdr_indexed, Idx} ->
+ {Ctx, encode_indexed(Idx)};
+ {name_indexed, Idx} ->
+ {
+ hpack_index:add(Ctx, Name, Value),
+ encode_indexed(Idx, Value, Opts)
+ };
+ not_indexed ->
+ {
+ hpack_index:add(Ctx, Name, Value),
+ encode_indexed(Name, Value, Opts)
+ }
+ end
+ end,
+
+ encode(NewCtx, Tail, [Encoded | Acc]);
+
+encode(_Ctx, [InvalidHeader | _], _Acc) ->
+ ?ERROR({invalid_header, InvalidHeader}).
+
+
+encode_never_index(Idx, Value, Opts) when is_integer(Idx) ->
+ Prefix = <<2#0001:4>>,
+ IdxBin = hpack_integer:encode(Idx, 4),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>;
+
+encode_never_index(Name, Value, Opts) when is_binary(Name) ->
+ Prefix = <<2#0001:4, 0:4>>,
+ NameBin = hpack_string:encode(Name, Opts),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>.
+
+
+encode_no_index(Idx, Value, Opts) when is_integer(Idx) ->
+ Prefix = <<2#0000:4>>,
+ IdxBin = hpack_integer:encode(Idx, 4),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>;
+
+encode_no_index(Name, Value, Opts) when is_binary(Name) ->
+ Prefix = <<2#0000:4, 0:4>>,
+ NameBin = hpack_string:encode(Name, Opts),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>.
+
+
+encode_indexed(Idx) ->
+ IdxBin = hpack_integer:encode(Idx, 7),
+ <<2#1:1, IdxBin/bits>>.
+
+
+encode_indexed(Idx, Value, Opts) when is_integer(Idx) ->
+ Prefix = <<2#01:2>>,
+ IdxBin = hpack_integer:encode(Idx, 6),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>;
+
+encode_indexed(Name, Value, Opts) when is_binary(Name) ->
+ Prefix = <<2#01:2, 2#000000:6>>,
+ NameBin = hpack_string:encode(Name, Opts),
+ ValueBin = hpack_string:encode(Value, Opts),
+ <>.
+
+
+name_index(Ctx, Name) when is_binary(Name) ->
+ case hpack_index:match(Ctx, {Name, undefined}) of
+ {_, Idx} when is_integer(Idx) -> Idx;
+ not_indexed -> Name
+ end.
+
+
+decode(Ctx, <<>>, Acc) ->
+ {ok, Ctx, lists:reverse(Acc)};
+
+decode(Ctx, <<2#1:1, _/bits>> = Bin, Acc) ->
+ decode_indexed(Ctx, Bin, Acc);
+
+decode(Ctx, <<2#01:2, _/bits>> = Bin, Acc) ->
+ decode_and_index(Ctx, Bin, Acc);
+
+decode(Ctx, <<2#0000:4, _/bits>> = Bin, Acc) ->
+ decode_no_index(Ctx, Bin, Acc);
+
+decode(Ctx, <<2#0001:4, _/bits>> = Bin, Acc) ->
+ decode_never_index(Ctx, Bin, Acc);
+
+decode(Ctx, <<2#001:3, _/bits>> = Bin, Acc) ->
+ decode_size_update(Ctx, Bin, Acc).
+
+
+decode_indexed(_Ctx, <<2#1:1, 2#0000000:7, _/binary>>, _Acc) ->
+ ?ERROR({invalid_index, 0});
+
+decode_indexed(Ctx, <<2#1:1, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 7),
+ Header = case hpack_index:lookup(Ctx, Idx) of
+ {N, V} -> {N, V, []};
+ undefined -> ?ERROR({unknown_index, Idx})
+ end,
+ decode(Ctx, B2, [Header | Acc]).
+
+
+decode_and_index(Ctx, <<2#01:2, 2#000000:6, B1/bits>>, Acc) ->
+ {Name, B2} = hpack_string:decode(B1),
+ {Value, B3} = hpack_string:decode(B2),
+ Header = case hpack_string:any_uncompressed([B1, B2]) of
+ true -> {Name, Value, [uncompressed]};
+ false -> {Name, Value, []}
+ end,
+ decode(hpack_index:add(Ctx, Name, Value), B3, [Header | Acc]);
+
+decode_and_index(Ctx, <<2#01:2, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 6),
+ Name = case hpack_index:lookup(Ctx, Idx) of
+ {N, _} -> N;
+ undefined -> ?ERROR({unknown_index, Idx})
+ end,
{Value, B3} = hpack_string:decode(B2),
- decode(B3,
- Acc ++ [{Str, Value}],
- Context#hpack_context{dynamic_table=hpack_index:add(Str, Value, T)});
-%% This is the case when the index is greater than 0, 0 being not yet
-%% indexed
-decode_literal_header_with_indexing(<<2#01:2,B1/bits>>, Acc,
- Context = #hpack_context{dynamic_table=T}) ->
- {Index, Rem} = hpack_integer:decode(B1,6),
- {Str, B2} = hpack_string:decode(Rem),
- {Name,_} =
- case hpack_index:lookup(Index, T) of
- undefined ->
- throw(undefined);
- {N, V} ->
- {N, V}
+ Header = case hpack_string:is_uncompressed(B2) of
+ true -> {Name, Value, [uncompressed]};
+ false -> {Name, Value, []}
end,
- decode(B2,
- Acc ++ [{Name, Str}],
- Context#hpack_context{dynamic_table=hpack_index:add(Name, Str, T)}).
+ decode(hpack_index:add(Ctx, Name, Value), B3, [Header | Acc]).
-decode_literal_header_without_indexing(<<2#0000:4,2#0000:4,B1/bits>>, Acc,
- Context) ->
- {Str, B2} = hpack_string:decode(B1),
+
+decode_no_index(Ctx, <<2#0000:4, 2#0000:4, B1/bits>>, Acc) ->
+ {Name, B2} = hpack_string:decode(B1),
{Value, B3} = hpack_string:decode(B2),
- decode(B3, Acc ++ [{Str, Value}], Context);
-decode_literal_header_without_indexing(<<2#0000:4,B1/bits>>, Acc,
- Context = #hpack_context{dynamic_table=T}) ->
- {Index, Rem} = hpack_integer:decode(B1,4),
- {Str, B2} = hpack_string:decode(Rem),
- {Name,_}= hpack_index:lookup(Index, T),
- decode(B2, Acc ++ [{Name, Str}], Context).
-
-decode_literal_header_never_indexed(<<2#0001:4,2#0000:4,B1/bits>>, Acc,
- Context) ->
- {Str, B2} = hpack_string:decode(B1),
+ Header = case hpack_string:any_uncompressed([B1, B2]) of
+ true -> {Name, Value, [uncompressed, no_index, no_name_index]};
+ false -> {Name, Value, [no_index, no_name_index]}
+ end,
+ decode(Ctx, B3, [Header | Acc]);
+
+decode_no_index(Ctx, <<2#0000:4, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 4),
+ Name = case hpack_index:lookup(Ctx, Idx) of
+ {N, _} -> N;
+ undefined -> ?ERROR({unknown_index, Idx})
+ end,
{Value, B3} = hpack_string:decode(B2),
- decode(B3, Acc ++ [{Str, Value}], Context);
-decode_literal_header_never_indexed(<<2#0001:4,B1/bits>>, Acc,
- Context = #hpack_context{dynamic_table=T}) ->
- {Index, Rem} = hpack_integer:decode(B1,4),
- {Str, B2} = hpack_string:decode(Rem),
- {Name,_}= hpack_index:lookup(Index, T),
- decode(B2, Acc ++ [{Name, Str}], Context).
-
-decode_dynamic_table_size_update(
- <<2#001:3,Bin/bits>>, []=Acc,
- #hpack_context{
- dynamic_table=T,
- connection_max_table_size=ConnMaxSize
- }=Context
- ) ->
- {NewSize, Rem} = hpack_integer:decode(Bin,5),
- case ConnMaxSize >= NewSize of
- true ->
- decode(Rem,
- Acc,
- Context#hpack_context{
- dynamic_table=hpack_index:resize(NewSize, T)
- });
- _ ->
- {error, compression_error}
- end.
+ Header = case hpack_string:is_uncompressed(B2) of
+ true -> {Name, Value, [uncompressed, no_index]};
+ false -> {Name, Value, [no_index]}
+ end,
+ decode(Ctx, B3, [Header | Acc]).
--spec encode(headers(),
- binary(),
- context()) ->
- {ok, {binary(), context()}}
- | {error, term()}.
-encode([], Acc, Context) ->
- {ok, {Acc, Context}};
-encode([{HeaderName, HeaderValue}|Tail], B, Context = #hpack_context{dynamic_table=T}) ->
- {BinToAdd, NewContext} = case hpack_index:match({HeaderName, HeaderValue}, T) of
- {indexed, I} ->
- {encode_indexed(I), Context};
- {literal_with_indexing, I} ->
- {encode_literal_indexed(I, HeaderValue),
- Context#hpack_context{dynamic_table=hpack_index:add(HeaderName, HeaderValue, T)}};
- {literal_wo_indexing, _X} ->
- {encode_literal_wo_index(HeaderName, HeaderValue),
- Context#hpack_context{dynamic_table=hpack_index:add(HeaderName, HeaderValue, T)}}
+
+decode_never_index(Ctx, <<2#0001:4, 2#0000:4, B1/bits>>, Acc) ->
+ {Name, B2} = hpack_string:decode(B1),
+ {Value, B3} = hpack_string:decode(B2),
+ Header = case hpack_string:any_uncompressed([B1, B2]) of
+ true -> {Name, Value, [uncompressed, never_index, no_name_index]};
+ false -> {Name, Value, [never_index, no_name_index]}
end,
- NewB = <>,
- encode(Tail, NewB, NewContext).
-
-encode_indexed(I) when I < 63 ->
- <<2#1:1,I:7>>;
-encode_indexed(I) ->
- Encoded = hpack_integer:encode(I, 7),
- <<2#1:1, Encoded/bits>>.
-
-encode_literal_indexed(I, Value) when I < 63 ->
- BinToAdd = encode_literal(Value),
- <<2#01:2,I:6,BinToAdd/binary>>;
-encode_literal_indexed(I, Value) ->
- Index = hpack_integer:encode(I, 6),
- BinToAdd = encode_literal(Value),
- <<2#01:2,Index/bits,BinToAdd/binary>>.
-
-encode_literal(Value) ->
- L = hpack_integer:encode(size(Value),7),
- <<2#0:1,L/bits,Value/binary>>.
-
-encode_literal_wo_index(Name, Value) ->
- EncName = encode_literal(Name),
- EncValue = encode_literal(Value),
- <<2#01000000,EncName/binary,EncValue/binary>>.
+ decode(Ctx, B3, [Header | Acc]);
+
+decode_never_index(Ctx, <<2#0001:4, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 4),
+ Name = case hpack_index:lookup(Ctx, Idx) of
+ {N, _} -> N;
+ undefined -> ?ERROR({unknown_index, Idx})
+ end,
+ {Value, B3} = hpack_string:decode(B2),
+ Header = case hpack_string:is_uncompressed(B2) of
+ true -> {Name, Value, [uncompressed, never_index]};
+ false -> {Name, Value, [never_index]}
+ end,
+ decode(Ctx, B3, [Header | Acc]).
+
+
+% TODO: Test whether resize has to precede all headers
+decode_size_update(Ctx, <<2#001:3, B1/bits>>, []) ->
+ {NewSize, B2} = hpack_integer:decode(B1, 5),
+ decode(resize(Ctx, NewSize), B2, []);
+
+decode_size_update(_Ctx, _Bin, Acc) when length(Acc) > 0 ->
+ ?ERROR({invalid_size_update, headers_received}).
diff --git a/src/hpack.hrl b/src/hpack.hrl
new file mode 100644
index 0000000..05c60ca
--- /dev/null
+++ b/src/hpack.hrl
@@ -0,0 +1,40 @@
+
+-record(hpack_ctx, {
+ table = [] :: [{pos_integer(), header_name(), header_value()}],
+ cur_size = 0 :: non_neg_integer(),
+ max_size = 4096 :: non_neg_integer(),
+ conn_max_size = 4096 :: non_neg_integer()
+}).
+
+
+-define(DYNAMIC_TABLE_MIN_INDEX, 62).
+-define(ERROR(Reason), throw({hpack_error, Reason})).
+
+
+-type context() :: #hpack_ctx{}.
+-type header_name() :: binary().
+-type header_value() :: binary().
+-type header_opt() :: never_index | no_index | no_name_index | uncompressed.
+-type header() :: {header_name(), header_value(), [header_opt()]}.
+-type headers() :: [header()].
+-type index_entry() :: {pos_integer(), header_name(), header_value()}.
+
+
+-type encode_error() ::
+ {invalid_table_size, integer()}.
+
+
+-type decode_error() ::
+ {invalid_packet, binary()} |
+ {invalid_index, integer()} |
+ {invalid_size_update, integer()} |
+ {invalid_huffman_encoding, partial_code} |
+ {invalid_huffman_encoding, internal_eos}.
+
+
+-type match_result() ::
+ {hdr_indexed, pos_integer()} |
+ {name_indexed, pos_integer()} |
+ no_index_entry.
+
+
diff --git a/src/hpack_debug.erl b/src/hpack_debug.erl
new file mode 100644
index 0000000..7321a5f
--- /dev/null
+++ b/src/hpack_debug.erl
@@ -0,0 +1,209 @@
+%% @doc
+%% The `hpack' module provides functions for working with HPACK as described in
+%% RFC 7541.
+%%
+%% @reference RFC 7541
+
+-module(hpack_debug).
+
+
+-export([
+ explain/2
+]).
+
+
+-include("hpack.hrl").
+
+
+-define(DONE(Last, Acc), ?ERROR({error, lists:reverse(Acc, [Last])})).
+
+
+%% @doc
+%% Explain the decoding of a given HPack binary representation
+%%
+%% If successful, returns `[{bitstring(), any()}]' listing the
+%% steps decoding takes.
+%%
+%% For example:
+%% ```
+%% {Headers, NewContext} = hpack:decode(Binary, OldContext).
+%% '''
+-spec explain(hpack:context(), binary()) -> [{bitstring(), any()}].
+explain(#hpack_ctx{} = Ctx, Bin) when is_binary(Bin) ->
+ try
+ explain(Ctx, Bin, [])
+ catch throw:{hpack_error, Error} ->
+ Error
+ end.
+
+
+explain(Ctx, <<>>, Acc) ->
+ {ok, Ctx, lists:reverse(Acc)};
+
+explain(Ctx, <<2#1:1, _/bits>> = Bin, Acc) ->
+ explain_indexed(Ctx, Bin, [{<<2#1:1>>, indexed_header} | Acc]);
+
+explain(Ctx, <<2#01:2, _/bits>> = Bin, Acc) ->
+ explain_and_index(Ctx, Bin, [{<<2#01:2>>, incremental_index} | Acc]);
+
+explain(Ctx, <<2#0000:4, _/bits>> = Bin, Acc) ->
+ explain_no_index(Ctx, Bin, [{<<2#0000:4>>, no_index} | Acc]);
+
+explain(Ctx, <<2#0001:4, _/bits>> = Bin, Acc) ->
+ explain_never_index(Ctx, Bin, [{<<2#0001:4>>, never_index} | Acc]);
+
+explain(Ctx, <<2#001:3, _/bits>> = Bin, Acc) ->
+ explain_size_update(Ctx, Bin, [{<<2#001:3>>, size_update} | Acc]).
+
+
+explain_indexed(_Ctx, <<2#1:1, 2#0000000:7, _/binary>>, Acc) ->
+ ?DONE({invalid_index, 0}, Acc);
+
+explain_indexed(Ctx, <<2#1:1, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 7),
+ Header = case hpack_index:lookup(Ctx, Idx) of
+ undefined ->
+ ?DONE({unknown_index, Idx}, Acc);
+ Else ->
+ Else
+ end,
+ explain(Ctx, B2, [Header | Acc]).
+
+
+explain_and_index(Ctx, <<2#01:2, 2#000000:6, B1/bits>>, Acc) ->
+ {Name, NameInfo, B2} = explain_string(B1),
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ NameBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {NameBits, {string, NameInfo, Name}},
+ {<<2#000000:6>>, unindexed_name}
+ ] ++ Acc,
+
+ explain(hpack_index:add(Ctx, Name, Value), B3, NewAcc);
+
+explain_and_index(Ctx, <<2#01:2, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 6),
+ {Name, _} = case hpack_index:lookup(Ctx, Idx) of
+ undefined ->
+ ?DONE({unknown_index, Idx}, Acc);
+ Else ->
+ Else
+ end,
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ IdxBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {IdxBits, {indexed_name, Idx, Name}}
+ ] ++ Acc,
+
+ explain(hpack_index:add(Ctx, Name, Value), B3, NewAcc).
+
+
+explain_no_index(Ctx, <<2#0000:4, 2#0000:4, B1/bits>>, Acc) ->
+ {Name, NameInfo, B2} = explain_string(B1),
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ NameBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {NameBits, {string, NameInfo, Name}},
+ {<<2#0000:4>>, unindexed_name}
+ ] ++ Acc,
+
+ explain(Ctx, B3, NewAcc);
+
+explain_no_index(Ctx, <<2#0000:4, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 4),
+ {Name, _} = case hpack_index:lookup(Ctx, Idx) of
+ undefined ->
+ ?DONE({unknown_index, Idx}, Acc);
+ Else ->
+ Else
+ end,
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ IdxBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {IdxBits, {indexed_name, Idx, Name}}
+ ] ++ Acc,
+
+ explain(Ctx, B3, NewAcc).
+
+
+explain_never_index(Ctx, <<2#0001:4, 2#0000:4, B1/bits>>, Acc) ->
+ {Name, NameInfo, B2} = explain_string(B1),
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ NameBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {NameBits, {string, NameInfo, Name}},
+ {<<2#0000:4>>, unindexed_name}
+ ] ++ Acc,
+
+ explain(Ctx, B3, NewAcc);
+
+explain_never_index(Ctx, <<2#0001:4, B1/bits>>, Acc) ->
+ {Idx, B2} = hpack_integer:decode(B1, 4),
+ {Name, _} = case hpack_index:lookup(Ctx, Idx) of
+ undefined -> ?DONE({unknown_index, Idx}, Acc);
+ Else -> Else
+ end,
+ {Value, ValueInfo, B3} = explain_string(B2),
+
+ IdxBits = hdbinary(B1, B2),
+ ValueBits = hdbinary(B2, B3),
+
+ NewAcc = [
+ {ValueBits, {string, ValueInfo, Value}},
+ {IdxBits, {indexed_name, Idx, Name}}
+ ] ++ Acc,
+
+ explain(Ctx, B3, NewAcc).
+
+
+% TODO: Test whether resize has to precede all headers
+explain_size_update(Ctx, <<2#001:3, B1/bits>>, Acc) ->
+ lists:foreach(fun
+ ({_, size_update}) ->
+ ok;
+ ({_, {new_size, _}}) ->
+ ok;
+ (_) ->
+ ?DONE({invalid_size_update, headers_received}, Acc)
+ end, Acc),
+ {NewSize, B2} = hpack_integer:decode(B1, 5),
+ UpdateBits = hdbinary(B1, B2),
+ NewAcc = [{UpdateBits, {new_size, NewSize}} | Acc],
+ explain(hpack:resize(Ctx, NewSize), B2, NewAcc).
+
+
+explain_string(<>) ->
+ {Length, B2} = hpack_integer:decode(B1, 7),
+ <> = B2,
+ LenBits = hdbinary(B1, B2),
+ {Info, Value} = case Huff of
+ 0 -> {{LenBits, Length, uncompressed}, Data};
+ 1 -> {{LenBits, Length, compressed}, hpack_huffman:decode(Data)}
+ end,
+ {Value, Info, B3}.
+
+
+hdbinary(B1, B2) when bit_size(B1) > bit_size(B2) ->
+ Len = bit_size(B1) - bit_size(B2),
+ <> = B1,
+ H.
diff --git a/src/hpack_huffman.erl b/src/hpack_huffman.erl
new file mode 100644
index 0000000..52bc0c0
--- /dev/null
+++ b/src/hpack_huffman.erl
@@ -0,0 +1,74 @@
+%% @private
+
+
+-module(hpack_huffman).
+
+-export([
+ encode/1,
+ decode/1
+]).
+
+
+-include("hpack.hrl").
+-include("hpack_huffman.hrl").
+
+
+encode(Bin) ->
+ try
+ encode(Bin, [], 0, size(Bin) * 8)
+ catch throw:compressed_larger ->
+ {error, compressed_larger}
+ end.
+
+
+decode(Bin) ->
+ decode(Bin, ?HUFFMAN_TREE, ?HUFFMAN_TREE, false, []).
+
+
+encode(<<>>, Acc, Size, OrigSize) ->
+ Remainder = Size rem 8,
+ Tail = case 8 - Remainder of
+ 1 -> [<< 1:1>>];
+ 2 -> [<< 3:2>>];
+ 3 -> [<< 7:3>>];
+ 4 -> [<< 15:4>>];
+ 5 -> [<< 31:5>>];
+ 6 -> [<< 63:6>>];
+ 7 -> [<<127:7>>];
+ 8 -> []
+ end,
+ Padding = if Remainder > 0 -> 8 - Remainder; true -> 0 end,
+ if Padding + Size < OrigSize -> ok; true ->
+ throw(compressed_larger)
+ end,
+ list_to_bitstring(lists:reverse(Acc, Tail));
+
+encode(<>, Acc, Size, OrigSize) ->
+ Code = element(Byte + 1, ?HUFFMAN_CODES),
+ NewSize = Size + bit_size(Code),
+ if NewSize < OrigSize -> ok; true ->
+ throw(compressed_larger)
+ end,
+ encode(Rest, [Code | Acc], NewSize, OrigSize).
+
+
+decode(<<>>, _, _, ZeroSeen, Acc) ->
+ if not ZeroSeen -> ok; true ->
+ ?ERROR({invalid_huffman_encoding, partial_code})
+ end,
+ list_to_binary(lists:reverse(Acc));
+
+decode(<<0:1, Rest/bits>>, {{v, Value}, _}, Tree, _ZeroSeen, Acc) ->
+ decode(Rest, Tree, Tree, false, [Value | Acc]);
+
+decode(<<0:1, Rest/bits>>, {Left, _}, Tree, _ZeroSeen, Acc) ->
+ decode(Rest, Left, Tree, true, Acc);
+
+decode(<<1:1, Rest/bits>>, {_, {v, Value}}, Tree, _ZeroSeen, Acc) ->
+ if Value < 256 -> ok; true ->
+ ?ERROR({invalid_huffman_encoding, internal_eos})
+ end,
+ decode(Rest, Tree, Tree, false, [Value | Acc]);
+
+decode(<<1:1, Rest/bits>>, {_, Right}, Tree, ZeroSeen, Acc) ->
+ decode(Rest, Right, Tree, ZeroSeen, Acc).
diff --git a/src/hpack_huffman.hrl b/src/hpack_huffman.hrl
new file mode 100644
index 0000000..e6412f1
--- /dev/null
+++ b/src/hpack_huffman.hrl
@@ -0,0 +1,520 @@
+-define(HUFFMAN_CODES, {
+ << 16#1ff8:13>>,
+ << 16#7fffd8:23>>,
+ << 16#fffffe2:28>>,
+ << 16#fffffe3:28>>,
+ << 16#fffffe4:28>>,
+ << 16#fffffe5:28>>,
+ << 16#fffffe6:28>>,
+ << 16#fffffe7:28>>,
+ << 16#fffffe8:28>>,
+ << 16#ffffea:24>>,
+ << 16#3ffffffc:30>>,
+ << 16#fffffe9:28>>,
+ << 16#fffffea:28>>,
+ << 16#3ffffffd:30>>,
+ << 16#fffffeb:28>>,
+ << 16#fffffec:28>>,
+ << 16#fffffed:28>>,
+ << 16#fffffee:28>>,
+ << 16#fffffef:28>>,
+ << 16#ffffff0:28>>,
+ << 16#ffffff1:28>>,
+ << 16#ffffff2:28>>,
+ << 16#3ffffffe:30>>,
+ << 16#ffffff3:28>>,
+ << 16#ffffff4:28>>,
+ << 16#ffffff5:28>>,
+ << 16#ffffff6:28>>,
+ << 16#ffffff7:28>>,
+ << 16#ffffff8:28>>,
+ << 16#ffffff9:28>>,
+ << 16#ffffffa:28>>,
+ << 16#ffffffb:28>>,
+ << 16#14: 6>>,
+ << 16#3f8:10>>,
+ << 16#3f9:10>>,
+ << 16#ffa:12>>,
+ << 16#1ff9:13>>,
+ << 16#15: 6>>,
+ << 16#f8: 8>>,
+ << 16#7fa:11>>,
+ << 16#3fa:10>>,
+ << 16#3fb:10>>,
+ << 16#f9: 8>>,
+ << 16#7fb:11>>,
+ << 16#fa: 8>>,
+ << 16#16: 6>>,
+ << 16#17: 6>>,
+ << 16#18: 6>>,
+ << 16#0: 5>>,
+ << 16#1: 5>>,
+ << 16#2: 5>>,
+ << 16#19: 6>>,
+ << 16#1a: 6>>,
+ << 16#1b: 6>>,
+ << 16#1c: 6>>,
+ << 16#1d: 6>>,
+ << 16#1e: 6>>,
+ << 16#1f: 6>>,
+ << 16#5c: 7>>,
+ << 16#fb: 8>>,
+ << 16#7ffc:15>>,
+ << 16#20: 6>>,
+ << 16#ffb:12>>,
+ << 16#3fc:10>>,
+ << 16#1ffa:13>>,
+ << 16#21: 6>>,
+ << 16#5d: 7>>,
+ << 16#5e: 7>>,
+ << 16#5f: 7>>,
+ << 16#60: 7>>,
+ << 16#61: 7>>,
+ << 16#62: 7>>,
+ << 16#63: 7>>,
+ << 16#64: 7>>,
+ << 16#65: 7>>,
+ << 16#66: 7>>,
+ << 16#67: 7>>,
+ << 16#68: 7>>,
+ << 16#69: 7>>,
+ << 16#6a: 7>>,
+ << 16#6b: 7>>,
+ << 16#6c: 7>>,
+ << 16#6d: 7>>,
+ << 16#6e: 7>>,
+ << 16#6f: 7>>,
+ << 16#70: 7>>,
+ << 16#71: 7>>,
+ << 16#72: 7>>,
+ << 16#fc: 8>>,
+ << 16#73: 7>>,
+ << 16#fd: 8>>,
+ << 16#1ffb:13>>,
+ << 16#7fff0:19>>,
+ << 16#1ffc:13>>,
+ << 16#3ffc:14>>,
+ << 16#22: 6>>,
+ << 16#7ffd:15>>,
+ << 16#3: 5>>,
+ << 16#23: 6>>,
+ << 16#4: 5>>,
+ << 16#24: 6>>,
+ << 16#5: 5>>,
+ << 16#25: 6>>,
+ << 16#26: 6>>,
+ << 16#27: 6>>,
+ << 16#6: 5>>,
+ << 16#74: 7>>,
+ << 16#75: 7>>,
+ << 16#28: 6>>,
+ << 16#29: 6>>,
+ << 16#2a: 6>>,
+ << 16#7: 5>>,
+ << 16#2b: 6>>,
+ << 16#76: 7>>,
+ << 16#2c: 6>>,
+ << 16#8: 5>>,
+ << 16#9: 5>>,
+ << 16#2d: 6>>,
+ << 16#77: 7>>,
+ << 16#78: 7>>,
+ << 16#79: 7>>,
+ << 16#7a: 7>>,
+ << 16#7b: 7>>,
+ << 16#7ffe:15>>,
+ << 16#7fc:11>>,
+ << 16#3ffd:14>>,
+ << 16#1ffd:13>>,
+ << 16#ffffffc:28>>,
+ << 16#fffe6:20>>,
+ << 16#3fffd2:22>>,
+ << 16#fffe7:20>>,
+ << 16#fffe8:20>>,
+ << 16#3fffd3:22>>,
+ << 16#3fffd4:22>>,
+ << 16#3fffd5:22>>,
+ << 16#7fffd9:23>>,
+ << 16#3fffd6:22>>,
+ << 16#7fffda:23>>,
+ << 16#7fffdb:23>>,
+ << 16#7fffdc:23>>,
+ << 16#7fffdd:23>>,
+ << 16#7fffde:23>>,
+ << 16#ffffeb:24>>,
+ << 16#7fffdf:23>>,
+ << 16#ffffec:24>>,
+ << 16#ffffed:24>>,
+ << 16#3fffd7:22>>,
+ << 16#7fffe0:23>>,
+ << 16#ffffee:24>>,
+ << 16#7fffe1:23>>,
+ << 16#7fffe2:23>>,
+ << 16#7fffe3:23>>,
+ << 16#7fffe4:23>>,
+ << 16#1fffdc:21>>,
+ << 16#3fffd8:22>>,
+ << 16#7fffe5:23>>,
+ << 16#3fffd9:22>>,
+ << 16#7fffe6:23>>,
+ << 16#7fffe7:23>>,
+ << 16#ffffef:24>>,
+ << 16#3fffda:22>>,
+ << 16#1fffdd:21>>,
+ << 16#fffe9:20>>,
+ << 16#3fffdb:22>>,
+ << 16#3fffdc:22>>,
+ << 16#7fffe8:23>>,
+ << 16#7fffe9:23>>,
+ << 16#1fffde:21>>,
+ << 16#7fffea:23>>,
+ << 16#3fffdd:22>>,
+ << 16#3fffde:22>>,
+ << 16#fffff0:24>>,
+ << 16#1fffdf:21>>,
+ << 16#3fffdf:22>>,
+ << 16#7fffeb:23>>,
+ << 16#7fffec:23>>,
+ << 16#1fffe0:21>>,
+ << 16#1fffe1:21>>,
+ << 16#3fffe0:22>>,
+ << 16#1fffe2:21>>,
+ << 16#7fffed:23>>,
+ << 16#3fffe1:22>>,
+ << 16#7fffee:23>>,
+ << 16#7fffef:23>>,
+ << 16#fffea:20>>,
+ << 16#3fffe2:22>>,
+ << 16#3fffe3:22>>,
+ << 16#3fffe4:22>>,
+ << 16#7ffff0:23>>,
+ << 16#3fffe5:22>>,
+ << 16#3fffe6:22>>,
+ << 16#7ffff1:23>>,
+ << 16#3ffffe0:26>>,
+ << 16#3ffffe1:26>>,
+ << 16#fffeb:20>>,
+ << 16#7fff1:19>>,
+ << 16#3fffe7:22>>,
+ << 16#7ffff2:23>>,
+ << 16#3fffe8:22>>,
+ << 16#1ffffec:25>>,
+ << 16#3ffffe2:26>>,
+ << 16#3ffffe3:26>>,
+ << 16#3ffffe4:26>>,
+ << 16#7ffffde:27>>,
+ << 16#7ffffdf:27>>,
+ << 16#3ffffe5:26>>,
+ << 16#fffff1:24>>,
+ << 16#1ffffed:25>>,
+ << 16#7fff2:19>>,
+ << 16#1fffe3:21>>,
+ << 16#3ffffe6:26>>,
+ << 16#7ffffe0:27>>,
+ << 16#7ffffe1:27>>,
+ << 16#3ffffe7:26>>,
+ << 16#7ffffe2:27>>,
+ << 16#fffff2:24>>,
+ << 16#1fffe4:21>>,
+ << 16#1fffe5:21>>,
+ << 16#3ffffe8:26>>,
+ << 16#3ffffe9:26>>,
+ << 16#ffffffd:28>>,
+ << 16#7ffffe3:27>>,
+ << 16#7ffffe4:27>>,
+ << 16#7ffffe5:27>>,
+ << 16#fffec:20>>,
+ << 16#fffff3:24>>,
+ << 16#fffed:20>>,
+ << 16#1fffe6:21>>,
+ << 16#3fffe9:22>>,
+ << 16#1fffe7:21>>,
+ << 16#1fffe8:21>>,
+ << 16#7ffff3:23>>,
+ << 16#3fffea:22>>,
+ << 16#3fffeb:22>>,
+ << 16#1ffffee:25>>,
+ << 16#1ffffef:25>>,
+ << 16#fffff4:24>>,
+ << 16#fffff5:24>>,
+ << 16#3ffffea:26>>,
+ << 16#7ffff4:23>>,
+ << 16#3ffffeb:26>>,
+ << 16#7ffffe6:27>>,
+ << 16#3ffffec:26>>,
+ << 16#3ffffed:26>>,
+ << 16#7ffffe7:27>>,
+ << 16#7ffffe8:27>>,
+ << 16#7ffffe9:27>>,
+ << 16#7ffffea:27>>,
+ << 16#7ffffeb:27>>,
+ << 16#ffffffe:28>>,
+ << 16#7ffffec:27>>,
+ << 16#7ffffed:27>>,
+ << 16#7ffffee:27>>,
+ << 16#7ffffef:27>>,
+ << 16#7fffff0:27>>,
+ << 16#3ffffee:26>>,
+ << 16#3fffffff:30>>
+}).
+
+
+-define(HUFFMAN_TREE,
+ {{{{{{v, 48},
+ {v, 49}},
+ {{v, 50},
+ {v, 97}}},
+ {{{v, 99},
+ {v, 101}},
+ {{v, 105},
+ {v, 111}}}},
+ {{{{v, 115},
+ {v, 116}},
+ {{{v, 32},
+ {v, 37}},
+ {{v, 45},
+ {v, 46}}}},
+ {{{{v, 47},
+ {v, 51}},
+ {{v, 52},
+ {v, 53}}},
+ {{{v, 54},
+ {v, 55}},
+ {{v, 56},
+ {v, 57}}}}}},
+ {{{{{{v, 61},
+ {v, 65}},
+ {{v, 95},
+ {v, 98}}},
+ {{{v, 100},
+ {v, 102}},
+ {{v, 103},
+ {v, 104}}}},
+ {{{{v, 108},
+ {v, 109}},
+ {{v, 110},
+ {v, 112}}},
+ {{{v, 114},
+ {v, 117}},
+ {{{v, 58},
+ {v, 66}},
+ {{v, 67},
+ {v, 68}}}}}},
+ {{{{{{v, 69},
+ {v, 70}},
+ {{v, 71},
+ {v, 72}}},
+ {{{v, 73},
+ {v, 74}},
+ {{v, 75},
+ {v, 76}}}},
+ {{{{v, 77},
+ {v, 78}},
+ {{v, 79},
+ {v, 80}}},
+ {{{v, 81},
+ {v, 82}},
+ {{v, 83},
+ {v, 84}}}}},
+ {{{{{v, 85},
+ {v, 86}},
+ {{v, 87},
+ {v, 89}}},
+ {{{v, 106},
+ {v, 107}},
+ {{v, 113},
+ {v, 118}}}},
+ {{{{v, 119},
+ {v, 120}},
+ {{v, 121},
+ {v, 122}}},
+ {{{{v, 38},
+ {v, 42}},
+ {{v, 44},
+ {v, 59}}},
+ {{{v, 88},
+ {v, 90}},
+ {{{{v, 33},
+ {v, 34}},
+ {{v, 40},
+ {v, 41}}},
+ {{{v, 63},
+ {{v, 39},
+ {v, 43}}},
+ {{{v, 124},
+ {{v, 35},
+ {v, 62}}},
+ {{{{v, 0},
+ {v, 36}},
+ {{v, 64},
+ {v, 91}}},
+ {{{v, 93},
+ {v, 126}},
+ {{{v, 94},
+ {v, 125}},
+ {{{v, 60},
+ {v, 96}},
+ {{v, 123},
+ {{{{{v, 92},
+ {v, 195}},
+ {{v, 208},
+ {{v, 128},
+ {v, 130}}}},
+ {{{{v, 131},
+ {v, 162}},
+ {{v, 184},
+ {v, 194}}},
+ {{{v, 224},
+ {v, 226}},
+ {{{v, 153},
+ {v, 161}},
+ {{v, 167},
+ {v, 172}}}}}},
+ {{{{{{v, 176},
+ {v, 177}},
+ {{v, 179},
+ {v, 209}}},
+ {{{v, 216},
+ {v, 217}},
+ {{v, 227},
+ {v, 229}}}},
+ {{{{v, 230},
+ {{v, 129},
+ {v, 132}}},
+ {{{v, 133},
+ {v, 134}},
+ {{v, 136},
+ {v, 146}}}},
+ {{{{v, 154},
+ {v, 156}},
+ {{v, 160},
+ {v, 163}}},
+ {{{v, 164},
+ {v, 169}},
+ {{v, 170},
+ {v, 173}}}}}},
+ {{{{{{v, 178},
+ {v, 181}},
+ {{v, 185},
+ {v, 186}}},
+ {{{v, 187},
+ {v, 189}},
+ {{v, 190},
+ {v, 196}}}},
+ {{{{v, 198},
+ {v, 228}},
+ {{v, 232},
+ {v, 233}}},
+ {{{{v, 1},
+ {v, 135}},
+ {{v, 137},
+ {v, 138}}},
+ {{{v, 139},
+ {v, 140}},
+ {{v, 141},
+ {v, 143}}}}}},
+ {{{{{{v, 147},
+ {v, 149}},
+ {{v, 150},
+ {v, 151}}},
+ {{{v, 152},
+ {v, 155}},
+ {{v, 157},
+ {v, 158}}}},
+ {{{{v, 165},
+ {v, 166}},
+ {{v, 168},
+ {v, 174}}},
+ {{{v, 175},
+ {v, 180}},
+ {{v, 182},
+ {v, 183}}}}},
+ {{{{{v, 188},
+ {v, 191}},
+ {{v, 197},
+ {v, 231}}},
+ {{{v, 239},
+ {{v, 9},
+ {v, 142}}},
+ {{{v, 144},
+ {v, 145}},
+ {{v, 148},
+ {v, 159}}}}},
+ {{{{{v, 171},
+ {v, 206}},
+ {{v, 215},
+ {v, 225}}},
+ {{{v, 236},
+ {v, 237}},
+ {{{v, 199},
+ {v, 207}},
+ {{v, 234},
+ {v, 235}}}}},
+ {{{{{{v, 192},
+ {v, 193}},
+ {{v, 200},
+ {v, 201}}},
+ {{{v, 202},
+ {v, 205}},
+ {{v, 210},
+ {v, 213}}}},
+ {{{{v, 218},
+ {v, 219}},
+ {{v, 238},
+ {v, 240}}},
+ {{{v, 242},
+ {v, 243}},
+ {{v, 255},
+ {{v, 203},
+ {v, 204}}}}}},
+ {{{{{{v, 211},
+ {v, 212}},
+ {{v, 214},
+ {v, 221}}},
+ {{{v, 222},
+ {v, 223}},
+ {{v, 241},
+ {v, 244}}}},
+ {{{{v, 245},
+ {v, 246}},
+ {{v, 247},
+ {v, 248}}},
+ {{{v, 250},
+ {v, 251}},
+ {{v, 252},
+ {v, 253}}}}},
+ {{{{{v, 254},
+ {{v, 2},
+ {v, 3}}},
+ {{{v, 4},
+ {v, 5}},
+ {{v, 6},
+ {v, 7}}}},
+ {{{{v, 8},
+ {v, 11}},
+ {{v, 12},
+ {v, 14}}},
+ {{{v, 15},
+ {v, 16}},
+ {{v, 17},
+ {v, 18}}}}},
+ {{{{{v, 19},
+ {v, 20}},
+ {{v, 21},
+ {v, 23}}},
+ {{{v, 24},
+ {v, 25}},
+ {{v, 26},
+ {v, 27}}}},
+ {{{{v, 28},
+ {v, 29}},
+ {{v, 30},
+ {v, 31}}},
+ {{{v, 127},
+ {v, 220}},
+ {{v, 249},
+ {{{v, 10},
+ {v, 13}},
+ {{v, 22},
+ {v, 256}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+).
diff --git a/src/hpack_index.erl b/src/hpack_index.erl
index 0f547a7..58aab89 100644
--- a/src/hpack_index.erl
+++ b/src/hpack_index.erl
@@ -3,290 +3,277 @@
-module(hpack_index).
-export([
- new/0,
- new/1,
+ resize/2,
+ size/1,
+ max_size/1,
+ table/1,
+
add/3,
+
lookup/2,
- resize/2,
- table_size/1,
- max_table_size/1,
match/2
]).
--type header_name() :: binary().
--type header_value():: binary().
--type header() :: {header_name(), header_value()}.
--export_type([header/0]).
--define(DYNAMIC_TABLE_MIN_INDEX, 62).
+-include("hpack.hrl").
+-include("hpack_index.hrl").
--record(dynamic_table, {
- table = [] :: [{pos_integer(), header_name(), header_value()}],
- %% max_size is the size allowed by the protocol
- max_size = 4096 :: non_neg_integer(),
- %% size is how big this thing really is
- size = 0 :: non_neg_integer()
- }).
--type dynamic_table() :: #dynamic_table{}.
--spec new() -> dynamic_table().
-new() -> #dynamic_table{}.
+-spec resize(context(), pos_integer()) -> context().
+resize(#hpack_ctx{} = Ctx0, NewMaxSize) ->
+ #hpack_ctx{
+ table = Table
+ } = Ctx0,
+ Ctx1 = Ctx0#hpack_ctx{
+ max_size = NewMaxSize
+ },
+ drop_entries(Ctx1, lists:reverse(Table), 0).
--spec new(pos_integer()) -> dynamic_table().
-new(S) -> #dynamic_table{max_size=S}.
--spec entry_size({pos_integer(), header_name(), header_value()}) -> pos_integer().
-entry_size({_, Name, Value}) ->
- entry_size(Name, Value).
+-spec size(context()) -> pos_integer().
+size(#hpack_ctx{} = Ctx) ->
+ Ctx#hpack_ctx.cur_size.
--spec entry_size(header_name(), header_value()) -> pos_integer().
-entry_size(Name, Value) ->
- 32 + size(Name) + size(Value).
-
--spec add(header_name(), header_value(), dynamic_table()) -> dynamic_table().
-add(Name, Value, DT) ->
- add(Name, Value, entry_size(Name, Value), DT).
-
--spec add(header_name(), header_value(), pos_integer(), dynamic_table()) -> dynamic_table().
-add(Name, Value, EntrySize, DT=#dynamic_table{table=T, size=S, max_size=MS})
- when EntrySize + S =< MS ->
- TPlus = lists:map(fun({N,H,V}) -> {N+1, H,V} end, T),
- DT#dynamic_table{size=S+EntrySize, table=[{?DYNAMIC_TABLE_MIN_INDEX, Name, Value}|TPlus]};
-add(_Name, _Value, EntrySize, DT=#dynamic_table{max_size=MS, table=[]})
- when EntrySize > MS ->
- DT;
-add(Name, Value, EntrySize, DT=#dynamic_table{size=S, max_size=MS})
- when EntrySize + S > MS ->
- add(Name, Value, EntrySize,
- droplast(DT)).
-
--spec droplast(dynamic_table()) -> dynamic_table().
-droplast(DT=#dynamic_table{table=[]}) -> DT;
-droplast(DT=#dynamic_table{table=T, size=S}) ->
- [Last|NewTR] = lists:reverse(T),
- DT#dynamic_table{size=S-entry_size(Last), table=lists:reverse(NewTR)}.
-
-
-%% TODO: There's a problem here, where if you shink the dynamic table
-%% size, you can never increase it.
-
-%% resize/2 sets the max_table_size, and truncates the table if it
-%% needs to be smaller to fit in that maximum
--spec resize(pos_integer(), dynamic_table()) -> dynamic_table().
-resize(NewSize, DT=#dynamic_table{size=S})
- when NewSize >= S ->
- DT#dynamic_table{max_size=NewSize};
-resize(NewSize, DT) ->
- resize(NewSize, droplast(DT)).
-
--spec table_size(dynamic_table()) -> pos_integer().
-table_size(#dynamic_table{size=S}) -> S.
-
--spec max_table_size(dynamic_table()) -> pos_integer().
-max_table_size(#dynamic_table{max_size=S}) -> S.
-
--spec lookup(pos_integer(), dynamic_table()) -> header() | undefined.
-lookup(1 , _) -> {<<":authority">>, <<>>};
-lookup(2 , _) -> {<<":method">>, <<"GET">>};
-lookup(3 , _) -> {<<":method">>, <<"POST">>};
-lookup(4 , _) -> {<<":path">>, <<"/">>};
-lookup(5 , _) -> {<<":path">>, <<"/index.html">>};
-lookup(6 , _) -> {<<":scheme">>, <<"http">>};
-lookup(7 , _) -> {<<":scheme">>, <<"https">>};
-lookup(8 , _) -> {<<":status">>, <<"200">>};
-lookup(9 , _) -> {<<":status">>, <<"204">>};
-lookup(10, _) -> {<<":status">>, <<"206">>};
-lookup(11, _) -> {<<":status">>, <<"304">>};
-lookup(12, _) -> {<<":status">>, <<"400">>};
-lookup(13, _) -> {<<":status">>, <<"404">>};
-lookup(14, _) -> {<<":status">>, <<"500">>};
-lookup(15, _) -> {<<"accept-charset">>, <<>>};
-lookup(16, _) -> {<<"accept-encoding">>, <<"gzip, deflate">>};
-lookup(17, _) -> {<<"accept-language">>, <<>>};
-lookup(18, _) -> {<<"accept-ranges">>, <<>>};
-lookup(19, _) -> {<<"accept">>, <<>>};
-lookup(20, _) -> {<<"access-control-allow-origin">>, <<>>};
-lookup(21, _) -> {<<"age">>, <<>>};
-lookup(22, _) -> {<<"allow">>, <<>>};
-lookup(23, _) -> {<<"authorization">>, <<>>};
-lookup(24, _) -> {<<"cache-control">>, <<>>};
-lookup(25, _) -> {<<"content-disposition">>, <<>>};
-lookup(26, _) -> {<<"content-encoding">>, <<>>};
-lookup(27, _) -> {<<"content-language">>, <<>>};
-lookup(28, _) -> {<<"content-length">>, <<>>};
-lookup(29, _) -> {<<"content-location">>, <<>>};
-lookup(30, _) -> {<<"content-range">>, <<>>};
-lookup(31, _) -> {<<"content-type">>, <<>>};
-lookup(32, _) -> {<<"cookie">>, <<>>};
-lookup(33, _) -> {<<"date">>, <<>>};
-lookup(34, _) -> {<<"etag">>, <<>>};
-lookup(35, _) -> {<<"expect">>, <<>>};
-lookup(36, _) -> {<<"expires">>, <<>>};
-lookup(37, _) -> {<<"from">>, <<>>};
-lookup(38, _) -> {<<"host">>, <<>>};
-lookup(39, _) -> {<<"if-match">>, <<>>};
-lookup(40, _) -> {<<"if-modified-since">>, <<>>};
-lookup(41, _) -> {<<"if-none-match">>, <<>>};
-lookup(42, _) -> {<<"if-range">>, <<>>};
-lookup(43, _) -> {<<"if-unmodified-since">>, <<>>};
-lookup(44, _) -> {<<"last-modified">>, <<>>};
-lookup(45, _) -> {<<"link">>, <<>>};
-lookup(46, _) -> {<<"location">>, <<>>};
-lookup(47, _) -> {<<"max-forwards">>, <<>>};
-lookup(48, _) -> {<<"proxy-authenticate">>, <<>>};
-lookup(49, _) -> {<<"proxy-authorization">>, <<>>};
-lookup(50, _) -> {<<"range">>, <<>>};
-lookup(51, _) -> {<<"referer">>, <<>>};
-lookup(52, _) -> {<<"refresh">>, <<>>};
-lookup(53, _) -> {<<"retry-after">>, <<>>};
-lookup(54, _) -> {<<"server">>, <<>>};
-lookup(55, _) -> {<<"set-cookie">>, <<>>};
-lookup(56, _) -> {<<"strict-transport-security">>, <<>>};
-lookup(57, _) -> {<<"transfer-encoding">>, <<>>};
-lookup(58, _) -> {<<"user-agent">>, <<>>};
-lookup(59, _) -> {<<"vary">>, <<>>};
-lookup(60, _) -> {<<"via">>, <<>>};
-lookup(61, _) -> {<<"www-authenticate">>, <<>>};
-lookup(Idx, #dynamic_table{table=T}) ->
- case lists:keyfind(Idx, 1, T) of
- false ->
- undefined;
- {Idx, Name, V} ->
- {Name, V}
+
+-spec max_size(context()) -> pos_integer().
+max_size(#hpack_ctx{} = Ctx) ->
+ Ctx#hpack_ctx.max_size.
+
+
+-spec table(context()) -> {non_neg_integer(), [any()]}.
+table(#hpack_ctx{} = Ctx) ->
+ {Ctx#hpack_ctx.cur_size, Ctx#hpack_ctx.table}.
+
+
+-spec add(context(), header_name(), header_value()) -> context().
+add(Ctx0, Name, Value) ->
+ #hpack_ctx{
+ table = Table,
+ max_size = MaxSize
+ } = Ctx0,
+
+ EntrySize = entry_size(Name, Value),
+ Ctx1 = drop_entries(Ctx0, lists:reverse(Table), EntrySize),
+ Ctx2 = relable_entries(Ctx1),
+ if EntrySize > MaxSize -> Ctx2; true ->
+ add_entry(Ctx2, Name, Value, EntrySize)
end.
--spec match(header(), dynamic_table()) -> {atom(), pos_integer()|undefined}.
-match({<<":authority">>, <<>>}, _) -> {indexed, 1 };
-match({<<":method">>, <<"GET">>}, _) -> {indexed, 2 };
-match({<<":method">>, <<"POST">>}, _) -> {indexed, 3 };
-match({<<":path">>, <<"/">>}, _) -> {indexed, 4 };
-match({<<":path">>, <<"/index.html">>}, _) -> {indexed, 5 };
-match({<<":scheme">>, <<"http">>}, _) -> {indexed, 6 };
-match({<<":scheme">>, <<"https">>}, _) -> {indexed, 7 };
-match({<<":status">>, <<"200">>}, _) -> {indexed, 8 };
-match({<<":status">>, <<"204">>}, _) -> {indexed, 9 };
-match({<<":status">>, <<"206">>}, _) -> {indexed, 10};
-match({<<":status">>, <<"304">>}, _) -> {indexed, 11};
-match({<<":status">>, <<"400">>}, _) -> {indexed, 12};
-match({<<":status">>, <<"404">>}, _) -> {indexed, 13};
-match({<<":status">>, <<"500">>}, _) -> {indexed, 14};
-match({<<"accept-charset">>, <<>>}, _) -> {indexed, 15};
-match({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> {indexed, 16};
-match({<<"accept-language">>, <<>>}, _) -> {indexed, 17};
-match({<<"accept-ranges">>, <<>>}, _) -> {indexed, 18};
-match({<<"accept">>, <<>>}, _) -> {indexed, 19};
-match({<<"access-control-allow-origin">>, <<>>}, _) -> {indexed, 20};
-match({<<"age">>, <<>>}, _) -> {indexed, 21};
-match({<<"allow">>, <<>>}, _) -> {indexed, 22};
-match({<<"authorization">>, <<>>}, _) -> {indexed, 23};
-match({<<"cache-control">>, <<>>}, _) -> {indexed, 24};
-match({<<"content-disposition">>, <<>>}, _) -> {indexed, 25};
-match({<<"content-encoding">>, <<>>}, _) -> {indexed, 26};
-match({<<"content-language">>, <<>>}, _) -> {indexed, 27};
-match({<<"content-length">>, <<>>}, _) -> {indexed, 28};
-match({<<"content-location">>, <<>>}, _) -> {indexed, 29};
-match({<<"content-range">>, <<>>}, _) -> {indexed, 30};
-match({<<"content-type">>, <<>>}, _) -> {indexed, 31};
-match({<<"cookie">>, <<>>}, _) -> {indexed, 32};
-match({<<"date">>, <<>>}, _) -> {indexed, 33};
-match({<<"etag">>, <<>>}, _) -> {indexed, 34};
-match({<<"expect">>, <<>>}, _) -> {indexed, 35};
-match({<<"expires">>, <<>>}, _) -> {indexed, 36};
-match({<<"from">>, <<>>}, _) -> {indexed, 37};
-match({<<"host">>, <<>>}, _) -> {indexed, 38};
-match({<<"if-match">>, <<>>}, _) -> {indexed, 39};
-match({<<"if-modified-since">>, <<>>}, _) -> {indexed, 40};
-match({<<"if-none-match">>, <<>>}, _) -> {indexed, 41};
-match({<<"if-range">>, <<>>}, _) -> {indexed, 42};
-match({<<"if-unmodified-since">>, <<>>}, _) -> {indexed, 43};
-match({<<"last-modified">>, <<>>}, _) -> {indexed, 44};
-match({<<"link">>, <<>>}, _) -> {indexed, 45};
-match({<<"location">>, <<>>}, _) -> {indexed, 46};
-match({<<"max-forwards">>, <<>>}, _) -> {indexed, 47};
-match({<<"proxy-authenticate">>, <<>>}, _) -> {indexed, 48};
-match({<<"proxy-authorization">>, <<>>}, _) -> {indexed, 49};
-match({<<"range">>, <<>>}, _) -> {indexed, 50};
-match({<<"referer">>, <<>>}, _) -> {indexed, 51};
-match({<<"refresh">>, <<>>}, _) -> {indexed, 52};
-match({<<"retry-after">>, <<>>}, _) -> {indexed, 53};
-match({<<"server">>, <<>>}, _) -> {indexed, 54};
-match({<<"set-cookie">>, <<>>}, _) -> {indexed, 55};
-match({<<"strict-transport-security">>, <<>>}, _) -> {indexed, 56};
-match({<<"transfer-encoding">>, <<>>}, _) -> {indexed, 57};
-match({<<"user-agent">>, <<>>}, _) -> {indexed, 58};
-match({<<"vary">>, <<>>}, _) -> {indexed, 59};
-match({<<"via">>, <<>>}, _) -> {indexed, 60};
-match({<<"www-authenticate">>, <<>>}, _) -> {indexed, 61};
-match({Name, Value}, #dynamic_table{table=T}) ->
- %% If there's a N/V match in the DT, return {indexed, Int}
- %% If there's a N match, make sure there's not also a static_match
- %% if so, use it, otherwise use the first element in the first match
- %% If not
- Found = lists:filter(fun({_,N,_}) -> N =:= Name end, T),
- ExactFound = lists:filter(fun({_,_,V}) -> V =:= Value end, Found),
-
- case {ExactFound, Found, static_match(Name)} of
- {[{I,Name,Value}|_], _, _} ->
- {indexed, I};
- {[], [{I,Name,_}|_], undefined} ->
- {literal_with_indexing, I};
- {[], _, I} when is_integer(I) ->
- {literal_with_indexing, I};
- {[], [], undefined} ->
- {literal_wo_indexing, undefined}
+
+-spec lookup(context(), pos_integer()) -> header() | undefined.
+lookup(_Ctx, Idx) when Idx > 0, Idx < ?DYNAMIC_TABLE_MIN_INDEX ->
+ element(Idx, ?STATIC_TABLE);
+
+lookup(#hpack_ctx{} = Ctx, Idx) ->
+ DynamicIdx = Idx - ?DYNAMIC_TABLE_MIN_INDEX + 1,
+ case lists:keyfind(DynamicIdx, 1, Ctx#hpack_ctx.table) of
+ {DynamicIdx, Name, Value} -> {Name, Value};
+ false -> undefined
end.
--spec static_match(header_name()) -> pos_integer() | undefined.
-static_match(<<":authority">>) -> 1 ;
-static_match(<<":method">>) -> 2 ;
-static_match(<<":path">>) -> 4 ;
-static_match(<<":scheme">>) -> 6 ;
-static_match(<<":status">>) -> 8 ;
-static_match(<<"accept-charset">>) -> 15;
-static_match(<<"accept-encoding">>) -> 16;
-static_match(<<"accept-language">>) -> 17;
-static_match(<<"accept-ranges">>) -> 18;
-static_match(<<"accept">>) -> 19;
-static_match(<<"access-control-allow-origin">>) -> 20;
-static_match(<<"age">>) -> 21;
-static_match(<<"allow">>) -> 22;
-static_match(<<"authorization">>) -> 23;
-static_match(<<"cache-control">>) -> 24;
-static_match(<<"content-disposition">>) -> 25;
-static_match(<<"content-encoding">>) -> 26;
-static_match(<<"content-language">>) -> 27;
-static_match(<<"content-length">>) -> 28;
-static_match(<<"content-location">>) -> 29;
-static_match(<<"content-range">>) -> 30;
-static_match(<<"content-type">>) -> 31;
-static_match(<<"cookie">>) -> 32;
-static_match(<<"date">>) -> 33;
-static_match(<<"etag">>) -> 34;
-static_match(<<"expect">>) -> 35;
-static_match(<<"expires">>) -> 36;
-static_match(<<"from">>) -> 37;
-static_match(<<"host">>) -> 38;
-static_match(<<"if-match">>) -> 39;
-static_match(<<"if-modified-since">>) -> 40;
-static_match(<<"if-none-match">>) -> 41;
-static_match(<<"if-range">>) -> 42;
-static_match(<<"if-unmodified-since">>) -> 43;
-static_match(<<"last-modified">>) -> 44;
-static_match(<<"link">>) -> 45;
-static_match(<<"location">>) -> 46;
-static_match(<<"max-forwards">>) -> 47;
-static_match(<<"proxy-authenticate">>) -> 48;
-static_match(<<"proxy-authorization">>) -> 49;
-static_match(<<"range">>) -> 50;
-static_match(<<"referer">>) -> 51;
-static_match(<<"refresh">>) -> 52;
-static_match(<<"retry-after">>) -> 53;
-static_match(<<"server">>) -> 54;
-static_match(<<"set-cookie">>) -> 55;
-static_match(<<"strict-transport-security">>) -> 56;
-static_match(<<"transfer-encoding">>) -> 57;
-static_match(<<"user-agent">>) -> 58;
-static_match(<<"vary">>) -> 59;
-static_match(<<"via">>) -> 60;
-static_match(<<"www-authenticate">>) -> 61;
-static_match(_) -> undefined.
+
+-spec match(context(), {header_name(), header_value()}) -> match_result().
+match(#hpack_ctx{} = Ctx, {Name, Value}) ->
+ % The order of preference for finding a usable cached
+ % header index is:
+ %
+ % 1. An exact static match
+ % 2. An exact dynamic match
+ % 3. A name only static match
+ % 4. A name only dynamic match
+ try
+ case static_exact({Name, Value}) of
+ I1 when is_integer(I1) ->
+ throw({hdr_indexed, I1});
+ undefined ->
+ ok
+ end,
+
+ DynamicNameIdx = lists:foldl(fun({I, N, V}, Acc) ->
+ case Name == N andalso Value == V of
+ true ->
+ throw({hdr_indexed, I + ?DYNAMIC_TABLE_MIN_INDEX - 1});
+ false ->
+ ok
+ end,
+ case Name == N andalso Acc == undefined of
+ true -> I + ?DYNAMIC_TABLE_MIN_INDEX - 1;
+ false -> Acc
+ end
+ end, undefined, Ctx#hpack_ctx.table),
+
+ case {static_name(Name), DynamicNameIdx} of
+ {I2, _} when is_integer(I2) -> {name_indexed, I2};
+ {_, I2} when is_integer(I2) -> {name_indexed, I2};
+ {undefined, undefined} -> not_indexed
+ end
+ catch
+ throw:{hdr_indexed, Idx} ->
+ {hdr_indexed, Idx}
+ end.
+
+
+drop_entries(#hpack_ctx{cur_size = 0} = Ctx, [], _EntrySize) ->
+ Ctx#hpack_ctx{
+ table = []
+ };
+
+drop_entries(Ctx, [{_Pos, Name, Value} | RevRest] = RevTable, EntrySize) ->
+ #hpack_ctx{
+ cur_size = CurSize,
+ max_size = MaxSize
+ } = Ctx,
+ case EntrySize + CurSize =< MaxSize of
+ true ->
+ Ctx#hpack_ctx{
+ table = lists:reverse(RevTable)
+ };
+ false ->
+ RemSize = entry_size(Name, Value),
+ NewCtx = Ctx#hpack_ctx{
+ cur_size = CurSize - RemSize
+ },
+ drop_entries(NewCtx, RevRest, EntrySize)
+ end.
+
+
+entry_size(Name, Value) ->
+ erlang:size(Name) + erlang:size(Value) + 32.
+
+
+relable_entries(Ctx) ->
+ #hpack_ctx{
+ table = Table
+ } = Ctx,
+ Ctx#hpack_ctx{
+ table = [{Idx + 1, Name, Value} || {Idx, Name, Value} <- Table]
+ }.
+
+
+add_entry(Ctx, Name, Value, EntrySize) ->
+ #hpack_ctx{
+ table = Table,
+ cur_size = CurSize
+ } = Ctx,
+ Ctx#hpack_ctx{
+ table = [{1, Name, Value} | Table],
+ cur_size = CurSize + EntrySize
+ }.
+
+
+static_exact({<<":authority">>, <<>>}) -> 1;
+static_exact({<<":method">>, <<"GET">>}) -> 2;
+static_exact({<<":method">>, <<"POST">>}) -> 3;
+static_exact({<<":path">>, <<"/">>}) -> 4;
+static_exact({<<":path">>, <<"/index.html">>}) -> 5;
+static_exact({<<":scheme">>, <<"http">>}) -> 6;
+static_exact({<<":scheme">>, <<"https">>}) -> 7;
+static_exact({<<":status">>, <<"200">>}) -> 8;
+static_exact({<<":status">>, <<"204">>}) -> 9;
+static_exact({<<":status">>, <<"206">>}) -> 10;
+static_exact({<<":status">>, <<"304">>}) -> 11;
+static_exact({<<":status">>, <<"400">>}) -> 12;
+static_exact({<<":status">>, <<"404">>}) -> 13;
+static_exact({<<":status">>, <<"500">>}) -> 14;
+static_exact({<<"accept-charset">>, <<>>}) -> 15;
+static_exact({<<"accept-encoding">>, <<"gzip, deflate">>}) -> 16;
+static_exact({<<"accept-language">>, <<>>}) -> 17;
+static_exact({<<"accept-ranges">>, <<>>}) -> 18;
+static_exact({<<"accept">>, <<>>}) -> 19;
+static_exact({<<"access-control-allow-origin">>, <<>>}) -> 20;
+static_exact({<<"age">>, <<>>}) -> 21;
+static_exact({<<"allow">>, <<>>}) -> 22;
+static_exact({<<"authorization">>, <<>>}) -> 23;
+static_exact({<<"cache-control">>, <<>>}) -> 24;
+static_exact({<<"content-disposition">>, <<>>}) -> 25;
+static_exact({<<"content-encoding">>, <<>>}) -> 26;
+static_exact({<<"content-language">>, <<>>}) -> 27;
+static_exact({<<"content-length">>, <<>>}) -> 28;
+static_exact({<<"content-location">>, <<>>}) -> 29;
+static_exact({<<"content-range">>, <<>>}) -> 30;
+static_exact({<<"content-type">>, <<>>}) -> 31;
+static_exact({<<"cookie">>, <<>>}) -> 32;
+static_exact({<<"date">>, <<>>}) -> 33;
+static_exact({<<"etag">>, <<>>}) -> 34;
+static_exact({<<"expect">>, <<>>}) -> 35;
+static_exact({<<"expires">>, <<>>}) -> 36;
+static_exact({<<"from">>, <<>>}) -> 37;
+static_exact({<<"host">>, <<>>}) -> 38;
+static_exact({<<"if-match">>, <<>>}) -> 39;
+static_exact({<<"if-modified-since">>, <<>>}) -> 40;
+static_exact({<<"if-none-match">>, <<>>}) -> 41;
+static_exact({<<"if-range">>, <<>>}) -> 42;
+static_exact({<<"if-unmodified-since">>, <<>>}) -> 43;
+static_exact({<<"last-modified">>, <<>>}) -> 44;
+static_exact({<<"link">>, <<>>}) -> 45;
+static_exact({<<"location">>, <<>>}) -> 46;
+static_exact({<<"max-forwards">>, <<>>}) -> 47;
+static_exact({<<"proxy-authenticate">>, <<>>}) -> 48;
+static_exact({<<"proxy-authorization">>, <<>>}) -> 49;
+static_exact({<<"range">>, <<>>}) -> 50;
+static_exact({<<"referer">>, <<>>}) -> 51;
+static_exact({<<"refresh">>, <<>>}) -> 52;
+static_exact({<<"retry-after">>, <<>>}) -> 53;
+static_exact({<<"server">>, <<>>}) -> 54;
+static_exact({<<"set-cookie">>, <<>>}) -> 55;
+static_exact({<<"strict-transport-security">>, <<>>}) -> 56;
+static_exact({<<"transfer-encoding">>, <<>>}) -> 57;
+static_exact({<<"user-agent">>, <<>>}) -> 58;
+static_exact({<<"vary">>, <<>>}) -> 59;
+static_exact({<<"via">>, <<>>}) -> 60;
+static_exact({<<"www-authenticate">>, <<>>}) -> 61;
+static_exact(_) -> undefined.
+
+static_name(<<":authority">>) -> 1;
+static_name(<<":method">>) -> 2;
+static_name(<<":path">>) -> 4;
+static_name(<<":scheme">>) -> 6;
+static_name(<<":status">>) -> 8;
+static_name(<<"accept-charset">>) -> 15;
+static_name(<<"accept-encoding">>) -> 16;
+static_name(<<"accept-language">>) -> 17;
+static_name(<<"accept-ranges">>) -> 18;
+static_name(<<"accept">>) -> 19;
+static_name(<<"access-control-allow-origin">>) -> 20;
+static_name(<<"age">>) -> 21;
+static_name(<<"allow">>) -> 22;
+static_name(<<"authorization">>) -> 23;
+static_name(<<"cache-control">>) -> 24;
+static_name(<<"content-disposition">>) -> 25;
+static_name(<<"content-encoding">>) -> 26;
+static_name(<<"content-language">>) -> 27;
+static_name(<<"content-length">>) -> 28;
+static_name(<<"content-location">>) -> 29;
+static_name(<<"content-range">>) -> 30;
+static_name(<<"content-type">>) -> 31;
+static_name(<<"cookie">>) -> 32;
+static_name(<<"date">>) -> 33;
+static_name(<<"etag">>) -> 34;
+static_name(<<"expect">>) -> 35;
+static_name(<<"expires">>) -> 36;
+static_name(<<"from">>) -> 37;
+static_name(<<"host">>) -> 38;
+static_name(<<"if-match">>) -> 39;
+static_name(<<"if-modified-since">>) -> 40;
+static_name(<<"if-none-match">>) -> 41;
+static_name(<<"if-range">>) -> 42;
+static_name(<<"if-unmodified-since">>) -> 43;
+static_name(<<"last-modified">>) -> 44;
+static_name(<<"link">>) -> 45;
+static_name(<<"location">>) -> 46;
+static_name(<<"max-forwards">>) -> 47;
+static_name(<<"proxy-authenticate">>) -> 48;
+static_name(<<"proxy-authorization">>) -> 49;
+static_name(<<"range">>) -> 50;
+static_name(<<"referer">>) -> 51;
+static_name(<<"refresh">>) -> 52;
+static_name(<<"retry-after">>) -> 53;
+static_name(<<"server">>) -> 54;
+static_name(<<"set-cookie">>) -> 55;
+static_name(<<"strict-transport-security">>) -> 56;
+static_name(<<"transfer-encoding">>) -> 57;
+static_name(<<"user-agent">>) -> 58;
+static_name(<<"vary">>) -> 59;
+static_name(<<"via">>) -> 60;
+static_name(<<"www-authenticate">>) -> 61;
+static_name(_) -> undefined.
diff --git a/src/hpack_index.hrl b/src/hpack_index.hrl
new file mode 100644
index 0000000..f430ad9
--- /dev/null
+++ b/src/hpack_index.hrl
@@ -0,0 +1,65 @@
+
+
+-define(STATIC_TABLE, {
+ {<<":authority">>, <<>>},
+ {<<":method">>, <<"GET">>},
+ {<<":method">>, <<"POST">>},
+ {<<":path">>, <<"/">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":status">>, <<"200">>},
+ {<<":status">>, <<"204">>},
+ {<<":status">>, <<"206">>},
+ {<<":status">>, <<"304">>},
+ {<<":status">>, <<"400">>},
+ {<<":status">>, <<"404">>},
+ {<<":status">>, <<"500">>},
+ {<<"accept-charset">>, <<>>},
+ {<<"accept-encoding">>, <<"gzip, deflate">>},
+ {<<"accept-language">>, <<>>},
+ {<<"accept-ranges">>, <<>>},
+ {<<"accept">>, <<>>},
+ {<<"access-control-allow-origin">>, <<>>},
+ {<<"age">>, <<>>},
+ {<<"allow">>, <<>>},
+ {<<"authorization">>, <<>>},
+ {<<"cache-control">>, <<>>},
+ {<<"content-disposition">>, <<>>},
+ {<<"content-encoding">>, <<>>},
+ {<<"content-language">>, <<>>},
+ {<<"content-length">>, <<>>},
+ {<<"content-location">>, <<>>},
+ {<<"content-range">>, <<>>},
+ {<<"content-type">>, <<>>},
+ {<<"cookie">>, <<>>},
+ {<<"date">>, <<>>},
+ {<<"etag">>, <<>>},
+ {<<"expect">>, <<>>},
+ {<<"expires">>, <<>>},
+ {<<"from">>, <<>>},
+ {<<"host">>, <<>>},
+ {<<"if-match">>, <<>>},
+ {<<"if-modified-since">>, <<>>},
+ {<<"if-none-match">>, <<>>},
+ {<<"if-range">>, <<>>},
+ {<<"if-unmodified-since">>, <<>>},
+ {<<"last-modified">>, <<>>},
+ {<<"link">>, <<>>},
+ {<<"location">>, <<>>},
+ {<<"max-forwards">>, <<>>},
+ {<<"proxy-authenticate">>, <<>>},
+ {<<"proxy-authorization">>, <<>>},
+ {<<"range">>, <<>>},
+ {<<"referer">>, <<>>},
+ {<<"refresh">>, <<>>},
+ {<<"retry-after">>, <<>>},
+ {<<"server">>, <<>>},
+ {<<"set-cookie">>, <<>>},
+ {<<"strict-transport-security">>, <<>>},
+ {<<"transfer-encoding">>, <<>>},
+ {<<"user-agent">>, <<>>},
+ {<<"vary">>, <<>>},
+ {<<"via">>, <<>>},
+ {<<"www-authenticate">>, <<>>}
+}).
\ No newline at end of file
diff --git a/src/hpack_integer.erl b/src/hpack_integer.erl
index d062066..37ce6be 100644
--- a/src/hpack_integer.erl
+++ b/src/hpack_integer.erl
@@ -6,101 +6,59 @@
%% as defined http://tools.ietf.org/html/rfc7541#section-5.1
-export([
- decode/2,
- encode/2
- ]).
+ encode/2,
+ decode/2
+]).
-%% To save every bit on the wire, an integer can fill a partial octet,
-%% and usually does in HPACK. If that integer is small enough to fit,
-%% then no further work is required.
-%% In the octet below, the first three bits were used for something
-%% else. Doesn't matter what. What matters is that we have 5 bits left
-%% for the integer, which means if it's less than 2^5-1, we're all
-%% good!
+-spec encode(non_neg_integer(), pos_integer()) -> bitstring().
+encode( 1, 1) -> << 1:1, 0:8>>;
+encode( 3, 2) -> << 3:2, 0:8>>;
+encode( 7, 3) -> << 7:3, 0:8>>;
+encode( 15, 4) -> << 15:4, 0:8>>;
+encode( 31, 5) -> << 31:5, 0:8>>;
+encode( 63, 6) -> << 63:6, 0:8>>;
+encode(127, 7) -> <<127:7, 0:8>>;
+encode(255, 8) -> <<255:8, 0:8>>;
-%% 0 1 2 3 4 5 6 7
-%% +---+---+---+---+---+---+---+---+
-%% | ? | ? | ? | Value |
-%% +---+---+---+-------------------+
-
-%% So what if it's greater than 2^5-1?
+encode(Int, N) when is_integer(Int), is_integer(N), Int < (1 bsl N - 1) ->
+ <>;
-%% 0 1 2 3 4 5 6 7
-%% +---+---+---+---+---+---+---+---+
-%% | ? | ? | ? | 1 1 1 1 1 |
-%% +---+---+---+-------------------+
-%% | 1 | Value-(2^5-1) LSB |
-%% +---+---------------------------+
-%% ...
-%% +---+---------------------------+
-%% | 0 | Value-(2^5-1) MSB |
-%% +---+---------------------------+
+encode(Int, N) when is_integer(Int), is_integer(N) ->
+ Prefix = 1 bsl N - 1,
+ Remaining = Int - Prefix,
+ encode_int(Remaining, [<>]).
-%% Then we don't want to waste those first 5 bits, so they're all set
-%% to 1, and we know the integer is greater than 2^5-1. How much
-%% greater? That's the question we answer with the following octets.
+-spec encode_int(non_neg_integer(), [bitstring()]) -> bitstring().
+encode_int(Int, Acc) when is_integer(Int), is_list(Acc) ->
+ ThisByte = (Int rem 128),
+ RestInt = Int bsr 7,
+ case RestInt of
+ 0 ->
+ list_to_bitstring(lists:reverse(Acc, [<>]));
+ _ ->
+ encode_int(RestInt, [<<1:1, ThisByte:7>> | Acc])
+ end.
-%% "Value-(2^5-1)" is "how much greater?". Let's call it I.
--spec decode(binary(), pos_integer()) -> {non_neg_integer(), binary()}.
-decode(<<1:1,Bin/bits>>, 1) ->
- prefix_plus(1, decode(Bin, 0, 0));
-decode(<<3:2,Bin/bits>>, 2) ->
- prefix_plus(3, decode(Bin, 0, 0));
-decode(<<7:3,Bin/bits>>, 3) ->
- prefix_plus(7, decode(Bin, 0, 0));
-decode(<<15:4,Bin/bits>>, 4) ->
- prefix_plus(15, decode(Bin, 0, 0));
-decode(<<31:5,Bin/bits>>, 5) ->
- prefix_plus(31, decode(Bin, 0, 0));
-decode(<<63:6,Bin/bits>>, 6) ->
- prefix_plus(63, decode(Bin, 0, 0));
-decode(<<127:7,Bin/bits>>, 7) ->
- prefix_plus(127, decode(Bin, 0, 0));
-decode(<<255:8,Bin/bits>>, 8) ->
- prefix_plus(255, decode(Bin, 0, 0));
-%% This clause means we have something small enough to fit in prefix
+-spec decode(bitstring(), pos_integer()) -> {non_neg_integer(), binary()}.
+decode(<< 1:1, Bin/bits>>, 1) -> decode(Bin, 1, 1);
+decode(<< 3:2, Bin/bits>>, 2) -> decode(Bin, 1, 3);
+decode(<< 7:3, Bin/bits>>, 3) -> decode(Bin, 1, 7);
+decode(<< 15:4, Bin/bits>>, 4) -> decode(Bin, 1, 15);
+decode(<< 31:5, Bin/bits>>, 5) -> decode(Bin, 1, 31);
+decode(<< 63:6, Bin/bits>>, 6) -> decode(Bin, 1, 63);
+decode(<<127:7, Bin/bits>>, 7) -> decode(Bin, 1, 127);
+decode(<<255:8, Bin/bits>>, 8) -> decode(Bin, 1, 255);
decode(Bin, Prefix) ->
- <> = Bin,
+ %% This clause means we have something small
+ %% enough to fit in prefix
+ <> = Bin,
{Value, Rem}.
--spec decode(binary(), non_neg_integer(), non_neg_integer()) -> {non_neg_integer(), binary()}.
-decode(<<1:1,Int:7,Rem/binary>>, M, I) ->
- decode(Rem, M+7, round(I + Int * math:pow(2, M)));
-decode(<<0:1,Int:7,Rem/binary>>, M, I) ->
- {round(I + Int * math:pow(2, M)), Rem}.
--spec prefix_plus(pos_integer(), {non_neg_integer(), binary()}) -> {non_neg_integer(), binary()}.
-prefix_plus(Prefix, {I, Rem}) ->
- {Prefix+I, Rem}.
+decode(<<1:1, Int:7, Rest/binary>>, Factor, Value) ->
+ decode(Rest, Factor bsl 7, Value + (Int * Factor));
--spec encode(non_neg_integer(), pos_integer()) -> binary().
-%% First clauses are peformance optimizations for Int == 2^Prefix - 1
-encode( 1,1) -> << 1:1,0:8>>;
-encode( 3,2) -> << 3:2,0:8>>;
-encode( 7,3) -> << 7:3,0:8>>;
-encode( 15,4) -> << 15:4,0:8>>;
-encode( 31,5) -> << 31:5,0:8>>;
-encode( 63,6) -> << 63:6,0:8>>;
-encode(127,7) -> <<127:7,0:8>>;
-encode(255,8) -> <<255,0>>;
-encode(Int, N) when Int < (1 bsl N - 1) ->
- <>;
-encode(Int, N) ->
- Prefix = 1 bsl N - 1,
- Remaining = Int - Prefix,
- Bin = encode_(Remaining, <<>>),
- <>.
-
--spec encode_(non_neg_integer(), binary()) -> binary().
-encode_(I, BinAcc) ->
- LeastSigSeven = (I rem 128),
- RestToEncode = I bsr 7,
- case RestToEncode of
- 0 ->
- <>;
- _ -> %% Adds the continuation bit
- ThisByte = 128 + LeastSigSeven,
- encode_(RestToEncode, <>)
- end.
+decode(<<0:1, Int:7, Rest/binary>>, Factor, Value) ->
+ {Value + (Int * Factor), Rest}.
diff --git a/src/hpack_string.erl b/src/hpack_string.erl
index 5a7f451..34681a4 100644
--- a/src/hpack_string.erl
+++ b/src/hpack_string.erl
@@ -3,26 +3,55 @@
-module(hpack_string).
-export([
- decode/1
- ]).
+ encode/2,
+ decode/1,
+
+ is_uncompressed/1,
+ any_uncompressed/1
+]).
+
+
+-include("hpack.hrl").
+
+
+-spec encode(binary(), [header_opt()]) -> binary().
+encode(Bin, Opts) ->
+ Uncompressed = lists:member(uncompressed, Opts),
+ {Compressed, DataBin} = case Uncompressed of
+ true ->
+ {false, Bin};
+ false ->
+ case hpack_huffman:encode(Bin) of
+ {error, compressed_larger} ->
+ {false, Bin};
+ HBin when is_binary(HBin) ->
+ {true, HBin}
+ end
+ end,
+ SizeBin = hpack_integer:encode(size(DataBin), 7),
+ case Compressed of
+ true -> <<1:1, SizeBin/bits, DataBin/binary>>;
+ false -> <<0:1, SizeBin/bits, DataBin/binary>>
+ end.
+
-spec decode(binary()) -> {binary(), binary()}.
decode(<<>>) ->
- {<<>>, <<>>};
-decode(<>) ->
- {Length, Rem} = hpack_integer:decode(Bin, 7),
- <> = Rem,
- Literal = case Huff of
- 1 ->
- huffman:decode(RawLiteral);
- 0 ->
- RawLiteral
- end,
- {Literal, B2}.
-
-%% As the Huffman-encoded data doesn't always end at an octet
-%% boundary, some padding is inserted after it, up to the next octet
-%% boundary. To prevent this padding from being misinterpreted as
-%% part of the string literal, the most significant bits of the code
-%% corresponding to the EOS (end-of-string) symbol are used.
-%% EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
+ ?ERROR({invalid_string, no_data});
+
+decode(<>) ->
+ {Length, B2} = hpack_integer:decode(B1, 7),
+ <> = B2,
+ Value = case Huff of
+ 0 -> Data;
+ 1 -> hpack_huffman:decode(Data)
+ end,
+ {Value, B3}.
+
+
+is_uncompressed(<<0:1, _/bits>>) -> true;
+is_uncompressed(<<1:1, _/bits>>) -> false.
+
+
+any_uncompressed(Bins) when is_list(Bins) ->
+ lists:any(fun is_uncompressed/1, Bins).
diff --git a/src/huffman.erl b/src/huffman.erl
deleted file mode 100644
index f932933..0000000
--- a/src/huffman.erl
+++ /dev/null
@@ -1,531 +0,0 @@
-%% @private
-
--module(huffman).
-
--export([decode/1, encode/1]).
-
-decode(Bin) -> decode(Bin, []).
-decode(<< 16#1ff8:13,Bits/bits>>, Acc) -> decode(Bits, [ 0|Acc]);
-decode(<< 16#7fffd8:23,Bits/bits>>, Acc) -> decode(Bits, [ 1|Acc]);
-decode(<< 16#fffffe2:28,Bits/bits>>, Acc) -> decode(Bits, [ 2|Acc]);
-decode(<< 16#fffffe3:28,Bits/bits>>, Acc) -> decode(Bits, [ 3|Acc]);
-decode(<< 16#fffffe4:28,Bits/bits>>, Acc) -> decode(Bits, [ 4|Acc]);
-decode(<< 16#fffffe5:28,Bits/bits>>, Acc) -> decode(Bits, [ 5|Acc]);
-decode(<< 16#fffffe6:28,Bits/bits>>, Acc) -> decode(Bits, [ 6|Acc]);
-decode(<< 16#fffffe7:28,Bits/bits>>, Acc) -> decode(Bits, [ 7|Acc]);
-decode(<< 16#fffffe8:28,Bits/bits>>, Acc) -> decode(Bits, [ 8|Acc]);
-decode(<< 16#ffffea:24,Bits/bits>>, Acc) -> decode(Bits, [ 9|Acc]);
-decode(<<16#3ffffffc:30,Bits/bits>>, Acc) -> decode(Bits, [ 10|Acc]);
-decode(<< 16#fffffe9:28,Bits/bits>>, Acc) -> decode(Bits, [ 11|Acc]);
-decode(<< 16#fffffea:28,Bits/bits>>, Acc) -> decode(Bits, [ 12|Acc]);
-decode(<<16#3ffffffd:30,Bits/bits>>, Acc) -> decode(Bits, [ 13|Acc]);
-decode(<< 16#fffffeb:28,Bits/bits>>, Acc) -> decode(Bits, [ 14|Acc]);
-decode(<< 16#fffffec:28,Bits/bits>>, Acc) -> decode(Bits, [ 15|Acc]);
-decode(<< 16#fffffed:28,Bits/bits>>, Acc) -> decode(Bits, [ 16|Acc]);
-decode(<< 16#fffffee:28,Bits/bits>>, Acc) -> decode(Bits, [ 17|Acc]);
-decode(<< 16#fffffef:28,Bits/bits>>, Acc) -> decode(Bits, [ 18|Acc]);
-decode(<< 16#ffffff0:28,Bits/bits>>, Acc) -> decode(Bits, [ 19|Acc]);
-decode(<< 16#ffffff1:28,Bits/bits>>, Acc) -> decode(Bits, [ 20|Acc]);
-decode(<< 16#ffffff2:28,Bits/bits>>, Acc) -> decode(Bits, [ 21|Acc]);
-decode(<<16#3ffffffe:30,Bits/bits>>, Acc) -> decode(Bits, [ 22|Acc]);
-decode(<< 16#ffffff3:28,Bits/bits>>, Acc) -> decode(Bits, [ 23|Acc]);
-decode(<< 16#ffffff4:28,Bits/bits>>, Acc) -> decode(Bits, [ 24|Acc]);
-decode(<< 16#ffffff5:28,Bits/bits>>, Acc) -> decode(Bits, [ 25|Acc]);
-decode(<< 16#ffffff6:28,Bits/bits>>, Acc) -> decode(Bits, [ 26|Acc]);
-decode(<< 16#ffffff7:28,Bits/bits>>, Acc) -> decode(Bits, [ 27|Acc]);
-decode(<< 16#ffffff8:28,Bits/bits>>, Acc) -> decode(Bits, [ 28|Acc]);
-decode(<< 16#ffffff9:28,Bits/bits>>, Acc) -> decode(Bits, [ 29|Acc]);
-decode(<< 16#ffffffa:28,Bits/bits>>, Acc) -> decode(Bits, [ 30|Acc]);
-decode(<< 16#ffffffb:28,Bits/bits>>, Acc) -> decode(Bits, [ 31|Acc]);
-decode(<< 16#14:6,Bits/bits>>, Acc) -> decode(Bits, [ 32|Acc]);
-decode(<< 16#3f8:10,Bits/bits>>, Acc) -> decode(Bits, [ 33|Acc]);
-decode(<< 16#3f9:10,Bits/bits>>, Acc) -> decode(Bits, [ 34|Acc]);
-decode(<< 16#ffa:12,Bits/bits>>, Acc) -> decode(Bits, [ 35|Acc]);
-decode(<< 16#1ff9:13,Bits/bits>>, Acc) -> decode(Bits, [ 36|Acc]);
-decode(<< 16#15:6,Bits/bits>>, Acc) -> decode(Bits, [ 37|Acc]);
-decode(<< 16#f8:8,Bits/bits>>, Acc) -> decode(Bits, [ 38|Acc]);
-decode(<< 16#7fa:11,Bits/bits>>, Acc) -> decode(Bits, [ 39|Acc]);
-decode(<< 16#3fa:10,Bits/bits>>, Acc) -> decode(Bits, [ 40|Acc]);
-decode(<< 16#3fb:10,Bits/bits>>, Acc) -> decode(Bits, [ 41|Acc]);
-decode(<< 16#f9:8,Bits/bits>>, Acc) -> decode(Bits, [ 42|Acc]);
-decode(<< 16#7fb:11,Bits/bits>>, Acc) -> decode(Bits, [ 43|Acc]);
-decode(<< 16#fa:8,Bits/bits>>, Acc) -> decode(Bits, [ 44|Acc]);
-decode(<< 16#16:6,Bits/bits>>, Acc) -> decode(Bits, [ 45|Acc]);
-decode(<< 16#17:6,Bits/bits>>, Acc) -> decode(Bits, [ 46|Acc]);
-decode(<< 16#18:6,Bits/bits>>, Acc) -> decode(Bits, [ 47|Acc]);
-decode(<< 16#0:5,Bits/bits>>, Acc) -> decode(Bits, [ 48|Acc]);
-decode(<< 16#1:5,Bits/bits>>, Acc) -> decode(Bits, [ 49|Acc]);
-decode(<< 16#2:5,Bits/bits>>, Acc) -> decode(Bits, [ 50|Acc]);
-decode(<< 16#19:6,Bits/bits>>, Acc) -> decode(Bits, [ 51|Acc]);
-decode(<< 16#1a:6,Bits/bits>>, Acc) -> decode(Bits, [ 52|Acc]);
-decode(<< 16#1b:6,Bits/bits>>, Acc) -> decode(Bits, [ 53|Acc]);
-decode(<< 16#1c:6,Bits/bits>>, Acc) -> decode(Bits, [ 54|Acc]);
-decode(<< 16#1d:6,Bits/bits>>, Acc) -> decode(Bits, [ 55|Acc]);
-decode(<< 16#1e:6,Bits/bits>>, Acc) -> decode(Bits, [ 56|Acc]);
-decode(<< 16#1f:6,Bits/bits>>, Acc) -> decode(Bits, [ 57|Acc]);
-decode(<< 16#5c:7,Bits/bits>>, Acc) -> decode(Bits, [ 58|Acc]);
-decode(<< 16#fb:8,Bits/bits>>, Acc) -> decode(Bits, [ 59|Acc]);
-decode(<< 16#7ffc:15,Bits/bits>>, Acc) -> decode(Bits, [ 60|Acc]);
-decode(<< 16#20:6,Bits/bits>>, Acc) -> decode(Bits, [ 61|Acc]);
-decode(<< 16#ffb:12,Bits/bits>>, Acc) -> decode(Bits, [ 62|Acc]);
-decode(<< 16#3fc:10,Bits/bits>>, Acc) -> decode(Bits, [ 63|Acc]);
-decode(<< 16#1ffa:13,Bits/bits>>, Acc) -> decode(Bits, [ 64|Acc]);
-decode(<< 16#21:6,Bits/bits>>, Acc) -> decode(Bits, [ 65|Acc]);
-decode(<< 16#5d:7,Bits/bits>>, Acc) -> decode(Bits, [ 66|Acc]);
-decode(<< 16#5e:7,Bits/bits>>, Acc) -> decode(Bits, [ 67|Acc]);
-decode(<< 16#5f:7,Bits/bits>>, Acc) -> decode(Bits, [ 68|Acc]);
-decode(<< 16#60:7,Bits/bits>>, Acc) -> decode(Bits, [ 69|Acc]);
-decode(<< 16#61:7,Bits/bits>>, Acc) -> decode(Bits, [ 70|Acc]);
-decode(<< 16#62:7,Bits/bits>>, Acc) -> decode(Bits, [ 71|Acc]);
-decode(<< 16#63:7,Bits/bits>>, Acc) -> decode(Bits, [ 72|Acc]);
-decode(<< 16#64:7,Bits/bits>>, Acc) -> decode(Bits, [ 73|Acc]);
-decode(<< 16#65:7,Bits/bits>>, Acc) -> decode(Bits, [ 74|Acc]);
-decode(<< 16#66:7,Bits/bits>>, Acc) -> decode(Bits, [ 75|Acc]);
-decode(<< 16#67:7,Bits/bits>>, Acc) -> decode(Bits, [ 76|Acc]);
-decode(<< 16#68:7,Bits/bits>>, Acc) -> decode(Bits, [ 77|Acc]);
-decode(<< 16#69:7,Bits/bits>>, Acc) -> decode(Bits, [ 78|Acc]);
-decode(<< 16#6a:7,Bits/bits>>, Acc) -> decode(Bits, [ 79|Acc]);
-decode(<< 16#6b:7,Bits/bits>>, Acc) -> decode(Bits, [ 80|Acc]);
-decode(<< 16#6c:7,Bits/bits>>, Acc) -> decode(Bits, [ 81|Acc]);
-decode(<< 16#6d:7,Bits/bits>>, Acc) -> decode(Bits, [ 82|Acc]);
-decode(<< 16#6e:7,Bits/bits>>, Acc) -> decode(Bits, [ 83|Acc]);
-decode(<< 16#6f:7,Bits/bits>>, Acc) -> decode(Bits, [ 84|Acc]);
-decode(<< 16#70:7,Bits/bits>>, Acc) -> decode(Bits, [ 85|Acc]);
-decode(<< 16#71:7,Bits/bits>>, Acc) -> decode(Bits, [ 86|Acc]);
-decode(<< 16#72:7,Bits/bits>>, Acc) -> decode(Bits, [ 87|Acc]);
-decode(<< 16#fc:8,Bits/bits>>, Acc) -> decode(Bits, [ 88|Acc]);
-decode(<< 16#73:7,Bits/bits>>, Acc) -> decode(Bits, [ 89|Acc]);
-decode(<< 16#fd:8,Bits/bits>>, Acc) -> decode(Bits, [ 90|Acc]);
-decode(<< 16#1ffb:13,Bits/bits>>, Acc) -> decode(Bits, [ 91|Acc]);
-decode(<< 16#7fff0:19,Bits/bits>>, Acc) -> decode(Bits, [ 92|Acc]);
-decode(<< 16#1ffc:13,Bits/bits>>, Acc) -> decode(Bits, [ 93|Acc]);
-decode(<< 16#3ffc:14,Bits/bits>>, Acc) -> decode(Bits, [ 94|Acc]);
-decode(<< 16#22:6,Bits/bits>>, Acc) -> decode(Bits, [ 95|Acc]);
-decode(<< 16#7ffd:15,Bits/bits>>, Acc) -> decode(Bits, [ 96|Acc]);
-decode(<< 16#3:5,Bits/bits>>, Acc) -> decode(Bits, [ 97|Acc]);
-decode(<< 16#23:6,Bits/bits>>, Acc) -> decode(Bits, [ 98|Acc]);
-decode(<< 16#4:5,Bits/bits>>, Acc) -> decode(Bits, [ 99|Acc]);
-decode(<< 16#24:6,Bits/bits>>, Acc) -> decode(Bits, [100|Acc]);
-decode(<< 16#5:5,Bits/bits>>, Acc) -> decode(Bits, [101|Acc]);
-decode(<< 16#25:6,Bits/bits>>, Acc) -> decode(Bits, [102|Acc]);
-decode(<< 16#26:6,Bits/bits>>, Acc) -> decode(Bits, [103|Acc]);
-decode(<< 16#27:6,Bits/bits>>, Acc) -> decode(Bits, [104|Acc]);
-decode(<< 16#6:5,Bits/bits>>, Acc) -> decode(Bits, [105|Acc]);
-decode(<< 16#74:7,Bits/bits>>, Acc) -> decode(Bits, [106|Acc]);
-decode(<< 16#75:7,Bits/bits>>, Acc) -> decode(Bits, [107|Acc]);
-decode(<< 16#28:6,Bits/bits>>, Acc) -> decode(Bits, [108|Acc]);
-decode(<< 16#29:6,Bits/bits>>, Acc) -> decode(Bits, [109|Acc]);
-decode(<< 16#2a:6,Bits/bits>>, Acc) -> decode(Bits, [110|Acc]);
-decode(<< 16#7:5,Bits/bits>>, Acc) -> decode(Bits, [111|Acc]);
-decode(<< 16#2b:6,Bits/bits>>, Acc) -> decode(Bits, [112|Acc]);
-decode(<< 16#76:7,Bits/bits>>, Acc) -> decode(Bits, [113|Acc]);
-decode(<< 16#2c:6,Bits/bits>>, Acc) -> decode(Bits, [114|Acc]);
-decode(<< 16#8:5,Bits/bits>>, Acc) -> decode(Bits, [115|Acc]);
-decode(<< 16#9:5,Bits/bits>>, Acc) -> decode(Bits, [116|Acc]);
-decode(<< 16#2d:6,Bits/bits>>, Acc) -> decode(Bits, [117|Acc]);
-decode(<< 16#77:7,Bits/bits>>, Acc) -> decode(Bits, [118|Acc]);
-decode(<< 16#78:7,Bits/bits>>, Acc) -> decode(Bits, [119|Acc]);
-decode(<< 16#79:7,Bits/bits>>, Acc) -> decode(Bits, [120|Acc]);
-decode(<< 16#7a:7,Bits/bits>>, Acc) -> decode(Bits, [121|Acc]);
-decode(<< 16#7b:7,Bits/bits>>, Acc) -> decode(Bits, [122|Acc]);
-decode(<< 16#7ffe:15,Bits/bits>>, Acc) -> decode(Bits, [123|Acc]);
-decode(<< 16#7fc:11,Bits/bits>>, Acc) -> decode(Bits, [124|Acc]);
-decode(<< 16#3ffd:14,Bits/bits>>, Acc) -> decode(Bits, [125|Acc]);
-decode(<< 16#1ffd:13,Bits/bits>>, Acc) -> decode(Bits, [126|Acc]);
-decode(<< 16#ffffffc:28,Bits/bits>>, Acc) -> decode(Bits, [127|Acc]);
-decode(<< 16#fffe6:20,Bits/bits>>, Acc) -> decode(Bits, [128|Acc]);
-decode(<< 16#3fffd2:22,Bits/bits>>, Acc) -> decode(Bits, [129|Acc]);
-decode(<< 16#fffe7:20,Bits/bits>>, Acc) -> decode(Bits, [130|Acc]);
-decode(<< 16#fffe8:20,Bits/bits>>, Acc) -> decode(Bits, [131|Acc]);
-decode(<< 16#3fffd3:22,Bits/bits>>, Acc) -> decode(Bits, [132|Acc]);
-decode(<< 16#3fffd4:22,Bits/bits>>, Acc) -> decode(Bits, [133|Acc]);
-decode(<< 16#3fffd5:22,Bits/bits>>, Acc) -> decode(Bits, [134|Acc]);
-decode(<< 16#7fffd9:23,Bits/bits>>, Acc) -> decode(Bits, [135|Acc]);
-decode(<< 16#3fffd6:22,Bits/bits>>, Acc) -> decode(Bits, [136|Acc]);
-decode(<< 16#7fffda:23,Bits/bits>>, Acc) -> decode(Bits, [137|Acc]);
-decode(<< 16#7fffdb:23,Bits/bits>>, Acc) -> decode(Bits, [138|Acc]);
-decode(<< 16#7fffdc:23,Bits/bits>>, Acc) -> decode(Bits, [139|Acc]);
-decode(<< 16#7fffdd:23,Bits/bits>>, Acc) -> decode(Bits, [140|Acc]);
-decode(<< 16#7fffde:23,Bits/bits>>, Acc) -> decode(Bits, [141|Acc]);
-decode(<< 16#ffffeb:24,Bits/bits>>, Acc) -> decode(Bits, [142|Acc]);
-decode(<< 16#7fffdf:23,Bits/bits>>, Acc) -> decode(Bits, [143|Acc]);
-decode(<< 16#ffffec:24,Bits/bits>>, Acc) -> decode(Bits, [144|Acc]);
-decode(<< 16#ffffed:24,Bits/bits>>, Acc) -> decode(Bits, [145|Acc]);
-decode(<< 16#3fffd7:22,Bits/bits>>, Acc) -> decode(Bits, [146|Acc]);
-decode(<< 16#7fffe0:23,Bits/bits>>, Acc) -> decode(Bits, [147|Acc]);
-decode(<< 16#ffffee:24,Bits/bits>>, Acc) -> decode(Bits, [148|Acc]);
-decode(<< 16#7fffe1:23,Bits/bits>>, Acc) -> decode(Bits, [149|Acc]);
-decode(<< 16#7fffe2:23,Bits/bits>>, Acc) -> decode(Bits, [150|Acc]);
-decode(<< 16#7fffe3:23,Bits/bits>>, Acc) -> decode(Bits, [151|Acc]);
-decode(<< 16#7fffe4:23,Bits/bits>>, Acc) -> decode(Bits, [152|Acc]);
-decode(<< 16#1fffdc:21,Bits/bits>>, Acc) -> decode(Bits, [153|Acc]);
-decode(<< 16#3fffd8:22,Bits/bits>>, Acc) -> decode(Bits, [154|Acc]);
-decode(<< 16#7fffe5:23,Bits/bits>>, Acc) -> decode(Bits, [155|Acc]);
-decode(<< 16#3fffd9:22,Bits/bits>>, Acc) -> decode(Bits, [156|Acc]);
-decode(<< 16#7fffe6:23,Bits/bits>>, Acc) -> decode(Bits, [157|Acc]);
-decode(<< 16#7fffe7:23,Bits/bits>>, Acc) -> decode(Bits, [158|Acc]);
-decode(<< 16#ffffef:24,Bits/bits>>, Acc) -> decode(Bits, [159|Acc]);
-decode(<< 16#3fffda:22,Bits/bits>>, Acc) -> decode(Bits, [160|Acc]);
-decode(<< 16#1fffdd:21,Bits/bits>>, Acc) -> decode(Bits, [161|Acc]);
-decode(<< 16#fffe9:20,Bits/bits>>, Acc) -> decode(Bits, [162|Acc]);
-decode(<< 16#3fffdb:22,Bits/bits>>, Acc) -> decode(Bits, [163|Acc]);
-decode(<< 16#3fffdc:22,Bits/bits>>, Acc) -> decode(Bits, [164|Acc]);
-decode(<< 16#7fffe8:23,Bits/bits>>, Acc) -> decode(Bits, [165|Acc]);
-decode(<< 16#7fffe9:23,Bits/bits>>, Acc) -> decode(Bits, [166|Acc]);
-decode(<< 16#1fffde:21,Bits/bits>>, Acc) -> decode(Bits, [167|Acc]);
-decode(<< 16#7fffea:23,Bits/bits>>, Acc) -> decode(Bits, [168|Acc]);
-decode(<< 16#3fffdd:22,Bits/bits>>, Acc) -> decode(Bits, [169|Acc]);
-decode(<< 16#3fffde:22,Bits/bits>>, Acc) -> decode(Bits, [170|Acc]);
-decode(<< 16#fffff0:24,Bits/bits>>, Acc) -> decode(Bits, [171|Acc]);
-decode(<< 16#1fffdf:21,Bits/bits>>, Acc) -> decode(Bits, [172|Acc]);
-decode(<< 16#3fffdf:22,Bits/bits>>, Acc) -> decode(Bits, [173|Acc]);
-decode(<< 16#7fffeb:23,Bits/bits>>, Acc) -> decode(Bits, [174|Acc]);
-decode(<< 16#7fffec:23,Bits/bits>>, Acc) -> decode(Bits, [175|Acc]);
-decode(<< 16#1fffe0:21,Bits/bits>>, Acc) -> decode(Bits, [176|Acc]);
-decode(<< 16#1fffe1:21,Bits/bits>>, Acc) -> decode(Bits, [177|Acc]);
-decode(<< 16#3fffe0:22,Bits/bits>>, Acc) -> decode(Bits, [178|Acc]);
-decode(<< 16#1fffe2:21,Bits/bits>>, Acc) -> decode(Bits, [179|Acc]);
-decode(<< 16#7fffed:23,Bits/bits>>, Acc) -> decode(Bits, [180|Acc]);
-decode(<< 16#3fffe1:22,Bits/bits>>, Acc) -> decode(Bits, [181|Acc]);
-decode(<< 16#7fffee:23,Bits/bits>>, Acc) -> decode(Bits, [182|Acc]);
-decode(<< 16#7fffef:23,Bits/bits>>, Acc) -> decode(Bits, [183|Acc]);
-decode(<< 16#fffea:20,Bits/bits>>, Acc) -> decode(Bits, [184|Acc]);
-decode(<< 16#3fffe2:22,Bits/bits>>, Acc) -> decode(Bits, [185|Acc]);
-decode(<< 16#3fffe3:22,Bits/bits>>, Acc) -> decode(Bits, [186|Acc]);
-decode(<< 16#3fffe4:22,Bits/bits>>, Acc) -> decode(Bits, [187|Acc]);
-decode(<< 16#7ffff0:23,Bits/bits>>, Acc) -> decode(Bits, [188|Acc]);
-decode(<< 16#3fffe5:22,Bits/bits>>, Acc) -> decode(Bits, [189|Acc]);
-decode(<< 16#3fffe6:22,Bits/bits>>, Acc) -> decode(Bits, [190|Acc]);
-decode(<< 16#7ffff1:23,Bits/bits>>, Acc) -> decode(Bits, [191|Acc]);
-decode(<< 16#3ffffe0:26,Bits/bits>>, Acc) -> decode(Bits, [192|Acc]);
-decode(<< 16#3ffffe1:26,Bits/bits>>, Acc) -> decode(Bits, [193|Acc]);
-decode(<< 16#fffeb:20,Bits/bits>>, Acc) -> decode(Bits, [194|Acc]);
-decode(<< 16#7fff1:19,Bits/bits>>, Acc) -> decode(Bits, [195|Acc]);
-decode(<< 16#3fffe7:22,Bits/bits>>, Acc) -> decode(Bits, [196|Acc]);
-decode(<< 16#7ffff2:23,Bits/bits>>, Acc) -> decode(Bits, [197|Acc]);
-decode(<< 16#3fffe8:22,Bits/bits>>, Acc) -> decode(Bits, [198|Acc]);
-decode(<< 16#1ffffec:25,Bits/bits>>, Acc) -> decode(Bits, [199|Acc]);
-decode(<< 16#3ffffe2:26,Bits/bits>>, Acc) -> decode(Bits, [200|Acc]);
-decode(<< 16#3ffffe3:26,Bits/bits>>, Acc) -> decode(Bits, [201|Acc]);
-decode(<< 16#3ffffe4:26,Bits/bits>>, Acc) -> decode(Bits, [202|Acc]);
-decode(<< 16#7ffffde:27,Bits/bits>>, Acc) -> decode(Bits, [203|Acc]);
-decode(<< 16#7ffffdf:27,Bits/bits>>, Acc) -> decode(Bits, [204|Acc]);
-decode(<< 16#3ffffe5:26,Bits/bits>>, Acc) -> decode(Bits, [205|Acc]);
-decode(<< 16#fffff1:24,Bits/bits>>, Acc) -> decode(Bits, [206|Acc]);
-decode(<< 16#1ffffed:25,Bits/bits>>, Acc) -> decode(Bits, [207|Acc]);
-decode(<< 16#7fff2:19,Bits/bits>>, Acc) -> decode(Bits, [208|Acc]);
-decode(<< 16#1fffe3:21,Bits/bits>>, Acc) -> decode(Bits, [209|Acc]);
-decode(<< 16#3ffffe6:26,Bits/bits>>, Acc) -> decode(Bits, [210|Acc]);
-decode(<< 16#7ffffe0:27,Bits/bits>>, Acc) -> decode(Bits, [211|Acc]);
-decode(<< 16#7ffffe1:27,Bits/bits>>, Acc) -> decode(Bits, [212|Acc]);
-decode(<< 16#3ffffe7:26,Bits/bits>>, Acc) -> decode(Bits, [213|Acc]);
-decode(<< 16#7ffffe2:27,Bits/bits>>, Acc) -> decode(Bits, [214|Acc]);
-decode(<< 16#fffff2:24,Bits/bits>>, Acc) -> decode(Bits, [215|Acc]);
-decode(<< 16#1fffe4:21,Bits/bits>>, Acc) -> decode(Bits, [216|Acc]);
-decode(<< 16#1fffe5:21,Bits/bits>>, Acc) -> decode(Bits, [217|Acc]);
-decode(<< 16#3ffffe8:26,Bits/bits>>, Acc) -> decode(Bits, [218|Acc]);
-decode(<< 16#3ffffe9:26,Bits/bits>>, Acc) -> decode(Bits, [219|Acc]);
-decode(<< 16#ffffffd:28,Bits/bits>>, Acc) -> decode(Bits, [220|Acc]);
-decode(<< 16#7ffffe3:27,Bits/bits>>, Acc) -> decode(Bits, [221|Acc]);
-decode(<< 16#7ffffe4:27,Bits/bits>>, Acc) -> decode(Bits, [222|Acc]);
-decode(<< 16#7ffffe5:27,Bits/bits>>, Acc) -> decode(Bits, [223|Acc]);
-decode(<< 16#fffec:20,Bits/bits>>, Acc) -> decode(Bits, [224|Acc]);
-decode(<< 16#fffff3:24,Bits/bits>>, Acc) -> decode(Bits, [225|Acc]);
-decode(<< 16#fffed:20,Bits/bits>>, Acc) -> decode(Bits, [226|Acc]);
-decode(<< 16#1fffe6:21,Bits/bits>>, Acc) -> decode(Bits, [227|Acc]);
-decode(<< 16#3fffe9:22,Bits/bits>>, Acc) -> decode(Bits, [228|Acc]);
-decode(<< 16#1fffe7:21,Bits/bits>>, Acc) -> decode(Bits, [229|Acc]);
-decode(<< 16#1fffe8:21,Bits/bits>>, Acc) -> decode(Bits, [230|Acc]);
-decode(<< 16#7ffff3:23,Bits/bits>>, Acc) -> decode(Bits, [231|Acc]);
-decode(<< 16#3fffea:22,Bits/bits>>, Acc) -> decode(Bits, [232|Acc]);
-decode(<< 16#3fffeb:22,Bits/bits>>, Acc) -> decode(Bits, [233|Acc]);
-decode(<< 16#1ffffee:25,Bits/bits>>, Acc) -> decode(Bits, [234|Acc]);
-decode(<< 16#1ffffef:25,Bits/bits>>, Acc) -> decode(Bits, [235|Acc]);
-decode(<< 16#fffff4:24,Bits/bits>>, Acc) -> decode(Bits, [236|Acc]);
-decode(<< 16#fffff5:24,Bits/bits>>, Acc) -> decode(Bits, [237|Acc]);
-decode(<< 16#3ffffea:26,Bits/bits>>, Acc) -> decode(Bits, [238|Acc]);
-decode(<< 16#7ffff4:23,Bits/bits>>, Acc) -> decode(Bits, [239|Acc]);
-decode(<< 16#3ffffeb:26,Bits/bits>>, Acc) -> decode(Bits, [240|Acc]);
-decode(<< 16#7ffffe6:27,Bits/bits>>, Acc) -> decode(Bits, [241|Acc]);
-decode(<< 16#3ffffec:26,Bits/bits>>, Acc) -> decode(Bits, [242|Acc]);
-decode(<< 16#3ffffed:26,Bits/bits>>, Acc) -> decode(Bits, [243|Acc]);
-decode(<< 16#7ffffe7:27,Bits/bits>>, Acc) -> decode(Bits, [244|Acc]);
-decode(<< 16#7ffffe8:27,Bits/bits>>, Acc) -> decode(Bits, [245|Acc]);
-decode(<< 16#7ffffe9:27,Bits/bits>>, Acc) -> decode(Bits, [246|Acc]);
-decode(<< 16#7ffffea:27,Bits/bits>>, Acc) -> decode(Bits, [247|Acc]);
-decode(<< 16#7ffffeb:27,Bits/bits>>, Acc) -> decode(Bits, [248|Acc]);
-decode(<< 16#ffffffe:28,Bits/bits>>, Acc) -> decode(Bits, [249|Acc]);
-decode(<< 16#7ffffec:27,Bits/bits>>, Acc) -> decode(Bits, [250|Acc]);
-decode(<< 16#7ffffed:27,Bits/bits>>, Acc) -> decode(Bits, [251|Acc]);
-decode(<< 16#7ffffee:27,Bits/bits>>, Acc) -> decode(Bits, [252|Acc]);
-decode(<< 16#7ffffef:27,Bits/bits>>, Acc) -> decode(Bits, [253|Acc]);
-decode(<< 16#7fffff0:27,Bits/bits>>, Acc) -> decode(Bits, [254|Acc]);
-decode(<< 16#3ffffee:26,Bits/bits>>, Acc) -> decode(Bits, [255|Acc]);
-decode(<<16#3fffffff:30,Bits/bits>>, Acc) -> decode(Bits, [256|Acc]);
-decode(_, Acc) ->
- list_to_binary(lists:reverse(Acc)).
-
-encode(Bin) -> encode(Bin, <<>>).
-encode(<< 0:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 1:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 2:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 3:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 4:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 5:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 6:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 7:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 8:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 9:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 10:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 11:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 12:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 13:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 14:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 15:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 16:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 17:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 18:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 19:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 20:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 21:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 22:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 23:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 24:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 25:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 26:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 27:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 28:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 29:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 30:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 31:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 32:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 33:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 34:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 35:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 36:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 37:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 38:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 39:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 40:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 41:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 42:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 43:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 44:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 45:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 46:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 47:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 48:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 49:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 50:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 51:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 52:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 53:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 54:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 55:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 56:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 57:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 58:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 59:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 60:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 61:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 62:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 63:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 64:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 65:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 66:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 67:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 68:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 69:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 70:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 71:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 72:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 73:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 74:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 75:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 76:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 77:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 78:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 79:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 80:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 81:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 82:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 83:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 84:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 85:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 86:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 87:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 88:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 89:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 90:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 91:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 92:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 93:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 94:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 95:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 96:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 97:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 98:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<< 99:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<100:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<101:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<102:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<103:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<104:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<105:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<106:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<107:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<108:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<109:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<110:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<111:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<112:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<113:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<114:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<115:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<116:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<117:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<118:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<119:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<120:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<121:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<122:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<123:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<124:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<125:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<126:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<127:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<128:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<129:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<130:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<131:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<132:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<133:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<134:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<135:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<136:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<137:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<138:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<139:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<140:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<141:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<142:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<143:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<144:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<145:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<146:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<147:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<148:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<149:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<150:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<151:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<152:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<153:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<154:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<155:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<156:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<157:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<158:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<159:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<160:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<161:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<162:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<163:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<164:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<165:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<166:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<167:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<168:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<169:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<170:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<171:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<172:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<173:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<174:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<175:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<176:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<177:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<178:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<179:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<180:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<181:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<182:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<183:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<184:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<185:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<186:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<187:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<188:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<189:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<190:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<191:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<192:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<193:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<194:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<195:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<196:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<197:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<198:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<199:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<200:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<201:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<202:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<203:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<204:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<205:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<206:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<207:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<208:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<209:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<210:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<211:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<212:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<213:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<214:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<215:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<216:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<217:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<218:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<219:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<220:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<221:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<222:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<223:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<224:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<225:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<226:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<227:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<228:8,T/binary>>, Acc) -> encode(T, <>);
-encode(<<229:8,T/binary>>, Acc) -> encode(T, <