1 Commits

Author SHA1 Message Date
fe26cb002d Initial C api 2025-04-19 23:59:06 -04:00
9 changed files with 116 additions and 228 deletions

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const Step = std.Build.Step;
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
@@ -33,18 +34,34 @@ pub fn build(b: *std.Build) void {
});
lib_mod.addImport("network", b.dependency("network", .{}).module("network"));
lib_mod.addImport("gatorcat", b.dependency("gatorcat", .{}).module("gatorcat"));
exe_mod.addImport("zaprus", lib_mod);
exe_mod.addImport("clap", b.dependency("clap", .{}).module("clap"));
const lib = b.addLibrary(.{
const static_lib = b.addLibrary(.{
.linkage = .static,
.name = "zaprus",
.root_module = lib_mod,
});
b.installArtifact(lib);
b.installArtifact(static_lib);
const dynamic_lib = b.addLibrary(.{
.linkage = .dynamic,
.name = "zaprus",
.root_module = lib_mod,
});
b.installArtifact(dynamic_lib);
// C Headers
const c_header = b.addInstallFileWithDir(
b.path("include/zaprus.h"),
.header,
"zaprus.h",
);
b.getInstallStep().dependOn(&c_header.step);
// This creates another `std.Build.Step.Compile`, but this one builds an executable
// rather than a static library.

View File

@@ -44,10 +44,6 @@
.url = "git+https://github.com/Hejsil/zig-clap?ref=0.10.0#e47028deaefc2fb396d3d9e9f7bd776ae0b2a43a",
.hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0",
},
.gatorcat = .{
.url = "git+https://github.com/kj4tmp/gatorcat#bb1847f6c95852e7a0ec8c07870a948c171d5f98",
.hash = "gatorcat-0.3.2-WcrpTf1mBwDrmPaIhKCfLJO064v8Sjjn7DBq4CKZSgHH",
},
},
.paths = .{
"build.zig",

24
include/zaprus.h Normal file
View File

@@ -0,0 +1,24 @@
// client
int zaprus_init(void);
int zaprus_deinit(void);
int zaprus_send_relay(const char* payload, usize len, char[4] dest);
int zaprus_send_initial_connection(const char* payload, usize len, uint16_t initial_port);
struct SaprusMessage* zaprus_connect(const char* payload, usize len);
// message
struct SaprusMessage {
};
// ptr should be freed by the caller.
int zaprus_message_to_bytes(struct SaprusMessage msg, char** ptr, usize* len);
// Return value should be destroyed with zaprus_message_deinit.
struct SaprusMessage* zaprus_message_from_bytes(const char* bytes, usize len);
void zaprus_message_deinit(struct SaprusMessage* msg);

View File

@@ -76,10 +76,7 @@ pub fn sendInitialConnection(payload: []const u8, initial_port: u16, allocator:
return msg;
}
pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
var foo: gcat.nic.RawSocket = try .init("enp7s0"); // /proc/net/dev
defer foo.deinit();
pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusMessage {
var initial_port: u16 = 0;
if (rand) |r| {
initial_port = r.intRangeAtMost(u16, 1024, 65000);
@@ -112,14 +109,10 @@ pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
// Complete handshake after awaiting response
try broadcastSaprusMessage(msg, randomPort(), allocator);
if (false) {
return initial_conn_res.?;
}
return null;
return initial_conn_res;
}
const SaprusMessage = @import("message.zig").Message;
const SaprusConnection = @import("Connection.zig");
const std = @import("std");
const Random = std.Random;
@@ -127,6 +120,5 @@ const posix = std.posix;
const mem = std.mem;
const network = @import("network");
const gcat = @import("gatorcat");
const Allocator = mem.Allocator;

View File

56
src/c_api.zig Normal file
View File

@@ -0,0 +1,56 @@
// client
export fn zaprus_init() c_int {
SaprusClient.init() catch return 1;
return 0;
}
export fn zaprus_deinit() c_int {
SaprusClient.deinit();
return 0;
}
export fn zaprus_send_relay(payload: [*]const u8, len: usize, dest: [4]u8) c_int {
SaprusClient.sendRelay(payload[0..len], dest, allocator) catch return 1;
return 0;
}
export fn zaprus_send_initial_connection(payload: [*]const u8, len: usize, initial_port: u16) c_int {
SaprusClient.sendInitialConnection(payload[0..len], initial_port, allocator) catch return 1;
return 0;
}
export fn zaprus_connect(payload: [*]const u8, len: usize) ?*SaprusMessage {
return SaprusClient.connect(payload[0..len], allocator) catch null;
}
// message
/// ptr should be freed by the caller.
export fn zaprus_message_to_bytes(msg: SaprusMessage, ptr: *[*]u8, len: *usize) c_int {
const bytes = msg.toBytes(allocator) catch return 1;
ptr.* = bytes[0..].*;
len.* = bytes.len;
return 0;
}
/// Return value should be destroyed with zaprus_message_deinit.
export fn zaprus_message_from_bytes(bytes: [*]const u8, len: usize) ?*SaprusMessage {
return SaprusMessage.fromBytes(bytes[0..len], allocator) catch null;
}
export fn zaprus_message_deinit(msg: *SaprusMessage) void {
msg.deinit(allocator);
}
const std = @import("std");
const zaprus = @import("./root.zig");
const SaprusClient = zaprus.Client;
const SaprusMessage = zaprus.Message;
const allocator = std.heap.c_allocator;
test {
std.testing.refAllDeclsRecursively(@This());
}

View File

@@ -50,7 +50,7 @@ pub fn main() !void {
}
if (res.args.relay) |r| {
const dest = parseDest(res.args.dest);
const dest = parseDest(res.args.dest) catch .{ 70, 70, 70, 70 };
try SaprusClient.sendRelay(
if (r.len > 0) r else "Hello darkness my old friend",
dest,
@@ -59,16 +59,23 @@ pub fn main() !void {
// std.debug.print("Sent: {s}\n", .{r});
return;
} else if (res.args.connect) |c| {
_ = SaprusClient.connect(if (c.len > 0) c else "Hello darkness my old friend", gpa) catch |err| switch (err) {
const conn_res: ?SaprusMessage = SaprusClient.connect(if (c.len > 0) c else "Hello darkness my old friend", gpa) catch |err| switch (err) {
error.WouldBlock => null,
else => return err,
};
defer if (conn_res) |r| r.deinit(gpa);
if (conn_res) |r| {
std.debug.print("{s}\n", .{r.connection.payload});
} else {
std.debug.print("No response from connection request\n", .{});
}
return;
}
return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
fn parseDest(in: ?[]const u8) [4]u8 {
fn parseDest(in: ?[]const u8) ![4]u8 {
if (in) |dest| {
if (dest.len <= 4) {
var res: [4]u8 = @splat(0);
@@ -76,10 +83,10 @@ fn parseDest(in: ?[]const u8) [4]u8 {
return res;
}
const addr = std.net.Ip4Address.parse(dest, 0) catch return "FAIL".*;
const addr = try std.net.Ip4Address.parse(dest, 0);
return @bitCast(addr.sa.addr);
}
return "zap\x00".*;
return .{ 70, 70, 70, 70 };
}
const builtin = @import("builtin");

View File

@@ -26,207 +26,8 @@ pub const ConnectionOptions = packed struct(u8) {
pub const Error = error{
NotImplementedSaprusType,
UnknownSaprusType,
InvalidMessage,
};
// ZERO COPY STUFF
// &payload could be a void value that is treated as a pointer to a [*]u8
pub const ZeroCopyMessage = packed struct {
const Relay = packed struct {
dest: @Vector(4, u8),
payload: void,
pub fn getPayload(self: *align(@alignOf(ZeroCopyMessage)) Relay) []u8 {
// Cast the 'self' pointer (which points to the Relay header,
// located at the same memory as the parent's 'bytes' field)
// to a pointer to void, as required by @fieldParentPtr for a void field.
// Preserve the known alignment.
const self_as_void_ptr: *align(@alignOf(ZeroCopyMessage)) void = @ptrCast(self);
// Cast the resulting *void pointer to the parent type *ZeroCopyMessage.
// This cast performs the necessary alignment check.
const parent: *ZeroCopyMessage = @alignCast(@fieldParentPtr("bytes", self_as_void_ptr));
// The 'length' field in the parent ZeroCopyMessage contains
// the size of the header (Relay) + payload length.
const total_len = parent.length;
// Payload length = total_len - size of the Relay header
const payload_len = total_len - @sizeOf(Relay);
// The payload starts immediately after the fixed fields of the Relay struct.
// The address of the 'payload' field represents this starting point.
const payload_start_ptr: [*]u8 = @ptrCast(&self.payload);
// Return a slice from the payload start address with the calculated length.
return payload_start_ptr[0..payload_len];
}
};
const Connection = packed struct {
src_port: u16, // random number > 1024
dest_port: u16, // random number > 1024
seq_num: u32 = 0,
msg_id: u32 = 0,
reserved: u8 = 0,
options: ConnectionOptions = .{},
payload: void,
pub fn getPayload(self: *align(1) Connection) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Connection)];
}
fn nativeFromNetworkEndian(self: *align(1) Connection) Error!void {
self.src_port = bigToNative(@TypeOf(self.src_port), self.src_port);
self.dest_port = bigToNative(@TypeOf(self.dest_port), self.dest_port);
self.seq_num = bigToNative(@TypeOf(self.seq_num), self.seq_num);
self.msg_id = bigToNative(@TypeOf(self.msg_id), self.msg_id);
}
fn networkFromNativeEndian(self: *align(1) Connection) Error!void {
self.src_port = nativeToBig(@TypeOf(self.src_port), self.src_port);
self.dest_port = nativeToBig(@TypeOf(self.dest_port), self.dest_port);
self.seq_num = nativeToBig(@TypeOf(self.seq_num), self.seq_num);
self.msg_id = nativeToBig(@TypeOf(self.msg_id), self.msg_id);
}
};
const Self = @This();
type: PacketType,
length: u16,
bytes: void = {},
pub fn init(allocator: Allocator, comptime @"type": PacketType, payload_len: u16) !*Self {
const header_size = @sizeOf(switch (@"type") {
.relay => Relay,
.connection => Connection,
else => return error.Bad,
});
const size = payload_len + @sizeOf(Self) + header_size;
const bytes = try allocator.alignedAlloc(u8, @alignOf(Self), size);
const res: *Self = @ptrCast(bytes.ptr);
res.type = @"type";
res.length = payload_len + header_size;
return res;
}
pub fn deinit(self: *Self, allocator: Allocator) void {
allocator.free(self.asBytes());
}
fn getRelay(self: *Self) *align(@alignOf(Self)) Relay {
return std.mem.bytesAsValue(Relay, &self.bytes);
}
fn getConnection(self: *Self) *align(@alignOf(Self)) Connection {
return std.mem.bytesAsValue(Connection, &self.bytes);
}
pub fn getSaprusTypePayload(self: *Self) Error!(union(PacketType) {
relay: *align(@alignOf(Self)) Relay,
file_transfer: void,
connection: *align(@alignOf(Self)) Connection,
}) {
return switch (self.type) {
.relay => .{ .relay = self.getRelay() },
.connection => .{ .connection = self.getConnection() },
.file_transfer => Error.NotImplementedSaprusType,
else => Error.UnknownSaprusType,
};
}
pub fn nativeFromNetworkEndian(self: *Self) Error!void {
self.type = @enumFromInt(bigToNative(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = bigToNative(@TypeOf(self.length), self.length);
switch (try self.getSaprusTypePayload()) {
.relay => {},
.connection => |*con| try con.*.nativeFromNetworkEndian(),
// We know other values are unreachable,
// because they would have returned an error from the switch condition.
else => unreachable,
}
}
pub fn networkFromNativeEndian(self: *Self) Error!void {
try switch (try self.getSaprusTypePayload()) {
.relay => {},
.connection => |*con| con.*.networkFromNativeEndian(),
.file_transfer => Error.NotImplementedSaprusType,
else => Error.UnknownSaprusType,
};
self.type = @enumFromInt(nativeToBig(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = nativeToBig(@TypeOf(self.length), self.length);
}
pub fn bytesAsValueUnchecked(bytes: []align(@alignOf(Self)) u8) *Self {
return std.mem.bytesAsValue(Self, bytes);
}
pub fn bytesAsValue(bytes: []align(@alignOf(Self)) u8) !*Self {
const res = bytesAsValueUnchecked(bytes);
return switch (res.type) {
.relay, .connection => if (bytes.len == res.length + @sizeOf(Self))
res
else
Error.InvalidMessage,
.file_transfer => Error.NotImplementedSaprusType,
else => Error.UnknownSaprusType,
};
}
pub fn asBytes(self: *Self) []align(@alignOf(Self)) u8 {
const size = @sizeOf(Self) + self.length;
return @as([*]align(@alignOf(Self)) u8, @ptrCast(self))[0..size];
}
};
test "testing variable length zero copy struct" {
const gpa = std.testing.allocator;
const payload = "Hello darkness my old friend";
// Create a view of the byte slice as a ZeroCopyMessage
const zcm: *ZeroCopyMessage = try .init(gpa, .relay, payload.len);
defer zcm.deinit(gpa);
std.debug.print("outer: {*}\n", .{zcm});
{
// Set the message values
{
// These are both set by the init call.
// zcm.type = .relay;
// zcm.length = payload_len;
}
const relay = (try zcm.getSaprusTypePayload()).relay;
relay.dest = .{ 1, 2, 3, 4 };
@memcpy(relay.getPayload(), payload);
}
{
const bytes = zcm.asBytes();
// Print the message as hex using the network byte order
try zcm.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian.
defer zcm.nativeFromNetworkEndian() catch unreachable;
std.debug.print("network bytes: {x}\n", .{bytes});
std.debug.print("bytes len: {d}\n", .{bytes.len});
}
if (false) {
// Illegal behavior
std.debug.print("{any}\n", .{(try zcm.getSaprusTypePayload()).connection});
}
try std.testing.expectEqualDeep(zcm, try ZeroCopyMessage.bytesAsValue(zcm.asBytes()));
}
/// All Saprus messages
pub const Message = union(PacketType) {
pub const Relay = struct {
@@ -369,7 +170,6 @@ const Allocator = std.mem.Allocator;
const asBytes = std.mem.asBytes;
const nativeToBig = std.mem.nativeToBig;
const bigToNative = std.mem.bigToNative;
test "Round trip Relay toBytes and fromBytes" {
const gpa = std.testing.allocator;
@@ -409,7 +209,3 @@ test "Round trip Connection toBytes and fromBytes" {
try std.testing.expectEqualDeep(msg, from_bytes);
}
test {
std.testing.refAllDeclsRecursive(@This());
}

View File

@@ -1,4 +1,4 @@
pub const Client = @import("Client.zig");
pub const Connection = @import("Connection.zig");
pub usingnamespace @import("message.zig");
pub usingnamespace @import("c_api.zig");