119 lines
5.1 KiB
Erlang
119 lines
5.1 KiB
Erlang
%%% @doc
|
|
%%% GRIDS URL parsing
|
|
%%%
|
|
%%% GRID(S): Gajumaru Remote Instruction Dispatch (Serialization)
|
|
%%% GRIDS is a Gajumaru protocol for encoding wallet instructions as URLs.
|
|
%%% Version 1 of the protocol consists of two verbs with two contexts each, collapsed to
|
|
%%% four symbols for brevity.
|
|
%%%
|
|
%%% The GRIDS schema begins with "grids://" or "grid://"
|
|
%%% Which way this is interpreted can vary depending on the verb.
|
|
%%%
|
|
%%% The typical "host" component is either an actual hostname or address and an optional
|
|
%%% port number (the defaut port being 3013), or a Gajumaru chain network IDi (in which
|
|
%%% case the port number is ignored if provided). Which way this field is interpreted
|
|
%%% depends on the verb.
|
|
%%%
|
|
%%% The first element of the path after the host component indicates the protocol version.
|
|
%%% Only version 1 exists at the time of this release.
|
|
%%%
|
|
%%% The next element of the path after the version is a single letter that indicates which
|
|
%%% action to take. The following actions are available:
|
|
%%% "s": Spend on Chain
|
|
%%% Constructs a spend transaction to the address indicated in the path component
|
|
%%% indicated in the final path element. Two qargs are valid in the trailing arguments
|
|
%%% section: "a" for amount (in Pucks, not Gajus!), and "p" for data payload.
|
|
%%% In this context the "host" field in the URL is interpreted as a chain network ID.
|
|
%%% "t": Transfer (spend) on Host
|
|
%%% The same as "spend" above, but in this context the host field of the URL is
|
|
%%% interpreted as host[:port] information and the network chain ID that will be used
|
|
%%% will be derived from whatever chain the given host reports.
|
|
%%% "d": Dead-drop signature request
|
|
%%% This instructs the wallet to retrieve a signature data blob from an HTTP or HTTPS
|
|
%%% URL that can be reconstructed by replacing "grids" with "https" or "grid" with
|
|
%%% "http", omitting the "/1/d" path component and then recnstructing the URL.
|
|
%%% This provides a lightweight method for services to enable contract calls from
|
|
%%% wallets that are not capable of compiling contract source.
|
|
%%% @end
|
|
|
|
-module(gd_grids).
|
|
-vsn("0.6.5").
|
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
|
-copyright("QPQ AG <info@qpq.swiss>").
|
|
-license("GPL-3.0-or-later").
|
|
|
|
-export([parse/1]).
|
|
-include("$zx_include/zx_logger.hrl").
|
|
|
|
|
|
-spec parse(URL) -> {ok, Instruction} | {error, Reason}
|
|
when URL :: string(),
|
|
Instruction :: {{spend, chain | node}, {Location, Recipient, Amount, Payload}}
|
|
| {{sign | mess, http | https}, URL},
|
|
Location :: Node :: {inet:ip_address() | inet:hostname(), inet:port_number()}
|
|
| Chain :: binary(),
|
|
Recipient :: gajudesk:id(),
|
|
Amount :: non_neg_integer(),
|
|
Payload :: binary(),
|
|
URL :: string(),
|
|
Reason :: bad_url.
|
|
|
|
parse(URL) ->
|
|
case uri_string:parse(URL) of
|
|
#{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grids"} ->
|
|
spend(R, chain, list_to_binary(H), Q);
|
|
#{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grid"} ->
|
|
spend(R, chain, list_to_binary(H), Q);
|
|
#{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grids"} ->
|
|
spend(R, node, {H, P}, Q);
|
|
#{path := "/1/t/" ++ R, host := H, port := P, query := Q, scheme := "grid"} ->
|
|
spend(R, node, {H, P}, Q);
|
|
#{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grids"} ->
|
|
spend(R, node, {H, 3013}, Q);
|
|
#{path := "/1/t/" ++ R, host := H, query := Q, scheme := "grid"} ->
|
|
spend(R, node, {H, 3013}, Q);
|
|
U = #{path := "/1/d/" ++ L, scheme := "grids"} ->
|
|
NewURL = uri_string:recompose(U#{scheme := "https", path := L}),
|
|
{ok ,{{sign, https}, NewURL}};
|
|
U = #{path := "/1/d/" ++ L, scheme := "grid"} ->
|
|
NewURL = uri_string:recompose(U#{scheme := "http", path := L}),
|
|
{ok, {{sign, http}, NewURL}};
|
|
{error, Reason, Info} ->
|
|
ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]),
|
|
{error, bad_url};
|
|
U ->
|
|
ok = tell("GRIDS cannot proceed with this result: ~p", [U]),
|
|
{error, bad_url}
|
|
end.
|
|
|
|
spend(Recipient, Context, Location, Qwargs) ->
|
|
case dissect_query(Qwargs) of
|
|
{ok, Amount, Payload} ->
|
|
{ok, {{spend, Context}, {Location, Recipient, Amount, Payload}}};
|
|
Error ->
|
|
Error
|
|
end.
|
|
|
|
|
|
dissect_query(Qwargs) ->
|
|
case uri_string:dissect_query(Qwargs) of
|
|
{error, Reason, Info} ->
|
|
ok = tell("URL parsing failed with ~w: ~p", [Reason, Info]),
|
|
{error, bad_url};
|
|
ArgList ->
|
|
case l_to_i(proplists:get_value("a", ArgList, "0")) of
|
|
{ok, Amount} ->
|
|
Payload = list_to_binary(proplists:get_value("p", ArgList, "")),
|
|
{ok, Amount, Payload};
|
|
Error ->
|
|
Error
|
|
end
|
|
end.
|
|
|
|
l_to_i(S) ->
|
|
try
|
|
{ok, list_to_integer(S)}
|
|
catch
|
|
error:badarg -> {error, bad_url}
|
|
end.
|