-module(gtg_ffi). -export([ halt/1, unix_seconds/0, unix_micros/0, gzip/1, gunzip/1, get_cwd/0, format_rfc3339/1, ed25519_generate/0, ed25519_public/1, ed25519_sign/2, lookup_txt/1, wait_http_callback/2 ]). halt(Code) -> erlang:halt(Code). unix_seconds() -> erlang:system_time(second). unix_micros() -> erlang:system_time(microsecond). gzip(Data) when is_binary(Data) -> zlib:gzip(Data). gunzip(Data) when is_binary(Data) -> try {ok, zlib:gunzip(Data)} catch _:_ -> {error, <<"gunzip failed">>} end. get_cwd() -> case file:get_cwd() of {ok, Cwd} -> {ok, unicode:characters_to_binary(Cwd)}; {error, Reason} -> {error, unicode:characters_to_binary(io_lib:format("~p", [Reason]))} end. format_rfc3339(Secs) when is_integer(Secs) -> {{Y, Mo, D}, {H, Mi, S}} = calendar:system_time_to_universal_time(Secs, second), list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ", [Y, Mo, D, H, Mi, S])). %% {PublicKey :: binary(), PrivateSeed :: binary()} — both 32 bytes. ed25519_generate() -> {Pub, Priv} = crypto:generate_key(eddsa, ed25519), Seed = case byte_size(Priv) of 32 -> Priv; 64 -> binary:part(Priv, 0, 32); _ -> Priv end, {Pub, Seed}. ed25519_public(Seed) when is_binary(Seed), byte_size(Seed) =:= 32 -> {Pub, _} = crypto:generate_key(eddsa, ed25519, Seed), Pub; ed25519_public(Priv) when is_binary(Priv), byte_size(Priv) =:= 64 -> Seed = binary:part(Priv, 0, 32), {Pub, _} = crypto:generate_key(eddsa, ed25519, Seed), Pub. ed25519_sign(Msg, Seed) when is_binary(Msg), is_binary(Seed) -> crypto:sign(eddsa, none, Msg, [Seed, ed25519]). lookup_txt(NameBin) when is_binary(NameBin) -> Name = unicode:characters_to_list(NameBin), case inet_res:lookup(Name, in, txt) of Records when is_list(Records) -> lists:flatmap(fun txt_strings/1, Records); _ -> [] end. txt_strings([H | _] = L) when is_list(H) -> [list_to_binary(lists:flatten(L))]; txt_strings(Txt) when is_list(Txt) -> [list_to_binary(Txt)]; txt_strings(Bin) when is_binary(Bin) -> [Bin]; txt_strings(_) -> []. %% Accept one HTTP request on 127.0.0.1:Port and return the raw query string %% from the request target (everything after '?'), or {error, ReasonBin}. wait_http_callback(Port, TimeoutMs) when is_integer(Port), is_integer(TimeoutMs) -> Opts = [ binary, {packet, raw}, {active, false}, {reuseaddr, true}, {ip, {127, 0, 0, 1}} ], case gen_tcp:listen(Port, Opts) of {ok, Listen} -> case gen_tcp:accept(Listen, TimeoutMs) of {ok, Sock} -> case gen_tcp:recv(Sock, 0, TimeoutMs) of {ok, Data} -> Reply = <<"HTTP/1.1 200 OK\r\n", "Content-Type: text/html; charset=utf-8\r\n", "Connection: close\r\n", "Content-Length: 62\r\n", "\r\n", "Authenticated. You can close this tab.">>, _ = gen_tcp:send(Sock, Reply), gen_tcp:close(Sock), gen_tcp:close(Listen), case extract_query(Data) of {ok, Qs} -> {ok, Qs}; error -> {error, <<"could not parse callback request">>} end; {error, Reason} -> gen_tcp:close(Sock), gen_tcp:close(Listen), {error, reason_bin(Reason)} end; {error, Reason} -> gen_tcp:close(Listen), {error, reason_bin(Reason)} end; {error, Reason} -> {error, reason_bin(Reason)} end. extract_query(Data) when is_binary(Data) -> %% First line: METHOD SP target SP HTTP/… case binary:split(Data, <<"\r\n">>) of [Line | _] -> case binary:split(Line, <<" ">>, [global]) of [_, Target | _] -> case binary:split(Target, <<"?">>) of [_, Qs] -> {ok, Qs}; _ -> {ok, <<>>} end; _ -> error end; _ -> error end. reason_bin(Reason) -> unicode:characters_to_binary(io_lib:format("~p", [Reason])).