1
0
forked from QPQ-AG/enoise
This commit is contained in:
2026-06-11 23:31:39 +09:00
parent 853f6b5a8d
commit a5da9d08f5
2 changed files with 108 additions and 80 deletions
+16 -17
View File
@@ -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(),
+89 -60
View File
@@ -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
Active =
case Active0 of
true -> true;
once -> {once, false}
end,
State = #state{ rx = Rx, tx = Tx, owner = Owner,
tcp_sock = TcpSock, active = Active },
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]),