//! Browser ABI. Protocol state, encoding, reply validation, and directory decoding //! all use the same cloud9 sources as native clients. No allocator or host imports. const std = @import("std"); const c9 = @import("cloud9"); const capacity = 65536; var input: [capacity]u8 = undefined; var output: [capacity]u8 = undefined; var staging: [capacity]u8 = undefined; var json: [capacity * 2]u8 = undefined; var client: c9.Client = undefined; var data: []const u8 = ""; var number: u32 = 0; var directory: bool = false; export fn init() void { client = .init(.{ .in = &input, .out = &output }); data = ""; number = 0; directory = false; } export fn input_ptr() [*]u8 { return &staging; } export fn input_capacity() u32 { return capacity; } export fn output_ptr() [*]const u8 { return client.output().ptr; } export fn output_len() u32 { return @intCast(client.output().len); } export fn sent() void { client.wrote(client.output().len); } export fn data_ptr() [*]const u8 { return data.ptr; } export fn data_len() u32 { return @intCast(data.len); } export fn result_number() u32 { return number; } export fn result_directory() bool { return directory; } export fn is_dead() bool { return client.dead; } export fn max_read() u32 { return client.maxRead(); } export fn max_write() u32 { return client.maxWrite(); } fn fail(message: []const u8) i32 { data = message; return -1; } fn submit(request: c9.Client.Request) i32 { _ = client.submit(request) catch |err| return fail(@errorName(err)); return 0; } export fn version() i32 { return submit(.{ .version = .{} }); } export fn attach(user_len: u32, tree_len: u32) i32 { if (user_len > capacity or tree_len > capacity - user_len) return fail("InputTooLarge"); return submit(.{ .attach = .{ .fid = 0, .uname = staging[0..user_len], .aname = staging[user_len..][0..tree_len] } }); } export fn walk(fid: u32, newfid: u32, len: u32) i32 { if (len > capacity) return fail("InputTooLarge"); var names: [c9.max_welem][]const u8 = undefined; var count: usize = 0; var it = std.mem.splitScalar(u8, staging[0..len], '/'); while (it.next()) |name| { if (name.len == 0) continue; if (count == names.len) return fail("TooManyNames"); names[count] = name; count += 1; } return submit(.{ .walk = .{ .fid = fid, .newfid = newfid, .names = names[0..count] } }); } export fn open(fid: u32, mode: u8) i32 { return submit(.{ .open = .{ .fid = fid, .mode = mode } }); } export fn read(fid: u32, offset: u64, count: u32) i32 { return submit(.{ .read = .{ .fid = fid, .offset = offset, .count = count } }); } export fn write(fid: u32, offset: u64, len: u32) i32 { if (len > capacity) return fail("InputTooLarge"); return submit(.{ .write = .{ .fid = fid, .offset = offset, .data = staging[0..len] } }); } export fn clunk(fid: u32) i32 { return submit(.{ .clunk = .{ .fid = fid } }); } export fn stat(fid: u32) i32 { return submit(.{ .stat = .{ .fid = fid } }); } fn writeStat(s: c9.Stat, writer: *std.Io.Writer) !void { try std.json.Stringify.value(.{ .name = s.name, .directory = s.qid.type & c9.qtdir != 0, .length = s.length, .mode = s.mode }, .{}, writer); } /// Result codes: 0 incomplete, -1 error, 1 version, 2 attach, 3 walk, /// 4 open, 5 read, 6 write, 7 clunk, 8 stat. Data is borrowed until next call. export fn receive(len: u32) i32 { if (len > capacity) return fail("InputTooLarge"); if (client.push(staging[0..len]) != len) return fail("InputFull"); const done = client.take() orelse return if (client.dead) fail("ProtocolError") else 0; data = ""; number = 0; directory = false; return switch (done.result) { .fail => |message| fail(message), .version => |v| blk: { data = v.version; number = v.msize; break :blk 1; }, .attach => |qid| blk: { directory = qid.type & c9.qtdir != 0; break :blk 2; }, .walk => |w| blk: { number = w.nwqid; if (w.nwqid != 0) directory = w.wqid[w.nwqid - 1].type & c9.qtdir != 0; break :blk 3; }, .open => |o| blk: { number = o.iounit; directory = o.qid.type & c9.qtdir != 0; break :blk 4; }, .read => |bytes| blk: { data = bytes; break :blk 5; }, .write => |count| blk: { number = count; break :blk 6; }, .clunk => 7, .stat => |s| blk: { var writer: std.Io.Writer = .fixed(&json); writeStat(s, &writer) catch return fail("OutputTooLarge"); data = writer.buffered(); break :blk 8; }, else => fail("UnexpectedReply"), }; } /// Parse complete directory stat records from one Rread, using cloud9.Stat. export fn decode_directory(len: u32) i32 { if (len > capacity) return fail("InputTooLarge"); var remaining: []const u8 = staging[0..len]; var writer: std.Io.Writer = .fixed(&json); writer.writeByte('[') catch unreachable; var first = true; while (remaining.len != 0) { if (remaining.len < 2) return fail("TruncatedDirectory"); const size: usize = @as(usize, std.mem.readInt(u16, remaining[0..2], .little)) + 2; if (size > remaining.len) return fail("TruncatedDirectory"); const s = c9.Stat.decode(remaining[0..size]) catch return fail("InvalidDirectory"); if (!first) writer.writeByte(',') catch return fail("OutputTooLarge"); writeStat(s, &writer) catch return fail("OutputTooLarge"); first = false; remaining = remaining[size..]; } writer.writeByte(']') catch return fail("OutputTooLarge"); data = writer.buffered(); return 0; }