From a5da9d08f50035c38e45bb313ac2eb15ef3947cf Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 11 Jun 2026 23:31:39 +0900 Subject: [PATCH] WIP --- src/enoise.erl | 33 ++++---- src/enoise_connection.erl | 155 ++++++++++++++++++++++---------------- 2 files changed, 108 insertions(+), 80 deletions(-) diff --git a/src/enoise.erl b/src/enoise.erl index 7b5d4b3..a052dfd 100644 --- a/src/enoise.erl +++ b/src/enoise.erl @@ -7,7 +7,6 @@ %%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp') %%% and after "upgrading" a `gen_tcp'-socket into a `enoise'-socket it has a %%% similar API as `gen_tcp'. -%%% %%% @end -module(enoise). @@ -55,7 +54,7 @@ -type timeout() :: pos_integer() | infinity. -type recv_return() :: {ok, binary(), com_state_state()} | {error, term()}). --type recv_msg_fun() :: fun((com_state_state(), timeout()) -> recv_return(). +-type recv_msg_fun() :: fun((com_state_state(), timeout()) -> recv_return()). -type send_msg_fun() :: fun((com_state_state(), binary()) -> ok). @@ -92,21 +91,6 @@ handshake(Options, Role) -> create_hstate(Options, Role). --spec step_handshake(HState, Data) -> Next - when HState :: enoise_hs_state:state(), - Data :: {rcvd, binary()} - | {send, binary()}, - Next :: {ok, send, binary(), enoise_hs_state:state()} - | {ok, rcvd, binary(), enoise_hs_state:state()} - | {ok, done, split_state()} - | {error, term()}. -%% @doc -%% Do a step (one of `{send, Payload}', `{rcvd, EncryptedData}', or `done') - -step_handshake(HState, Data) -> - do_step_handshake(HState, Data). - - -spec handshake(Options, Role, ComState) -> Outcome when Options :: options(), Role :: enoise_hs_state:noise_role(), @@ -126,6 +110,21 @@ handshake(Options, Role, ComState) -> end. +-spec step_handshake(HState, Data) -> Next + when HState :: enoise_hs_state:state(), + Data :: {rcvd, binary()} + | {send, binary()}, + Next :: {ok, send, binary(), enoise_hs_state:state()} + | {ok, rcvd, binary(), enoise_hs_state:state()} + | {ok, done, split_state()} + | {error, term()}. +%% @doc +%% Do a step (one of `{send, Payload}', `{rcvd, EncryptedData}', or `done') + +step_handshake(HState, Data) -> + do_step_handshake(HState, Data). + + -spec connect(TcpSock, Options) -> Outcome when TcpSock :: gen_tcp:socket(), Options :: options(), diff --git a/src/enoise_connection.erl b/src/enoise_connection.erl index 53c12dd..8e144e1 100644 --- a/src/enoise_connection.erl +++ b/src/enoise_connection.erl @@ -1,121 +1,148 @@ -%%% ------------------------------------------------------------------ +%%% @copyright 2026, QPQ AG %%% @copyright 2018, Aeternity Anstalt %%% -%%% @doc Module implementing a gen_server for holding a handshaked -%%% Noise connection over gen_tcp. +%%% @doc +%%% A gen_server for holding a Noise connection over gen_tcp. %%% %%% Some care is needed since the underlying transmission is broken up %%% into Noise packets, so we need some buffering. -%%% %%% @end -%%% ------------------------------------------------------------------ -module(enoise_connection). --export([ controlling_process/2 - , close/1 - , send/2 - , set_active/2 - , start_link/5 - ]). +-export([controlling_process/2, + close/1, + send/2, + set_active/2, + start_link/5]). -%% gen_server callbacks +%% gen_server -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(enoise, { pid }). +-record(enoise, {pid}). + +-record(s, + {rx = , + tx = , + owner = none :: none | pid(), + owner_ref = none :: none | reference(), + tcp_sock = none :: none | gen_tcp:socket(), + active = once :: true | {once, boolean()}, + msgbuf = [] :: list(), + rawbuf = <<>> :: binary()}). --record(state, {rx, tx, owner, owner_ref, tcp_sock, active, msgbuf = [], rawbuf = <<>>}). -%% -- API -------------------------------------------------------------------- start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) -> - Active = case Active0 of - true -> true; - once -> {once, false} - end, - State = #state{ rx = Rx, tx = Tx, owner = Owner, - tcp_sock = TcpSock, active = Active }, - + Active = + case Active0 of + true -> true; + once -> {once, false} + end, + State = + #s{rx = Rx, + tx = Tx, + owner = Owner, + tcp_sock = TcpSock, + active = Active}, case gen_server:start_link(?MODULE, [State], []) of {ok, Pid} -> case gen_tcp:controlling_process(TcpSock, Pid) of ok -> - %% Changing controlling process require a bit of - %% fiddling with already received and delivered content... - [ Pid ! {tcp, TcpSock, Buf} || Buf /= <<>> ], + % Changing controlling process require a bit of + % fiddling with already received and delivered content... + ok = + case Buf =/= <<>> of + true -> Pid ! {tcp, TcpSock, Buf}; + false -> ok + end, flush_tcp(Pid, TcpSock), {ok, Pid}; - Err = {error, _} -> + Error -> close(Pid), - Err + Error end; - Err = {error, _} -> - Err + Error + Error end. + -spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}. + send(Noise, Data) -> gen_server:call(Noise, {send, Data}). + -spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}. + set_active(Noise, Active) -> gen_server:call(Noise, {active, self(), Active}). + -spec close(Noise :: pid()) -> ok | {error, term()}. + close(Noise) -> gen_server:call(Noise, close). + -spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}. + controlling_process(Noise, NewPid) -> gen_server:call(Noise, {controlling_process, self(), NewPid}, 100). -%% -- gen_server callbacks --------------------------------------------------- -init([#state{owner = Owner} = State]) -> + +%% gen_server + +init([#s{owner = Owner} = State]) -> OwnerRef = erlang:monitor(process, Owner), - {ok, State#state{owner_ref = OwnerRef}}. + {ok, State#s{owner_ref = OwnerRef}}. -handle_call(close, _From, S) -> - {stop, normal, ok, S}; -handle_call(_Call, _From, S = #state{ tcp_sock = closed }) -> - {reply, {error, closed}, S}; -handle_call({send, Data}, _From, S) -> - {Res, S1} = handle_send(S, Data), - {reply, Res, S1}; -handle_call({controlling_process, OldPid, NewPid}, _From, S) -> - {Res, S1} = handle_control_change(S, OldPid, NewPid), - {reply, Res, S1}; -handle_call({active, Pid, NewActive}, _From, S) -> - {Res, S1} = handle_active(S, Pid, NewActive), - {reply, Res, S1}. -handle_cast(_Msg, S) -> - {noreply, S}. +handle_call(close, _, State) -> + {stop, normal, ok, State}; +handle_call(_Call, _, State = #s{tcp_sock = closed}) -> + {reply, {error, closed}, State}; +handle_call({send, Data}, _, State) -> + {Result, NewState} = handle_send(State, Data), + {reply, Result, NewState}; +handle_call({controlling_process, OldPID, NewPID}, _, State) -> + {Result, NewState} = handle_control_change(State, OldPID, NewPID), + {reply, Result, NewState}; +handle_call({active, PID, NewActive}, _, State) -> + {Result, NewState} = handle_active(State, PID, NewActive), + {reply, Result, NewState}. -handle_info({tcp, TS, Data}, S = #state{ tcp_sock = TS, owner = O }) -> + +handle_cast(_, State) -> + {noreply, State}. + + +handle_info({tcp, TS, Data}, State = #s{tcp_sock = TS, owner = O}) -> try - {S1, Msgs} = handle_data(S, Data), - S2 = handle_msgs(S1#state{ msgbuf = S1#state.msgbuf ++ Msgs }), - set_active(S2), - {noreply, S2} + {NextState = #s{msgbuf = Buf}, Msgs} = handle_data(State, Data), + NewState = handle_msgs(NextState#s{msgbuf = Buf ++ Msgs}), + set_active(NewState), + {noreply, NewState} catch error:{enoise_error, _} -> %% We are not likely to recover, but leave the decision to upstream O ! {enoise_error, TS, decrypt_error}, - {noreply, S} + {noreply, State} end; -handle_info({tcp_closed, TS}, S = #state{ tcp_sock = TS, owner = O }) -> +handle_info({tcp_closed, TS}, State = #s{tcp_sock = TS, owner = O}) -> O ! {tcp_closed, TS}, - {noreply, S#state{ tcp_sock = closed }}; + {noreply, State#s{tcp_sock = closed}}; handle_info({'DOWN', OwnerRef, process, _, normal}, - S = #state { tcp_sock = TS, owner_ref = OwnerRef }) -> + State = #s{tcp_sock = TS, owner_ref = OwnerRef}) -> close_tcp(TS), - {stop, normal, S#state{ tcp_sock = closed, owner_ref = undefined }}; -handle_info({'DOWN', _, _, _, _}, S) -> + {stop, normal, State#s{tcp_sock = closed, owner_ref = undefined}}; +handle_info({'DOWN', _, _, _, _}, State) -> %% Ignore non-normal monitor messages - we are linked. - {noreply, S}; -handle_info(_Msg, S) -> - {noreply, S}. + {noreply, State}; +handle_info(_Msg, State) -> + {noreply, State}. -terminate(_Reason, #state{ tcp_sock = TcpSock, owner_ref = ORef }) -> + +terminate(_, #s{tcp_sock = TcpSock, owner_ref = ORef}) -> [ gen_tcp:close(TcpSock) || TcpSock /= closed ], [ erlang:demonitor(ORef, [flush]) || ORef /= undefined ], ok. @@ -124,7 +151,9 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. -%% -- Local functions -------------------------------------------------------- + +%%% Handlers + handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) -> NewRef = erlang:monitor(process, NewPid), erlang:demonitor(OldRef, [flush]),