a cabal implementation in erlang
-module(enoise_cable_test).

-include_lib("eunit/include/eunit.hrl").

cable_framing_test() ->
    % Generate keypairs
    AliceKeys = enoise_keypair:new(dh25519),
    BobKeys = enoise_keypair:new(dh25519),

    % Shared PSK
    PSK =
        hex:hexstr_to_bin("0808080808080808080808080808080808080808080808080808080808080808"),

    % Start TCP listener
    % PIDs for synchronization
    Self = self(),

    % Spawn Bob (server) in his own process
    ServerPid =
        spawn_link(fun() ->
            {ok, ListenerPid} = enoise_cable:listen(0),
            {ok, Port} = enoise_cable:port(ListenerPid),

            Self ! {server_port, Port},

            {ok, BobConn} =
                enoise_cable:accept(ListenerPid, [{keypair, BobKeys}, {psk, PSK}]),
            Self ! server_ready,
            server_receive_loop(BobConn, Self, [])
        end),

    Port =
        receive
            {server_port, P} ->
                P
        end,

    % Spawn Alice (client) in her own process
    spawn_link(fun() ->
        {ok, AliceConn} =
            enoise_cable:connect("localhost", Port, [{keypair, AliceKeys}, {psk, PSK}]),
        Self ! client_ready,

        % Test 1: Send small message
        Msg1 = <<"Hello, Cable!">>,
        ok = enoise_cable:send(AliceConn, Msg1),

        % Test 3: Send large message
        LargeMsg = binary:copy(<<"X">>, 100000),
        ok = enoise_cable:send(AliceConn, LargeMsg),

        % Test 2: Wait for message from Bob and reply
        receive
            {cable_transport, _, Msg2} ->
                % Echo back for confirmation in main test
                Self ! {client_received, Msg2},
                ok
        after 5000 -> Self ! {client_error, timeout_recv}
        end,

        % Cleanup
        enoise_cable:close(AliceConn)
    end),

    % Wait for both peers to be ready
    wait_for_ready(),

    % Now expect Bob to receive messages from Alice
    receive
        {server_received, <<"Hello, Cable!">>} ->
            ok;
        Other1 ->
            error({unexpected_msg1, Other1})
    after 5000 ->
        error(timeout_msg1)
    end,

    LargeMsg = binary:copy(<<"X">>, 100000),
    receive
        {server_received, ReceivedLarge} ->
            ?assertEqual(LargeMsg, ReceivedLarge),
            ok;
        OtherLarge ->
            error({unexpected_large_msg, OtherLarge})
    after 10000 ->
        error(timeout_large_msg)
    end,

    % Now let Bob send a message back
    ServerPid ! {send_response, <<"Hello, Alice!">>},

    % Wait for Alice to receive it
    receive
        {client_received, <<"Hello, Alice!">>} ->
            ok
    after 5000 ->
        error(timeout_msg2)
    end,

    % Cleanup - processes will exit on their own
    ok.

%% Server loop: receives messages and handles commands
server_receive_loop(Conn, TestPid, Seen) ->
    receive
        {send_response, Msg} ->
            ok = enoise_cable:send(Conn, Msg),
            server_receive_loop(Conn, TestPid, Seen);
        {cable_transport, _, Msg} ->
            io:format(user, "[test] Server loop received ~p bytes~n", [byte_size(Msg)]),
            TestPid ! {server_received, Msg},
            server_receive_loop(Conn, TestPid, [Msg | Seen])
    after 10000 ->
        % Graceful exit
        io:format(user, "[test] Server loop timeout, closing~n", []),
        enoise_cable:close(Conn)
    end.

wait_for_ready() ->
    wait_for_ready(undefined, undefined).

wait_for_ready(M1, M2) when M1 =/= undefined, M2 =/= undefined ->
    io:format(user, "both ready~n", []),
    ok;
wait_for_ready(Srv, Cli) ->
    receive
        client_ready ->
            io:format(user, "Client ready~n", []),
            wait_for_ready(Srv, client_read);
        server_ready ->
            io:format(user, "Server ready~n", []),
            wait_for_ready(server_ready, Cli)
    after 5000 ->
        error(timeout_waiting_for_ready)
    end.