UDP receipt

This commit is contained in:
Jarvis Carroll 2025-10-24 12:06:29 +00:00
parent e9efcccfd0
commit 4d7dcf160c
3 changed files with 171 additions and 1 deletions

View File

@ -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.

118
src/msp_connection.erl Normal file
View File

@ -0,0 +1,118 @@
%%% @doc
%%% Minimal Stream Protocol: Connection Worker
%%% @end
-module(msp_connection).
-vsn("0.1.0").
-behavior(gen_server).
-author("Jarvis Carroll <spiveehere@gmail.com>").
-copyright("Jarvis Carroll <spiveehere@gmail.com>").
-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.

42
src/msp_tests.erl Normal file
View File

@ -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.