52 Commits

Author SHA1 Message Date
efcbfe1c7b going off the deep end with with generic message type 2025-05-10 21:46:53 -04:00
cde5c3626c 2025-05-10 21:46:53 -04:00
e84d1a2300 2025-05-10 21:46:53 -04:00
1b7d9bbb1a Remove bytesAsValueUnchecked
Callers can instead use std.mem.bytesAsValue directly.
2025-05-10 21:46:53 -04:00
1512ec1a86 Cleanup asBytes and test it 2025-05-10 21:46:53 -04:00
f1dce257be Simplify init interface 2025-05-10 21:46:53 -04:00
bcab1e4d00 2025-05-10 21:46:53 -04:00
0e8f016978 Align the bytes instead of the struct 2025-05-10 21:46:53 -04:00
fc53e87389 2025-05-10 21:46:53 -04:00
cbf554e853 2025-05-10 21:46:53 -04:00
775212013f 2025-05-10 21:46:53 -04:00
339ac5cfe5 2025-05-10 21:46:53 -04:00
eacfaffb6b 2025-05-10 21:46:53 -04:00
1731b2e643 2025-05-10 21:46:53 -04:00
dae66a0039 Starting real connections 2025-05-10 21:46:53 -04:00
683a2015b0 Use FAIL as the default dest if unable to parse 2025-04-27 18:03:06 -04:00
c34748dab3 Add CLI to specify dest for relay messages
The dest can be specified as a 4 char ASCII string, or as an IPv4 address.
2025-04-19 22:26:32 -04:00
5b88f0df6a Rename Saprus to Client internally 2025-04-14 07:13:09 -04:00
23f7ad8f94 Break out the impl to a lib
This will make it easier to make a C library.
2025-04-13 17:14:18 -04:00
8779b29149 Do some things for invy 2025-04-13 16:41:20 -04:00
0f4a7c9bcd Use dynamic array instead of arraylist
we know the size (assuming the len is correct) so we can preallocate the whole array
2025-04-06 21:33:00 -04:00
2302e6930d Depend on clap the same way as network 2025-04-06 13:08:09 -04:00
935e552a59 Don't print help after sending message 2025-04-06 13:08:09 -04:00
3424217539 Add comment for cli example reference 2025-04-06 13:08:09 -04:00
dcb962593d Allow caller to specify what kind of message to send with arg 2025-04-06 13:08:09 -04:00
c2f8c77c52 Nicer message for no response 2025-04-06 13:08:09 -04:00
6eef36e78a Handle network blocking well 2025-04-06 13:08:09 -04:00
8278648ba9 Don't use multi threading where it is not required 2025-04-06 13:08:09 -04:00
6b38d5bb74 Last handshake packet uses non 8888 port 2025-04-06 13:08:09 -04:00
ec94e85ab9 Complete the handshake 2025-04-06 13:08:09 -04:00
a2072436aa Receive and print the response from the sentinel 2025-04-06 13:08:09 -04:00
774d52ad59 Initial awaiting for handshake response
seems like i might be getting my own initial connection?
I get a response even without the sentinal running.
2025-04-06 13:08:09 -04:00
b219fdc1f5 Start proper connection handshake 2025-04-06 13:08:09 -04:00
433a97fe5a Move binary back to zaprus
Also clean up the args for the aux functions by computing the type instead of passing it
2025-04-06 13:08:09 -04:00
3c935698aa Correctly handle the endiness and packedness of the the header reading and writing 2025-04-06 13:08:09 -04:00
bc75e86904 Write the header as a packed int
this seems like the best way to do it.
2025-04-06 13:08:09 -04:00
ac5511e9bd Not using writer
Use direct mem copying into the buf instead of writing to it with the writer interface.
Probably better to use the writer actually, since this suffers from the same issue with the extra two null bytes.
2025-04-06 13:08:09 -04:00
efcd866d6c Initial testing of connection message 2025-04-06 13:08:09 -04:00
7b07520adb Move sendRelay logic to Saprus struct 2025-04-06 13:08:09 -04:00
448e900004 Break relay into a specific program 2025-04-06 13:08:09 -04:00
93161ff4bd Move types to their own file
Make it so I can specify the payload message
2025-04-06 13:08:09 -04:00
58ed9048da GREATLY improve clarity 2025-04-06 13:08:09 -04:00
88b2734886 Move dba to top level scope
This makes it so references to it are lazily compiled, so I can throw a compileError if it is referenced in the wrong mode.
2025-04-06 13:08:09 -04:00
e24220c98b add comments!!!!! 2025-04-06 13:08:09 -04:00
b1def25c69 move aux funcs back into the union
also move the body of the base64 handling back to the only place it is used now
2025-04-06 13:08:09 -04:00
d60d1fd335 Remove redundant comptime
Move edge together at the end of the switch
2025-04-06 13:08:09 -04:00
ef36894c70 Properly initialize the SaprusMessage fromBytes
Will actually use the provided packet type inline.
2025-04-06 13:08:09 -04:00
a0f6e08794 dedup some
Still need to clean up fromBytesAux
2025-04-06 13:08:09 -04:00
53d7e62054 Break out encoding and decoding into its own functions 2025-04-06 13:08:09 -04:00
3bd955f0bf Add decoding connection messages 2025-04-06 13:08:09 -04:00
6b8d2ec1bd Add packet decoding
Big bug where I was setting the payload length do be the pre-encoded size rather than the encoded size.
2025-04-06 13:08:09 -04:00
2d1beef44a Small cleanup
Rename allocator to gpa (general purpose allocator) and move DebugAllocator type out of main
2025-04-06 13:08:09 -04:00
7 changed files with 584 additions and 102 deletions

