feat: add C & Zig bindings for tcp connector and acceptor
This commit is contained in:
parent
ed91a28f5f
commit
e19ff271d0
9 changed files with 269 additions and 0 deletions
|
|
@ -60,6 +60,7 @@ pub fn build(b: *std.Build) void {
|
|||
"src/c/signal.cpp",
|
||||
"src/c/timeout.cpp",
|
||||
"src/c/unx.cpp",
|
||||
"src/c/tcp.cpp",
|
||||
"src/c/trace.cpp",
|
||||
"src/cbor.cpp",
|
||||
"src/executor_asio.cpp",
|
||||
|
|
@ -124,6 +125,7 @@ pub fn build(b: *std.Build) void {
|
|||
"test/tests.cpp",
|
||||
"test/timeout_test.cpp",
|
||||
"test/unx_c_api.cpp",
|
||||
"test/tcp_c_api.cpp",
|
||||
}, .flags = &cppflags });
|
||||
tests.linkLibrary(lib);
|
||||
tests.linkLibrary(asio_dep.artifact("asio"));
|
||||
|
|
|
|||
31
include/thespian/c/tcp.h
Normal file
31
include/thespian/c/tcp.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
// NOLINTBEGIN(modernize-use-trailing-return-type)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct thespian_tcp_acceptor_handle;
|
||||
struct thespian_tcp_acceptor_handle *
|
||||
thespian_tcp_acceptor_create(const char *tag);
|
||||
uint16_t thespian_tcp_acceptor_listen(struct thespian_tcp_acceptor_handle *,
|
||||
struct in6_addr ip, uint16_t port);
|
||||
int thespian_tcp_acceptor_close(struct thespian_tcp_acceptor_handle *);
|
||||
void thespian_tcp_acceptor_destroy(struct thespian_tcp_acceptor_handle *);
|
||||
|
||||
struct thespian_tcp_connector_handle;
|
||||
struct thespian_tcp_connector_handle *
|
||||
thespian_tcp_connector_create(const char *tag);
|
||||
int thespian_tcp_connector_connect(struct thespian_tcp_connector_handle *,
|
||||
struct in6_addr ip, uint16_t port);
|
||||
int thespian_tcp_connector_cancel(struct thespian_tcp_connector_handle *);
|
||||
void thespian_tcp_connector_destroy(struct thespian_tcp_connector_handle *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
// NOLINTEND(modernize-use-trailing-return-type)
|
||||
|
|
@ -49,4 +49,15 @@ struct connector {
|
|||
connector_ref ref;
|
||||
};
|
||||
|
||||
// C++ helpers used by the C binding layer
|
||||
|
||||
auto acceptor_listen(acceptor_impl *h, const in6_addr &ip, port_t port)
|
||||
-> port_t;
|
||||
void acceptor_close(acceptor_impl *h);
|
||||
void destroy_acceptor(acceptor_impl *h);
|
||||
|
||||
void connector_connect(connector_impl *h, const in6_addr &ip, port_t port);
|
||||
void connector_cancel(connector_impl *h);
|
||||
void destroy_connector(connector_impl *h);
|
||||
|
||||
} // namespace thespian::tcp
|
||||
|
|
|
|||
126
src/c/tcp.cpp
Normal file
126
src/c/tcp.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include <thespian/c/context.h>
|
||||
#include <thespian/c/tcp.h>
|
||||
#include <thespian/tcp.hpp>
|
||||
|
||||
using ::port_t;
|
||||
using thespian::tcp::acceptor_impl;
|
||||
using thespian::tcp::connector_impl;
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto thespian_tcp_acceptor_create(const char *tag)
|
||||
-> struct thespian_tcp_acceptor_handle * {
|
||||
try {
|
||||
auto *h = thespian::tcp::acceptor::create(tag).ref.release();
|
||||
return reinterpret_cast<struct thespian_tcp_acceptor_handle *>(h); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_acceptor_create error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto thespian_tcp_acceptor_listen(struct thespian_tcp_acceptor_handle *handle,
|
||||
in6_addr ip, uint16_t port) -> uint16_t {
|
||||
try {
|
||||
port_t p = thespian::tcp::acceptor_listen(
|
||||
reinterpret_cast<acceptor_impl *>(handle), ip, port); // NOLINT
|
||||
return static_cast<uint16_t>(p);
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return 0;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_acceptor_listen error");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto thespian_tcp_acceptor_close(struct thespian_tcp_acceptor_handle *handle)
|
||||
-> int {
|
||||
try {
|
||||
thespian::tcp::acceptor_close(
|
||||
reinterpret_cast<acceptor_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_acceptor_close error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void thespian_tcp_acceptor_destroy(
|
||||
struct thespian_tcp_acceptor_handle *handle) {
|
||||
try {
|
||||
thespian::tcp::destroy_acceptor(
|
||||
reinterpret_cast<acceptor_impl *>(handle)); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_acceptor_destroy error");
|
||||
}
|
||||
}
|
||||
|
||||
auto thespian_tcp_connector_create(const char *tag)
|
||||
-> struct thespian_tcp_connector_handle * {
|
||||
try {
|
||||
auto *h = thespian::tcp::connector::create(tag).ref.release();
|
||||
return reinterpret_cast<struct thespian_tcp_connector_handle *>( // NOLINT
|
||||
h);
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_connector_create error");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto thespian_tcp_connector_connect(
|
||||
struct thespian_tcp_connector_handle *handle, in6_addr ip, uint16_t port)
|
||||
-> int {
|
||||
try {
|
||||
thespian::tcp::connector_connect(
|
||||
reinterpret_cast<connector_impl *>(handle), // NOLINT
|
||||
ip, port);
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_connector_connect error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
auto thespian_tcp_connector_cancel(struct thespian_tcp_connector_handle *handle)
|
||||
-> int {
|
||||
try {
|
||||
thespian::tcp::connector_cancel(
|
||||
reinterpret_cast<connector_impl *>(handle)); // NOLINT
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_connector_cancel error");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void thespian_tcp_connector_destroy(
|
||||
struct thespian_tcp_connector_handle *handle) {
|
||||
try {
|
||||
thespian::tcp::destroy_connector(
|
||||
reinterpret_cast<connector_impl *>(handle)); // NOLINT
|
||||
} catch (const std::exception &e) {
|
||||
thespian_set_last_error(e.what());
|
||||
} catch (...) {
|
||||
thespian_set_last_error("unknown thespian_tcp_connector_destroy error");
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
@ -1267,6 +1267,21 @@ void connector::connect(in6_addr ip, port_t port, in6_addr lip, port_t lport) {
|
|||
|
||||
void connector::cancel() { ref->cancel(); }
|
||||
|
||||
// C API helpers - visibility requires complete types
|
||||
|
||||
auto acceptor_listen(acceptor_impl *h, const in6_addr &ip, port_t port)
|
||||
-> port_t {
|
||||
return h->listen(ip, port);
|
||||
}
|
||||
void acceptor_close(acceptor_impl *h) { h->close(); }
|
||||
void destroy_acceptor(acceptor_impl *h) { delete h; }
|
||||
|
||||
void connector_connect(connector_impl *h, const in6_addr &ip, port_t port) {
|
||||
h->connect(ip, port, in6addr_any, 0);
|
||||
}
|
||||
void connector_cancel(connector_impl *h) { h->cancel(); }
|
||||
void destroy_connector(connector_impl *h) { delete h; }
|
||||
|
||||
} // namespace tcp
|
||||
|
||||
namespace unx {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ const c = @cImport({
|
|||
@cInclude("thespian/c/timeout.h");
|
||||
@cInclude("thespian/c/signal.h");
|
||||
@cInclude("thespian/c/unx.h");
|
||||
@cInclude("thespian/c/tcp.h");
|
||||
@cInclude("netinet/in.h");
|
||||
});
|
||||
const c_posix = if (builtin.os.tag != .windows) @cImport({
|
||||
@cInclude("thespian/backtrace.h");
|
||||
|
|
@ -797,6 +799,55 @@ pub const unx_connector = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const tcp_acceptor = struct {
|
||||
handle: *c.struct_thespian_tcp_acceptor_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(tag_: []const u8) !Self {
|
||||
return .{ .handle = c.thespian_tcp_acceptor_create(tag_) orelse return log_last_error(error.ThespianTcpAcceptorInitFailed) };
|
||||
}
|
||||
|
||||
pub fn listen(self: *const Self, ip: c.in6_addr, port: u16) !u16 {
|
||||
const ret = c.thespian_tcp_acceptor_listen(self.handle, ip, port);
|
||||
if (ret == 0) return error.ThespianTcpAcceptorListenFailed;
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn close(self: *const Self) !void {
|
||||
const ret = c.thespian_tcp_acceptor_close(self.handle);
|
||||
if (ret < 0) return error.ThespianTcpAcceptorCloseFailed;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_tcp_acceptor_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
pub const tcp_connector = struct {
|
||||
handle: *c.struct_thespian_tcp_connector_handle,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(tag_: []const u8) !Self {
|
||||
return .{ .handle = c.thespian_tcp_connector_create(tag_) orelse return log_last_error(error.ThespianTcpConnectorInitFailed) };
|
||||
}
|
||||
|
||||
pub fn connect(self: *const Self, ip: c.in6_addr, port: u16) !void {
|
||||
const ret = c.thespian_tcp_connector_connect(self.handle, ip, port);
|
||||
if (ret < 0) return error.ThespianTcpConnectorConnectFailed;
|
||||
}
|
||||
|
||||
pub fn cancel(self: *const Self) !void {
|
||||
const ret = c.thespian_tcp_connector_cancel(self.handle);
|
||||
if (ret < 0) return error.ThespianTcpConnectorCancelFailed;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self) void {
|
||||
c.thespian_tcp_connector_destroy(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
pub const timeout = struct {
|
||||
handle: ?*c.struct_thespian_timeout_handle,
|
||||
|
||||
|
|
|
|||
31
test/tcp_c_api.cpp
Normal file
31
test/tcp_c_api.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#include "tests.hpp"
|
||||
#include <netinet/in.h>
|
||||
#include <thespian/c/tcp.h>
|
||||
|
||||
using namespace thespian;
|
||||
|
||||
// simple smoke test of tcp C API: create + destroy handles and listen on any
|
||||
// port
|
||||
|
||||
auto tcp_c_api(thespian::context &ctx, bool &result, thespian::env_t env)
|
||||
-> thespian::result {
|
||||
(void)ctx;
|
||||
(void)env;
|
||||
|
||||
struct thespian_tcp_acceptor_handle *a = thespian_tcp_acceptor_create("tag");
|
||||
check(a != nullptr);
|
||||
uint16_t port = thespian_tcp_acceptor_listen(a, in6addr_any, 0);
|
||||
// port may be zero if something went wrong; ignore for smoke.
|
||||
(void)port;
|
||||
thespian_tcp_acceptor_close(a);
|
||||
thespian_tcp_acceptor_destroy(a);
|
||||
|
||||
struct thespian_tcp_connector_handle *c =
|
||||
thespian_tcp_connector_create("tag");
|
||||
check(c != nullptr);
|
||||
// don't attempt to connect, simply exercise create/destroy
|
||||
thespian_tcp_connector_destroy(c);
|
||||
|
||||
result = true;
|
||||
return ok();
|
||||
}
|
||||
|
|
@ -112,6 +112,7 @@ extern "C" auto runtestcase(const char *name) -> int {
|
|||
tests["spawn_exit"] = spawn_exit;
|
||||
tests["timeout_test"] = timeout_test;
|
||||
tests["unx_c_api"] = unx_c_api;
|
||||
tests["tcp_c_api"] = tcp_c_api;
|
||||
|
||||
env_t env{};
|
||||
env_t log_env{};
|
||||
|
|
|
|||
|
|
@ -28,3 +28,4 @@ testcase perf_spawn;
|
|||
testcase spawn_exit;
|
||||
testcase timeout_test;
|
||||
testcase unx_c_api;
|
||||
testcase tcp_c_api;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue