wip noisy echo
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
%% @doc
|
||||
%% super simple telnet echo server daemon
|
||||
%%
|
||||
%% no noise yet
|
||||
%%
|
||||
%% pimps and hos model (from trash talk template):
|
||||
%%
|
||||
%% ```
|
||||
%% t_sup
|
||||
%% |
|
||||
%% t_clients
|
||||
%% |
|
||||
%% +--- t_client_man pimp
|
||||
%% |
|
||||
%% +--- t_client_sup bottom bitch
|
||||
%% |
|
||||
%% +--- <0.1.0> (t_client) ho
|
||||
%% +--- <0.2.0> (t_client) ho
|
||||
%% +--- <0.3.0> (t_client) ho
|
||||
%% '''
|
||||
%%
|
||||
%% each ho:
|
||||
%%
|
||||
%% <ul>
|
||||
%% <li>Takes listen socket as arg</li>
|
||||
%% <li>spawns next ho</li>
|
||||
%% </ul>
|
||||
%%
|
||||
%% ```
|
||||
%% start(ListenSocket) ->
|
||||
%% t_client_sup:start_acceptor(ListenSocket).
|
||||
%%
|
||||
%% start_link(ListenSocket) ->
|
||||
%% proc_lib:start_link(?MODULE, init, [self(), ListenSocket]).
|
||||
%%
|
||||
%% init(Parent, ListenSocket) ->
|
||||
%% ok = io:format("~p Listening.~n", [self()]),
|
||||
%% Debug = sys:debug_options([]),
|
||||
%% ok = proc_lib:init_ack(Parent, {ok, self()}),
|
||||
%% listen(Parent, Debug, ListenSocket).
|
||||
%%
|
||||
%% listen(Parent, Debug, ListenSocket) ->
|
||||
%% case gen_tcp:accept(ListenSocket) of
|
||||
%% {ok, Socket} ->
|
||||
%% {ok, _} = start(ListenSocket),
|
||||
%% {ok, Peer} = inet:peername(Socket),
|
||||
%% ok = io:format("~p Connection accepted from: ~p~n", [self(), Peer]),
|
||||
%% ok = t_client_man:enroll(),
|
||||
%% State = #s{socket = Socket},
|
||||
%% loop(Parent, Debug, State);
|
||||
%% {error, closed} ->
|
||||
%% ok = io:format("~p Retiring: Listen socket closed.~n", [self()]),
|
||||
%% exit(normal)
|
||||
%% end.
|
||||
%% '''
|
||||
%%
|
||||
%% @end
|
||||
-module(echod).
|
||||
|
||||
-include("./ansi.hrl").
|
||||
-export([main/0]).
|
||||
|
||||
|
||||
% 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(),
|
||||
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, 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 enoise:accept(LSock, noise_options()) of
|
||||
{ok, NSock, _} ->
|
||||
h_logln(Name, "taken"),
|
||||
% tell daddy to spawn the next ho
|
||||
Daddy ! {ho, self(), taken},
|
||||
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{nsock = ASock},
|
||||
h_loop(HS_II);
|
||||
{error, closed} ->
|
||||
h_logln(Name, "didn't get taken")
|
||||
end.
|
||||
|
||||
|
||||
h_loop(HS = #hs{name = Name, daddy = Daddy, nsock = ASock}) ->
|
||||
ok = inet:setopts(ASock, [{active, once}]),
|
||||
receive
|
||||
{tcp, ASock, 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, ASock, 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, Socket, Msg) ->
|
||||
ok = io:format(">> ~ts :: ~ts~n", [Name, Msg]),
|
||||
enoise:send(Socket, 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]}.
|
||||
Reference in New Issue
Block a user