diff --git a/src/msp.erl b/src/msp.erl index c94eb4c..98978ed 100644 --- a/src/msp.erl +++ b/src/msp.erl @@ -9,7 +9,17 @@ start() -> application:start(hakuzaru). stop() -> application:stop(hakuzaru). start(normal, _Args) -> - msp_sup:start_link(). + {ok, Sup} = msp_sup:start_link(), + case init:get_plain_arguments() of + [_] -> + io:format("MSP started as idle process.~n"); + [_, "test"] -> + io:format("Running tests.~n", []), + msp_tests:spawn_tests(); + [_ | CLArgs] -> + io:format("Unknown args ~p~n", [CLArgs]) + end, + {ok, Sup}. stop(_State) -> ok. diff --git a/src/msp_connection.erl b/src/msp_connection.erl new file mode 100644 index 0000000..e69d79b --- /dev/null +++ b/src/msp_connection.erl @@ -0,0 +1,118 @@ +%%% @doc +%%% Minimal Stream Protocol: Connection Worker +%%% @end + +-module(msp_connection). +-vsn("0.1.0"). +-behavior(gen_server). +-author("Jarvis Carroll "). +-copyright("Jarvis Carroll "). +-license("MIT"). + +-export([begin_msp/5]). + +%% gen_server +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). +-include("$zx_include/zx_logger.hrl"). + + +%%% Type and Record Definitions + +-type endpoint() :: {inet:ip_address(), inet:port_number()}. + +% msp_channel server. +-type channel() :: pid(). + +-record(s, + {socket :: gen_udp:socket(), + peer :: endpoint(), + side :: 0 | 1, + connections = #{} :: #{integer() => channel()}}). + +-type state() :: none | #s{}. + +%%% Interface + +% msp does not negotiate new connections, but once you have a +% socket, and a host that knows you will be talking MSP, then a +% new msp_connection can be initialized. +begin_msp(Connection, OurSock, TheirIP, TheirPort, OurSide) -> + % Transfer the socket to the gen_server. If it is + % active then a bunch of messages will be received + % at once, but that is fine. + case gen_udp:controlling_process(OurSock, Connection) of + ok -> + gen_server:cast(Connection, {begin_msp, OurSock, {TheirIP, TheirPort}, OurSide}); + {error, Reason} -> + {error, Reason} + end. + +%%% gen_server + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason :: term()}. + +start_link() -> + gen_server:start_link(?MODULE, none, []). + + +% TODO: Ask msp_connection_man for the socket and endpoint +init(none) -> + {ok, none}. + + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp", [From, Unexpected]), + {reply, undef, State}. + + +handle_cast({begin_msp, Sock, Peer, Side}, none) -> + State = do_begin_msp(Sock, Peer, Side), + {noreply, State}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp", [Unexpected]), + {noreply, State}. + + +handle_info({udp, Sock, IP, Port, Packet}, State = #s{socket = Sock, peer = {IP, Port}}) -> + NewState = do_dispatch(State, Packet), + {noreply, NewState}; +handle_info(Unexpected, State) -> + ok = io:format(warning, "Unexpected info: ~tp", [Unexpected]), + {noreply, State}. + + +-spec code_change(OldVersion, State, Extra) -> {ok, NewState} | {error, Reason} + when OldVersion :: Version | {old, Version}, + Version :: term(), + State :: state(), + Extra :: term(), + NewState :: term(), + Reason :: term(). + +code_change(_, State, _) -> + {ok, State}. + + +terminate(_, _) -> + ok. + + + +%%% Doer Functions + +do_begin_msp(Sock, Peer, Side) -> + ok = inet:setopts(Sock, [{active, once}]), + State = #s{socket = Sock, + peer = Peer, + side = Side}, + State. + +do_dispatch(State = #s{socket = Sock}, Packet) -> + io:format("Got data: ~p~n", [Packet]), + ok = inet:setopts(Sock, [{active, once}]), + State. + diff --git a/src/msp_tests.erl b/src/msp_tests.erl new file mode 100644 index 0000000..6fdd7e1 --- /dev/null +++ b/src/msp_tests.erl @@ -0,0 +1,42 @@ +-module(msp_tests). +-export([spawn_tests/0, run_tests_and_halt/0, run_tests_protected/0, run_tests/0]). + +spawn_tests() -> + spawn(?MODULE, run_tests_and_halt, []), + ok. + +run_tests_and_halt() -> + Result = run_tests_protected(), + io:format("Tests returned ~p~n", [Result]), + halt(). + +run_tests_protected() -> + try + run_tests() + catch + _:Reason:Stack -> {error, Reason, Stack} + end. + +run_tests() -> + ok = send_test(), + ok. + +make_connection(OurPort, TheirIP, TheirPort, Side) -> + {ok, Sock} = gen_udp:open(OurPort), + {ok, Pid} = msp_connection:start_link(), + ok = msp_connection:begin_msp(Pid, Sock, TheirIP, TheirPort, Side), + {Pid, Sock}. + + +send_test() -> + IP = {127, 0, 0, 1}, + PortA = 5555, + PortB = 6666, + {A, SockA} = make_connection(PortA, IP, PortB, 0), + {B, SockB} = make_connection(PortB, IP, PortA, 1), + gen_udp:send(SockA, {IP, PortB}, <<"message sent from A to B">>), + gen_udp:send(SockB, {IP, PortA}, <<"message sent from B to A">>), + timer:sleep(10), + ok. + +