summaryrefslogtreecommitdiff
path: root/src/9p.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-14 13:55:41 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit95681ff7017b8a9e4c8f9fa6a7371d1233432f2f (patch)
tree34787d6bb62944cc778624685387db7376ccf71b /src/9p.zig
parent682e237df7e8b22f820d14a4adee58e6e2f84268 (diff)
downloadpardes-95681ff7017b8a9e4c8f9fa6a7371d1233432f2f.tar.gz
pardes-95681ff7017b8a9e4c8f9fa6a7371d1233432f2f.zip
9p: use cloud9 protocol sessions and transports
Diffstat (limited to 'src/9p.zig')
-rw-r--r--src/9p.zig1676
1 files changed, 108 insertions, 1568 deletions
diff --git a/src/9p.zig b/src/9p.zig
index 61a08b84..9513fdb5 100644
--- a/src/9p.zig
+++ b/src/9p.zig
@@ -1,704 +1,45 @@
+//! Pardes filesystem adapter. The 9P protocol and transports live in cloud9.
const std = @import("std");
const assert = std.debug.assert;
-
-pub const Error = error{
- Truncated,
- Overlong,
- BadTag,
- BadValue,
- Trailing,
- NoSpace,
-};
-
-pub const Type = enum(u8) {
- tversion = 100,
- rversion = 101,
- tauth = 102,
- rauth = 103,
- tattach = 104,
- rattach = 105,
- terror = 106,
- rerror = 107,
- tflush = 108,
- rflush = 109,
- twalk = 110,
- rwalk = 111,
- topen = 112,
- ropen = 113,
- tcreate = 114,
- rcreate = 115,
- tread = 116,
- rread = 117,
- twrite = 118,
- rwrite = 119,
- tclunk = 120,
- rclunk = 121,
- tremove = 122,
- rremove = 123,
- tstat = 124,
- rstat = 125,
- twstat = 126,
- rwstat = 127,
- _,
-};
-
-pub fn isT(t: Type) bool {
- return @intFromEnum(t) % 2 == 0;
-}
-
-pub const header_len: usize = 4 + 1 + 2;
-
-pub const qid_len: usize = 1 + 4 + 8;
-
-pub const stat_fixed: usize = 2 + qid_len + 5 * 2 + 4 * 4 + 8;
-
-pub const notag: u16 = 0xFFFF;
-
-pub const nofid: u32 = 0xFFFF_FFFF;
-
-pub const max_welem: usize = 16;
-
-pub const min_msize: u32 = 4096;
-
-pub const iohdrsz: u32 = 24;
-
-pub const errmax: usize = 128;
-
-pub const qtdir: u8 = 0x80;
-pub const qtappend: u8 = 0x40;
-pub const qtexcl: u8 = 0x20;
-pub const qtmount: u8 = 0x10;
-pub const qtauth: u8 = 0x08;
-pub const qttmp: u8 = 0x04;
-pub const qtfile: u8 = 0x00;
-
-pub const dmdir: u32 = 0x8000_0000;
-pub const dmappend: u32 = 0x4000_0000;
-pub const dmexcl: u32 = 0x2000_0000;
-pub const dmmount: u32 = 0x1000_0000;
-pub const dmauth: u32 = 0x0800_0000;
-pub const dmtmp: u32 = 0x0400_0000;
-pub const dmperm: u32 = 0o777;
-
-comptime {
- assert(header_len == 7);
- assert(qid_len == 13);
- assert(stat_fixed == 49);
- for (std.enums.values(Type)) |t| {
- const even = @intFromEnum(t) % 2 == 0;
- assert(isT(t) == even);
- assert(std.mem.startsWith(u8, @tagName(t), if (even) "t" else "r"));
- }
-}
-
-pub const Qid = struct {
- type: u8,
- version: u32,
- path: u64,
-
- pub fn encode(self: Qid, buf: []u8) Error![]u8 {
- if (buf.len < qid_len) return error.NoSpace;
- buf[0] = self.type;
- std.mem.writeInt(u32, buf[1..5], self.version, .little);
- std.mem.writeInt(u64, buf[5..13], self.path, .little);
- return buf[0..qid_len];
- }
-
- pub fn decode(bytes: []const u8) Error!Qid {
- if (bytes.len < qid_len) return error.Truncated;
- return .{
- .type = bytes[0],
- .version = std.mem.readInt(u32, bytes[1..5], .little),
- .path = std.mem.readInt(u64, bytes[5..13], .little),
- };
- }
-};
-
-pub const Stat = struct {
- type: u16,
- dev: u32,
- qid: Qid,
- mode: u32,
- atime: u32,
- mtime: u32,
- length: u64,
- name: []const u8,
- uid: []const u8,
- gid: []const u8,
- muid: []const u8,
-
- pub fn size(self: Stat) Error!u16 {
- const n =
- 2 + // type
- 4 + // dev
- qid_len + // qid: type[1] version[4] path[8]
- 4 + // mode
- 4 + // atime
- 4 + // mtime
- 8 + // length
- 2 + self.name.len +
- 2 + self.uid.len +
- 2 + self.gid.len +
- 2 + self.muid.len;
- assert(n >= stat_fixed - 2);
- if (n > std.math.maxInt(u16)) return error.Overlong;
- return @intCast(n);
- }
-
- pub fn encode(self: Stat, buf: []u8) Error![]u8 {
- const n = try self.size();
- const total = @as(usize, n) + 2;
- if (buf.len < total) return error.NoSpace;
- var w: Writer = .init(buf[0..total]);
- try w.putU16(n);
- try w.putU16(self.type);
- try w.putU32(self.dev);
- try w.putQid(self.qid);
- try w.putU32(self.mode);
- try w.putU32(self.atime);
- try w.putU32(self.mtime);
- try w.putU64(self.length);
- try w.putString(self.name);
- try w.putString(self.uid);
- try w.putString(self.gid);
- try w.putString(self.muid);
- assert(w.n == total);
- return buf[0..total];
- }
-
- pub fn decode(bytes: []const u8) Error!Stat {
- var r: Reader = .init(bytes);
- const n = try r.getU16();
- const body = bytes.len - 2;
- if (n > body) return error.Truncated;
- if (n < body) return error.Trailing;
- const self: Stat = .{
- .type = try r.getU16(),
- .dev = try r.getU32(),
- .qid = try r.getQid(),
- .mode = try r.getU32(),
- .atime = try r.getU32(),
- .mtime = try r.getU32(),
- .length = try r.getU64(),
- .name = try r.getString(),
- .uid = try r.getString(),
- .gid = try r.getString(),
- .muid = try r.getString(),
- };
- try r.end();
- return self;
- }
-};
-
-pub const Msg = union(enum) {
- tversion: struct { msize: u32, version: []const u8 },
- rversion: struct { msize: u32, version: []const u8 },
-
- tauth: struct { afid: u32, uname: []const u8, aname: []const u8 },
- rauth: struct { aqid: Qid },
-
- tattach: struct { fid: u32, afid: u32, uname: []const u8, aname: []const u8 },
- rattach: struct { qid: Qid },
-
- rerror: struct { ename: []const u8 },
-
- tflush: struct { oldtag: u16 },
- rflush: void,
-
- twalk: struct {
- fid: u32,
- newfid: u32,
- nwname: u16,
- wname: [max_welem][]const u8 = @splat(""),
- },
- rwalk: struct {
- nwqid: u16,
- wqid: [max_welem]Qid = @splat(.{ .type = 0, .version = 0, .path = 0 }),
- },
-
- topen: struct { fid: u32, mode: u8 },
- ropen: struct { qid: Qid, iounit: u32 },
-
- tcreate: struct { fid: u32, name: []const u8, perm: u32, mode: u8 },
- rcreate: struct { qid: Qid, iounit: u32 },
-
- tread: struct { fid: u32, offset: u64, count: u32 },
- rread: struct { data: []const u8 },
-
- twrite: struct { fid: u32, offset: u64, data: []const u8 },
- rwrite: struct { count: u32 },
-
- tclunk: struct { fid: u32 },
- rclunk: void,
- tremove: struct { fid: u32 },
- rremove: void,
-
- tstat: struct { fid: u32 },
- rstat: struct { stat: Stat },
- twstat: struct { fid: u32, stat: Stat },
- rwstat: void,
-
- pub fn msgType(msg: Msg) Type {
- return switch (msg) {
- inline else => |_, t| @field(Type, @tagName(t)),
- };
- }
-};
-
-pub const Decoded = struct {
- tag: u16,
- msg: Msg,
-};
-
-pub fn frameLen(prefix: []const u8) ?u32 {
- if (prefix.len < 4) return null;
- return std.mem.readInt(u32, prefix[0..4], .little);
-}
-
-fn totalLen(msg: Msg) Error!usize {
- const body: usize = switch (msg) {
- .tversion => |m| 4 + try stringLen(m.version),
- .rversion => |m| 4 + try stringLen(m.version),
- .tauth => |m| 4 + try stringLen(m.uname) + try stringLen(m.aname),
- .rauth => qid_len,
- .tattach => |m| 4 + 4 + try stringLen(m.uname) + try stringLen(m.aname),
- .rattach => qid_len,
- .rerror => |m| try stringLen(m.ename),
- .tflush => 2,
- .rflush => 0,
- .twalk => |m| blk: {
- assert(m.nwname <= max_welem);
- var n: usize = 4 + 4 + 2;
- for (m.wname[0..m.nwname]) |name| n += try stringLen(name);
- break :blk n;
- },
- .rwalk => |m| blk: {
- assert(m.nwqid <= max_welem);
- break :blk 2 + @as(usize, m.nwqid) * qid_len;
- },
- .topen => 4 + 1,
- .ropen => qid_len + 4,
- .tcreate => |m| 4 + try stringLen(m.name) + 4 + 1,
- .rcreate => qid_len + 4,
- .tread => 4 + 8 + 4,
- .rread => |m| try dataLen(m.data),
- .twrite => |m| 4 + 8 + try dataLen(m.data),
- .rwrite => 4,
- .tclunk => 4,
- .rclunk => 0,
- .tremove => 4,
- .rremove => 0,
- .tstat => 4,
- .rstat => |m| 2 + 2 + @as(usize, try m.stat.size()),
- .twstat => |m| 4 + 2 + 2 + @as(usize, try m.stat.size()),
- .rwstat => 0,
- };
- const total = header_len + body;
- if (total > std.math.maxInt(u32)) return error.Overlong;
- return total;
-}
-
-fn stringLen(s: []const u8) Error!usize {
- if (s.len > std.math.maxInt(u16)) return error.Overlong;
- return 2 + s.len;
-}
-
-fn dataLen(d: []const u8) Error!usize {
- if (d.len > std.math.maxInt(u32)) return error.Overlong;
- return 4 + d.len;
-}
-
-pub fn encode(msg: Msg, tag: u16, buf: []u8) Error![]u8 {
- const total = try totalLen(msg);
- if (total > buf.len) return error.NoSpace;
-
- var w: Writer = .init(buf[0..total]);
- try w.putU32(@intCast(total));
- try w.putByte(@intFromEnum(msg.msgType()));
- try w.putU16(tag);
-
- switch (msg) {
- .tversion => |m| {
- try w.putU32(m.msize);
- try w.putString(m.version);
- },
- .rversion => |m| {
- try w.putU32(m.msize);
- try w.putString(m.version);
- },
- .tauth => |m| {
- try w.putU32(m.afid);
- try w.putString(m.uname);
- try w.putString(m.aname);
- },
- .rauth => |m| try w.putQid(m.aqid),
- .tattach => |m| {
- try w.putU32(m.fid);
- try w.putU32(m.afid);
- try w.putString(m.uname);
- try w.putString(m.aname);
- },
- .rattach => |m| try w.putQid(m.qid),
- .rerror => |m| try w.putString(m.ename),
- .tflush => |m| try w.putU16(m.oldtag),
- .rflush => {},
- .twalk => |m| {
- try w.putU32(m.fid);
- try w.putU32(m.newfid);
- try w.putU16(m.nwname);
- for (m.wname[0..m.nwname]) |name| try w.putString(name);
- },
- .rwalk => |m| {
- try w.putU16(m.nwqid);
- for (m.wqid[0..m.nwqid]) |qid| try w.putQid(qid);
- },
- .topen => |m| {
- try w.putU32(m.fid);
- try w.putByte(m.mode);
- },
- .ropen => |m| {
- try w.putQid(m.qid);
- try w.putU32(m.iounit);
- },
- .tcreate => |m| {
- try w.putU32(m.fid);
- try w.putString(m.name);
- try w.putU32(m.perm);
- try w.putByte(m.mode);
- },
- .rcreate => |m| {
- try w.putQid(m.qid);
- try w.putU32(m.iounit);
- },
- .tread => |m| {
- try w.putU32(m.fid);
- try w.putU64(m.offset);
- try w.putU32(m.count);
- },
- .rread => |m| {
- try w.putU32(@intCast(m.data.len));
- try w.putBytes(m.data);
- },
- .twrite => |m| {
- try w.putU32(m.fid);
- try w.putU64(m.offset);
- try w.putU32(@intCast(m.data.len));
- try w.putBytes(m.data);
- },
- .rwrite => |m| try w.putU32(m.count),
- .tclunk => |m| try w.putU32(m.fid),
- .rclunk => {},
- .tremove => |m| try w.putU32(m.fid),
- .rremove => {},
- .tstat => |m| try w.putU32(m.fid),
- .rstat => |m| {
- try w.putU16(try m.stat.size() + 2);
- try w.putStat(m.stat);
- },
- .twstat => |m| {
- try w.putU32(m.fid);
- try w.putU16(try m.stat.size() + 2);
- try w.putStat(m.stat);
- },
- .rwstat => {},
- }
-
- assert(w.n == total);
- return buf[0..total];
-}
-
-pub fn decode(bytes: []const u8) Error!Decoded {
- if (bytes.len < header_len) return error.Truncated;
- const size = std.mem.readInt(u32, bytes[0..4], .little);
- if (size < header_len) return error.BadValue;
- if (size > bytes.len) return error.Truncated;
- if (size < bytes.len) return error.Trailing;
-
- const t: Type = @enumFromInt(bytes[4]);
- const tag = std.mem.readInt(u16, bytes[5..7], .little);
-
- var r: Reader = .init(bytes[header_len..size]);
-
- const msg: Msg = switch (t) {
- .tversion => .{ .tversion = .{ .msize = try r.getU32(), .version = try r.getString() } },
- .rversion => .{ .rversion = .{ .msize = try r.getU32(), .version = try r.getString() } },
- .tauth => .{ .tauth = .{
- .afid = try r.getU32(),
- .uname = try r.getString(),
- .aname = try r.getString(),
- } },
- .rauth => .{ .rauth = .{ .aqid = try r.getQid() } },
- .tattach => .{ .tattach = .{
- .fid = try r.getU32(),
- .afid = try r.getU32(),
- .uname = try r.getString(),
- .aname = try r.getString(),
- } },
- .rattach => .{ .rattach = .{ .qid = try r.getQid() } },
- .rerror => .{ .rerror = .{ .ename = try r.getString() } },
- .tflush => .{ .tflush = .{ .oldtag = try r.getU16() } },
- .rflush => .rflush,
- .twalk => blk: {
- var m: Msg = .{ .twalk = .{
- .fid = try r.getU32(),
- .newfid = try r.getU32(),
- .nwname = try r.getU16(),
- } };
- if (m.twalk.nwname > max_welem) return error.Overlong;
- for (m.twalk.wname[0..m.twalk.nwname]) |*name| name.* = try r.getString();
- break :blk m;
- },
- .rwalk => blk: {
- var m: Msg = .{ .rwalk = .{ .nwqid = try r.getU16() } };
- if (m.rwalk.nwqid > max_welem) return error.Overlong;
- for (m.rwalk.wqid[0..m.rwalk.nwqid]) |*qid| qid.* = try r.getQid();
- break :blk m;
- },
- .topen => .{ .topen = .{ .fid = try r.getU32(), .mode = try r.getByte() } },
- .ropen => .{ .ropen = .{ .qid = try r.getQid(), .iounit = try r.getU32() } },
- .tcreate => .{ .tcreate = .{
- .fid = try r.getU32(),
- .name = try r.getString(),
- .perm = try r.getU32(),
- .mode = try r.getByte(),
- } },
- .rcreate => .{ .rcreate = .{ .qid = try r.getQid(), .iounit = try r.getU32() } },
- .tread => .{ .tread = .{
- .fid = try r.getU32(),
- .offset = try r.getU64(),
- .count = try r.getU32(),
- } },
- .rread => .{ .rread = .{ .data = try r.getData() } },
- .twrite => .{ .twrite = .{
- .fid = try r.getU32(),
- .offset = try r.getU64(),
- .data = try r.getData(),
- } },
- .rwrite => .{ .rwrite = .{ .count = try r.getU32() } },
- .tclunk => .{ .tclunk = .{ .fid = try r.getU32() } },
- .rclunk => .rclunk,
- .tremove => .{ .tremove = .{ .fid = try r.getU32() } },
- .rremove => .rremove,
- .tstat => .{ .tstat = .{ .fid = try r.getU32() } },
- .rstat => .{ .rstat = .{ .stat = try Stat.decode(try r.getBlob16()) } },
- .twstat => .{ .twstat = .{
- .fid = try r.getU32(),
- .stat = try Stat.decode(try r.getBlob16()),
- } },
- .rwstat => .rwstat,
- .terror, _ => return error.BadTag,
- };
-
- try r.end();
- return .{ .tag = tag, .msg = msg };
-}
-
-const Writer = struct {
- buf: []u8,
- n: usize = 0,
-
- fn init(buf: []u8) Writer {
- return .{ .buf = buf };
- }
-
- fn room(w: *Writer, k: usize) Error![]u8 {
- if (w.buf.len - w.n < k) return error.NoSpace;
- defer w.n += k;
- return w.buf[w.n..][0..k];
- }
-
- fn putByte(w: *Writer, v: u8) Error!void {
- (try w.room(1))[0] = v;
- }
-
- fn putU16(w: *Writer, v: u16) Error!void {
- std.mem.writeInt(u16, (try w.room(2))[0..2], v, .little);
- }
-
- fn putU32(w: *Writer, v: u32) Error!void {
- std.mem.writeInt(u32, (try w.room(4))[0..4], v, .little);
- }
-
- fn putU64(w: *Writer, v: u64) Error!void {
- std.mem.writeInt(u64, (try w.room(8))[0..8], v, .little);
- }
-
- fn putBytes(w: *Writer, v: []const u8) Error!void {
- @memcpy(try w.room(v.len), v);
- }
-
- fn putString(w: *Writer, v: []const u8) Error!void {
- assert(v.len <= std.math.maxInt(u16));
- try w.putU16(@intCast(v.len));
- try w.putBytes(v);
- }
-
- fn putQid(w: *Writer, v: Qid) Error!void {
- _ = try v.encode(try w.room(qid_len));
- }
-
- fn putStat(w: *Writer, v: Stat) Error!void {
- const total = @as(usize, try v.size()) + 2;
- _ = try v.encode(try w.room(total));
- }
-};
-
-const Reader = struct {
- bytes: []const u8,
- i: usize = 0,
-
- fn init(bytes: []const u8) Reader {
- return .{ .bytes = bytes };
- }
-
- fn take(r: *Reader, n: usize) Error![]const u8 {
- if (r.bytes.len - r.i < n) return error.Truncated;
- defer r.i += n;
- return r.bytes[r.i..][0..n];
- }
-
- fn getByte(r: *Reader) Error!u8 {
- return (try r.take(1))[0];
- }
-
- fn getU16(r: *Reader) Error!u16 {
- return std.mem.readInt(u16, (try r.take(2))[0..2], .little);
- }
-
- fn getU32(r: *Reader) Error!u32 {
- return std.mem.readInt(u32, (try r.take(4))[0..4], .little);
- }
-
- fn getU64(r: *Reader) Error!u64 {
- return std.mem.readInt(u64, (try r.take(8))[0..8], .little);
- }
-
- fn getString(r: *Reader) Error![]const u8 {
- return r.take(try r.getU16());
- }
-
- fn getData(r: *Reader) Error![]const u8 {
- return r.take(try r.getU32());
- }
-
- fn getBlob16(r: *Reader) Error![]const u8 {
- return r.take(try r.getU16());
- }
-
- fn getQid(r: *Reader) Error!Qid {
- return Qid.decode(try r.take(qid_len));
- }
-
- fn end(r: *Reader) Error!void {
- if (r.i != r.bytes.len) return error.Trailing;
- }
-};
-
const testing = std.testing;
-
-fn roundTrip(buf: []u8, tag: u16, msg: Msg) !Msg {
- const bytes = try encode(msg, tag, buf);
- try testing.expectEqual(bytes.len, frameLen(bytes).?);
- const got = try decode(bytes);
- try testing.expectEqual(tag, got.tag);
- try testing.expectEqual(msg.msgType(), got.msg.msgType());
- try expectMsgEqual(msg, got.msg);
- return got.msg;
-}
-
-fn expectStatEqual(want: Stat, have: Stat) !void {
- try testing.expectEqual(want.type, have.type);
- try testing.expectEqual(want.dev, have.dev);
- try testing.expectEqual(want.qid, have.qid);
- try testing.expectEqual(want.mode, have.mode);
- try testing.expectEqual(want.atime, have.atime);
- try testing.expectEqual(want.mtime, have.mtime);
- try testing.expectEqual(want.length, have.length);
- try testing.expectEqualStrings(want.name, have.name);
- try testing.expectEqualStrings(want.uid, have.uid);
- try testing.expectEqualStrings(want.gid, have.gid);
- try testing.expectEqualStrings(want.muid, have.muid);
-}
-
-fn expectMsgEqual(want: Msg, have: Msg) !void {
- switch (want) {
- .tversion => |w| {
- try testing.expectEqual(w.msize, have.tversion.msize);
- try testing.expectEqualStrings(w.version, have.tversion.version);
- },
- .rversion => |w| {
- try testing.expectEqual(w.msize, have.rversion.msize);
- try testing.expectEqualStrings(w.version, have.rversion.version);
- },
- .tauth => |w| {
- try testing.expectEqual(w.afid, have.tauth.afid);
- try testing.expectEqualStrings(w.uname, have.tauth.uname);
- try testing.expectEqualStrings(w.aname, have.tauth.aname);
- },
- .rauth => |w| try testing.expectEqual(w.aqid, have.rauth.aqid),
- .tattach => |w| {
- try testing.expectEqual(w.fid, have.tattach.fid);
- try testing.expectEqual(w.afid, have.tattach.afid);
- try testing.expectEqualStrings(w.uname, have.tattach.uname);
- try testing.expectEqualStrings(w.aname, have.tattach.aname);
- },
- .rattach => |w| try testing.expectEqual(w.qid, have.rattach.qid),
- .rerror => |w| try testing.expectEqualStrings(w.ename, have.rerror.ename),
- .tflush => |w| try testing.expectEqual(w.oldtag, have.tflush.oldtag),
- .rflush, .rclunk, .rremove, .rwstat => {},
- .twalk => |w| {
- try testing.expectEqual(w.fid, have.twalk.fid);
- try testing.expectEqual(w.newfid, have.twalk.newfid);
- try testing.expectEqual(w.nwname, have.twalk.nwname);
- for (w.wname[0..w.nwname], have.twalk.wname[0..w.nwname]) |a, b|
- try testing.expectEqualStrings(a, b);
- },
- .rwalk => |w| {
- try testing.expectEqual(w.nwqid, have.rwalk.nwqid);
- for (w.wqid[0..w.nwqid], have.rwalk.wqid[0..w.nwqid]) |a, b|
- try testing.expectEqual(a, b);
- },
- .topen => |w| {
- try testing.expectEqual(w.fid, have.topen.fid);
- try testing.expectEqual(w.mode, have.topen.mode);
- },
- .ropen => |w| {
- try testing.expectEqual(w.qid, have.ropen.qid);
- try testing.expectEqual(w.iounit, have.ropen.iounit);
- },
- .tcreate => |w| {
- try testing.expectEqual(w.fid, have.tcreate.fid);
- try testing.expectEqualStrings(w.name, have.tcreate.name);
- try testing.expectEqual(w.perm, have.tcreate.perm);
- try testing.expectEqual(w.mode, have.tcreate.mode);
- },
- .rcreate => |w| {
- try testing.expectEqual(w.qid, have.rcreate.qid);
- try testing.expectEqual(w.iounit, have.rcreate.iounit);
- },
- .tread => |w| {
- try testing.expectEqual(w.fid, have.tread.fid);
- try testing.expectEqual(w.offset, have.tread.offset);
- try testing.expectEqual(w.count, have.tread.count);
- },
- .rread => |w| try testing.expectEqualStrings(w.data, have.rread.data),
- .twrite => |w| {
- try testing.expectEqual(w.fid, have.twrite.fid);
- try testing.expectEqual(w.offset, have.twrite.offset);
- try testing.expectEqualStrings(w.data, have.twrite.data);
- },
- .rwrite => |w| try testing.expectEqual(w.count, have.rwrite.count),
- .tclunk => |w| try testing.expectEqual(w.fid, have.tclunk.fid),
- .tremove => |w| try testing.expectEqual(w.fid, have.tremove.fid),
- .tstat => |w| try testing.expectEqual(w.fid, have.tstat.fid),
- .rstat => |w| try expectStatEqual(w.stat, have.rstat.stat),
- .twstat => |w| {
- try testing.expectEqual(w.fid, have.twstat.fid);
- try expectStatEqual(w.stat, have.twstat.stat);
- },
- }
-}
-
+const cloud9 = @import("cloud9");
+pub const Error = cloud9.Error;
+pub const Type = cloud9.Type;
+pub const isT = cloud9.isT;
+pub const header_len = cloud9.header_len;
+pub const qid_len = cloud9.qid_len;
+pub const stat_fixed = cloud9.stat_fixed;
+pub const notag = cloud9.notag;
+pub const nofid = cloud9.nofid;
+pub const max_welem = cloud9.max_welem;
+pub const iohdrsz = cloud9.iohdrsz;
+pub const Qid = cloud9.Qid;
+pub const Stat = cloud9.Stat;
+pub const Msg = cloud9.Msg;
+pub const Decoded = cloud9.Decoded;
+pub const frameLen = cloud9.frameLen;
+pub const encode = cloud9.encode;
+pub const decode = cloud9.decode;
+pub const qtdir = cloud9.qtdir;
+pub const qtappend = cloud9.qtappend;
+pub const qtexcl = cloud9.qtexcl;
+pub const qtmount = cloud9.qtmount;
+pub const qtauth = cloud9.qtauth;
+pub const qttmp = cloud9.qttmp;
+pub const qtfile = cloud9.qtfile;
+pub const dmdir = cloud9.dmdir;
+pub const dmappend = cloud9.dmappend;
+pub const dmexcl = cloud9.dmexcl;
+pub const dmmount = cloud9.dmmount;
+pub const dmauth = cloud9.dmauth;
+pub const dmtmp = cloud9.dmtmp;
+pub const dmperm = cloud9.dmperm;
+pub const Client = cloud9.Client;
+pub const ClientError = cloud9.ClientError;
+pub const max_tags = cloud9.max_tags;
+const totalLen = cloud9.encodedLen;
+pub const errmax: usize = 128;
+pub const min_msize: u32 = 4096;
const sample_qid: Qid = .{ .type = qtdir, .version = 3, .path = 0x0102_0304_0506_0708 };
const sample_stat: Stat = .{
@@ -715,435 +56,6 @@ const sample_stat: Stat = .{
.muid = "goblin",
};
-test "9p: the type numbers and their parity are the protocol's own" {
- try testing.expectEqual(@as(u8, 100), @intFromEnum(Type.tversion));
- try testing.expectEqual(@as(u8, 106), @intFromEnum(Type.terror));
- try testing.expectEqual(@as(u8, 107), @intFromEnum(Type.rerror));
- try testing.expectEqual(@as(u8, 126), @intFromEnum(Type.twstat));
- try testing.expectEqual(@as(u8, 127), @intFromEnum(Type.rwstat));
-
- try testing.expectEqual(@as(usize, 28), std.enums.values(Type).len);
- for (std.enums.values(Type), 100..) |t, want| try testing.expectEqual(@as(u8, @intCast(want)), @intFromEnum(t));
-
- try testing.expect(isT(.tversion));
- try testing.expect(!isT(.rversion));
- try testing.expect(isT(.twstat));
- try testing.expect(!isT(.rwstat));
-
- try testing.expectEqual(@as(u16, 0xFFFF), notag);
- try testing.expectEqual(@as(u32, 0xFFFF_FFFF), nofid);
- try testing.expectEqual(@as(usize, 16), max_welem);
-}
-
-test "9p: a qid is thirteen bytes" {
- var buf: [32]u8 = undefined;
- const bytes = try sample_qid.encode(&buf);
- try testing.expectEqual(qid_len, bytes.len);
- try testing.expectEqual(@as(usize, 13), bytes.len);
- try testing.expectEqual(sample_qid, try Qid.decode(bytes));
- try testing.expectError(error.Truncated, Qid.decode(bytes[0..12]));
- try testing.expectError(error.NoSpace, sample_qid.encode(buf[0..12]));
-}
-
-test "9p: an encoded stat is size() + 2 bytes" {
- var buf: [256]u8 = undefined;
- const bytes = try sample_stat.encode(&buf);
- const n = try sample_stat.size();
- try testing.expectEqual(@as(usize, n) + 2, bytes.len);
- try testing.expectEqual(@as(u16, 69), n);
- try testing.expectEqual(stat_fixed - 2 + 22, n);
- try testing.expectEqual(n, std.mem.readInt(u16, bytes[0..2], .little));
- try expectStatEqual(sample_stat, try Stat.decode(bytes));
-
- const bare: Stat = .{
- .type = 0,
- .dev = 0,
- .qid = .{ .type = qtfile, .version = 0, .path = 0 },
- .mode = 0,
- .atime = 0,
- .mtime = 0,
- .length = 0,
- .name = "",
- .uid = "",
- .gid = "",
- .muid = "",
- };
- try testing.expectEqual(@as(u16, 47), try bare.size());
- try testing.expectEqual(@as(usize, 49), (try bare.encode(&buf)).len);
-}
-
-test "9p: every message round-trips" {
- var buf: [512]u8 = undefined;
-
- _ = try roundTrip(&buf, notag, .{ .tversion = .{ .msize = 8192, .version = "9P2000" } });
- _ = try roundTrip(&buf, notag, .{ .rversion = .{ .msize = 8192, .version = "9P2000" } });
- _ = try roundTrip(&buf, notag, .{ .rversion = .{ .msize = min_msize, .version = "unknown" } });
- _ = try roundTrip(&buf, 1, .{ .tauth = .{ .afid = 1, .uname = "goblin", .aname = "" } });
- _ = try roundTrip(&buf, 1, .{ .rauth = .{ .aqid = .{ .type = qtauth, .version = 0, .path = 9 } } });
- _ = try roundTrip(&buf, 2, .{ .tattach = .{ .fid = 0, .afid = nofid, .uname = "goblin", .aname = "" } });
- _ = try roundTrip(&buf, 2, .{ .rattach = .{ .qid = sample_qid } });
- _ = try roundTrip(&buf, 3, .{ .rerror = .{ .ename = "no such file" } });
- _ = try roundTrip(&buf, 4, .{ .tflush = .{ .oldtag = 3 } });
- _ = try roundTrip(&buf, 4, .rflush);
- _ = try roundTrip(&buf, 5, .{ .twalk = .{ .fid = 0, .newfid = 1, .nwname = 2, .wname = .{ "7", "body" } ++ @as([max_welem - 2][]const u8, @splat("")) } });
- _ = try roundTrip(&buf, 5, .{ .rwalk = .{ .nwqid = 2, .wqid = .{ sample_qid, sample_qid } ++ @as([max_welem - 2]Qid, @splat(sample_qid)) } });
- _ = try roundTrip(&buf, 6, .{ .topen = .{ .fid = 1, .mode = 0 } });
- _ = try roundTrip(&buf, 6, .{ .ropen = .{ .qid = sample_qid, .iounit = 8192 - iohdrsz } });
- _ = try roundTrip(&buf, 7, .{ .tcreate = .{ .fid = 1, .name = "new", .perm = dmdir | 0o777, .mode = 2 } });
- _ = try roundTrip(&buf, 7, .{ .rcreate = .{ .qid = sample_qid, .iounit = 0 } });
- _ = try roundTrip(&buf, 8, .{ .tread = .{ .fid = 1, .offset = 0xdead_beef_cafe, .count = 4096 } });
- _ = try roundTrip(&buf, 8, .{ .rread = .{ .data = "hello" } });
- _ = try roundTrip(&buf, 8, .{ .rread = .{ .data = "" } });
- _ = try roundTrip(&buf, 9, .{ .twrite = .{ .fid = 1, .offset = 0, .data = "Edit ,d" } });
- _ = try roundTrip(&buf, 9, .{ .twrite = .{ .fid = 1, .offset = 0, .data = "" } });
- _ = try roundTrip(&buf, 9, .{ .rwrite = .{ .count = 7 } });
- _ = try roundTrip(&buf, 10, .{ .tclunk = .{ .fid = 1 } });
- _ = try roundTrip(&buf, 10, .rclunk);
- _ = try roundTrip(&buf, 11, .{ .tremove = .{ .fid = 1 } });
- _ = try roundTrip(&buf, 11, .rremove);
- _ = try roundTrip(&buf, 12, .{ .tstat = .{ .fid = 1 } });
- _ = try roundTrip(&buf, 12, .{ .rstat = .{ .stat = sample_stat } });
- _ = try roundTrip(&buf, 13, .{ .twstat = .{ .fid = 1, .stat = sample_stat } });
- _ = try roundTrip(&buf, 13, .rwstat);
-
- try testing.expectEqual(@as(usize, 27), @typeInfo(Msg).@"union".fields.len);
- try testing.expectEqual(std.enums.values(Type).len - 1, @typeInfo(Msg).@"union".fields.len);
-}
-
-test "9p: empty and maximum-length strings survive the trip" {
- var buf: [70_000]u8 = undefined;
-
- const empty = try roundTrip(&buf, 1, .{ .tattach = .{ .fid = 0, .afid = nofid, .uname = "", .aname = "" } });
- try testing.expectEqual(@as(usize, 0), empty.tattach.uname.len);
- try testing.expectEqual(@as(usize, header_len + 4 + 4 + 2 + 2), (try encode(empty, 1, &buf)).len);
-
- var big: [65_536]u8 = undefined;
- @memset(&big, 'x');
- const max = big[0..std.math.maxInt(u16)];
- const got = try roundTrip(&buf, 1, .{ .rerror = .{ .ename = max } });
- try testing.expectEqual(@as(usize, 65_535), got.rerror.ename.len);
- try testing.expectError(error.Overlong, encode(.{ .rerror = .{ .ename = &big } }, 1, &buf));
-
- var wide = sample_stat;
- wide.name = max;
- try testing.expectError(error.Overlong, wide.size());
- try testing.expectError(error.Overlong, encode(.{ .rstat = .{ .stat = wide } }, 1, &buf));
-}
-
-test "9p: Twalk carries 0, 1 and 16 elements and refuses 17" {
- var buf: [512]u8 = undefined;
-
- const zero = try roundTrip(&buf, 1, .{ .twalk = .{ .fid = 0, .newfid = 1, .nwname = 0 } });
- try testing.expectEqual(@as(u16, 0), zero.twalk.nwname);
- try testing.expectEqual(@as(usize, header_len + 4 + 4 + 2), (try encode(zero, 1, &buf)).len);
-
- _ = try roundTrip(&buf, 1, .{ .twalk = .{
- .fid = 0,
- .newfid = 1,
- .nwname = 1,
- .wname = .{"body"} ++ @as([max_welem - 1][]const u8, @splat("")),
- } });
-
- const names: [max_welem][]const u8 = .{ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p" };
- const full = try roundTrip(&buf, 1, .{ .twalk = .{ .fid = 0, .newfid = 1, .nwname = max_welem, .wname = names } });
- try testing.expectEqual(@as(u16, 16), full.twalk.nwname);
- for (names, full.twalk.wname[0..max_welem]) |a, b| try testing.expectEqualStrings(a, b);
- _ = try roundTrip(&buf, 1, .{ .rwalk = .{ .nwqid = max_welem, .wqid = @splat(sample_qid) } });
-
- var raw: [256]u8 = undefined;
- const bad = blk: {
- var w: Writer = .init(&raw);
- try w.putU32(0); // patched below
- try w.putByte(@intFromEnum(Type.twalk));
- try w.putU16(1);
- try w.putU32(0);
- try w.putU32(1);
- try w.putU16(17);
- for (0..17) |i| try w.putString(&[_]u8{@intCast('a' + i)});
- std.mem.writeInt(u32, raw[0..4], @intCast(w.n), .little);
- break :blk raw[0..w.n];
- };
- try testing.expectEqual(@as(usize, header_len + 4 + 4 + 2 + 17 * 3), bad.len);
- try testing.expectError(error.Overlong, decode(bad));
-
- const bad_r = blk: {
- var w: Writer = .init(&raw);
- try w.putU32(0);
- try w.putByte(@intFromEnum(Type.rwalk));
- try w.putU16(1);
- try w.putU16(17);
- for (0..17) |_| try w.putQid(sample_qid);
- std.mem.writeInt(u32, raw[0..4], @intCast(w.n), .little);
- break :blk raw[0..w.n];
- };
- try testing.expectError(error.Overlong, decode(bad_r));
-}
-
-test "9p: the stat double length" {
- var buf: [512]u8 = undefined;
-
- var good: [512]u8 = undefined;
- const n = blk: {
- const bytes = try encode(.{ .rstat = .{ .stat = sample_stat } }, 1, &buf);
- @memcpy(good[0..bytes.len], bytes);
- break :blk bytes.len;
- };
- const inner = try sample_stat.size();
- try testing.expectEqual(inner + 2, std.mem.readInt(u16, good[header_len..][0..2], .little));
- try testing.expectEqual(inner, std.mem.readInt(u16, good[header_len + 2 ..][0..2], .little));
- try testing.expectEqual(header_len + 2 + @as(usize, inner) + 2, n);
-
- const w_bytes = try encode(.{ .twstat = .{ .fid = 7, .stat = sample_stat } }, 1, &buf);
- try testing.expectEqual(inner + 2, std.mem.readInt(u16, w_bytes[header_len + 4 ..][0..2], .little));
- try testing.expectEqual(inner, std.mem.readInt(u16, w_bytes[header_len + 6 ..][0..2], .little));
-
- var off: [512]u8 = undefined;
-
- @memcpy(off[0..n], good[0..n]);
- std.mem.writeInt(u16, off[header_len..][0..2], inner, .little);
- try testing.expectError(error.Truncated, decode(off[0..n]));
-
- @memcpy(off[0..n], good[0..n]);
- std.mem.writeInt(u16, off[header_len..][0..2], inner + 4, .little);
- try testing.expectError(error.Truncated, decode(off[0..n]));
-
- @memcpy(off[0..n], good[0..n]);
- std.mem.writeInt(u16, off[header_len + 2 ..][0..2], inner + 2, .little);
- try testing.expectError(error.Truncated, decode(off[0..n]));
-
- @memcpy(off[0..n], good[0..n]);
- std.mem.writeInt(u16, off[header_len + 2 ..][0..2], inner - 1, .little);
- try testing.expectError(error.Trailing, decode(off[0..n]));
-}
-
-fn expectTruncatedAtEveryBoundary(full: []const u8) !void {
- var scratch: [1024]u8 = undefined;
- var n: usize = 0;
- while (n < full.len) : (n += 1) {
- try testing.expectError(error.Truncated, decode(full[0..n]));
- if (n < header_len) continue;
- @memcpy(scratch[0..n], full[0..n]);
- std.mem.writeInt(u32, scratch[0..4], @intCast(n), .little);
- try testing.expectError(error.Truncated, decode(scratch[0..n]));
- }
- _ = try decode(full);
-}
-
-test "9p: truncation at every field boundary is refused" {
- var buf: [512]u8 = undefined;
-
- try expectTruncatedAtEveryBoundary(try encode(
- .{ .tversion = .{ .msize = 8192, .version = "9P2000" } },
- notag,
- &buf,
- ));
- try expectTruncatedAtEveryBoundary(try encode(.{ .twalk = .{
- .fid = 1,
- .newfid = 2,
- .nwname = 3,
- .wname = .{ "usr", "", "bin" } ++ @as([max_welem - 3][]const u8, @splat("")),
- } }, 1, &buf));
- try expectTruncatedAtEveryBoundary(try encode(
- .{ .tread = .{ .fid = 1, .offset = 0x0102_0304_0506_0708, .count = 8168 } },
- 1,
- &buf,
- ));
- try expectTruncatedAtEveryBoundary(try encode(.{ .rstat = .{ .stat = sample_stat } }, 1, &buf));
- try expectTruncatedAtEveryBoundary(try encode(.{ .rread = .{ .data = "12345678" } }, 1, &buf));
- try expectTruncatedAtEveryBoundary(try encode(
- .{ .rwalk = .{ .nwqid = 3, .wqid = @splat(sample_qid) } },
- 1,
- &buf,
- ));
- try expectTruncatedAtEveryBoundary(try encode(.{ .twstat = .{ .fid = 1, .stat = sample_stat } }, 1, &buf));
-}
-
-test "9p: a size field that disagrees with the buffer is refused" {
- var buf: [512]u8 = undefined;
- const bytes = try encode(.{ .tclunk = .{ .fid = 1 } }, 1, &buf);
- try testing.expectEqual(@as(usize, 11), bytes.len);
-
- var raw: [64]u8 = undefined;
- @memcpy(raw[0..bytes.len], bytes);
-
- for ([_]u32{ 12, 13, 64, 1 << 20, std.math.maxInt(u32) }) |claim| {
- std.mem.writeInt(u32, raw[0..4], claim, .little);
- try testing.expectError(error.Truncated, decode(raw[0..bytes.len]));
- }
-
- std.mem.writeInt(u32, raw[0..4], 10, .little);
- try testing.expectError(error.Trailing, decode(raw[0..bytes.len]));
-
- for ([_]u32{ 0, 1, 6 }) |claim| {
- std.mem.writeInt(u32, raw[0..4], claim, .little);
- try testing.expectError(error.BadValue, decode(raw[0..bytes.len]));
- try testing.expectError(error.BadValue, decode(raw[0..header_len]));
- }
-}
-
-test "9p: an unknown or illegal type byte is refused" {
- var buf: [512]u8 = undefined;
- const bytes = try encode(.{ .tclunk = .{ .fid = 1 } }, 1, &buf);
- var raw: [64]u8 = undefined;
- @memcpy(raw[0..bytes.len], bytes);
-
- for ([_]u8{ 0, 1, 8, 12, 99, 106, 128, 255 }) |t| {
- raw[4] = t;
- try testing.expectError(error.BadTag, decode(raw[0..bytes.len]));
- }
-
- var t: u16 = 0;
- while (t <= 255) : (t += 1) {
- raw[4] = @intCast(t);
- const defined = t >= 100 and t <= 127 and t != @intFromEnum(Type.terror);
- if (decode(raw[0..bytes.len])) |got| {
- try testing.expectEqual(@as(u8, @intCast(t)), @intFromEnum(got.msg.msgType()));
- try testing.expect(t == @intFromEnum(Type.tclunk) or
- t == @intFromEnum(Type.tremove) or
- t == @intFromEnum(Type.tstat) or
- t == @intFromEnum(Type.rwrite));
- } else |err| {
- if (!defined) try testing.expectEqual(Error.BadTag, err);
- }
- }
-}
-
-test "9p: trailing bytes inside the size are refused" {
- var raw: [64]u8 = undefined;
-
- var w: Writer = .init(&raw);
- try w.putU32(12);
- try w.putByte(@intFromEnum(Type.tclunk));
- try w.putU16(1);
- try w.putU32(7);
- try w.putByte(0xAA);
- try testing.expectEqual(@as(usize, 12), w.n);
- try testing.expectError(error.Trailing, decode(raw[0..12]));
-
- w = .init(&raw);
- try w.putU32(header_len + 2 + 2);
- try w.putByte(@intFromEnum(Type.tflush));
- try w.putU16(1);
- try w.putU16(3);
- try w.putU16(3);
- try testing.expectError(error.Trailing, decode(raw[0..w.n]));
-}
-
-test "9p: frameLen needs four bytes" {
- var buf: [512]u8 = undefined;
- const bytes = try encode(.{ .tread = .{ .fid = 1, .offset = 0, .count = 8168 } }, 1, &buf);
- try testing.expectEqual(@as(usize, 23), bytes.len);
-
- for (0..4) |n| try testing.expectEqual(@as(?u32, null), frameLen(bytes[0..n]));
- try testing.expectEqual(@as(?u32, 23), frameLen(bytes[0..4]));
- try testing.expectEqual(@as(?u32, 23), frameLen(bytes));
-
- var raw: [4]u8 = .{ 0xFF, 0xFF, 0xFF, 0xFF };
- try testing.expectEqual(@as(?u32, std.math.maxInt(u32)), frameLen(&raw));
- raw = .{ 0, 0, 0, 0 };
- try testing.expectEqual(@as(?u32, 0), frameLen(&raw));
-}
-
-test "9p: encode refuses a short buffer and writes nothing" {
- var buf: [512]u8 = undefined;
- const want = (try encode(.{ .rstat = .{ .stat = sample_stat } }, 1, &buf)).len;
-
- var n: usize = 0;
- while (n < want) : (n += 1) {
- var scratch: [512]u8 = @splat(0xAA);
- try testing.expectError(error.NoSpace, encode(.{ .rstat = .{ .stat = sample_stat } }, 1, scratch[0..n]));
- for (scratch) |b| try testing.expectEqual(@as(u8, 0xAA), b);
- }
-
- var exact: [512]u8 = @splat(0xAA);
- try testing.expectEqual(want, (try encode(.{ .rstat = .{ .stat = sample_stat } }, 1, exact[0..want])).len);
- try testing.expectEqual(@as(u8, 0xAA), exact[want]);
-}
-
-test "9p: byte for byte against u9fs convS2M" {
- var buf: [512]u8 = undefined;
-
- try testing.expectEqualSlices(u8, &.{
- 0x13, 0x00, 0x00, 0x00, // size = 19
- 0x64, // Tversion = 100
- 0xff, 0xff, // NOTAG
- 0x00, 0x20, 0x00, 0x00, // msize = 8192
- 0x06, 0x00, // n = 6
- '9', 'P',
- '2', '0',
- '0', '0',
- }, try encode(.{ .tversion = .{ .msize = 8192, .version = "9P2000" } }, notag, &buf));
-
- try testing.expectEqualSlices(u8, &.{
- 0x1b, 0x00, 0x00, 0x00, // size = 27
- 0x6e, // Twalk = 110
- 0x01, 0x00, // tag = 1
- 0x01, 0x00, 0x00, 0x00, // fid = 1
- 0x02, 0x00, 0x00, 0x00, // newfid = 2
- 0x02, 0x00, // nwname = 2
- 0x03, 0x00,
- 'u', 's',
- 'r', 0x03,
- 0x00, 'b',
- 'i', 'n',
- }, try encode(.{ .twalk = .{
- .fid = 1,
- .newfid = 2,
- .nwname = 2,
- .wname = .{ "usr", "bin" } ++ @as([max_welem - 2][]const u8, @splat("")),
- } }, 1, &buf));
-
- try testing.expectEqualSlices(u8, &.{
- 0x0e, 0x00, 0x00, 0x00, // size = 14
- 0x75, // Rread = 117
- 0x09, 0x00, // tag = 9
- 0x03, 0x00, 0x00, 0x00, // count = 3
- 'a', 'b', 'c',
- }, try encode(.{ .rread = .{ .data = "abc" } }, 9, &buf));
-
- const one: Stat = .{
- .type = 0,
- .dev = 0,
- .qid = .{ .type = qtdir, .version = 1, .path = 2 },
- .mode = dmdir | 0o755,
- .atime = 3,
- .mtime = 4,
- .length = 0,
- .name = "a",
- .uid = "u",
- .gid = "g",
- .muid = "m",
- };
- try testing.expectEqual(@as(u16, 51), try one.size());
- try testing.expectEqualSlices(u8, &.{
- 0x3e, 0x00, 0x00, 0x00, // size = 62
- 0x7d, // Rstat = 125
- 0x07, 0x00, // tag = 7
- 0x35, 0x00, // OUTER count = 53 = 51 + 2
- 0x33, 0x00, // stat size = 51, excluding these two
- 0x00, 0x00, // type
- 0x00, 0x00, 0x00, 0x00, // dev
- 0x80, // qid.type = QTDIR
- 0x01, 0x00, 0x00, 0x00, // qid.version = 1
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // qid.path = 2
- 0xed, 0x01, 0x00, 0x80, // mode = DMDIR | 0755
- 0x03, 0x00, 0x00, 0x00, // atime
- 0x04, 0x00, 0x00, 0x00, // mtime
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // length
- 0x01, 0x00, 'a', // name
- 0x01, 0x00, 'u', // uid
- 0x01, 0x00, 'g', // gid
- 0x01, 0x00, 'm', // muid
- }, try encode(.{ .rstat = .{ .stat = one } }, 7, &buf));
-
- try testing.expectEqual(qtdir, @as(u8, @intCast(dmdir >> 24)));
- try testing.expectEqual(qtappend, @as(u8, @intCast(dmappend >> 24)));
- try testing.expectEqual(qtexcl, @as(u8, @intCast(dmexcl >> 24)));
- try testing.expectEqual(qtauth, @as(u8, @intCast(dmauth >> 24)));
- try testing.expectEqual(qttmp, @as(u8, @intCast(dmtmp >> 24)));
- try testing.expectEqual(@as(u32, 0o777), dmperm);
-}
-
pub const e_unknown_fid = "fid unknown or out of range";
pub const e_fid_in_use = "fid already in use";
pub const e_bad_use = "bad use of fid";
@@ -1222,18 +134,9 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
return struct {
const Self = @This();
- in: []u8,
- out: []u8,
+ protocol: cloud9.Server,
root: u64,
- in_len: usize = 0,
- frame: u32 = 0,
- out_len: usize = 0,
- out_off: usize = 0,
-
- msize: u32 = 0,
- dead: bool = false,
-
uname: [username_capacity]u8 = @splat(0),
uname_len: u8 = 0,
@@ -1306,7 +209,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
assert(opts.in.len >= msize_min);
assert(opts.out.len >= 2 * msize_min);
assert(opts.root != 0);
- return .{ .in = opts.in, .out = opts.out, .root = opts.root };
+ return .{ .protocol = .init(.{ .in = opts.in, .out = opts.out }), .root = opts.root };
}
pub fn references(s: *const Self, node: u64) bool {
@@ -1318,11 +221,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
pub fn hangup(s: *Self) void {
s.reset();
- s.dead = true;
- s.in_len = 0;
- s.frame = 0;
- s.out_len = 0;
- s.out_off = 0;
+ s.protocol.hangup();
}
fn reset(s: *Self) void {
@@ -1335,45 +234,25 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
}
pub fn push(s: *Self, bytes: []const u8) usize {
- if (s.dead) return 0;
- const n = @min(bytes.len, s.in.len - s.in_len);
- @memcpy(s.in[s.in_len..][0..n], bytes[0..n]);
- s.in_len += n;
- return n;
+ return s.protocol.push(bytes);
}
pub fn output(s: *const Self) []const u8 {
- return s.out[s.out_off..s.out_len];
+ return s.protocol.output();
}
pub fn wrote(s: *Self, n: usize) void {
- assert(n <= s.out_len - s.out_off);
- s.out_off += n;
- if (s.out_off == s.out_len) {
- s.out_off = 0;
- s.out_len = 0;
- }
- }
-
- fn compact(s: *Self) void {
- assert(s.out_off <= s.out_len);
- const n = s.out_len - s.out_off;
- std.mem.copyForwards(u8, s.out[0..n], s.out[s.out_off..s.out_len]);
- s.out_off = 0;
- s.out_len = n;
+ s.protocol.wrote(n);
}
fn hasRoom(s: *Self) bool {
- if (s.out_off != 0) s.compact();
- return s.out.len - s.out_len >= @max(s.msize, msize_min);
+ return s.protocol.hasRoom();
}
fn emit(s: *Self, tag: u16, msg: Msg) void {
- const bytes = encode(msg, tag, s.out[s.out_len..]) catch {
- s.dead = true;
- return;
+ s.protocol.reply(tag, msg) catch {
+ s.protocol.dead = true;
};
- s.out_len += bytes.len;
}
fn fail(s: *Self, tag: u16, ename: []const u8) void {
@@ -1477,44 +356,14 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
fn startFrame(s: *Self) bool {
assert(s.job.kind == .none);
- assert(s.frame == 0);
- if (s.dead) return false;
- const len = frameLen(s.in[0..s.in_len]) orelse return false;
- if (len < header_len or len > s.in.len) {
- s.dead = true;
- return false;
- }
- if (len > s.in_len) return false;
- s.frame = len;
+ const got = (s.protocol.receive() catch return false) orelse return false;
defer if (s.job.kind == .none) s.dropFrame();
-
- const got = decode(s.in[0..len]) catch {
- s.fail(std.mem.readInt(u16, s.in[5..7], .little), e_botch);
- return true;
- };
- if (!isT(got.msg.msgType())) {
- s.fail(got.tag, e_botch);
- return true;
- }
- if (s.msize == 0 and got.msg != .tversion) {
- s.fail(got.tag, e_botch);
- return true;
- }
- if (s.msize != 0 and len > s.msize) {
- s.fail(got.tag, e_botch);
- return true;
- }
s.dispatch(got);
return true;
}
fn dropFrame(s: *Self) void {
- assert(s.frame != 0);
- assert(s.frame <= s.in_len);
- const n = s.frame;
- std.mem.copyForwards(u8, s.in[0 .. s.in_len - n], s.in[n..s.in_len]);
- s.in_len -= n;
- s.frame = 0;
+ s.protocol.release();
}
fn dispatch(s: *Self, got: Decoded) void {
@@ -1537,13 +386,16 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
}
fn version(s: *Self, tag: u16, want: u32, ver: []const u8) void {
- const cap: u32 = @intCast(@min(s.in.len, s.out.len / 2));
- const m = @min(want, cap);
- if (m < msize_min) return s.fail(tag, e_small_msize);
- const known = std.mem.startsWith(u8, ver, "9P");
+ assert(tag == notag);
s.reset();
- s.msize = if (known) m else 0;
- s.emit(tag, .{ .rversion = .{ .msize = m, .version = if (known) "9P2000" else "unknown" } });
+ // The adapter's fixed directory staging requires this capacity.
+ if (want < msize_min) {
+ s.protocol.dead = true;
+ return;
+ }
+ s.protocol.negotiate(want, ver) catch {
+ s.protocol.dead = true;
+ };
}
fn attach(s: *Self, tag: u16, fid: u32, uname: []const u8, aname: []const u8) void {
@@ -1610,7 +462,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
const i = s.findFid(fid) orelse return s.fail(tag, e_unknown_fid);
const f = &s.fids[i];
if (!f.open or (f.omode & 3) == owrite) return s.fail(tag, e_bad_use);
- const want = @min(count, s.msize - header_len - 4);
+ const want = @min(count, s.protocol.msize - header_len - 4);
if (!f.dir) {
s.job = .{ .kind = .read, .tag = tag, .fid = fid, .offset = offset, .count = want };
return;
@@ -1821,7 +673,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
fn finishJob(s: *Self) void {
s.job = .{};
- if (s.frame != 0) s.dropFrame();
+ if (s.protocol.frame != 0) s.dropFrame();
}
fn jobReply(s: *Self, r: *const fs.Reply, bytes: []const u8) void {
@@ -1893,7 +745,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
f.dirindex = 0;
s.emit(j.tag, .{ .ropen = .{
.qid = qidOf(f.node, f.dir),
- .iounit = s.msize - iohdrsz,
+ .iounit = s.protocol.msize - iohdrsz,
} });
s.finishJob();
},
@@ -1914,7 +766,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
f.perm = r.attr.mode;
f.dir = r.attr.dir;
const response: Msg = .{ .rstat = .{ .stat = s.statOf(f, r.attr) } };
- if ((totalLen(response) catch unreachable) > s.msize)
+ if ((totalLen(response) catch unreachable) > s.protocol.msize)
s.fail(j.tag, e_small_msize)
else
s.emit(j.tag, response);
@@ -1988,7 +840,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
}
fn emitDirRead(s: *Self, tag: u16, fid: u32, staging: []const u8, count: u32) void {
- const buf = s.out[s.out_len..];
+ const buf = s.protocol.out[s.protocol.out_len..];
assert(buf.len > header_len + 4);
const cap = @min(@as(usize, count), buf.len - header_len - 4);
const who = s.uname[0..s.uname_len];
@@ -2022,11 +874,7 @@ pub fn Server(comptime fs: type, comptime fid_capacity: usize) type {
if (entries == 0 and staging.len != 0) return s.fail(tag, e_count_small);
const payload: u32 = @intCast(n - header_len - 4);
comptime assert(header_len == 7);
- std.mem.writeInt(u32, buf[0..4], @intCast(n), .little);
- buf[4] = @intFromEnum(Type.rread);
- std.mem.writeInt(u16, buf[5..7], tag, .little);
- std.mem.writeInt(u32, buf[7..11], payload, .little);
- s.out_len += n;
+ s.emit(tag, .{ .rread = .{ .data = buf[header_len + 4 .. n] } });
if (s.findFid(fid)) |k| {
s.fids[k].diroff += payload;
s.fids[k].dirindex += entries;
@@ -2315,12 +1163,10 @@ test "9p server: the version handshake clamps, falls back, and refuses" {
got = try h.reap();
try testing.expectEqualStrings("unknown", got.msg.rversion.version);
try h.send(1, .{ .tattach = .{ .fid = 0, .afid = nofid, .uname = "goblin", .aname = "" } });
- got = try h.reap();
- try testing.expectEqualStrings(e_botch, got.msg.rerror.ename);
-
+ try testing.expect(h.srv.protocol.dead);
+ h.start();
try h.send(notag, .{ .tversion = .{ .msize = 64, .version = "9P2000" } });
- got = try h.reap();
- try testing.expectEqualStrings(e_small_msize, got.msg.rerror.ename);
+ try testing.expect(h.srv.protocol.dead);
try testing.expectEqual(@as(u32, 216), msize_min - 1);
}
@@ -2450,7 +1296,7 @@ test "9p server: backend-sized filenames survive walk and stat within negotiated
var out: [2048]u8 = undefined;
var encoded: [1024]u8 = undefined;
var server = Native.init(.{ .in = &in, .out = &out, .root = 1 });
- server.msize = case.msize;
+ server.protocol.msize = case.msize;
server.setUname("u" ** username_capacity);
server.fids[0] = .{ .used = true, .fid = 0, .node = 1, .dir = true, .perm = 0o500 };
const name = filename[0..case.length];
@@ -2489,7 +1335,7 @@ test "9p server: backend-sized filenames survive walk and stat within negotiated
try testing.expectEqual(null, server.next());
response = try decode(server.output());
try testing.expect(response.msg == .rclunk);
- try testing.expect(!server.dead);
+ try testing.expect(!server.protocol.dead);
}
}
@@ -2636,6 +1482,13 @@ test "9p server: long directory names remain whole across pages" {
@memcpy(entries[275..], "next");
var found: [2][]const u8 = undefined;
+ {
+ var request: [64]u8 = undefined;
+ const bytes = try encode(.{ .tread = .{ .fid = 0, .offset = 0, .count = 330 } }, 5, &request);
+ _ = h.srv.protocol.push(bytes);
+ _ = (try h.srv.protocol.receive()).?;
+ h.srv.protocol.release();
+ }
h.srv.emitDirRead(5, 0, &entries, 330);
var got = try h.reap();
const first = got.msg.rread.data.len;
@@ -2643,6 +1496,13 @@ test "9p server: long directory names remain whole across pages" {
try testing.expectEqualStrings(entries[10..265], found[0]);
try testing.expectEqual(@as(u64, 1), h.srv.fids[0].dirindex);
+ {
+ var request: [64]u8 = undefined;
+ const bytes = try encode(.{ .tread = .{ .fid = 0, .offset = 0, .count = 330 } }, 6, &request);
+ _ = h.srv.protocol.push(bytes);
+ _ = (try h.srv.protocol.receive()).?;
+ h.srv.protocol.release();
+ }
h.srv.emitDirRead(6, 0, entries[265..], 330);
got = try h.reap();
try testing.expectEqual(@as(usize, 1), try dirNames(got.msg.rread.data, &found));
@@ -2650,6 +1510,13 @@ test "9p server: long directory names remain whole across pages" {
try testing.expectEqual(@as(u64, 2), h.srv.fids[0].dirindex);
try testing.expectEqual(first + got.msg.rread.data.len, h.srv.fids[0].diroff);
+ {
+ var request: [64]u8 = undefined;
+ const bytes = try encode(.{ .tread = .{ .fid = 0, .offset = 0, .count = 4096 } }, 7, &request);
+ _ = h.srv.protocol.push(bytes);
+ _ = (try h.srv.protocol.receive()).?;
+ h.srv.protocol.release();
+ }
h.srv.emitDirRead(7, 0, &entries, 4096);
got = try h.reap();
try testing.expectEqual(@as(usize, 2), try dirNames(got.msg.rread.data, &found));
@@ -2728,7 +1595,7 @@ test "9p server: the reply queue is a FIFO that survives a partial write" {
var flood: [8192]u8 = @splat(0);
try testing.expectEqual(@as(usize, 4096), h.srv.push(&flood));
h.pump();
- try testing.expect(h.srv.dead);
+ try testing.expect(h.srv.protocol.dead);
try testing.expectEqual(@as(usize, 0), h.srv.push(&flood));
}
@@ -2938,36 +1805,18 @@ test "9p server: a message arriving a byte at a time is served when its last byt
try h.quiet();
}
-test "9p server: what is not 9P2000 on this connection is refused, not guessed" {
- var h: Harness = .{};
- try h.handshake(4096);
-
- var buf: [64]u8 = undefined;
- const good = try encode(.{ .tstat = .{ .fid = 0 } }, 5, &buf);
-
- var raw: [64]u8 = undefined;
- @memcpy(raw[0..good.len], good);
- raw[4] = @intFromEnum(Type.rstat);
- try testing.expectEqual(good.len, h.srv.push(raw[0..good.len]));
- h.pump();
- var got = try h.reap();
- try testing.expectEqual(@as(u16, 5), got.tag);
- try testing.expectEqualStrings(e_botch, got.msg.rerror.ename);
-
- @memcpy(raw[0..good.len], good);
- raw[4] = 8;
- _ = h.srv.push(raw[0..good.len]);
- h.pump();
- got = try h.reap();
- try testing.expectEqual(@as(u16, 5), got.tag);
- try testing.expectEqualStrings(e_botch, got.msg.rerror.ename);
-
- @memcpy(raw[0..good.len], good);
- std.mem.writeInt(u32, raw[0..4], 3, .little);
- _ = h.srv.push(raw[0..good.len]);
- h.pump();
- try h.quiet();
- try testing.expect(h.srv.dead);
+test "9p server: invalid framing and reply types terminate the connection" {
+ for ([_]u8{ @intFromEnum(Type.rstat), 8, 0 }) |kind| {
+ var h: Harness = .{};
+ try h.handshake(4096);
+ var buf: [64]u8 = undefined;
+ const raw = try encode(.{ .tstat = .{ .fid = 0 } }, 5, &buf);
+ if (kind == 0) std.mem.writeInt(u32, raw[0..4], 3, .little) else raw[4] = kind;
+ _ = h.srv.push(raw);
+ h.pump();
+ try h.quiet();
+ try testing.expect(h.srv.protocol.dead);
+ }
}
test "9p server: a connection that drops still pays the core its releases" {
@@ -3060,314 +1909,6 @@ test "9p server: board and native capacities size the actual fid storage" {
try testing.expect(3 * 4096 + @sizeOf(Board) <= 24 * 1024);
}
-pub const max_tags: usize = 16;
-
-const twrite_header: usize = header_len + 4 + 8 + 4;
-
-const rread_header: usize = header_len + 4;
-
-comptime {
- assert(twrite_header == 23);
- assert(rread_header == 11);
- assert(max_tags <= notag);
- assert(msize_min > rread_header);
- assert(msize_min > twrite_header);
-}
-
-pub const ClientError = error{
- NoTags,
- NoSpace,
- TooLarge,
- Handshake,
- Dead,
- BadRequest,
-};
-
-pub const Client = struct {
- in: []u8,
- out: []u8,
-
- in_len: usize = 0,
- frame: u32 = 0,
- out_len: usize = 0,
- out_off: usize = 0,
-
- msize: u32 = 0,
- asked: u32 = 0,
- versioning: bool = false,
- dead: bool = false,
-
- tags: [max_tags]Slot = @splat(.{}),
-
- const Slot = struct {
- op: ?Op = null,
- count: u32 = 0,
- };
-
- pub const Op = enum { version, attach, walk, open, read, write, clunk, stat };
-
- pub const Request = union(Op) {
- version: struct { msize: u32 = 0 },
- attach: struct { fid: u32, uname: []const u8, aname: []const u8 = "" },
- walk: struct { fid: u32, newfid: u32, names: []const []const u8 },
- open: struct { fid: u32, mode: u8 },
- read: struct { fid: u32, offset: u64, count: u32 },
- write: struct { fid: u32, offset: u64, data: []const u8 },
- clunk: struct { fid: u32 },
- stat: struct { fid: u32 },
- };
-
- pub const Result = union(enum) {
- fail: []const u8,
- version: struct { msize: u32, version: []const u8 },
- attach: Qid,
- walk: struct { nwqid: u16, wqid: [max_welem]Qid },
- open: struct { qid: Qid, iounit: u32 },
- read: []const u8,
- write: u32,
- clunk: void,
- stat: Stat,
- };
-
- pub const Done = struct {
- tag: u16,
- op: Op,
- result: Result,
- };
-
- pub const Options = struct {
- in: []u8,
- out: []u8,
- };
-
- pub fn init(opts: Options) Client {
- assert(opts.in.len >= msize_min);
- assert(opts.out.len >= msize_min);
- return .{ .in = opts.in, .out = opts.out };
- }
-
- pub fn hangup(c: *Client) void {
- c.dead = true;
- c.tags = @splat(.{});
- c.versioning = false;
- c.msize = 0;
- c.asked = 0;
- c.in_len = 0;
- c.frame = 0;
- c.out_len = 0;
- c.out_off = 0;
- }
-
- pub fn push(c: *Client, bytes: []const u8) usize {
- if (c.dead) return 0;
- const n = @min(bytes.len, c.in.len - c.in_len);
- @memcpy(c.in[c.in_len..][0..n], bytes[0..n]);
- c.in_len += n;
- return n;
- }
-
- pub fn output(c: *const Client) []const u8 {
- return c.out[c.out_off..c.out_len];
- }
-
- pub fn wrote(c: *Client, n: usize) void {
- assert(n <= c.out_len - c.out_off);
- c.out_off += n;
- if (c.out_off == c.out_len) {
- c.out_off = 0;
- c.out_len = 0;
- }
- }
-
- fn compact(c: *Client) void {
- assert(c.out_off <= c.out_len);
- const n = c.out_len - c.out_off;
- std.mem.copyForwards(u8, c.out[0..n], c.out[c.out_off..c.out_len]);
- c.out_off = 0;
- c.out_len = n;
- }
-
- fn dropFrame(c: *Client) void {
- assert(c.frame != 0);
- assert(c.frame <= c.in_len);
- const n = c.frame;
- std.mem.copyForwards(u8, c.in[0 .. c.in_len - n], c.in[n..c.in_len]);
- c.in_len -= n;
- c.frame = 0;
- }
-
- pub fn maxRead(c: *const Client) u32 {
- if (c.msize == 0) return 0;
- return c.msize - @as(u32, @intCast(rread_header));
- }
-
- pub fn maxWrite(c: *const Client) u32 {
- if (c.msize == 0) return 0;
- return c.msize - @as(u32, @intCast(twrite_header));
- }
-
- pub fn pending(c: *const Client) usize {
- var n: usize = @intFromBool(c.versioning);
- for (c.tags) |t| n += @intFromBool(t.op != null);
- return n;
- }
-
- pub fn submit(c: *Client, req: Request) ClientError!u16 {
- if (c.dead) return error.Dead;
- if (req == .version) return c.beginVersion(req.version.msize);
- if (c.msize == 0 or c.versioning) return error.Handshake;
-
- const msg: Msg = switch (req) {
- .version => unreachable, // handled above
- .attach => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- break :blk .{ .tattach = .{
- .fid = m.fid,
- .afid = nofid,
- .uname = m.uname,
- .aname = m.aname,
- } };
- },
- .walk => |m| blk: {
- if (m.fid == nofid or m.newfid == nofid) return error.BadRequest;
- if (m.names.len > max_welem) return error.BadRequest;
- var w: [max_welem][]const u8 = @splat("");
- for (m.names, 0..) |n, i| {
- if (n.len == 0) return error.BadRequest;
- if (std.mem.indexOfAny(u8, n, "/\x00") != null) return error.BadRequest;
- w[i] = n;
- }
- break :blk .{ .twalk = .{
- .fid = m.fid,
- .newfid = m.newfid,
- .nwname = @intCast(m.names.len),
- .wname = w,
- } };
- },
- .open => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- break :blk .{ .topen = .{ .fid = m.fid, .mode = m.mode } };
- },
- .read => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- if (m.count > c.maxRead()) return error.TooLarge;
- break :blk .{ .tread = .{ .fid = m.fid, .offset = m.offset, .count = m.count } };
- },
- .write => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- break :blk .{ .twrite = .{ .fid = m.fid, .offset = m.offset, .data = m.data } };
- },
- .clunk => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- break :blk .{ .tclunk = .{ .fid = m.fid } };
- },
- .stat => |m| blk: {
- if (m.fid == nofid) return error.BadRequest;
- break :blk .{ .tstat = .{ .fid = m.fid } };
- },
- };
- const need = totalLen(msg) catch return error.TooLarge;
- if (need > c.msize) return error.TooLarge;
-
- const op = std.meta.activeTag(req);
- const tag = c.claim(op) orelse return error.NoTags;
- errdefer c.tags[tag] = .{};
- try c.emit(tag, msg);
- if (op == .read) c.tags[tag].count = req.read.count;
- return tag;
- }
-
- fn beginVersion(c: *Client, want: u32) ClientError!u16 {
- if (c.pending() != 0) return error.Handshake;
- const cap: u32 = @intCast(@min(c.in.len, c.out.len, std.math.maxInt(u32)));
- const m = @min(if (want == 0) cap else want, cap);
- if (m < msize_min) return error.BadRequest;
- try c.emit(notag, .{ .tversion = .{ .msize = m, .version = "9P2000" } });
- c.msize = 0;
- c.asked = m;
- c.versioning = true;
- return notag;
- }
-
- fn claim(c: *Client, op: Op) ?u16 {
- for (&c.tags, 0..) |*t, i| {
- if (t.op != null) continue;
- t.* = .{ .op = op };
- return @intCast(i);
- }
- return null;
- }
-
- fn emit(c: *Client, tag: u16, msg: Msg) ClientError!void {
- if (c.out_off != 0) c.compact();
- const bytes = encode(msg, tag, c.out[c.out_len..]) catch return error.NoSpace;
- c.out_len += bytes.len;
- }
-
- pub fn take(c: *Client) ?Done {
- if (c.frame != 0) c.dropFrame();
- if (c.dead) return null;
- const len = frameLen(c.in[0..c.in_len]) orelse return null;
- if (len < header_len or len > c.in.len) return c.die();
- if (c.msize != 0 and len > c.msize) return c.die();
- if (len > c.in_len) return null;
- c.frame = len;
- const got = decode(c.in[0..len]) catch return c.die();
- return c.consume(got);
- }
-
- fn die(c: *Client) ?Done {
- c.dead = true;
- return null;
- }
-
- fn consume(c: *Client, got: Decoded) ?Done {
- if (isT(got.msg.msgType())) return c.die();
- if (got.msg == .rversion) return c.version(got);
- if (c.versioning or c.msize == 0) return c.die();
- if (got.tag >= max_tags) return c.die();
- const slot = &c.tags[got.tag];
- const op = slot.op orelse return c.die();
- const result: Result = switch (got.msg) {
- .rerror => |m| .{ .fail = m.ename },
- .rattach => |m| if (op != .attach) return c.die() else .{ .attach = m.qid },
- .rwalk => |m| if (op != .walk) return c.die() else .{
- .walk = .{ .nwqid = m.nwqid, .wqid = m.wqid },
- },
- .ropen => |m| if (op != .open) return c.die() else .{
- .open = .{ .qid = m.qid, .iounit = m.iounit },
- },
- .rread => |m| blk: {
- if (op != .read) return c.die();
- if (m.data.len > slot.count) return c.die();
- break :blk .{ .read = m.data };
- },
- .rwrite => |m| if (op != .write) return c.die() else .{ .write = m.count },
- .rclunk => if (op != .clunk) return c.die() else .clunk,
- .rstat => |m| if (op != .stat) return c.die() else .{ .stat = m.stat },
- else => return c.die(),
- };
- slot.* = .{};
- return .{ .tag = got.tag, .op = op, .result = result };
- }
-
- fn version(c: *Client, got: Decoded) ?Done {
- if (!c.versioning) return c.die();
- if (got.tag != notag) return c.die();
- const m = got.msg.rversion;
- if (m.msize > c.asked or m.msize < msize_min) return c.die();
- c.versioning = false;
- if (std.mem.eql(u8, m.version, "9P2000")) {
- c.msize = m.msize;
- } else if (!std.mem.eql(u8, m.version, "unknown")) {
- return c.die();
- }
- return .{ .tag = notag, .op = .version, .result = .{
- .version = .{ .msize = m.msize, .version = m.version },
- } };
- }
-};
-
const Pair = struct {
srv_in: [4096]u8 = undefined,
srv_out: [8192]u8 = undefined,
@@ -3647,7 +2188,7 @@ test "9p client: an msize below the floor, and one the server tried to raise" {
var buf: [64]u8 = undefined;
var c = Client.init(.{ .in = &in, .out = &out });
- try testing.expectError(error.BadRequest, c.submit(.{ .version = .{ .msize = msize_min - 1 } }));
+ try testing.expectError(error.BadRequest, c.submit(.{ .version = .{ .msize = cloud9.Server.msize_min - 1 } }));
_ = try c.submit(.{ .version = .{} });
try testing.expectEqual(@as(u32, 512), c.asked);
@@ -3771,9 +2312,8 @@ test "9p client: hangup and a dead connection refuse everything after" {
try testing.expectError(error.Dead, p.cli.submit(.{ .version = .{} }));
}
-test "9p client: one session is a hundred and change bytes plus its buffers" {
- try testing.expect(@sizeOf(Client.Slot) <= 8);
- try testing.expect(@sizeOf(Client) <= 256);
+test "9p client: session memory is bounded independently of input" {
+ try testing.expect(@sizeOf(Client) <= 512);
try testing.expect(2 * 8192 + @sizeOf(Client) <= 17 * 1024);
- try testing.expect(2 * msize_min + @sizeOf(Client) <= 700);
+ try testing.expect(2 * cloud9.Server.msize_min + @sizeOf(Client) <= 700);
}