This took a while to debug!! Turns out I don't actually handle datagrams that have been received before the workers are all initialized... oops!!
139 lines
3.8 KiB
Erlang
139 lines
3.8 KiB
Erlang
%%% @doc
|
|
%%% Minimal Stream Protocol: Connection Worker
|
|
%%% @end
|
|
|
|
-module(msp_router).
|
|
-vsn("0.1.0").
|
|
-behavior(gen_server).
|
|
-author("Jarvis Carroll <spiveehere@gmail.com>").
|
|
-copyright("Jarvis Carroll <spiveehere@gmail.com>").
|
|
-license("MIT").
|
|
|
|
-export([begin_routing/4, add_channel/3]).
|
|
|
|
%% 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_router can be initialized.
|
|
begin_routing(Router, 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, Router) of
|
|
ok ->
|
|
gen_server:cast(Router, {begin_routing, OurSock, {TheirIP, TheirPort}, OurSide});
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end.
|
|
|
|
add_channel(Router, ChanID, ChanPID) ->
|
|
gen_server:cast(Router, {add_channel, ChanID, ChanPID}).
|
|
|
|
%%% gen_server
|
|
|
|
-spec start_link() -> Result
|
|
when Result :: {ok, pid()}
|
|
| {error, Reason :: term()}.
|
|
|
|
start_link() ->
|
|
gen_server:start_link(?MODULE, none, []).
|
|
|
|
|
|
% TODO: SWP? SWP of SWPs?
|
|
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_routing, Sock, Peer, Side}, none) ->
|
|
State = do_begin_routing(Sock, Peer, Side),
|
|
{noreply, State};
|
|
handle_cast({add_channel, ChanID, ChanPID}, State) ->
|
|
NewState = do_add_channel(ChanID, ChanPID, State),
|
|
{noreply, NewState};
|
|
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}}) ->
|
|
io:format("Got packet: ~p~n", [Packet]),
|
|
do_dispatch(State, Packet),
|
|
{noreply, State};
|
|
handle_info(Unexpected, State) ->
|
|
ok = io:format("Unexpected info: ~tp~n", [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_routing(Sock, Peer, Side) ->
|
|
ok = inet:setopts(Sock, [{active, once}, {mode, binary}]),
|
|
State = #s{socket = Sock,
|
|
peer = Peer,
|
|
side = Side},
|
|
State.
|
|
|
|
do_add_channel(ChanID, ChanPID, State = #s{connections = Conns}) ->
|
|
NewConns = maps:put(ChanID, ChanPID, Conns),
|
|
State#s{connections = NewConns}.
|
|
|
|
do_dispatch(#s{socket = Sock, peer = Peer, connections = Conns}, <<ID:8, Packet/bytes>>) ->
|
|
ok = inet:setopts(Sock, [{active, once}]),
|
|
case maps:find(ID, Conns) of
|
|
{ok, Conn} ->
|
|
erlang:send(Conn, {msp_fragment, Packet});
|
|
error ->
|
|
{ok, OurPort} = inet:port(Sock),
|
|
{TheirIP, TheirPort} = Peer,
|
|
msp_channel_man:dispatch_fallback(OurPort, TheirIP, TheirPort, ID, Packet)
|
|
end;
|
|
do_dispatch(_, <<>>) ->
|
|
% Empty datagram?
|
|
ok.
|
|
|