library: five protocols speak the envelope
The folded header stops being a rule in a document and becomes the layout on the wire. Verbs number from sixteen, leaving describe, enumerate, subscribe and unsubscribe reserved and answered the same way by every provider — none of them writes a line to do it. What each protocol used to carry in a field of its own now travels in the header: a vfs node and a display layer are the packet's target, and a reply opens with a status the envelope stamps rather than one each protocol spelled for itself. Display gains the most. One forty-byte request had served eleven verbs, so attach_scanout smuggled stride through x, refresh through y and format through colour, and every coordinate crossed as a bitcast. Per-operation structs end all three: the fields have their own names and their own signs, and the tile payload grows to 224 bytes because the prefix shrank. Scanout loses a message maximum of 64 it had no business declaring — it answers calls, and the floor for a call is 256 — and virtio-gpu stops hard-coding that number at its harness. Two changes are semantic rather than notational. A directory now ends at an entry with no name, because the fixed part of a reply always travels and a zero-length reply no longer exists to mean anything. And input joins the service harness, the last loop in the tree that answered no ping and heard no terminate; its subscriber table, its pruning and its fan-out are the same code, and a shutdown now asks it to stop instead of killing it. A new conformance case reads the registry's own listing and asks every protocol it finds for its name, its version and its verb count, then offers a verb nobody defines and requires -ENOSYS — the envelope's promise, checked against providers rather than against itself. What it cannot reach in that boot it names on the serial line instead of passing quietly. Suite 110/110.
This commit is contained in:
@@ -548,34 +548,43 @@ var heartbeat_running = false;
|
||||
/// `pending_capability`. `arrived` is the capability the *request* carried, owned
|
||||
/// by the turn — nothing here has to close it, only `bind` has to claim it.
|
||||
fn serveRegistry(request_bytes: []const u8, reply: []u8, sender: u32, arrived: *Arrival) usize {
|
||||
if (request_bytes.len < vfs_protocol.request_size)
|
||||
return answer(reply, -envelope.EPROTO, 0, 0);
|
||||
// The header is read field by field rather than reinterpreted whole: the
|
||||
// operation is an enum on the wire and the bytes come from anyone at all, so
|
||||
// a value outside it must be a refusal, never a decoded enum.
|
||||
if (request_bytes.len < envelope.prefix_size)
|
||||
return answer(reply, -envelope.EPROTO, 0);
|
||||
// The header is read field by field rather than reinterpreted whole, and the
|
||||
// verb is compared as a number rather than decoded into the generated
|
||||
// `Operation`: the bytes come from anyone at all, so a value outside the enum
|
||||
// must be a refusal, never an `@enumFromInt`. This is deliberately NOT
|
||||
// `Protocol.Provider.dispatch` for the same reason — PID 1 reads a stranger's
|
||||
// packet, and it reads it by hand.
|
||||
const operation = std.mem.readInt(u32, request_bytes[0..4], .little);
|
||||
const cursor = std.mem.readInt(u64, request_bytes[16..24], .little);
|
||||
const declared = std.mem.readInt(u32, request_bytes[24..28], .little);
|
||||
const payload_len = @min(@as(usize, declared), request_bytes.len - vfs_protocol.request_size);
|
||||
const payload = request_bytes[vfs_protocol.request_size..][0..payload_len];
|
||||
const body = request_bytes[envelope.prefix_size..];
|
||||
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.bind))
|
||||
return answer(reply, onBind(sender, payload, arrived), 0, 0);
|
||||
return answer(reply, onBind(sender, body, arrived), 0);
|
||||
// Only `bind` claims a capability; one attached to anything else is closed by
|
||||
// the turn's `defer` in the loop, along with the ones sent to a request that
|
||||
// was too short to name a verb at all.
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.open)) return onOpen(reply, sender, payload);
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.readdir)) return onReaddir(reply, cursor);
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.open)) {
|
||||
// `open`'s fixed part is the flags word, which means nothing to a
|
||||
// namespace; the name follows it as the packet's tail.
|
||||
if (body.len < @sizeOf(vfs_protocol.Open)) return answer(reply, -envelope.EPROTO, 0);
|
||||
return onOpen(reply, sender, body[@sizeOf(vfs_protocol.Open)..]);
|
||||
}
|
||||
if (operation == @intFromEnum(vfs_protocol.Operation.readdir)) {
|
||||
if (body.len < @sizeOf(vfs_protocol.Readdir)) return answer(reply, -envelope.EPROTO, 0);
|
||||
return onReaddir(reply, std.mem.readInt(u64, body[0..8], .little));
|
||||
}
|
||||
// Everything else a filesystem answers is meaningless here: `/protocol` holds
|
||||
// contracts, not bytes.
|
||||
return answer(reply, -envelope.ENOSYS, 0, 0);
|
||||
return answer(reply, -envelope.ENOSYS, 0);
|
||||
}
|
||||
|
||||
/// Lay down a vfs reply header (and say how many payload bytes follow it).
|
||||
fn answer(reply: []u8, status: i32, node: u64, payload_len: usize) usize {
|
||||
const header = vfs_protocol.Reply{ .status = status, .node = node, .len = @intCast(payload_len) };
|
||||
@memcpy(reply[0..vfs_protocol.reply_size], std.mem.asBytes(&header));
|
||||
return vfs_protocol.reply_size + payload_len;
|
||||
/// Lay down the envelope's reply prefix (and say how many payload bytes the
|
||||
/// caller has already written after it).
|
||||
fn answer(reply: []u8, status: i32, payload_len: usize) usize {
|
||||
const header = envelope.Status{ .status = status, .len = @intCast(payload_len) };
|
||||
@memcpy(reply[0..envelope.prefix_size], std.mem.asBytes(&header));
|
||||
return envelope.prefix_size + payload_len;
|
||||
}
|
||||
|
||||
/// `bind(name, capability = the provider's endpoint)`. The capability is the
|
||||
@@ -667,15 +676,19 @@ fn onBind(sender: u32, raw_name: []const u8, arrived: *Arrival) i32 {
|
||||
/// other, a line in a world-readable log ring, or a serial write costing
|
||||
/// milliseconds.)
|
||||
fn onOpen(reply: []u8, sender: u32, raw_name: []const u8) usize {
|
||||
const name = contractName(raw_name) orelse return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
const name = contractName(raw_name) orelse return answer(reply, -envelope.ENOENT, 0);
|
||||
refreshProcessTable();
|
||||
const identity = identify(sender);
|
||||
const permitted = if (identity) |who| mayOpen(who, name) else false;
|
||||
const binding = findBinding(name);
|
||||
if (!permitted) return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
const found = binding orelse return answer(reply, -envelope.ENOENT, 0, 0);
|
||||
if (!permitted) return answer(reply, -envelope.ENOENT, 0);
|
||||
const found = binding orelse return answer(reply, -envelope.ENOENT, 0);
|
||||
pending_capability = found.endpoint;
|
||||
return answer(reply, 0, 0, 0);
|
||||
// A contract node has no node id — the capability is the whole answer — but
|
||||
// the protocol says an `open` reply carries one, so it carries a zero.
|
||||
const opened = vfs_protocol.Opened{ .node = 0 };
|
||||
@memcpy(reply[envelope.prefix_size..][0..@sizeOf(vfs_protocol.Opened)], std.mem.asBytes(&opened));
|
||||
return answer(reply, 0, @sizeOf(vfs_protocol.Opened));
|
||||
}
|
||||
|
||||
/// `readdir(cursor)` — the namespace, browsable. One entry per turn, as the vfs
|
||||
@@ -690,18 +703,25 @@ fn onReaddir(reply: []u8, cursor: u64) usize {
|
||||
continue;
|
||||
}
|
||||
const name = binding.nameSlice();
|
||||
const entry = vfs_protocol.DirectoryEntry{
|
||||
return writeEntry(reply, .{
|
||||
.kind = @intFromEnum(vfs_protocol.NodeKind.protocol),
|
||||
.name_len = @intCast(name.len),
|
||||
.size = binding.task,
|
||||
};
|
||||
const total = vfs_protocol.directory_entry_size + name.len;
|
||||
if (vfs_protocol.reply_size + total > reply.len) return answer(reply, -envelope.EPROTO, 0, 0);
|
||||
@memcpy(reply[vfs_protocol.reply_size..][0..vfs_protocol.directory_entry_size], std.mem.asBytes(&entry));
|
||||
@memcpy(reply[vfs_protocol.reply_size + vfs_protocol.directory_entry_size ..][0..name.len], name);
|
||||
return answer(reply, 0, 0, total);
|
||||
}, name);
|
||||
}
|
||||
return answer(reply, 0, 0, 0); // end of directory
|
||||
// End of directory, which the envelope spells as an entry with no name: the
|
||||
// reply's own length cannot say it any more, because the fixed reply part
|
||||
// always travels.
|
||||
return writeEntry(reply, .{}, &.{});
|
||||
}
|
||||
|
||||
/// One `readdir` reply: the entry, then its name inline.
|
||||
fn writeEntry(reply: []u8, entry: vfs_protocol.DirectoryEntry, name: []const u8) usize {
|
||||
const total = vfs_protocol.directory_entry_size + name.len;
|
||||
if (envelope.prefix_size + total > reply.len) return answer(reply, -envelope.EPROTO, 0);
|
||||
@memcpy(reply[envelope.prefix_size..][0..vfs_protocol.directory_entry_size], std.mem.asBytes(&entry));
|
||||
@memcpy(reply[envelope.prefix_size + vfs_protocol.directory_entry_size ..][0..name.len], name);
|
||||
return answer(reply, 0, total);
|
||||
}
|
||||
|
||||
pub fn main(startup: process.Init) void {
|
||||
|
||||
Reference in New Issue
Block a user