summaryrefslogtreecommitdiff
path: root/test/fuzz.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-14 14:10:28 -0300
committerGabriel Schneider <[email protected]>2026-09-14 14:20:25 -0300
commit5f24c4a2284a0af84fb9d116f7a39f6a58e42ae9 (patch)
tree8679763439492361fe99ea01f5190e5204fa590c /test/fuzz.zig
downloadcloud9-5f24c4a2284a0af84fb9d116f7a39f6a58e42ae9.tar.gz
cloud9-5f24c4a2284a0af84fb9d116f7a39f6a58e42ae9.zip
Implement base 9P2000 sessions, shared transports, and conformance probes
Diffstat (limited to 'test/fuzz.zig')
-rw-r--r--test/fuzz.zig70
1 files changed, 70 insertions, 0 deletions
diff --git a/test/fuzz.zig b/test/fuzz.zig
new file mode 100644
index 0000000..b7d9f21
--- /dev/null
+++ b/test/fuzz.zig
@@ -0,0 +1,70 @@
+//! Deterministic local robustness probes; reference implementations see valid traffic only.
+const std = @import("std");
+const c9 = @import("cloud9");
+pub fn main(init: std.process.Init) !void {
+ const args = try init.minimal.args.toSlice(init.arena.allocator());
+ const seed = if (args.len > 1) try std.fmt.parseInt(u64, args[1], 10) else 4200;
+ const iterations = if (args.len > 2) try std.fmt.parseInt(u32, args[2], 10) else 100000;
+ if (args.len > 3 or iterations > 10000000) return error.Arguments;
+ var prng: std.Random.DefaultPrng = .init(seed);
+ const random = prng.random();
+ var accepted: usize = 0;
+ var rejected: usize = 0;
+ var buffer: [8192]u8 = undefined;
+ var encoded: [8192]u8 = undefined;
+ var payload: [4096]u8 = undefined;
+ var server_in: [8192]u8 = undefined;
+ var server_out: [8192]u8 = undefined;
+ for (0..iterations) |_| {
+ random.bytes(&payload);
+ const data = payload[0..random.uintLessThan(usize, payload.len)];
+ const qid: c9.Qid = .{ .type = random.int(u8), .version = random.int(u32), .path = random.int(u64) };
+ const stat: c9.Stat = .{
+ .type = 0,
+ .dev = 0,
+ .qid = qid,
+ .mode = random.int(u32),
+ .atime = 0,
+ .mtime = 0,
+ .length = random.int(u64),
+ .name = "file",
+ .uid = "user",
+ .gid = "group",
+ .muid = "",
+ };
+ const messages = [_]c9.Msg{
+ .{ .tversion = .{ .msize = 8192, .version = "9P2000" } },
+ .{ .twalk = .{ .fid = 0, .newfid = 1, .nwname = 16, .wname = @splat("dir") } },
+ .{ .rwalk = .{ .nwqid = 16, .wqid = @splat(qid) } },
+ .{ .twrite = .{ .fid = random.int(u32), .offset = random.int(u64), .data = data } },
+ .{ .rread = .{ .data = data } },
+ .{ .rstat = .{ .stat = stat } },
+ .{ .twstat = .{ .fid = 1, .stat = stat } },
+ .{ .tflush = .{ .oldtag = random.int(u16) } },
+ .{ .tread = .{ .fid = 1, .offset = random.int(u64), .count = random.int(u32) } },
+ };
+ const msg = messages[random.uintLessThan(usize, messages.len)];
+ var frame: []u8 = try c9.encode(msg, random.int(u16), &buffer);
+ // Leave some valid seeds unchanged; probe truncation and altered fields locally.
+ switch (random.uintLessThan(u8, 4)) {
+ 0 => {},
+ 1 => frame = frame[0..random.uintLessThan(usize, frame.len)],
+ 2 => frame[random.uintLessThan(usize, frame.len)] ^= random.int(u8),
+ 3 => std.mem.writeInt(u32, frame[0..4], random.int(u32), .little),
+ else => unreachable,
+ }
+ if (c9.decode(frame)) |decoded| {
+ const output = try c9.encode(decoded.msg, decoded.tag, &encoded);
+ if (!std.mem.eql(u8, frame, output)) return error.RoundTrip;
+ accepted += 1;
+ } else |_| rejected += 1;
+ var server: c9.Server = .init(.{ .in = &server_in, .out = &server_out });
+ _ = server.push(frame);
+ if (server.receive() catch null) |request| {
+ if (request.msg != .tversion) return error.BeforeVersion;
+ server.negotiate(request.msg.tversion.msize, request.msg.tversion.version) catch {};
+ server.release();
+ }
+ }
+ std.debug.print("seed={d} iterations={d} accepted={d} rejected={d}\n", .{ seed, iterations, accepted, rejected });
+}