From 941d408ea40022da3148b5b96483d5e8f95caaa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 23 Dec 2020 17:50:12 +0100 Subject: Initial HTTP/3 and QPACK implementation Cowlib now uses GitHub Actions for CI. As a result of this change, Cowlib is tested against OTP-24+. This commit adds initial implementations of cow_http3, cow_http3_machine and cow_qpack. Because QPACK is similar to HPACK, some encoding and decoding functions were moved to a common include file, particularly the huffman functions. The cow_http module now contains the types and functions common to all or most versions of HTTP. The types and functions specific to HTTP/1 were moved to the new cow_http1 module. Because HTTP/3 is similar to HTTP/2, part of the code processing headers is common and can be found in cow_http. Other functions common to both versions were moved out of cow_http2_machine. This commit updates comments indicating that the HTTP/2 PRIORITY mechanism will no longer be implemented. --- src/cow_http3.erl | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 src/cow_http3.erl (limited to 'src/cow_http3.erl') diff --git a/src/cow_http3.erl b/src/cow_http3.erl new file mode 100644 index 0000000..d3776ec --- /dev/null +++ b/src/cow_http3.erl @@ -0,0 +1,458 @@ +%% Copyright (c) 2023-2024, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(cow_http3). + +%% Parsing. +-export([parse/1]). +-export([parse_unidi_stream_header/1]). +-export([code_to_error/1]). + +%% Building. +-export([data/1]). +-export([headers/1]). +-export([settings/1]). +-export([error_to_code/1]). +-export([encode_int/1]). + +-type stream_id() :: non_neg_integer(). +-export_type([stream_id/0]). + +-type push_id() :: non_neg_integer(). +-export_type([push_id/0]). + +-type settings() :: #{ + qpack_max_table_capacity => 0..16#3fffffffffffffff, + max_field_section_size => 0..16#3fffffffffffffff, + qpack_blocked_streams => 0..16#3fffffffffffffff, + enable_connect_protocol => boolean() +}. +-export_type([settings/0]). + +-type error() :: h3_no_error + | h3_general_protocol_error + | h3_internal_error + | h3_stream_creation_error + | h3_closed_critical_stream + | h3_frame_unexpected + | h3_frame_error + | h3_excessive_load + | h3_id_error + | h3_settings_error + | h3_missing_settings + | h3_request_rejected + | h3_request_cancelled + | h3_request_incomplete + | h3_message_error + | h3_connect_error + | h3_version_fallback. +-export_type([error/0]). + +-type frame() :: {data, binary()} + | {headers, binary()} + | {cancel_push, push_id()} + | {settings, settings()} + | {push_promise, push_id(), binary()} + | {goaway, stream_id() | push_id()} + | {max_push_id, push_id()}. +-export_type([frame/0]). + +%% Parsing. + +-spec parse(binary()) + -> {ok, frame(), binary()} + | {more, {data, binary()} | ignore, non_neg_integer()} + | {ignore, binary()} + | {connection_error, h3_frame_error | h3_frame_unexpected | h3_settings_error, atom()} + | more. + +%% +%% DATA frames. +%% +parse(<<0, 0:2, Len:6, Data:Len/binary, Rest/bits>>) -> + {ok, {data, Data}, Rest}; +parse(<<0, 1:2, Len:14, Data:Len/binary, Rest/bits>>) -> + {ok, {data, Data}, Rest}; +parse(<<0, 2:2, Len:30, Data:Len/binary, Rest/bits>>) -> + {ok, {data, Data}, Rest}; +parse(<<0, 3:2, Len:62, Data:Len/binary, Rest/bits>>) -> + {ok, {data, Data}, Rest}; +%% DATA frames may be split over multiple QUIC packets +%% but we want to process them immediately rather than +%% risk buffering a very large payload. +parse(<<0, 0:2, Len:6, Data/bits>>) when byte_size(Data) < Len -> + {more, {data, Data}, Len - byte_size(Data)}; +parse(<<0, 1:2, Len:14, Data/bits>>) when byte_size(Data) < Len -> + {more, {data, Data}, Len - byte_size(Data)}; +parse(<<0, 2:2, Len:30, Data/bits>>) when byte_size(Data) < Len -> + {more, {data, Data}, Len - byte_size(Data)}; +parse(<<0, 3:2, Len:62, Data/bits>>) when byte_size(Data) < Len -> + {more, {data, Data}, Len - byte_size(Data)}; +%% +%% HEADERS frames. +%% +parse(<<1, 0:2, 0:6, _/bits>>) -> + {connection_error, h3_frame_error, + 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; +parse(<<1, 1:2, 0:14, _/bits>>) -> + {connection_error, h3_frame_error, + 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; +parse(<<1, 2:2, 0:30, _/bits>>) -> + {connection_error, h3_frame_error, + 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; +parse(<<1, 3:2, 0:62, _/bits>>) -> + {connection_error, h3_frame_error, + 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; +parse(<<1, 0:2, Len:6, EncodedFieldSection:Len/binary, Rest/bits>>) -> + {ok, {headers, EncodedFieldSection}, Rest}; +parse(<<1, 1:2, Len:14, EncodedFieldSection:Len/binary, Rest/bits>>) -> + {ok, {headers, EncodedFieldSection}, Rest}; +parse(<<1, 2:2, Len:30, EncodedFieldSection:Len/binary, Rest/bits>>) -> + {ok, {headers, EncodedFieldSection}, Rest}; +parse(<<1, 3:2, Len:62, EncodedFieldSection:Len/binary, Rest/bits>>) -> + {ok, {headers, EncodedFieldSection}, Rest}; +%% +%% CANCEL_PUSH frames. +%% +parse(<<3, 0:2, 1:6, 0:2, PushID:6, Rest/bits>>) -> + {ok, {cancel_push, PushID}, Rest}; +parse(<<3, 0:2, 2:6, 1:2, PushID:14, Rest/bits>>) -> + {ok, {cancel_push, PushID}, Rest}; +parse(<<3, 0:2, 4:6, 2:2, PushID:30, Rest/bits>>) -> + {ok, {cancel_push, PushID}, Rest}; +parse(<<3, 0:2, 8:6, 3:2, PushID:62, Rest/bits>>) -> + {ok, {cancel_push, PushID}, Rest}; +parse(<<3, _/bits>>) -> + {connection_error, h3_frame_error, + 'CANCEL_PUSH frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.3)'}; +%% +%% SETTINGS frames. +%% +parse(<<4, 0:2, Len:6, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_settings_id(Rest, Len, #{}); +parse(<<4, 1:2, Len:14, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_settings_id(Rest, Len, #{}); +parse(<<4, 2:2, Len:30, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_settings_id(Rest, Len, #{}); +parse(<<4, 3:2, Len:62, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_settings_id(Rest, Len, #{}); +%% +%% PUSH_PROMISE frames. +%% +parse(<<5, 0:2, Len:6, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_push_promise(Rest, Len); +parse(<<5, 1:2, Len:14, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_push_promise(Rest, Len); +parse(<<5, 2:2, Len:30, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_push_promise(Rest, Len); +parse(<<5, 3:2, Len:62, Rest/bits>>) when byte_size(Rest) >= Len -> + parse_push_promise(Rest, Len); +%% +%% GOAWAY frames. +%% +parse(<<7, 0:2, 1:6, 0:2, StreamOrPushID:6, Rest/bits>>) -> + {ok, {goaway, StreamOrPushID}, Rest}; +parse(<<7, 0:2, 2:6, 1:2, StreamOrPushID:14, Rest/bits>>) -> + {ok, {goaway, StreamOrPushID}, Rest}; +parse(<<7, 0:2, 4:6, 2:2, StreamOrPushID:30, Rest/bits>>) -> + {ok, {goaway, StreamOrPushID}, Rest}; +parse(<<7, 0:2, 8:6, 3:2, StreamOrPushID:62, Rest/bits>>) -> + {ok, {goaway, StreamOrPushID}, Rest}; +parse(<<7, 0:2, N:6, _/bits>>) when N =:= 1; N =:= 2; N =:= 4; N =:= 8 -> + more; +parse(<<7, _/bits>>) -> + {connection_error, h3_frame_error, + 'GOAWAY frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.6)'}; +%% +%% MAX_PUSH_ID frames. +%% +parse(<<13, 0:2, 1:6, 0:2, PushID:6, Rest/bits>>) -> + {ok, {max_push_id, PushID}, Rest}; +parse(<<13, 0:2, 2:6, 1:2, PushID:14, Rest/bits>>) -> + {ok, {max_push_id, PushID}, Rest}; +parse(<<13, 0:2, 4:6, 2:2, PushID:30, Rest/bits>>) -> + {ok, {max_push_id, PushID}, Rest}; +parse(<<13, 0:2, 8:6, 3:2, PushID:62, Rest/bits>>) -> + {ok, {max_push_id, PushID}, Rest}; +parse(<<13, 0:2, N:6, _/bits>>) when N =:= 1; N =:= 2; N =:= 4; N =:= 8 -> + more; +parse(<<13, _/bits>>) -> + {connection_error, h3_frame_error, + 'MAX_PUSH_ID frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.6)'}; +%% +%% HTTP/2 frame types must be rejected. +%% +parse(<<2, _/bits>>) -> + {connection_error, h3_frame_unexpected, + 'HTTP/2 PRIORITY frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; +parse(<<6, _/bits>>) -> + {connection_error, h3_frame_unexpected, + 'HTTP/2 PING frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; +parse(<<8, _/bits>>) -> + {connection_error, h3_frame_unexpected, + 'HTTP/2 WINDOW_UPDATE frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; +parse(<<9, _/bits>>) -> + {connection_error, h3_frame_unexpected, + 'HTTP/2 CONTINUATION frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; +%% +%% Unknown frames must be ignored. +parse(<<0:2, Type:6, 0:2, Len:6, Rest/bits>>) + when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> + parse_ignore(Rest, Len); +parse(<<0:2, Type:6, 1:2, Len:14, Rest/bits>>) + when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> + parse_ignore(Rest, Len); +parse(<<0:2, Type:6, 2:2, Len:30, Rest/bits>>) + when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> + parse_ignore(Rest, Len); +parse(<<0:2, Type:6, 3:2, Len:62, Rest/bits>>) + when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> + parse_ignore(Rest, Len); +parse(<<1:2, _:14, 0:2, Len:6, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<1:2, _:14, 1:2, Len:14, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<1:2, _:14, 2:2, Len:30, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<1:2, _:14, 3:2, Len:62, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<2:2, _:30, 0:2, Len:6, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<2:2, _:30, 1:2, Len:14, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<2:2, _:30, 2:2, Len:30, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<2:2, _:30, 3:2, Len:62, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<3:2, _:62, 0:2, Len:6, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<3:2, _:62, 1:2, Len:14, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<3:2, _:62, 2:2, Len:30, Rest/bits>>) -> + parse_ignore(Rest, Len); +parse(<<3:2, _:62, 3:2, Len:62, Rest/bits>>) -> + parse_ignore(Rest, Len); +%% +%% Incomplete frames for those we fully process only. +%% +parse(_) -> + more. + +parse_settings_id(Rest, 0, Settings) -> + {ok, {settings, Settings}, Rest}; +parse_settings_id(<<0:2, Identifier:6, Rest/bits>>, Len, Settings) when Len >= 1 -> + parse_settings_val(Rest, Len - 1, Settings, Identifier); +parse_settings_id(<<1:2, Identifier:14, Rest/bits>>, Len, Settings) when Len >= 2 -> + parse_settings_val(Rest, Len - 2, Settings, Identifier); +parse_settings_id(<<2:2, Identifier:30, Rest/bits>>, Len, Settings) when Len >= 4 -> + parse_settings_val(Rest, Len - 4, Settings, Identifier); +parse_settings_id(<<3:2, Identifier:62, Rest/bits>>, Len, Settings) when Len >= 8 -> + parse_settings_val(Rest, Len - 8, Settings, Identifier); +parse_settings_id(_, _, _) -> + {connection_error, h3_frame_error, + 'SETTINGS payload size exceeds the length given. (RFC9114 7.1, RFC9114 7.2.4)'}. + +parse_settings_val(<<0:2, Value:6, Rest/bits>>, Len, Settings, Identifier) when Len >= 1 -> + parse_settings_id_val(Rest, Len - 1, Settings, Identifier, Value); +parse_settings_val(<<1:2, Value:14, Rest/bits>>, Len, Settings, Identifier) when Len >= 2 -> + parse_settings_id_val(Rest, Len - 2, Settings, Identifier, Value); +parse_settings_val(<<2:2, Value:30, Rest/bits>>, Len, Settings, Identifier) when Len >= 4 -> + parse_settings_id_val(Rest, Len - 4, Settings, Identifier, Value); +parse_settings_val(<<3:2, Value:62, Rest/bits>>, Len, Settings, Identifier) when Len >= 8 -> + parse_settings_id_val(Rest, Len - 8, Settings, Identifier, Value); +parse_settings_val(_, _, _, _) -> + {connection_error, h3_frame_error, + 'SETTINGS payload size exceeds the length given. (RFC9114 7.1, RFC9114 7.2.4)'}. + +parse_settings_id_val(Rest, Len, Settings, Identifier, Value) -> + case Identifier of + %% SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC9204). + 1 -> + parse_settings_key_val(Rest, Len, Settings, qpack_max_table_capacity, Value); + %% SETTINGS_MAX_FIELD_SECTION_SIZE (RFC9114). + 6 -> + parse_settings_key_val(Rest, Len, Settings, max_field_section_size, Value); + %% SETTINGS_QPACK_BLOCKED_STREAMS (RFC9204). + 7 -> + parse_settings_key_val(Rest, Len, Settings, qpack_blocked_streams, Value); + %% SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC9220). + 8 when Value =:= 0 -> + parse_settings_key_val(Rest, Len, Settings, enable_connect_protocol, false); + 8 when Value =:= 1 -> + parse_settings_key_val(Rest, Len, Settings, enable_connect_protocol, true); + 8 -> + {connection_error, h3_settings_error, + 'The SETTINGS_ENABLE_CONNECT_PROTOCOL value MUST be 0 or 1. (RFC9220 3, RFC8441 3)'}; + _ when Identifier < 6 -> + {connection_error, h3_settings_error, + 'HTTP/2 setting not defined for HTTP/3 must be rejected. (RFC9114 7.2.4.1)'}; + %% Unknown settings must be ignored. + _ -> + parse_settings_id(Rest, Len, Settings) + end. + +parse_settings_key_val(Rest, Len, Settings, Key, Value) -> + case Settings of + #{Key := _} -> + {connection_error, h3_settings_error, + 'A duplicate setting identifier was found. (RFC9114 7.2.4)'}; + _ -> + parse_settings_id(Rest, Len, Settings#{Key => Value}) + end. + +parse_push_promise(<<0:2, PushID:6, Data/bits>>, Len) -> + <> = Data, + {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; +parse_push_promise(<<1:2, PushID:14, Data/bits>>, Len) -> + <> = Data, + {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; +parse_push_promise(<<2:2, PushID:30, Data/bits>>, Len) -> + <> = Data, + {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; +parse_push_promise(<<3:2, PushID:62, Data/bits>>, Len) -> + <> = Data, + {ok, {push_promise, PushID, EncodedFieldSection}, Rest}. + +%% Large ignored frames could lead to DoS. Users of +%% this module must limit the size of such frames. +parse_ignore(Data, Len) -> + case Data of + <<_:Len/binary, Rest/bits>> -> + {ignore, Rest}; + _ -> + {more, ignore, Len - byte_size(Data)} + end. + +-spec parse_unidi_stream_header(binary()) + -> {ok, control | push | encoder | decoder, binary()} + | {undefined, binary()}. + +parse_unidi_stream_header(<<0, Rest/bits>>) -> + {ok, control, Rest}; +parse_unidi_stream_header(<<1, Rest/bits>>) -> + {ok, push, Rest}; +parse_unidi_stream_header(<<2, Rest/bits>>) -> + {ok, encoder, Rest}; +parse_unidi_stream_header(<<3, Rest/bits>>) -> + {ok, decoder, Rest}; +parse_unidi_stream_header(<<0:2, _:6, Rest/bits>>) -> + {undefined, Rest}; +parse_unidi_stream_header(<<1:2, _:14, Rest/bits>>) -> + {undefined, Rest}; +parse_unidi_stream_header(<<2:2, _:30, Rest/bits>>) -> + {undefined, Rest}; +parse_unidi_stream_header(<<3:2, _:62, Rest/bits>>) -> + {undefined, Rest}. + +-spec code_to_error(non_neg_integer()) -> error(). + +code_to_error(16#0100) -> h3_no_error; +code_to_error(16#0101) -> h3_general_protocol_error; +code_to_error(16#0102) -> h3_internal_error; +code_to_error(16#0103) -> h3_stream_creation_error; +code_to_error(16#0104) -> h3_closed_critical_stream; +code_to_error(16#0105) -> h3_frame_unexpected; +code_to_error(16#0106) -> h3_frame_error; +code_to_error(16#0107) -> h3_excessive_load; +code_to_error(16#0108) -> h3_id_error; +code_to_error(16#0109) -> h3_settings_error; +code_to_error(16#010a) -> h3_missing_settings; +code_to_error(16#010b) -> h3_request_rejected; +code_to_error(16#010c) -> h3_request_cancelled; +code_to_error(16#010d) -> h3_request_incomplete; +code_to_error(16#010e) -> h3_message_error; +code_to_error(16#010f) -> h3_connect_error; +code_to_error(16#0110) -> h3_version_fallback; +%% Unknown/reserved error codes must be treated +%% as equivalent to H3_NO_ERROR. +code_to_error(_) -> h3_no_error. + +%% Building. + +-spec data(iodata()) -> iolist(). + +data(Data) -> + Len = encode_int(iolist_size(Data)), + [<<0:8>>, Len, Data]. + +-spec headers(iodata()) -> iolist(). + +headers(HeaderBlock) -> + Len = encode_int(iolist_size(HeaderBlock)), + [<<1:8>>, Len, HeaderBlock]. + +-spec settings(settings()) -> iolist(). + +settings(Settings) when Settings =:= #{} -> + <<4:8, 0:8>>; +settings(Settings) -> + Payload = settings_payload(Settings), + Len = encode_int(iolist_size(Payload)), + [<<4:8>>, Len, Payload]. + +settings_payload(Settings) -> + Payload = [case Key of + %% SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC9204). + qpack_max_table_capacity when Value =:= 0 -> <<>>; + qpack_max_table_capacity -> [encode_int(1), encode_int(Value)]; + %% SETTINGS_MAX_FIELD_SECTION_SIZE (RFC9114). + max_header_list_size when Value =:= infinity -> <<>>; + max_header_list_size -> [encode_int(6), encode_int(Value)]; + %% SETTINGS_QPACK_BLOCKED_STREAMS (RFC9204). + qpack_blocked_streams when Value =:= 0 -> <<>>; + qpack_blocked_streams -> [encode_int(1), encode_int(Value)]; + %% SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC9220). + enable_connect_protocol when Value -> [encode_int(8), encode_int(1)]; + enable_connect_protocol -> [encode_int(8), encode_int(0)] + end || {Key, Value} <- maps:to_list(Settings)], + %% Include one reserved identifier in addition. + ReservedType = 16#1f * (rand:uniform(148764065110560900) - 1) + 16#21, + [encode_int(ReservedType), encode_int(rand:uniform(15384) - 1)|Payload]. + +-spec error_to_code(error()) -> non_neg_integer(). + +error_to_code(h3_no_error) -> + %% Implementations should select a reserved error code + %% with some probability when they would have sent H3_NO_ERROR. (RFC9114 8.1) + case rand:uniform(2) of + 1 -> 16#0100; + 2 -> 16#1f * (rand:uniform(148764065110560900) - 1) + 16#21 + end; +error_to_code(h3_general_protocol_error) -> 16#0101; +error_to_code(h3_internal_error) -> 16#0102; +error_to_code(h3_stream_creation_error) -> 16#0103; +error_to_code(h3_closed_critical_stream) -> 16#0104; +error_to_code(h3_frame_unexpected) -> 16#0105; +error_to_code(h3_frame_error) -> 16#0106; +error_to_code(h3_excessive_load) -> 16#0107; +error_to_code(h3_id_error) -> 16#0108; +error_to_code(h3_settings_error) -> 16#0109; +error_to_code(h3_missing_settings) -> 16#010a; +error_to_code(h3_request_rejected) -> 16#010b; +error_to_code(h3_request_cancelled) -> 16#010c; +error_to_code(h3_request_incomplete) -> 16#010d; +error_to_code(h3_message_error) -> 16#010e; +error_to_code(h3_connect_error) -> 16#010f; +error_to_code(h3_version_fallback) -> 16#0110. + +-spec encode_int(0..16#3fffffffffffffff) -> binary(). + +encode_int(I) when I < 64 -> + <<0:2, I:6>>; +encode_int(I) when I < 16384 -> + <<1:2, I:14>>; +encode_int(I) when I < 1073741824 -> + <<2:2, I:30>>; +encode_int(I) when I < 4611686018427387904 -> + <<3:2, I:62>>. -- cgit v1.2.3