A protocol is reached by name now, not by a compile-time integer. Init is PID 1 and already knows which binary it started, so init serves /protocol as a vfs backend: bind claims a contract with the provider's endpoint attached, open answers with that endpoint as the reply's capability, and readdir lists what is bound with the task and binary behind it. The kernel reserves the prefix — nothing may mount over it, under it, or unmount it — and ServiceId, ipc_register and ipc_lookup are gone, their syscall numbers left vacant. A bind is authorized by who the caller *is*: the kernel-stamped binary together with the supervising task's identity, matched against /system/configuration/protocol.csv. Identity, not spelling — spawn is ungated, so an attacker can run any bundled binary, and a name-only rule would have let it launder grants through an init of its own making. A name a live process holds is refused to everyone else; a dead one's is released. Three review rounds against a hostile ring-3 process found what 108 green tests could not, because the suite contains no attacker. Publishing init's supervision endpoint as the registry put PID 1's mailbox in every process's hands, where two forged bytes reached the shutdown path: privileged traffic is now believed only from the task that holds the contract it speaks for. A capability arriving on a request outlived every path that ignored it, one handle per call until the table was full — in init, and in the harness ten services share — so the arriving capability is owned by the turn and released unless a handler says otherwise. And the kernel let anyone holding an endpoint handle aim signals, timers, exit notices and interrupts at it: binding now requires having created it. Suite 108/108. The new protocol-registry case asserts eleven properties, each one an attack that must fail.
221 lines
11 KiB
Zig
221 lines
11 KiB
Zig
//! User-space input helpers: the client and publisher sides of the input service, so a
|
|
//! program listening for input events — or a driver broadcasting them — doesn't hand-roll
|
|
//! the IPC. Layered over `ipc` (endpoints, capability passing, `send`) and the shared
|
|
//! `input-protocol` wire format, the way `device.zig` layers over the raw `device_*` calls.
|
|
//! See system/services/input/input.zig.
|
|
//!
|
|
//! The service carries several device classes (keyboard, mouse, joystick/gamepad). A
|
|
//! **source** publishes its class with the matching method:
|
|
//! var source = input.connectSource() orelse return;
|
|
//! _ = source.publishKeyboardEvent(.{ .kind = ..., .keycode = ..., ... });
|
|
//! _ = source.publishMouseEvent(.{ ... });
|
|
//! _ = source.publishJoystickEvent(.{ ... });
|
|
//!
|
|
//! A **subscriber** either takes one class with a typed helper —
|
|
//! var keys = input.subscribeKeyboard() orelse return;
|
|
//! while (true) { const key = keys.next() orelse continue; ... }
|
|
//! — or takes several at once and inspects the tagged envelope:
|
|
//! var listener = input.subscribeAll() orelse return;
|
|
//! while (true) {
|
|
//! const event = listener.next() orelse continue;
|
|
//! if (event.asKeyboard()) |k| { ... } else if (event.asMouse()) |m| { ... }
|
|
//! }
|
|
|
|
const std = @import("std");
|
|
const channel = @import("channel");
|
|
const ipc = @import("ipc");
|
|
const time = @import("time");
|
|
const input_protocol = @import("input-protocol");
|
|
|
|
pub const DeviceKind = input_protocol.DeviceKind;
|
|
pub const InputEvent = input_protocol.InputEvent;
|
|
pub const KeyEvent = input_protocol.KeyEvent;
|
|
pub const MouseEvent = input_protocol.MouseEvent;
|
|
pub const JoystickEvent = input_protocol.JoystickEvent;
|
|
pub const EventKind = input_protocol.EventKind;
|
|
pub const MouseEventKind = input_protocol.MouseEventKind;
|
|
pub const JoystickEventKind = input_protocol.JoystickEventKind;
|
|
pub const Keycode = input_protocol.Keycode;
|
|
|
|
/// Interest masks re-exported so a caller can `subscribe(input.device_keyboard |
|
|
/// input.device_mouse)`.
|
|
pub const device_keyboard = input_protocol.device_keyboard;
|
|
pub const device_mouse = input_protocol.device_mouse;
|
|
pub const device_joystick = input_protocol.device_joystick;
|
|
pub const device_all = input_protocol.device_all;
|
|
|
|
/// Open `/protocol/input`, retrying while it is still coming up. Both a subscriber and
|
|
/// a source race the service's bind at boot, so both wait for it here rather than
|
|
/// failing. Returns the provider's endpoint handle, or null if it never appears.
|
|
fn lookupService() ?ipc.Handle {
|
|
var attempts: usize = 0;
|
|
while (attempts < 100) : (attempts += 1) {
|
|
if (channel.openEndpoint("input")) |handle| return handle;
|
|
time.sleepMillis(50);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// --- subscribing ------------------------------------------------------------
|
|
|
|
/// A subscription to the input service: our own endpoint, which the service pushes events
|
|
/// to. `next` returns each event as a tagged `InputEvent`; use `asKeyboard`/`asMouse`/
|
|
/// `asJoystick` to decode. Created with `subscribe`/`subscribeAll`; for a single device
|
|
/// class prefer the typed helpers (`subscribeKeyboard`, ...), which return decoded events.
|
|
pub const Subscriber = struct {
|
|
/// The endpoint the service delivers events to (created and owned by us; its handle
|
|
/// was handed to the service as a capability at subscribe time).
|
|
endpoint: ipc.Handle,
|
|
receive: [input_protocol.event_size]u8 = undefined,
|
|
|
|
/// Block until the next event is pushed, and return it. Events arrive as asynchronous
|
|
/// buffered messages (`ipc_send` from the service), so nothing is owed in reply — the
|
|
/// empty reply this issues is a harmless no-op. Returns null for any non-event wake-up
|
|
/// (there should be none), so callers can loop.
|
|
pub fn next(self: *Subscriber) ?InputEvent {
|
|
const got = ipc.replyWait(self.endpoint, &.{}, &self.receive, null);
|
|
if (!got.isMessage() or got.len < input_protocol.event_size) return null;
|
|
return std.mem.bytesToValue(InputEvent, self.receive[0..input_protocol.event_size]);
|
|
}
|
|
};
|
|
|
|
/// Subscribe to the input classes named in `device_mask` (an OR of `device_*`, or
|
|
/// `device_all`). Creates an endpoint for the service to push to and hands it over as a
|
|
/// capability. Returns a `Subscriber` to loop `next` on, or null on failure.
|
|
pub fn subscribe(device_mask: u32) ?Subscriber {
|
|
const service = lookupService() orelse return null;
|
|
const endpoint = ipc.createIpcEndpoint() orelse return null;
|
|
|
|
var request = input_protocol.Request{ .operation = @intFromEnum(input_protocol.Operation.subscribe), .device_mask = device_mask };
|
|
var reply: [input_protocol.reply_size]u8 = undefined;
|
|
const result = ipc.callCap(service, std.mem.asBytes(&request), &reply, endpoint) catch return null;
|
|
if (result.len < input_protocol.reply_size) return null;
|
|
if (std.mem.bytesToValue(input_protocol.Reply, reply[0..input_protocol.reply_size]).status != 0) return null;
|
|
return .{ .endpoint = endpoint };
|
|
}
|
|
|
|
/// Subscribe to every input class (keyboard, mouse, joystick) on one stream.
|
|
pub fn subscribeAll() ?Subscriber {
|
|
return subscribe(device_all);
|
|
}
|
|
|
|
/// A subscriber filtered to keyboard events, whose `next` returns a decoded `KeyEvent`.
|
|
pub const KeyboardSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *KeyboardSubscriber) ?KeyEvent {
|
|
return (self.inner.next() orelse return null).asKeyboard();
|
|
}
|
|
};
|
|
|
|
/// A subscriber filtered to mouse events, whose `next` returns a decoded `MouseEvent`.
|
|
pub const MouseSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *MouseSubscriber) ?MouseEvent {
|
|
return (self.inner.next() orelse return null).asMouse();
|
|
}
|
|
};
|
|
|
|
/// A subscriber filtered to joystick/gamepad events, whose `next` returns a decoded
|
|
/// `JoystickEvent`.
|
|
pub const JoystickSubscriber = struct {
|
|
inner: Subscriber,
|
|
pub fn next(self: *JoystickSubscriber) ?JoystickEvent {
|
|
return (self.inner.next() orelse return null).asJoystick();
|
|
}
|
|
};
|
|
|
|
/// Subscribe to keyboard events only; `next` returns decoded `KeyEvent`s.
|
|
pub fn subscribeKeyboard() ?KeyboardSubscriber {
|
|
return .{ .inner = subscribe(device_keyboard) orelse return null };
|
|
}
|
|
|
|
/// Subscribe to mouse events only; `next` returns decoded `MouseEvent`s.
|
|
pub fn subscribeMouse() ?MouseSubscriber {
|
|
return .{ .inner = subscribe(device_mouse) orelse return null };
|
|
}
|
|
|
|
/// Subscribe to joystick/gamepad events only; `next` returns decoded `JoystickEvent`s.
|
|
pub fn subscribeJoystick() ?JoystickSubscriber {
|
|
return .{ .inner = subscribe(device_joystick) orelse return null };
|
|
}
|
|
|
|
// --- publishing -------------------------------------------------------------
|
|
|
|
/// A connection to the input service for a source (a keyboard/mouse/joystick driver) that
|
|
/// publishes events. Each `publish*Event` is a short synchronous call the service answers
|
|
/// at once; its own fan-out to subscribers is asynchronous, so publishing never blocks on
|
|
/// a slow subscriber.
|
|
pub const Publisher = struct {
|
|
service: ipc.Handle,
|
|
|
|
fn publish(self: Publisher, event: InputEvent) bool {
|
|
var request = input_protocol.Request{ .operation = @intFromEnum(input_protocol.Operation.publish), .event = event };
|
|
var reply: [input_protocol.reply_size]u8 = undefined;
|
|
const len = ipc.call(self.service, std.mem.asBytes(&request), &reply) catch return false;
|
|
if (len < input_protocol.reply_size) return false;
|
|
return std.mem.bytesToValue(input_protocol.Reply, reply[0..input_protocol.reply_size]).status == 0;
|
|
}
|
|
|
|
/// Broadcast a keyboard event to every subscriber that took keyboard events.
|
|
pub fn publishKeyboardEvent(self: Publisher, event: KeyEvent) bool {
|
|
return self.publish(InputEvent.fromKeyboard(event));
|
|
}
|
|
/// Broadcast a mouse event to every subscriber that took mouse events.
|
|
pub fn publishMouseEvent(self: Publisher, event: MouseEvent) bool {
|
|
return self.publish(InputEvent.fromMouse(event));
|
|
}
|
|
/// Broadcast a joystick/gamepad event to every subscriber that took joystick events.
|
|
pub fn publishJoystickEvent(self: Publisher, event: JoystickEvent) bool {
|
|
return self.publish(InputEvent.fromJoystick(event));
|
|
}
|
|
};
|
|
|
|
/// Connect to the input service as an event source, waiting for it to come up. Returns a
|
|
/// `Publisher`, or null if the service never registered.
|
|
pub fn connectSource() ?Publisher {
|
|
return .{ .service = lookupService() orelse return null };
|
|
}
|
|
|
|
// --- synthetic scaffolding --------------------------------------------------
|
|
|
|
/// Synthetic key events, shared by the demo source and the keyboard driver's placeholder
|
|
/// stream while real scancode decoding is still a follow-up. `step` rolls through A..E,
|
|
/// emitting for each key a `key_down`, then a `key_press` carrying the character, then a
|
|
/// `key_up`. Scaffolding, not wire protocol — hence it lives with the helpers.
|
|
pub fn syntheticKeyEvent(step: usize) KeyEvent {
|
|
const Key = struct { code: Keycode, character: u32 };
|
|
const keys = [_]Key{
|
|
.{ .code = .a, .character = 'A' },
|
|
.{ .code = .b, .character = 'B' },
|
|
.{ .code = .c, .character = 'C' },
|
|
.{ .code = .d, .character = 'D' },
|
|
.{ .code = .e, .character = 'E' },
|
|
};
|
|
const key = keys[(step / 3) % keys.len];
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(EventKind.key_down), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
|
|
1 => .{ .kind = @intFromEnum(EventKind.key_press), .keycode = @intFromEnum(key.code), .character = key.character, .modifiers = 0 },
|
|
else => .{ .kind = @intFromEnum(EventKind.key_up), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
|
|
};
|
|
}
|
|
|
|
/// Synthetic mouse events (placeholder until real PS/2 packet decoding). `step` alternates
|
|
/// a small diagonal motion with a left-button click.
|
|
pub fn syntheticMouseEvent(step: usize) MouseEvent {
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(MouseEventKind.motion), .button = 0, .dx = 1, .dy = 1, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
|
|
1 => .{ .kind = @intFromEnum(MouseEventKind.button_down), .button = input_protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = input_protocol.mouse_button_left },
|
|
else => .{ .kind = @intFromEnum(MouseEventKind.button_up), .button = input_protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
|
|
};
|
|
}
|
|
|
|
/// Synthetic joystick/gamepad events (placeholder until a real controller driver). `step`
|
|
/// sweeps axis 0 and toggles button 0.
|
|
pub fn syntheticJoystickEvent(step: usize) JoystickEvent {
|
|
return switch (step % 3) {
|
|
0 => .{ .kind = @intFromEnum(JoystickEventKind.axis), .control = 0, .value = 16384, .buttons = 0 },
|
|
1 => .{ .kind = @intFromEnum(JoystickEventKind.button_down), .control = 0, .value = 0, .buttons = 1 },
|
|
else => .{ .kind = @intFromEnum(JoystickEventKind.button_up), .control = 0, .value = 0, .buttons = 0 },
|
|
};
|
|
}
|