203 lines
6.2 KiB
Erlang
Executable File
203 lines
6.2 KiB
Erlang
Executable File
#!/usr/bin/env escript
|
|
|
|
-mode(compile).
|
|
-include("./ansi.hrl").
|
|
|
|
% ho from pimp pov
|
|
-record(pho,
|
|
{pid :: pid(),
|
|
ref :: reference(),
|
|
name :: string()}).
|
|
|
|
%% pimp state
|
|
-record(ps,
|
|
{lsock = none :: gen_tcp:socket(),
|
|
hos = [] :: [#pho{}],
|
|
ho_names = init_ho_names() :: [string()]}).
|
|
|
|
|
|
%% ho state
|
|
-record(hs,
|
|
{name = none :: string(),
|
|
daddy = none :: pid(),
|
|
asock = none :: gen_tcp:socket(),
|
|
nsock = none :: enoise:noise_socket()}).
|
|
|
|
|
|
main([]) ->
|
|
io:format("RBX example 4 telnet echo server daemon~n"
|
|
"`one' part of the many-to-one relationship~n",
|
|
[]),
|
|
p_init().
|
|
|
|
|
|
noise_options() ->
|
|
% totally safe crypto
|
|
Secret = crypto:hash(sha3_256, <<"alice">>),
|
|
Public = <<116, 159, 91, 248, 138, 250, 73, 40, 231, 50, 81, 110,
|
|
137, 163, 44, 76, 48, 130, 225, 95, 168, 121, 93, 44,
|
|
148, 42, 180, 103, 11, 40, 168, 96>>,
|
|
KP = enoise_keypair:new(dh25519, Secret, Public),
|
|
[{noise, "Noise_NN_25519_ChaChaPoly_BLAKE2b"},
|
|
{e, KP}].
|
|
|
|
|
|
% pimp process startup
|
|
p_init() ->
|
|
p_logln("pimp startup (p_init)"),
|
|
Port = 6969,
|
|
Opts = [binary, {packet, 0}, {active, once}],
|
|
case gen_tcp:listen(Port, Opts) of
|
|
{ok, LSock} ->
|
|
ok = p_logfln("pimp starting: ~tp", [self()]),
|
|
PS_I = #ps{lsock = LSock},
|
|
PS_II = p_spawn_ho(PS_I),
|
|
p_loop(PS_II);
|
|
{error, closed} ->
|
|
%% dont be pimp
|
|
p_logln("listen socket closed")
|
|
end.
|
|
|
|
p_loop(PS = #ps{hos = Hos}) ->
|
|
receive
|
|
{ho, _HoPid, taken} ->
|
|
NewPS = p_spawn_ho(PS),
|
|
p_loop(NewPS);
|
|
{ho, HoPid, sent, Data} ->
|
|
HoName = p_ho_name(HoPid, PS),
|
|
SendMessage = io_lib:format("~ts :: ~ts\r\n", [HoName, Data]),
|
|
ok = p_echo_except(HoPid, HoName, SendMessage, Hos),
|
|
p_loop(PS);
|
|
Down = {'DOWN', _, _, _, _} ->
|
|
NewPS = p_ho_down(Down, PS),
|
|
p_loop(NewPS);
|
|
Unknown ->
|
|
ok = p_logfln("unknown message: ~tw", [Unknown]),
|
|
p_loop(PS)
|
|
end.
|
|
|
|
% given pid of ho, get her name
|
|
p_ho_name(HoPid, #ps{hos = Hos}) ->
|
|
case lists:keyfind(HoPid, #pho.pid, Hos) of
|
|
#pho{name = Name} -> Name;
|
|
false -> "JaneDoe"
|
|
end.
|
|
|
|
% pimp spawns new ho
|
|
p_spawn_ho(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) ->
|
|
% new name for ho, initial ho state
|
|
{N, NewNames} = recycle(Names),
|
|
InitHS = #hs{name = N, daddy = self()},
|
|
% spawn the new ho
|
|
ok = p_logfln("spawning new ho: ~ts", [N]),
|
|
SpawnHo = fun() -> h_init(LSock, InitHS) end,
|
|
{HPid, HRef} = erlang:spawn_monitor(SpawnHo),
|
|
% new ho record from Pimp POV
|
|
NewHo = #pho{pid = HPid, ref = HRef, name = N},
|
|
ok = p_logfln("spawned new ho: ~tw", [NewHo]),
|
|
NewPS = PS#ps{hos = [NewHo | Hos], ho_names = NewNames},
|
|
NewPS.
|
|
|
|
% ho down, remove her from roster
|
|
p_ho_down({'DOWN', _Ref, process, Pid, Info}, PS = #ps{hos = Hos}) ->
|
|
ok = p_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]),
|
|
NewHos = lists:keydelete(Pid, #pho.pid, Hos),
|
|
NewPS = PS#ps{hos = NewHos},
|
|
NewPS.
|
|
|
|
|
|
% echo message from one ho to all the rest
|
|
% skip if equal
|
|
p_echo_except(SrcPid, SrcName, Data, [#pho{pid = SrcPid} | Rest]) ->
|
|
p_echo_except(SrcPid, SrcName, Data, Rest);
|
|
p_echo_except(SrcPid, SrcName, Data, [#pho{pid = DstPid} | Rest]) ->
|
|
DstPid ! {pimp, send, Data},
|
|
p_echo_except(SrcPid, SrcName, Data, Rest);
|
|
p_echo_except(_, _, _, []) ->
|
|
ok.
|
|
|
|
%% runs in ho context
|
|
h_init(LSock, HS = #hs{name = Name, daddy = Daddy, asock = none, nsock = none}) ->
|
|
h_logln(Name, "h_init"),
|
|
% this is key: accept needs to run in ho context, otherwise she
|
|
% doesn't own the acceptor socket
|
|
%
|
|
% blocks until client connects
|
|
case gen_tcp:accept(LSock) of
|
|
{ok, ASock} ->
|
|
h_logln(Name, "taken"),
|
|
% tell daddy to spawn the next ho
|
|
Daddy ! {ho, self(), taken},
|
|
{ok, NSock, _} = enoise:accept(ASock, noise_options()),
|
|
Welcome = io_lib:format("HI! I'm ~ts. What's your name?\r\n",
|
|
[Name]),
|
|
ok = h_send(Name, NSock, Welcome),
|
|
HS_II = HS#hs{asock = ASock, nsock = NSock},
|
|
h_loop(HS_II);
|
|
{error, closed} ->
|
|
h_logln(Name, "didn't get taken")
|
|
end.
|
|
|
|
|
|
h_loop(HS = #hs{name = Name, daddy = Daddy, asock = ASock, nsock = NSock}) ->
|
|
ok = inet:setopts(ASock, [{active, once}]),
|
|
receive
|
|
{noise, NSock, Data} ->
|
|
Data_II = string:chomp(Data),
|
|
ok = h_rcvd(Name, Data_II),
|
|
Daddy ! {ho, self(), sent, Data_II},
|
|
h_loop(HS);
|
|
{pimp, send, Data} ->
|
|
ok = h_send(Name, NSock, Data),
|
|
h_loop(HS);
|
|
{tcp_closed, ASock} ->
|
|
exit(its_fine);
|
|
Unknown ->
|
|
ok = h_logfln(Name, "unknown message: ~tw", [Unknown]),
|
|
h_loop(HS)
|
|
end.
|
|
|
|
|
|
% pimp log in bold
|
|
p_logln(LogStr) ->
|
|
Msg = io_lib:format("!! Daddy :: ~ts~n", [LogStr]),
|
|
PimpChars = ?ANSI_BOLD([Msg]),
|
|
io:put_chars(PimpChars).
|
|
|
|
p_logfln(LogStr, Args) ->
|
|
p_logln(io_lib:format(LogStr, Args)).
|
|
|
|
|
|
% ho log dimmed
|
|
h_logln(HoName, LogStr) ->
|
|
FMsg = io_lib:format("!! ~ts :: ~ts~n", [HoName, LogStr]),
|
|
HoChars = ?ANSI_DIM([FMsg]),
|
|
io:put_chars(HoChars).
|
|
|
|
h_logfln(HoName, MsgStr, Args) ->
|
|
h_logln(HoName, io_lib:format(MsgStr, Args)).
|
|
|
|
% normal: john messages
|
|
h_rcvd(Name, MsgStr) ->
|
|
Msg = io_lib:format("<< ~ts :: ~ts~n", [Name, MsgStr]),
|
|
io:put_chars(Msg).
|
|
|
|
h_send(Name, NSock, Msg) ->
|
|
ok = io:format(">> ~ts :: ~ts~n", [Name, Msg]),
|
|
enoise:send(NSock, Msg).
|
|
|
|
|
|
init_ho_names() ->
|
|
["Crystal", "Tiffany", "Amber", "Brandy", "Lola", "Angel",
|
|
"Ginger", "Candy", "Charity", "Anastasia", "Cherry", "Kitty",
|
|
"Jade", "Destiny", "Devon", "Chastity", "Raven", "Scarlett",
|
|
"Bambi", "Star", "Paris", "Dallas", "Diamond", "Skye",
|
|
"Trinity", "Tawny", "Layla", "Lexie", "Roxy", "Porsche",
|
|
"Nevaeh", "Ashlynn", "Aspen", "Chyna", "Lexus", "Unique",
|
|
"Chardonnay", "Houston", "London", "Coco", "Luscious",
|
|
"Delight", "Capri", "Trixie", "Cinnamon"].
|
|
|
|
|
|
recycle([N | Ns]) ->
|
|
{N, Ns ++ [N]}.
|