diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/r3_hex_api.erl | 120 | ||||
| -rw-r--r-- | src/r3_hex_api_key.erl | 31 | ||||
| -rw-r--r-- | src/r3_hex_api_package.erl | 49 | ||||
| -rw-r--r-- | src/r3_hex_api_package_owner.erl | 34 | ||||
| -rw-r--r-- | src/r3_hex_api_release.erl | 60 | ||||
| -rw-r--r-- | src/r3_hex_api_user.erl | 46 | ||||
| -rw-r--r-- | src/r3_hex_core.erl | 92 | ||||
| -rw-r--r-- | src/r3_hex_core.hrl | 3 | ||||
| -rw-r--r-- | src/r3_hex_erl_tar.erl | 1966 | ||||
| -rw-r--r-- | src/r3_hex_erl_tar.hrl | 405 | ||||
| -rw-r--r-- | src/r3_hex_filename.erl | 60 | ||||
| -rw-r--r-- | src/r3_hex_http.erl | 43 | ||||
| -rw-r--r-- | src/r3_hex_http_httpc.erl | 39 | ||||
| -rw-r--r-- | src/r3_hex_pb_names.erl | 735 | ||||
| -rw-r--r-- | src/r3_hex_pb_package.erl | 1699 | ||||
| -rw-r--r-- | src/r3_hex_pb_signed.erl | 564 | ||||
| -rw-r--r-- | src/r3_hex_pb_versions.erl | 958 | ||||
| -rw-r--r-- | src/r3_hex_registry.erl | 133 | ||||
| -rw-r--r-- | src/r3_hex_repo.erl | 174 | ||||
| -rw-r--r-- | src/r3_hex_tarball.erl | 507 | ||||
| -rw-r--r-- | src/r3_safe_erl_term.erl | 678 | ||||
| -rw-r--r-- | src/r3_safe_erl_term.xrl | 79 | ||||
| -rw-r--r-- | src/rebar.app.src | 1 | ||||
| -rw-r--r-- | src/rebar_hex_repos.erl | 27 | ||||
| -rw-r--r-- | src/rebar_packages.erl | 8 | ||||
| -rw-r--r-- | src/rebar_pkg_resource.erl | 6 | ||||
| -rw-r--r-- | src/rebar_utils.erl | 2 | 
27 files changed, 8498 insertions, 21 deletions
| diff --git a/src/r3_hex_api.erl b/src/r3_hex_api.erl new file mode 100644 index 0000000..09d9bcf --- /dev/null +++ b/src/r3_hex_api.erl @@ -0,0 +1,120 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @hidden + +-module(r3_hex_api). + +-export([ +    delete/2, +    get/2, +    post/3, +    put/3, +    encode_query_string/1, +    build_repository_path/2, +    build_organization_path/2, +    join_path_segments/1 +]). +-define(ERL_CONTENT_TYPE, <<"application/vnd.hex+erlang">>). + +get(Config, Path) -> +    request(Config, get, Path, undefined). + +post(Config, Path, Body) -> +    request(Config, post, Path, encode_body(Body)). + +put(Config, Path, Body) -> +    request(Config, put, Path, encode_body(Body)). + +delete(Config, Path) -> +    request(Config, delete, Path, undefined). + +%% @private +encode_query_string(List) -> +    QueryString = +        join("&", +            lists:map(fun +                ({K, V}) when is_atom(V) -> +                    atom_to_list(K) ++ "=" ++ atom_to_list(V); +                ({K, V}) when is_binary(V) -> +                    atom_to_list(K) ++ "=" ++ binary_to_list(V); +                ({K, V}) when is_integer(V) -> +                    atom_to_list(K) ++ "=" ++ integer_to_list(V) +            end, List)), +    Encoded = http_uri:encode(QueryString), +    list_to_binary(Encoded). + +%% @private +build_repository_path(#{api_repository := Repo}, Path) when is_binary(Repo) -> +    ["repos", Repo | Path]; +build_repository_path(#{api_repository := undefined}, Path) -> +    Path. + +%% @private +build_organization_path(#{api_organization := Org}, Path) when is_binary(Org) -> +    ["orgs", Org | Path]; +build_organization_path(#{api_organization := undefined}, Path) -> +    Path. + +%% @private +join_path_segments(Segments) -> +    erlang:iolist_to_binary(join(<<"/">>, lists:map(fun encode/1, Segments))). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +request(Config, Method, PathSegments, Body) when is_list(PathSegments) -> +    Path = join_path_segments(PathSegments), +    request(Config, Method, Path, Body); +request(Config, Method, Path, Body) when is_binary(Path) and is_map(Config) -> +    DefaultHeaders = make_headers(Config), +    ReqHeaders = maps:merge(maps:get(http_headers, Config, #{}), DefaultHeaders), +    ReqHeaders2 = put_new(<<"accept">>, ?ERL_CONTENT_TYPE, ReqHeaders), + +    case r3_hex_http:request(Config, Method, build_url(Path, Config), ReqHeaders2, Body) of +        {ok, {Status, RespHeaders, RespBody}} = Response -> +            ContentType = maps:get(<<"content-type">>, RespHeaders, <<"">>), +            case binary:match(ContentType, ?ERL_CONTENT_TYPE) of +                {_, _} -> +                    {ok, {Status, RespHeaders, binary_to_term(RespBody)}}; + +                nomatch -> +                    Response +            end; + +        Other -> +            Other +    end. + +encode(Binary) when is_binary(Binary) -> +    encode(binary_to_list(Binary)); +encode(String) when is_list(String) -> +    http_uri:encode(String). + +build_url(Path, #{api_url := URI}) -> +    <<URI/binary, "/", Path/binary>>. + +encode_body({_ContentType, _Body} = Body) -> +    Body; +encode_body(Body) -> +    {binary_to_list(?ERL_CONTENT_TYPE), term_to_binary(Body)}. + +%% TODO: copy-pasted from r3_hex_repo +make_headers(Config) -> +    maps:fold(fun set_header/3, #{}, Config). + +set_header(api_key, Token, Headers) when is_binary(Token) -> maps:put(<<"authorization">>, Token, Headers); +set_header(_, _, Headers) -> Headers. + +put_new(Key, Value, Map) -> +    case maps:find(Key, Map) of +        {ok, _} -> Map; +        error -> maps:put(Key, Value, Map) +    end. + +%% https://github.com/erlang/otp/blob/OTP-20.3/lib/stdlib/src/lists.erl#L1449:L1453 +join(_Sep, []) -> []; +join(Sep, [H|T]) -> [H|join_prepend(Sep, T)]. + +join_prepend(_Sep, []) -> []; +join_prepend(Sep, [H|T]) -> [Sep,H|join_prepend(Sep,T)]. diff --git a/src/r3_hex_api_key.erl b/src/r3_hex_api_key.erl new file mode 100644 index 0000000..68c0b13 --- /dev/null +++ b/src/r3_hex_api_key.erl @@ -0,0 +1,31 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_key). +-export([ +    list/1, +    get/2, +    add/3, +    delete/2, +    delete_all/1 +]). + +list(Config) when is_map(Config) -> +    Path = r3_hex_api:build_organization_path(Config, ["keys"]), +    r3_hex_api:get(Config, Path). + +get(Config, Name) when is_map(Config) -> +    Path = r3_hex_api:build_organization_path(Config, ["keys", Name]), +    r3_hex_api:get(Config, Path). + +add(Config, Name, Permissions) when is_map(Config) -> +    Path = r3_hex_api:build_organization_path(Config, ["keys"]), +    Params = #{<<"name">> => Name, <<"permissions">> => Permissions}, +    r3_hex_api:post(Config, Path, Params). + +delete(Config, Name) when is_map(Config) -> +    Path = r3_hex_api:build_organization_path(Config, ["keys", Name]), +    r3_hex_api:delete(Config, Path). + +delete_all(Config) when is_map(Config) -> +    Path = r3_hex_api:build_organization_path(Config, ["keys"]), +    r3_hex_api:delete(Config, Path). diff --git a/src/r3_hex_api_package.erl b/src/r3_hex_api_package.erl new file mode 100644 index 0000000..903adc0 --- /dev/null +++ b/src/r3_hex_api_package.erl @@ -0,0 +1,49 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_package). +-export([get/2, search/3]). + +%% @doc +%% Gets package. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_package:get(r3_hex_core:default_config(), <<"package">>). +%% {ok, {200, ..., #{ +%%     <<"name">> => <<"package1">>, +%%     <<"meta">> => #{ +%%         <<"description">> => ..., +%%         <<"licenses">> => ..., +%%         <<"links">> => ..., +%%         <<"maintainers">> => ... +%%     }, +%%     ..., +%%     <<"releases">> => [ +%%         #{<<"url">> => ..., <<"version">> => <<"0.5.0">>}], +%%         #{<<"url">> => ..., <<"version">> => <<"1.0.0">>}], +%%         ... +%%     ]}}} +%% ''' +%% @end +get(Config, Name) when is_binary(Name) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", Name]), +    r3_hex_api:get(Config, Path). + +%% @doc +%% Searches packages. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_package:search(r3_hex_core:default_config(), <<"package">>, []). +%% {ok, {200, ..., [ +%%     #{<<"name">> => <<"package1">>, ...}, +%%     ... +%% ]}} +%% ''' +search(Config, Query, SearchParams) when is_binary(Query) and is_list(SearchParams) and is_map(Config) -> +    QueryString = r3_hex_api:encode_query_string([{search, Query} | SearchParams]), +    Path = r3_hex_api:join_path_segments(r3_hex_api:build_repository_path(Config, ["packages"])), +    PathQuery = <<Path/binary, "?", QueryString/binary>>, +    r3_hex_api:get(Config, PathQuery). diff --git a/src/r3_hex_api_package_owner.erl b/src/r3_hex_api_package_owner.erl new file mode 100644 index 0000000..3a83b43 --- /dev/null +++ b/src/r3_hex_api_package_owner.erl @@ -0,0 +1,34 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_package_owner). +-export([ +    add/3, +    delete/3, +    get/3, +    list/2 +]). + +%% Examples: +%% +%% ``` +%% > r3_hex_api_owner:list(r3_hex_core:default_config(), <<"package">>). +%% {ok, {200, ..., [ +%%     #{<<"username">> => <<"alice">>, ...}, +%%     ... +%% ]}} +%% ''' +list(Config, PackageName) when is_binary(PackageName) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners"]), +    r3_hex_api:get(Config, Path). + +get(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), +    r3_hex_api:get(Config, Path). + +add(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), +    r3_hex_api:put(Config, Path, #{}). + +delete(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), +    r3_hex_api:delete(Config, Path). diff --git a/src/r3_hex_api_release.erl b/src/r3_hex_api_release.erl new file mode 100644 index 0000000..4acda0e --- /dev/null +++ b/src/r3_hex_api_release.erl @@ -0,0 +1,60 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_release). +-export([ +    delete/3, +    get/3, +    publish/2, +    retire/4, +    unretire/3 +]). + +%% @doc +%% Gets package release. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api:get_release(<<"package">>, <<"1.0.0">>, r3_hex_core:default_config()). +%% {ok, {200, ..., #{ +%%     <<"version">> => <<"1.0.0">>, +%%     <<"meta">> => #{ +%%         <<"description">> => ..., +%%         <<"licenses">> => ..., +%%         <<"links">> => ..., +%%         <<"maintainers">> => ... +%%     }, +%%     ...}}} +%% ''' +%% @end +get(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version]), +    r3_hex_api:get(Config, Path). + +publish(Config, Tarball) when is_binary(Tarball) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["publish"]), +    TarballContentType = "application/octet-stream", +    Config2 = put_header(<<"content-length">>, integer_to_binary(byte_size(Tarball)), Config), +    Body = {TarballContentType, Tarball}, +    r3_hex_api:post(Config2, Path, Body). + +delete(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version]), +    r3_hex_api:delete(Config, Path). + +retire(Config, Name, Version, Params) when is_binary(Name) and is_binary(Version) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version, "retire"]), +    r3_hex_api:post(Config, Path, Params). + +unretire(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> +    Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version, "retire"]), +    r3_hex_api:delete(Config, Path). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +put_header(Name, Value, Config) -> +    Headers = maps:get(http_headers, Config, #{}), +    Headers2 = maps:put(Name, Value, Headers), +    maps:put(http_headers, Headers2, Config). diff --git a/src/r3_hex_api_user.erl b/src/r3_hex_api_user.erl new file mode 100644 index 0000000..67d32ce --- /dev/null +++ b/src/r3_hex_api_user.erl @@ -0,0 +1,46 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_user). +-export([ +    create/4, +    get/2, +    me/1, +    reset_password/2 +]). + +me(Config) when is_map(Config) -> +    r3_hex_api:get(Config, ["users", "me"]). + +create(Config, Username, Password, Email) -> +    Params = #{ +      <<"username">> => Username, +      <<"password">> => Password, +      <<"email">> => Email +    }, +    r3_hex_api:post(Config, ["users"], Params). + +reset_password(Username, Config) when is_binary(Username) and is_map(Config) -> +    r3_hex_api:post(Config, ["users", Username, "reset"], #{}). + +%% @doc +%% Gets user. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_user:get(<<"user">>, r3_hex_core:default_config()). +%% {ok, {200, ..., #{ +%%      <<"username">> => <<"user">>, +%%      <<"packages">> => [ +%%          #{ +%%              <<"name">> => ..., +%%              <<"url">> => ..., +%%              ... +%%          }, +%%          ... +%%      ], +%%      ...}}} +%% ''' +%% @end +get(Config, Username) when is_binary(Username) and is_map(Config) -> +    r3_hex_api:get(Config, ["users", Username]). diff --git a/src/r3_hex_core.erl b/src/r3_hex_core.erl new file mode 100644 index 0000000..84eabe6 --- /dev/null +++ b/src/r3_hex_core.erl @@ -0,0 +1,92 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @doc +%% hex_core entrypoint module. +%% +%% ### Config +%% +%% Most functions in the hex_core API takes a configuration. The configuration sets things +%% like HTTP client to use, and API and repository URL. Some of these configuration options +%% will likely be static for your application and some may change depending on the function +%% you call. +%% +%% ##### Options +%% +%% * `api_key' - Authentication key used when accessing the HTTP API. +%% * `api_organization' - Name of the organization endpoint in the API, this should +%%   for example be set when accessing key for a specific organization. +%% * `api_repository' - Name of the repository endpoint in the API, this should +%%   for example be set when accessing packages from a specific repository. +%% * `api_url' - URL to the HTTP API (default: `https://hex.pm/api'). +%% * `http_adapter' - Callback module used for HTTP requests, see [`r3_hex_http'](r3_hex_http.html) +%%   (default: `r3_hex_http_httpc'). +%% * `http_etag' - Sets the `if-none-match' HTTP header with the given value to do a +%%   conditional HTTP request. +%% * `http_adapter_config' - Configuration to pass to the HTTP adapter. +%% * `http_user_agent_fragment' - Will be appended to the `user-agent` HTTP header (default: `(httpc)'). +%% * `repo_key' - Authentication key used when accessing the repository. +%% * `repo_name' - Name of the repository, used for verifying the repository signature +%%   authenticity (default: `hexpm'). +%% * `repo_public_key' - Public key used to verify the repository signature +%%   (defaults to hexpm public key `https://hex.pm/docs/public_keys'). +%% * `repo_url' - URL to the repository (default: `https://repo.hex.pm'). +%% * `repo_organization' - Name of the organization repository, appends `/repos/:name' +%%    to the repository URL and overrides the `repo_name' option. +%% * `repo_verify' - If `true' will verify the repository signature (default: `true'). +%% * `repo_verify_origin' - If `true' will verify the repository signature origin, +%%   requires protobuf messages as of hex_core v0.4.0 (default: `true'). + +-module(r3_hex_core). +-export([default_config/0]). + +-export_type([config/0]). + +%% https://hex.pm/docs/public_keys +-define(HEXPM_PUBLIC_KEY, <<"-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApqREcFDt5vV21JVe2QNB +Edvzk6w36aNFhVGWN5toNJRjRJ6m4hIuG4KaXtDWVLjnvct6MYMfqhC79HAGwyF+ +IqR6Q6a5bbFSsImgBJwz1oadoVKD6ZNetAuCIK84cjMrEFRkELtEIPNHblCzUkkM +3rS9+DPlnfG8hBvGi6tvQIuZmXGCxF/73hU0/MyGhbmEjIKRtG6b0sJYKelRLTPW +XgK7s5pESgiwf2YC/2MGDXjAJfpfCd0RpLdvd4eRiXtVlE9qO9bND94E7PgQ/xqZ +J1i2xWFndWa6nfFnRxZmCStCOZWYYPlaxr+FZceFbpMwzTNs4g3d4tLNUcbKAIH4 +0wIDAQAB +-----END PUBLIC KEY-----">>). + + +-type config() :: #{ +    api_key => binary() | undefined, +    api_organization => binary() | undefined, +    api_repository => binary() | undefined, +    api_url => binary(), +    http_adapter => module(), +    http_etag => binary() | undefined, +    http_adapter_config => map(), +    http_user_agent_fragment => binary(), +    repo_key => binary() | undefined, +    repo_name => binary(), +    repo_public_key => binary(), +    repo_url => binary(), +    repo_organization => binary() | undefined, +    repo_verify => boolean(), +    repo_verify_origin => boolean() +}. + +-spec default_config() -> config(). +default_config() -> +    #{ +        api_key => undefined, +        api_organization => undefined, +        api_repository => undefined, +        api_url => <<"https://hex.pm/api">>, +        http_adapter => r3_hex_http_httpc, +        http_adapter_config => #{profile => default}, +        http_etag => undefined, +        http_user_agent_fragment => <<"(httpc)">>, +        repo_key => undefined, +        repo_name => <<"hexpm">>, +        repo_public_key => ?HEXPM_PUBLIC_KEY, +        repo_url => <<"https://repo.hex.pm">>, +        repo_organization => undefined, +        repo_verify => true, +        repo_verify_origin => true +    }. diff --git a/src/r3_hex_core.hrl b/src/r3_hex_core.hrl new file mode 100644 index 0000000..31c2bf9 --- /dev/null +++ b/src/r3_hex_core.hrl @@ -0,0 +1,3 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-define(HEX_CORE_VERSION, "0.5.0"). diff --git a/src/r3_hex_erl_tar.erl b/src/r3_hex_erl_tar.erl new file mode 100644 index 0000000..a698e43 --- /dev/null +++ b/src/r3_hex_erl_tar.erl @@ -0,0 +1,1966 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @private +%% Copied from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/erl_tar.erl +%% with modifications: +%% - Change module name to `r3_hex_erl_tar` +%% - Set tar mtimes to 0 and remove dependency on :os.system_time/1 +%% - Preserve modes when building tarball +%% - Do not crash if failing to write tar +%% - Allow setting file_info opts on :r3_hex_erl_tar.add + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%%     http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% This module implements extraction/creation of tar archives. +%% It supports reading most common tar formats, namely V7, STAR, +%% USTAR, GNU, BSD/libarchive, and PAX. It produces archives in USTAR +%% format, unless it must use PAX headers, in which case it produces PAX +%% format. +%% +%% The following references where used: +%%   http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5 +%%   http://www.gnu.org/software/tar/manual/html_node/Standard.html +%%   http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html +-module(r3_hex_erl_tar). + +-export([init/3, +         create/2, create/3, +         extract/1, extract/2, +         table/1, table/2, t/1, tt/1, +         open/2, close/1, +         add/3, add/4, add/5, +         format_error/1]). + +-include_lib("kernel/include/file.hrl"). +-include_lib("r3_hex_erl_tar.hrl"). + +%% Converts the short error reason to a descriptive string. +-spec format_error(term()) -> string(). +format_error(invalid_tar_checksum) -> +    "Checksum failed"; +format_error(bad_header) -> +    "Unrecognized tar header format"; +format_error({bad_header, Reason}) -> +    lists:flatten(io_lib:format("Unrecognized tar header format: ~p", [Reason])); +format_error({invalid_header, negative_size}) -> +    "Invalid header: negative size"; +format_error(invalid_sparse_header_size) -> +    "Invalid sparse header: negative size"; +format_error(invalid_sparse_map_entry) -> +    "Invalid sparse map entry"; +format_error({invalid_sparse_map_entry, Reason}) -> +    lists:flatten(io_lib:format("Invalid sparse map entry: ~p", [Reason])); +format_error(invalid_end_of_archive) -> +    "Invalid end of archive"; +format_error(eof) -> +    "Unexpected end of file"; +format_error(integer_overflow) -> +    "Failed to parse numeric: integer overflow"; +format_error({misaligned_read, Pos}) -> +    lists:flatten(io_lib:format("Read a block which was misaligned: block_size=~p pos=~p", +                                [?BLOCK_SIZE, Pos])); +format_error(invalid_gnu_1_0_sparsemap) -> +    "Invalid GNU sparse map (version 1.0)"; +format_error({invalid_gnu_0_1_sparsemap, Format}) -> +    lists:flatten(io_lib:format("Invalid GNU sparse map (version ~s)", [Format])); +format_error(unsafe_path) -> +    "The path points above the current working directory"; +format_error({Name,Reason}) -> +    lists:flatten(io_lib:format("~ts: ~ts", [Name,format_error(Reason)])); +format_error(Atom) when is_atom(Atom) -> +    file:format_error(Atom); +format_error(Term) -> +    lists:flatten(io_lib:format("~tp", [Term])). + +%% Initializes a new reader given a custom file handle and I/O wrappers +-spec init(handle(), write | read, file_op()) -> {ok, reader()} | {error, badarg}. +init(Handle, AccessMode, Fun) when is_function(Fun, 2) -> +    Reader = #reader{handle=Handle,access=AccessMode,func=Fun}, +    {ok, Pos, Reader2} = do_position(Reader, {cur, 0}), +    {ok, Reader2#reader{pos=Pos}}; +init(_Handle, _AccessMode, _Fun) -> +    {error, badarg}. + +%%%================================================================ +%% Extracts all files from the tar file Name. +-spec extract(open_handle()) -> ok | {error, term()}. +extract(Name) -> +    extract(Name, []). + +%% Extracts (all) files from the tar file Name. +%% Options accepted: +%%  - cooked: Opens the tar file without mode `raw` +%%  - compressed: Uncompresses the tar file when reading +%%  - memory: Returns the tar contents as a list of tuples {Name, Bin} +%%  - keep_old_files: Extracted files will not overwrite the destination +%%  - {files, ListOfFilesToExtract}: Only extract ListOfFilesToExtract +%%  - verbose: Prints verbose information about the extraction, +%%  - {cwd, AbsoluteDir}: Sets the current working directory for the extraction +-spec extract(open_handle(), [extract_opt()]) -> +                     ok +                         | {ok, [{string(), binary()}]} +                         | {error, term()}. +extract({binary, Bin}, Opts) when is_list(Opts) -> +    do_extract({binary, Bin}, Opts); +extract({file, Fd}, Opts) when is_list(Opts) -> +    do_extract({file, Fd}, Opts); +extract(#reader{}=Reader, Opts) when is_list(Opts) -> +    do_extract(Reader, Opts); +extract(Name, Opts) when is_list(Name); is_binary(Name), is_list(Opts) -> +    do_extract(Name, Opts). + +do_extract(Handle, Opts) when is_list(Opts) -> +    Opts2 = extract_opts(Opts), +    Acc = if Opts2#read_opts.output =:= memory -> []; true -> ok end, +    foldl_read(Handle, fun extract1/4, Acc, Opts2). + +extract1(eof, Reader, _, Acc) when is_list(Acc) -> +    {ok, {ok, lists:reverse(Acc)}, Reader}; +extract1(eof, Reader, _, leading_slash) -> +    error_logger:info_msg("erl_tar: removed leading '/' from member names\n"), +    {ok, ok, Reader}; +extract1(eof, Reader, _, Acc) -> +    {ok, Acc, Reader}; +extract1(#tar_header{name=Name,size=Size}=Header, Reader0, Opts, Acc0) -> +    case check_extract(Name, Opts) of +        true -> +            case do_read(Reader0, Size) of +                {ok, Bin, Reader1} -> +                    Acc = extract2(Header, Bin, Opts, Acc0), +                    {ok, Acc, Reader1}; +                {error, _} = Err -> +                    throw(Err) +            end; +        false -> +            {ok, Acc0, skip_file(Reader0)} +    end. + +extract2(Header, Bin, Opts, Acc) -> +    case write_extracted_element(Header, Bin, Opts) of +        ok -> +            case Header of +                #tar_header{name="/"++_} -> +                    leading_slash; +                #tar_header{} -> +                    Acc +            end; +        {ok, NameBin} when is_list(Acc) -> +            [NameBin | Acc]; +        {error, _} = Err -> +            throw(Err) +    end. + +%% Checks if the file Name should be extracted. +check_extract(_, #read_opts{files=all}) -> +    true; +check_extract(Name, #read_opts{files=Files}) -> +    ordsets:is_element(Name, Files). + +%%%================================================================ +%% The following table functions produce a list of information about +%% the files contained in the archive. +-type filename() :: string(). +-type typeflag() :: regular | link | symlink | +                    char | block | directory | +                    fifo | reserved | unknown. +-type mode() :: non_neg_integer(). +-type uid() :: non_neg_integer(). +-type gid() :: non_neg_integer(). + +-type tar_entry() :: {filename(), +                      typeflag(), +                      non_neg_integer(), +                      tar_time(), +                      mode(), +                      uid(), +                      gid()}. + +%% Returns a list of names of the files in the tar file Name. +-spec table(open_handle()) -> {ok, [string()]} | {error, term()}. +table(Name) -> +    table(Name, []). + +%% Returns a list of names of the files in the tar file Name. +%% Options accepted: compressed, verbose, cooked. +-spec table(open_handle(), [compressed | verbose | cooked]) -> +                   {ok, [tar_entry()]} | {error, term()}. +table(Name, Opts) when is_list(Opts) -> +    foldl_read(Name, fun table1/4, [], table_opts(Opts)). + +table1(eof, Reader, _, Result) -> +    {ok, {ok, lists:reverse(Result)}, Reader}; +table1(#tar_header{}=Header, Reader, #read_opts{verbose=Verbose}, Result) -> +    Attrs = table1_attrs(Header, Verbose), +    Reader2 = skip_file(Reader), +    {ok, [Attrs|Result], Reader2}. + +%% Extracts attributes relevant to table1's output +table1_attrs(#tar_header{typeflag=Typeflag,mode=Mode}=Header, true) -> +    Type = typeflag(Typeflag), +    Name = Header#tar_header.name, +    Mtime = Header#tar_header.mtime, +    Uid = Header#tar_header.uid, +    Gid = Header#tar_header.gid, +    Size = Header#tar_header.size, +    {Name, Type, Size, Mtime, Mode, Uid, Gid}; +table1_attrs(#tar_header{name=Name}, _Verbose) -> +    Name. + +typeflag(?TYPE_REGULAR) -> regular; +typeflag(?TYPE_REGULAR_A) -> regular; +typeflag(?TYPE_GNU_SPARSE) -> regular; +typeflag(?TYPE_CONT) -> regular; +typeflag(?TYPE_LINK) -> link; +typeflag(?TYPE_SYMLINK) -> symlink; +typeflag(?TYPE_CHAR) -> char; +typeflag(?TYPE_BLOCK) -> block; +typeflag(?TYPE_DIR) -> directory; +typeflag(?TYPE_FIFO) -> fifo; +typeflag(_) -> unknown. + +%%%================================================================ +%% Comments for printing the contents of a tape archive, +%% meant to be invoked from the shell. + +%% Prints each filename in the archive +-spec t(file:filename()) -> ok | {error, term()}. +t(Name) when is_list(Name); is_binary(Name) -> +    case table(Name) of +        {ok, List} -> +            lists:foreach(fun(N) -> ok = io:format("~ts\n", [N]) end, List); +        Error -> +            Error +    end. + +%% Prints verbose information about each file in the archive +-spec tt(open_handle()) -> ok | {error, term()}. +tt(Name) -> +    case table(Name, [verbose]) of +        {ok, List} -> +            lists:foreach(fun print_header/1, List); +        Error -> +            Error +    end. + +%% Used by tt/1 to print a tar_entry tuple +-spec print_header(tar_entry()) -> ok. +print_header({Name, Type, Size, Mtime, Mode, Uid, Gid}) -> +    io:format("~s~s ~4w/~-4w ~7w ~s ~s\n", +              [type_to_string(Type), mode_to_string(Mode), +               Uid, Gid, Size, time_to_string(Mtime), Name]). + +type_to_string(regular)   -> "-"; +type_to_string(directory) -> "d"; +type_to_string(link)      -> "l"; +type_to_string(symlink)   -> "s"; +type_to_string(char)      -> "c"; +type_to_string(block)     -> "b"; +type_to_string(fifo)      -> "f"; +type_to_string(unknown)   -> "?". + +%% Converts a numeric mode to its human-readable representation +mode_to_string(Mode) -> +    mode_to_string(Mode, "xwrxwrxwr", []). +mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 -> +    mode_to_string(Mode bsr 1, T, [C|Acc]); +mode_to_string(Mode, [_|T], Acc) -> +    mode_to_string(Mode bsr 1, T, [$-|Acc]); +mode_to_string(_, [], Acc) -> +    Acc. + +%% Converts a tar_time() (POSIX time) to a readable string +time_to_string(Secs0) -> +    Epoch = calendar:datetime_to_gregorian_seconds(?EPOCH), +    Secs = Epoch + Secs0, +    DateTime0 = calendar:gregorian_seconds_to_datetime(Secs), +    DateTime = calendar:universal_time_to_local_time(DateTime0), +    {{Y, Mon, Day}, {H, Min, _}} = DateTime, +    io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]). + +two_d(N) -> +    tl(integer_to_list(N + 100)). + +month(1) -> "Jan"; +month(2) -> "Feb"; +month(3) -> "Mar"; +month(4) -> "Apr"; +month(5) -> "May"; +month(6) -> "Jun"; +month(7) -> "Jul"; +month(8) -> "Aug"; +month(9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +%%%================================================================ +%% The open function with friends is to keep the file and binary api of this module +-type open_handle() :: file:filename() +                     | {binary, binary()} +                     | {file, term()}. +-spec open(open_handle(), [write | compressed | cooked]) -> +                  {ok, reader()} | {error, term()}. +open({binary, Bin}, Mode) when is_binary(Bin) -> +    do_open({binary, Bin}, Mode); +open({file, Fd}, Mode) -> +    do_open({file, Fd}, Mode); +open(Name, Mode) when is_list(Name); is_binary(Name) -> +    do_open(Name, Mode). + +do_open(Name, Mode) when is_list(Mode) -> +    case open_mode(Mode) of +        {ok, Access, Raw, Opts} -> +            open1(Name, Access, Raw, Opts); +        {error, Reason} -> +            {error, {Name, Reason}} +    end. + +open1({binary,Bin}, read, _Raw, Opts) when is_binary(Bin) -> +    case file:open(Bin, [ram,binary,read]) of +        {ok,File} -> +            _ = [ram_file:uncompress(File) || Opts =:= [compressed]], +            {ok, #reader{handle=File,access=read,func=fun file_op/2}}; +        Error -> +            Error +    end; +open1({file, Fd}, read, _Raw, _Opts) -> +    Reader = #reader{handle=Fd,access=read,func=fun file_op/2}, +    case do_position(Reader, {cur, 0}) of +        {ok, Pos, Reader2} -> +            {ok, Reader2#reader{pos=Pos}}; +        {error, _} = Err -> +            Err +    end; +open1(Name, Access, Raw, Opts) when is_list(Name) or is_binary(Name) -> +    case file:open(Name, Raw ++ [binary, Access|Opts]) of +        {ok, File} -> +            {ok, #reader{handle=File,access=Access,func=fun file_op/2}}; +        {error, Reason} -> +            {error, {Name, Reason}} +    end. + +open_mode(Mode) -> +    open_mode(Mode, false, [raw], []). + +open_mode(read, _, Raw, _) -> +    {ok, read, Raw, []}; +open_mode(write, _, Raw, _) -> +    {ok, write, Raw, []}; +open_mode([read|Rest], false, Raw, Opts) -> +    open_mode(Rest, read, Raw, Opts); +open_mode([write|Rest], false, Raw, Opts) -> +    open_mode(Rest, write, Raw, Opts); +open_mode([compressed|Rest], Access, Raw, Opts) -> +    open_mode(Rest, Access, Raw, [compressed|Opts]); +open_mode([cooked|Rest], Access, _Raw, Opts) -> +    open_mode(Rest, Access, [], Opts); +open_mode([], Access, Raw, Opts) -> +    {ok, Access, Raw, Opts}; +open_mode(_, _, _, _) -> +    {error, einval}. + +file_op(write, {Fd, Data}) -> +    file:write(Fd, Data); +file_op(position, {Fd, Pos}) -> +    file:position(Fd, Pos); +file_op(read2, {Fd, Size}) -> +    file:read(Fd, Size); +file_op(close, Fd) -> +    file:close(Fd). + +%% Closes a tar archive. +-spec close(reader()) -> ok | {error, term()}. +close(#reader{access=read}=Reader) -> +    ok = do_close(Reader); +close(#reader{access=write}=Reader) -> +    {ok, Reader2} = pad_file(Reader), +    ok = do_close(Reader2), +    ok; +close(_) -> +    {error, einval}. + +pad_file(#reader{pos=Pos}=Reader) -> +    %% There must be at least two zero blocks at the end. +    PadCurrent = skip_padding(Pos+?BLOCK_SIZE), +    Padding = <<0:PadCurrent/unit:8>>, +    do_write(Reader, [Padding, ?ZERO_BLOCK, ?ZERO_BLOCK]). + + +%%%================================================================ +%% Creation/modification of tar archives + +%% Creates a tar file Name containing the given files. +-spec create(file:filename(), filelist()) -> ok | {error, {string(), term()}}. +create(Name, FileList) when is_list(Name); is_binary(Name) -> +    create(Name, FileList, []). + +%% Creates a tar archive Name containing the given files. +%% Accepted options: verbose, compressed, cooked +-spec create(file:filename(), filelist(), [create_opt()]) -> +                    ok | {error, term()} | {error, {string(), term()}}. +create(Name, FileList, Options) when is_list(Name); is_binary(Name) -> +    Mode = lists:filter(fun(X) -> (X=:=compressed) or (X=:=cooked) +                        end, Options), +    case open(Name, [write|Mode]) of +        {ok, TarFile} -> +            do_create(TarFile, FileList, Options); +        {error, _} = Err -> +            Err +    end. + +do_create(TarFile, [], _Opts) -> +    close(TarFile); +do_create(TarFile, [{NameInArchive, NameOrBin}|Rest], Opts) -> +    case add(TarFile, NameOrBin, NameInArchive, Opts) of +        ok -> +            do_create(TarFile, Rest, Opts); +        {error, _} = Err -> +            _ = close(TarFile), +            Err +    end; +do_create(TarFile, [Name|Rest], Opts) -> +    case add(TarFile, Name, Name, Opts) of +        ok -> +            do_create(TarFile, Rest, Opts); +        {error, _} = Err -> +            _ = close(TarFile), +            Err +    end. + +%% Adds a file to a tape archive. +-type add_type() :: string() +                  | {string(), string()} +                  | {string(), binary()}. +-spec add(reader(), add_type(), [add_opt()]) -> ok | {error, term()}. +add(Reader, {NameInArchive, Name}, Opts) +  when is_list(NameInArchive), is_list(Name) -> +    do_add(Reader, Name, NameInArchive, undefined, Opts); +add(Reader, {NameInArchive, Bin}, Opts) +  when is_list(NameInArchive), is_binary(Bin) -> +    do_add(Reader, Bin, NameInArchive, undefined, Opts); +add(Reader, {NameInArchive, Bin, Mode}, Opts) +  when is_list(NameInArchive), is_binary(Bin), is_integer(Mode) -> +    do_add(Reader, Bin, NameInArchive, Mode, Opts); +add(Reader, Name, Opts) when is_list(Name) -> +    do_add(Reader, Name, Name, undefined, Opts). + + +-spec add(reader(), string() | binary(), string(), [add_opt()]) -> +                 ok | {error, term()}. +add(Reader, NameOrBin, NameInArchive, Options) +  when is_list(NameOrBin); is_binary(NameOrBin), +       is_list(NameInArchive), is_list(Options) -> +    do_add(Reader, NameOrBin, NameInArchive, undefined, Options). + +-spec add(reader(), string() | binary(), string(), integer(), [add_opt()]) -> +                 ok | {error, term()}. +add(Reader, NameOrBin, NameInArchive, Mode, Options) +  when is_list(NameOrBin); is_binary(NameOrBin), +       is_list(NameInArchive), is_integer(Mode), is_list(Options) -> +    do_add(Reader, NameOrBin, NameInArchive, Mode, Options). + +do_add(#reader{access=write}=Reader, Name, NameInArchive, Mode, Options) +  when is_list(NameInArchive), is_list(Options) -> +    RF = fun(F) -> apply_file_info_opts(Options, file:read_link_info(F, [{time, posix}])) end, +    Opts = #add_opts{read_info=RF}, +    add1(Reader, Name, NameInArchive, Mode, add_opts(Options, Options, Opts)); +do_add(#reader{access=read},_,_,_,_) -> +    {error, eacces}; +do_add(Reader,_,_,_,_) -> +    {error, {badarg, Reader}}. + +add_opts([dereference|T], AllOptions, Opts) -> +    RF = fun(F) -> apply_file_info_opts(AllOptions, file:read_file_info(F, [{time, posix}])) end, +    add_opts(T, AllOptions, Opts#add_opts{read_info=RF}); +add_opts([verbose|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{verbose=true}); +add_opts([{chunks,N}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{chunk_size=N}); +add_opts([{atime,Value}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{atime=Value}); +add_opts([{mtime,Value}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{mtime=Value}); +add_opts([{ctime,Value}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{ctime=Value}); +add_opts([{uid,Value}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{uid=Value}); +add_opts([{gid,Value}|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts#add_opts{gid=Value}); +add_opts([_|T], AllOptions, Opts) -> +    add_opts(T, AllOptions, Opts); +add_opts([], _AllOptions, Opts) -> +    Opts. + +apply_file_info_opts(Opts, {ok, FileInfo}) -> +    {ok, do_apply_file_info_opts(Opts, FileInfo)}; +apply_file_info_opts(_Opts, Other) -> +    Other. + +do_apply_file_info_opts([{atime,Value}|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo#file_info{atime=Value}); +do_apply_file_info_opts([{mtime,Value}|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo#file_info{mtime=Value}); +do_apply_file_info_opts([{ctime,Value}|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo#file_info{ctime=Value}); +do_apply_file_info_opts([{uid,Value}|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo#file_info{uid=Value}); +do_apply_file_info_opts([{gid,Value}|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo#file_info{gid=Value}); +do_apply_file_info_opts([_|T], FileInfo) -> +    do_apply_file_info_opts(T, FileInfo); +do_apply_file_info_opts([], FileInfo) -> +    FileInfo. + +add1(#reader{}=Reader, Name, NameInArchive, undefined, #add_opts{read_info=ReadInfo}=Opts) +  when is_list(Name) -> +    Res = case ReadInfo(Name) of +              {error, Reason0} -> +                  {error, {Name, Reason0}}; +              {ok, #file_info{type=symlink}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  {ok, Linkname} = file:read_link(Name), +                  Header = fileinfo_to_header(NameInArchive, Fi, Linkname), +                  add_header(Reader, Header, Opts); +              {ok, #file_info{type=regular}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  Header = fileinfo_to_header(NameInArchive, Fi, false), +                  {ok, Reader2} = add_header(Reader, Header, Opts), +                  FileSize = Header#tar_header.size, +                  {ok, FileSize, Reader3} = do_copy(Reader2, Name, Opts), +                  Padding = skip_padding(FileSize), +                  Pad = <<0:Padding/unit:8>>, +                  do_write(Reader3, Pad); +              {ok, #file_info{type=directory}=Fi} -> +                  add_directory(Reader, Name, NameInArchive, Fi, Opts); +              {ok, #file_info{}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  Header = fileinfo_to_header(NameInArchive, Fi, false), +                  add_header(Reader, Header, Opts) +          end, +    case Res of +        ok -> ok; +        {ok, _Reader} -> ok; +        {error, _Reason} = Err -> Err +    end; +add1(Reader, Bin, NameInArchive, Mode, Opts) when is_binary(Bin) -> +    add_verbose(Opts, "a ~ts~n", [NameInArchive]), +    Now = 0, +    Header = #tar_header{ +                name = NameInArchive, +                size = byte_size(Bin), +                typeflag = ?TYPE_REGULAR, +                atime = add_opts_time(Opts#add_opts.atime, Now), +                mtime = add_opts_time(Opts#add_opts.mtime, Now), +                ctime = add_opts_time(Opts#add_opts.ctime, Now), +                uid = Opts#add_opts.uid, +                gid = Opts#add_opts.gid, +                mode = default_mode(Mode, 8#100644)}, +    {ok, Reader2} = add_header(Reader, Header, Opts), +    Padding = skip_padding(byte_size(Bin)), +    Data = [Bin, <<0:Padding/unit:8>>], +    case do_write(Reader2, Data) of +        {ok, _Reader3} -> ok; +        {error, Reason} -> {error, {NameInArchive, Reason}} +    end. + +add_opts_time(undefined, _Now) -> 0; +add_opts_time(Time, _Now) -> Time. + +default_mode(undefined, Mode) -> Mode; +default_mode(Mode, _) -> Mode. + +add_directory(Reader, DirName, NameInArchive, Info, Opts) -> +    case file:list_dir(DirName) of +        {ok, []} -> +            add_verbose(Opts, "a ~ts~n", [NameInArchive]), +            Header = fileinfo_to_header(NameInArchive, Info, false), +            add_header(Reader, Header, Opts); +        {ok, Files} -> +            add_verbose(Opts, "a ~ts~n", [NameInArchive]), +            try add_files(Reader, Files, DirName, NameInArchive, Opts) of +                ok -> ok; +                {error, _} = Err -> Err +            catch +                throw:{error, {_Name, _Reason}} = Err -> Err; +                throw:{error, Reason} -> {error, {DirName, Reason}} +            end; +        {error, Reason} -> +            {error, {DirName, Reason}} +    end. + +add_files(_Reader, [], _Dir, _DirInArchive, _Opts) -> +    ok; +add_files(Reader, [Name|Rest], Dir, DirInArchive, #add_opts{read_info=Info}=Opts) -> +    FullName = filename:join(Dir, Name), +    NameInArchive = filename:join(DirInArchive, Name), +    Res = case Info(FullName) of +              {error, Reason} -> +                  {error, {FullName, Reason}}; +              {ok, #file_info{type=directory}=Fi} -> +                  add_directory(Reader, FullName, NameInArchive, Fi, Opts); +              {ok, #file_info{type=symlink}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  {ok, Linkname} = file:read_link(FullName), +                  Header = fileinfo_to_header(NameInArchive, Fi, Linkname), +                  add_header(Reader, Header, Opts); +              {ok, #file_info{type=regular}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  Header = fileinfo_to_header(NameInArchive, Fi, false), +                  {ok, Reader2} = add_header(Reader, Header, Opts), +                  FileSize = Header#tar_header.size, +                  {ok, FileSize, Reader3} = do_copy(Reader2, FullName, Opts), +                  Padding = skip_padding(FileSize), +                  Pad = <<0:Padding/unit:8>>, +                  do_write(Reader3, Pad); +              {ok, #file_info{}=Fi} -> +                  add_verbose(Opts, "a ~ts~n", [NameInArchive]), +                  Header = fileinfo_to_header(NameInArchive, Fi, false), +                  add_header(Reader, Header, Opts) +          end, +    case Res of +        ok -> add_files(Reader, Rest, Dir, DirInArchive, Opts); +        {ok, ReaderNext} -> add_files(ReaderNext, Rest, Dir, DirInArchive, Opts); +        {error, _} = Err -> Err +    end. + +format_string(String, Size) when length(String) > Size -> +    throw({error, {write_string, field_too_long}}); +format_string(String, Size) -> +    Ascii = to_ascii(String), +    if byte_size(Ascii) < Size -> +            [Ascii, 0]; +       true -> +            Ascii +    end. + +format_octal(Octal) -> +    iolist_to_binary(io_lib:fwrite("~.8B", [Octal])). + +add_header(#reader{}=Reader, #tar_header{}=Header, Opts) -> +    {ok, Iodata} = build_header(Header, Opts), +    do_write(Reader, Iodata). + +write_to_block(Block, IoData, Start) when is_list(IoData) -> +    write_to_block(Block, iolist_to_binary(IoData), Start); +write_to_block(Block, Bin, Start) when is_binary(Bin) -> +    Size = byte_size(Bin), +    <<Head:Start/unit:8, _:Size/unit:8, Rest/binary>> = Block, +    <<Head:Start/unit:8, Bin/binary, Rest/binary>>. + +build_header(#tar_header{}=Header, Opts) -> +    #tar_header{ +       name=Name, +       mode=Mode, +       uid=Uid, +       gid=Gid, +       size=Size, +       typeflag=Type, +       linkname=Linkname, +       uname=Uname, +       gname=Gname, +       devmajor=Devmaj, +       devminor=Devmin +      } = Header, +    Mtime = Header#tar_header.mtime, + +    Block0 = ?ZERO_BLOCK, +    {Block1, Pax0} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, Name, ?PAX_PATH, #{}), +    Block2 = write_octal(Block1, ?V7_MODE, ?V7_MODE_LEN, Mode), +    {Block3, Pax1} = write_numeric(Block2, ?V7_UID, ?V7_UID_LEN, Uid, ?PAX_UID, Pax0), +    {Block4, Pax2} = write_numeric(Block3, ?V7_GID, ?V7_GID_LEN, Gid, ?PAX_GID, Pax1), +    {Block5, Pax3} = write_numeric(Block4, ?V7_SIZE, ?V7_SIZE_LEN, Size, ?PAX_SIZE, Pax2), +    {Block6, Pax4} = write_numeric(Block5, ?V7_MTIME, ?V7_MTIME_LEN, Mtime, ?PAX_NONE, Pax3), +    {Block7, Pax5} = write_string(Block6, ?V7_TYPE, ?V7_TYPE_LEN, <<Type>>, ?PAX_NONE, Pax4), +    {Block8, Pax6} = write_string(Block7, ?V7_LINKNAME, ?V7_LINKNAME_LEN, +                                  Linkname, ?PAX_LINKPATH, Pax5), +    {Block9, Pax7} = write_string(Block8, ?USTAR_UNAME, ?USTAR_UNAME_LEN, +                                  Uname, ?PAX_UNAME, Pax6), +    {Block10, Pax8} = write_string(Block9, ?USTAR_GNAME, ?USTAR_GNAME_LEN, +                                   Gname, ?PAX_GNAME, Pax7), +    {Block11, Pax9} = write_numeric(Block10, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN, +                                    Devmaj, ?PAX_NONE, Pax8), +    {Block12, Pax10} = write_numeric(Block11, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN, +                                     Devmin, ?PAX_NONE, Pax9), +    {Block13, Pax11} = set_path(Block12, Pax10), +    PaxEntry = case maps:size(Pax11) of +                   0 -> []; +                   _ -> build_pax_entry(Header, Pax11, Opts) +               end, +    Block14 = set_format(Block13, ?FORMAT_USTAR), +    Block15 = set_checksum(Block14), +    {ok, [PaxEntry, Block15]}. + +set_path(Block0, Pax) -> +     %% only use ustar header when name is too long +    case maps:get(?PAX_PATH, Pax, nil) of +        nil -> +            {Block0, Pax}; +        PaxPath -> +            case split_ustar_path(PaxPath) of +                {ok, UstarName, UstarPrefix} -> +                    {Block1, _} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, +                                               UstarName, ?PAX_NONE, #{}), +                    {Block2, _} = write_string(Block1, ?USTAR_PREFIX, ?USTAR_PREFIX_LEN, +                                               UstarPrefix, ?PAX_NONE, #{}), +                    {Block2, maps:remove(?PAX_PATH, Pax)}; +                false -> +                    {Block0, Pax} +            end +    end. + +set_format(Block0, Format) +  when Format =:= ?FORMAT_USTAR; Format =:= ?FORMAT_PAX -> +    Block1 = write_to_block(Block0, ?MAGIC_USTAR, ?USTAR_MAGIC), +    write_to_block(Block1, ?VERSION_USTAR, ?USTAR_VERSION); +set_format(_Block, Format) -> +    throw({error, {invalid_format, Format}}). + +set_checksum(Block) -> +    Checksum = compute_checksum(Block), +    write_octal(Block, ?V7_CHKSUM, ?V7_CHKSUM_LEN, Checksum). + +build_pax_entry(Header, PaxAttrs, Opts) -> +    Path = Header#tar_header.name, +    Filename = filename:basename(Path), +    Dir = filename:dirname(Path), +    Path2 = filename:join([Dir, "PaxHeaders.0", Filename]), +    AsciiPath = to_ascii(Path2), +    Path3 = if byte_size(AsciiPath) > ?V7_NAME_LEN -> +                    binary_part(AsciiPath, 0, ?V7_NAME_LEN - 1); +               true -> +                    AsciiPath +            end, +    Keys = maps:keys(PaxAttrs), +    SortedKeys = lists:sort(Keys), +    PaxFile = build_pax_file(SortedKeys, PaxAttrs), +    Size = byte_size(PaxFile), +    Padding = (?BLOCK_SIZE - +                   (byte_size(PaxFile) rem ?BLOCK_SIZE)) rem ?BLOCK_SIZE, +    Pad = <<0:Padding/unit:8>>, +    PaxHeader = #tar_header{ +                   name=unicode:characters_to_list(Path3), +                   size=Size, +                   mtime=Header#tar_header.mtime, +                   atime=Header#tar_header.atime, +                   ctime=Header#tar_header.ctime, +                   typeflag=?TYPE_X_HEADER +                  }, +    {ok, PaxHeaderData} = build_header(PaxHeader, Opts), +    [PaxHeaderData, PaxFile, Pad]. + +build_pax_file(Keys, PaxAttrs) -> +    build_pax_file(Keys, PaxAttrs, []). +build_pax_file([], _, Acc) -> +    unicode:characters_to_binary(Acc); +build_pax_file([K|Rest], Attrs, Acc) -> +    V = maps:get(K, Attrs), +    Size = sizeof(K) + sizeof(V) + 3, +    Size2 = sizeof(Size) + Size, +    Key = to_string(K), +    Value = to_string(V), +    Record = unicode:characters_to_binary(io_lib:format("~B ~ts=~ts\n", [Size2, Key, Value])), +    if byte_size(Record) =/= Size2 -> +            Size3 = byte_size(Record), +            Record2 = io_lib:format("~B ~ts=~ts\n", [Size3, Key, Value]), +            build_pax_file(Rest, Attrs, [Acc, Record2]); +       true -> +            build_pax_file(Rest, Attrs, [Acc, Record]) +    end. + +sizeof(Bin) when is_binary(Bin) -> +    byte_size(Bin); +sizeof(List) when is_list(List) -> +    length(List); +sizeof(N) when is_integer(N) -> +    byte_size(integer_to_binary(N)); +sizeof(N) when is_float(N) -> +    byte_size(float_to_binary(N)). + +to_string(Bin) when is_binary(Bin) -> +    unicode:characters_to_list(Bin); +to_string(List) when is_list(List) -> +    List; +to_string(N) when is_integer(N) -> +    integer_to_list(N); +to_string(N) when is_float(N) -> +    float_to_list(N). + +split_ustar_path(Path) -> +    Len = length(Path), +    NotAscii = not is_ascii(Path), +    if Len =< ?V7_NAME_LEN; NotAscii -> +            false; +       true -> +            PathBin = binary:list_to_bin(Path), +            case binary:split(PathBin, [<<$/>>], [global, trim_all]) of +                [Part] when byte_size(Part) >= ?V7_NAME_LEN -> +                    false; +                Parts -> +                    case lists:last(Parts) of +                        Name when byte_size(Name) >= ?V7_NAME_LEN -> +                            false; +                        Name -> +                            Parts2 = lists:sublist(Parts, length(Parts) - 1), +                            join_split_ustar_path(Parts2, {ok, Name, nil}) +                    end +            end +    end. + +join_split_ustar_path([], Acc) -> +    Acc; +join_split_ustar_path([Part|_], {ok, _, nil}) +  when byte_size(Part) > ?USTAR_PREFIX_LEN -> +    false; +join_split_ustar_path([Part|_], {ok, _Name, Acc}) +  when (byte_size(Part)+byte_size(Acc)) > ?USTAR_PREFIX_LEN -> +    false; +join_split_ustar_path([Part|Rest], {ok, Name, nil}) -> +    join_split_ustar_path(Rest, {ok, Name, Part}); +join_split_ustar_path([Part|Rest], {ok, Name, Acc}) -> +    join_split_ustar_path(Rest, {ok, Name, <<Acc/binary,$/,Part/binary>>}). + +write_octal(Block, Pos, Size, X) -> +    Octal = zero_pad(format_octal(X), Size-1), +    if byte_size(Octal) < Size -> +            write_to_block(Block, Octal, Pos); +       true -> +            throw({error, {write_failed, octal_field_too_long}}) +    end. + +write_string(Block, Pos, Size, Str, PaxAttr, Pax0) -> +    NotAscii = not is_ascii(Str), +    if PaxAttr =/= ?PAX_NONE andalso (length(Str) > Size orelse NotAscii) -> +            Pax1 = maps:put(PaxAttr, Str, Pax0), +            {Block, Pax1}; +       true -> +            Formatted = format_string(Str, Size), +            {write_to_block(Block, Formatted, Pos), Pax0} +    end. +write_numeric(Block, Pos, Size, X, PaxAttr, Pax0) -> +    %% attempt octal +    Octal = zero_pad(format_octal(X), Size-1), +    if byte_size(Octal) < Size -> +            {write_to_block(Block, [Octal, 0], Pos), Pax0}; +       PaxAttr =/= ?PAX_NONE -> +            Pax1 = maps:put(PaxAttr, X, Pax0), +            {Block, Pax1}; +       true -> +            throw({error, {write_failed, numeric_field_too_long}}) +    end. + +zero_pad(Str, Size) when byte_size(Str) >= Size -> +    Str; +zero_pad(Str, Size) -> +    Padding = Size - byte_size(Str), +    Pad = binary:copy(<<$0>>, Padding), +    <<Pad/binary, Str/binary>>. + + +%%%================================================================ +%% Functions for creating or modifying tar archives + +read_block(Reader) -> +    case do_read(Reader, ?BLOCK_SIZE) of +        eof -> +            throw({error, eof}); +        %% Two zero blocks mark the end of the archive +        {ok, ?ZERO_BLOCK, Reader1} -> +            case do_read(Reader1, ?BLOCK_SIZE) of +                eof -> +                    % This is technically a malformed end-of-archive marker, +                    % as two ZERO_BLOCKs are expected as the marker, +                    % but if we've already made it this far, we should just ignore it +                    eof; +                {ok, ?ZERO_BLOCK, _Reader2} -> +                    eof; +                {ok, _Block, _Reader2} -> +                    throw({error, invalid_end_of_archive}); +                {error,_} = Err -> +                    throw(Err) +            end; +        {ok, Block, Reader1} when is_binary(Block) -> +            {ok, Block, Reader1}; +        {error, _} = Err -> +            throw(Err) +    end. + +get_header(#reader{}=Reader) -> +    case read_block(Reader) of +        eof -> +            eof; +        {ok, Block, Reader1} -> +            convert_header(Block, Reader1) +    end. + +%% Converts the tar header to a record. +to_v7(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    #header_v7{ +       name=binary_part(Bin, ?V7_NAME, ?V7_NAME_LEN), +       mode=binary_part(Bin, ?V7_MODE, ?V7_MODE_LEN), +       uid=binary_part(Bin, ?V7_UID, ?V7_UID_LEN), +       gid=binary_part(Bin, ?V7_GID, ?V7_GID_LEN), +       size=binary_part(Bin, ?V7_SIZE, ?V7_SIZE_LEN), +       mtime=binary_part(Bin, ?V7_MTIME, ?V7_MTIME_LEN), +       checksum=binary_part(Bin, ?V7_CHKSUM, ?V7_CHKSUM_LEN), +       typeflag=binary:at(Bin, ?V7_TYPE), +       linkname=binary_part(Bin, ?V7_LINKNAME, ?V7_LINKNAME_LEN) +      }; +to_v7(_) -> +    {error, header_block_too_small}. + +to_gnu(#header_v7{}=V7, Bin) +  when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    #header_gnu{ +       header_v7=V7, +       magic=binary_part(Bin, ?GNU_MAGIC, ?GNU_MAGIC_LEN), +       version=binary_part(Bin, ?GNU_VERSION, ?GNU_VERSION_LEN), +       uname=binary_part(Bin, 265, 32), +       gname=binary_part(Bin, 297, 32), +       devmajor=binary_part(Bin, 329, 8), +       devminor=binary_part(Bin, 337, 8), +       atime=binary_part(Bin, 345, 12), +       ctime=binary_part(Bin, 357, 12), +       sparse=to_sparse_array(binary_part(Bin, 386, 24*4+1)), +       real_size=binary_part(Bin, 483, 12) +      }. + +to_star(#header_v7{}=V7, Bin) +  when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    #header_star{ +       header_v7=V7, +       magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN), +       version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN), +       uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN), +       gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN), +       devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN), +       devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN), +       prefix=binary_part(Bin, 345, 131), +       atime=binary_part(Bin, 476, 12), +       ctime=binary_part(Bin, 488, 12), +       trailer=binary_part(Bin, ?STAR_TRAILER, ?STAR_TRAILER_LEN) +      }. + +to_ustar(#header_v7{}=V7, Bin) +  when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    #header_ustar{ +       header_v7=V7, +       magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN), +       version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN), +       uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN), +       gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN), +       devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN), +       devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN), +       prefix=binary_part(Bin, 345, 155) +      }. + +to_sparse_array(Bin) when is_binary(Bin) -> +    MaxEntries = byte_size(Bin) div 24, +    IsExtended = 1 =:= binary:at(Bin, 24*MaxEntries), +    Entries = parse_sparse_entries(Bin, MaxEntries-1, []), +    #sparse_array{ +       entries=Entries, +       max_entries=MaxEntries, +       is_extended=IsExtended +      }. + +parse_sparse_entries(<<>>, _, Acc) -> +    Acc; +parse_sparse_entries(_, -1, Acc) -> +    Acc; +parse_sparse_entries(Bin, N, Acc) -> +    case to_sparse_entry(binary_part(Bin, N*24, 24)) of +        nil -> +            parse_sparse_entries(Bin, N-1, Acc); +        Entry = #sparse_entry{} -> +            parse_sparse_entries(Bin, N-1, [Entry|Acc]) +    end. + +-define(EMPTY_ENTRY, <<0,0,0,0,0,0,0,0,0,0,0,0>>). +to_sparse_entry(Bin) when is_binary(Bin), byte_size(Bin) =:= 24 -> +    OffsetBin = binary_part(Bin, 0, 12), +    NumBytesBin = binary_part(Bin, 12, 12), +    case {OffsetBin, NumBytesBin} of +        {?EMPTY_ENTRY, ?EMPTY_ENTRY} -> +            nil; +        _ -> +            #sparse_entry{ +               offset=parse_numeric(OffsetBin), +               num_bytes=parse_numeric(NumBytesBin)} +    end. + +-spec get_format(binary()) -> {ok, pos_integer(), header_v7()} +                                  | ?FORMAT_UNKNOWN +                                  | {error, term()}. +get_format(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    do_get_format(to_v7(Bin), Bin). + +do_get_format({error, _} = Err, _Bin) -> +    Err; +do_get_format(#header_v7{}=V7, Bin) +  when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    Checksum = parse_octal(V7#header_v7.checksum), +    Chk1 = compute_checksum(Bin), +    Chk2 = compute_signed_checksum(Bin), +    if Checksum =/= Chk1 andalso Checksum =/= Chk2 -> +            ?FORMAT_UNKNOWN; +       true -> +            %% guess magic +            Ustar = to_ustar(V7, Bin), +            Star = to_star(V7, Bin), +            Magic = Ustar#header_ustar.magic, +            Version = Ustar#header_ustar.version, +            Trailer = Star#header_star.trailer, +            Format = if +                         Magic =:= ?MAGIC_USTAR, Trailer =:= ?TRAILER_STAR -> +                             ?FORMAT_STAR; +                         Magic =:= ?MAGIC_USTAR -> +                             ?FORMAT_USTAR; +                         Magic =:= ?MAGIC_GNU, Version =:= ?VERSION_GNU -> +                             ?FORMAT_GNU; +                         true -> +                             ?FORMAT_V7 +                     end, +            {ok, Format, V7} +    end. + +unpack_format(Format, #header_v7{}=V7, Bin, Reader) +  when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> +    Mtime = parse_numeric(V7#header_v7.mtime), +    Header0 = #tar_header{ +                 name=parse_string(V7#header_v7.name), +                 mode=parse_numeric(V7#header_v7.mode), +                 uid=parse_numeric(V7#header_v7.uid), +                 gid=parse_numeric(V7#header_v7.gid), +                 size=parse_numeric(V7#header_v7.size), +                 mtime=Mtime, +                 atime=Mtime, +                 ctime=Mtime, +                 typeflag=V7#header_v7.typeflag, +                 linkname=parse_string(V7#header_v7.linkname) +                }, +    Typeflag = Header0#tar_header.typeflag, +    Header1 = if Format > ?FORMAT_V7 -> +                      unpack_modern(Format, V7, Bin, Header0); +                 true -> +                      Name = Header0#tar_header.name, +                      Header0#tar_header{name=safe_join_path("", Name)} +              end, +    HeaderOnly = is_header_only_type(Typeflag), +    Header2 = if HeaderOnly -> +                      Header1#tar_header{size=0}; +                 true -> +                      Header1 +              end, +    if Typeflag =:= ?TYPE_GNU_SPARSE -> +            Gnu = to_gnu(V7, Bin), +            RealSize = parse_numeric(Gnu#header_gnu.real_size), +            {Sparsemap, Reader2} = parse_sparse_map(Gnu, Reader), +            Header3 = Header2#tar_header{size=RealSize}, +            {Header3, new_sparse_file_reader(Reader2, Sparsemap, RealSize)}; +       true -> +            FileReader = #reg_file_reader{ +                            handle=Reader, +                            num_bytes=Header2#tar_header.size, +                            size=Header2#tar_header.size, +                            pos = 0 +                           }, +            {Header2, FileReader} +    end. + +unpack_modern(Format, #header_v7{}=V7, Bin, #tar_header{}=Header0) +  when is_binary(Bin) -> +    Typeflag = Header0#tar_header.typeflag, +    Ustar = to_ustar(V7, Bin), +    H0 = Header0#tar_header{ +            uname=parse_string(Ustar#header_ustar.uname), +            gname=parse_string(Ustar#header_ustar.gname)}, +    H1 = if Typeflag =:= ?TYPE_CHAR +            orelse Typeflag =:= ?TYPE_BLOCK -> +                Ma = parse_numeric(Ustar#header_ustar.devmajor), +                Mi = parse_numeric(Ustar#header_ustar.devminor), +                H0#tar_header{ +                    devmajor=Ma, +                    devminor=Mi +                }; +            true -> +                H0 +        end, +    {Prefix, H2} = case Format of +                        ?FORMAT_USTAR -> +                            {parse_string(Ustar#header_ustar.prefix), H1}; +                        ?FORMAT_STAR -> +                            Star = to_star(V7, Bin), +                            Prefix0 = parse_string(Star#header_star.prefix), +                            Atime0 = Star#header_star.atime, +                            Atime = parse_numeric(Atime0), +                            Ctime0 = Star#header_star.ctime, +                            Ctime = parse_numeric(Ctime0), +                            {Prefix0, H1#tar_header{ +                                        atime=Atime, +                                        ctime=Ctime +                                    }}; +                        _ -> +                            {"", H1} +                    end, +    Name = H2#tar_header.name, +    H2#tar_header{name=safe_join_path(Prefix, Name)}. + + +safe_join_path([], Name) -> +    filename:join([Name]); +safe_join_path(Prefix, []) -> +    filename:join([Prefix]); +safe_join_path(Prefix, Name) -> +    filename:join(Prefix, Name). + +new_sparse_file_reader(Reader, Sparsemap, RealSize) -> +    true = validate_sparse_entries(Sparsemap, RealSize), +    #sparse_file_reader{ +       handle = Reader, +       num_bytes = RealSize, +       pos = 0, +       size = RealSize, +       sparse_map = Sparsemap}. + +validate_sparse_entries(Entries, RealSize) -> +    validate_sparse_entries(Entries, RealSize, 0, 0). +validate_sparse_entries([], _RealSize, _I, _LastOffset) -> +    true; +validate_sparse_entries([#sparse_entry{}=Entry|Rest], RealSize, I, LastOffset) -> +    Offset = Entry#sparse_entry.offset, +    NumBytes = Entry#sparse_entry.num_bytes, +    if +        Offset > ?MAX_INT64-NumBytes -> +            throw({error, {invalid_sparse_map_entry, offset_too_large}}); +        Offset+NumBytes > RealSize -> +            throw({error, {invalid_sparse_map_entry, offset_too_large}}); +        I > 0 andalso LastOffset > Offset -> +            throw({error, {invalid_sparse_map_entry, overlapping_offsets}}); +        true -> +            ok +    end, +    validate_sparse_entries(Rest, RealSize, I+1, Offset+NumBytes). + + +-spec parse_sparse_map(header_gnu(), reader_type()) -> +                              {[sparse_entry()], reader_type()}. +parse_sparse_map(#header_gnu{sparse=Sparse}, Reader) +  when Sparse#sparse_array.is_extended -> +    parse_sparse_map(Sparse, Reader, []); +parse_sparse_map(#header_gnu{sparse=Sparse}, Reader) -> +    {Sparse#sparse_array.entries, Reader}. +parse_sparse_map(#sparse_array{is_extended=true,entries=Entries}, Reader, Acc) -> +    case read_block(Reader) of +        eof -> +            throw({error, eof}); +        {ok, Block, Reader2} -> +            Sparse2 = to_sparse_array(Block), +            parse_sparse_map(Sparse2, Reader2, Entries++Acc) +    end; +parse_sparse_map(#sparse_array{entries=Entries}, Reader, Acc) -> +    Sorted = lists:sort(fun (#sparse_entry{offset=A},#sparse_entry{offset=B}) -> +                                A =< B +                        end, Entries++Acc), +    {Sorted, Reader}. + +%% Defined by taking the sum of the unsigned byte values of the +%% entire header record, treating the checksum bytes to as ASCII spaces +compute_checksum(<<H1:?V7_CHKSUM/binary, +                   H2:?V7_CHKSUM_LEN/binary, +                   Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary, +                   _/binary>>) -> +    C0 = checksum(H1) + (byte_size(H2) * $\s), +    C1 = checksum(Rest), +    C0 + C1. + +compute_signed_checksum(<<H1:?V7_CHKSUM/binary, +                          H2:?V7_CHKSUM_LEN/binary, +                          Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary, +                          _/binary>>) -> +    C0 = signed_checksum(H1) + (byte_size(H2) * $\s), +    C1 = signed_checksum(Rest), +    C0 + C1. + +%% Returns the checksum of a binary. +checksum(Bin) -> checksum(Bin, 0). +checksum(<<A/unsigned,Rest/binary>>, Sum) -> +    checksum(Rest, Sum+A); +checksum(<<>>, Sum) -> Sum. + +signed_checksum(Bin) -> signed_checksum(Bin, 0). +signed_checksum(<<A/signed,Rest/binary>>, Sum) -> +    signed_checksum(Rest, Sum+A); +signed_checksum(<<>>, Sum) -> Sum. + +-spec parse_numeric(binary()) -> non_neg_integer(). +parse_numeric(<<>>) -> +    0; +parse_numeric(<<First, _/binary>> = Bin) -> +    %% check for base-256 format first +    %% if the bit is set, then all following bits constitute a two's +    %% complement encoded number in big-endian byte order +    if +        First band 16#80 =/= 0 -> +            %% Handling negative numbers relies on the following identity: +            %%     -a-1 == ^a +            %% If the number is negative, we use an inversion mask to invert +            %% the data bytes and treat the value as an unsigned number +            Inv = if First band 16#40 =/= 0 -> 16#00; true -> 16#FF end, +            Bytes = binary:bin_to_list(Bin), +            Reducer = fun (C, {I, X}) -> +                              C1 = C bxor Inv, +                              C2 = if I =:= 0 -> C1 band 16#7F; true -> C1 end, +                              if (X bsr 56) > 0 -> +                                      throw({error,integer_overflow}); +                                 true -> +                                      {I+1, (X bsl 8) bor C2} +                              end +                      end, +            {_, N} = lists:foldl(Reducer, {0,0}, Bytes), +            if (N bsr 63) > 0 -> +                    throw({error, integer_overflow}); +               true -> +                    if Inv =:= 16#FF -> +                            -1 bxor N; +                       true -> +                            N +                    end +            end; +        true -> +            %% normal case is an octal number +            parse_octal(Bin) +    end. + +parse_octal(Bin) when is_binary(Bin) -> +    %% skip leading/trailing zero bytes and spaces +    do_parse_octal(Bin, <<>>). +do_parse_octal(<<>>, <<>>) -> +    0; +do_parse_octal(<<>>, Acc) -> +    case io_lib:fread("~8u", binary:bin_to_list(Acc)) of +        {error, _} -> throw({error, invalid_tar_checksum}); +        {ok, [Octal], []} -> Octal; +        {ok, _, _} -> throw({error, invalid_tar_checksum}) +    end; +do_parse_octal(<<$\s,Rest/binary>>, Acc) -> +    do_parse_octal(Rest, Acc); +do_parse_octal(<<0, Rest/binary>>, Acc) -> +    do_parse_octal(Rest, Acc); +do_parse_octal(<<C, Rest/binary>>, Acc) -> +    do_parse_octal(Rest, <<Acc/binary, C>>). + +parse_string(Bin) when is_binary(Bin) -> +    do_parse_string(Bin, <<>>). +do_parse_string(<<>>, Acc) -> +    case unicode:characters_to_list(Acc) of +        Str when is_list(Str) -> +            Str; +        {incomplete, _Str, _Rest} -> +            binary:bin_to_list(Acc); +        {error, _Str, _Rest} -> +            throw({error, {bad_header, invalid_string}}) +    end; +do_parse_string(<<0, _/binary>>, Acc) -> +    do_parse_string(<<>>, Acc); +do_parse_string(<<C, Rest/binary>>, Acc) -> +    do_parse_string(Rest, <<Acc/binary, C>>). + +convert_header(Bin, #reader{pos=Pos}=Reader) +  when byte_size(Bin) =:= ?BLOCK_SIZE, (Pos rem ?BLOCK_SIZE) =:= 0 -> +    case get_format(Bin) of +        ?FORMAT_UNKNOWN -> +            throw({error, bad_header}); +        {ok, Format, V7} -> +            unpack_format(Format, V7, Bin, Reader); +        {error, Reason} -> +            throw({error, {bad_header, Reason}}) +    end; +convert_header(Bin, #reader{pos=Pos}) when byte_size(Bin) =:= ?BLOCK_SIZE -> +    throw({error, misaligned_read, Pos}); +convert_header(Bin, _Reader) when byte_size(Bin) =:= 0 -> +    eof; +convert_header(_Bin, _Reader) -> +    throw({error, eof}). + +%% Creates a partially-populated header record based +%% on the provided file_info record. If the file is +%% a symlink, then `link` is used as the link target. +%% If the file is a directory, a slash is appended to the name. +fileinfo_to_header(Name, #file_info{}=Fi, Link) when is_list(Name) -> +    BaseHeader = #tar_header{name=Name, +                             mtime=0, +                             atime=0, +                             ctime=0, +                             mode=Fi#file_info.mode, +                             typeflag=?TYPE_REGULAR}, +    do_fileinfo_to_header(BaseHeader, Fi, Link). + +do_fileinfo_to_header(Header, #file_info{size=Size,type=regular}, _Link) -> +    Header#tar_header{size=Size,typeflag=?TYPE_REGULAR}; +do_fileinfo_to_header(#tar_header{name=Name}=Header, +                      #file_info{type=directory}, _Link) -> +    Header#tar_header{name=Name++"/",typeflag=?TYPE_DIR}; +do_fileinfo_to_header(Header, #file_info{type=symlink}, Link) -> +    Header#tar_header{typeflag=?TYPE_SYMLINK,linkname=Link}; +do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link) +  when (Mode band ?S_IFMT) =:= ?S_IFCHR -> +    Header#tar_header{typeflag=?TYPE_CHAR, +                      devmajor=Fi#file_info.major_device, +                      devminor=Fi#file_info.minor_device}; +do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link) +  when (Mode band ?S_IFMT) =:= ?S_IFBLK -> +    Header#tar_header{typeflag=?TYPE_BLOCK, +                      devmajor=Fi#file_info.major_device, +                      devminor=Fi#file_info.minor_device}; +do_fileinfo_to_header(Header, #file_info{type=other,mode=Mode}, _Link) +  when (Mode band ?S_IFMT) =:= ?S_FIFO -> +    Header#tar_header{typeflag=?TYPE_FIFO}; +do_fileinfo_to_header(Header, Fi, _Link) -> +    {error, {invalid_file_type, Header#tar_header.name, Fi}}. + +is_ascii(Str) when is_list(Str) -> +    not lists:any(fun (Char) -> Char >= 16#80 end, Str); +is_ascii(Bin) when is_binary(Bin) -> +    is_ascii1(Bin). + +is_ascii1(<<>>) -> +    true; +is_ascii1(<<C,_Rest/binary>>) when C >= 16#80 -> +    false; +is_ascii1(<<_, Rest/binary>>) -> +    is_ascii1(Rest). + +to_ascii(Str) when is_list(Str) -> +    case is_ascii(Str) of +        true -> +            unicode:characters_to_binary(Str); +        false -> +            Chars = lists:filter(fun (Char) -> Char < 16#80 end, Str), +            unicode:characters_to_binary(Chars) +    end; +to_ascii(Bin) when is_binary(Bin) -> +    to_ascii(Bin, <<>>). +to_ascii(<<>>, Acc) -> +    Acc; +to_ascii(<<C, Rest/binary>>, Acc) when C < 16#80 -> +    to_ascii(Rest, <<Acc/binary,C>>); +to_ascii(<<_, Rest/binary>>, Acc) -> +    to_ascii(Rest, Acc). + +is_header_only_type(?TYPE_SYMLINK) -> true; +is_header_only_type(?TYPE_LINK)    -> true; +is_header_only_type(?TYPE_DIR)     -> true; +is_header_only_type(_) -> false. + +foldl_read(#reader{access=read}=Reader, Fun, Accu, #read_opts{}=Opts) +  when is_function(Fun,4) -> +    case foldl_read0(Reader, Fun, Accu, Opts) of +        {ok, Result, _Reader2} -> +            Result; +        {error, _} = Err -> +            Err +    end; +foldl_read(#reader{access=Access}, _Fun, _Accu, _Opts) -> +    {error, {read_mode_expected, Access}}; +foldl_read(TarName, Fun, Accu, #read_opts{}=Opts) +  when is_function(Fun,4) -> +    try open(TarName, [read|Opts#read_opts.open_mode]) of +        {ok, #reader{access=read}=Reader} -> +            try +                foldl_read(Reader, Fun, Accu, Opts) +            after +                _ = close(Reader) +            end; +        {error, _} = Err -> +            Err +    catch +        throw:Err -> +            Err +    end. + +foldl_read0(Reader, Fun, Accu, Opts) -> +    try foldl_read1(Fun, Accu, Reader, Opts, #{}) of +        {ok,_,_} = Ok -> +            Ok +    catch +        throw:{error, {Reason, Format, Args}} -> +            read_verbose(Opts, Format, Args), +            {error, Reason}; +        throw:Err -> +            Err +    end. + +foldl_read1(Fun, Accu0, Reader0, Opts, ExtraHeaders) -> +    {ok, Reader1} = skip_unread(Reader0), +    case get_header(Reader1) of +        eof -> +            Fun(eof, Reader1, Opts, Accu0); +        {Header, Reader2} -> +            case Header#tar_header.typeflag of +                ?TYPE_X_HEADER -> +                    {ExtraHeaders2, Reader3} = parse_pax(Reader2), +                    ExtraHeaders3 = maps:merge(ExtraHeaders, ExtraHeaders2), +                    foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders3); +                ?TYPE_GNU_LONGNAME -> +                    {RealName, Reader3} = get_real_name(Reader2), +                    ExtraHeaders2 = maps:put(?PAX_PATH, +                                             parse_string(RealName), ExtraHeaders), +                    foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2); +                ?TYPE_GNU_LONGLINK -> +                    {RealName, Reader3} = get_real_name(Reader2), +                    ExtraHeaders2 = maps:put(?PAX_LINKPATH, +                                             parse_string(RealName), ExtraHeaders), +                    foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2); +                _ -> +                    Header1 = merge_pax(Header, ExtraHeaders), +                    {ok, NewAccu, Reader3} = Fun(Header1, Reader2, Opts, Accu0), +                    foldl_read1(Fun, NewAccu, Reader3, Opts, #{}) +            end +    end. + +%% Applies all known PAX attributes to the current tar header +-spec merge_pax(tar_header(), #{binary() => binary()}) -> tar_header(). +merge_pax(Header, ExtraHeaders) when is_map(ExtraHeaders) -> +    do_merge_pax(Header, maps:to_list(ExtraHeaders)). + +do_merge_pax(Header, []) -> +    Header; +do_merge_pax(Header, [{?PAX_PATH, Path}|Rest]) -> +    do_merge_pax(Header#tar_header{name=unicode:characters_to_list(Path)}, Rest); +do_merge_pax(Header, [{?PAX_LINKPATH, LinkPath}|Rest]) -> +    do_merge_pax(Header#tar_header{linkname=unicode:characters_to_list(LinkPath)}, Rest); +do_merge_pax(Header, [{?PAX_GNAME, Gname}|Rest]) -> +    do_merge_pax(Header#tar_header{gname=unicode:characters_to_list(Gname)}, Rest); +do_merge_pax(Header, [{?PAX_UNAME, Uname}|Rest]) -> +    do_merge_pax(Header#tar_header{uname=unicode:characters_to_list(Uname)}, Rest); +do_merge_pax(Header, [{?PAX_UID, Uid}|Rest]) -> +    Uid2 = binary_to_integer(Uid), +    do_merge_pax(Header#tar_header{uid=Uid2}, Rest); +do_merge_pax(Header, [{?PAX_GID, Gid}|Rest]) -> +    Gid2 = binary_to_integer(Gid), +    do_merge_pax(Header#tar_header{gid=Gid2}, Rest); +do_merge_pax(Header, [{?PAX_ATIME, Atime}|Rest]) -> +    Atime2 = parse_pax_time(Atime), +    do_merge_pax(Header#tar_header{atime=Atime2}, Rest); +do_merge_pax(Header, [{?PAX_MTIME, Mtime}|Rest]) -> +    Mtime2 = parse_pax_time(Mtime), +    do_merge_pax(Header#tar_header{mtime=Mtime2}, Rest); +do_merge_pax(Header, [{?PAX_CTIME, Ctime}|Rest]) -> +    Ctime2 = parse_pax_time(Ctime), +    do_merge_pax(Header#tar_header{ctime=Ctime2}, Rest); +do_merge_pax(Header, [{?PAX_SIZE, Size}|Rest]) -> +    Size2 = binary_to_integer(Size), +    do_merge_pax(Header#tar_header{size=Size2}, Rest); +do_merge_pax(Header, [{<<?PAX_XATTR_STR, _Key/binary>>, _Value}|Rest]) -> +    do_merge_pax(Header, Rest); +do_merge_pax(Header, [_Ignore|Rest]) -> +    do_merge_pax(Header, Rest). + +%% Returns the time since UNIX epoch as a datetime +-spec parse_pax_time(binary()) -> tar_time(). +parse_pax_time(Bin) when is_binary(Bin) -> +    TotalNano = case binary:split(Bin, [<<$.>>]) of +                    [SecondsStr, NanoStr0] -> +                        Seconds = binary_to_integer(SecondsStr), +                        if byte_size(NanoStr0) < ?MAX_NANO_INT_SIZE -> +                                %% right pad +                                PaddingN = ?MAX_NANO_INT_SIZE-byte_size(NanoStr0), +                                Padding = binary:copy(<<$0>>, PaddingN), +                                NanoStr1 = <<NanoStr0/binary,Padding/binary>>, +                                Nano = binary_to_integer(NanoStr1), +                                (Seconds*?BILLION)+Nano; +                           byte_size(NanoStr0) > ?MAX_NANO_INT_SIZE -> +                                %% right truncate +                                NanoStr1 = binary_part(NanoStr0, 0, ?MAX_NANO_INT_SIZE), +                                Nano = binary_to_integer(NanoStr1), +                                (Seconds*?BILLION)+Nano; +                           true -> +                                (Seconds*?BILLION)+binary_to_integer(NanoStr0) +                        end; +                    [SecondsStr] -> +                        binary_to_integer(SecondsStr)*?BILLION +                end, +    %% truncate to microseconds +    Micro = TotalNano div 1000, +    Mega = Micro div 1000000000000, +    Secs = Micro div 1000000 - (Mega*1000000), +    Secs. + +%% Given a regular file reader, reads the whole file and +%% parses all extended attributes it contains. +parse_pax(#reg_file_reader{handle=Handle,num_bytes=0}) -> +    {#{}, Handle}; +parse_pax(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) -> +    case do_read(Handle0, NumBytes) of +        {ok, Bytes, Handle1} -> +            do_parse_pax(Handle1, Bytes, #{}); +        {error, _} = Err -> +            throw(Err) +    end. + +do_parse_pax(Reader, <<>>, Headers) -> +    {Headers, Reader}; +do_parse_pax(Reader, Bin, Headers) -> +    {Key, Value, Residual} = parse_pax_record(Bin), +    NewHeaders = maps:put(Key, Value, Headers), +    do_parse_pax(Reader, Residual, NewHeaders). + +%% Parse an extended attribute +parse_pax_record(Bin) when is_binary(Bin) -> +    case binary:split(Bin, [<<$\n>>]) of +        [Record, Residual] -> +            case [X || X <- binary:split(Record, [<<$\s>>], [global]), X =/= <<>>] of +                [_Len, Record1] -> +                    case  [X || X <- binary:split(Record1, [<<$=>>], [global]), X =/= <<>>] of +                        [AttrName, AttrValue] -> +                            {AttrName, AttrValue, Residual}; +                        _Other -> +                            throw({error, malformed_pax_record}) +                    end; +                _Other -> +                    throw({error, malformed_pax_record}) +            end; +        _Other -> +            throw({error, malformed_pax_record}) +    end. + +get_real_name(#reg_file_reader{handle=Handle,num_bytes=0}) -> +    {"", Handle}; +get_real_name(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) -> +    case do_read(Handle0, NumBytes) of +        {ok, RealName, Handle1} -> +            {RealName, Handle1}; +        {error, _} = Err -> +            throw(Err) +    end; +get_real_name(#sparse_file_reader{num_bytes=NumBytes}=Reader0) -> +    case do_read(Reader0, NumBytes) of +        {ok, RealName, Reader1} -> +            {RealName, Reader1}; +        {error, _} = Err -> +            throw(Err) +    end. + +%% Skip the remaining bytes for the current file entry +skip_file(#reg_file_reader{handle=Handle0,pos=Pos,size=Size}=Reader) -> +    Padding = skip_padding(Size), +    AbsPos = Handle0#reader.pos + (Size-Pos) + Padding, +    case do_position(Handle0, AbsPos) of +        {ok, _, Handle1} -> +            Reader#reg_file_reader{handle=Handle1,num_bytes=0,pos=Size}; +        Err -> +            throw(Err) +    end; +skip_file(#sparse_file_reader{pos=Pos,size=Size}=Reader) -> +    case do_read(Reader, Size-Pos) of +        {ok, _, Reader2} -> +            Reader2; +        Err -> +            throw(Err) +    end. + +skip_padding(0) -> +    0; +skip_padding(Size) when (Size rem ?BLOCK_SIZE) =:= 0 -> +    0; +skip_padding(Size) when Size =< ?BLOCK_SIZE -> +    ?BLOCK_SIZE - Size; +skip_padding(Size) -> +    ?BLOCK_SIZE - (Size rem ?BLOCK_SIZE). + +skip_unread(#reader{pos=Pos}=Reader0) when (Pos rem ?BLOCK_SIZE) > 0 -> +    Padding = skip_padding(Pos + ?BLOCK_SIZE), +    AbsPos = Pos + Padding, +    case do_position(Reader0, AbsPos) of +        {ok, _, Reader1} -> +            {ok, Reader1}; +        Err -> +            throw(Err) +    end; +skip_unread(#reader{}=Reader) -> +    {ok, Reader}; +skip_unread(#reg_file_reader{handle=Handle,num_bytes=0}) -> +    skip_unread(Handle); +skip_unread(#reg_file_reader{}=Reader) -> +    #reg_file_reader{handle=Handle} = skip_file(Reader), +    {ok, Handle}; +skip_unread(#sparse_file_reader{handle=Handle,num_bytes=0}) -> +    skip_unread(Handle); +skip_unread(#sparse_file_reader{}=Reader) -> +    #sparse_file_reader{handle=Handle} = skip_file(Reader), +    {ok, Handle}. + +write_extracted_element(#tar_header{name=Name,typeflag=Type}, +                        Bin, +                        #read_opts{output=memory}=Opts) -> +    case typeflag(Type) of +        regular -> +            read_verbose(Opts, "x ~ts~n", [Name]), +            {ok, {Name, Bin}}; +        _ -> +            ok +    end; +write_extracted_element(#tar_header{name=Name0}=Header, Bin, Opts) -> +    Name1 = make_safe_path(Name0, Opts), +    Created = +        case typeflag(Header#tar_header.typeflag) of +            regular -> +                create_regular(Name1, Name0, Bin, Opts); +            directory -> +                read_verbose(Opts, "x ~ts~n", [Name0]), +                create_extracted_dir(Name1, Opts); +            symlink -> +                read_verbose(Opts, "x ~ts~n", [Name0]), +                create_symlink(Name1, Header#tar_header.linkname, Opts); +            Device when Device =:= char orelse Device =:= block -> +                %% char/block devices will be created as empty files +                %% and then have their major/minor device set later +                create_regular(Name1, Name0, <<>>, Opts); +            fifo -> +                %% fifo devices will be created as empty files +                create_regular(Name1, Name0, <<>>, Opts); +            Other -> % Ignore. +                read_verbose(Opts, "x ~ts - unsupported type ~p~n", +                             [Name0, Other]), +                not_written +        end, +    case Created of +        ok  -> set_extracted_file_info(Name1, Header); +        not_written -> ok +    end. + +make_safe_path([$/|Path], Opts) -> +    make_safe_path(Path, Opts); +make_safe_path(Path, #read_opts{cwd=Cwd}) -> +    case r3_hex_filename:safe_relative_path(Path) of +        unsafe -> +            throw({error,{Path,unsafe_path}}); +        SafePath -> +            filename:absname(SafePath, Cwd) +    end. + +create_regular(Name, NameInArchive, Bin, Opts) -> +    case write_extracted_file(Name, Bin, Opts) of +        not_written -> +            read_verbose(Opts, "x ~ts - exists, not created~n", [NameInArchive]), +            not_written; +        Ok -> +            read_verbose(Opts, "x ~ts~n", [NameInArchive]), +            Ok +    end. + +create_extracted_dir(Name, _Opts) -> +    case file:make_dir(Name) of +        ok -> ok; +        {error,enotsup} -> not_written; +        {error,eexist} -> not_written; +        {error,enoent} -> make_dirs(Name, dir); +        {error,Reason} -> throw({error, Reason}) +    end. + +create_symlink(Name, Linkname, Opts) -> +    case file:make_symlink(Linkname, Name) of +        ok -> ok; +        {error,enoent} -> +            ok = make_dirs(Name, file), +            create_symlink(Name, Linkname, Opts); +        {error,eexist} -> not_written; +        {error,enotsup} -> +            read_verbose(Opts, "x ~ts - symbolic links not supported~n", [Name]), +            not_written; +        {error,Reason} -> throw({error, Reason}) +    end. + +write_extracted_file(Name, Bin, Opts) -> +    Write = +        case Opts#read_opts.keep_old_files of +            true -> +                case file:read_file_info(Name) of +                    {ok, _} -> false; +                    _ -> true +                end; +            false -> true +        end, +    case Write of +        true  -> write_file(Name, Bin); +        false -> not_written +    end. + +write_file(Name, Bin) -> +    case file:write_file(Name, Bin) of +        ok -> ok; +        {error,enoent} -> +            case make_dirs(Name, file) of +                ok -> +                    write_file(Name, Bin); +                {error,Reason} -> +                    throw({error, Reason}) +            end; +        {error,Reason} -> +            throw({error, Reason}) +    end. + +set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_SYMLINK}) -> ok; +set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_LINK})    -> ok; +set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_CHAR}=Header) -> +    set_device_info(Name, Header); +set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_BLOCK}=Header) -> +    set_device_info(Name, Header); +set_extracted_file_info(Name, #tar_header{mtime=Mtime,mode=Mode}) -> +    Info = #file_info{mode=Mode, mtime=Mtime}, +    file:write_file_info(Name, Info, [{time, posix}]). + +set_device_info(Name, #tar_header{}=Header) -> +    Mtime = Header#tar_header.mtime, +    Mode = Header#tar_header.mode, +    Devmajor = Header#tar_header.devmajor, +    Devminor = Header#tar_header.devminor, +    Info = #file_info{ +              mode=Mode, +              mtime=Mtime, +              major_device=Devmajor, +              minor_device=Devminor +             }, +    file:write_file_info(Name, Info). + +%% Makes all directories leading up to the file. + +make_dirs(Name, file) -> +    filelib:ensure_dir(Name); +make_dirs(Name, dir) -> +    filelib:ensure_dir(filename:join(Name,"*")). + +%% Prints the message on if the verbose option is given (for reading). +read_verbose(#read_opts{verbose=true}, Format, Args) -> +    io:format(Format, Args); +read_verbose(_, _, _) -> +    ok. + +%% Prints the message on if the verbose option is given. +add_verbose(#add_opts{verbose=true}, Format, Args) -> +    io:format(Format, Args); +add_verbose(_, _, _) -> +    ok. + +%%%%%%%%%%%%%%%%%% +%% I/O primitives +%%%%%%%%%%%%%%%%%% + +do_write(#reader{handle=Handle,func=Fun}=Reader0, Data) +  when is_function(Fun,2) -> +    case Fun(write,{Handle,Data}) of +        ok -> +            {ok, Pos, Reader1} = do_position(Reader0, {cur,0}), +            {ok, Reader1#reader{pos=Pos}}; +        {error, _} = Err -> +            Err +    end. + +do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=0}=Opts) +  when is_function(Fun, 2) -> +    do_copy(Reader, Source, Opts#add_opts{chunk_size=65536}); +do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=ChunkSize}) +    when is_function(Fun, 2) -> +    case file:open(Source, [read, binary]) of +        {ok, SourceFd} -> +            case copy_chunked(Reader, SourceFd, ChunkSize, 0) of +                {ok, _Copied, _Reader2} = Ok-> +                    _ = file:close(SourceFd), +                    Ok; +                Err -> +                    _ = file:close(SourceFd), +                    throw(Err) +            end; +        Err -> +            throw(Err) +    end. + +copy_chunked(#reader{}=Reader, Source, ChunkSize, Copied) -> +    case file:read(Source, ChunkSize) of +        {ok, Bin} -> +            {ok, Reader2} = do_write(Reader, Bin), +            copy_chunked(Reader2, Source, ChunkSize, Copied+byte_size(Bin)); +        eof -> +            {ok, Copied, Reader}; +        Other -> +            Other +    end. + + +do_position(#reader{handle=Handle,func=Fun}=Reader, Pos) +  when is_function(Fun,2)-> +    case Fun(position, {Handle,Pos}) of +        {ok, NewPos} -> +            %% since Pos may not always be an absolute seek, +            %% make sure we update the reader with the new absolute position +            {ok, AbsPos} = Fun(position, {Handle, {cur, 0}}), +            {ok, NewPos, Reader#reader{pos=AbsPos}}; +        Other -> +            Other +    end. + +do_read(#reg_file_reader{handle=Handle,pos=Pos,size=Size}=Reader, Len) -> +    NumBytes = Size - Pos, +    ActualLen = if NumBytes - Len < 0 -> NumBytes; true -> Len end, +    case do_read(Handle, ActualLen) of +        {ok, Bin, Handle2} -> +            NewPos = Pos + ActualLen, +            NumBytes2 = Size - NewPos, +            Reader1 = Reader#reg_file_reader{ +                        handle=Handle2, +                        pos=NewPos, +                        num_bytes=NumBytes2}, +            {ok, Bin, Reader1}; +        Other -> +            Other +    end; +do_read(#sparse_file_reader{}=Reader, Len) -> +    do_sparse_read(Reader, Len); +do_read(#reader{pos=Pos,handle=Handle,func=Fun}=Reader, Len) +  when is_function(Fun,2)-> +    %% Always convert to binary internally +    case Fun(read2,{Handle,Len}) of +        {ok, List} when is_list(List) -> +            Bin = list_to_binary(List), +            NewPos = Pos+byte_size(Bin), +            {ok, Bin, Reader#reader{pos=NewPos}}; +        {ok, Bin} when is_binary(Bin) -> +            NewPos = Pos+byte_size(Bin), +            {ok, Bin, Reader#reader{pos=NewPos}}; +        Other -> +            Other +    end. + + +do_sparse_read(Reader, Len) -> +    do_sparse_read(Reader, Len, <<>>). + +do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{num_bytes=0}|Entries] +                                  }=Reader0, Len, Acc) -> +    %% skip all empty fragments +    Reader1 = Reader0#sparse_file_reader{sparse_map=Entries}, +    do_sparse_read(Reader1, Len, Acc); +do_sparse_read(#sparse_file_reader{sparse_map=[], +                                   pos=Pos,size=Size}=Reader0, Len, Acc) +  when Pos < Size -> +    %% if there are no more fragments, it is possible that there is one last sparse hole +    %% this behaviour matches the BSD tar utility +    %% however, GNU tar stops returning data even if we haven't reached the end +    {ok, Bin, Reader1} = read_sparse_hole(Reader0, Size, Len), +    do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); +do_sparse_read(#sparse_file_reader{sparse_map=[]}=Reader, _Len, Acc) -> +    {ok, Acc, Reader}; +do_sparse_read(#sparse_file_reader{}=Reader, 0, Acc) -> +    {ok, Acc, Reader}; +do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{offset=Offset}|_], +                                   pos=Pos}=Reader0, Len, Acc) +  when Pos < Offset -> +    {ok, Bin, Reader1} = read_sparse_hole(Reader0, Offset, Offset-Pos), +    do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); +do_sparse_read(#sparse_file_reader{sparse_map=[Entry|Entries], +                                   pos=Pos}=Reader0, Len, Acc) -> +    %% we're in a data fragment, so read from it +    %% end offset of fragment +    EndPos = Entry#sparse_entry.offset + Entry#sparse_entry.num_bytes, +    %% bytes left in fragment +    NumBytes = EndPos - Pos, +    ActualLen = if Len > NumBytes -> NumBytes; true -> Len end, +    case do_read(Reader0#sparse_file_reader.handle, ActualLen) of +        {ok, Bin, Handle} -> +            BytesRead = byte_size(Bin), +            ActualEndPos = Pos+BytesRead, +            Reader1 = if ActualEndPos =:= EndPos -> +                              Reader0#sparse_file_reader{sparse_map=Entries}; +                         true -> +                              Reader0 +                      end, +            Size = Reader1#sparse_file_reader.size, +            NumBytes2 = Size - ActualEndPos, +            Reader2 = Reader1#sparse_file_reader{ +                        handle=Handle, +                        pos=ActualEndPos, +                        num_bytes=NumBytes2}, +            do_sparse_read(Reader2, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); +        Other -> +            Other +    end. + +%% Reads a sparse hole ending at Offset +read_sparse_hole(#sparse_file_reader{pos=Pos}=Reader, Offset, Len) -> +    N = Offset - Pos, +    N2 = if N > Len -> +                 Len; +            true -> +                 N +         end, +    Bin = <<0:N2/unit:8>>, +    NumBytes = Reader#sparse_file_reader.size - (Pos+N2), +    {ok, Bin, Reader#sparse_file_reader{ +                num_bytes=NumBytes, +                pos=Pos+N2}}. + +-spec do_close(reader()) -> ok | {error, term()}. +do_close(#reader{handle=Handle,func=Fun}) when is_function(Fun,2) -> +    Fun(close,Handle). + +%%%%%%%%%%%%%%%%%% +%% Option parsing +%%%%%%%%%%%%%%%%%% + +extract_opts(List) -> +    extract_opts(List, default_options()). + +table_opts(List) -> +    read_opts(List, default_options()). + +default_options() -> +    {ok, Cwd} = file:get_cwd(), +    #read_opts{cwd=Cwd}. + +extract_opts([keep_old_files|Rest], Opts) -> +    extract_opts(Rest, Opts#read_opts{keep_old_files=true}); +extract_opts([{cwd, Cwd}|Rest], Opts) -> +    extract_opts(Rest, Opts#read_opts{cwd=Cwd}); +extract_opts([{files, Files}|Rest], Opts) -> +    Set = ordsets:from_list(Files), +    extract_opts(Rest, Opts#read_opts{files=Set}); +extract_opts([memory|Rest], Opts) -> +    extract_opts(Rest, Opts#read_opts{output=memory}); +extract_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> +    extract_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]}); +extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> +    extract_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]}); +extract_opts([verbose|Rest], Opts) -> +    extract_opts(Rest, Opts#read_opts{verbose=true}); +extract_opts([Other|Rest], Opts) -> +    extract_opts(Rest, read_opts([Other], Opts)); +extract_opts([], Opts) -> +    Opts. + +read_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> +    read_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]}); +read_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> +    read_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]}); +read_opts([verbose|Rest], Opts) -> +    read_opts(Rest, Opts#read_opts{verbose=true}); +read_opts([_|Rest], Opts) -> +    read_opts(Rest, Opts); +read_opts([], Opts) -> +    Opts. diff --git a/src/r3_hex_erl_tar.hrl b/src/r3_hex_erl_tar.hrl new file mode 100644 index 0000000..4834b2d --- /dev/null +++ b/src/r3_hex_erl_tar.hrl @@ -0,0 +1,405 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +% Copied from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/erl_tar.hrl + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%%     http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%% Options used when adding files to a tar archive. +-record(add_opts, { +          read_info,          %% Fun to use for read file/link info. +          chunk_size = 0,     %% For file reading when sending to sftp. 0=do not chunk +          verbose = false,    %% Verbose on/off. +          atime = undefined, +          mtime = undefined, +          ctime = undefined, +          uid = 0, +          gid = 0}). +-type add_opts() :: #add_opts{}. + +%% Options used when reading a tar archive. +-record(read_opts, { +          cwd                    :: string(),  %% Current working directory. +          keep_old_files = false :: boolean(), %% Owerwrite or not. +          files = all,                         %% Set of files to extract (or all) +          output = file :: 'file' | 'memory', +          open_mode = [],                      %% Open mode options. +          verbose = false :: boolean()}).      %% Verbose on/off. +-type read_opts() :: #read_opts{}. + +-type add_opt() :: dereference | +                   verbose | +                   {chunks, pos_integer()}. + +-type extract_opt() :: {cwd, string()} | +                       {files, [string()]} | +                       compressed | +                       cooked | +                       memory | +                       keep_old_files | +                       verbose. + +-type create_opt() :: compressed | +                      cooked | +                      dereference | +                      verbose. + +-type filelist() :: [file:filename() | +                     {string(), binary()} | +                     {string(), file:filename()}]. + +-type tar_time() :: non_neg_integer(). + +%% The tar header, once fully parsed. +-record(tar_header, { +          name = "" :: string(),                %% name of header file entry +          mode = 8#100644 :: non_neg_integer(), %% permission and mode bits +          uid = 0 :: non_neg_integer(),         %% user id of owner +          gid = 0 :: non_neg_integer(),         %% group id of owner +          size = 0 :: non_neg_integer(),        %% length in bytes +          mtime :: tar_time(),                  %% modified time +          typeflag :: char(),                   %% type of header entry +          linkname = "" :: string(),            %% target name of link +          uname = "" :: string(),               %% user name of owner +          gname = "" :: string(),               %% group name of owner +          devmajor = 0 :: non_neg_integer(),    %% major number of character or block device +          devminor = 0 :: non_neg_integer(),    %% minor number of character or block device +          atime :: tar_time(),                  %% access time +          ctime :: tar_time()                   %% status change time +         }). +-type tar_header() :: #tar_header{}. + +%% Metadata for a sparse file fragment +-record(sparse_entry, { +         offset = 0 :: non_neg_integer(), +         num_bytes = 0 :: non_neg_integer()}). +-type sparse_entry() :: #sparse_entry{}. +%% Contains metadata about fragments of a sparse file +-record(sparse_array, { +          entries = [] :: [sparse_entry()], +          is_extended = false :: boolean(), +          max_entries = 0 :: non_neg_integer()}). +-type sparse_array() :: #sparse_array{}. +%% A subset of tar header fields common to all tar implementations +-record(header_v7, { +          name :: binary(), +          mode :: binary(), %% octal +          uid :: binary(), %% integer +          gid :: binary(), %% integer +          size :: binary(), %% integer +          mtime :: binary(), %% integer +          checksum :: binary(), %% integer +          typeflag :: byte(), %% char +          linkname :: binary()}). +-type header_v7() :: #header_v7{}. +%% The set of fields specific to GNU tar formatted archives +-record(header_gnu, { +          header_v7 :: header_v7(), +          magic :: binary(), +          version :: binary(), +          uname :: binary(), +          gname :: binary(), +          devmajor :: binary(), %% integer +          devminor :: binary(), %% integer +          atime :: binary(), %% integer +          ctime :: binary(), %% integer +          sparse :: sparse_array(), +          real_size :: binary()}). %% integer +-type header_gnu() :: #header_gnu{}. +%% The set of fields specific to STAR-formatted archives +-record(header_star, { +          header_v7 :: header_v7(), +          magic :: binary(), +          version :: binary(), +          uname :: binary(), +          gname :: binary(), +          devmajor :: binary(), %% integer +          devminor :: binary(), %% integer +          prefix :: binary(), +          atime :: binary(), %% integer +          ctime :: binary(), %% integer +          trailer :: binary()}). +-type header_star() :: #header_star{}. +%% The set of fields specific to USTAR-formatted archives +-record(header_ustar, { +          header_v7 :: header_v7(), +          magic :: binary(), +          version :: binary(), +          uname :: binary(), +          gname :: binary(), +          devmajor :: binary(), %% integer +          devminor :: binary(), %% integer +          prefix :: binary()}). +-type header_ustar() :: #header_ustar{}. + +-type header_fields() :: header_v7() | +                         header_gnu() | +                         header_star() | +                         header_ustar(). + +%% The overall tar reader, it holds the low-level file handle, +%% its access, position, and the I/O primitives wrapper. +-record(reader, { +          handle :: file:io_device() | term(), +          access :: read | write | ram, +          pos = 0 :: non_neg_integer(), +          func :: file_op() +         }). +-type reader() :: #reader{}. +%% A reader for a regular file within the tar archive, +%% It tracks its current state relative to that file. +-record(reg_file_reader, { +          handle :: reader(), +          num_bytes = 0, +          pos = 0, +          size = 0 +         }). +-type reg_file_reader() :: #reg_file_reader{}. +%% A reader for a sparse file within the tar archive, +%% It tracks its current state relative to that file. +-record(sparse_file_reader, { +          handle :: reader(), +          num_bytes = 0, %% bytes remaining +          pos = 0, %% pos +          size = 0, %% total size of file +          sparse_map = #sparse_array{} +         }). +-type sparse_file_reader() :: #sparse_file_reader{}. + +%% Types for the readers +-type reader_type() :: reader() | reg_file_reader() | sparse_file_reader(). +-type handle() :: file:io_device() | term(). + +%% Type for the I/O primitive wrapper function +-type file_op() :: fun((write | close | read2 | position, +                       {handle(), iodata()} | handle() | {handle(), non_neg_integer()} +                        | {handle(), non_neg_integer()}) -> +                              ok | eof | {ok, string() | binary()} | {ok, non_neg_integer()} +                                 | {error, term()}). + +%% These constants (except S_IFMT) are +%% used to determine what type of device +%% a file is. Namely, `S_IFMT band file_info.mode` +%% will equal one of these contants, and tells us +%% which type it is. The stdlib file_info record +%% does not differentiate between device types, and +%% will not allow us to differentiate between sockets +%% and named pipes. These constants are pulled from libc. +-define(S_IFMT, 61440). +-define(S_IFSOCK, 49152). %% socket +-define(S_FIFO, 4096).    %% fifo/named pipe +-define(S_IFBLK, 24576).  %% block device +-define(S_IFCHR, 8192).   %% character device + +%% Typeflag constants for the tar header +-define(TYPE_REGULAR, $0).         %% regular file +-define(TYPE_REGULAR_A, 0).        %% regular file +-define(TYPE_LINK, $1).            %% hard link +-define(TYPE_SYMLINK, $2).         %% symbolic link +-define(TYPE_CHAR, $3).            %% character device node +-define(TYPE_BLOCK, $4).           %% block device node +-define(TYPE_DIR, $5).             %% directory +-define(TYPE_FIFO, $6).            %% fifo node +-define(TYPE_CONT, $7).            %% reserved +-define(TYPE_X_HEADER, $x).        %% extended header +-define(TYPE_X_GLOBAL_HEADER, $g). %% global extended header +-define(TYPE_GNU_LONGNAME, $L).    %% next file has a long name +-define(TYPE_GNU_LONGLINK, $K).    %% next file symlinks to a file with a long name +-define(TYPE_GNU_SPARSE, $S).      %% sparse file + +%% Mode constants from tar spec +-define(MODE_ISUID, 4000).    %% set uid +-define(MODE_ISGID, 2000).    %% set gid +-define(MODE_ISVTX, 1000).    %% save text (sticky bit) +-define(MODE_ISDIR, 40000).   %% directory +-define(MODE_ISFIFO, 10000).  %% fifo +-define(MODE_ISREG, 100000).  %% regular file +-define(MODE_ISLNK, 120000).  %% symbolic link +-define(MODE_ISBLK, 60000).   %% block special file +-define(MODE_ISCHR, 20000).   %% character special file +-define(MODE_ISSOCK, 140000). %% socket + +%% Keywords for PAX extended header +-define(PAX_ATIME, <<"atime">>). +-define(PAX_CHARSET, <<"charset">>). +-define(PAX_COMMENT, <<"comment">>). +-define(PAX_CTIME, <<"ctime">>). %% ctime is not a valid pax header +-define(PAX_GID, <<"gid">>). +-define(PAX_GNAME, <<"gname">>). +-define(PAX_LINKPATH, <<"linkpath">>). +-define(PAX_MTIME, <<"mtime">>). +-define(PAX_PATH, <<"path">>). +-define(PAX_SIZE, <<"size">>). +-define(PAX_UID, <<"uid">>). +-define(PAX_UNAME, <<"uname">>). +-define(PAX_XATTR, <<"SCHILY.xattr.">>). +-define(PAX_XATTR_STR, "SCHILY.xattr."). +-define(PAX_NONE, <<"">>). + +%% Tar format constants +%% Unknown format +-define(FORMAT_UNKNOWN, 0). +%% The format of the original Unix V7 tar tool prior to standardization +-define(FORMAT_V7, 1). +%% The old and new GNU formats, incompatible with USTAR. +%% This covers the old GNU sparse extension, but it does +%% not cover the GNU sparse extensions using PAX headers, +%% versions 0.0, 0.1, and 1.0; these fall under the PAX format. +-define(FORMAT_GNU, 2). +%% Schily's tar format, which is incompatible with USTAR. +%% This does not cover STAR extensions to the PAX format; these +%% fall under the PAX format. +-define(FORMAT_STAR, 3). +%% USTAR is the former standardization of tar defined in POSIX.1-1988, +%% it is incompatible with the GNU and STAR formats. +-define(FORMAT_USTAR, 4). +%% PAX is the latest standardization of tar defined in POSIX.1-2001. +%% This is an extension of USTAR and is "backwards compatible" with it. +%% +%% Some newer formats add their own extensions to PAX, such as GNU sparse +%% files and SCHILY extended attributes. Since they are backwards compatible +%% with PAX, they will be labelled as "PAX". +-define(FORMAT_PAX, 5). + +%% Magic constants +-define(MAGIC_GNU, <<"ustar ">>). +-define(VERSION_GNU, <<" \x00">>). +-define(MAGIC_USTAR, <<"ustar\x00">>). +-define(VERSION_USTAR, <<"00">>). +-define(TRAILER_STAR, <<"tar\x00">>). + +%% Size constants +-define(BLOCK_SIZE, 512). %% size of each block in a tar stream +-define(NAME_SIZE, 100). %% max length of the name field in USTAR format +-define(PREFIX_SIZE, 155). %% max length of the prefix field in USTAR format + +%% Maximum size of a nanosecond value as an integer +-define(MAX_NANO_INT_SIZE, 9). +%% Maximum size of a 64-bit signed integer +-define(MAX_INT64, (1 bsl 63 - 1)). + +-define(PAX_GNU_SPARSE_NUMBLOCKS, <<"GNU.sparse.numblocks">>). +-define(PAX_GNU_SPARSE_OFFSET, <<"GNU.sparse.offset">>). +-define(PAX_GNU_SPARSE_NUMBYTES, <<"GNU.sparse.numbytes">>). +-define(PAX_GNU_SPARSE_MAP, <<"GNU.sparse.map">>). +-define(PAX_GNU_SPARSE_NAME, <<"GNU.sparse.name">>). +-define(PAX_GNU_SPARSE_MAJOR, <<"GNU.sparse.major">>). +-define(PAX_GNU_SPARSE_MINOR, <<"GNU.sparse.minor">>). +-define(PAX_GNU_SPARSE_SIZE, <<"GNU.sparse.size">>). +-define(PAX_GNU_SPARSE_REALSIZE, <<"GNU.sparse.realsize">>). + +-define(V7_NAME, 0). +-define(V7_NAME_LEN, 100). +-define(V7_MODE, 100). +-define(V7_MODE_LEN, 8). +-define(V7_UID, 108). +-define(V7_UID_LEN, 8). +-define(V7_GID, 116). +-define(V7_GID_LEN, 8). +-define(V7_SIZE, 124). +-define(V7_SIZE_LEN, 12). +-define(V7_MTIME, 136). +-define(V7_MTIME_LEN, 12). +-define(V7_CHKSUM, 148). +-define(V7_CHKSUM_LEN, 8). +-define(V7_TYPE, 156). +-define(V7_TYPE_LEN, 1). +-define(V7_LINKNAME, 157). +-define(V7_LINKNAME_LEN, 100). + +-define(STAR_TRAILER, 508). +-define(STAR_TRAILER_LEN, 4). + +-define(USTAR_MAGIC, 257). +-define(USTAR_MAGIC_LEN, 6). +-define(USTAR_VERSION, 263). +-define(USTAR_VERSION_LEN, 2). +-define(USTAR_UNAME, 265). +-define(USTAR_UNAME_LEN, 32). +-define(USTAR_GNAME, 297). +-define(USTAR_GNAME_LEN, 32). +-define(USTAR_DEVMAJ, 329). +-define(USTAR_DEVMAJ_LEN, 8). +-define(USTAR_DEVMIN, 337). +-define(USTAR_DEVMIN_LEN, 8). +-define(USTAR_PREFIX, 345). +-define(USTAR_PREFIX_LEN, 155). + +-define(GNU_MAGIC, 257). +-define(GNU_MAGIC_LEN, 6). +-define(GNU_VERSION, 263). +-define(GNU_VERSION_LEN, 2). + +%% ?BLOCK_SIZE of zero-bytes. +%% Two of these in a row mark the end of an archive. +-define(ZERO_BLOCK, <<0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0, +                      0,0,0,0,0,0,0,0,0,0,0,0>>). + +-define(BILLION, 1000000000). + +-define(EPOCH, {{1970,1,1}, {0,0,0}}). diff --git a/src/r3_hex_filename.erl b/src/r3_hex_filename.erl new file mode 100644 index 0000000..f04c72f --- /dev/null +++ b/src/r3_hex_filename.erl @@ -0,0 +1,60 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +% @private +% Excerpt from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/filename.erl#L761-L788 +% with modifications for changing local function calls to remote function calls +% to the `filename` module, for the functions `pathtype/1`, `split/1`, and `join/1` +% +% safe_relative_path/1 was not present in earlier OTP releases. + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%%     http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(r3_hex_filename). +-export([safe_relative_path/1]). + +safe_relative_path(Path) -> +    case filename:pathtype(Path) of +        relative -> +            Cs0 = filename:split(Path), +            safe_relative_path_1(Cs0, []); +        _ -> +            unsafe +    end. + +safe_relative_path_1(["."|T], Acc) -> +    safe_relative_path_1(T, Acc); +safe_relative_path_1([<<".">>|T], Acc) -> +    safe_relative_path_1(T, Acc); +safe_relative_path_1([".."|T], Acc) -> +    climb(T, Acc); +safe_relative_path_1([<<"..">>|T], Acc) -> +    climb(T, Acc); +safe_relative_path_1([H|T], Acc) -> +    safe_relative_path_1(T, [H|Acc]); +safe_relative_path_1([], []) -> +    []; +safe_relative_path_1([], Acc) -> +    filename:join(lists:reverse(Acc)). + +climb(_, []) -> +    unsafe; +climb(T, [_|Acc]) -> +    safe_relative_path_1(T, Acc). diff --git a/src/r3_hex_http.erl b/src/r3_hex_http.erl new file mode 100644 index 0000000..b4794b5 --- /dev/null +++ b/src/r3_hex_http.erl @@ -0,0 +1,43 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_http). +-export([request/5]). +-ifdef(TEST). +-export([user_agent/1]). +-endif. +-include_lib("r3_hex_core.hrl"). + +-type method() :: get | post | put | patch | delete. +-type status() :: non_neg_integer(). +-type headers() :: #{binary() => binary()}. +-type body() :: {ContentType :: binary(), Body :: binary()} | undefined. +-type adapter_config() :: map(). + +-callback request(method(), URI :: binary(), headers(), body(), adapter_config()) -> +    {ok, status(), headers(), binary()} | +    {error, term()}. + +-spec request(r3_hex_core:config(), method(), URI :: binary(), headers(), body()) -> +    {ok, {status(), headers(), binary()}} | {error, term()}. +request(Config, Method, URI, Headers, Body) when is_binary(URI) and is_map(Headers) -> +    Adapter = maps:get(http_adapter, Config), +    UserAgentFragment = maps:get(http_user_agent_fragment, Config), +    Headers2 = put_new(<<"user-agent">>, user_agent(UserAgentFragment), Headers), +    AdapterConfig = maps:get(http_adapter_config, Config, #{}), +    Adapter:request(Method, URI, Headers2, Body, AdapterConfig). + +user_agent(UserAgentFragment) -> +    OTPRelease = erlang:system_info(otp_release), +    ERTSVersion = erlang:system_info(version), +    OTPString = " (OTP/" ++ OTPRelease ++ ") (erts/" ++ ERTSVersion ++ ")", +    iolist_to_binary(["hex_core/", ?HEX_CORE_VERSION, " ", UserAgentFragment, OTPString]). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +put_new(Key, Value, Map) -> +    case maps:find(Key, Map) of +        {ok, _} -> Map; +        error -> maps:put(Key, Value, Map) +    end. diff --git a/src/r3_hex_http_httpc.erl b/src/r3_hex_http_httpc.erl new file mode 100644 index 0000000..0be9d16 --- /dev/null +++ b/src/r3_hex_http_httpc.erl @@ -0,0 +1,39 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @hidden + +-module(r3_hex_http_httpc). +-behaviour(r3_hex_http). +-export([request/5]). + +%%==================================================================== +%% API functions +%%==================================================================== + +request(Method, URI, ReqHeaders, Body, AdapterConfig) -> +    Profile = maps:get(profile, AdapterConfig, default), +    Request = build_request(URI, ReqHeaders, Body), +    {ok, {{_, StatusCode, _}, RespHeaders, RespBody}} = +        httpc:request(Method, Request, [], [{body_format, binary}], Profile), +    RespHeaders2 = load_headers(RespHeaders), +    {ok, {StatusCode, RespHeaders2, RespBody}}. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +build_request(URI, ReqHeaders, Body) -> +    build_request2(binary_to_list(URI), dump_headers(ReqHeaders), Body). + +build_request2(URI, ReqHeaders, undefined) -> +    {URI, ReqHeaders}; +build_request2(URI, ReqHeaders, {ContentType, Body}) -> +    {URI, ReqHeaders, ContentType, Body}. + +dump_headers(Map) -> +    maps:fold(fun(K, V, Acc) -> +        [{binary_to_list(K), binary_to_list(V)} | Acc] end, [], Map). + +load_headers(List) -> +    lists:foldl(fun({K, V}, Acc) -> +        maps:put(list_to_binary(K), list_to_binary(V), Acc) end, #{}, List). diff --git a/src/r3_hex_pb_names.erl b/src/r3_hex_pb_names.erl new file mode 100644 index 0000000..455092c --- /dev/null +++ b/src/r3_hex_pb_names.erl @@ -0,0 +1,735 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_names). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Names'() :: +      #{packages                => ['Package'()],   % = 1 +        repository              => iodata()         % = 2 +       }. + +-type 'Package'() :: +      #{name                    => iodata()         % = 1 +       }. + +-export_type(['Names'/0, 'Package'/0]). + +-spec encode_msg('Names'() | 'Package'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> +    encode_msg(Msg, MsgName, []). + +-spec encode_msg('Names'() | 'Package'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> +    verify_msg(Msg, MsgName, Opts), +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Names' -> e_msg_Names(id(Msg, TrUserData), TrUserData); +      'Package' -> +	  e_msg_Package(id(Msg, TrUserData), TrUserData) +    end. + + +e_msg_Names(Msg, TrUserData) -> +    e_msg_Names(Msg, <<>>, TrUserData). + + +e_msg_Names(#{repository := F2} = M, Bin, TrUserData) -> +    B1 = case M of +	   #{packages := F1} -> +	       TrF1 = id(F1, TrUserData), +	       if TrF1 == [] -> Bin; +		  true -> e_field_Names_packages(TrF1, Bin, TrUserData) +	       end; +	   _ -> Bin +	 end, +    begin +      TrF2 = id(F2, TrUserData), +      e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) +    end. + +e_msg_Package(Msg, TrUserData) -> +    e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F1}, Bin, TrUserData) -> +    begin +      TrF1 = id(F1, TrUserData), +      e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) +    end. + +e_mfield_Names_packages(Msg, Bin, TrUserData) -> +    SubBin = e_msg_Package(Msg, <<>>, TrUserData), +    Bin2 = e_varint(byte_size(SubBin), Bin), +    <<Bin2/binary, SubBin/binary>>. + +e_field_Names_packages([Elem | Rest], Bin, +		       TrUserData) -> +    Bin2 = <<Bin/binary, 10>>, +    Bin3 = e_mfield_Names_packages(id(Elem, TrUserData), +				   Bin2, TrUserData), +    e_field_Names_packages(Rest, Bin3, TrUserData); +e_field_Names_packages([], Bin, _TrUserData) -> Bin. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> +    e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> +    e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> +    <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> +    <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> +    Utf8 = unicode:characters_to_binary(S), +    Bin2 = e_varint(byte_size(Utf8), Bin), +    <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_binary(Bytes) -> +    Bin2 = e_varint(byte_size(Bytes), Bin), +    <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_list(Bytes) -> +    BytesBin = iolist_to_binary(Bytes), +    Bin2 = e_varint(byte_size(BytesBin), Bin), +    <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> +    <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> +    <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> +    <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> +    <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> +    <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> +    <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> +    Bin2 = <<Bin/binary, (N band 127 bor 128)>>, +    e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> +    decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> +    TrUserData = proplists:get_value(user_data, Opts), +    decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason -> +        StackTrace = erlang:get_stacktrace(), +        error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-endif. + +-endif. + +decode_msg_2_doit('Names', Bin, TrUserData) -> +    id(d_msg_Names(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Package', Bin, TrUserData) -> +    id(d_msg_Package(Bin, TrUserData), TrUserData). + + + +d_msg_Names(Bin, TrUserData) -> +    dfp_read_field_def_Names(Bin, 0, 0, id([], TrUserData), +			     id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Names(<<10, Rest/binary>>, Z1, Z2, +			 F@_1, F@_2, TrUserData) -> +    d_field_Names_packages(Rest, Z1, Z2, F@_1, F@_2, +			   TrUserData); +dfp_read_field_def_Names(<<18, Rest/binary>>, Z1, Z2, +			 F@_1, F@_2, TrUserData) -> +    d_field_Names_repository(Rest, Z1, Z2, F@_1, F@_2, +			     TrUserData); +dfp_read_field_def_Names(<<>>, 0, 0, R1, F@_2, +			 TrUserData) -> +    S1 = #{repository => F@_2}, +    if R1 == '$undef' -> S1; +       true -> S1#{packages => lists_reverse(R1, TrUserData)} +    end; +dfp_read_field_def_Names(Other, Z1, Z2, F@_1, F@_2, +			 TrUserData) -> +    dg_read_field_def_Names(Other, Z1, Z2, F@_1, F@_2, +			    TrUserData). + +dg_read_field_def_Names(<<1:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Names(Rest, N + 7, X bsl N + Acc, +			    F@_1, F@_2, TrUserData); +dg_read_field_def_Names(<<0:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Names_packages(Rest, 0, 0, F@_1, F@_2, +				 TrUserData); +      18 -> +	  d_field_Names_repository(Rest, 0, 0, F@_1, F@_2, +				   TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Names(Rest, 0, 0, F@_1, F@_2, TrUserData); +	    1 -> skip_64_Names(Rest, 0, 0, F@_1, F@_2, TrUserData); +	    2 -> +		skip_length_delimited_Names(Rest, 0, 0, F@_1, F@_2, +					    TrUserData); +	    3 -> +		skip_group_Names(Rest, Key bsr 3, 0, F@_1, F@_2, +				 TrUserData); +	    5 -> skip_32_Names(Rest, 0, 0, F@_1, F@_2, TrUserData) +	  end +    end; +dg_read_field_def_Names(<<>>, 0, 0, R1, F@_2, +			TrUserData) -> +    S1 = #{repository => F@_2}, +    if R1 == '$undef' -> S1; +       true -> S1#{packages => lists_reverse(R1, TrUserData)} +    end. + +d_field_Names_packages(<<1:1, X:7, Rest/binary>>, N, +		       Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Names_packages(Rest, N + 7, X bsl N + Acc, F@_1, +			   F@_2, TrUserData); +d_field_Names_packages(<<0:1, X:7, Rest/binary>>, N, +		       Acc, Prev, F@_2, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bs:Len/binary, Rest2/binary>> = Rest, +			   {id(d_msg_Package(Bs, TrUserData), TrUserData), +			    Rest2} +			 end, +    dfp_read_field_def_Names(RestF, 0, 0, +			     cons(NewFValue, Prev, TrUserData), F@_2, +			     TrUserData). + +d_field_Names_repository(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Names_repository(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, TrUserData); +d_field_Names_repository(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Names(RestF, 0, 0, F@_1, NewFValue, +			     TrUserData). + +skip_varint_Names(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		  F@_1, F@_2, TrUserData) -> +    skip_varint_Names(Rest, Z1, Z2, F@_1, F@_2, TrUserData); +skip_varint_Names(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		  F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, +			     TrUserData). + +skip_length_delimited_Names(<<1:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    skip_length_delimited_Names(Rest, N + 7, X bsl N + Acc, +				F@_1, F@_2, TrUserData); +skip_length_delimited_Names(<<0:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, F@_2, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Names(Rest2, 0, 0, F@_1, F@_2, +			     TrUserData). + +skip_group_Names(Bin, FNum, Z2, F@_1, F@_2, +		 TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Names(Rest, 0, Z2, F@_1, F@_2, +			     TrUserData). + +skip_32_Names(<<_:32, Rest/binary>>, Z1, Z2, F@_1, F@_2, +	      TrUserData) -> +    dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, +			     TrUserData). + +skip_64_Names(<<_:64, Rest/binary>>, Z1, Z2, F@_1, F@_2, +	      TrUserData) -> +    dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, +			     TrUserData). + +d_msg_Package(Bin, TrUserData) -> +    dfp_read_field_def_Package(Bin, 0, 0, +			       id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, +			   F@_1, TrUserData) -> +    d_field_Package_name(Rest, Z1, Z2, F@_1, TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, F@_1, _) -> +    #{name => F@_1}; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, +			   TrUserData) -> +    dg_read_field_def_Package(Other, Z1, Z2, F@_1, +			      TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, +			      F@_1, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Package_name(Rest, 0, 0, F@_1, TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> skip_varint_Package(Rest, 0, 0, F@_1, TrUserData); +	    1 -> skip_64_Package(Rest, 0, 0, F@_1, TrUserData); +	    2 -> +		skip_length_delimited_Package(Rest, 0, 0, F@_1, +					      TrUserData); +	    3 -> +		skip_group_Package(Rest, Key bsr 3, 0, F@_1, +				   TrUserData); +	    5 -> skip_32_Package(Rest, 0, 0, F@_1, TrUserData) +	  end +    end; +dg_read_field_def_Package(<<>>, 0, 0, F@_1, _) -> +    #{name => F@_1}. + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, +		     F@_1, TrUserData) +    when N < 57 -> +    d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, +			 TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, +		     _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, NewFValue, +			       TrUserData). + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, TrUserData) -> +    skip_varint_Package(Rest, Z1, Z2, F@_1, TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, +			       TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, TrUserData) +    when N < 57 -> +    skip_length_delimited_Package(Rest, N + 7, +				  X bsl N + Acc, F@_1, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Package(Rest2, 0, 0, F@_1, +			       TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Package(Rest, 0, Z2, F@_1, +			       TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, +			       TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, +			       TrUserData). + +read_group(Bin, FieldNum) -> +    {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), +    <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, +    {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) +  when N < (32-7) -> +    read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, +          FieldNum) -> +    Key = X bsl N + Acc, +    TagLen1 = TagLen + 1, +    case {Key bsr 3, Key band 7} of +        {FieldNum, 4} -> % 4 = group_end +            {NumBytes, TagLen1}; +        {_, 0} -> % 0 = varint +            read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); +        {_, 1} -> % 1 = bits64 +            <<_:64, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); +        {_, 2} -> % 2 = length_delimited +            read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); +        {_, 3} -> % 3 = group_start +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 4} -> % 4 = group_end +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 5} -> % 5 = bits32 +            <<_:32, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) +    end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> +    read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> +    Len = X bsl N + Acc, +    NumBytes1 = NumBytes + 1, +    <<_:Len/binary, Tl2/binary>> = Tl, +    read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> +    merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Names' -> merge_msg_Names(Prev, New, TrUserData); +      'Package' -> merge_msg_Package(Prev, New, TrUserData) +    end. + +-compile({nowarn_unused_function,merge_msg_Names/3}). +merge_msg_Names(#{} = PMsg, +		#{repository := NFrepository} = NMsg, TrUserData) -> +    S1 = #{repository => NFrepository}, +    case {PMsg, NMsg} of +      {#{packages := PFpackages}, +       #{packages := NFpackages}} -> +	  S1#{packages => +		  'erlang_++'(PFpackages, NFpackages, TrUserData)}; +      {_, #{packages := NFpackages}} -> +	  S1#{packages => NFpackages}; +      {#{packages := PFpackages}, _} -> +	  S1#{packages => PFpackages}; +      {_, _} -> S1 +    end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{}, #{name := NFname}, _) -> +    #{name => NFname}. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> +    verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Names' -> v_msg_Names(Msg, [MsgName], TrUserData); +      'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); +      _ -> mk_type_error(not_a_known_message, Msg, []) +    end. + + +-compile({nowarn_unused_function,v_msg_Names/3}). +v_msg_Names(#{repository := F2} = M, Path, +	    TrUserData) -> +    case M of +      #{packages := F1} -> +	  if is_list(F1) -> +		 _ = [v_msg_Package(Elem, [packages | Path], TrUserData) +		      || Elem <- F1], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, {msg, 'Package'}}, F1, +			       [packages | Path]) +	  end; +      _ -> ok +    end, +    v_type_string(F2, [repository | Path], TrUserData), +    lists:foreach(fun (packages) -> ok; +		      (repository) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Names(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [repository] -- maps:keys(M), 'Names'}, +		  M, Path); +v_msg_Names(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Names'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F1} = M, Path, TrUserData) -> +    v_type_string(F1, [name | Path], TrUserData), +    lists:foreach(fun (name) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, [name] -- maps:keys(M), +		   'Package'}, +		  M, Path); +v_msg_Package(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) +    when is_list(S); is_binary(S) -> +    try unicode:characters_to_binary(S) of +      B when is_binary(B) -> ok; +      {error, _, _} -> +	  mk_type_error(bad_unicode_string, S, Path) +    catch +      error:badarg -> +	  mk_type_error(bad_unicode_string, S, Path) +    end; +v_type_string(X, Path, _TrUserData) -> +    mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> +    Path2 = prettify_path(Path), +    erlang:error({gpb_type_error, +		  {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> +    list_to_atom(string:join(lists:map(fun atom_to_list/1, +				       lists:reverse(PathR)), +			     ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> +    [{{msg, 'Names'}, +      [#{name => packages, fnum => 1, rnum => 2, +	 type => {msg, 'Package'}, occurrence => repeated, +	 opts => []}, +       #{name => repository, fnum => 2, rnum => 3, +	 type => string, occurrence => required, opts => []}]}, +     {{msg, 'Package'}, +      [#{name => name, fnum => 1, rnum => 2, type => string, +	 occurrence => required, opts => []}]}]. + + +get_msg_names() -> ['Names', 'Package']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Names', 'Package']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> +    case find_msg_def(MsgName) of +      Fs when is_list(Fs) -> Fs; +      error -> erlang:error({no_such_msg, MsgName}) +    end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> +    erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Names') -> +    [#{name => packages, fnum => 1, rnum => 2, +       type => {msg, 'Package'}, occurrence => repeated, +       opts => []}, +     #{name => repository, fnum => 2, rnum => 3, +       type => string, occurrence => required, opts => []}]; +find_msg_def('Package') -> +    [#{name => name, fnum => 1, rnum => 2, type => string, +       occurrence => required, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> +    erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> +    "4.3.1". + +gpb_version_as_list() -> +    [4,3,1]. diff --git a/src/r3_hex_pb_package.erl b/src/r3_hex_pb_package.erl new file mode 100644 index 0000000..131a5da --- /dev/null +++ b/src/r3_hex_pb_package.erl @@ -0,0 +1,1699 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_package). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([enum_symbol_by_value_RetirementReason/1, enum_value_by_symbol_RetirementReason/1]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types +-type 'RetirementReason'() :: 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED'. +-export_type(['RetirementReason'/0]). + +%% message types +-type 'Package'() :: +      #{releases                => ['Release'()],   % = 1 +        name                    => iodata(),        % = 2 +        repository              => iodata()         % = 3 +       }. + +-type 'Release'() :: +      #{version                 => iodata(),        % = 1 +        checksum                => iodata(),        % = 2 +        dependencies            => ['Dependency'()] % = 3 +        %% retired              => 'RetirementStatus'() % = 4 +       }. + +-type 'RetirementStatus'() :: +      #{reason                  => 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED' | integer() % = 1, enum RetirementReason +        %% message              => iodata()         % = 2 +       }. + +-type 'Dependency'() :: +      #{package                 => iodata(),        % = 1 +        requirement             => iodata()         % = 2 +        %% optional             => boolean() | 0 | 1 % = 3 +        %% app                  => iodata()         % = 4 +        %% repository           => iodata()         % = 5 +       }. + +-export_type(['Package'/0, 'Release'/0, 'RetirementStatus'/0, 'Dependency'/0]). + +-spec encode_msg('Package'() | 'Release'() | 'RetirementStatus'() | 'Dependency'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> +    encode_msg(Msg, MsgName, []). + +-spec encode_msg('Package'() | 'Release'() | 'RetirementStatus'() | 'Dependency'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> +    verify_msg(Msg, MsgName, Opts), +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Package' -> +	  e_msg_Package(id(Msg, TrUserData), TrUserData); +      'Release' -> +	  e_msg_Release(id(Msg, TrUserData), TrUserData); +      'RetirementStatus' -> +	  e_msg_RetirementStatus(id(Msg, TrUserData), TrUserData); +      'Dependency' -> +	  e_msg_Dependency(id(Msg, TrUserData), TrUserData) +    end. + + +e_msg_Package(Msg, TrUserData) -> +    e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F2, repository := F3} = M, Bin, +	      TrUserData) -> +    B1 = case M of +	   #{releases := F1} -> +	       TrF1 = id(F1, TrUserData), +	       if TrF1 == [] -> Bin; +		  true -> e_field_Package_releases(TrF1, Bin, TrUserData) +	       end; +	   _ -> Bin +	 end, +    B2 = begin +	   TrF2 = id(F2, TrUserData), +	   e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) +	 end, +    begin +      TrF3 = id(F3, TrUserData), +      e_type_string(TrF3, <<B2/binary, 26>>, TrUserData) +    end. + +e_msg_Release(Msg, TrUserData) -> +    e_msg_Release(Msg, <<>>, TrUserData). + + +e_msg_Release(#{version := F1, checksum := F2} = M, Bin, +	      TrUserData) -> +    B1 = begin +	   TrF1 = id(F1, TrUserData), +	   e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) +	 end, +    B2 = begin +	   TrF2 = id(F2, TrUserData), +	   e_type_bytes(TrF2, <<B1/binary, 18>>, TrUserData) +	 end, +    B3 = case M of +	   #{dependencies := F3} -> +	       TrF3 = id(F3, TrUserData), +	       if TrF3 == [] -> B2; +		  true -> +		      e_field_Release_dependencies(TrF3, B2, TrUserData) +	       end; +	   _ -> B2 +	 end, +    case M of +      #{retired := F4} -> +	  begin +	    TrF4 = id(F4, TrUserData), +	    e_mfield_Release_retired(TrF4, <<B3/binary, 34>>, +				     TrUserData) +	  end; +      _ -> B3 +    end. + +e_msg_RetirementStatus(Msg, TrUserData) -> +    e_msg_RetirementStatus(Msg, <<>>, TrUserData). + + +e_msg_RetirementStatus(#{reason := F1} = M, Bin, +		       TrUserData) -> +    B1 = begin +	   TrF1 = id(F1, TrUserData), +	   e_enum_RetirementReason(TrF1, <<Bin/binary, 8>>, +				   TrUserData) +	 end, +    case M of +      #{message := F2} -> +	  begin +	    TrF2 = id(F2, TrUserData), +	    e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) +	  end; +      _ -> B1 +    end. + +e_msg_Dependency(Msg, TrUserData) -> +    e_msg_Dependency(Msg, <<>>, TrUserData). + + +e_msg_Dependency(#{package := F1, requirement := F2} = +		     M, +		 Bin, TrUserData) -> +    B1 = begin +	   TrF1 = id(F1, TrUserData), +	   e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) +	 end, +    B2 = begin +	   TrF2 = id(F2, TrUserData), +	   e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) +	 end, +    B3 = case M of +	   #{optional := F3} -> +	       begin +		 TrF3 = id(F3, TrUserData), +		 e_type_bool(TrF3, <<B2/binary, 24>>, TrUserData) +	       end; +	   _ -> B2 +	 end, +    B4 = case M of +	   #{app := F4} -> +	       begin +		 TrF4 = id(F4, TrUserData), +		 e_type_string(TrF4, <<B3/binary, 34>>, TrUserData) +	       end; +	   _ -> B3 +	 end, +    case M of +      #{repository := F5} -> +	  begin +	    TrF5 = id(F5, TrUserData), +	    e_type_string(TrF5, <<B4/binary, 42>>, TrUserData) +	  end; +      _ -> B4 +    end. + +e_mfield_Package_releases(Msg, Bin, TrUserData) -> +    SubBin = e_msg_Release(Msg, <<>>, TrUserData), +    Bin2 = e_varint(byte_size(SubBin), Bin), +    <<Bin2/binary, SubBin/binary>>. + +e_field_Package_releases([Elem | Rest], Bin, +			 TrUserData) -> +    Bin2 = <<Bin/binary, 10>>, +    Bin3 = e_mfield_Package_releases(id(Elem, TrUserData), +				     Bin2, TrUserData), +    e_field_Package_releases(Rest, Bin3, TrUserData); +e_field_Package_releases([], Bin, _TrUserData) -> Bin. + +e_mfield_Release_dependencies(Msg, Bin, TrUserData) -> +    SubBin = e_msg_Dependency(Msg, <<>>, TrUserData), +    Bin2 = e_varint(byte_size(SubBin), Bin), +    <<Bin2/binary, SubBin/binary>>. + +e_field_Release_dependencies([Elem | Rest], Bin, +			     TrUserData) -> +    Bin2 = <<Bin/binary, 26>>, +    Bin3 = e_mfield_Release_dependencies(id(Elem, +					    TrUserData), +					 Bin2, TrUserData), +    e_field_Release_dependencies(Rest, Bin3, TrUserData); +e_field_Release_dependencies([], Bin, _TrUserData) -> +    Bin. + +e_mfield_Release_retired(Msg, Bin, TrUserData) -> +    SubBin = e_msg_RetirementStatus(Msg, <<>>, TrUserData), +    Bin2 = e_varint(byte_size(SubBin), Bin), +    <<Bin2/binary, SubBin/binary>>. + +e_enum_RetirementReason('RETIRED_OTHER', Bin, +			_TrUserData) -> +    <<Bin/binary, 0>>; +e_enum_RetirementReason('RETIRED_INVALID', Bin, +			_TrUserData) -> +    <<Bin/binary, 1>>; +e_enum_RetirementReason('RETIRED_SECURITY', Bin, +			_TrUserData) -> +    <<Bin/binary, 2>>; +e_enum_RetirementReason('RETIRED_DEPRECATED', Bin, +			_TrUserData) -> +    <<Bin/binary, 3>>; +e_enum_RetirementReason('RETIRED_RENAMED', Bin, +			_TrUserData) -> +    <<Bin/binary, 4>>; +e_enum_RetirementReason(V, Bin, _TrUserData) -> +    e_varint(V, Bin). + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> +    e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> +    e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> +    <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> +    <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> +    Utf8 = unicode:characters_to_binary(S), +    Bin2 = e_varint(byte_size(Utf8), Bin), +    <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_binary(Bytes) -> +    Bin2 = e_varint(byte_size(Bytes), Bin), +    <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_list(Bytes) -> +    BytesBin = iolist_to_binary(Bytes), +    Bin2 = e_varint(byte_size(BytesBin), Bin), +    <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> +    <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> +    <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> +    <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> +    <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> +    <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> +    <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> +    Bin2 = <<Bin/binary, (N band 127 bor 128)>>, +    e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> +    decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> +    TrUserData = proplists:get_value(user_data, Opts), +    decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason -> +        StackTrace = erlang:get_stacktrace(), +        error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-endif. + +-endif. + +decode_msg_2_doit('Package', Bin, TrUserData) -> +    id(d_msg_Package(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Release', Bin, TrUserData) -> +    id(d_msg_Release(Bin, TrUserData), TrUserData); +decode_msg_2_doit('RetirementStatus', Bin, +		  TrUserData) -> +    id(d_msg_RetirementStatus(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Dependency', Bin, TrUserData) -> +    id(d_msg_Dependency(Bin, TrUserData), TrUserData). + + + +d_msg_Package(Bin, TrUserData) -> +    dfp_read_field_def_Package(Bin, 0, 0, +			       id([], TrUserData), id('$undef', TrUserData), +			       id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_releases(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			     TrUserData); +dfp_read_field_def_Package(<<18, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_name(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			 TrUserData); +dfp_read_field_def_Package(<<26, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_repository(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, R1, F@_2, F@_3, +			   TrUserData) -> +    S1 = #{name => F@_2, repository => F@_3}, +    if R1 == '$undef' -> S1; +       true -> S1#{releases => lists_reverse(R1, TrUserData)} +    end; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, +			   F@_3, TrUserData) -> +    dg_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, +			      F@_3, TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, +			      F@_1, F@_2, F@_3, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Package_releases(Rest, 0, 0, F@_1, F@_2, F@_3, +				   TrUserData); +      18 -> +	  d_field_Package_name(Rest, 0, 0, F@_1, F@_2, F@_3, +			       TrUserData); +      26 -> +	  d_field_Package_repository(Rest, 0, 0, F@_1, F@_2, F@_3, +				     TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				    TrUserData); +	    1 -> +		skip_64_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				TrUserData); +	    2 -> +		skip_length_delimited_Package(Rest, 0, 0, F@_1, F@_2, +					      F@_3, TrUserData); +	    3 -> +		skip_group_Package(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, +				   TrUserData); +	    5 -> +		skip_32_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				TrUserData) +	  end +    end; +dg_read_field_def_Package(<<>>, 0, 0, R1, F@_2, F@_3, +			  TrUserData) -> +    S1 = #{name => F@_2, repository => F@_3}, +    if R1 == '$undef' -> S1; +       true -> S1#{releases => lists_reverse(R1, TrUserData)} +    end. + +d_field_Package_releases(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_releases(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, F@_3, TrUserData); +d_field_Package_releases(<<0:1, X:7, Rest/binary>>, N, +			 Acc, Prev, F@_2, F@_3, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bs:Len/binary, Rest2/binary>> = Rest, +			   {id(d_msg_Release(Bs, TrUserData), TrUserData), +			    Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, +			       cons(NewFValue, Prev, TrUserData), F@_2, F@_3, +			       TrUserData). + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, +		     F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, +			 F@_2, F@_3, TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, +		     F@_1, _, F@_3, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, F@_1, NewFValue, +			       F@_3, TrUserData). + +d_field_Package_repository(<<1:1, X:7, Rest/binary>>, N, +			   Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_repository(Rest, N + 7, X bsl N + Acc, +			       F@_1, F@_2, F@_3, TrUserData); +d_field_Package_repository(<<0:1, X:7, Rest/binary>>, N, +			   Acc, F@_1, F@_2, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, F@_1, F@_2, +			       NewFValue, TrUserData). + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, TrUserData) -> +    skip_varint_Package(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    skip_length_delimited_Package(Rest, N + 7, +				  X bsl N + Acc, F@_1, F@_2, F@_3, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, F@_2, F@_3, +		   TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Package(Rest, 0, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +d_msg_Release(Bin, TrUserData) -> +    dfp_read_field_def_Release(Bin, 0, 0, +			       id('$undef', TrUserData), +			       id('$undef', TrUserData), id([], TrUserData), +			       id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Release(<<10, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    d_field_Release_version(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			    F@_4, TrUserData); +dfp_read_field_def_Release(<<18, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    d_field_Release_checksum(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			     F@_4, TrUserData); +dfp_read_field_def_Release(<<26, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    d_field_Release_dependencies(Rest, Z1, Z2, F@_1, F@_2, +				 F@_3, F@_4, TrUserData); +dfp_read_field_def_Release(<<34, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    d_field_Release_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			    F@_4, TrUserData); +dfp_read_field_def_Release(<<>>, 0, 0, F@_1, F@_2, R1, +			   F@_4, TrUserData) -> +    S1 = #{version => F@_1, checksum => F@_2}, +    S2 = if R1 == '$undef' -> S1; +	    true -> +		S1#{dependencies => lists_reverse(R1, TrUserData)} +	 end, +    if F@_4 == '$undef' -> S2; +       true -> S2#{retired => F@_4} +    end; +dfp_read_field_def_Release(Other, Z1, Z2, F@_1, F@_2, +			   F@_3, F@_4, TrUserData) -> +    dg_read_field_def_Release(Other, Z1, Z2, F@_1, F@_2, +			      F@_3, F@_4, TrUserData). + +dg_read_field_def_Release(<<1:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Release(Rest, N + 7, X bsl N + Acc, +			      F@_1, F@_2, F@_3, F@_4, TrUserData); +dg_read_field_def_Release(<<0:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Release_version(Rest, 0, 0, F@_1, F@_2, F@_3, +				  F@_4, TrUserData); +      18 -> +	  d_field_Release_checksum(Rest, 0, 0, F@_1, F@_2, F@_3, +				   F@_4, TrUserData); +      26 -> +	  d_field_Release_dependencies(Rest, 0, 0, F@_1, F@_2, +				       F@_3, F@_4, TrUserData); +      34 -> +	  d_field_Release_retired(Rest, 0, 0, F@_1, F@_2, F@_3, +				  F@_4, TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, +				    TrUserData); +	    1 -> +		skip_64_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, +				TrUserData); +	    2 -> +		skip_length_delimited_Release(Rest, 0, 0, F@_1, F@_2, +					      F@_3, F@_4, TrUserData); +	    3 -> +		skip_group_Release(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, +				   F@_4, TrUserData); +	    5 -> +		skip_32_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, +				TrUserData) +	  end +    end; +dg_read_field_def_Release(<<>>, 0, 0, F@_1, F@_2, R1, +			  F@_4, TrUserData) -> +    S1 = #{version => F@_1, checksum => F@_2}, +    S2 = if R1 == '$undef' -> S1; +	    true -> +		S1#{dependencies => lists_reverse(R1, TrUserData)} +	 end, +    if F@_4 == '$undef' -> S2; +       true -> S2#{retired => F@_4} +    end. + +d_field_Release_version(<<1:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 57 -> +    d_field_Release_version(Rest, N + 7, X bsl N + Acc, +			    F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_version(<<0:1, X:7, Rest/binary>>, N, +			Acc, _, F@_2, F@_3, F@_4, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Release(RestF, 0, 0, NewFValue, F@_2, +			       F@_3, F@_4, TrUserData). + +d_field_Release_checksum(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 57 -> +    d_field_Release_checksum(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_checksum(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, _, F@_3, F@_4, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Release(RestF, 0, 0, F@_1, NewFValue, +			       F@_3, F@_4, TrUserData). + +d_field_Release_dependencies(<<1:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 57 -> +    d_field_Release_dependencies(Rest, N + 7, X bsl N + Acc, +				 F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_dependencies(<<0:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, Prev, F@_4, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bs:Len/binary, Rest2/binary>> = Rest, +			   {id(d_msg_Dependency(Bs, TrUserData), TrUserData), +			    Rest2} +			 end, +    dfp_read_field_def_Release(RestF, 0, 0, F@_1, F@_2, +			       cons(NewFValue, Prev, TrUserData), F@_4, +			       TrUserData). + +d_field_Release_retired(<<1:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 57 -> +    d_field_Release_retired(Rest, N + 7, X bsl N + Acc, +			    F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_retired(<<0:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, F@_3, Prev, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bs:Len/binary, Rest2/binary>> = Rest, +			   {id(d_msg_RetirementStatus(Bs, TrUserData), +			       TrUserData), +			    Rest2} +			 end, +    dfp_read_field_def_Release(RestF, 0, 0, F@_1, F@_2, +			       F@_3, +			       if Prev == '$undef' -> NewFValue; +				  true -> +				      merge_msg_RetirementStatus(Prev, +								 NewFValue, +								 TrUserData) +			       end, +			       TrUserData). + +skip_varint_Release(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    skip_varint_Release(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			F@_4, TrUserData); +skip_varint_Release(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, F@_4, TrUserData). + +skip_length_delimited_Release(<<1:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) +    when N < 57 -> +    skip_length_delimited_Release(Rest, N + 7, +				  X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, +				  TrUserData); +skip_length_delimited_Release(<<0:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Release(Rest2, 0, 0, F@_1, F@_2, +			       F@_3, F@_4, TrUserData). + +skip_group_Release(Bin, FNum, Z2, F@_1, F@_2, F@_3, +		   F@_4, TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Release(Rest, 0, Z2, F@_1, F@_2, +			       F@_3, F@_4, TrUserData). + +skip_32_Release(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, F@_4, TrUserData) -> +    dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, F@_4, TrUserData). + +skip_64_Release(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, F@_4, TrUserData) -> +    dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, F@_4, TrUserData). + +d_msg_RetirementStatus(Bin, TrUserData) -> +    dfp_read_field_def_RetirementStatus(Bin, 0, 0, +					id('$undef', TrUserData), +					id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_RetirementStatus(<<8, Rest/binary>>, +				    Z1, Z2, F@_1, F@_2, TrUserData) -> +    d_field_RetirementStatus_reason(Rest, Z1, Z2, F@_1, +				    F@_2, TrUserData); +dfp_read_field_def_RetirementStatus(<<18, Rest/binary>>, +				    Z1, Z2, F@_1, F@_2, TrUserData) -> +    d_field_RetirementStatus_message(Rest, Z1, Z2, F@_1, +				     F@_2, TrUserData); +dfp_read_field_def_RetirementStatus(<<>>, 0, 0, F@_1, +				    F@_2, _) -> +    S1 = #{reason => F@_1}, +    if F@_2 == '$undef' -> S1; +       true -> S1#{message => F@_2} +    end; +dfp_read_field_def_RetirementStatus(Other, Z1, Z2, F@_1, +				    F@_2, TrUserData) -> +    dg_read_field_def_RetirementStatus(Other, Z1, Z2, F@_1, +				       F@_2, TrUserData). + +dg_read_field_def_RetirementStatus(<<1:1, X:7, +				     Rest/binary>>, +				   N, Acc, F@_1, F@_2, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_RetirementStatus(Rest, N + 7, +				       X bsl N + Acc, F@_1, F@_2, TrUserData); +dg_read_field_def_RetirementStatus(<<0:1, X:7, +				     Rest/binary>>, +				   N, Acc, F@_1, F@_2, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      8 -> +	  d_field_RetirementStatus_reason(Rest, 0, 0, F@_1, F@_2, +					  TrUserData); +      18 -> +	  d_field_RetirementStatus_message(Rest, 0, 0, F@_1, F@_2, +					   TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_RetirementStatus(Rest, 0, 0, F@_1, F@_2, +					     TrUserData); +	    1 -> +		skip_64_RetirementStatus(Rest, 0, 0, F@_1, F@_2, +					 TrUserData); +	    2 -> +		skip_length_delimited_RetirementStatus(Rest, 0, 0, F@_1, +						       F@_2, TrUserData); +	    3 -> +		skip_group_RetirementStatus(Rest, Key bsr 3, 0, F@_1, +					    F@_2, TrUserData); +	    5 -> +		skip_32_RetirementStatus(Rest, 0, 0, F@_1, F@_2, +					 TrUserData) +	  end +    end; +dg_read_field_def_RetirementStatus(<<>>, 0, 0, F@_1, +				   F@_2, _) -> +    S1 = #{reason => F@_1}, +    if F@_2 == '$undef' -> S1; +       true -> S1#{message => F@_2} +    end. + +d_field_RetirementStatus_reason(<<1:1, X:7, +				  Rest/binary>>, +				N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_RetirementStatus_reason(Rest, N + 7, +				    X bsl N + Acc, F@_1, F@_2, TrUserData); +d_field_RetirementStatus_reason(<<0:1, X:7, +				  Rest/binary>>, +				N, Acc, _, F@_2, TrUserData) -> +    {NewFValue, RestF} = {id(d_enum_RetirementReason(begin +						       <<Res:32/signed-native>> = +							   <<(X bsl N + +								Acc):32/unsigned-native>>, +						       id(Res, TrUserData) +						     end), +			     TrUserData), +			  Rest}, +    dfp_read_field_def_RetirementStatus(RestF, 0, 0, +					NewFValue, F@_2, TrUserData). + +d_field_RetirementStatus_message(<<1:1, X:7, +				   Rest/binary>>, +				 N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_RetirementStatus_message(Rest, N + 7, +				     X bsl N + Acc, F@_1, F@_2, TrUserData); +d_field_RetirementStatus_message(<<0:1, X:7, +				   Rest/binary>>, +				 N, Acc, F@_1, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_RetirementStatus(RestF, 0, 0, F@_1, +					NewFValue, TrUserData). + +skip_varint_RetirementStatus(<<1:1, _:7, Rest/binary>>, +			     Z1, Z2, F@_1, F@_2, TrUserData) -> +    skip_varint_RetirementStatus(Rest, Z1, Z2, F@_1, F@_2, +				 TrUserData); +skip_varint_RetirementStatus(<<0:1, _:7, Rest/binary>>, +			     Z1, Z2, F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, +					F@_2, TrUserData). + +skip_length_delimited_RetirementStatus(<<1:1, X:7, +					 Rest/binary>>, +				       N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    skip_length_delimited_RetirementStatus(Rest, N + 7, +					   X bsl N + Acc, F@_1, F@_2, +					   TrUserData); +skip_length_delimited_RetirementStatus(<<0:1, X:7, +					 Rest/binary>>, +				       N, Acc, F@_1, F@_2, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_RetirementStatus(Rest2, 0, 0, F@_1, +					F@_2, TrUserData). + +skip_group_RetirementStatus(Bin, FNum, Z2, F@_1, F@_2, +			    TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_RetirementStatus(Rest, 0, Z2, F@_1, +					F@_2, TrUserData). + +skip_32_RetirementStatus(<<_:32, Rest/binary>>, Z1, Z2, +			 F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, +					F@_2, TrUserData). + +skip_64_RetirementStatus(<<_:64, Rest/binary>>, Z1, Z2, +			 F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, +					F@_2, TrUserData). + +d_msg_Dependency(Bin, TrUserData) -> +    dfp_read_field_def_Dependency(Bin, 0, 0, +				  id('$undef', TrUserData), +				  id('$undef', TrUserData), +				  id('$undef', TrUserData), +				  id('$undef', TrUserData), +				  id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Dependency(<<10, Rest/binary>>, Z1, +			      Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    d_field_Dependency_package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<18, Rest/binary>>, Z1, +			      Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    d_field_Dependency_requirement(Rest, Z1, Z2, F@_1, F@_2, +				   F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<24, Rest/binary>>, Z1, +			      Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    d_field_Dependency_optional(Rest, Z1, Z2, F@_1, F@_2, +				F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<34, Rest/binary>>, Z1, +			      Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    d_field_Dependency_app(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			   F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<42, Rest/binary>>, Z1, +			      Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    d_field_Dependency_repository(Rest, Z1, Z2, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<>>, 0, 0, F@_1, F@_2, +			      F@_3, F@_4, F@_5, _) -> +    S1 = #{package => F@_1, requirement => F@_2}, +    S2 = if F@_3 == '$undef' -> S1; +	    true -> S1#{optional => F@_3} +	 end, +    S3 = if F@_4 == '$undef' -> S2; +	    true -> S2#{app => F@_4} +	 end, +    if F@_5 == '$undef' -> S3; +       true -> S3#{repository => F@_5} +    end; +dfp_read_field_def_Dependency(Other, Z1, Z2, F@_1, F@_2, +			      F@_3, F@_4, F@_5, TrUserData) -> +    dg_read_field_def_Dependency(Other, Z1, Z2, F@_1, F@_2, +				 F@_3, F@_4, F@_5, TrUserData). + +dg_read_field_def_Dependency(<<1:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Dependency(Rest, N + 7, X bsl N + Acc, +				 F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dg_read_field_def_Dependency(<<0:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, +			     TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Dependency_package(Rest, 0, 0, F@_1, F@_2, F@_3, +				     F@_4, F@_5, TrUserData); +      18 -> +	  d_field_Dependency_requirement(Rest, 0, 0, F@_1, F@_2, +					 F@_3, F@_4, F@_5, TrUserData); +      24 -> +	  d_field_Dependency_optional(Rest, 0, 0, F@_1, F@_2, +				      F@_3, F@_4, F@_5, TrUserData); +      34 -> +	  d_field_Dependency_app(Rest, 0, 0, F@_1, F@_2, F@_3, +				 F@_4, F@_5, TrUserData); +      42 -> +	  d_field_Dependency_repository(Rest, 0, 0, F@_1, F@_2, +					F@_3, F@_4, F@_5, TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, +				       F@_4, F@_5, TrUserData); +	    1 -> +		skip_64_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, +				   F@_5, TrUserData); +	    2 -> +		skip_length_delimited_Dependency(Rest, 0, 0, F@_1, F@_2, +						 F@_3, F@_4, F@_5, TrUserData); +	    3 -> +		skip_group_Dependency(Rest, Key bsr 3, 0, F@_1, F@_2, +				      F@_3, F@_4, F@_5, TrUserData); +	    5 -> +		skip_32_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, +				   F@_5, TrUserData) +	  end +    end; +dg_read_field_def_Dependency(<<>>, 0, 0, F@_1, F@_2, +			     F@_3, F@_4, F@_5, _) -> +    S1 = #{package => F@_1, requirement => F@_2}, +    S2 = if F@_3 == '$undef' -> S1; +	    true -> S1#{optional => F@_3} +	 end, +    S3 = if F@_4 == '$undef' -> S2; +	    true -> S2#{app => F@_4} +	 end, +    if F@_5 == '$undef' -> S3; +       true -> S3#{repository => F@_5} +    end. + +d_field_Dependency_package(<<1:1, X:7, Rest/binary>>, N, +			   Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 57 -> +    d_field_Dependency_package(Rest, N + 7, X bsl N + Acc, +			       F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_package(<<0:1, X:7, Rest/binary>>, N, +			   Acc, _, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Dependency(RestF, 0, 0, NewFValue, +				  F@_2, F@_3, F@_4, F@_5, TrUserData). + +d_field_Dependency_requirement(<<1:1, X:7, +				 Rest/binary>>, +			       N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 57 -> +    d_field_Dependency_requirement(Rest, N + 7, +				   X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, +				   TrUserData); +d_field_Dependency_requirement(<<0:1, X:7, +				 Rest/binary>>, +			       N, Acc, F@_1, _, F@_3, F@_4, F@_5, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, +				  NewFValue, F@_3, F@_4, F@_5, TrUserData). + +d_field_Dependency_optional(<<1:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 57 -> +    d_field_Dependency_optional(Rest, N + 7, X bsl N + Acc, +				F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_optional(<<0:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, F@_2, _, F@_4, F@_5, TrUserData) -> +    {NewFValue, RestF} = {id(X bsl N + Acc =/= 0, +			     TrUserData), +			  Rest}, +    dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, +				  NewFValue, F@_4, F@_5, TrUserData). + +d_field_Dependency_app(<<1:1, X:7, Rest/binary>>, N, +		       Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 57 -> +    d_field_Dependency_app(Rest, N + 7, X bsl N + Acc, F@_1, +			   F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_app(<<0:1, X:7, Rest/binary>>, N, +		       Acc, F@_1, F@_2, F@_3, _, F@_5, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, +				  F@_3, NewFValue, F@_5, TrUserData). + +d_field_Dependency_repository(<<1:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) +    when N < 57 -> +    d_field_Dependency_repository(Rest, N + 7, +				  X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, +				  TrUserData); +d_field_Dependency_repository(<<0:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, F@_4, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, +				  F@_3, F@_4, NewFValue, TrUserData). + +skip_varint_Dependency(<<1:1, _:7, Rest/binary>>, Z1, +		       Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    skip_varint_Dependency(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			   F@_4, F@_5, TrUserData); +skip_varint_Dependency(<<0:1, _:7, Rest/binary>>, Z1, +		       Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData). + +skip_length_delimited_Dependency(<<1:1, X:7, +				   Rest/binary>>, +				 N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, +				 TrUserData) +    when N < 57 -> +    skip_length_delimited_Dependency(Rest, N + 7, +				     X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, +				     F@_5, TrUserData); +skip_length_delimited_Dependency(<<0:1, X:7, +				   Rest/binary>>, +				 N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, +				 TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Dependency(Rest2, 0, 0, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData). + +skip_group_Dependency(Bin, FNum, Z2, F@_1, F@_2, F@_3, +		      F@_4, F@_5, TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Dependency(Rest, 0, Z2, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData). + +skip_32_Dependency(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		   F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData). + +skip_64_Dependency(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		   F@_2, F@_3, F@_4, F@_5, TrUserData) -> +    dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, +				  F@_3, F@_4, F@_5, TrUserData). + +d_enum_RetirementReason(0) -> 'RETIRED_OTHER'; +d_enum_RetirementReason(1) -> 'RETIRED_INVALID'; +d_enum_RetirementReason(2) -> 'RETIRED_SECURITY'; +d_enum_RetirementReason(3) -> 'RETIRED_DEPRECATED'; +d_enum_RetirementReason(4) -> 'RETIRED_RENAMED'; +d_enum_RetirementReason(V) -> V. + +read_group(Bin, FieldNum) -> +    {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), +    <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, +    {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) +  when N < (32-7) -> +    read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, +          FieldNum) -> +    Key = X bsl N + Acc, +    TagLen1 = TagLen + 1, +    case {Key bsr 3, Key band 7} of +        {FieldNum, 4} -> % 4 = group_end +            {NumBytes, TagLen1}; +        {_, 0} -> % 0 = varint +            read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); +        {_, 1} -> % 1 = bits64 +            <<_:64, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); +        {_, 2} -> % 2 = length_delimited +            read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); +        {_, 3} -> % 3 = group_start +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 4} -> % 4 = group_end +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 5} -> % 5 = bits32 +            <<_:32, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) +    end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> +    read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> +    Len = X bsl N + Acc, +    NumBytes1 = NumBytes + 1, +    <<_:Len/binary, Tl2/binary>> = Tl, +    read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> +    merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Package' -> merge_msg_Package(Prev, New, TrUserData); +      'Release' -> merge_msg_Release(Prev, New, TrUserData); +      'RetirementStatus' -> +	  merge_msg_RetirementStatus(Prev, New, TrUserData); +      'Dependency' -> +	  merge_msg_Dependency(Prev, New, TrUserData) +    end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{} = PMsg, +		  #{name := NFname, repository := NFrepository} = NMsg, +		  TrUserData) -> +    S1 = #{name => NFname, repository => NFrepository}, +    case {PMsg, NMsg} of +      {#{releases := PFreleases}, +       #{releases := NFreleases}} -> +	  S1#{releases => +		  'erlang_++'(PFreleases, NFreleases, TrUserData)}; +      {_, #{releases := NFreleases}} -> +	  S1#{releases => NFreleases}; +      {#{releases := PFreleases}, _} -> +	  S1#{releases => PFreleases}; +      {_, _} -> S1 +    end. + +-compile({nowarn_unused_function,merge_msg_Release/3}). +merge_msg_Release(#{} = PMsg, +		  #{version := NFversion, checksum := NFchecksum} = NMsg, +		  TrUserData) -> +    S1 = #{version => NFversion, checksum => NFchecksum}, +    S2 = case {PMsg, NMsg} of +	   {#{dependencies := PFdependencies}, +	    #{dependencies := NFdependencies}} -> +	       S1#{dependencies => +		       'erlang_++'(PFdependencies, NFdependencies, +				   TrUserData)}; +	   {_, #{dependencies := NFdependencies}} -> +	       S1#{dependencies => NFdependencies}; +	   {#{dependencies := PFdependencies}, _} -> +	       S1#{dependencies => PFdependencies}; +	   {_, _} -> S1 +	 end, +    case {PMsg, NMsg} of +      {#{retired := PFretired}, #{retired := NFretired}} -> +	  S2#{retired => +		  merge_msg_RetirementStatus(PFretired, NFretired, +					     TrUserData)}; +      {_, #{retired := NFretired}} -> +	  S2#{retired => NFretired}; +      {#{retired := PFretired}, _} -> +	  S2#{retired => PFretired}; +      {_, _} -> S2 +    end. + +-compile({nowarn_unused_function,merge_msg_RetirementStatus/3}). +merge_msg_RetirementStatus(#{} = PMsg, +			   #{reason := NFreason} = NMsg, _) -> +    S1 = #{reason => NFreason}, +    case {PMsg, NMsg} of +      {_, #{message := NFmessage}} -> +	  S1#{message => NFmessage}; +      {#{message := PFmessage}, _} -> +	  S1#{message => PFmessage}; +      _ -> S1 +    end. + +-compile({nowarn_unused_function,merge_msg_Dependency/3}). +merge_msg_Dependency(#{} = PMsg, +		     #{package := NFpackage, requirement := NFrequirement} = +			 NMsg, +		     _) -> +    S1 = #{package => NFpackage, +	   requirement => NFrequirement}, +    S2 = case {PMsg, NMsg} of +	   {_, #{optional := NFoptional}} -> +	       S1#{optional => NFoptional}; +	   {#{optional := PFoptional}, _} -> +	       S1#{optional => PFoptional}; +	   _ -> S1 +	 end, +    S3 = case {PMsg, NMsg} of +	   {_, #{app := NFapp}} -> S2#{app => NFapp}; +	   {#{app := PFapp}, _} -> S2#{app => PFapp}; +	   _ -> S2 +	 end, +    case {PMsg, NMsg} of +      {_, #{repository := NFrepository}} -> +	  S3#{repository => NFrepository}; +      {#{repository := PFrepository}, _} -> +	  S3#{repository => PFrepository}; +      _ -> S3 +    end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> +    verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); +      'Release' -> v_msg_Release(Msg, [MsgName], TrUserData); +      'RetirementStatus' -> +	  v_msg_RetirementStatus(Msg, [MsgName], TrUserData); +      'Dependency' -> +	  v_msg_Dependency(Msg, [MsgName], TrUserData); +      _ -> mk_type_error(not_a_known_message, Msg, []) +    end. + + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F2, repository := F3} = M, Path, +	      TrUserData) -> +    case M of +      #{releases := F1} -> +	  if is_list(F1) -> +		 _ = [v_msg_Release(Elem, [releases | Path], TrUserData) +		      || Elem <- F1], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, {msg, 'Release'}}, F1, +			       [releases | Path]) +	  end; +      _ -> ok +    end, +    v_type_string(F2, [name | Path], TrUserData), +    v_type_string(F3, [repository | Path], TrUserData), +    lists:foreach(fun (releases) -> ok; +		      (name) -> ok; +		      (repository) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [name, repository] -- maps:keys(M), 'Package'}, +		  M, Path); +v_msg_Package(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Release/3}). +v_msg_Release(#{version := F1, checksum := F2} = M, +	      Path, TrUserData) -> +    v_type_string(F1, [version | Path], TrUserData), +    v_type_bytes(F2, [checksum | Path], TrUserData), +    case M of +      #{dependencies := F3} -> +	  if is_list(F3) -> +		 _ = [v_msg_Dependency(Elem, [dependencies | Path], +				       TrUserData) +		      || Elem <- F3], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, {msg, 'Dependency'}}, +			       F3, [dependencies | Path]) +	  end; +      _ -> ok +    end, +    case M of +      #{retired := F4} -> +	  v_msg_RetirementStatus(F4, [retired | Path], +				 TrUserData); +      _ -> ok +    end, +    lists:foreach(fun (version) -> ok; +		      (checksum) -> ok; +		      (dependencies) -> ok; +		      (retired) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Release(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [version, checksum] -- maps:keys(M), 'Release'}, +		  M, Path); +v_msg_Release(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Release'}, X, Path). + +-compile({nowarn_unused_function,v_msg_RetirementStatus/3}). +v_msg_RetirementStatus(#{reason := F1} = M, Path, +		       TrUserData) -> +    v_enum_RetirementReason(F1, [reason | Path], +			    TrUserData), +    case M of +      #{message := F2} -> +	  v_type_string(F2, [message | Path], TrUserData); +      _ -> ok +    end, +    lists:foreach(fun (reason) -> ok; +		      (message) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_RetirementStatus(M, Path, _TrUserData) +    when is_map(M) -> +    mk_type_error({missing_fields, [reason] -- maps:keys(M), +		   'RetirementStatus'}, +		  M, Path); +v_msg_RetirementStatus(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'RetirementStatus'}, X, +		  Path). + +-compile({nowarn_unused_function,v_msg_Dependency/3}). +v_msg_Dependency(#{package := F1, requirement := F2} = +		     M, +		 Path, TrUserData) -> +    v_type_string(F1, [package | Path], TrUserData), +    v_type_string(F2, [requirement | Path], TrUserData), +    case M of +      #{optional := F3} -> +	  v_type_bool(F3, [optional | Path], TrUserData); +      _ -> ok +    end, +    case M of +      #{app := F4} -> +	  v_type_string(F4, [app | Path], TrUserData); +      _ -> ok +    end, +    case M of +      #{repository := F5} -> +	  v_type_string(F5, [repository | Path], TrUserData); +      _ -> ok +    end, +    lists:foreach(fun (package) -> ok; +		      (requirement) -> ok; +		      (optional) -> ok; +		      (app) -> ok; +		      (repository) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Dependency(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [package, requirement] -- maps:keys(M), 'Dependency'}, +		  M, Path); +v_msg_Dependency(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Dependency'}, X, Path). + +-compile({nowarn_unused_function,v_enum_RetirementReason/3}). +v_enum_RetirementReason('RETIRED_OTHER', _Path, +			_TrUserData) -> +    ok; +v_enum_RetirementReason('RETIRED_INVALID', _Path, +			_TrUserData) -> +    ok; +v_enum_RetirementReason('RETIRED_SECURITY', _Path, +			_TrUserData) -> +    ok; +v_enum_RetirementReason('RETIRED_DEPRECATED', _Path, +			_TrUserData) -> +    ok; +v_enum_RetirementReason('RETIRED_RENAMED', _Path, +			_TrUserData) -> +    ok; +v_enum_RetirementReason(V, Path, TrUserData) +    when is_integer(V) -> +    v_type_sint32(V, Path, TrUserData); +v_enum_RetirementReason(X, Path, _TrUserData) -> +    mk_type_error({invalid_enum, 'RetirementReason'}, X, +		  Path). + +-compile({nowarn_unused_function,v_type_sint32/3}). +v_type_sint32(N, _Path, _TrUserData) +    when -2147483648 =< N, N =< 2147483647 -> +    ok; +v_type_sint32(N, Path, _TrUserData) +    when is_integer(N) -> +    mk_type_error({value_out_of_range, sint32, signed, 32}, +		  N, Path); +v_type_sint32(X, Path, _TrUserData) -> +    mk_type_error({bad_integer, sint32, signed, 32}, X, +		  Path). + +-compile({nowarn_unused_function,v_type_bool/3}). +v_type_bool(false, _Path, _TrUserData) -> ok; +v_type_bool(true, _Path, _TrUserData) -> ok; +v_type_bool(0, _Path, _TrUserData) -> ok; +v_type_bool(1, _Path, _TrUserData) -> ok; +v_type_bool(X, Path, _TrUserData) -> +    mk_type_error(bad_boolean_value, X, Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) +    when is_list(S); is_binary(S) -> +    try unicode:characters_to_binary(S) of +      B when is_binary(B) -> ok; +      {error, _, _} -> +	  mk_type_error(bad_unicode_string, S, Path) +    catch +      error:badarg -> +	  mk_type_error(bad_unicode_string, S, Path) +    end; +v_type_string(X, Path, _TrUserData) -> +    mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,v_type_bytes/3}). +v_type_bytes(B, _Path, _TrUserData) when is_binary(B) -> +    ok; +v_type_bytes(B, _Path, _TrUserData) when is_list(B) -> +    ok; +v_type_bytes(X, Path, _TrUserData) -> +    mk_type_error(bad_binary_value, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> +    Path2 = prettify_path(Path), +    erlang:error({gpb_type_error, +		  {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> +    list_to_atom(string:join(lists:map(fun atom_to_list/1, +				       lists:reverse(PathR)), +			     ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> +    [{{enum, 'RetirementReason'}, +      [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, +       {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, +       {'RETIRED_RENAMED', 4}]}, +     {{msg, 'Package'}, +      [#{name => releases, fnum => 1, rnum => 2, +	 type => {msg, 'Release'}, occurrence => repeated, +	 opts => []}, +       #{name => name, fnum => 2, rnum => 3, type => string, +	 occurrence => required, opts => []}, +       #{name => repository, fnum => 3, rnum => 4, +	 type => string, occurrence => required, opts => []}]}, +     {{msg, 'Release'}, +      [#{name => version, fnum => 1, rnum => 2, +	 type => string, occurrence => required, opts => []}, +       #{name => checksum, fnum => 2, rnum => 3, type => bytes, +	 occurrence => required, opts => []}, +       #{name => dependencies, fnum => 3, rnum => 4, +	 type => {msg, 'Dependency'}, occurrence => repeated, +	 opts => []}, +       #{name => retired, fnum => 4, rnum => 5, +	 type => {msg, 'RetirementStatus'}, +	 occurrence => optional, opts => []}]}, +     {{msg, 'RetirementStatus'}, +      [#{name => reason, fnum => 1, rnum => 2, +	 type => {enum, 'RetirementReason'}, +	 occurrence => required, opts => []}, +       #{name => message, fnum => 2, rnum => 3, type => string, +	 occurrence => optional, opts => []}]}, +     {{msg, 'Dependency'}, +      [#{name => package, fnum => 1, rnum => 2, +	 type => string, occurrence => required, opts => []}, +       #{name => requirement, fnum => 2, rnum => 3, +	 type => string, occurrence => required, opts => []}, +       #{name => optional, fnum => 3, rnum => 4, type => bool, +	 occurrence => optional, opts => []}, +       #{name => app, fnum => 4, rnum => 5, type => string, +	 occurrence => optional, opts => []}, +       #{name => repository, fnum => 5, rnum => 6, +	 type => string, occurrence => optional, opts => []}]}]. + + +get_msg_names() -> +    ['Package', 'Release', 'RetirementStatus', +     'Dependency']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> +    ['Package', 'Release', 'RetirementStatus', +     'Dependency']. + + +get_enum_names() -> ['RetirementReason']. + + +fetch_msg_def(MsgName) -> +    case find_msg_def(MsgName) of +      Fs when is_list(Fs) -> Fs; +      error -> erlang:error({no_such_msg, MsgName}) +    end. + + +fetch_enum_def(EnumName) -> +    case find_enum_def(EnumName) of +      Es when is_list(Es) -> Es; +      error -> erlang:error({no_such_enum, EnumName}) +    end. + + +find_msg_def('Package') -> +    [#{name => releases, fnum => 1, rnum => 2, +       type => {msg, 'Release'}, occurrence => repeated, +       opts => []}, +     #{name => name, fnum => 2, rnum => 3, type => string, +       occurrence => required, opts => []}, +     #{name => repository, fnum => 3, rnum => 4, +       type => string, occurrence => required, opts => []}]; +find_msg_def('Release') -> +    [#{name => version, fnum => 1, rnum => 2, +       type => string, occurrence => required, opts => []}, +     #{name => checksum, fnum => 2, rnum => 3, type => bytes, +       occurrence => required, opts => []}, +     #{name => dependencies, fnum => 3, rnum => 4, +       type => {msg, 'Dependency'}, occurrence => repeated, +       opts => []}, +     #{name => retired, fnum => 4, rnum => 5, +       type => {msg, 'RetirementStatus'}, +       occurrence => optional, opts => []}]; +find_msg_def('RetirementStatus') -> +    [#{name => reason, fnum => 1, rnum => 2, +       type => {enum, 'RetirementReason'}, +       occurrence => required, opts => []}, +     #{name => message, fnum => 2, rnum => 3, type => string, +       occurrence => optional, opts => []}]; +find_msg_def('Dependency') -> +    [#{name => package, fnum => 1, rnum => 2, +       type => string, occurrence => required, opts => []}, +     #{name => requirement, fnum => 2, rnum => 3, +       type => string, occurrence => required, opts => []}, +     #{name => optional, fnum => 3, rnum => 4, type => bool, +       occurrence => optional, opts => []}, +     #{name => app, fnum => 4, rnum => 5, type => string, +       occurrence => optional, opts => []}, +     #{name => repository, fnum => 5, rnum => 6, +       type => string, occurrence => optional, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def('RetirementReason') -> +    [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, +     {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, +     {'RETIRED_RENAMED', 4}]; +find_enum_def(_) -> error. + + +enum_symbol_by_value('RetirementReason', Value) -> +    enum_symbol_by_value_RetirementReason(Value). + + +enum_value_by_symbol('RetirementReason', Sym) -> +    enum_value_by_symbol_RetirementReason(Sym). + + +enum_symbol_by_value_RetirementReason(0) -> +    'RETIRED_OTHER'; +enum_symbol_by_value_RetirementReason(1) -> +    'RETIRED_INVALID'; +enum_symbol_by_value_RetirementReason(2) -> +    'RETIRED_SECURITY'; +enum_symbol_by_value_RetirementReason(3) -> +    'RETIRED_DEPRECATED'; +enum_symbol_by_value_RetirementReason(4) -> +    'RETIRED_RENAMED'. + + +enum_value_by_symbol_RetirementReason('RETIRED_OTHER') -> +    0; +enum_value_by_symbol_RetirementReason('RETIRED_INVALID') -> +    1; +enum_value_by_symbol_RetirementReason('RETIRED_SECURITY') -> +    2; +enum_value_by_symbol_RetirementReason('RETIRED_DEPRECATED') -> +    3; +enum_value_by_symbol_RetirementReason('RETIRED_RENAMED') -> +    4. + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> +    erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> +    "4.3.1". + +gpb_version_as_list() -> +    [4,3,1]. diff --git a/src/r3_hex_pb_signed.erl b/src/r3_hex_pb_signed.erl new file mode 100644 index 0000000..e8b188a --- /dev/null +++ b/src/r3_hex_pb_signed.erl @@ -0,0 +1,564 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_signed). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Signed'() :: +      #{payload                 => iodata()         % = 1 +        %% signature            => iodata()         % = 2 +       }. + +-export_type(['Signed'/0]). + +-spec encode_msg('Signed'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> +    encode_msg(Msg, MsgName, []). + +-spec encode_msg('Signed'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> +    verify_msg(Msg, MsgName, Opts), +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Signed' -> +	  e_msg_Signed(id(Msg, TrUserData), TrUserData) +    end. + + +e_msg_Signed(Msg, TrUserData) -> +    e_msg_Signed(Msg, <<>>, TrUserData). + + +e_msg_Signed(#{payload := F1} = M, Bin, TrUserData) -> +    B1 = begin +	   TrF1 = id(F1, TrUserData), +	   e_type_bytes(TrF1, <<Bin/binary, 10>>, TrUserData) +	 end, +    case M of +      #{signature := F2} -> +	  begin +	    TrF2 = id(F2, TrUserData), +	    e_type_bytes(TrF2, <<B1/binary, 18>>, TrUserData) +	  end; +      _ -> B1 +    end. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> +    e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> +    e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> +    <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> +    <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> +    Utf8 = unicode:characters_to_binary(S), +    Bin2 = e_varint(byte_size(Utf8), Bin), +    <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_binary(Bytes) -> +    Bin2 = e_varint(byte_size(Bytes), Bin), +    <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_list(Bytes) -> +    BytesBin = iolist_to_binary(Bytes), +    Bin2 = e_varint(byte_size(BytesBin), Bin), +    <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> +    <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> +    <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> +    <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> +    <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> +    <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> +    <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> +    Bin2 = <<Bin/binary, (N band 127 bor 128)>>, +    e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> +    decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> +    TrUserData = proplists:get_value(user_data, Opts), +    decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason -> +        StackTrace = erlang:get_stacktrace(), +        error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-endif. + +-endif. + +decode_msg_2_doit('Signed', Bin, TrUserData) -> +    id(d_msg_Signed(Bin, TrUserData), TrUserData). + + + +d_msg_Signed(Bin, TrUserData) -> +    dfp_read_field_def_Signed(Bin, 0, 0, +			      id('$undef', TrUserData), +			      id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Signed(<<10, Rest/binary>>, Z1, Z2, +			  F@_1, F@_2, TrUserData) -> +    d_field_Signed_payload(Rest, Z1, Z2, F@_1, F@_2, +			   TrUserData); +dfp_read_field_def_Signed(<<18, Rest/binary>>, Z1, Z2, +			  F@_1, F@_2, TrUserData) -> +    d_field_Signed_signature(Rest, Z1, Z2, F@_1, F@_2, +			     TrUserData); +dfp_read_field_def_Signed(<<>>, 0, 0, F@_1, F@_2, _) -> +    S1 = #{payload => F@_1}, +    if F@_2 == '$undef' -> S1; +       true -> S1#{signature => F@_2} +    end; +dfp_read_field_def_Signed(Other, Z1, Z2, F@_1, F@_2, +			  TrUserData) -> +    dg_read_field_def_Signed(Other, Z1, Z2, F@_1, F@_2, +			     TrUserData). + +dg_read_field_def_Signed(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Signed(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, TrUserData); +dg_read_field_def_Signed(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Signed_payload(Rest, 0, 0, F@_1, F@_2, +				 TrUserData); +      18 -> +	  d_field_Signed_signature(Rest, 0, 0, F@_1, F@_2, +				   TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData); +	    1 -> skip_64_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData); +	    2 -> +		skip_length_delimited_Signed(Rest, 0, 0, F@_1, F@_2, +					     TrUserData); +	    3 -> +		skip_group_Signed(Rest, Key bsr 3, 0, F@_1, F@_2, +				  TrUserData); +	    5 -> skip_32_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData) +	  end +    end; +dg_read_field_def_Signed(<<>>, 0, 0, F@_1, F@_2, _) -> +    S1 = #{payload => F@_1}, +    if F@_2 == '$undef' -> S1; +       true -> S1#{signature => F@_2} +    end. + +d_field_Signed_payload(<<1:1, X:7, Rest/binary>>, N, +		       Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Signed_payload(Rest, N + 7, X bsl N + Acc, F@_1, +			   F@_2, TrUserData); +d_field_Signed_payload(<<0:1, X:7, Rest/binary>>, N, +		       Acc, _, F@_2, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Signed(RestF, 0, 0, NewFValue, F@_2, +			      TrUserData). + +d_field_Signed_signature(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Signed_signature(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, TrUserData); +d_field_Signed_signature(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Signed(RestF, 0, 0, F@_1, NewFValue, +			      TrUserData). + +skip_varint_Signed(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		   F@_1, F@_2, TrUserData) -> +    skip_varint_Signed(Rest, Z1, Z2, F@_1, F@_2, +		       TrUserData); +skip_varint_Signed(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		   F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, +			      TrUserData). + +skip_length_delimited_Signed(<<1:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    skip_length_delimited_Signed(Rest, N + 7, X bsl N + Acc, +				 F@_1, F@_2, TrUserData); +skip_length_delimited_Signed(<<0:1, X:7, Rest/binary>>, +			     N, Acc, F@_1, F@_2, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Signed(Rest2, 0, 0, F@_1, F@_2, +			      TrUserData). + +skip_group_Signed(Bin, FNum, Z2, F@_1, F@_2, +		  TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Signed(Rest, 0, Z2, F@_1, F@_2, +			      TrUserData). + +skip_32_Signed(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +	       F@_2, TrUserData) -> +    dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, +			      TrUserData). + +skip_64_Signed(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +	       F@_2, TrUserData) -> +    dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, +			      TrUserData). + +read_group(Bin, FieldNum) -> +    {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), +    <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, +    {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) +  when N < (32-7) -> +    read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, +          FieldNum) -> +    Key = X bsl N + Acc, +    TagLen1 = TagLen + 1, +    case {Key bsr 3, Key band 7} of +        {FieldNum, 4} -> % 4 = group_end +            {NumBytes, TagLen1}; +        {_, 0} -> % 0 = varint +            read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); +        {_, 1} -> % 1 = bits64 +            <<_:64, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); +        {_, 2} -> % 2 = length_delimited +            read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); +        {_, 3} -> % 3 = group_start +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 4} -> % 4 = group_end +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 5} -> % 5 = bits32 +            <<_:32, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) +    end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> +    read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> +    Len = X bsl N + Acc, +    NumBytes1 = NumBytes + 1, +    <<_:Len/binary, Tl2/binary>> = Tl, +    read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> +    merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Signed' -> merge_msg_Signed(Prev, New, TrUserData) +    end. + +-compile({nowarn_unused_function,merge_msg_Signed/3}). +merge_msg_Signed(#{} = PMsg, +		 #{payload := NFpayload} = NMsg, _) -> +    S1 = #{payload => NFpayload}, +    case {PMsg, NMsg} of +      {_, #{signature := NFsignature}} -> +	  S1#{signature => NFsignature}; +      {#{signature := PFsignature}, _} -> +	  S1#{signature => PFsignature}; +      _ -> S1 +    end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> +    verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Signed' -> v_msg_Signed(Msg, [MsgName], TrUserData); +      _ -> mk_type_error(not_a_known_message, Msg, []) +    end. + + +-compile({nowarn_unused_function,v_msg_Signed/3}). +v_msg_Signed(#{payload := F1} = M, Path, TrUserData) -> +    v_type_bytes(F1, [payload | Path], TrUserData), +    case M of +      #{signature := F2} -> +	  v_type_bytes(F2, [signature | Path], TrUserData); +      _ -> ok +    end, +    lists:foreach(fun (payload) -> ok; +		      (signature) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Signed(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [payload] -- maps:keys(M), 'Signed'}, +		  M, Path); +v_msg_Signed(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Signed'}, X, Path). + +-compile({nowarn_unused_function,v_type_bytes/3}). +v_type_bytes(B, _Path, _TrUserData) when is_binary(B) -> +    ok; +v_type_bytes(B, _Path, _TrUserData) when is_list(B) -> +    ok; +v_type_bytes(X, Path, _TrUserData) -> +    mk_type_error(bad_binary_value, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> +    Path2 = prettify_path(Path), +    erlang:error({gpb_type_error, +		  {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> +    list_to_atom(string:join(lists:map(fun atom_to_list/1, +				       lists:reverse(PathR)), +			     ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> +    [{{msg, 'Signed'}, +      [#{name => payload, fnum => 1, rnum => 2, type => bytes, +	 occurrence => required, opts => []}, +       #{name => signature, fnum => 2, rnum => 3, +	 type => bytes, occurrence => optional, opts => []}]}]. + + +get_msg_names() -> ['Signed']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Signed']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> +    case find_msg_def(MsgName) of +      Fs when is_list(Fs) -> Fs; +      error -> erlang:error({no_such_msg, MsgName}) +    end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> +    erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Signed') -> +    [#{name => payload, fnum => 1, rnum => 2, type => bytes, +       occurrence => required, opts => []}, +     #{name => signature, fnum => 2, rnum => 3, +       type => bytes, occurrence => optional, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> +    erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> +    "4.3.1". + +gpb_version_as_list() -> +    [4,3,1]. diff --git a/src/r3_hex_pb_versions.erl b/src/r3_hex_pb_versions.erl new file mode 100644 index 0000000..66407dd --- /dev/null +++ b/src/r3_hex_pb_versions.erl @@ -0,0 +1,958 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_versions). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Versions'() :: +      #{packages                => ['Package'()],   % = 1 +        repository              => iodata()         % = 2 +       }. + +-type 'Package'() :: +      #{name                    => iodata(),        % = 1 +        versions                => [iodata()],      % = 2 +        retired                 => [integer()]      % = 3, 32 bits +       }. + +-export_type(['Versions'/0, 'Package'/0]). + +-spec encode_msg('Versions'() | 'Package'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> +    encode_msg(Msg, MsgName, []). + +-spec encode_msg('Versions'() | 'Package'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> +    verify_msg(Msg, MsgName, Opts), +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Versions' -> +	  e_msg_Versions(id(Msg, TrUserData), TrUserData); +      'Package' -> +	  e_msg_Package(id(Msg, TrUserData), TrUserData) +    end. + + +e_msg_Versions(Msg, TrUserData) -> +    e_msg_Versions(Msg, <<>>, TrUserData). + + +e_msg_Versions(#{repository := F2} = M, Bin, +	       TrUserData) -> +    B1 = case M of +	   #{packages := F1} -> +	       TrF1 = id(F1, TrUserData), +	       if TrF1 == [] -> Bin; +		  true -> e_field_Versions_packages(TrF1, Bin, TrUserData) +	       end; +	   _ -> Bin +	 end, +    begin +      TrF2 = id(F2, TrUserData), +      e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) +    end. + +e_msg_Package(Msg, TrUserData) -> +    e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F1} = M, Bin, TrUserData) -> +    B1 = begin +	   TrF1 = id(F1, TrUserData), +	   e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) +	 end, +    B2 = case M of +	   #{versions := F2} -> +	       TrF2 = id(F2, TrUserData), +	       if TrF2 == [] -> B1; +		  true -> e_field_Package_versions(TrF2, B1, TrUserData) +	       end; +	   _ -> B1 +	 end, +    case M of +      #{retired := F3} -> +	  TrF3 = id(F3, TrUserData), +	  if TrF3 == [] -> B2; +	     true -> e_field_Package_retired(TrF3, B2, TrUserData) +	  end; +      _ -> B2 +    end. + +e_mfield_Versions_packages(Msg, Bin, TrUserData) -> +    SubBin = e_msg_Package(Msg, <<>>, TrUserData), +    Bin2 = e_varint(byte_size(SubBin), Bin), +    <<Bin2/binary, SubBin/binary>>. + +e_field_Versions_packages([Elem | Rest], Bin, +			  TrUserData) -> +    Bin2 = <<Bin/binary, 10>>, +    Bin3 = e_mfield_Versions_packages(id(Elem, TrUserData), +				      Bin2, TrUserData), +    e_field_Versions_packages(Rest, Bin3, TrUserData); +e_field_Versions_packages([], Bin, _TrUserData) -> Bin. + +e_field_Package_versions([Elem | Rest], Bin, +			 TrUserData) -> +    Bin2 = <<Bin/binary, 18>>, +    Bin3 = e_type_string(id(Elem, TrUserData), Bin2, +			 TrUserData), +    e_field_Package_versions(Rest, Bin3, TrUserData); +e_field_Package_versions([], Bin, _TrUserData) -> Bin. + +e_field_Package_retired(Elems, Bin, TrUserData) +    when Elems =/= [] -> +    SubBin = e_pfield_Package_retired(Elems, <<>>, +				      TrUserData), +    Bin2 = <<Bin/binary, 26>>, +    Bin3 = e_varint(byte_size(SubBin), Bin2), +    <<Bin3/binary, SubBin/binary>>; +e_field_Package_retired([], Bin, _TrUserData) -> Bin. + +e_pfield_Package_retired([Value | Rest], Bin, +			 TrUserData) -> +    Bin2 = e_type_int32(id(Value, TrUserData), Bin, +			TrUserData), +    e_pfield_Package_retired(Rest, Bin2, TrUserData); +e_pfield_Package_retired([], Bin, _TrUserData) -> Bin. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> +    e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> +    e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) +    when 0 =< Value, Value =< 127 -> +    <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> +    <<N:64/unsigned-native>> = <<Value:64/signed-native>>, +    e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> +    <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> +    <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> +    Utf8 = unicode:characters_to_binary(S), +    Bin2 = e_varint(byte_size(Utf8), Bin), +    <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_binary(Bytes) -> +    Bin2 = e_varint(byte_size(Bytes), Bin), +    <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) +    when is_list(Bytes) -> +    BytesBin = iolist_to_binary(Bytes), +    Bin2 = e_varint(byte_size(BytesBin), Bin), +    <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> +    <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> +    <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> +    <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> +    <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> +    <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> +    <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> +    <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> +    <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> +    Bin2 = <<Bin/binary, (N band 127 bor 128)>>, +    e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> +    decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> +    TrUserData = proplists:get_value(user_data, Opts), +    decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> +    try decode_msg_2_doit(MsgName, Bin, TrUserData) +    catch Class:Reason -> +        StackTrace = erlang:get_stacktrace(), +        error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) +    end. +-endif. + +-endif. + +decode_msg_2_doit('Versions', Bin, TrUserData) -> +    id(d_msg_Versions(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Package', Bin, TrUserData) -> +    id(d_msg_Package(Bin, TrUserData), TrUserData). + + + +d_msg_Versions(Bin, TrUserData) -> +    dfp_read_field_def_Versions(Bin, 0, 0, +				id([], TrUserData), id('$undef', TrUserData), +				TrUserData). + +dfp_read_field_def_Versions(<<10, Rest/binary>>, Z1, Z2, +			    F@_1, F@_2, TrUserData) -> +    d_field_Versions_packages(Rest, Z1, Z2, F@_1, F@_2, +			      TrUserData); +dfp_read_field_def_Versions(<<18, Rest/binary>>, Z1, Z2, +			    F@_1, F@_2, TrUserData) -> +    d_field_Versions_repository(Rest, Z1, Z2, F@_1, F@_2, +				TrUserData); +dfp_read_field_def_Versions(<<>>, 0, 0, R1, F@_2, +			    TrUserData) -> +    S1 = #{repository => F@_2}, +    if R1 == '$undef' -> S1; +       true -> S1#{packages => lists_reverse(R1, TrUserData)} +    end; +dfp_read_field_def_Versions(Other, Z1, Z2, F@_1, F@_2, +			    TrUserData) -> +    dg_read_field_def_Versions(Other, Z1, Z2, F@_1, F@_2, +			       TrUserData). + +dg_read_field_def_Versions(<<1:1, X:7, Rest/binary>>, N, +			   Acc, F@_1, F@_2, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Versions(Rest, N + 7, X bsl N + Acc, +			       F@_1, F@_2, TrUserData); +dg_read_field_def_Versions(<<0:1, X:7, Rest/binary>>, N, +			   Acc, F@_1, F@_2, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Versions_packages(Rest, 0, 0, F@_1, F@_2, +				    TrUserData); +      18 -> +	  d_field_Versions_repository(Rest, 0, 0, F@_1, F@_2, +				      TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Versions(Rest, 0, 0, F@_1, F@_2, +				     TrUserData); +	    1 -> +		skip_64_Versions(Rest, 0, 0, F@_1, F@_2, TrUserData); +	    2 -> +		skip_length_delimited_Versions(Rest, 0, 0, F@_1, F@_2, +					       TrUserData); +	    3 -> +		skip_group_Versions(Rest, Key bsr 3, 0, F@_1, F@_2, +				    TrUserData); +	    5 -> +		skip_32_Versions(Rest, 0, 0, F@_1, F@_2, TrUserData) +	  end +    end; +dg_read_field_def_Versions(<<>>, 0, 0, R1, F@_2, +			   TrUserData) -> +    S1 = #{repository => F@_2}, +    if R1 == '$undef' -> S1; +       true -> S1#{packages => lists_reverse(R1, TrUserData)} +    end. + +d_field_Versions_packages(<<1:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Versions_packages(Rest, N + 7, X bsl N + Acc, +			      F@_1, F@_2, TrUserData); +d_field_Versions_packages(<<0:1, X:7, Rest/binary>>, N, +			  Acc, Prev, F@_2, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bs:Len/binary, Rest2/binary>> = Rest, +			   {id(d_msg_Package(Bs, TrUserData), TrUserData), +			    Rest2} +			 end, +    dfp_read_field_def_Versions(RestF, 0, 0, +				cons(NewFValue, Prev, TrUserData), F@_2, +				TrUserData). + +d_field_Versions_repository(<<1:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    d_field_Versions_repository(Rest, N + 7, X bsl N + Acc, +				F@_1, F@_2, TrUserData); +d_field_Versions_repository(<<0:1, X:7, Rest/binary>>, +			    N, Acc, F@_1, _, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Versions(RestF, 0, 0, F@_1, +				NewFValue, TrUserData). + +skip_varint_Versions(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		     F@_1, F@_2, TrUserData) -> +    skip_varint_Versions(Rest, Z1, Z2, F@_1, F@_2, +			 TrUserData); +skip_varint_Versions(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		     F@_1, F@_2, TrUserData) -> +    dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, +				TrUserData). + +skip_length_delimited_Versions(<<1:1, X:7, +				 Rest/binary>>, +			       N, Acc, F@_1, F@_2, TrUserData) +    when N < 57 -> +    skip_length_delimited_Versions(Rest, N + 7, +				   X bsl N + Acc, F@_1, F@_2, TrUserData); +skip_length_delimited_Versions(<<0:1, X:7, +				 Rest/binary>>, +			       N, Acc, F@_1, F@_2, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Versions(Rest2, 0, 0, F@_1, F@_2, +				TrUserData). + +skip_group_Versions(Bin, FNum, Z2, F@_1, F@_2, +		    TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Versions(Rest, 0, Z2, F@_1, F@_2, +				TrUserData). + +skip_32_Versions(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		 F@_2, TrUserData) -> +    dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, +				TrUserData). + +skip_64_Versions(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		 F@_2, TrUserData) -> +    dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, +				TrUserData). + +d_msg_Package(Bin, TrUserData) -> +    dfp_read_field_def_Package(Bin, 0, 0, +			       id('$undef', TrUserData), id([], TrUserData), +			       id([], TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_name(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			 TrUserData); +dfp_read_field_def_Package(<<18, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_versions(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			     TrUserData); +dfp_read_field_def_Package(<<26, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_pfield_Package_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			     TrUserData); +dfp_read_field_def_Package(<<24, Rest/binary>>, Z1, Z2, +			   F@_1, F@_2, F@_3, TrUserData) -> +    d_field_Package_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			    TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, F@_1, R1, R2, +			   TrUserData) -> +    #{name => F@_1, +      versions => lists_reverse(R1, TrUserData), +      retired => lists_reverse(R2, TrUserData)}; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, +			   F@_3, TrUserData) -> +    dg_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, +			      F@_3, TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 32 - 7 -> +    dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, +			      F@_1, F@_2, F@_3, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, +			  Acc, F@_1, F@_2, F@_3, TrUserData) -> +    Key = X bsl N + Acc, +    case Key of +      10 -> +	  d_field_Package_name(Rest, 0, 0, F@_1, F@_2, F@_3, +			       TrUserData); +      18 -> +	  d_field_Package_versions(Rest, 0, 0, F@_1, F@_2, F@_3, +				   TrUserData); +      26 -> +	  d_pfield_Package_retired(Rest, 0, 0, F@_1, F@_2, F@_3, +				   TrUserData); +      24 -> +	  d_field_Package_retired(Rest, 0, 0, F@_1, F@_2, F@_3, +				  TrUserData); +      _ -> +	  case Key band 7 of +	    0 -> +		skip_varint_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				    TrUserData); +	    1 -> +		skip_64_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				TrUserData); +	    2 -> +		skip_length_delimited_Package(Rest, 0, 0, F@_1, F@_2, +					      F@_3, TrUserData); +	    3 -> +		skip_group_Package(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, +				   TrUserData); +	    5 -> +		skip_32_Package(Rest, 0, 0, F@_1, F@_2, F@_3, +				TrUserData) +	  end +    end; +dg_read_field_def_Package(<<>>, 0, 0, F@_1, R1, R2, +			  TrUserData) -> +    #{name => F@_1, +      versions => lists_reverse(R1, TrUserData), +      retired => lists_reverse(R2, TrUserData)}. + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, +		     F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, +			 F@_2, F@_3, TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, +		     _, F@_2, F@_3, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, NewFValue, F@_2, +			       F@_3, TrUserData). + +d_field_Package_versions(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_versions(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, F@_3, TrUserData); +d_field_Package_versions(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, Prev, F@_3, TrUserData) -> +    {NewFValue, RestF} = begin +			   Len = X bsl N + Acc, +			   <<Bytes:Len/binary, Rest2/binary>> = Rest, +			   {id(binary:copy(Bytes), TrUserData), Rest2} +			 end, +    dfp_read_field_def_Package(RestF, 0, 0, F@_1, +			       cons(NewFValue, Prev, TrUserData), F@_3, +			       TrUserData). + +d_field_Package_retired(<<1:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_field_Package_retired(Rest, N + 7, X bsl N + Acc, +			    F@_1, F@_2, F@_3, TrUserData); +d_field_Package_retired(<<0:1, X:7, Rest/binary>>, N, +			Acc, F@_1, F@_2, Prev, TrUserData) -> +    {NewFValue, RestF} = {begin +			    <<Res:32/signed-native>> = <<(X bsl N + +							    Acc):32/unsigned-native>>, +			    id(Res, TrUserData) +			  end, +			  Rest}, +    dfp_read_field_def_Package(RestF, 0, 0, F@_1, F@_2, +			       cons(NewFValue, Prev, TrUserData), TrUserData). + +d_pfield_Package_retired(<<1:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    d_pfield_Package_retired(Rest, N + 7, X bsl N + Acc, +			     F@_1, F@_2, F@_3, TrUserData); +d_pfield_Package_retired(<<0:1, X:7, Rest/binary>>, N, +			 Acc, F@_1, F@_2, E, TrUserData) -> +    Len = X bsl N + Acc, +    <<PackedBytes:Len/binary, Rest2/binary>> = Rest, +    NewSeq = d_packed_field_Package_retired(PackedBytes, 0, +					    0, E, TrUserData), +    dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, +			       NewSeq, TrUserData). + +d_packed_field_Package_retired(<<1:1, X:7, +				 Rest/binary>>, +			       N, Acc, AccSeq, TrUserData) +    when N < 57 -> +    d_packed_field_Package_retired(Rest, N + 7, +				   X bsl N + Acc, AccSeq, TrUserData); +d_packed_field_Package_retired(<<0:1, X:7, +				 Rest/binary>>, +			       N, Acc, AccSeq, TrUserData) -> +    {NewFValue, RestF} = {begin +			    <<Res:32/signed-native>> = <<(X bsl N + +							    Acc):32/unsigned-native>>, +			    id(Res, TrUserData) +			  end, +			  Rest}, +    d_packed_field_Package_retired(RestF, 0, 0, +				   [NewFValue | AccSeq], TrUserData); +d_packed_field_Package_retired(<<>>, 0, 0, AccSeq, _) -> +    AccSeq. + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, TrUserData) -> +    skip_varint_Package(Rest, Z1, Z2, F@_1, F@_2, F@_3, +			TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, +		    F@_1, F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, TrUserData) +    when N < 57 -> +    skip_length_delimited_Package(Rest, N + 7, +				  X bsl N + Acc, F@_1, F@_2, F@_3, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, +			      N, Acc, F@_1, F@_2, F@_3, TrUserData) -> +    Length = X bsl N + Acc, +    <<_:Length/binary, Rest2/binary>> = Rest, +    dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, F@_2, F@_3, +		   TrUserData) -> +    {_, Rest} = read_group(Bin, FNum), +    dfp_read_field_def_Package(Rest, 0, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, +		F@_2, F@_3, TrUserData) -> +    dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, +			       F@_3, TrUserData). + +read_group(Bin, FieldNum) -> +    {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), +    <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, +    {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) +  when N < (32-7) -> +    read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, +          FieldNum) -> +    Key = X bsl N + Acc, +    TagLen1 = TagLen + 1, +    case {Key bsr 3, Key band 7} of +        {FieldNum, 4} -> % 4 = group_end +            {NumBytes, TagLen1}; +        {_, 0} -> % 0 = varint +            read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); +        {_, 1} -> % 1 = bits64 +            <<_:64, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); +        {_, 2} -> % 2 = length_delimited +            read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); +        {_, 3} -> % 3 = group_start +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 4} -> % 4 = group_end +            read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); +        {_, 5} -> % 5 = bits32 +            <<_:32, Tl2/binary>> = Tl, +            read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) +    end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> +    read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) +  when N < (64-7) -> +    read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> +    Len = X bsl N + Acc, +    NumBytes1 = NumBytes + 1, +    <<_:Len/binary, Tl2/binary>> = Tl, +    read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> +    merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Versions' -> merge_msg_Versions(Prev, New, TrUserData); +      'Package' -> merge_msg_Package(Prev, New, TrUserData) +    end. + +-compile({nowarn_unused_function,merge_msg_Versions/3}). +merge_msg_Versions(#{} = PMsg, +		   #{repository := NFrepository} = NMsg, TrUserData) -> +    S1 = #{repository => NFrepository}, +    case {PMsg, NMsg} of +      {#{packages := PFpackages}, +       #{packages := NFpackages}} -> +	  S1#{packages => +		  'erlang_++'(PFpackages, NFpackages, TrUserData)}; +      {_, #{packages := NFpackages}} -> +	  S1#{packages => NFpackages}; +      {#{packages := PFpackages}, _} -> +	  S1#{packages => PFpackages}; +      {_, _} -> S1 +    end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{} = PMsg, #{name := NFname} = NMsg, +		  TrUserData) -> +    S1 = #{name => NFname}, +    S2 = case {PMsg, NMsg} of +	   {#{versions := PFversions}, +	    #{versions := NFversions}} -> +	       S1#{versions => +		       'erlang_++'(PFversions, NFversions, TrUserData)}; +	   {_, #{versions := NFversions}} -> +	       S1#{versions => NFversions}; +	   {#{versions := PFversions}, _} -> +	       S1#{versions => PFversions}; +	   {_, _} -> S1 +	 end, +    case {PMsg, NMsg} of +      {#{retired := PFretired}, #{retired := NFretired}} -> +	  S2#{retired => +		  'erlang_++'(PFretired, NFretired, TrUserData)}; +      {_, #{retired := NFretired}} -> +	  S2#{retired => NFretired}; +      {#{retired := PFretired}, _} -> +	  S2#{retired => PFretired}; +      {_, _} -> S2 +    end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> +    verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> +    TrUserData = proplists:get_value(user_data, Opts), +    case MsgName of +      'Versions' -> +	  v_msg_Versions(Msg, [MsgName], TrUserData); +      'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); +      _ -> mk_type_error(not_a_known_message, Msg, []) +    end. + + +-compile({nowarn_unused_function,v_msg_Versions/3}). +v_msg_Versions(#{repository := F2} = M, Path, +	       TrUserData) -> +    case M of +      #{packages := F1} -> +	  if is_list(F1) -> +		 _ = [v_msg_Package(Elem, [packages | Path], TrUserData) +		      || Elem <- F1], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, {msg, 'Package'}}, F1, +			       [packages | Path]) +	  end; +      _ -> ok +    end, +    v_type_string(F2, [repository | Path], TrUserData), +    lists:foreach(fun (packages) -> ok; +		      (repository) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Versions(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, +		   [repository] -- maps:keys(M), 'Versions'}, +		  M, Path); +v_msg_Versions(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Versions'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F1} = M, Path, TrUserData) -> +    v_type_string(F1, [name | Path], TrUserData), +    case M of +      #{versions := F2} -> +	  if is_list(F2) -> +		 _ = [v_type_string(Elem, [versions | Path], TrUserData) +		      || Elem <- F2], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, string}, F2, +			       [versions | Path]) +	  end; +      _ -> ok +    end, +    case M of +      #{retired := F3} -> +	  if is_list(F3) -> +		 _ = [v_type_int32(Elem, [retired | Path], TrUserData) +		      || Elem <- F3], +		 ok; +	     true -> +		 mk_type_error({invalid_list_of, int32}, F3, +			       [retired | Path]) +	  end; +      _ -> ok +    end, +    lists:foreach(fun (name) -> ok; +		      (versions) -> ok; +		      (retired) -> ok; +		      (OtherKey) -> +			  mk_type_error({extraneous_key, OtherKey}, M, Path) +		  end, +		  maps:keys(M)), +    ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> +    mk_type_error({missing_fields, [name] -- maps:keys(M), +		   'Package'}, +		  M, Path); +v_msg_Package(X, Path, _TrUserData) -> +    mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_type_int32/3}). +v_type_int32(N, _Path, _TrUserData) +    when -2147483648 =< N, N =< 2147483647 -> +    ok; +v_type_int32(N, Path, _TrUserData) when is_integer(N) -> +    mk_type_error({value_out_of_range, int32, signed, 32}, +		  N, Path); +v_type_int32(X, Path, _TrUserData) -> +    mk_type_error({bad_integer, int32, signed, 32}, X, +		  Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) +    when is_list(S); is_binary(S) -> +    try unicode:characters_to_binary(S) of +      B when is_binary(B) -> ok; +      {error, _, _} -> +	  mk_type_error(bad_unicode_string, S, Path) +    catch +      error:badarg -> +	  mk_type_error(bad_unicode_string, S, Path) +    end; +v_type_string(X, Path, _TrUserData) -> +    mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> +    Path2 = prettify_path(Path), +    erlang:error({gpb_type_error, +		  {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> +    list_to_atom(string:join(lists:map(fun atom_to_list/1, +				       lists:reverse(PathR)), +			     ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> +    [{{msg, 'Versions'}, +      [#{name => packages, fnum => 1, rnum => 2, +	 type => {msg, 'Package'}, occurrence => repeated, +	 opts => []}, +       #{name => repository, fnum => 2, rnum => 3, +	 type => string, occurrence => required, opts => []}]}, +     {{msg, 'Package'}, +      [#{name => name, fnum => 1, rnum => 2, type => string, +	 occurrence => required, opts => []}, +       #{name => versions, fnum => 2, rnum => 3, +	 type => string, occurrence => repeated, opts => []}, +       #{name => retired, fnum => 3, rnum => 4, type => int32, +	 occurrence => repeated, opts => [packed]}]}]. + + +get_msg_names() -> ['Versions', 'Package']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Versions', 'Package']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> +    case find_msg_def(MsgName) of +      Fs when is_list(Fs) -> Fs; +      error -> erlang:error({no_such_msg, MsgName}) +    end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> +    erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Versions') -> +    [#{name => packages, fnum => 1, rnum => 2, +       type => {msg, 'Package'}, occurrence => repeated, +       opts => []}, +     #{name => repository, fnum => 2, rnum => 3, +       type => string, occurrence => required, opts => []}]; +find_msg_def('Package') -> +    [#{name => name, fnum => 1, rnum => 2, type => string, +       occurrence => required, opts => []}, +     #{name => versions, fnum => 2, rnum => 3, +       type => string, occurrence => repeated, opts => []}, +     #{name => retired, fnum => 3, rnum => 4, type => int32, +       occurrence => repeated, opts => [packed]}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> +    erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> +    erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> +    "4.3.1". + +gpb_version_as_list() -> +    [4,3,1]. diff --git a/src/r3_hex_registry.erl b/src/r3_hex_registry.erl new file mode 100644 index 0000000..86f9176 --- /dev/null +++ b/src/r3_hex_registry.erl @@ -0,0 +1,133 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_registry). +-export([ +    encode_names/1, +    decode_names/2, +    encode_versions/1, +    decode_versions/2, +    encode_package/1, +    decode_package/3, +    sign_protobuf/2, +    decode_signed/1, +    decode_and_verify_signed/2, +    sign/2, +    verify/3 +]). +-include_lib("public_key/include/public_key.hrl"). + +-type private_key() :: public_key:rsa_private_key() | binary(). +-type public_key() :: public_key:rsa_public_key() | binary(). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Encode Names message. +encode_names(Names) -> +    r3_hex_pb_names:encode_msg(Names, 'Names'). + +%% @doc +%% Decode message created with encode_names/1. +decode_names(Payload, no_verify) -> +    #{packages := Packages} = r3_hex_pb_names:decode_msg(Payload, 'Names'), +    {ok, Packages}; + +decode_names(Payload, Repository) -> +    case r3_hex_pb_names:decode_msg(Payload, 'Names') of +        #{repository := Repository, packages := Packages} -> +            {ok, Packages}; +        _ -> +            {error, unverified} +    end. + +%% @doc +%% Encode Versions message. +encode_versions(Versions) -> +    r3_hex_pb_versions:encode_msg(Versions, 'Versions'). + +%% @doc +%% Decode message created with encode_versions/1. +decode_versions(Payload, no_verify) -> +    #{packages := Packages} = r3_hex_pb_versions:decode_msg(Payload, 'Versions'), +    {ok, Packages}; + +decode_versions(Payload, Repository) -> +    case r3_hex_pb_versions:decode_msg(Payload, 'Versions') of +        #{repository := Repository, packages := Packages} -> +            {ok, Packages}; +        _ -> +            {error, unverified} +    end. + +%% @doc +%% Encode Package message. +encode_package(Package) -> +    r3_hex_pb_package:encode_msg(Package, 'Package'). + +%% @doc +%% Decode message created with encode_package/1. +decode_package(Payload, no_verify, no_verify) -> +    #{releases := Releases} = r3_hex_pb_package:decode_msg(Payload, 'Package'), +    {ok, Releases}; + +decode_package(Payload, Repository, Package) -> +    case r3_hex_pb_package:decode_msg(Payload, 'Package') of +        #{repository := Repository, name := Package, releases := Releases} -> +            {ok, Releases}; +        _ -> +            {error, unverified} +    end. + +%% @doc +%% Encode Signed message. +sign_protobuf(Payload, PrivateKey) -> +    Signature = sign(Payload, PrivateKey), +    r3_hex_pb_signed:encode_msg(#{payload => Payload, signature => Signature}, 'Signed'). + +%% @doc +%% Decode message created with sign_protobuf/2 without verification. +decode_signed(Signed) -> +    r3_hex_pb_signed:decode_msg(Signed, 'Signed'). + +%% @doc +%% Decode message created with sign_protobuf/2 and verify it against public key. +-spec decode_and_verify_signed(map(), public_key()) -> {ok, binary()} | {error, term()}. +decode_and_verify_signed(Signed, PublicKey) -> +    #{payload := Payload, signature := Signature} = decode_signed(Signed), +    case verify(Payload, Signature, PublicKey) of +        true -> {ok, Payload}; +        false -> {error, unverified}; +        {error, Reason} -> {error, Reason} +    end. + +%% @doc +%% Signs binary with given private key. +-spec sign(binary(), private_key()) -> binary(). +sign(Binary, PrivateKey) -> +    {ok, RSAPrivateKey} = key(PrivateKey), +    public_key:sign(Binary, sha512, RSAPrivateKey). + +%% @doc +%% Verifies binary against signature and a public key. +-spec verify(binary(), binary(), public_key()) -> boolean() | {error, term()}. +verify(Binary, Signature, PublicKey) -> +    case key(PublicKey) of +        {ok, RSAPublicKey} -> public_key:verify(Binary, sha512, Signature, RSAPublicKey); +        {error, Reason} -> {error, Reason} +    end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +key(#'RSAPublicKey'{} = Key) -> +    {ok, Key}; +key(#'RSAPrivateKey'{} = Key) -> +    {ok, Key}; +key(Binary) when is_binary(Binary) -> +    case public_key:pem_decode(Binary) of +        [Entry | _] -> {ok, public_key:pem_entry_decode(Entry)}; +        _ -> {error, bad_key} +    end. diff --git a/src/r3_hex_repo.erl b/src/r3_hex_repo.erl new file mode 100644 index 0000000..dd8264f --- /dev/null +++ b/src/r3_hex_repo.erl @@ -0,0 +1,174 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_repo). +-export([ +    get_names/1, +    get_versions/1, +    get_package/2, +    get_tarball/3 +]). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Gets names resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_names(r3_hex_core:default_config()). +%% {ok, {200, ..., +%%     [ +%%         #{name => <<"package1">>}, +%%         #{name => <<"package2">>}, +%%     ]}} +%% ''' +%% @end +get_names(Config) when is_map(Config) -> +    Verify = maps:get(repo_verify_origin, Config, true), +    Decoder = fun(Data) -> +        case Verify of +            true -> r3_hex_registry:decode_names(Data, repo_name(Config)); +            false -> r3_hex_registry:decode_names(Data, no_verify) +        end +    end, +    get_protobuf(Config, <<"names">>, Decoder). + +%% @doc +%% Gets versions resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_versions(Config). +%% {ok, {200, ..., +%%     [ +%%         #{name => <<"package1">>, retired => [], +%%           versions => [<<"1.0.0">>]}, +%%         #{name => <<"package2">>, retired => [<<"0.5.0>>"], +%%           versions => [<<"0.5.0">>, <<"1.0.0">>]}, +%%     ]}} +%% ''' +%% @end +get_versions(Config) when is_map(Config) -> +    Verify = maps:get(repo_verify_origin, Config, true), +    Decoder = fun(Data) -> +        case Verify of +            true -> r3_hex_registry:decode_versions(Data, repo_name(Config)); +            false -> r3_hex_registry:decode_versions(Data, no_verify) +        end +    end, +    get_protobuf(Config, <<"versions">>, Decoder). + +%% @doc +%% Gets package resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_package(r3_hex_core:default_config(), <<"package1">>). +%% {ok, {200, ..., +%%     { +%%         #{checksum => ..., version => <<"0.5.0">>, dependencies => []}, +%%         #{checksum => ..., version => <<"1.0.0">>, dependencies => [ +%%             #{package => <<"package2">>, optional => true, requirement => <<"~> 0.1">>} +%%         ]}, +%%     ]}} +%% ''' +%% @end +get_package(Config, Name) when is_binary(Name) and is_map(Config) -> +    Verify = maps:get(repo_verify_origin, Config, true), +    Decoder = fun(Data) -> +        case Verify of +            true -> r3_hex_registry:decode_package(Data, repo_name(Config), Name); +            false -> r3_hex_registry:decode_package(Data, no_verify, no_verify) +        end +    end, +    get_protobuf(Config, <<"packages/", Name/binary>>, Decoder). + +%% @doc +%% Gets tarball from the repository. +%% +%% Examples: +%% +%% ``` +%% > {ok, {200, _, Tarball}} = r3_hex_repo:get_tarball(<<"package1">>, <<"1.0.0">>, r3_hex_core:default_config()), +%% > {ok, #{metadata := Metadata}} = r3_hex_tarball:unpack(Tarball, memory). +%% ''' +%% @end +get_tarball(Config, Name, Version) -> +    ReqHeaders = make_headers(Config), + +    case get(Config, tarball_url(Config, Name, Version), ReqHeaders) of +        {ok, {200, RespHeaders, Tarball}} -> +            {ok, {200, RespHeaders, Tarball}}; + +        Other -> +            Other +    end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +get(Config, URI, Headers) -> +    r3_hex_http:request(Config, get, URI, Headers, undefined). + +get_protobuf(Config, Path, Decoder) -> +    PublicKey = maps:get(repo_public_key, Config), +    ReqHeaders = make_headers(Config), + +    case get(Config, build_url(Config, Path), ReqHeaders) of +        {ok, {200, RespHeaders, Compressed}} -> +            Signed = zlib:gunzip(Compressed), +            case decode(Signed, PublicKey, Decoder, Config) of +                {ok, Decoded} -> +                    {ok, {200, RespHeaders, Decoded}}; + +                {error, _} = Error -> +                    Error +            end; + +        Other -> +            Other +    end. + +decode(Signed, PublicKey, Decoder, Config) -> +    Verify = maps:get(repo_verify, Config, true), + +    case Verify of +        true -> +            case r3_hex_registry:decode_and_verify_signed(Signed, PublicKey) of +                {ok, Payload} -> +                    Decoder(Payload); +                Other -> +                    Other +            end; +        false -> +            #{payload := Payload} = r3_hex_registry:decode_signed(Signed), +            Decoder(Payload) +    end. + +repo_name(#{repo_organization := Name}) when is_binary(Name) -> Name; +repo_name(#{repo_name := Name}) when is_binary(Name) -> Name. + +tarball_url(Config, Name, Version) -> +    Filename = tarball_filename(Name, Version), +    build_url(Config, <<"tarballs/", Filename/binary>>). + +build_url(#{repo_url := URI, repo_organization := Org}, Path) when is_binary(Org) -> +    <<URI/binary, "/repos/", Org/binary, "/", Path/binary>>; +build_url(#{repo_url := URI, repo_organization := undefined}, Path) -> +    <<URI/binary, "/", Path/binary>>. + +tarball_filename(Name, Version) -> +    <<Name/binary, "-", Version/binary, ".tar">>. + +make_headers(Config) -> +    maps:fold(fun set_header/3, #{}, Config). + +set_header(http_etag, ETag, Headers) when is_binary(ETag) -> maps:put(<<"if-none-match">>, ETag, Headers); +set_header(repo_key, Token, Headers) when is_binary(Token) -> maps:put(<<"authorization">>, Token, Headers); +set_header(_, _, Headers) -> Headers. diff --git a/src/r3_hex_tarball.erl b/src/r3_hex_tarball.erl new file mode 100644 index 0000000..dd2c77a --- /dev/null +++ b/src/r3_hex_tarball.erl @@ -0,0 +1,507 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_tarball). +-export([create/2, create_docs/1, unpack/2, format_checksum/1, format_error/1]). +-ifdef(TEST). +-export([do_decode_metadata/1, gzip/1, normalize_requirements/1]). +-endif. +-define(VERSION, <<"3">>). +-define(TARBALL_MAX_SIZE, 8 * 1024 * 1024). +-define(TARBALL_MAX_UNCOMPRESSED_SIZE, 64 * 1024 * 1024). +-define(BUILD_TOOL_FILES, [ +    {<<"mix.exs">>, <<"mix">>}, +    {<<"rebar.config">>, <<"rebar3">>}, +    {<<"rebar">>, <<"rebar3">>}, +    {<<"Makefile">>, <<"make">>}, +    {<<"Makefile.win">>, <<"make">>} +]). +-include_lib("kernel/include/file.hrl"). + +-type checksum() :: binary(). +-type contents() :: #{filename() => binary()}. +-type filename() :: string(). +-type files() :: [filename() | {filename(), filename()}] | contents(). +-type metadata() :: map(). +-type tarball() :: binary(). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Creates a package tarball. +%% +%% Examples: +%% +%% ``` +%% > Metadata = #{<<"name">> => <<"foo">>, <<"version">> => <<"1.0.0">>}, +%% > Files = [{"src/foo.erl", <<"-module(foo).">>}], +%% > {ok, {Tarball, Checksum}} = r3_hex_tarball:create(Metadata, Files). +%% > Tarball. +%% <<86,69,...>> +%% > Checksum. +%% <<40,32,...>> +%% ''' +%% @end +-spec create(metadata(), files()) -> {ok, {tarball(), checksum()}}. +create(Metadata, Files) -> +    MetadataBinary = encode_metadata(Metadata), +    ContentsTarball = create_memory_tarball(Files), +    ContentsTarballCompressed = gzip(ContentsTarball), +    Checksum = checksum(?VERSION, MetadataBinary, ContentsTarballCompressed), +    ChecksumBase16 = encode_base16(Checksum), + +    OuterFiles = [ +       {"VERSION", ?VERSION}, +       {"CHECKSUM", ChecksumBase16}, +       {"metadata.config", MetadataBinary}, +       {"contents.tar.gz", ContentsTarballCompressed} +    ], + +    Tarball = create_memory_tarball(OuterFiles), + +    UncompressedSize = byte_size(ContentsTarball), + +    case(byte_size(Tarball) > ?TARBALL_MAX_SIZE) or (UncompressedSize > ?TARBALL_MAX_UNCOMPRESSED_SIZE) of +        true -> +            {error, {tarball, too_big}}; + +        false -> +            {ok, {Tarball, Checksum}} +    end. + +%% @doc +%% Creates a docs tarball. +%% +%% Examples: +%% +%% ``` +%% > Files = [{"doc/index.html", <<"Docs">>}], +%% > {ok, {Tarball, Checksum}} = r3_hex_tarball:create_docs(Files). +%% > Tarball. +%% %%=> <<86,69,...>> +%% > Checksum. +%% %%=> <<40,32,...>> +%% ''' +%% @end +-spec create_docs(files()) -> {ok, {tarball(), checksum()}}. +create_docs(Files) -> +    UncompressedTarball = create_memory_tarball(Files), +    UncompressedSize = byte_size(UncompressedTarball), +    Tarball = gzip(UncompressedTarball), +    Checksum = checksum(Tarball), +    Size = byte_size(Tarball), + +    case(Size > ?TARBALL_MAX_SIZE) or (UncompressedSize > ?TARBALL_MAX_UNCOMPRESSED_SIZE) of +        true -> +            {error, {tarball, too_big}}; + +        false -> +            {ok, {Tarball, Checksum}} +    end. + +%% @doc +%% Unpacks a package tarball. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_tarball:unpack(Tarball, memory). +%% {ok,#{checksum => <<...>>, +%%       contents => [{"src/foo.erl",<<"-module(foo).">>}], +%%       metadata => #{<<"name">> => <<"foo">>, ...}}} +%% +%% > r3_hex_tarball:unpack(Tarball, "path/to/unpack"). +%% {ok,#{checksum => <<...>>, +%%       metadata => #{<<"name">> => <<"foo">>, ...}}} +%% ''' +-spec unpack(tarball(), memory) -> +                {ok, #{checksum => checksum(), metadata => metadata(), contents => contents()}} | +                {error, term()}; +            (tarball(), filename()) -> +                {ok, #{checksum => checksum(), metadata => metadata()}} | +                {error, term()}. +unpack(Tarball, _) when byte_size(Tarball) > ?TARBALL_MAX_SIZE -> +    {error, {tarball, too_big}}; + +unpack(Tarball, Output) -> +    case r3_hex_erl_tar:extract({binary, Tarball}, [memory]) of +        {ok, []} -> +            {error, {tarball, empty}}; + +        {ok, FileList} -> +            do_unpack(maps:from_list(FileList), Output); + +        {error, Reason} -> +            {error, {tarball, Reason}} +    end. + +%% @doc +%% Returns base16-encoded representation of checksum. +-spec format_checksum(checksum()) -> binary(). +format_checksum(Checksum) -> +    encode_base16(Checksum). + +%% @doc +%% Converts an error reason term to a human-readable error message string. +-spec format_error(term()) -> string(). +format_error({tarball, empty}) -> "empty tarball"; +format_error({tarball, too_big}) -> "tarball is too big"; +format_error({tarball, {missing_files, Files}}) -> io_lib:format("missing files: ~p", [Files]); +format_error({tarball, {invalid_files, Files}}) -> io_lib:format("invalid files: ~p", [Files]); +format_error({tarball, {bad_version, Vsn}}) -> io_lib:format("unsupported version: ~p", [Vsn]); +format_error({tarball, invalid_checksum}) -> "invalid tarball checksum"; +format_error({tarball, Reason}) -> "tarball error, " ++ r3_hex_erl_tar:format_error(Reason); +format_error({inner_tarball, Reason}) -> "inner tarball error, " ++ r3_hex_erl_tar:format_error(Reason); +format_error({metadata, invalid_terms}) -> "error reading package metadata: invalid terms"; +format_error({metadata, not_key_value}) -> "error reading package metadata: not in key-value format"; +format_error({metadata, Reason}) -> "error reading package metadata" ++ r3_safe_erl_term:format_error(Reason); + +format_error({checksum_mismatch, ExpectedChecksum, ActualChecksum}) -> +    io_lib:format( +        "tarball checksum mismatch~n~n" ++ +        "Expected (base16-encoded): ~s~n" ++ +        "Actual   (base16-encoded): ~s", +        [encode_base16(ExpectedChecksum), encode_base16(ActualChecksum)]). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +checksum(Version, MetadataBinary, ContentsBinary) -> +    Blob = <<Version/binary, MetadataBinary/binary, ContentsBinary/binary>>, +    crypto:hash(sha256, Blob). + +checksum(ContentsBinary) -> +    Blob = <<ContentsBinary/binary>>, +    crypto:hash(sha256, Blob). + +encode_metadata(Meta) -> +    Data = lists:map( +        fun(MetaPair) -> +            String = io_lib_pretty:print(binarify(MetaPair), [{encoding, utf8}]), +            unicode:characters_to_binary([String, ".\n"]) +        end, maps:to_list(Meta)), +    iolist_to_binary(Data). + +do_unpack(Files, Output) -> +    State = #{ +        checksum => undefined, +        contents => undefined, +        files => Files, +        metadata => undefined, +        output => Output +    }, +    State1 = check_files(State), +    State2 = check_version(State1), +    State3 = check_checksum(State2), +    State4 = decode_metadata(State3), +    finish_unpack(State4). + +finish_unpack({error, _} = Error) -> +    Error; +finish_unpack(#{metadata := Metadata, files := Files, output := Output}) -> +    _Version = maps:get("VERSION", Files), +    Checksum = decode_base16(maps:get("CHECKSUM", Files)), +    ContentsBinary = maps:get("contents.tar.gz", Files), +    case unpack_tarball(ContentsBinary, Output) of +        ok -> +            copy_metadata_config(Output, maps:get("metadata.config", Files)), +            {ok, #{checksum => Checksum, metadata => Metadata}}; + +        {ok, Contents} -> +            {ok, #{checksum => Checksum, metadata => Metadata, contents => Contents}}; + +        {error, Reason} -> +            {error, {inner_tarball, Reason}} +    end. + +copy_metadata_config(Output, MetadataBinary) -> +    ok = file:write_file(filename:join(Output, "hex_metadata.config"), MetadataBinary). + +check_files(#{files := Files} = State) -> +    RequiredFiles = ["VERSION", "CHECKSUM", "metadata.config", "contents.tar.gz"], +    case diff_keys(Files, RequiredFiles, []) of +        ok -> +            State; + +        {error, {missing_keys, Keys}} -> +            {error, {tarball, {missing_files, Keys}}}; + +        {error, {unknown_keys, Keys}} -> +            {error, {tarball, {invalid_files, Keys}}} +    end. + +check_version({error, _} = Error) -> +    Error; +check_version(#{files := Files} = State) -> +    case maps:get("VERSION", Files) of +        <<"3">> -> +            State; + +        Version -> +            {error, {tarball, {bad_version, Version}}} +    end. + +check_checksum({error, _} = Error) -> +    Error; +check_checksum(#{files := Files} = State) -> +    ChecksumBase16 = maps:get("CHECKSUM", Files), +    ExpectedChecksum = decode_base16(ChecksumBase16), + +    Version = maps:get("VERSION", Files), +    MetadataBinary = maps:get("metadata.config", Files), +    ContentsBinary = maps:get("contents.tar.gz", Files), +    ActualChecksum = checksum(Version, MetadataBinary, ContentsBinary), + +    if +        byte_size(ExpectedChecksum) /= 32 -> +            {error, {tarball, invalid_checksum}}; + +        ExpectedChecksum == ActualChecksum -> +            maps:put(checksum, ExpectedChecksum, State); + +        true -> +            {error, {tarball, {checksum_mismatch, ExpectedChecksum, ActualChecksum}}} +    end. + +decode_metadata({error, _} = Error) -> +    Error; +decode_metadata(#{files := #{"metadata.config" := Binary}} = State) when is_binary(Binary) -> +    case do_decode_metadata(Binary) of +        #{} = Metadata -> maps:put(metadata, normalize_metadata(Metadata), State); +        Other -> Other +    end. + +do_decode_metadata(Binary) when is_binary(Binary) -> +    {ok, String} = characters_to_list(Binary), + +    case r3_safe_erl_term:string(String) of +        {ok, Tokens, _Line} -> +            try +                Terms = r3_safe_erl_term:terms(Tokens), +                maps:from_list(Terms) +            catch +                error:function_clause -> +                    {error, {metadata, invalid_terms}}; + +                error:badarg -> +                    {error, {metadata, not_key_value}} +            end; + +        {error, {_Line, r3_safe_erl_term, Reason}, _Line2} -> +            {error, {metadata, Reason}} +    end. + +characters_to_list(Binary) -> +    case unicode:characters_to_list(Binary) of +        List when is_list(List) -> +            {ok, List}; +        {error, _, _} -> +            case unicode:characters_to_list(Binary, latin1) of +                List when is_list(List) -> {ok, List}; +                Other -> Other +            end +    end. + +normalize_metadata(Metadata1) -> +    Metadata2 = maybe_update_with(<<"requirements">>, fun normalize_requirements/1, Metadata1), +    Metadata3 = maybe_update_with(<<"links">>, fun try_into_map/1, Metadata2), +    Metadata4 = maybe_update_with(<<"extra">>, fun try_into_map/1, Metadata3), +    guess_build_tools(Metadata4). + +normalize_requirements(Requirements) -> +    case is_list(Requirements) andalso (Requirements /= []) andalso is_list(hd(Requirements)) of +        true -> +            maps:from_list(lists:map(fun normalize_legacy_requirement/1, Requirements)); + +        false -> +            try_into_map(fun normalize_normal_requirement/1, Requirements) +    end. + +normalize_normal_requirement({Name, Requirement}) -> +    {Name, try_into_map(Requirement)}. + +normalize_legacy_requirement(Requirement) -> +    Map = maps:from_list(Requirement), +    Name = maps:get(<<"name">>, Map), +    {Name, maps:without([<<"name">>], Map)}. + +guess_build_tools(#{<<"build_tools">> := BuildTools} = Metadata) when is_list(BuildTools) -> +    Metadata; +guess_build_tools(#{<<"files">> := Filenames} = Metadata) -> +    BaseFiles = [Filename || Filename <- Filenames, filename:dirname(binary_to_list(Filename)) == "."], +    BuildTools = lists:usort([Tool || {Filename, Tool} <- ?BUILD_TOOL_FILES, lists:member(Filename, BaseFiles)]), +    Metadata#{<<"build_tools">> => BuildTools}; +guess_build_tools(Metadata) -> +    Metadata. + +%%==================================================================== +%% Tar Helpers +%%==================================================================== + +unpack_tarball(ContentsBinary, memory) -> +    r3_hex_erl_tar:extract({binary, ContentsBinary}, [memory, compressed]); +unpack_tarball(ContentsBinary, Output) -> +    case r3_hex_erl_tar:extract({binary, ContentsBinary}, [{cwd, Output}, compressed]) of +        ok -> +            [try_updating_mtime(filename:join(Output, Path)) || Path <- filelib:wildcard("**", Output)], +            ok; +        Other -> +            Other +    end. + +%% let it silently fail for bad symlinks +try_updating_mtime(Path) -> +    Time = calendar:universal_time(), +    _ = file:write_file_info(Path, #file_info{mtime=Time}, [{time, universal}]), +    ok. + +create_memory_tarball(Files) -> +    Path = tmp_path(), +    {ok, Tar} = r3_hex_erl_tar:open(Path, [write]), + +    try +        add_files(Tar, Files) +    after +        ok = r3_hex_erl_tar:close(Tar) +    end, +    {ok, Tarball} = file:read_file(Path), +    ok = file:delete(Path), +    Tarball. + +tmp_path() -> +    "tmp_" ++ binary_to_list(encode_base16(crypto:strong_rand_bytes(32))). + +add_files(Tar, Files) when is_list(Files) -> +    lists:map(fun(File) -> add_file(Tar, File) end, Files). + +add_file(Tar, {Filename, Contents}) when is_list(Filename) and is_binary(Contents) -> +    ok = r3_hex_erl_tar:add(Tar, Contents, Filename, tar_opts()); +add_file(Tar, Filename) when is_list(Filename) -> +    add_file(Tar, {Filename, Filename}); +add_file(Tar, {Filename, AbsFilename}) when is_list(Filename), is_list(AbsFilename) -> +    {ok, FileInfo} = file:read_link_info(AbsFilename, []), + +    case FileInfo#file_info.type of +        symlink -> +            ok = r3_hex_erl_tar:add(Tar, {Filename, AbsFilename}, tar_opts()); +        directory -> +            case file:list_dir(AbsFilename) of +                {ok, []} -> +                    r3_hex_erl_tar:add(Tar, {Filename, AbsFilename}, tar_opts()); + +                {ok, _} -> +                    ok +            end; +        _ -> +            Mode = FileInfo#file_info.mode, +            {ok, Contents} = file:read_file(AbsFilename), +            ok = r3_hex_erl_tar:add(Tar, Contents, Filename, Mode, tar_opts()) +    end. + +tar_opts() -> +    NixEpoch = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), +    Y2kEpoch = calendar:datetime_to_gregorian_seconds({{2000, 1, 1}, {0, 0, 0}}), +    Epoch = Y2kEpoch - NixEpoch, +    [{atime, Epoch}, {mtime, Epoch}, {ctime, Epoch}, {uid, 0}, {gid, 0}]. + +%% Reproducible gzip by not setting mtime and OS +%% +%% From https://tools.ietf.org/html/rfc1952 +%% +%% +---+---+---+---+---+---+---+---+---+---+ +%% |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->) +%% +---+---+---+---+---+---+---+---+---+---+ +%% +%% +=======================+ +%% |...compressed blocks...| (more-->) +%% +=======================+ +%% +%% +---+---+---+---+---+---+---+---+ +%% |     CRC32     |     ISIZE     | +%% +---+---+---+---+---+---+---+---+ +gzip(Uncompressed) -> +    Compressed = gzip_no_header(Uncompressed), +    Header = <<31, 139, 8, 0, 0, 0, 0, 0, 0, 0>>, +    Crc = erlang:crc32(Uncompressed), +    Size = byte_size(Uncompressed), +    Trailer = <<Crc:32/little, Size:32/little>>, +    iolist_to_binary([Header, Compressed, Trailer]). + +gzip_no_header(Uncompressed) -> +    Zstream = zlib:open(), + +    try +        zlib:deflateInit(Zstream, default, deflated, -15, 8, default), +        Compressed = zlib:deflate(Zstream, Uncompressed, finish), +        zlib:deflateEnd(Zstream), +        iolist_to_binary(Compressed) +    after +        zlib:close(Zstream) +    end. + +%%==================================================================== +%% Helpers +%%==================================================================== + +binarify(Binary) when is_binary(Binary) -> Binary; +binarify(Number) when is_number(Number) -> Number; +binarify(Atom) when Atom == undefined orelse is_boolean(Atom) -> Atom; +binarify(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8); +binarify(List) when is_list(List) -> +    [binarify(E) || E <- List]; +binarify({Key, Value}) -> +    {binarify(Key), binarify(Value)}; +binarify(Map) when is_map(Map) -> +     List = maps:to_list(Map), +     lists:map(fun({K, V}) -> binarify({K, V}) end, List). + +diff_keys(Map, RequiredKeys, OptionalKeys) -> +    Keys = maps:keys(Map), +    MissingKeys = RequiredKeys -- Keys, +    UnknownKeys = Keys -- (RequiredKeys ++ OptionalKeys), + +    case {MissingKeys, UnknownKeys} of +        {[], []} -> +            ok; + +        {_, [_ | _]} -> +            {error, {unknown_keys, UnknownKeys}}; + +        _ -> +            {error, {missing_keys, MissingKeys}} +    end. + +maybe_update_with(Key, Fun, Map) -> +    case maps:find(Key, Map) of +        {ok, Value} -> maps:put(Key, Fun(Value), Map); +        error -> Map +    end. + +try_into_map(List) -> +    try_into_map(fun(X) -> X end, List). + +try_into_map(Fun, Input) -> +    case is_list(Input) andalso lists:all(fun(E) -> is_tuple(E) andalso (tuple_size(E) == 2) end, Input) of +        true -> maps:from_list(lists:map(Fun, Input)); +        false -> Input +    end. + +encode_base16(Binary) -> +    <<X:256/big-unsigned-integer>> = Binary, +    String = string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X]))), +    list_to_binary(String). + +%% Based on https://github.com/goj/base16/blob/master/src/base16.erl +%% (C) 2012, Erlang Solutions Ltd. + +decode_base16(Base16) -> +    << <<(unhex(H) bsl 4 + unhex(L))>> || <<H,L>> <= Base16 >>. + +unhex(D) when $0 =< D andalso D =< $9 -> +    D - $0; +unhex(D) when $a =< D andalso D =< $f -> +    10 + D - $a; +unhex(D) when $A =< D andalso D =< $F -> +    10 + D - $A. diff --git a/src/r3_safe_erl_term.erl b/src/r3_safe_erl_term.erl new file mode 100644 index 0000000..34c306a --- /dev/null +++ b/src/r3_safe_erl_term.erl @@ -0,0 +1,678 @@ +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 0). +%% The source of this file is part of leex distribution, as such it +%% has the same Copyright as the other files in the leex +%% distribution. The Copyright is defined in the accompanying file +%% COPYRIGHT. However, the resultant scanner generated by leex is the +%% property of the creator of the scanner and is not covered by that +%% Copyright. + +-module(r3_safe_erl_term). + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% User code. This is placed here to allow extra attributes. +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 26). + +-export([terms/1]). + +terms(Tokens) -> +  terms(Tokens, []). + +terms([{dot, _} = H], Buffer) -> +  [buffer_to_term([H|Buffer])]; +terms([{dot, _} = H|T], Buffer) -> +  [buffer_to_term([H|Buffer])|terms(T, [])]; +terms([H|T], Buffer) -> +  terms(T, [H|Buffer]). + +buffer_to_term(Buffer) -> +  {ok, Term} = erl_parse:parse_term(lists:reverse(Buffer)), +  Term. + +unquote(TokenChars, TokenLen) -> +  lists:sublist(TokenChars, 2, TokenLen - 2). + +tokenize_atom(TokenChars, TokenLine) -> +  try list_to_existing_atom(TokenChars) of +    Atom -> {token, {atom, TokenLine, Atom}} +  catch +    error:badarg -> {error, "illegal atom " ++ TokenChars} +  end. + +escape([$\\|Cs]) -> +  do_escape(Cs); +escape([C|Cs]) -> +  [C|escape(Cs)]; +escape([]) -> []. + +do_escape([O1,O2,O3|S]) when +    O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> +  [(O1*8 + O2)*8 + O3 - 73*$0|escape(S)]; +do_escape([$^,C|Cs]) -> +  [C band 31|escape(Cs)]; +do_escape([C|Cs]) when C >= $\000, C =< $\s -> +  escape(Cs); +do_escape([C|Cs]) -> +  [escape_char(C)|escape(Cs)]. + +escape_char($n) -> $\n;       %\n = LF +escape_char($r) -> $\r;       %\r = CR +escape_char($t) -> $\t;       %\t = TAB +escape_char($v) -> $\v;       %\v = VT +escape_char($b) -> $\b;       %\b = BS +escape_char($f) -> $\f;       %\f = FF +escape_char($e) -> $\e;       %\e = ESC +escape_char($s) -> $\s;       %\s = SPC +escape_char($d) -> $\d;       %\d = DEL +escape_char(C) -> C. + +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 14). + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. +%% Note the line number going into yystate, L0, is line of token +%% start while line number returned is line of token end. We want line +%% of token start. + +string([], L, [], Ts) ->                     % No partial tokens! +    {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> +    case yystate(yystate(), Ics0, L0, 0, reject, 0) of +        {A,Alen,Ics1,L1} ->                  % Accepting end state +            string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts); +        {A,Alen,Ics1,L1,_S1} ->              % Accepting transistion state +            string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts); +        {reject,_Alen,Tlen,_Ics1,L1,_S1} ->  % After a non-accepting state +            {error,{L0,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; +        {A,Alen,Tlen,_Ics1,L1,_S1} -> +            Tcs1 = yysuf(Tcs, Alen), +            L2 = adjust_line(Tlen, Alen, Tcs1, L1), +            string_cont(Tcs1, L2, yyaction(A, Alen, Tcs, L0), Ts) +    end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. Push back characters +%% are prepended to RestChars. + +-dialyzer({nowarn_function, string_cont/4}). + +string_cont(Rest, Line, {token,T}, Ts) -> +    string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {token,T,Push}, Ts) -> +    NewRest = Push ++ Rest, +    string(NewRest, Line, NewRest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> +    string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T,Push}, Ts) -> +    NewRest = Push ++ Rest, +    string(NewRest, Line, NewRest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> +    string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {skip_token,Push}, Ts) -> +    NewRest = Push ++ Rest, +    string(NewRest, Line, NewRest, Ts); +string_cont(_Rest, Line, {error,S}, _Ts) -> +    {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars) -> +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. The continuation is: +%% {token,State,CurrLine,TokenChars,TokenLen,TokenLine,AccAction,AccLen} + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> +    token(yystate(), Chars, Line, Chars, 0, Line, reject, 0); +token({token,State,Line,Tcs,Tlen,Tline,Action,Alen}, Chars, _) -> +    token(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Action, Alen). + +%% token(State, InChars, Line, TokenChars, TokenLen, TokenLine, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% The argument order is chosen to be more efficient. + +token(S0, Ics0, L0, Tcs, Tlen0, Tline, A0, Alen0) -> +    case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of +        %% Accepting end state, we have a token. +        {A1,Alen1,Ics1,L1} -> +            token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline)); +        %% Accepting transition state, can take more chars. +        {A1,Alen1,[],L1,S1} ->                  % Need more chars to check +            {more,{token,S1,L1,Tcs,Alen1,Tline,A1,Alen1}}; +        {A1,Alen1,Ics1,L1,_S1} ->               % Take what we got +            token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline)); +        %% After a non-accepting state, maybe reach accept state later. +        {A1,Alen1,Tlen1,[],L1,S1} ->            % Need more chars to check +            {more,{token,S1,L1,Tcs,Tlen1,Tline,A1,Alen1}}; +        {reject,_Alen1,Tlen1,eof,L1,_S1} ->     % No token match +            %% Check for partial token which is error. +            Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE, +                                          %% Skip eof tail in Tcs. +                                          {illegal,yypre(Tcs, Tlen1)}},L1}; +                     true -> {eof,L1} +                  end, +            {done,Ret,eof}; +        {reject,_Alen1,Tlen1,Ics1,L1,_S1} ->    % No token match +            Error = {Tline,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, +            {done,{error,Error,L1},Ics1}; +        {A1,Alen1,Tlen1,_Ics1,L1,_S1} ->       % Use last accept match +            Tcs1 = yysuf(Tcs, Alen1), +            L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), +            token_cont(Tcs1, L2, yyaction(A1, Alen1, Tcs, Tline)) +    end. + +%% token_cont(RestChars, Line, Token) +%% If we have a token or error then return done, else if we have a +%% skip_token then continue. + +-dialyzer({nowarn_function, token_cont/3}). + +token_cont(Rest, Line, {token,T}) -> +    {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {token,T,Push}) -> +    NewRest = Push ++ Rest, +    {done,{ok,T,Line},NewRest}; +token_cont(Rest, Line, {end_token,T}) -> +    {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T,Push}) -> +    NewRest = Push ++ Rest, +    {done,{ok,T,Line},NewRest}; +token_cont(Rest, Line, skip_token) -> +    token(yystate(), Rest, Line, Rest, 0, Line, reject, 0); +token_cont(Rest, Line, {skip_token,Push}) -> +    NewRest = Push ++ Rest, +    token(yystate(), NewRest, Line, NewRest, 0, Line, reject, 0); +token_cont(Rest, Line, {error,S}) -> +    {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. The continuation is: +%% {tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Tokens,AccAction,AccLen} +%% {skip_tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Error,AccAction,AccLen} + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> +    tokens(yystate(), Chars, Line, Chars, 0, Line, [], reject, 0); +tokens({tokens,State,Line,Tcs,Tlen,Tline,Ts,Action,Alen}, Chars, _) -> +    tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Ts, Action, Alen); +tokens({skip_tokens,State,Line,Tcs,Tlen,Tline,Error,Action,Alen}, Chars, _) -> +    skip_tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Error, Action, Alen). + +%% tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Ts, A0, Alen0) -> +    case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of +        %% Accepting end state, we have a token. +        {A1,Alen1,Ics1,L1} -> +            tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts); +        %% Accepting transition state, can take more chars. +        {A1,Alen1,[],L1,S1} ->                  % Need more chars to check +            {more,{tokens,S1,L1,Tcs,Alen1,Tline,Ts,A1,Alen1}}; +        {A1,Alen1,Ics1,L1,_S1} ->               % Take what we got +            tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts); +        %% After a non-accepting state, maybe reach accept state later. +        {A1,Alen1,Tlen1,[],L1,S1} ->            % Need more chars to check +            {more,{tokens,S1,L1,Tcs,Tlen1,Tline,Ts,A1,Alen1}}; +        {reject,_Alen1,Tlen1,eof,L1,_S1} ->     % No token match +            %% Check for partial token which is error, no need to skip here. +            Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE, +                                          %% Skip eof tail in Tcs. +                                          {illegal,yypre(Tcs, Tlen1)}},L1}; +                     Ts == [] -> {eof,L1}; +                     true -> {ok,yyrev(Ts),L1} +                  end, +            {done,Ret,eof}; +        {reject,_Alen1,Tlen1,_Ics1,L1,_S1} -> +            %% Skip rest of tokens. +            Error = {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, +            skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error); +        {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> +            Token = yyaction(A1, Alen1, Tcs, Tline), +            Tcs1 = yysuf(Tcs, Alen1), +            L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), +            tokens_cont(Tcs1, L2, Token, Ts) +    end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% If we have an end_token or error then return done, else if we have +%% a token then save it and continue, else if we have a skip_token +%% just continue. + +-dialyzer({nowarn_function, tokens_cont/4}). + +tokens_cont(Rest, Line, {token,T}, Ts) -> +    tokens(yystate(), Rest, Line, Rest, 0, Line, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {token,T,Push}, Ts) -> +    NewRest = Push ++ Rest, +    tokens(yystate(), NewRest, Line, NewRest, 0, Line, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> +    {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, {end_token,T,Push}, Ts) -> +    NewRest = Push ++ Rest, +    {done,{ok,yyrev(Ts, [T]),Line},NewRest}; +tokens_cont(Rest, Line, skip_token, Ts) -> +    tokens(yystate(), Rest, Line, Rest, 0, Line, Ts, reject, 0); +tokens_cont(Rest, Line, {skip_token,Push}, Ts) -> +    NewRest = Push ++ Rest, +    tokens(yystate(), NewRest, Line, NewRest, 0, Line, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, _Ts) -> +    skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%%skip_tokens(InChars, Line, Error) -> {done,{error,Error,Line},Ics}. +%% Skip tokens until an end token, junk everything and return the error. + +skip_tokens(Ics, Line, Error) -> +    skip_tokens(yystate(), Ics, Line, Ics, 0, Line, Error, reject, 0). + +%% skip_tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Error, A0, Alen0) -> +    case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of +        {A1,Alen1,Ics1,L1} ->                  % Accepting end state +            skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error); +        {A1,Alen1,[],L1,S1} ->                 % After an accepting state +            {more,{skip_tokens,S1,L1,Tcs,Alen1,Tline,Error,A1,Alen1}}; +        {A1,Alen1,Ics1,L1,_S1} -> +            skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error); +        {A1,Alen1,Tlen1,[],L1,S1} ->           % After a non-accepting state +            {more,{skip_tokens,S1,L1,Tcs,Tlen1,Tline,Error,A1,Alen1}}; +        {reject,_Alen1,_Tlen1,eof,L1,_S1} -> +            {done,{error,Error,L1},eof}; +        {reject,_Alen1,Tlen1,_Ics1,L1,_S1} -> +            skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error); +        {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> +            Token = yyaction(A1, Alen1, Tcs, Tline), +            Tcs1 = yysuf(Tcs, Alen1), +            L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), +            skip_cont(Tcs1, L2, Token, Error) +    end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Skip tokens until we have an end_token or error then return done +%% with the original rror. + +-dialyzer({nowarn_function, skip_cont/4}). + +skip_cont(Rest, Line, {token,_T}, Error) -> +    skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {token,_T,Push}, Error) -> +    NewRest = Push ++ Rest, +    skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {end_token,_T}, Error) -> +    {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {end_token,_T,Push}, Error) -> +    NewRest = Push ++ Rest, +    {done,{error,Error,Line},NewRest}; +skip_cont(Rest, Line, skip_token, Error) -> +    skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {skip_token,Push}, Error) -> +    NewRest = Push ++ Rest, +    skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {error,_S}, Error) -> +    skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0). + +-compile({nowarn_unused_function, [yyrev/1, yyrev/2, yypre/2, yysuf/2]}). + +yyrev(List) -> lists:reverse(List). +yyrev(List, Tail) -> lists:reverse(List, Tail). +yypre(List, N) -> lists:sublist(List, N). +yysuf(List, N) -> lists:nthtail(N, List). + +%% adjust_line(TokenLength, AcceptLength, Chars, Line) -> NewLine +%% Make sure that newlines in Chars are not counted twice. +%% Line has been updated with respect to newlines in the prefix of +%% Chars consisting of (TokenLength - AcceptLength) characters. + +-compile({nowarn_unused_function, adjust_line/4}). + +adjust_line(N, N, _Cs, L) -> L; +adjust_line(T, A, [$\n|Cs], L) -> +    adjust_line(T-1, A, Cs, L-1); +adjust_line(T, A, [_|Cs], L) -> +    adjust_line(T-1, A, Cs, L). + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, CurrTokLen, AcceptAction, AcceptLen) -> +%% {Action, AcceptLen, RestChars, Line} | +%% {Action, AcceptLen, RestChars, Line, State} | +%% {reject, AcceptLen, CurrTokLen, RestChars, Line, State} | +%% {Action, AcceptLen, CurrTokLen, RestChars, Line, State}. +%% Generated state transition functions. The non-accepting end state +%% return signal either an unrecognised character or end of current +%% input. + +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.erl", 360). +yystate() -> 13. + +yystate(20, [60|Ics], Line, Tlen, Action, Alen) -> +    yystate(10, Ics, Line, Tlen+1, Action, Alen); +yystate(20, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,20}; +yystate(19, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [39|Ics], Line, Tlen, Action, Alen) -> +    yystate(15, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,19}; +yystate(18, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [34|Ics], Line, Tlen, Action, Alen) -> +    yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,18}; +yystate(17, [37|Ics], Line, Tlen, _, _) -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, [10|Ics], Line, Tlen, _, _) -> +    yystate(17, Ics, Line+1, Tlen+1, 8, Tlen); +yystate(17, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> +    yystate(17, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 32 -> +    yystate(17, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, Ics, Line, Tlen, _, _) -> +    {8,Tlen,Ics,Line,17}; +yystate(16, Ics, Line, Tlen, _, _) -> +    {4,Tlen,Ics,Line}; +yystate(15, [92|Ics], Line, Tlen, _, _) -> +    yystate(11, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [39|Ics], Line, Tlen, _, _) -> +    yystate(7, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [10|Ics], Line, Tlen, _, _) -> +    yystate(0, Ics, Line+1, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> +    yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 38 -> +    yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 40, C =< 91 -> +    yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 93 -> +    yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, Ics, Line, Tlen, _, _) -> +    {1,Tlen,Ics,Line,15}; +yystate(14, [94|Ics], Line, Tlen, Action, Alen) -> +    yystate(18, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [93|Ics], Line, Tlen, Action, Alen) -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [34|Ics], Line, Tlen, Action, Alen) -> +    yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 95 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,14}; +yystate(13, [125|Ics], Line, Tlen, Action, Alen) -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [123|Ics], Line, Tlen, Action, Alen) -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [93|Ics], Line, Tlen, Action, Alen) -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [91|Ics], Line, Tlen, Action, Alen) -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [62|Ics], Line, Tlen, Action, Alen) -> +    yystate(2, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [61|Ics], Line, Tlen, Action, Alen) -> +    yystate(2, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [60|Ics], Line, Tlen, Action, Alen) -> +    yystate(20, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [47|Ics], Line, Tlen, Action, Alen) -> +    yystate(4, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [46|Ics], Line, Tlen, Action, Alen) -> +    yystate(3, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [39|Ics], Line, Tlen, Action, Alen) -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [37|Ics], Line, Tlen, Action, Alen) -> +    yystate(8, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [35|Ics], Line, Tlen, Action, Alen) -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [34|Ics], Line, Tlen, Action, Alen) -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(17, Ics, Line+1, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 32 -> +    yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 43, C =< 45 -> +    yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 48, C =< 57 -> +    yystate(12, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 97, C =< 122 -> +    yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(13, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,13}; +yystate(12, [C|Ics], Line, Tlen, _, _) when C >= 48, C =< 57 -> +    yystate(12, Ics, Line, Tlen+1, 3, Tlen); +yystate(12, Ics, Line, Tlen, _, _) -> +    {3,Tlen,Ics,Line,12}; +yystate(11, [94|Ics], Line, Tlen, Action, Alen) -> +    yystate(19, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [93|Ics], Line, Tlen, Action, Alen) -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [39|Ics], Line, Tlen, Action, Alen) -> +    yystate(15, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 95 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,11}; +yystate(10, Ics, Line, Tlen, _, _) -> +    {5,Tlen,Ics,Line}; +yystate(9, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [34|Ics], Line, Tlen, Action, Alen) -> +    yystate(1, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> +    yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,9}; +yystate(8, [37|Ics], Line, Tlen, _, _) -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [10|Ics], Line, Tlen, _, _) -> +    yystate(17, Ics, Line+1, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 32 -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 33, C =< 36 -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 38 -> +    yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, Ics, Line, Tlen, _, _) -> +    {8,Tlen,Ics,Line,8}; +yystate(7, Ics, Line, Tlen, _, _) -> +    {1,Tlen,Ics,Line}; +yystate(6, [92|Ics], Line, Tlen, _, _) -> +    yystate(14, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [34|Ics], Line, Tlen, _, _) -> +    yystate(1, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [10|Ics], Line, Tlen, _, _) -> +    yystate(9, Ics, Line+1, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> +    yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 33 -> +    yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 35, C =< 91 -> +    yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 93 -> +    yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, Ics, Line, Tlen, _, _) -> +    {2,Tlen,Ics,Line,6}; +yystate(5, [95|Ics], Line, Tlen, _, _) -> +    yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [64|Ics], Line, Tlen, _, _) -> +    yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 48, C =< 57 -> +    yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 65, C =< 90 -> +    yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 97, C =< 122 -> +    yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, Ics, Line, Tlen, _, _) -> +    {0,Tlen,Ics,Line,5}; +yystate(4, Ics, Line, Tlen, _, _) -> +    {7,Tlen,Ics,Line}; +yystate(3, Ics, Line, Tlen, _, _) -> +    {6,Tlen,Ics,Line}; +yystate(2, [62|Ics], Line, Tlen, Action, Alen) -> +    yystate(10, Ics, Line, Tlen+1, Action, Alen); +yystate(2, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,2}; +yystate(1, Ics, Line, Tlen, _, _) -> +    {2,Tlen,Ics,Line}; +yystate(0, [92|Ics], Line, Tlen, Action, Alen) -> +    yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [39|Ics], Line, Tlen, Action, Alen) -> +    yystate(7, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [10|Ics], Line, Tlen, Action, Alen) -> +    yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> +    yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,0}; +yystate(S, Ics, Line, Tlen, Action, Alen) -> +    {Action,Alen,Tlen,Ics,Line,S}. + +%% yyaction(Action, TokenLength, TokenChars, TokenLine) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +yyaction(0, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_0(TokenChars, TokenLine); +yyaction(1, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_1(TokenChars, TokenLen, TokenLine); +yyaction(2, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_2(TokenChars, TokenLen, TokenLine); +yyaction(3, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_3(TokenChars, TokenLine); +yyaction(4, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_4(TokenChars, TokenLine); +yyaction(5, TokenLen, YYtcs, TokenLine) -> +    TokenChars = yypre(YYtcs, TokenLen), +    yyaction_5(TokenChars, TokenLine); +yyaction(6, _, _, TokenLine) -> +    yyaction_6(TokenLine); +yyaction(7, _, _, TokenLine) -> +    yyaction_7(TokenLine); +yyaction(8, _, _, _) -> +    yyaction_8(); +yyaction(_, _, _, _) -> error. + +-compile({inline,yyaction_0/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 14). +yyaction_0(TokenChars, TokenLine) -> +     tokenize_atom (TokenChars, TokenLine) . + +-compile({inline,yyaction_1/3}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 15). +yyaction_1(TokenChars, TokenLen, TokenLine) -> +     tokenize_atom (escape (unquote (TokenChars, TokenLen)), TokenLine) . + +-compile({inline,yyaction_2/3}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 16). +yyaction_2(TokenChars, TokenLen, TokenLine) -> +     { token, { string, TokenLine, escape (unquote (TokenChars, TokenLen)) } } . + +-compile({inline,yyaction_3/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 17). +yyaction_3(TokenChars, TokenLine) -> +     { token, { integer, TokenLine, list_to_integer (TokenChars) } } . + +-compile({inline,yyaction_4/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 18). +yyaction_4(TokenChars, TokenLine) -> +     { token, { list_to_atom (TokenChars), TokenLine } } . + +-compile({inline,yyaction_5/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 19). +yyaction_5(TokenChars, TokenLine) -> +     { token, { list_to_atom (TokenChars), TokenLine } } . + +-compile({inline,yyaction_6/1}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 20). +yyaction_6(TokenLine) -> +     { token, { dot, TokenLine } } . + +-compile({inline,yyaction_7/1}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 21). +yyaction_7(TokenLine) -> +     { token, { '/', TokenLine } } . + +-compile({inline,yyaction_8/0}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 22). +yyaction_8() -> +     skip_token . + +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 313). diff --git a/src/r3_safe_erl_term.xrl b/src/r3_safe_erl_term.xrl new file mode 100644 index 0000000..742aa6b --- /dev/null +++ b/src/r3_safe_erl_term.xrl @@ -0,0 +1,79 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%%% Author  : Robert Virding +%%% Purpose : Token definitions for Erlang. + +Definitions. + +D = [0-9] +U = [A-Z] +L = [a-z] +A = ({U}|{L}|{D}|_|@) +WS = ([\000-\s]|%.*) + +Rules. + +{L}{A}*             : tokenize_atom(TokenChars, TokenLine). +'(\\\^.|\\.|[^'])*' : tokenize_atom(escape(unquote(TokenChars, TokenLen)), TokenLine). +"(\\\^.|\\.|[^"])*" : {token, {string, TokenLine, escape(unquote(TokenChars, TokenLen))}}. +{D}+                : {token, {integer, TokenLine, list_to_integer(TokenChars)}}. +[\#\[\]}{,+-]       : {token, {list_to_atom(TokenChars), TokenLine}}. +(<<|>>|=>)          : {token, {list_to_atom(TokenChars), TokenLine}}. +\.                  : {token, {dot, TokenLine}}. +/                   : {token, {'/', TokenLine}}. +{WS}+               : skip_token. + +Erlang code. + +-export([terms/1]). + +terms(Tokens) -> +  terms(Tokens, []). + +terms([{dot, _} = H], Buffer) -> +  [buffer_to_term([H|Buffer])]; +terms([{dot, _} = H|T], Buffer) -> +  [buffer_to_term([H|Buffer])|terms(T, [])]; +terms([H|T], Buffer) -> +  terms(T, [H|Buffer]). + +buffer_to_term(Buffer) -> +  {ok, Term} = erl_parse:parse_term(lists:reverse(Buffer)), +  Term. + +unquote(TokenChars, TokenLen) -> +  lists:sublist(TokenChars, 2, TokenLen - 2). + +tokenize_atom(TokenChars, TokenLine) -> +  try list_to_existing_atom(TokenChars) of +    Atom -> {token, {atom, TokenLine, Atom}} +  catch +    error:badarg -> {error, "illegal atom " ++ TokenChars} +  end. + +escape([$\\|Cs]) -> +  do_escape(Cs); +escape([C|Cs]) -> +  [C|escape(Cs)]; +escape([]) -> []. + +do_escape([O1,O2,O3|S]) when +    O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> +  [(O1*8 + O2)*8 + O3 - 73*$0|escape(S)]; +do_escape([$^,C|Cs]) -> +  [C band 31|escape(Cs)]; +do_escape([C|Cs]) when C >= $\000, C =< $\s -> +  escape(Cs); +do_escape([C|Cs]) -> +  [escape_char(C)|escape(Cs)]. + +escape_char($n) -> $\n;       %\n = LF +escape_char($r) -> $\r;       %\r = CR +escape_char($t) -> $\t;       %\t = TAB +escape_char($v) -> $\v;       %\v = VT +escape_char($b) -> $\b;       %\b = BS +escape_char($f) -> $\f;       %\f = FF +escape_char($e) -> $\e;       %\e = ESC +escape_char($s) -> $\s;       %\s = SPC +escape_char($d) -> $\d;       %\d = DEL +escape_char(C) -> C. diff --git a/src/rebar.app.src b/src/rebar.app.src index 6058efc..40d953b 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -30,7 +30,6 @@                    relx,                    cf,                    inets, -                  hex_core,                    eunit_formatters]},    {env, [          %% Default log level diff --git a/src/rebar_hex_repos.erl b/src/rebar_hex_repos.erl index babaa32..ed5b1a8 100644 --- a/src/rebar_hex_repos.erl +++ b/src/rebar_hex_repos.erl @@ -20,6 +20,7 @@                    api_url => binary(),                    api_key => binary(),                    repo_url => binary(), +                  repo_key => binary(),                    repo_public_key => binary(),                    repo_verify => binary(),                    repo_verify_origin => binary()}. @@ -68,17 +69,21 @@ repos(HexConfig) ->              merge_repos(RepoList ++ [HexDefaultConfig])      end. -%% merge repos must add a field repo_name to work with hex_core 0.4.0 +%% merge repos must add a field repo_name to work with r3_hex_core 0.5.0  -spec merge_repos([repo()]) -> [repo()].  merge_repos(Repos) -> -    lists:foldl(fun(R=#{name := Name}, ReposAcc) -> -                        %% private organizations include the parent repo before a : +    lists:foldl(fun(R = #{name := Name}, ReposAcc) -> +                        %% private orgs are in the format of <<"parent:org">>                          case rebar_string:split(Name, <<":">>) of                              [Repo, Org] -> -                                %% hex_core uses repo_name for parent + +                                %% We set the repo_organization and api_organization to org +                                %% for fetching and publishing private packages.                                  update_repo_list(R#{name => Name, -                                                    repo_name => Repo, -                                                    organization => Org, +                                                    repo_name => Org, +                                                    repo_organization => Org, +                                                    api_organization => Org, +                                                    api_repository => Org,                                                      parent => Repo}, ReposAcc);                              _ ->                                  update_repo_list(R#{repo_name => Name}, ReposAcc) @@ -86,15 +91,15 @@ merge_repos(Repos) ->                  end, [], Repos).  update_organizations(Repos) -> -    lists:map(fun(Repo=#{organization := Organization, +    lists:map(fun(Repo=#{repo_name := RepoName,                           parent := ParentName}) ->                        {ok, Parent} = get_repo_config(ParentName, Repos),                        ParentRepoUrl = rebar_utils:to_list(maps:get(repo_url, Parent)), -                      {ok, RepoUrl} = +                      {ok, _RepoUrl} =                            rebar_utils:url_append_path(ParentRepoUrl, -                                                      filename:join("repos", rebar_utils:to_list(Organization))), +                                                      filename:join("repos", rebar_utils:to_list(RepoName))),                        %% still let the organization config override this constructed repo url -                      maps:merge(Parent#{repo_url => rebar_utils:to_binary(RepoUrl)}, Repo); +                      maps:merge(Parent#{repo_url => rebar_utils:to_binary(ParentRepoUrl)}, Repo);                   (Repo) ->                        Repo                end, Repos). @@ -107,7 +112,7 @@ update_repo_list(R, []) ->      [R].  default_repo() -> -    HexDefaultConfig = hex_core:default_config(), +    HexDefaultConfig = r3_hex_core:default_config(),      HexDefaultConfig#{name => ?PUBLIC_HEX_REPO, repo_verify_origin => repo_verify_origin()}.  repo_verify_origin() -> diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index fc68cab..a260e47 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -31,7 +31,7 @@ format_error({missing_package, Pkg}) ->  -spec get(rebar_hex_repos:repo(), binary()) -> {ok, map()} | {error, term()}.  get(Config, Name) -> -    try hex_api_package:get(Config, Name) of +    try r3_hex_api_package:get(Config, Name) of          {ok, {200, _Headers, PkgInfo}} ->              {ok, PkgInfo};          {ok, {404, _, _}} -> @@ -233,7 +233,7 @@ parse_checksum(Checksum) ->  update_package(Name, RepoConfig=#{name := Repo}, State) ->      ?MODULE:verify_table(State), -    try hex_repo:get_package(get_package_repo_config(RepoConfig), Name) of +    try r3_hex_repo:get_package(get_package_repo_config(RepoConfig), Name) of          {ok, {200, _Headers, Releases}} ->              _ = insert_releases(Name, Releases, Repo, ?PACKAGE_TABLE),              {ok, RegistryDir} = rebar_packages:registry_dir(State), @@ -244,7 +244,7 @@ update_package(Name, RepoConfig=#{name := Repo}, State) ->              fail;          Error ->              ?DEBUG("Hex get_package request failed: ~p", [Error]), -            %% TODO: add better log message. hex_core should export a format_error +            %% TODO: add better log message. r3_hex_core should export a format_error              ?WARN("Failed to update package from repo ~ts", [Repo]),              fail      catch @@ -255,8 +255,6 @@ update_package(Name, RepoConfig=#{name := Repo}, State) ->  get_package_repo_config(RepoConfig=#{mirror_of := Repo}) ->      get_package_repo_config(maps:remove(mirror_of, RepoConfig#{name => Repo})); -get_package_repo_config(RepoConfig=#{read_key := Key}) -> -    get_package_repo_config(maps:remove(read_key, RepoConfig#{repo_key => Key}));  get_package_repo_config(RepoConfig) ->      RepoConfig. diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 823b7fc..5de2437 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -29,7 +29,7 @@  -spec init(atom(), rebar_state:t()) -> {ok, rebar_resource_v2:resource()}.  init(Type, State) ->      {ok, Vsn} = application:get_key(rebar, vsn), -    BaseConfig = #{http_adapter => hex_http_httpc, +    BaseConfig = #{http_adapter => r3_hex_http_httpc,                     http_user_agent_fragment =>                         <<"(rebar3/", (list_to_binary(Vsn))/binary, ") (httpc)">>,                     http_adapter_config => #{profile => rebar}}, @@ -148,7 +148,7 @@ format_error({bad_registry_checksum, Name, Vsn, Expected, Found}) ->               -> {ok, cached} | {ok, binary(), binary()} | error.  request(Config, Name, Version, ETag) ->      Config1 = Config#{http_etag => ETag}, -    try hex_repo:get_tarball(Config1, Name, Version) of +    try r3_hex_repo:get_tarball(Config1, Name, Version) of          {ok, {200, #{<<"etag">> := ETag1}, Tarball}} ->              {ok, Tarball, ETag1};          {ok, {304, _Headers, _}} -> @@ -248,7 +248,7 @@ serve_from_cache(TmpDir, CachePath, Pkg) ->        Res :: ok | {error,_} | {bad_registry_checksum, integer(), integer()}.  serve_from_memory(TmpDir, Binary, {pkg, _Name, _Vsn, Hash, _RepoConfig}) ->      RegistryChecksum = list_to_integer(binary_to_list(Hash), 16), -    case hex_tarball:unpack(Binary, TmpDir) of +    case r3_hex_tarball:unpack(Binary, TmpDir) of          {ok, #{checksum := <<Checksum:256/big-unsigned>>}} when RegistryChecksum =/= Checksum ->              ?DEBUG("Expected hash ~64.16.0B does not match checksum of fetched package ~64.16.0B",                     [RegistryChecksum, Checksum]), diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 11add61..f1e440a 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -915,7 +915,7 @@ url_append_path(Url, ExtraPath) ->       case http_uri:parse(Url) of           {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->               {ok, lists:append([atom_to_list(Scheme), "://", UserInfo, Host, ":", integer_to_list(Port), -                                filename:join(Path, ExtraPath), "?", Query])}; +                                filename:join(Path, ExtraPath), Query])};           _ ->               error       end. | 
