forked from QPQ-AG/enoise
WIP
This commit is contained in:
+16
-17
@@ -7,7 +7,6 @@
|
|||||||
%%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp')
|
%%% 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
|
%%% and after "upgrading" a `gen_tcp'-socket into a `enoise'-socket it has a
|
||||||
%%% similar API as `gen_tcp'.
|
%%% similar API as `gen_tcp'.
|
||||||
%%%
|
|
||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(enoise).
|
-module(enoise).
|
||||||
@@ -55,7 +54,7 @@
|
|||||||
-type timeout() :: pos_integer() | infinity.
|
-type timeout() :: pos_integer() | infinity.
|
||||||
-type recv_return() :: {ok, binary(), com_state_state()}
|
-type recv_return() :: {ok, binary(), com_state_state()}
|
||||||
| {error, term()}).
|
| {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).
|
-type send_msg_fun() :: fun((com_state_state(), binary()) -> ok).
|
||||||
|
|
||||||
@@ -92,21 +91,6 @@ handshake(Options, Role) ->
|
|||||||
create_hstate(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
|
-spec handshake(Options, Role, ComState) -> Outcome
|
||||||
when Options :: options(),
|
when Options :: options(),
|
||||||
Role :: enoise_hs_state:noise_role(),
|
Role :: enoise_hs_state:noise_role(),
|
||||||
@@ -126,6 +110,21 @@ handshake(Options, Role, ComState) ->
|
|||||||
end.
|
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
|
-spec connect(TcpSock, Options) -> Outcome
|
||||||
when TcpSock :: gen_tcp:socket(),
|
when TcpSock :: gen_tcp:socket(),
|
||||||
Options :: options(),
|
Options :: options(),
|
||||||
|
|||||||
+89
-60
@@ -1,121 +1,148 @@
|
|||||||
%%% ------------------------------------------------------------------
|
%%% @copyright 2026, QPQ AG
|
||||||
%%% @copyright 2018, Aeternity Anstalt
|
%%% @copyright 2018, Aeternity Anstalt
|
||||||
%%%
|
%%%
|
||||||
%%% @doc Module implementing a gen_server for holding a handshaked
|
%%% @doc
|
||||||
%%% Noise connection over gen_tcp.
|
%%% A gen_server for holding a Noise connection over gen_tcp.
|
||||||
%%%
|
%%%
|
||||||
%%% Some care is needed since the underlying transmission is broken up
|
%%% Some care is needed since the underlying transmission is broken up
|
||||||
%%% into Noise packets, so we need some buffering.
|
%%% into Noise packets, so we need some buffering.
|
||||||
%%%
|
|
||||||
%%% @end
|
%%% @end
|
||||||
%%% ------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(enoise_connection).
|
-module(enoise_connection).
|
||||||
|
|
||||||
-export([ controlling_process/2
|
-export([controlling_process/2,
|
||||||
, close/1
|
close/1,
|
||||||
, send/2
|
send/2,
|
||||||
, set_active/2
|
set_active/2,
|
||||||
, start_link/5
|
start_link/5]).
|
||||||
]).
|
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
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}) ->
|
start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) ->
|
||||||
Active = case Active0 of
|
Active =
|
||||||
|
case Active0 of
|
||||||
true -> true;
|
true -> true;
|
||||||
once -> {once, false}
|
once -> {once, false}
|
||||||
end,
|
end,
|
||||||
State = #state{ rx = Rx, tx = Tx, owner = Owner,
|
State =
|
||||||
tcp_sock = TcpSock, active = Active },
|
#s{rx = Rx,
|
||||||
|
tx = Tx,
|
||||||
|
owner = Owner,
|
||||||
|
tcp_sock = TcpSock,
|
||||||
|
active = Active},
|
||||||
case gen_server:start_link(?MODULE, [State], []) of
|
case gen_server:start_link(?MODULE, [State], []) of
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
case gen_tcp:controlling_process(TcpSock, Pid) of
|
case gen_tcp:controlling_process(TcpSock, Pid) of
|
||||||
ok ->
|
ok ->
|
||||||
%% Changing controlling process require a bit of
|
% Changing controlling process require a bit of
|
||||||
%% fiddling with already received and delivered content...
|
% fiddling with already received and delivered content...
|
||||||
[ Pid ! {tcp, TcpSock, Buf} || Buf /= <<>> ],
|
ok =
|
||||||
|
case Buf =/= <<>> of
|
||||||
|
true -> Pid ! {tcp, TcpSock, Buf};
|
||||||
|
false -> ok
|
||||||
|
end,
|
||||||
flush_tcp(Pid, TcpSock),
|
flush_tcp(Pid, TcpSock),
|
||||||
{ok, Pid};
|
{ok, Pid};
|
||||||
Err = {error, _} ->
|
Error ->
|
||||||
close(Pid),
|
close(Pid),
|
||||||
Err
|
Error
|
||||||
end;
|
end;
|
||||||
Err = {error, _} ->
|
Error
|
||||||
Err
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}.
|
-spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}.
|
||||||
|
|
||||||
send(Noise, Data) ->
|
send(Noise, Data) ->
|
||||||
gen_server:call(Noise, {send, Data}).
|
gen_server:call(Noise, {send, Data}).
|
||||||
|
|
||||||
|
|
||||||
-spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}.
|
-spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}.
|
||||||
|
|
||||||
set_active(Noise, Active) ->
|
set_active(Noise, Active) ->
|
||||||
gen_server:call(Noise, {active, self(), Active}).
|
gen_server:call(Noise, {active, self(), Active}).
|
||||||
|
|
||||||
|
|
||||||
-spec close(Noise :: pid()) -> ok | {error, term()}.
|
-spec close(Noise :: pid()) -> ok | {error, term()}.
|
||||||
|
|
||||||
close(Noise) ->
|
close(Noise) ->
|
||||||
gen_server:call(Noise, close).
|
gen_server:call(Noise, close).
|
||||||
|
|
||||||
|
|
||||||
-spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}.
|
-spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}.
|
||||||
|
|
||||||
controlling_process(Noise, NewPid) ->
|
controlling_process(Noise, NewPid) ->
|
||||||
gen_server:call(Noise, {controlling_process, self(), NewPid}, 100).
|
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),
|
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) ->
|
handle_call(close, _, State) ->
|
||||||
{noreply, S}.
|
{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
|
try
|
||||||
{S1, Msgs} = handle_data(S, Data),
|
{NextState = #s{msgbuf = Buf}, Msgs} = handle_data(State, Data),
|
||||||
S2 = handle_msgs(S1#state{ msgbuf = S1#state.msgbuf ++ Msgs }),
|
NewState = handle_msgs(NextState#s{msgbuf = Buf ++ Msgs}),
|
||||||
set_active(S2),
|
set_active(NewState),
|
||||||
{noreply, S2}
|
{noreply, NewState}
|
||||||
catch error:{enoise_error, _} ->
|
catch error:{enoise_error, _} ->
|
||||||
%% We are not likely to recover, but leave the decision to upstream
|
%% We are not likely to recover, but leave the decision to upstream
|
||||||
O ! {enoise_error, TS, decrypt_error},
|
O ! {enoise_error, TS, decrypt_error},
|
||||||
{noreply, S}
|
{noreply, State}
|
||||||
end;
|
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},
|
O ! {tcp_closed, TS},
|
||||||
{noreply, S#state{ tcp_sock = closed }};
|
{noreply, State#s{tcp_sock = closed}};
|
||||||
handle_info({'DOWN', OwnerRef, process, _, normal},
|
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),
|
close_tcp(TS),
|
||||||
{stop, normal, S#state{ tcp_sock = closed, owner_ref = undefined }};
|
{stop, normal, State#s{tcp_sock = closed, owner_ref = undefined}};
|
||||||
handle_info({'DOWN', _, _, _, _}, S) ->
|
handle_info({'DOWN', _, _, _, _}, State) ->
|
||||||
%% Ignore non-normal monitor messages - we are linked.
|
%% Ignore non-normal monitor messages - we are linked.
|
||||||
{noreply, S};
|
{noreply, State};
|
||||||
handle_info(_Msg, S) ->
|
handle_info(_Msg, State) ->
|
||||||
{noreply, S}.
|
{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 ],
|
[ gen_tcp:close(TcpSock) || TcpSock /= closed ],
|
||||||
[ erlang:demonitor(ORef, [flush]) || ORef /= undefined ],
|
[ erlang:demonitor(ORef, [flush]) || ORef /= undefined ],
|
||||||
ok.
|
ok.
|
||||||
@@ -124,7 +151,9 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
|
||||||
%% -- Local functions --------------------------------------------------------
|
|
||||||
|
%%% Handlers
|
||||||
|
|
||||||
handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) ->
|
handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) ->
|
||||||
NewRef = erlang:monitor(process, NewPid),
|
NewRef = erlang:monitor(process, NewPid),
|
||||||
erlang:demonitor(OldRef, [flush]),
|
erlang:demonitor(OldRef, [flush]),
|
||||||
|
|||||||
Reference in New Issue
Block a user