From d77ee6d8f53901247e75684dcf66ce8b40dbfe89 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 15 Oct 2024 13:58:58 +0900 Subject: [PATCH] Add GRIDS parser --- src/gmc_con.erl | 21 +++++++++++++- src/gmc_grids.erl | 71 +++++++++++++++++++++++++++++++++++++++++++++++ src/gmc_gui.erl | 36 ++++++++++++++++++------ 3 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 src/gmc_grids.erl diff --git a/src/gmc_con.erl b/src/gmc_con.erl index 8aec637..2b11961 100644 --- a/src/gmc_con.erl +++ b/src/gmc_con.erl @@ -13,7 +13,7 @@ -behavior(gen_server). -export([show_ui/1, open_wallet/2, close_wallet/0, password/2, - nonce/1, spend/2, chain/1, + nonce/1, spend/2, chain/1, grids/1, make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1]). -export([encrypt/2, decrypt/2]). -export([start_link/0, stop/0, save/1, save/2]). @@ -103,6 +103,12 @@ chain(ID) -> gen_server:cast(?MODULE, {chain, ID}). +-spec grids(string()) -> ok. + +grids(String) -> + gen_server:cast(?MODULE, {grids, String}). + + -spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok when Type :: {eddsa, ed25519}, Size :: 256, @@ -268,6 +274,9 @@ handle_cast({spend, KeyID, TX}, State) -> handle_cast({chain, ID}, State) -> NewState = do_chain(ID, State), {noreply, NewState}; +handle_cast({grids, String}, State) -> + ok = do_grids(String), + {noreply, State}; handle_cast({make_key, Name, Seed, Encoding, Transform}, State) -> NewState = do_make_key(Name, Seed, Encoding, Transform, State), {noreply, NewState}; @@ -355,6 +364,16 @@ do_chain(ID, State = #s{prefs = Prefs}) -> State. +do_grids(String) -> + case gmc_grids:parse(String) of + {ok, Instruction} -> do_grids2(Instruction); + Error -> gmc_gui:trouble(Error) + end. + +do_grids2(Instruction) -> + tell("GRIDS: ~tp", [Instruction]). + + do_make_key(Name, <<>>, _, Transform, State) -> Bin = crypto:strong_rand_bytes(32), do_make_key2(Name, Bin, Transform, State); diff --git a/src/gmc_grids.erl b/src/gmc_grids.erl new file mode 100644 index 0000000..c1c311d --- /dev/null +++ b/src/gmc_grids.erl @@ -0,0 +1,71 @@ +-module(gmc_grids). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("QPQ AG "). +-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, http | https}, URL}, + Location :: Node :: {inet:ip_address() | inet:hostname(), inet:port_number()} + | Chain :: binary(), + Recipient :: clutch: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 -> + Amount = proplists:get_value("a", ArgList, 0), + Payload = proplists:get_value("p", ArgList, <<>>), + {ok, Amount, Payload} + end. + diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl index 06756db..7f5109d 100644 --- a/src/gmc_gui.erl +++ b/src/gmc_gui.erl @@ -741,14 +741,32 @@ is_int(S) -> lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). -grids_dialogue(State) -> -%grids_dialogue(State = #s{frame = Frame, j = J}) -> - tell("Handle GRIDS URL"), -% ok = -% case zxw:modal_text_input(Frame, J("GRIDS"), J("ZA GRIDS"), [J("URL")]) of -% {ok, [URL]} -> tell("Received URL: ~p", [URL]); -% cancel -> tell("User cancelled GRIDS action.") -% end, +grids_dialogue(State = #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Label = J("GRIDS URL"), + URL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), + URL_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), + _ = wxStaticBoxSizer:add(URL_Sz, URL_Tx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, URL_Sz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(URL_Tx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + case wxTextCtrl:getValue(URL_Tx) of + "" -> ok; + String -> gmc_con:grids(String) + end; + ?wxID_CANCEL -> + ok + end, State. @@ -802,7 +820,7 @@ do_chain(ChainID, #node{ip = IP}, #s{buttons = Buttons}) -> do_ask_password(#s{frame = Frame, prefs = Prefs, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), Sizer = wxBoxSizer:new(?wxVERTICAL), - Label = "Password (leave blank for no password)", + Label = J("Password (leave blank for no password)"), PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY), _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)),