3RUDKBK2XSZZSMORSCHSLHZCVSQFCCZEHQFB2BFSPCGXC5XVLJMQC Z3LME4VKGE7Z5CXYOXH4YDZNHQPVCP7ORSMAQ2AQO7ZVQ5WASLHQC INDYNANFIEAIVL4YWBE75UPFVZ6BUOMP4LTIUEO2PQXVYSHRSDDQC KQZ7HZUWHFHJJVQJIR2PFGMUTIFOASWLSTD54LDBINHTAXYKEXKQC 6RQQDL46IO2ZFTJSEJREWJIMTNHOH4UBSO2VXAYNLEWNUR72OWHQC CBHKQGLDCAH2E4ZNACITBSMADOKPERFCWQPUGMH7UN5TLJXLYI4QC 55WLMLEEVBRSTAFRZ5RGF7TOGUF5OPVCPA2TMHAQK45OUO7PA3YQC G7UELMX4I2NFSZKZFFACQL5M3DRWQDQGM3NSHHVYAE75JSLXAVWQC % Open a listening socketListenOpts = [binary,{packet, 0},{active, false},{reuseaddr, true}],{ok, ListenSocket} = gen_tcp:listen(Port, ListenOpts),{ok, ListenPort} = inet:port(ListenSocket),
% Start enoise_cable listenerTcpOpts = [{reuseaddr, true}],case enoise_cable:listen(Port, TcpOpts) of{ok, ListenerPid} ->{ok, ListenPort} = enoise_cable:port(ListenerPid),
% Capture the correct process ID for the transport serverTransportPid = self(),io:format("[Transport] Starting server on port ~p (pid: ~p)~n", [ListenPort, TransportPid]),
% Capture the correct process ID for the transport serverTransportPid = self(),io:format("[Transport] Starting encrypted server on port ~p (pid: ~p)~n", [ListenPort, TransportPid]),
% Initialize state{ok, #state{listen_socket = ListenSocket,listen_port = ListenPort,key_pair = KeyPair,event_handler = Handler,socket_to_peer = #{},peer_to_socket = #{}}}
% Initialize state{ok, #state{listener_pid = ListenerPid,listen_port = ListenPort,key_pair = KeyPair,event_handler = Handler,conn_to_peer = #{},peer_to_conn = #{}}};{error, Reason} ->{stop, Reason}end
handle_call({send, Peer, Binary}, _From, State = #state{peer_to_socket = PtoS}) ->% Find socket by peer address directlycase maps:get(Peer, PtoS, undefined) of
handle_call({send, Peer, Binary}, _From, State = #state{peer_to_conn = PtoC}) ->% Find connection pid by peer address directlycase maps:get(Peer, PtoC, undefined) of
% Start a socket loop to handle incoming dataTransportPid = self(),spawn_link(fun() -> socket_loop(Socket, PeerAddr, TransportPid) end),
% Store mapping of ConnPid -> PeerAddr and PeerAddr -> ConnPidNewCtoP = maps:put(ConnPid, PeerAddr, CtoP),NewPtoC = maps:put(PeerAddr, ConnPid, PtoC),
{noreply, State#state{socket_to_peer = NewStoP, peer_to_socket = NewPtoS}};%% Handle incoming message from a peerhandle_info({peer_data, _Socket, PeerAddr, Data}, State = #state{event_handler = Handler}) ->io:format("[Transport] Received ~p bytes from ~p~n", [byte_size(Data), PeerAddr]),% Forward data to handlerHandler ! {peerData, PeerAddr, Data},
{noreply, State};%% Handle peer disconnection
{noreply, State#state{conn_to_peer = NewCtoP, peer_to_conn = NewPtoC}};%% Handle incoming encrypted message from enoise_cablehandle_info({cable_transport, ConnPid, Data}, State = #state{event_handler = Handler, conn_to_peer = CtoP}) ->case maps:get(ConnPid, CtoP, undefined) ofundefined ->io:format("[Transport] Received data from unknown connection ~p~n", [ConnPid]),{noreply, State};PeerAddr ->io:format("[Transport] Received ~p bytes from ~p~n", [byte_size(Data), PeerAddr]),% Forward data to handlerHandler ! {peerData, PeerAddr, Data},{noreply, State}end;%% Handle connection process termination
io:format("[Transport] Connection closed by peer ~p~n", [PeerAddr]),% Clean up mappingsNewStoP = maps:remove(Socket, StoP),NewPtoS = maps:remove(PeerAddr, PtoS),
case maps:get(ConnPid, CtoP, undefined) ofundefined ->io:format("[Transport] Unknown connection ~p terminated~n", [ConnPid]),{noreply, State};PeerAddr ->io:format("[Transport] Connection closed with peer ~p~n", [PeerAddr]),% Clean up mappingsNewCtoP = maps:remove(ConnPid, CtoP),NewPtoC = maps:remove(PeerAddr, PtoC),
case gen_tcp:accept(ListenSocket) of{ok, Socket} ->io:format("[Transport] Accepted new connection: ~p~n", [Socket]),{ok, {PeerIP, PeerPort}} = inet:peername(Socket),
Opts = [{keypair, KeyPair}],case enoise_cable:accept(ListenerPid, Opts) of{ok, ConnPid} ->io:format("[Transport] Accepted new encrypted connection: ~p~n", [ConnPid]),% Set the controlling process to the transport gen_server% so it receives {cable_transport, ConnPid, Data} messagesok = enoise_cable:controlling_process(ConnPid, TransportPid),% Get the peer address from the connection{ok, {PeerIP, PeerPort}} = enoise_cable:peername(ConnPid),
accept_loop(ListenSocket, TransportPid)end.%% Socket receive loopsocket_loop(Socket, PeerAddr, TransportPid) ->case gen_tcp:recv(Socket, 0) of{ok, Data} ->TransportPid ! {peer_data, Socket, PeerAddr, Data},socket_loop(Socket, PeerAddr, TransportPid);{error, closed} ->io:format("[Transport] Socket ~p closed by peer~n", [Socket]),TransportPid ! {peer_closed, Socket, PeerAddr};{error, Reason} ->io:format("[Transport] Socket error: ~p~n", [Reason]),TransportPid ! {peer_closed, Socket, PeerAddr}
accept_loop(ListenerPid, KeyPair, TransportPid)
do_dial(Host, Port, TransportPid) ->case gen_tcp:connect(Host, Port, [binary, {packet, 0}, {active, false}]) of{ok, Socket} ->io:format("[Transport] Connected to ~p:~p~n", [Host, Port]),{ok, {PeerIP, PeerPort}} = inet:peername(Socket),PeerAddr = {PeerIP, PeerPort},% Transfer socket ownership to the transport gen_server before we exit% Otherwise the socket will be closed when this process terminatesok = gen_tcp:controlling_process(Socket, TransportPid),TransportPid ! {new_connection, Socket, PeerAddr},% The transport gen_server will spawn socket_loop when it handles {new_connection, ...}
do_dial(Host, Port, KeyPair, TransportPid) ->Opts = [{keypair, KeyPair}],case enoise_cable:connect(Host, Port, Opts) of{ok, ConnPid} ->io:format("[Transport] Encrypted connection established to ~p:~p (conn: ~p)~n", [Host, Port, ConnPid]),% Set the controlling process to the transport gen_server% so it receives {cable_transport, ConnPid, Data} messagesok = enoise_cable:controlling_process(ConnPid, TransportPid),% Use Host/Port as the peer address since we don't have the actual remote addressPeerAddr = {Host, Port},TransportPid ! {new_connection, ConnPid, PeerAddr},
create_or_load_transport_keypair(Location) ->Fname = filename:join(Location, "transport_secret"),case file:read_file(Fname) of%% load existing{ok, Content} when byte_size(Content) =:= 32 ->io:format("[Transport] Loaded existing Curve25519 keypair~n"),enoise_keypair:new(dh25519, Content);%% generate new keypair{error, enoent} ->Kp = enoise_keypair:new(dh25519),{kp, dh25519, Secret, _Public} = Kp,ok = file:write_file(Fname, Secret),ok = file:change_mode(Fname, 8#0400),io:format("[Transport] Created fresh Curve25519 keypair~n"),Kpend.
% enoise now handles cable framing (length + segmentation)io:format(user, "[enoise_cable] Sending message: ~p bytes~n", [byte_size(Message)]),case enoise:send(EConn, Message) ofok ->io:format(user, "[enoise_cable] Message sent~n", []),{reply, ok, State};{error, Reason} ->{reply, {error, Reason}, State}end;
Res = enoise:send(EConn, Message),{reply, Res, State};