From e23694e6682d1e5885754af2bfc53aa70793736b Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 23 Jun 2026 10:22:48 +0900 Subject: [PATCH] WIP --- src/znoise.erl | 2 +- src/znoise_cipher.erl | 123 +++++++++++++ src/znoise_cipher_state.erl | 88 ---------- src/znoise_crypto.erl | 6 +- src/znoise_hs_state.erl | 6 +- src/znoise_keypair.erl | 26 +-- src/znoise_protocol.erl | 29 ++-- src/znoise_sym_state.erl | 20 +-- src/znoise_tcp.erl | 336 +++++++++++++++--------------------- 9 files changed, 309 insertions(+), 327 deletions(-) create mode 100644 src/znoise_cipher.erl delete mode 100644 src/znoise_cipher_state.erl diff --git a/src/znoise.erl b/src/znoise.erl index 8f65fb4..7965b24 100644 --- a/src/znoise.erl +++ b/src/znoise.erl @@ -18,7 +18,7 @@ -copyright("QPQ AG "). -license("ISC"). -%% Main function with generic Noise handshake +%% Generic Noise handshake -export([handshake/2, handshake/3, step_handshake/2]). %% API exports - Mainly mimicing gen_tcp diff --git a/src/znoise_cipher.erl b/src/znoise_cipher.erl new file mode 100644 index 0000000..6930487 --- /dev/null +++ b/src/znoise_cipher.erl @@ -0,0 +1,123 @@ +%%% @copyright 2026, QPQ AG +%%% @copyright 2018, Aeternity Anstalt +%%% +%%% @doc +%%% Module encapsulating a Noise Cipher state +%%% @end + +-module(znoise_cipher). +-vsn("0.1.0"). +-author("Craig Everett "). +-author("Hans Svensson "). +-copyright("QPQ AG "). +-license("ISC"). + +-export([cipher/1, + decrypt_with_ad/3, + encrypt_with_ad/3, + has_key/1, + init/2, + key/1, + rekey/1, + set_key/2, + set_nonce/2]). + +-include("znoise.hrl"). + +-type cipher() :: 'ChaChaPoly' | 'AESGCM'. +-type nonce() :: non_neg_integer(). +-type key() :: empty | binary(). + +% cs: "cipher state" +-record(cs, + {k = empty :: key(), + n = 0 :: nonce(), + cipher = 'ChaChaPoly' :: cipher()}). + +-opaque state() :: #cs{}. + +-export_type([cipher/0, state/0]). + + +-spec init(key(), cipher()) -> state(). + +init(Key, Cipher) -> + #cs{k = Key, n = 0, cipher = Cipher}. + + +-spec set_key(CState, NewKey) -> NewCState + when CState :: state(), + NewKey :: key(), + NewCState :: state(). + +set_key(CState, NewKey) -> + CState#cs{k = NewKey, n = 0}. + + +-spec has_key(state()) -> boolean(). + +has_key(#cs{k = Key}) -> + Key =/= empty. + + +-spec set_nonce(CState, NewNonce) -> NewCState + when CState :: state(), + NewNonce :: nonce(), + NewCState :: state(). + +set_nonce(CState, Nonce) -> + CState#cs{n = Nonce}. + + +-spec encrypt_with_ad(CState, AD, PlainText) -> Outcome + when CState :: state(), + AD :: binary(), + PlainText :: binary(), + Outcome :: {ok, NewCState, CipherText} | {error, Reason}, + NewCState :: state(), + CipherText :: binary(), + Reason :: term(). + +encrypt_with_ad(CState = #cs{k = empty}, _AD, PlainText) -> + {ok, CState, PlainText}; +encrypt_with_ad(CState = #cs{k = K, n = N, cipher = Cipher}, AD, PlainText) -> + CipherText = znoise_crypto:encrypt(Cipher, K, N, AD, PlainText), + {ok, CState#cs{n = N + 1}, CipherText}. + + +-spec decrypt_with_ad(CState, AD, CipherText) -> Outcome + when CState :: state(), + AD :: binary(), + CipherText :: binary(), + Outcome :: {ok, NewCState, PlainText} | {error, Reason}, + NewCState :: state(), + PlainText :: binary(), + Reason :: term(). + +decrypt_with_ad(CState = #cs{k = empty}, _AD, CipherText) -> + {ok, CState, CipherText}; +decrypt_with_ad(CState = #cs{k = K, n = N, cipher = Cipher}, AD, CipherText) -> + case znoise_crypto:decrypt(Cipher, K, N, AD, CipherText) of + PlainText when is_binary(PlainText) -> + {ok, CState#cs{n = N + 1}, PlainText}; + Error -> + Error + end. + +-spec rekey(CState :: state()) -> state(). + +rekey(CState = #cs{k = empty}) -> + CState; +rekey(CState = #cs{k = K, cipher = Cipher}) -> + CState#cs{k = znoise_crypto:rekey(Cipher, K)}. + + +-spec cipher(CState :: state()) -> cipher(). + +cipher(#cs{cipher = Cipher}) -> + Cipher. + +-spec key(CState :: state()) -> key(). + +key(#cs{k = K}) -> + K. diff --git a/src/znoise_cipher_state.erl b/src/znoise_cipher_state.erl deleted file mode 100644 index 3f43563..0000000 --- a/src/znoise_cipher_state.erl +++ /dev/null @@ -1,88 +0,0 @@ -%%% @copyright 2026, QPQ AG -%%% @copyright 2018, Aeternity Anstalt -%%% -%%% @doc -%%% Module encapsulating a Noise Cipher state -%%% @end - --module(znoise_cipher_state). --vsn("0.1.0"). --author("Craig Everett "). --author("Hans Svensson "). --copyright("QPQ AG "). --license("ISC"). - --export([ cipher/1 - , decrypt_with_ad/3 - , encrypt_with_ad/3 - , has_key/1 - , init/2 - , key/1 - , rekey/1 - , set_key/2 - , set_nonce/2 - ]). - --include("znoise.hrl"). - --type noise_cipher() :: 'ChaChaPoly' | 'AESGCM'. --type nonce() :: non_neg_integer(). --type key() :: empty | binary(). - --record(noise_cs, { k = empty :: key() - , n = 0 :: nonce() - , cipher = 'ChaChaPoly' :: noise_cipher() }). - --opaque state() :: #noise_cs{}. - --export_type([noise_cipher/0, state/0]). - --spec init(Key :: key(), Cipher :: noise_cipher()) -> state(). -init(Key, Cipher) -> - #noise_cs{ k = Key, n = 0, cipher = Cipher }. - --spec set_key(CState :: state(), NewKey :: key()) -> state(). -set_key(CState, NewKey) -> - CState#noise_cs{ k = NewKey, n = 0 }. - --spec has_key(CState :: state()) -> boolean(). -has_key(#noise_cs{ k = Key }) -> - Key =/= empty. - --spec set_nonce(CState :: state(), NewNonce :: nonce()) -> state(). -set_nonce(CState = #noise_cs{}, Nonce) -> - CState#noise_cs{ n = Nonce }. - --spec encrypt_with_ad(CState :: state(), AD :: binary(), PlainText :: binary()) -> - {ok, state(), binary()} | {error, term()}. -encrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, PlainText) -> - {ok, CState, PlainText}; -encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainText) -> - CipherText = znoise_crypto:encrypt(Cipher, K, N, AD, PlainText), - {ok, CState#noise_cs{ n = N+1 }, CipherText}. - --spec decrypt_with_ad(CState :: state(), AD :: binary(), CipherText :: binary()) -> - {ok, state(), binary()} | {error, term()}. -decrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, CipherText) -> - {ok, CState, CipherText}; -decrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, CipherText) -> - case znoise_crypto:decrypt(Cipher, K, N, AD, CipherText) of - PlainText when is_binary(PlainText) -> - {ok, CState#noise_cs{ n = N+1 }, PlainText}; - Err = {error, _} -> - Err - end. - --spec rekey(CState :: state()) -> state(). -rekey(CState = #noise_cs{ k = empty }) -> - CState; -rekey(CState = #noise_cs{ k = K, cipher = Cipher }) -> - CState#noise_cs{ k = znoise_crypto:rekey(Cipher, K) }. - --spec cipher(CState :: state()) -> noise_cipher(). -cipher(#noise_cs{ cipher = Cipher }) -> - Cipher. - --spec key(CState :: state()) -> key(). -key(#noise_cs{ k = K }) -> - K. diff --git a/src/znoise_crypto.erl b/src/znoise_crypto.erl index 921f3f0..b81958a 100644 --- a/src/znoise_crypto.erl +++ b/src/znoise_crypto.erl @@ -63,7 +63,7 @@ hkdf(Hash, Key, Data) -> Output3 = hmac(Hash, TempKey, <>), [Output1, Output2, Output3]. --spec rekey(Cipher :: znoise_cipher_state:noise_cipher(), Key :: binary()) -> binary(). +-spec rekey(Cipher :: znoise_cipher:noise_cipher(), Key :: binary()) -> binary(). rekey('ChaChaPoly', K0) -> KLen = 32, <> = encrypt('ChaChaPoly', K0, ?MAX_NONCE, <<>>, <<0:(32*8)>>), @@ -71,13 +71,13 @@ rekey('ChaChaPoly', K0) -> rekey(Cipher, K) -> encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>). --spec encrypt(Cipher :: znoise_cipher_state:noise_cipher(), Key :: binary(), +-spec encrypt(Cipher :: znoise_cipher:noise_cipher(), Key :: binary(), Nonce :: non_neg_integer(), Ad :: binary(), PlainText :: binary()) -> binary(). encrypt(Cipher, K, N, Ad, PlainText) -> {CText, CTag} = crypto:crypto_one_time_aead(cipher(Cipher), K, nonce(Cipher, N), PlainText, Ad, true), <>. --spec decrypt(Cipher ::znoise_cipher_state:noise_cipher(), Key :: binary(), +-spec decrypt(Cipher ::znoise_cipher:noise_cipher(), Key :: binary(), Nonce :: non_neg_integer(), AD :: binary(), CipherText :: binary()) -> binary() | {error, term()}. decrypt(Cipher, K, N, Ad, CipherText0) -> diff --git a/src/znoise_hs_state.erl b/src/znoise_hs_state.erl index 219f535..eaa0197 100644 --- a/src/znoise_hs_state.erl +++ b/src/znoise_hs_state.erl @@ -25,8 +25,8 @@ -type noise_dh() :: dh25519 | dh448. -type noise_token() :: s | e | ee | ss | es | se. -type keypair() :: znoise_keypair:keypair(). --type noise_split_state() :: #{rx := znoise_cipher_state:state(), - tx := znoise_cipher_state:state(), +-type noise_split_state() :: #{rx := znoise_cipher:state(), + tx := znoise_cipher:state(), hs_hash := binary(), final_state => state() }. @@ -180,7 +180,7 @@ dh(#noise_hs{ dh = DH }, Key1, Key2) -> has_key(#noise_hs{ ss = SS }) -> CS = znoise_sym_state:cipher_state(SS), - znoise_cipher_state:has_key(CS). + znoise_cipher:has_key(CS). mix_key(HS = #noise_hs{ ss = SS0 }, Data) -> HS#noise_hs{ ss = znoise_sym_state:mix_key(SS0, Data) }. diff --git a/src/znoise_keypair.erl b/src/znoise_keypair.erl index 66a0a86..43a6196 100644 --- a/src/znoise_keypair.erl +++ b/src/znoise_keypair.erl @@ -12,30 +12,32 @@ -copyright("QPQ AG "). -license("ISC"). --export([ key_type/1 - , new/1 - , new/2 - , new/3 - , pubkey/1 - , seckey/1 - ]). +-export([key_type/1, + new/1, + new/2, + new/3, + pubkey/1, + seckey/1]). -type key_type() :: dh25519 | dh448. --record(kp, { type :: key_type() - , sec :: binary() | undefined - , pub :: binary() }). +-record(kp, + {type :: key_type(), + sec :: binary() | undefined, + pub :: binary()}). -opaque keypair() :: #kp{}. %% Abstract keypair holding a secret key/public key pair and its type. -export_type([keypair/0]). -%% @doc Generate a new keypair of type `Type'. -spec new(Type :: key_type()) -> keypair(). +%% @doc Generate a new keypair of type `Type'. + new(Type) -> {Pub, Sec} = new_key_pair(Type), - #kp{ type = Type, sec = Sec, pub = Pub }. + #kp{type = Type, sec = Sec, pub = Pub}. + %% @doc Create a new keypair of type `Type'. If `Public' is `undefined' %% it will be computed from the `Secret' (using the curve/algorithm diff --git a/src/znoise_protocol.erl b/src/znoise_protocol.erl index 354905b..fea65ea 100644 --- a/src/znoise_protocol.erl +++ b/src/znoise_protocol.erl @@ -12,15 +12,15 @@ -copyright("QPQ AG "). -license("ISC"). --export([ cipher/1 - , dh/1 - , from_name/1 - , hash/1 - , msgs/2 - , pattern/1 - , pre_msgs/2 - , supported/0 - , to_name/1]). +-export([cipher/1, + dh/1, + from_name/1, + hash/1, + msgs/2, + pattern/1, + pre_msgs/2, + supported/0, + to_name/1]). -ifdef(TEST). -export([to_name/4, from_name_pattern/1, to_name_pattern/1]). @@ -30,17 +30,16 @@ -type noise_msg() :: {in | out, [znoise_hs_state:noise_token()]}. -record(noise_protocol, - { hs_pattern = noiseNN :: noise_pattern() - , dh = dh25519 :: znoise_hs_state:noise_dh() - , cipher = 'ChaChaPoly' :: znoise_cipher_state:noise_cipher() - , hash = blake2b :: znoise_sym_state:noise_hash() - }). + {hs_pattern = noiseNN :: noise_pattern() + dh = dh25519 :: znoise_hs_state:noise_dh() + cipher = 'ChaChaPoly' :: znoise_cipher:noise_cipher() + hash = blake2b :: znoise_sym_state:noise_hash()}). -opaque protocol() :: #noise_protocol{}. -export_type([noise_msg/0, noise_pattern/0, protocol/0]). --spec cipher(Protocol :: protocol()) -> znoise_cipher_state:noise_cipher(). +-spec cipher(Protocol :: protocol()) -> znoise_cipher:noise_cipher(). cipher(#noise_protocol{ cipher = Cipher }) -> Cipher. diff --git a/src/znoise_sym_state.erl b/src/znoise_sym_state.erl index 63e11d6..cee7c9c 100644 --- a/src/znoise_sym_state.erl +++ b/src/znoise_sym_state.erl @@ -28,7 +28,7 @@ -type noise_hash() :: sha256 | sha512 | blake2s | blake2b. --record(noise_ss, { cs :: znoise_cipher_state:state() +-record(noise_ss, { cs :: znoise_cipher:state() , ck = <<>> :: binary() , h = <<>> :: binary() , hash = blake2b :: noise_hash() }). @@ -50,13 +50,13 @@ init(Protocol) -> #noise_ss{ h = H1 , ck = H1 , hash = Hash - , cs = znoise_cipher_state:init(empty, Cipher) }. + , cs = znoise_cipher:init(empty, Cipher) }. -spec mix_key(SState :: state(), InputKeyMaterial :: binary()) -> state(). mix_key(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) -> [CK1, <> | _] = znoise_crypto:hkdf(Hash, CK0, InputKeyMaterial), - CS1 = znoise_cipher_state:set_key(CS0, TempK), + CS1 = znoise_cipher:set_key(CS0, TempK), SState#noise_ss{ ck = CK1, cs = CS1 }. -spec mix_hash(SState :: state(), Data :: binary()) -> state(). @@ -68,32 +68,32 @@ mix_hash(SState = #noise_ss{ hash = Hash, h = H0 }, Data) -> mix_key_and_hash(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) -> [CK1, TempH, <>] = znoise_crypto:hkdf(Hash, CK0, InputKeyMaterial), - CS1 = znoise_cipher_state:set_key(CS0, TempK), + CS1 = znoise_cipher:set_key(CS0, TempK), mix_hash(SState#noise_ss{ ck = CK1, cs = CS1 }, TempH). -spec encrypt_and_hash(SState :: state(), PlainText :: binary()) -> {ok, state(), binary()}. encrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, PlainText) -> - {ok, CS1, CipherText} = znoise_cipher_state:encrypt_with_ad(CS0, H, PlainText), + {ok, CS1, CipherText} = znoise_cipher:encrypt_with_ad(CS0, H, PlainText), {ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), CipherText}. -spec decrypt_and_hash(SState :: state(), CipherText :: binary()) -> {ok, state(), binary()} | {error, term()}. decrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, CipherText) -> - case znoise_cipher_state:decrypt_with_ad(CS0, H, CipherText) of + case znoise_cipher:decrypt_with_ad(CS0, H, CipherText) of Err = {error, _} -> Err; {ok, CS1, PlainText} -> {ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), PlainText} end. --spec split(SState :: state()) -> {znoise_cipher_state:state(), znoise_cipher_state:state()}. +-spec split(SState :: state()) -> {znoise_cipher:state(), znoise_cipher:state()}. split(#noise_ss{ hash = Hash, ck = CK, cs = CS }) -> [<>, <>, _] = znoise_crypto:hkdf(Hash, CK, <<>>), - {znoise_cipher_state:set_key(CS, TempK1), - znoise_cipher_state:set_key(CS, TempK2)}. + {znoise_cipher:set_key(CS, TempK1), + znoise_cipher:set_key(CS, TempK2)}. --spec cipher_state(SState :: state()) -> znoise_cipher_state:state(). +-spec cipher_state(SState :: state()) -> znoise_cipher:state(). cipher_state(#noise_ss{ cs = CS }) -> CS. diff --git a/src/znoise_tcp.erl b/src/znoise_tcp.erl index 9b7da6e..7837462 100644 --- a/src/znoise_tcp.erl +++ b/src/znoise_tcp.erl @@ -4,8 +4,8 @@ %%% @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. +%%% Currently only "raw" mode is supported, but a `{packet, N}' +%%% option would be very convenient in the future. %%% @end -module(znoise_tcp). @@ -15,229 +15,175 @@ -copyright("QPQ AG "). -license("ISC"). --export([controlling_process/2, - close/1, +-export([start_listen/2, + start_connect/2, send/2, - set_active/2, - start_link/5]). + close/1]). -%% gen_server --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +-include("$zx_include/zx_logger.hrl"). --record(znoise, - {pid}). +% TODO: provide gen_tcp's packet option or 'raw' -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()}). + {rx = none :: none | znoise_cipher:state(), + tx = none :: none | znoise_cipher:state(), + sock = none :: none | gen_tcp:socket(), + mode = raw :: raw | {packet, 1..4}, + buff = <<>> :: binary()}). -start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) -> - 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... - ok = - case Buf =/= <<>> of - true -> Pid ! {tcp, TcpSock, Buf}; - false -> ok - end, - flush_tcp(Pid, TcpSock), - {ok, Pid}; - Error -> - close(Pid), - Error - end; - Error - Error +%%% Initializers + +-spec start_listen(ListenSock, Options, Noise) -> Outcome + when ListenSock :: + Options :: + Noise :: + Outcome :: {ok, pid()} | {error, Reason :: term()}. + +start_listen(ListenSock, Options, Noise) -> + proc_lib:start_link(?MODULE, init_listen, [self(), ListenSock, Options, Noise]). + + +init_listen(Parent, ListenSock, Options, Noise) -> + Debug = sys:debug_options(proplists:get_value(debug, Options, [])), + ok = proc_lib:init_ack(Parent, {ok, self()}), + listen(Parent, Debug, ListenSock, Options, Noise). + + +listen(Parent, Debug, ListenSock, Options, Noise) -> + + +start_connect(Host, Options, Noise) -> + proc_lib:start_link(?MODULE, init_connect, [self(), Host, Options, Noise]). + + +init_connect(Parent, Host, Options, Noise) -> + Debug = sys:debug_options(proplists:get_value(debug, Options, [])), + ok = proc_lib:init_ack(Parent, {ok, self()}), + connect(Parent, Debug, Host, Options, Noise). + + +connect(Parent, Debug, Host, Options, Noise) -> + + + +%%% Interface + +-spec send(Conn, Data) -> Outcome + when Conn :: pid(), + Data :: binary() | {file, file:filename()}, + Outcome :: ok | {error, Reason :: term()}. + +send(Conn, Data) -> + call(Conn, {send, Data}). + + +-spec close(Conn) -> Outcome + case Conn :: pid(), + Outcome :: ok | {error, Reason :: term()}. + +close(Conn) -> + call(Conn, close). + + +%% Non-system calls should only ever come from the parent + +call(Conn, Data) -> + call(Conn, Data, 5000). + +call(Conn, Data, Timeout) -> + Ref = make_ref(), + Conn ! {call, Ref, Data}, + receive + {resp, Ref, Result} -> Result + after Timeout -> {error, timeout} end. --spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}. +-spec loop(Parent, Debug, State) -> no_return() + when Parent :: pid(), + Debug :: [sys:dbg_opt()], + State :: state(). -send(Noise, Data) -> - gen_server:call(Noise, {send, Data}). +loop(Parent, Debug, State = #s{sock = Sock}) -> + case inet:setopts(Sock, [{active, once}]) of + ok -> wait(Parent, Debug, State); + {error, einval} -> exit(normal) + end. + +wait(Parent, Debug, State = #s{sock = Sock}) -> + receive + {tcp, Sock, Binary} -> + Newtate = get_bytes(Parent, Binary, State), + loop(Parent, Debug, NewState); + {call, Ref, {send, Data}} -> + {Result, NewState} = send_bytes(Data, State), + Parent ! {resp, Ref, Result}, + wait(Parent, Debug, NewState); + {call, Ref, close} -> + Result = gen_tcp:close(Sock), + Parent ! {resp, Ref, Result}, + exit(normal); + {tcp_closed, Sock} -> + Parent ! {noise_closed, self()}, + ok = tell(info, "Socket closed!"), + exit(normal); + {tcp_error, Sock, Info} -> + Parent ! {noise_error, Info}, + ok = tell(info, "Socket error: ~tp", [Info]), + exit(normal); + {system, From, Request} -> + sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, State); + Unexpected -> + ok = tell(info, "~p Unexpected message: ~tp", [self(), Unexpected]), + wait(Parent, Debug, State) + end. --spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}. - -set_active(Noise, Active) -> - gen_server:call(Noise, {active, self(), Active}). +system_continue(Parent, Debug, State) -> + wait(Parent, Debug, State). --spec close(Noise :: pid()) -> ok | {error, term()}. - -close(Noise) -> - gen_server:call(Noise, close). +system_terminate(Reason, _Parent, _Debug, _State) -> + exit(Reason). --spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}. - -controlling_process(Noise, NewPid) -> - gen_server:call(Noise, {controlling_process, self(), NewPid}, 100). +system_get_state(State) -> + {ok, State#s{rx = nope, tx = nope}}. -%% gen_server - -init([#s{owner = Owner} = State]) -> - OwnerRef = erlang:monitor(process, Owner), - {ok, State#s{owner_ref = OwnerRef}}. +system_replace_state(SusFun, State) -> + ok = tell(info, "Attempt to run system_replace_state/2 with ~tp", [SusFun]), + {ok, State, State}. -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_cast(_, State) -> - {noreply, State}. - - -handle_info({tcp, TS, Data}, State = #s{tcp_sock = TS, owner = O}) -> - try - {NextState = #s{msgbuf = Buf}, Msgs} = handle_data(State, Data), - NewState = handle_msgs(NextState#s{msgbuf = Buf ++ Msgs}), - set_active(NewState), - {noreply, NewState} - catch error:{znoise_error, _} -> - %% We are not likely to recover, but leave the decision to upstream - O ! {znoise_error, TS, decrypt_error}, - {noreply, State} - end; -handle_info({tcp_closed, TS}, State = #s{tcp_sock = TS, owner = O}) -> - O ! {tcp_closed, TS}, - {noreply, State#s{tcp_sock = closed}}; -handle_info({'DOWN', OwnerRef, process, _, normal}, - State = #s{tcp_sock = TS, owner_ref = OwnerRef}) -> - close_tcp(TS), - {stop, normal, State#s{tcp_sock = closed, owner_ref = undefined}}; -handle_info({'DOWN', _, _, _, _}, State) -> - %% Ignore non-normal monitor messages - we are linked. - {noreply, State}; -handle_info(_Msg, State) -> - {noreply, State}. - - -terminate(_, #s{tcp_sock = TcpSock, owner_ref = ORef}) -> - [ gen_tcp:close(TcpSock) || TcpSock /= closed ], - [ erlang:demonitor(ORef, [flush]) || ORef /= undefined ], - ok. - -code_change(_OldVsn, State, _Extra) -> +system_code_change(State, _Module, _OldVsn, _Extra) -> {ok, State}. - %%% Handlers -handle_control_change(State = #s{owner = PID, owner_ref = OldRef}, PID, NewPID) -> - NewRef = erlang:monitor(process, NewPID), - erlang:demonitor(OldRef, [flush]), - {ok, State#s{owner = NewPID, owner_ref = NewRef}}; -handle_control_change(State, _, _) -> - {{error, not_owner}, State}. - - -handle_active(State = #s{owner = PID, tcp_sock = TcpSock}, PID, Active) -> - case Active of - true -> - inet:setopts(TcpSock, [{active, true}]), - {ok, handle_msgs(State#s{active = true})}; - once -> - NewState = handle_msgs(State#s{active = {once, false}}), - set_active(NewState), - {ok, NewState} +read_bytes(Parent, <>, State = #s{buff = <<>>, rx = RX}) -> + case znoise_cipher:decrypt_with_ad(RX, <<>>, CT) of + {ok, NewRX, PT} -> + Parent ! {noise, PT}, + read_bytes(Parent, Rest, State#s{rx = NewRX}); + {error, _} -> + Parent ! {noise_error, decrypt_input_failed} + exit(normal) end; -handle_active(State, _, _) -> - {{error, not_owner}, State}. - - -handle_data(State = #s{rawbuf = Buf, rx = RX}, Data) -> - case <> of - B = <> when Len > byte_size(Rest) -> - {State#s{rawbuf = B}, []}; %% Not a full Noise message - save it - <> -> - <> = Rest, - case znoise_cipher_state:decrypt_with_ad(RX, <<>>, Msg) of - {ok, NewRX, NewMsg} -> - {NewState, Msgs} = handle_data(State#s{rawbuf = Rest2, rx = NewRX}, <<>>), - {NewState, [NewMsg | Msgs]}; - {error, _} -> - error({znoise_error, decrypt_input_failed}) - end; - EmptyOrSingleByte -> - {State#s{rawbuf = EmptyOrSingleByte}, []} +read_bytes(Parent, Received, State = #s{buff = <<>>}) -> + State#s{buff = Received}; +read_bytes(Parent, Received, State = #s{buff = Buff}) -> + case <> of + <<>> -> State; + Data -> read_bytes(Parent, Data, State#s{buff = <<>>}) end. -handle_msgs(State = #s{msgbuf = []}) -> - State; -handle_msgs(State = #s{msgbuf = Msgs, active = true, owner = Owner}) -> - [ Owner ! {noise, #znoise{pid = self()}, Msg} || Msg <- Msgs ], - State#s{msgbuf = []}; -handle_msgs(State = #s{msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner}) -> - case Delivered of - true -> - State; - false -> - Owner ! {noise, #znoise{pid = self()}, Msg}, - State#s{msgbuf = Msgs, active = {once, true}} - end. - - -handle_send(State = #s{tcp_sock = TcpSock, tx = TX}, Data) -> - {ok, MewTX, Msg} = znoise_cipher_state:encrypt_with_ad(TX, <<>>, Data), +send_bytes(Data, State = #s{sock = Sock, tx = TX}) -> + {ok, NewTX, Msg} = znoise_cipher:encrypt_with_ad(TX, <<>>, Data), case gen_tcp:send(TcpSock, <<(byte_size(Msg)):16, Msg/binary>>) of - ok -> {ok, State#s{tx = MewTX}}; + ok -> {ok, State#s{tx = NewTX}}; Error -> {Error, State} end. - - -set_active(#s{msgbuf = [], active = {once, _}, tcp_sock = TcpSock}) -> - inet:setopts(TcpSock, [{active, once}]); -set_active(_) -> - ok. - - -flush_tcp(Pid, TcpSock) -> - receive {tcp, TcpSock, Data} -> - Pid ! {tcp, TcpSock, Data}, - flush_tcp(Pid, TcpSock) - after 1 -> ok - end. - - -close_tcp(closed) -> - ok; -close_tcp(Sock) -> - gen_tcp:close(Sock).