Expose local socket protocol and mimctl basics

This commit is contained in:
slhx agent
2026-06-21 01:53:16 +02:00
parent 108c0533a8
commit 6c3b7fa55a
3 changed files with 392 additions and 5 deletions
+119
View File
@@ -0,0 +1,119 @@
const std = @import("std");
const session_mod = @import("session.zig");
// Tiny local session protocol for trusted same-user tools.
// req: session/002, session/003, governance/003
test {
_ = handleLine;
}
pub const max_request_bytes = 4096;
pub fn handleLine(allocator: std.mem.Allocator, session: *session_mod.Session, line: []const u8) ![]u8 {
const trimmed = std.mem.trimEnd(u8, line, "\r\n");
if (std.mem.eql(u8, trimmed, "state")) return stateResponse(allocator, session);
if (std.mem.startsWith(u8, trimmed, "open ")) {
const bytes = trimmed[5..];
if (!std.unicode.utf8ValidateSlice(bytes)) return allocator.dupe(u8, "err invalid utf8\n");
try session.openFixture(bytes);
return stateResponse(allocator, session);
}
if (std.mem.startsWith(u8, trimmed, "command ")) {
return commandResponse(allocator, session, trimmed[8..]);
}
return allocator.dupe(u8, "err unknown request\n");
}
fn commandResponse(allocator: std.mem.Allocator, session: *session_mod.Session, command: []const u8) ![]u8 {
if (std.mem.eql(u8, command, "move_left")) return dispatchAndRespond(allocator, session, .move_left);
if (std.mem.eql(u8, command, "move_right")) return dispatchAndRespond(allocator, session, .move_right);
if (std.mem.eql(u8, command, "delete_backward")) return dispatchAndRespond(allocator, session, .delete_backward);
if (std.mem.startsWith(u8, command, "insert ")) return dispatchAndRespond(allocator, session, .{ .insert = command[7..] });
return allocator.dupe(u8, "err unknown command\n");
}
fn dispatchAndRespond(allocator: std.mem.Allocator, session: *session_mod.Session, command: session_mod.Command) ![]u8 {
session.dispatch(command) catch |err| switch (err) {
error.InvalidUtf8Insertion => return allocator.dupe(u8, "err invalid utf8\n"),
error.NoBufferOpen => return allocator.dupe(u8, "err no buffer open\n"),
else => return err,
};
return stateResponse(allocator, session);
}
fn stateResponse(allocator: std.mem.Allocator, session: *session_mod.Session) ![]u8 {
const snap = session.snapshot() catch |err| switch (err) {
error.NoBufferOpen => return allocator.dupe(u8, "err no buffer open\n"),
};
return std.fmt.allocPrint(
allocator,
"ok state cursor_byte={d} cursor_cell={d} bytes_len={d}\n",
.{ snap.cursor_byte, snap.cursor_cell, snap.bytes.len },
);
}
test "regular: protocol opens bytes, reports state, and dispatches commands" {
var session = session_mod.Session.init(std.testing.allocator);
defer session.deinit();
const open = try handleLine(std.testing.allocator, &session, "open café\n");
defer std.testing.allocator.free(open);
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=5\n", open);
const moved = try handleLine(std.testing.allocator, &session, "command move_right\n");
defer std.testing.allocator.free(moved);
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=5\n", moved);
const inserted = try handleLine(std.testing.allocator, &session, "command insert 🔥\n");
defer std.testing.allocator.free(inserted);
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=3 bytes_len=9\n", inserted);
}
test "regular: protocol delete command removes a whole UTF-8 codepoint" {
var session = session_mod.Session.init(std.testing.allocator);
defer session.deinit();
var response = try handleLine(std.testing.allocator, &session, "open aé\n");
std.testing.allocator.free(response);
response = try handleLine(std.testing.allocator, &session, "command move_right\n");
std.testing.allocator.free(response);
response = try handleLine(std.testing.allocator, &session, "command move_right\n");
std.testing.allocator.free(response);
response = try handleLine(std.testing.allocator, &session, "command delete_backward\n");
defer std.testing.allocator.free(response);
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=1\n", response);
}
test "adversarial: protocol rejects unknown requests and commands" {
var session = session_mod.Session.init(std.testing.allocator);
defer session.deinit();
const request = try handleLine(std.testing.allocator, &session, "subscribe everything\n");
defer std.testing.allocator.free(request);
try std.testing.expectEqualStrings("err unknown request\n", request);
try session.openFixture("abc");
const command = try handleLine(std.testing.allocator, &session, "command plugin.load\n");
defer std.testing.allocator.free(command);
try std.testing.expectEqualStrings("err unknown command\n", command);
}
test "adversarial: protocol reports no buffer and invalid utf8 without corrupting state" {
var session = session_mod.Session.init(std.testing.allocator);
defer session.deinit();
const no_buffer = try handleLine(std.testing.allocator, &session, "state\n");
defer std.testing.allocator.free(no_buffer);
try std.testing.expectEqualStrings("err no buffer open\n", no_buffer);
try session.openFixture("safe");
const before = try session.snapshot();
const bad = [_]u8{ 'c', 'o', 'm', 'm', 'a', 'n', 'd', ' ', 'i', 'n', 's', 'e', 'r', 't', ' ', 0xc3, 0x28 };
const invalid = try handleLine(std.testing.allocator, &session, &bad);
defer std.testing.allocator.free(invalid);
try std.testing.expectEqualStrings("err invalid utf8\n", invalid);
const after = try session.snapshot();
try std.testing.expectEqualStrings(before.bytes, after.bytes);
}