54 Commits

Author SHA1 Message Date
56e72928c6 fix use after free 2025-05-10 21:46:53 -04:00
a80c9abfe7 Attempt to base64 encode the connection payload
For some reason I am still getting this:

2025/05/10 16:37:06 Error decoding message: SGVsbG8gZGFya25lc3MgbXkgb2xkIGZyaWVuZA==::53475673624738675a4746796132356c63334d6762586b676232786b49475a79615756755a413d3daaaa
2025-05-10 21:46:53 -04:00
245dab4909 Use slice for init, and add better error sets.
The slice sets us avoid allocating within the init function.
This means init can't fail, and it also makes it easier to stack allocate messages (slice an array buffer, instead of creating a stack allocator).
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 513 additions and 95 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",

152
src/Client.zig Normal file
View File

@@ -0,0 +1,152 @@
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_bytes = try allocator.alignedAlloc(
u8,
@alignOf(SaprusMessage),
try SaprusMessage.lengthForPayloadLength(
.relay,
base64Enc.calcSize(payload.len),
),
);
defer allocator.free(msg_bytes);
const msg: *SaprusMessage = .init(.relay, msg_bytes);
const relay = (try msg.getSaprusTypePayload()).relay;
relay.dest = dest;
_ = base64Enc.encode(relay.getPayload(), payload);
try broadcastSaprusMessage(msg, 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_bytes = try allocator.alignedAlloc(
u8,
@alignOf(SaprusMessage),
try SaprusMessage.lengthForPayloadLength(
.connection,
base64Enc.calcSize(payload.len),
),
);
const msg: *SaprusMessage = .init(.connection, msg_bytes);
const connection = (try msg.getSaprusTypePayload()).connection;
connection.src_port = initial_port;
connection.dest_port = dest_port;
_ = base64Enc.encode(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;
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);
defer allocator.free(msg.asBytes());
var response_buf: [4096]u8 align(@alignOf(SaprusMessage)) = undefined;
_ = 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[0..len]});
initial_conn_res = SaprusMessage.init(.connection, 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 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,113 +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| d.allocator() else std.heap.smp_allocator; const gpa = if (is_debug) dba.allocator() else std.heap.smp_allocator;
const msg = SaprusMessage{ // CLI parsing adapted from the example here
.relay = .{ // https://github.com/Hejsil/zig-clap/blob/e47028deaefc2fb396d3d9e9f7bd776ae0b2a43a/README.md#examples
.header = .{ .dest = .{ 255, 255, 255, 255 } },
.payload = "Hello darkness my old friend", // 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_bytes = try msg.toBytes(allocator); try SaprusClient.init();
defer allocator.free(msg_bytes); defer SaprusClient.deinit();
try network.init(); if (res.args.help != 0) {
defer network.deinit(); return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
var sock = try network.Socket.create(.ipv4, .udp); if (res.args.relay) |r| {
defer sock.close(); 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;
}
try sock.setBroadcast(true); return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
// Bind to 0.0.0.0:0 fn parseDest(in: ?[]const u8) [4]u8 {
const bind_addr = network.EndPoint{ if (in) |dest| {
.address = network.Address{ .ipv4 = network.Address.IPv4.any }, if (dest.len <= 4) {
.port = 0, var res: [4]u8 = @splat(0);
}; @memcpy(res[0..dest.len], dest);
return res;
}
const dest_addr = network.EndPoint{ const addr = std.net.Ip4Address.parse(dest, 0) catch return "FAIL".*;
.address = network.Address{ .ipv4 = network.Address.IPv4.broadcast }, return @bitCast(addr.sa.addr);
.port = 8888, }
}; return "zap\x00".*;
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");

254
src/message.zig Normal file
View File

@@ -0,0 +1,254 @@
/// 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 MessageTypeError = error{
NotImplementedSaprusType,
UnknownSaprusType,
};
pub const MessageParseError = MessageTypeError || error{
InvalidMessage,
};
// 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 = {},
/// Takes a byte slice, and returns a Message struct backed by the slice.
/// This properly initializes the top level headers within the slice.
pub fn init(@"type": PacketType, bytes: []align(@alignOf(Self)) u8) *Self {
std.debug.assert(bytes.len >= @sizeOf(Self));
const res: *Self = @ptrCast(bytes.ptr);
res.type = @"type";
res.length = @intCast(bytes.len - @sizeOf(Self));
return res;
}
pub fn lengthForPayloadLength(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 {
std.debug.assert(payload_len < std.math.maxInt(u16));
const header_size = @sizeOf(switch (@"type") {
.relay => Relay,
.connection => Connection,
.file_transfer => return MessageTypeError.NotImplementedSaprusType,
else => return MessageTypeError.UnknownSaprusType,
});
return @intCast(payload_len + @sizeOf(Self) + header_size);
}
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) MessageTypeError!(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 => MessageTypeError.NotImplementedSaprusType,
else => MessageTypeError.UnknownSaprusType,
};
}
pub fn nativeFromNetworkEndian(self: *Self) MessageTypeError!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) MessageTypeError!void {
try switch (try self.getSaprusTypePayload()) {
.relay => {},
.connection => |*con| con.*.networkFromNativeEndian(),
.file_transfer => MessageTypeError.NotImplementedSaprusType,
else => MessageTypeError.UnknownSaprusType,
};
self.type = @enumFromInt(nativeToBig(
@typeInfo(@TypeOf(self.type)).@"enum".tag_type,
@intFromEnum(self.type),
));
self.length = nativeToBig(@TypeOf(self.length), self.length);
}
/// Deprecated.
/// If I need the bytes, I should just pass around the slice that is backing this to begin with.
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 "Round trip Relay toBytes and fromBytes" {
const gpa = std.testing.allocator;
const msg = Message{
.relay = .{
.header = .{ .dest = .{ 255, 255, 255, 255 } },
.payload = "Hello darkness my old friend",
},
};
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
try std.testing.expectEqualDeep(msg, from_bytes);
}
test "Round trip Connection toBytes and fromBytes" {
const gpa = std.testing.allocator;
const msg = Message{
.connection = .{
.header = .{
.src_port = 0,
.dest_port = 0,
},
.payload = "Hello darkness my old friend",
},
};
const to_bytes = try msg.toBytes(gpa);
defer gpa.free(to_bytes);
const from_bytes = try Message.fromBytes(to_bytes, gpa);
defer from_bytes.deinit(gpa);
try std.testing.expectEqualDeep(msg, from_bytes);
}
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");