summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobby Zambito <contact@robbyzambito.me>2026-01-24 16:13:35 -0500
committerRobby Zambito <contact@robbyzambito.me>2026-01-24 17:16:06 -0500
commit16fd65e281776bd27d9aefdcfb39fa8b8f7a9fba (patch)
treede77af954753512ccca8d030f8637048696ef4b6
parent8965a4d5d4ce494ae45ea28186379ea7aea9d2e1 (diff)
Add C API
-rw-r--r--build.zig17
-rw-r--r--include/zaprus.h33
-rw-r--r--src/Connection.zig4
-rw-r--r--src/c_api.zig89
-rw-r--r--src/main.zig1
5 files changed, 144 insertions, 0 deletions
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 <stdint.h>
+#include <stdlib.h>
+
+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", .{});