summaryrefslogtreecommitdiff
path: root/test/fuzz.zig
blob: b7d9f21fec01a189d3689baf5e379ff364879ea1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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 });
}