diff --git a/build.zig b/build.zig index 86d17b8..96fb563 100644 --- a/build.zig +++ b/build.zig @@ -116,6 +116,7 @@ pub fn build(b: *std.Build) void { "test/endpoint_tcp.cpp", "test/hub_filter.cpp", "test/ip_tcp_client_server.cpp", + "test/ip_tcp_client_server_c.cpp", "test/ip_udp_echo.cpp", "test/metronome_test.cpp", "test/perf_cbor.cpp", diff --git a/test/ip_tcp_client_server_c.cpp b/test/ip_tcp_client_server_c.cpp new file mode 100644 index 0000000..99953a4 --- /dev/null +++ b/test/ip_tcp_client_server_c.cpp @@ -0,0 +1,235 @@ +#include "tests.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static thread_local char msg_json[256]; // NOLINT + +static void capture_json(c_string_view s) { + snprintf(msg_json, sizeof(msg_json), "%.*s", (int)s.len, s.base); +} + +static bool msg_is(cbor_buffer m, const char *pat) { + cbor_to_json(m, capture_json); + return strcmp(msg_json, pat) == 0; +} + +static bool msg_extract_int(cbor_buffer m, const char *pat, int *out) { + cbor_to_json(m, capture_json); + return sscanf(msg_json, pat, out) == 1; // NOLINT +} + +static thespian_result send(thespian_handle h, cbor::buffer msg) { + cbor_buffer m{msg.data(), msg.size()}; + return thespian_handle_send_raw(h, m); +} + +// --------------------------------------------------------------------------- +// client_connection +// --------------------------------------------------------------------------- + +struct cc_state { + thespian_socket_handle *sock; + thespian_handle client; +}; + +static void cc_dtor(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_socket_destroy(st->sock); + thespian_handle_destroy(st->client); + delete st; +} + +static thespian_result cc_receive(thespian_behaviour_state s, thespian_handle, + cbor_buffer m) { + auto *st = static_cast(s); + int written = 0; + if (msg_is(m, + "[\"socket\",\"client_connection\",\"read_complete\",\"ping\"]")) { + thespian_socket_write(st->sock, "pong", 4); + } else if (msg_extract_int( + m, "[\"socket\",\"client_connection\",\"write_complete\",%d]", + &written)) { + thespian_socket_read(st->sock); + } else if (msg_is(m, "[\"socket\",\"client_connection\",\"closed\"]")) { + send(st->client, cbor::array("client_connection", "done")); + return thespian_exit("success"); + } + return nullptr; +} + +static thespian_result cc_start(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_socket_read(st->sock); + thespian_receive(cc_receive, s, cc_dtor); + return nullptr; +} + +// --------------------------------------------------------------------------- +// client +// --------------------------------------------------------------------------- + +struct client_state { + thespian_tcp_connector_handle *connector; + thespian_handle server; + uint16_t port; +}; + +static void client_dtor(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_tcp_connector_destroy(st->connector); + thespian_handle_destroy(st->server); + delete st; +} + +static thespian_result client_receive(thespian_behaviour_state s, + thespian_handle, cbor_buffer m) { + auto *st = static_cast(s); + int fd = 0; + if (msg_extract_int(m, "[\"connector\",\"client\",\"connected\",%d]", &fd)) { + auto *cc = new cc_state{thespian_socket_create("client_connection", fd), + thespian_handle_clone(thespian_self())}; + thespian_handle out{}; + thespian_spawn_link(cc_start, cc, "client_connection", nullptr, &out); + thespian_handle_destroy(out); + } else if (msg_is(m, "[\"client_connection\",\"done\"]")) { + send(st->server, cbor::array("client", "done")); + return thespian_exit("normal"); + } + return nullptr; +} + +static thespian_result client_start(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_tcp_connector_connect(st->connector, in6addr_loopback, st->port); + thespian_receive(client_receive, s, client_dtor); + return nullptr; +} + +// --------------------------------------------------------------------------- +// server_connection +// --------------------------------------------------------------------------- + +struct sc_state { + thespian_socket_handle *sock; + thespian_handle server; +}; + +static void sc_dtor(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_socket_destroy(st->sock); + thespian_handle_destroy(st->server); + delete st; +} + +static thespian_result sc_receive(thespian_behaviour_state s, thespian_handle, + cbor_buffer m) { + auto *st = static_cast(s); + int written = 0; + if (msg_extract_int( + m, "[\"socket\",\"server_connection\",\"write_complete\",%d]", + &written)) { + thespian_socket_read(st->sock); + } else if (msg_is(m, "[\"socket\",\"server_connection\",\"read_complete\"," + "\"pong\"]")) { + thespian_socket_close(st->sock); + } else if (msg_is(m, "[\"socket\",\"server_connection\",\"closed\"]")) { + send(st->server, cbor::array("server_connection", "done")); + return thespian_exit("success"); + } + return nullptr; +} + +static thespian_result sc_start(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_socket_write(st->sock, "ping", 4); + thespian_receive(sc_receive, s, sc_dtor); + return nullptr; +} + +// --------------------------------------------------------------------------- +// server +// --------------------------------------------------------------------------- + +struct server_state { + thespian_tcp_acceptor_handle *acceptor; + bool acceptor_closed{false}; + bool client_done{false}; + bool server_conn_done{false}; +}; + +static void server_dtor(thespian_behaviour_state s) { + auto *st = static_cast(s); + thespian_tcp_acceptor_destroy(st->acceptor); + delete st; +} + +static thespian_result server_receive(thespian_behaviour_state s, + thespian_handle, cbor_buffer m) { + auto *st = static_cast(s); + int fd = 0; + if (msg_extract_int(m, "[\"acceptor\",\"server\",\"accept\",%d]", &fd)) { + auto *sc = new sc_state{thespian_socket_create("server_connection", fd), + thespian_handle_clone(thespian_self())}; + thespian_handle out{}; + thespian_spawn_link(sc_start, sc, "server_connection", nullptr, &out); + thespian_handle_destroy(out); + thespian_tcp_acceptor_close(st->acceptor); + } else if (msg_is(m, "[\"acceptor\",\"server\",\"closed\"]")) { + st->acceptor_closed = true; + } else if (msg_is(m, "[\"client\",\"done\"]")) { + st->client_done = true; + } else if (msg_is(m, "[\"server_connection\",\"done\"]")) { + st->server_conn_done = true; + } + if (st->acceptor_closed && st->client_done && st->server_conn_done) + return thespian_exit("success"); + return nullptr; +} + +static thespian_result server_start(thespian_behaviour_state s) { + auto *st = static_cast(s); + st->acceptor = thespian_tcp_acceptor_create("server"); + uint16_t port = + thespian_tcp_acceptor_listen(st->acceptor, in6addr_loopback, 0); + + auto *cl = new client_state{thespian_tcp_connector_create("client"), + thespian_handle_clone(thespian_self()), port}; + thespian_handle out{}; + thespian_spawn_link(client_start, cl, "client", nullptr, &out); + thespian_handle_destroy(out); + + thespian_receive(server_receive, s, server_dtor); + return nullptr; +} + +// --------------------------------------------------------------------------- +// Entry point +// --------------------------------------------------------------------------- + +auto ip_tcp_client_server_c(thespian::context &ctx, bool &result, + thespian::env_t /*env*/) -> thespian::result { + auto *st = new server_state{}; + thespian_handle out{}; + thespian_context_spawn_link( + reinterpret_cast(&ctx), // NOLINT + server_start, st, + [](thespian_exit_handler_state r, const char *msg, size_t len) { + if (strncmp(msg, "success", len) == 0) + *static_cast(r) = true; + }, + &result, "ip_tcp_client_server_c", nullptr, &out); + thespian_handle_destroy(out); + return thespian::ok(); +} diff --git a/test/tests.cpp b/test/tests.cpp index aba9ea0..3fecd27 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -103,6 +103,7 @@ extern "C" auto runtestcase(const char *name) -> int { tests["endpoint_tcp"] = endpoint_tcp; tests["hub_filter"] = hub_filter; tests["ip_tcp_client_server"] = ip_tcp_client_server; + tests["ip_tcp_client_server_c"] = ip_tcp_client_server_c; tests["ip_udp_echo"] = ip_udp_echo; tests["metronome_test"] = metronome_test; tests["perf_cbor"] = perf_cbor; diff --git a/test/tests.hpp b/test/tests.hpp index 4cf3e4b..c248006 100644 --- a/test/tests.hpp +++ b/test/tests.hpp @@ -19,6 +19,7 @@ testcase endpoint_tcp; testcase endpoint_unx; testcase hub_filter; testcase ip_tcp_client_server; +testcase ip_tcp_client_server_c; testcase ip_udp_echo; testcase metronome_test; testcase perf_cbor; diff --git a/test/tests_cpp.zig b/test/tests_cpp.zig index 7c17268..b8ee481 100644 --- a/test/tests_cpp.zig +++ b/test/tests_cpp.zig @@ -35,6 +35,10 @@ test "ip_tcp_client_server" { try testcase("ip_tcp_client_server"); } +test "ip_tcp_client_server_c" { + try testcase("ip_tcp_client_server_c"); +} + test "ip_udp_echo" { try testcase("ip_udp_echo"); }