From 16fd65e281776bd27d9aefdcfb39fa8b8f7a9fba Mon Sep 17 00:00:00 2001 From: Robby Zambito Date: Sat, 24 Jan 2026 16:13:35 -0500 Subject: Add C API --- build.zig | 17 +++++++++++ include/zaprus.h | 33 ++++++++++++++++++++ src/Connection.zig | 4 +++ src/c_api.zig | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 1 + 5 files changed, 144 insertions(+) create mode 100644 include/zaprus.h create mode 100644 src/c_api.zig diff --git a/build.zig b/build.zig index c07def0..1b7e474 100644 --- a/build.zig +++ b/build.zig @@ -41,6 +41,23 @@ pub fn build(b: *std.Build) void { .target = target, }); + // Create static library + const lib = b.addLibrary(.{ + .name = "zaprus", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/c_api.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + .imports = &.{ + .{ .name = "zaprus", .module = mod }, + }, + }), + }); + + b.installArtifact(lib); + lib.installHeader(b.path("include/zaprus.h"), "zaprus.h"); + // Here we define an executable. An executable needs to have a root module // which needs to expose a `main` function. While we could add a main function // to the module defined above, it's sometimes preferable to split business diff --git a/include/zaprus.h b/include/zaprus.h new file mode 100644 index 0000000..e81383b --- /dev/null +++ b/include/zaprus.h @@ -0,0 +1,33 @@ +#ifndef ZAPRUS_H +#define ZAPRUS_H + +#include +#include + +typedef void* zaprus_client; +typedef void* zaprus_connection; + +// Returns NULL if there was an error. +zaprus_client zaprus_init_client(void); + +void zaprus_deinit_client(zaprus_client client); + +// Returns 0 on success, else returns 1. +int zaprus_client_send_relay(zaprus_client client, const char* payload, size_t payload_len, const char dest[4]); + +// Returns NULL if there was an error. +// Caller should call zaprus_deinit_connection when done with the connection. +zaprus_connection zaprus_connect(zaprus_client client, const char* payload, size_t payload_len); + +void zaprus_deinit_connection(zaprus_connection connection); + +// Capacity is the maximum length of the output buffer. +// out_len is modified to specify how much of the capacity is used by the response. +// Blocks until the next message is available, or returns 1 if the underlying socket times out. +// Returns 0 on success, else returns 1. +int zaprus_connection_next(zaprus_connection connection, char *out, size_t capacity, size_t *out_len); + +// Returns 0 on success, else returns 1. +int zaprus_connection_send(zaprus_connection connection, const char *payload, size_t payload_len); + +#endif // ZAPRUS_H diff --git a/src/Connection.zig b/src/Connection.zig index 95805de..cdeb57e 100644 --- a/src/Connection.zig +++ b/src/Connection.zig @@ -12,6 +12,10 @@ pub fn init(socket: RawSocket, headers: EthIpUdp, connection: SaprusMessage) Con }; } +pub fn deinit(self: *Connection) void { + self.socket.deinit(); +} + pub fn next(self: Connection, io: Io, buf: []u8) ![]const u8 { _ = io; log.debug("Awaiting connection message", .{}); diff --git a/src/c_api.zig b/src/c_api.zig new file mode 100644 index 0000000..8f861b9 --- /dev/null +++ b/src/c_api.zig @@ -0,0 +1,89 @@ +const std = @import("std"); +const zaprus = @import("zaprus"); + +// Opaque types for C API +const ZaprusClient = opaque {}; +const ZaprusConnection = opaque {}; + +const alloc = std.heap.c_allocator; +const io = std.Io.Threaded.global_single_threaded.io(); + +export fn zaprus_init_client() ?*ZaprusClient { + const client = alloc.create(zaprus.Client) catch return null; + client.* = zaprus.Client.init() catch { + alloc.destroy(client); + return null; + }; + return @ptrCast(client); +} + +export fn zaprus_deinit_client(client: ?*ZaprusClient) void { + const c: ?*zaprus.Client = @ptrCast(@alignCast(client)); + if (c) |zc| { + zc.deinit(); + alloc.destroy(zc); + } +} + +export fn zaprus_client_send_relay( + client: ?*ZaprusClient, + payload: [*c]const u8, + payload_len: usize, + dest: [*c]const u8, +) c_int { + const c: ?*zaprus.Client = @ptrCast(@alignCast(client)); + const zc = c orelse return 1; + + zc.sendRelay(io, payload[0..payload_len], dest[0..4].*) catch return 1; + return 0; +} + +export fn zaprus_connect( + client: ?*ZaprusClient, + payload: [*c]const u8, + payload_len: usize, +) ?*ZaprusConnection { + const c: ?*zaprus.Client = @ptrCast(@alignCast(client)); + const zc = c orelse return null; + + const connection = alloc.create(zaprus.Connection) catch return null; + connection.* = zc.connect(io, payload[0..payload_len]) catch { + alloc.destroy(connection); + return null; + }; + return @ptrCast(connection); +} + +export fn zaprus_deinit_connection(connection: ?*ZaprusConnection) void { + const c: ?*zaprus.Connection = @ptrCast(@alignCast(connection)); + if (c) |zc| { + zc.deinit(); + alloc.destroy(zc); + } +} + +export fn zaprus_connection_next( + connection: ?*ZaprusConnection, + out: [*c]u8, + capacity: usize, + out_len: *usize, +) c_int { + const c: ?*zaprus.Connection = @ptrCast(@alignCast(connection)); + const zc = c orelse return 1; + + const result = zc.next(io, out[0..capacity]) catch return 1; + out_len.* = result.len; + return 0; +} + +export fn zaprus_connection_send( + connection: ?*ZaprusConnection, + payload: [*c]const u8, + payload_len: usize, +) c_int { + const c: ?*zaprus.Connection = @ptrCast(@alignCast(connection)); + const zc = c orelse return 1; + + zc.send(io, payload[0..payload_len]) catch return 1; + return 0; +} diff --git a/src/main.zig b/src/main.zig index 3fcdeda..2cbcb24 100644 --- a/src/main.zig +++ b/src/main.zig @@ -134,6 +134,7 @@ pub fn main(init: std.process.Init) !void { var w: Writer = .fixed(&init_con_buf); try w.print("{b64}", .{flags.connect.?}); var connection = try client.connect(init.io, w.buffered()); + defer connection.deinit(); log.debug("Connection started", .{}); -- cgit