From 727c0aedd6d40cf6e86736294017bd61935cb12e Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Tue, 25 Feb 2025 22:01:38 +0000 Subject: [PATCH] Implement demo of DER comms --- client/src/main_window.vala | 9 ++++- server/.gitignore | 1 + server/asn1/StudySystemProtocol.asn1 | 11 ++++++ server/rebar.config | 4 ++ server/src/proto_sup.erl | 10 ++++- server/src/session_server.erl | 42 ++++++++++++++++++++ server/src/session_sup.erl | 25 ++++++++++++ server/src/tcp_server.erl | 58 ++++++++++------------------ 8 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 server/asn1/StudySystemProtocol.asn1 create mode 100644 server/src/session_server.erl create mode 100644 server/src/session_sup.erl diff --git a/client/src/main_window.vala b/client/src/main_window.vala index 02064db..0a63bd5 100644 --- a/client/src/main_window.vala +++ b/client/src/main_window.vala @@ -14,7 +14,9 @@ namespace StudySystemClient { this.connection = connection; connection.received.connect((msg) => { - response_label.label = "Response: " + (string)msg; + var der = Der.decode(msg) as Der.Choice; + var str = der.value as Der.Utf8String; + response_label.label = "Response: " + str.value; }); var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 10); @@ -24,7 +26,10 @@ namespace StudySystemClient { box.margin_bottom = 10; send_button = new Gtk.Button.with_label("Send"); - send_button.clicked.connect(() => connection.send("Foo".data)); + send_button.clicked.connect(() => { + var msg = new Der.Choice(0, new Der.Null()); + connection.send(msg.encode()); + }); box.append(send_button); response_label = new Gtk.Label(""); diff --git a/server/.gitignore b/server/.gitignore index ed7d163..5b03e2e 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,2 +1,3 @@ _build/* *.beam +src/StudySystemProtocol.erl diff --git a/server/asn1/StudySystemProtocol.asn1 b/server/asn1/StudySystemProtocol.asn1 new file mode 100644 index 0000000..af43d69 --- /dev/null +++ b/server/asn1/StudySystemProtocol.asn1 @@ -0,0 +1,11 @@ +StudySystemProtocol DEFINITIONS EXPLICIT TAGS ::= BEGIN + +Request ::= CHOICE { + foo [0] NULL +} + +Response ::= CHOICE { + msg [0] UTF8String +} + +END diff --git a/server/rebar.config b/server/rebar.config index 2656fd5..19f581b 100644 --- a/server/rebar.config +++ b/server/rebar.config @@ -1,2 +1,6 @@ {erl_opts, [debug_info]}. {deps, []}. +{plugins, [{provider_asn1, "0.4.1"}]}. +{provider_hooks, [{pre, [{compile, {asn, compile}}]}, + {post, [{clean, {asn, clean}}]}]}. +{asn1_args, [{compile_opts, [der]}]}. diff --git a/server/src/proto_sup.erl b/server/src/proto_sup.erl index 7149757..9ba2e6f 100644 --- a/server/src/proto_sup.erl +++ b/server/src/proto_sup.erl @@ -11,10 +11,16 @@ start_link(Port, CertDir) -> supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, CertDir]). init([Port, CertDir]) -> - SupFlags = #{stragegy => one_for_all, + SupFlags = #{stragegy => one_for_one, intensity => 1, period => 5}, - ChildSpecs = [#{id => tcp_server, + ChildSpecs = [#{id => session_sup, + start => {session_sup, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [session_sup]}, + #{id => tcp_server, start => {tcp_server, start_link, [Port, CertDir]}, restart => permanent, shutdown => 5000, diff --git a/server/src/session_server.erl b/server/src/session_server.erl new file mode 100644 index 0000000..1db542c --- /dev/null +++ b/server/src/session_server.erl @@ -0,0 +1,42 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(session_server). +-behaviour(gen_server). + +-export([start_link/1]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +start_link(Socket) -> + gen_server:start_link(?MODULE, Socket, []). + +init(Socket) -> + ssl:setopts(Socket, [{active, true}]), + {ok, #{socket => Socket}}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({ssl, Socket, Data}, State) -> + case 'StudySystemProtocol':decode('Request', Data) of + {ok, {foo, _}} -> + {ok, Encoded} + = 'StudySystemProtocol':encode('Response', {msg, "Foo"}), + ssl:send(Socket, Encoded); + Result -> + io:format("Invalid message: ~p~n", [Result]), + ok + end, + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #{socket := Socket}) -> + ssl:close(Socket). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/server/src/session_sup.erl b/server/src/session_sup.erl new file mode 100644 index 0000000..a2c5b67 --- /dev/null +++ b/server/src/session_sup.erl @@ -0,0 +1,25 @@ +% Copyright (c) Camden Dixie O'Brien +% SPDX-License-Identifier: AGPL-3.0-only + +-module(session_sup). +-behaviour(supervisor). + +-export([start_link/0, init/1, start_session/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 10, + period => 1}, + ChildSpec = #{id => session_server, + start => {session_server, start_link, []}, + restart => temporary, + shutdown => 5000, + type => worker, + modules => [session_server]}, + {ok, {SupFlags, [ChildSpec]}}. + +start_session(Socket) -> + supervisor:start_child(?MODULE, [Socket]). diff --git a/server/src/tcp_server.erl b/server/src/tcp_server.erl index 80133d8..f77f8d7 100644 --- a/server/src/tcp_server.erl +++ b/server/src/tcp_server.erl @@ -20,8 +20,8 @@ init([Port, CertDir]) -> {verify, verify_peer}, {fail_if_no_peer_cert, true}], {ok, Socket} = ssl:listen(Port, TcpOpts ++ SslOpts), - Pid = spawn_link(fun() -> acceptor_loop(Socket) end), - {ok, #state{socket = Socket, acceptor = Pid}}. + self() ! accept, + {ok, #state{socket = Socket}}. handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -29,46 +29,30 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, State) -> {noreply, State}. -handle_info({'EXIT', Pid, Reason}, State = #state{acceptor = Pid}) -> - {stop, {acceptor_died, Reason}, State}; +handle_info(accept, State = #state{socket = Socket}) -> + case ssl:transport_accept(Socket) of + {ok, TlsSocket} -> + self() ! {handshake, TlsSocket}, + self() ! accept; + {error, closed} -> + ok + end, + {noreply, State}; +handle_info({handshake, TlsSocket}, State) -> + case ssl:handshake(TlsSocket) of + {ok, ClientSocket} -> + {ok, Pid} = session_sup:start_session(ClientSocket), + ok = ssl:controlling_process(ClientSocket, Pid); + {error, _Reason} -> + ok + end, + {noreply, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, #state{socket = Socket}) -> - gen_tcp:close(Socket), + ssl:close(Socket), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. - -acceptor_loop(Socket) -> - case ssl:transport_accept(Socket) of - {ok, TlsSocket} -> - case ssl:handshake(TlsSocket) of - {ok, ClientSocket} -> - Pid = spawn( - fun() -> handle_connection(ClientSocket) end), - ok = ssl:controlling_process(ClientSocket, Pid), - acceptor_loop(Socket); - {error, _Reason} -> - acceptor_loop(Socket) - end; - {error, closed} -> - ok - end. - -handle_connection(Socket) -> - ssl:setopts(Socket, [{active, true}]), - handle_connection_loop(Socket). - -handle_connection_loop(Socket) -> - receive - {ssl, Socket, Data} -> - handle_client_msg(Socket, Data), - handle_connection_loop(Socket); - {ssl_closed, Socket} -> - ok - end. - -handle_client_msg(Socket, Msg) -> - ssl:send(Socket, Msg).