View File

@@ -15,6 +15,12 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize. // set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const lib_mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// We will also create a module for our other entry point, 'main.zig'. // We will also create a module for our other entry point, 'main.zig'.
const exe_mod = b.createModule(.{ const exe_mod = b.createModule(.{
// `root_source_file` is the Zig "entry point" of the module. If a module // `root_source_file` is the Zig "entry point" of the module. If a module
@@ -26,7 +32,19 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
exe_mod.addImport("network", b.dependency("network", .{}).module("network")); 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(.{
.linkage = .static,
.name = "zaprus",
.root_module = lib_mod,
});
b.installArtifact(lib);
// This creates another `std.Build.Step.Compile`, but this one builds an executable // This creates another `std.Build.Step.Compile`, but this one builds an executable
// rather than a static library. // rather than a static library.

View File

@@ -40,6 +40,14 @@
.url = "https://github.com/ikskuh/zig-network/archive/c76240d2240711a3dcbf1c0fb461d5d1f18be79a.zip", .url = "https://github.com/ikskuh/zig-network/archive/c76240d2240711a3dcbf1c0fb461d5d1f18be79a.zip",
.hash = "network-0.1.0-AAAAAOwlAQAQ6zKPUrsibdpGisxld9ftUKGdMvcCSpaj", .hash = "network-0.1.0-AAAAAOwlAQAQ6zKPUrsibdpGisxld9ftUKGdMvcCSpaj",
}, },
.clap = .{
.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 = .{ .paths = .{
"build.zig", "build.zig",

137
src/Client.zig Normal file
View File

@@ -0,0 +1,137 @@
const base64Enc = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
const base64Dec = std.base64.Base64Decoder.init(std.base64.standard_alphabet_chars, '=');
var rand: ?Random = null;
pub fn init() !void {
var prng = Random.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try posix.getrandom(mem.asBytes(&seed));
break :blk seed;
});
rand = prng.random();
try network.init();
}
pub fn deinit() void {
network.deinit();
}
fn broadcastSaprusMessage(msg: *SaprusMessage, udp_port: u16) !void {
if (false) {
var foo: gcat.nic.RawSocket = try .init("enp7s0"); // /proc/net/dev
defer foo.deinit();
}
const msg_bytes = msg.asBytes();
try msg.networkFromNativeEndian();
defer msg.nativeFromNetworkEndian() catch unreachable;
var sock = try network.Socket.create(.ipv4, .udp);
defer sock.close();
try sock.setBroadcast(true);
// Bind to 0.0.0.0:0
const bind_addr = network.EndPoint{
.address = network.Address{ .ipv4 = network.Address.IPv4.any },
.port = 0,
};
const dest_addr = network.EndPoint{
.address = network.Address{ .ipv4 = network.Address.IPv4.broadcast },
.port = udp_port,
};
try sock.bind(bind_addr);
_ = try sock.sendTo(dest_addr, msg_bytes);
}
pub fn sendRelay(payload: []const u8, dest: [4]u8, allocator: Allocator) !void {
const msg: *SaprusMessageNew(.relay) = try .init(
allocator,
@intCast(base64Enc.calcSize(payload.len)),
);
defer msg.deinit(allocator);
msg.dest = dest;
_ = base64Enc.encode(msg.getPayload(), payload);
try broadcastSaprusMessage(try SaprusMessage.bytesAsValue(msg.asBytes()), 8888);
}
fn randomPort() u16 {
var p: u16 = 0;
if (rand) |r| {
p = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
return p;
}
pub fn sendInitialConnection(payload: []const u8, initial_port: u16, allocator: Allocator) !*SaprusMessage {
const dest_port = randomPort();
const msg: *SaprusMessage = try .init(allocator, .connection, @intCast(payload.len));
defer msg.deinit(allocator);
const connection = (try msg.getSaprusTypePayload()).connection;
connection.src_port = initial_port;
connection.dest_port = dest_port;
@memcpy(connection.getPayload(), payload);
try broadcastSaprusMessage(msg, 8888);
return msg;
}
pub fn connect(payload: []const u8, allocator: Allocator) !?SaprusConnection {
var initial_port: u16 = 0;
if (rand) |r| {
initial_port = r.intRangeAtMost(u16, 1024, 65000);
} else unreachable;
var initial_conn_res: ?SaprusMessage = null;
errdefer if (initial_conn_res) |*c| c.deinit(allocator);
var sock = try network.Socket.create(.ipv4, .udp);
defer sock.close();
// Bind to 255.255.255.255:8888
const bind_addr = network.EndPoint{
.address = network.Address{ .ipv4 = network.Address.IPv4.broadcast },
.port = 8888,
};
// timeout 1s
try sock.setReadTimeout(1 * std.time.us_per_s);
try sock.bind(bind_addr);
const msg = try sendInitialConnection(payload, initial_port, allocator);
var response_buf: [4096]u8 align(4) = @splat(0);
_ = try sock.receive(&response_buf); // Ignore message that I sent.
const len = try sock.receive(&response_buf);
std.debug.print("response bytes: {x}\n", .{response_buf});
initial_conn_res = (try SaprusMessage.bytesAsValue(response_buf[0..len])).*;
// Complete handshake after awaiting response
try broadcastSaprusMessage(msg, randomPort());
if (false) {
return initial_conn_res.?;
}
return null;
}
const SaprusMessage = @import("message.zig").Message;
const SaprusMessageNew = @import("message.zig").MessageNew;
const SaprusConnection = @import("Connection.zig");
const std = @import("std");
const Random = std.Random;
const posix = std.posix;
const mem = std.mem;
const network = @import("network");
const gcat = @import("gatorcat");
const Allocator = mem.Allocator;

0
src/Connection.zig Normal file
View File

View File

@@ -1,119 +1,95 @@
const is_debug = builtin.mode == .Debug; const is_debug = builtin.mode == .Debug;
const base64 = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, '=');
const SaprusPacketType = enum(u16) { /// This creates a debug allocator that can only be referenced in debug mode.
relay = 0x003C, /// You should check for is_debug around every reference to dba.
file_transfer = 0x8888, var dba: DebugAllocator =
connection = 0x00E9, if (is_debug)
}; DebugAllocator.init
else
const SaprusConnectionOptions = packed struct(u8) { @compileError("Should not use debug allocator in release mode");
opt1: bool = false,
opt2: bool = false,
opt3: bool = false,
opt4: bool = false,
opt5: bool = false,
opt6: bool = false,
opt7: bool = false,
opt8: bool = false,
};
const SaprusMessage = union(SaprusPacketType) {
const Relay = struct {
header: packed struct {
dest: @Vector(4, u8),
},
payload: []const u8,
};
const Connection = struct {
header: packed struct {
src_port: u16,
dest_port: u16,
seq_num: u32 = 0,
msg_id: u32 = 0,
reserved: u8 = 0,
options: SaprusConnectionOptions = .{},
},
payload: []const u8,
};
relay: Relay,
file_transfer: void, // unimplemented
connection: Connection,
fn toBytes(self: SaprusMessage, allocator: Allocator) ![]u8 {
var buf = std.ArrayList(u8).init(allocator);
const w = buf.writer();
try w.writeInt(u16, @intFromEnum(self), .big);
switch (self) {
.relay => |r| {
try w.writeStructEndian(r.header, .big);
try w.writeInt(u16, @intCast(r.payload.len), .big);
try base64.encodeWriter(w, r.payload);
},
.file_transfer => unreachable,
.connection => |c| {
try w.writeStructEndian(c.header, .big);
try w.writeInt(u16, @intCast(c.payload.len), .big);
try base64.encodeWriter(w, c.payload);
},
}
return buf.toOwnedSlice();
}
};
pub fn main() !void { pub fn main() !void {
const DBA = std.heap.DebugAllocator(.{}); defer if (is_debug) {
var dba: ?DBA = if (comptime is_debug) DBA.init else null; _ = dba.deinit();
defer if (dba) |*d| {
_ = d.deinit();
}; };
var allocator = if (dba) |*d| const gpa = if (is_debug) dba.allocator() else std.heap.smp_allocator;
d.allocator()
else blk: { // CLI parsing adapted from the example here
var buf: [128]u8 = undefined; // https://github.com/Hejsil/zig-clap/blob/e47028deaefc2fb396d3d9e9f7bd776ae0b2a43a/README.md#examples
var fba = std.heap.FixedBufferAllocator.init(&buf);
break :blk fba.allocator(); // First we specify what parameters our program can take.
// We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`.
const params = comptime clap.parseParamsComptime(
\\-h, --help Display this help and exit.
\\-r, --relay <str> A relay message to send.
\\-d, --dest <str> An IPv4 or <= 4 ASCII byte string.
\\-c, --connect <str> A connection message to send.
\\
);
// Initialize our diagnostics, which can be used for reporting useful errors.
// This is optional. You can also pass `.{}` to `clap.parse` if you don't
// care about the extra information `Diagnostics` provides.
var diag = clap.Diagnostic{};
var res = clap.parse(clap.Help, &params, clap.parsers.default, .{
.diagnostic = &diag,
.allocator = gpa,
}) catch |err| {
// Report useful error and exit.
diag.report(std.io.getStdErr().writer(), err) catch {};
return err;
}; };
defer res.deinit();
const msg = SaprusMessage{ try SaprusClient.init();
.relay = .{ defer SaprusClient.deinit();
.header = .{ .dest = .{ 255, 255, 255, 255 } },
.payload = "Hello darkness my old friend", if (res.args.help != 0) {
}, return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
if (res.args.relay) |r| {
const dest = parseDest(res.args.dest);
try SaprusClient.sendRelay(
if (r.len > 0) r else "Hello darkness my old friend",
dest,
gpa,
);
// 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) {
error.WouldBlock => null,
else => return err,
}; };
return;
}
const msg_bytes = try msg.toBytes(allocator); return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
defer allocator.free(msg_bytes); }
try network.init(); fn parseDest(in: ?[]const u8) [4]u8 {
defer network.deinit(); if (in) |dest| {
if (dest.len <= 4) {
var res: [4]u8 = @splat(0);
@memcpy(res[0..dest.len], dest);
return res;
}
var sock = try network.Socket.create(.ipv4, .udp); const addr = std.net.Ip4Address.parse(dest, 0) catch return "FAIL".*;
defer sock.close(); return @bitCast(addr.sa.addr);
}
try sock.setBroadcast(true); return "zap\x00".*;
// Bind to 0.0.0.0:0
const bind_addr = network.EndPoint{
.address = network.Address{ .ipv4 = network.Address.IPv4.any },
.port = 0,
};
const dest_addr = network.EndPoint{
.address = network.Address{ .ipv4 = network.Address.IPv4.broadcast },
.port = 8888,
};
try sock.bind(bind_addr);
_ = try sock.sendTo(dest_addr, msg_bytes);
} }
const builtin = @import("builtin"); const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const DebugAllocator = std.heap.DebugAllocator(.{});
const ArrayList = std.ArrayList;
const network = @import("network"); const zaprus = @import("zaprus");
const SaprusClient = zaprus.Client;
const SaprusMessage = zaprus.Message;
const clap = @import("clap");

339
src/message.zig Normal file
View File

@@ -0,0 +1,339 @@
/// Type tag for Message union.
/// This is the first value in the actual packet sent over the network.
pub const PacketType = enum(u16) {
relay = 0x003C,
file_transfer = 0x8888,
connection = 0x00E9,
_,
};
/// Reserved option values.
/// Currently unused.
pub const ConnectionOptions = packed struct(u8) {
opt1: bool = false,
opt2: bool = false,
opt3: bool = false,
opt4: bool = false,
opt5: bool = false,
opt6: bool = false,
opt7: bool = false,
opt8: bool = false,
};
pub const Error = error{
NotImplementedSaprusType,
UnknownSaprusType,
InvalidMessage,
};
pub fn MessageNew(comptime packet_type: PacketType) type {
comptime {
if (packet_type == .file_transfer)
@compileError("File transfer not implemented");
if (packet_type != .relay and packet_type != .connection)
@compileError("Unkown message type");
}
return packed struct {
const Self = @This();
const SelfBytes = []align(@alignOf(Self)) u8;
const Relay = struct {
pub fn getPayload(self: *Self) []u8 {
return @as([*]align(@alignOf(Self)) u8, @ptrCast(&self.payload))[0 .. self.length - 4];
}
};
const Connection = packed struct {
pub fn getPayload(self: Self) []u8 {
return @as([*]u8, &self.payload)[0 .. self.length - 4];
}
};
type: PacketType = packet_type,
length: u16,
// Relay
dest: if (packet_type == .relay) @Vector(4, u8) else void,
// Connection
src_port: if (packet_type == .connection) u16 else void, // random number > 1024
dest_port: if (packet_type == .connection) u16 else void, // random number > 1024
seq_num: if (packet_type == .connection) u32 else void,
msg_id: if (packet_type == .connection) u32 else void,
reserved: if (packet_type == .connection) u8 else void,
options: if (packet_type == .connection) ConnectionOptions else void = if (packet_type == .connection) .{} else {},
// Relay or Connection
payload: switch (packet_type) {
.relay, .connection => void,
else => noreturn,
},
pub usingnamespace switch (packet_type) {
.relay => Relay,
.connection => Connection,
.file_transfer => @compileError("File Transfer message type not implemented"),
else => @compileError("Unknown message type"),
};
pub fn init(allocator: Allocator, payload_len: u16) !*Self {
const size = payload_len + @sizeOf(Self);
const bytes = try allocator.alignedAlloc(u8, @alignOf(Self), size);
const res: *Self = @ptrCast(bytes.ptr);
res.type = packet_type;
res.length = payload_len;
return res;
}
pub fn deinit(self: *Self, allocator: Allocator) void {
allocator.free(self.asBytes());
}
pub fn nativeFromNetworkEndian(self: *Self) void {
self.type = @enumFromInt(bigToNative(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = bigToNative(@TypeOf(self.length), self.length);
if (packet_type == .connection) {
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);
}
}
pub fn networkFromNativeEndian(self: *Self) void {
self.type = @enumFromInt(bigToNative(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = bigToNative(@TypeOf(self.length), self.length);
if (packet_type == .connection) {
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);
}
}
pub fn asBytes(self: *Self) SelfBytes {
const size = @sizeOf(Self) + self.length;
return @as([*]align(@alignOf(Self)) u8, @ptrCast(self))[0..size];
}
};
}
test MessageNew {
comptime for (@typeInfo(MessageNew(.connection)).@"struct".decls) |field| {
@compileLog(field);
};
}
// pub fn bytesAsMessage(bytes: []const u8) !*Self {
// const res = std.mem.bytesAsValue(Self, 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,
// };
// }
// ZERO COPY STUFF
// &payload could be a void value that is treated as a pointer to a [*]u8
/// All Saprus messages
pub const Message = packed struct {
const Relay = packed struct {
dest: @Vector(4, u8),
payload: void,
pub fn getPayload(self: *align(1) Relay) []u8 {
const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16));
return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @sizeOf(Relay)];
}
};
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) 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) 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();
const SelfBytes = []align(@alignOf(Self)) u8;
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,
.file_transfer => return Error.NotImplementedSaprusType,
else => return Error.UnknownSaprusType,
});
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(1) Relay {
return std.mem.bytesAsValue(Relay, &self.bytes);
}
fn getConnection(self: *Self) *align(1) Connection {
return std.mem.bytesAsValue(Connection, &self.bytes);
}
pub fn getSaprusTypePayload(self: *Self) Error!(union(PacketType) {
relay: *align(1) Relay,
file_transfer: void,
connection: *align(1) 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);
errdefer {
// If the payload specific headers fail, revert the top level header values
self.type = @enumFromInt(nativeToBig(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = nativeToBig(@TypeOf(self.length), self.length);
}
switch (try self.getSaprusTypePayload()) {
.relay => {},
.connection => |*con| 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 bytesAsValue(bytes: SelfBytes) !*Self {
const res = std.mem.bytesAsValue(Self, 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) SelfBytes {
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 Message
const msg: *Message = try .init(gpa, .relay, payload.len);
defer msg.deinit(gpa);
{
// Set the message values
{
// These are both set by the init call.
// msg.type = .relay;
// msg.length = payload_len;
}
const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = .{ 1, 2, 3, 4 };
@memcpy(relay.getPayload(), payload);
}
{
const bytes = msg.asBytes();
// Print the message as hex using the network byte order
try msg.networkFromNativeEndian();
// We know the error from nativeFromNetworkEndian is unreachable because
// it would have returned an error from networkFromNativeEndian.
defer msg.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 msg.getSaprusTypePayload()).connection});
}
try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes()));
}
const std = @import("std");
const Allocator = std.mem.Allocator;
const asBytes = std.mem.asBytes;
const nativeToBig = std.mem.nativeToBig;
const bigToNative = std.mem.bigToNative;
test {
std.testing.refAllDeclsRecursive(@This());
}

4
src/root.zig Normal file
View File

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