mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-08-11 14:16:41 +03:00
vmsig: a neutral signaling layer between sensors/input and controls
An epoll-driven, neutral transfer-event bus that connects sensors and input actuators to one or more controls, bidirectionally. It owns the transfer context and events — delivery order, priority, protocol-level timing, and an interrupt-driven event model over fd sources (eventfd/timerfd/sockets) — and stays agnostic to both the sensor/input drivers and the control. What lives here: - memctx: a coherent address-space context per endpoint — the guest address-space root paired with a pre-opened read-only RAM-region fd, with per-endpoint epoch invalidation and retained replay to late subscribers. Perception lives in out-of-tree sensor libraries that consume this datum read-only. - exclusive-ownership leases for destructive resource classes (input, power, memory-write). - write-signaled memory writes (MEMWRITE): an atomic write to guest memory routed through the seam under an exclusive lease, never a writable mapping. - a host-management seam for VM lifecycle/status and a neutral input-injection command path. - multi-VM endpoints; capability-gated, audited control authorization over an in-process or unix-socket transport. Builds against headers only by default (a stub mode that exercises the seam without a VM); armed builds link the real sensor/input libraries behind flags.
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
/* test_memwrite.c — write-signaled seam (MEMWRITE): atomic guest-memory write under an
|
||||
* exclusive lease. Stub mode (no VM): proves the full path cap -> grant -> lease-gate ->
|
||||
* route -> adapter -> ACT_ACK without actuation, plus the default-deny and fencing
|
||||
* invariants. The adapter never sees a control name (SISC).
|
||||
*
|
||||
* 1) happy path: CAP_MEMWRITE + a MEMWRITE lease -> CMD_MEMWRITE -> ACT_ACK{ok=1};
|
||||
* 2) extent default-deny: len > VMSIG_MEMWRITE_MAX and a missing SRC flag -> ACK{ok=0};
|
||||
* 3) lease gate: CMD_MEMWRITE WITHOUT an acquired lease -> dropped at the gate (no ACK);
|
||||
* 4) cap gate: a control WITHOUT CAP_MEMWRITE cannot acquire the lease (DENIED{NOCAP});
|
||||
* 5) in-flight fence: A holds the lease, queues a write, B preempts SYNCHRONOUSLY -> A's
|
||||
* queued write is dropped by the fence (no ACK for A's corr), B's write actuates.
|
||||
* In-proc, under ASAN. */
|
||||
#include "vmsig.h"
|
||||
#include "memctx.h" /* VMSIG_MEMWRITE_MAX: the adapter's extent bound (private) */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } \
|
||||
} while (0)
|
||||
|
||||
/* ---- in-proc control: records lease replies + MEMWRITE ACKs ---- */
|
||||
typedef struct {
|
||||
void* core;
|
||||
int granted, denied, last_deny_reason;
|
||||
int ack_ok[64]; /* ok flag per ACK in arrival order */
|
||||
uint32_t ack_corr[64]; /* corr per ACK */
|
||||
int nack;
|
||||
int stop_replies, replies; /* stop the loop after N lease replies (0=off) */
|
||||
int stop_acks; /* stop the loop after N acks (0=off) */
|
||||
} cstate;
|
||||
|
||||
typedef struct { cstate* s; } cref;
|
||||
static cref* g_refs[16]; static int g_nrefs = 0;
|
||||
static cref* cref_new(cstate* s) {
|
||||
cref* r = calloc(1, sizeof *r); r->s = s;
|
||||
if (g_nrefs < 16) g_refs[g_nrefs++] = r;
|
||||
return r;
|
||||
}
|
||||
static void cref_free_all(void) { for (int i = 0; i < g_nrefs; i++) free(g_refs[i]); g_nrefs = 0; }
|
||||
|
||||
static int on_ev(void* user, const vmsig_event* ev) {
|
||||
cref* r = user; cstate* s = r->s;
|
||||
switch (ev->kind) {
|
||||
case VMSIG_EV_LEASE_GRANTED: s->granted++; s->replies++; break;
|
||||
case VMSIG_EV_LEASE_DENIED:
|
||||
s->denied++;
|
||||
s->last_deny_reason = (int)((const vmsig_lease_req*)ev->inln)->reason;
|
||||
s->replies++;
|
||||
break;
|
||||
case VMSIG_EV_ACT_ACK:
|
||||
if (s->nack < 64) {
|
||||
/* inln layout from mc_memwrite_ack: {int ok; uint32_t corr; uint32_t origin}. */
|
||||
int ok; memcpy(&ok, ev->inln, sizeof ok);
|
||||
s->ack_ok[s->nack] = ok;
|
||||
s->ack_corr[s->nack] = ev->corr;
|
||||
s->nack++;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (s->stop_replies && s->replies >= s->stop_replies) vmsig_core_stop(s->core);
|
||||
if (s->stop_acks && s->nack >= s->stop_acks) vmsig_core_stop(s->core);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void* add_ctl(vmsig_core* core, cstate* s, uint32_t cap, uint32_t arb_prio) {
|
||||
cref* r = cref_new(s);
|
||||
vmsig_inproc_cfg cfg; memset(&cfg, 0, sizeof cfg);
|
||||
cfg.on_event = on_ev; cfg.user = r;
|
||||
cfg.sub.source_mask = 0xFFFFFFFFu; cfg.sub.prio_min = VMSIG_PRIO_BULK;
|
||||
void* ctl = vmsig_inproc_control_new(&cfg);
|
||||
vmsig_grant g; memset(&g, 0, sizeof g);
|
||||
g.endpoint_mask = 1ull << 0; g.source_mask = 0xFFFFFFFFu;
|
||||
g.cap_mask = cap | VMSIG_CAP_OBSERVE; g.arb_prio = arb_prio;
|
||||
vmsig_core_add_control(core, vmsig_inproc_control_ops(), ctl, &g);
|
||||
return ctl;
|
||||
}
|
||||
|
||||
/* ---- DOWN send helpers ---- */
|
||||
static int acquire_mw(void* ctl) {
|
||||
vmsig_event d; memset(&d, 0, sizeof d);
|
||||
d.kind = VMSIG_EV_CMD_ACQUIRE; d.source = VMSIG_SRC_MEMCTX; d.dir = VMSIG_DIR_DOWN;
|
||||
d.endpoint = 0; d.prio = VMSIG_PRIO_HIGH;
|
||||
vmsig_lease_req lr = { VMSIG_LEASE_MEMWRITE, 0 };
|
||||
memcpy(d.inln, &lr, sizeof lr);
|
||||
return vmsig_inproc_send(ctl, &d);
|
||||
}
|
||||
|
||||
/* CMD_MEMWRITE with inline SRC; corr for tracking. flags: VMSIG_MW_SRC_* (0 => no SRC). */
|
||||
static int send_write(void* ctl, uint64_t gva, uint32_t len, uint32_t flags,
|
||||
const void* src, uint32_t corr) {
|
||||
vmsig_event d; memset(&d, 0, sizeof d);
|
||||
d.kind = VMSIG_EV_CMD_MEMWRITE; d.source = VMSIG_SRC_MEMCTX; d.dir = VMSIG_DIR_DOWN;
|
||||
d.endpoint = 0; d.prio = VMSIG_PRIO_HIGH; d.corr = corr;
|
||||
vmsig_memwrite mw = { gva, len, flags };
|
||||
memcpy(d.inln, &mw, sizeof mw);
|
||||
if ((flags & VMSIG_MW_SRC_INLINE) && src && len <= VMSIG_MEMWRITE_INLINE)
|
||||
memcpy(d.inln + sizeof mw, src, len);
|
||||
return vmsig_inproc_send(ctl, &d);
|
||||
}
|
||||
|
||||
/* Run the loop until N acks (used after queuing actuated writes). */
|
||||
static void run_until_acks(cstate* s, int n) {
|
||||
vmsig_core* c = (vmsig_core*)s->core;
|
||||
s->stop_acks = n; s->stop_replies = 0;
|
||||
vmsig_core_run(c);
|
||||
s->stop_acks = 0;
|
||||
}
|
||||
|
||||
/* ---- 1+2+3: happy path, extent default-deny, lease gate -------------------- */
|
||||
static void test_path_and_deny(void) {
|
||||
printf("test_path_and_deny\n");
|
||||
vmsig_ctx* ctx = vmsig_ctx_new();
|
||||
vmsig_core* core = vmsig_core_new(ctx);
|
||||
cstate s; memset(&s, 0, sizeof s); s.core = core;
|
||||
|
||||
void* A = add_ctl(core, &s, VMSIG_CAP_MEMWRITE, 10);
|
||||
CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx");
|
||||
|
||||
/* 3) lease gate: without ACQUIRE the write is dropped at the gate (-1, no actuation). */
|
||||
uint8_t pat[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3, 4 };
|
||||
CHECK(send_write(A, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 99) == -1,
|
||||
"3: CMD_MEMWRITE without a lease is dropped by the gate");
|
||||
|
||||
/* acquire the MEMWRITE lease (synchronous intercept; UP reply paced by ctx). */
|
||||
CHECK(acquire_mw(A) == 0, "acquire submitted");
|
||||
|
||||
/* 1) happy path: inline write -> queued -> ACT_ACK{ok=1}. Also drains the GRANTED reply. */
|
||||
CHECK(send_write(A, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 11) == 0,
|
||||
"1: owner's CMD_MEMWRITE passes the gate");
|
||||
|
||||
/* 2) extent: len > MAX -> ACK{ok=0}, NOT actuated (queued ack on the loop thread). */
|
||||
CHECK(send_write(A, 0x2000, VMSIG_MEMWRITE_MAX + 1, VMSIG_MW_SRC_INLINE, pat, 22) == 0,
|
||||
"2: over-extent write is accepted by the gate (denied inside the adapter)");
|
||||
/* 2b) missing SRC flag -> ACK{ok=0}. */
|
||||
CHECK(send_write(A, 0x3000, 4, 0u, NULL, 33) == 0,
|
||||
"2b: no-SRC-flag write is accepted by the gate (denied inside the adapter)");
|
||||
|
||||
/* expect 3 ACKs (corr 11/22/33) + the GRANTED reply. */
|
||||
run_until_acks(&s, 3);
|
||||
|
||||
CHECK(s.granted == 1, "lease GRANTED once");
|
||||
int saw11_ok = -1, saw22_ok = -1, saw33_ok = -1, saw99 = 0;
|
||||
for (int i = 0; i < s.nack; i++) {
|
||||
if (s.ack_corr[i] == 11) saw11_ok = s.ack_ok[i];
|
||||
if (s.ack_corr[i] == 22) saw22_ok = s.ack_ok[i];
|
||||
if (s.ack_corr[i] == 33) saw33_ok = s.ack_ok[i];
|
||||
if (s.ack_corr[i] == 99) saw99 = 1;
|
||||
}
|
||||
CHECK(saw11_ok == 1, "1: happy-path write ACKs ok=1 (stub)");
|
||||
CHECK(saw22_ok == 0, "2: over-extent write ACKs ok=0 (default-deny)");
|
||||
CHECK(saw33_ok == 0, "2b: no-SRC-flag write ACKs ok=0 (default-deny)");
|
||||
CHECK(!saw99, "3: the gate-dropped write produced no ACK");
|
||||
|
||||
vmsig_core_free(core);
|
||||
vmsig_ctx_free(ctx);
|
||||
}
|
||||
|
||||
/* ---- 4: cap gate — no CAP_MEMWRITE cannot acquire the lease ----------------- */
|
||||
static void test_cap_gate(void) {
|
||||
printf("test_cap_gate\n");
|
||||
vmsig_ctx* ctx = vmsig_ctx_new();
|
||||
vmsig_core* core = vmsig_core_new(ctx);
|
||||
cstate s; memset(&s, 0, sizeof s); s.core = core;
|
||||
|
||||
void* NC = add_ctl(core, &s, 0u /* no MEMWRITE */, 10);
|
||||
CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx");
|
||||
|
||||
CHECK(acquire_mw(NC) == 0, "acquire submitted");
|
||||
s.stop_replies = 1; vmsig_core_run(core); s.stop_replies = 0;
|
||||
|
||||
CHECK(s.denied == 1, "4: acquire without CAP_MEMWRITE -> DENIED");
|
||||
CHECK(s.last_deny_reason == VMSIG_LEASE_DENY_NOCAP, "4: reason=NOCAP");
|
||||
CHECK(s.granted == 0, "4: not granted");
|
||||
|
||||
vmsig_core_free(core);
|
||||
vmsig_ctx_free(ctx);
|
||||
}
|
||||
|
||||
/* ---- 5: in-flight fence — losing the lease before pump_down drops the write -- */
|
||||
static void test_inflight_fence(void) {
|
||||
printf("test_inflight_fence\n");
|
||||
vmsig_ctx* ctx = vmsig_ctx_new();
|
||||
vmsig_core* core = vmsig_core_new(ctx);
|
||||
cstate s; memset(&s, 0, sizeof s); s.core = core;
|
||||
|
||||
void* A = add_ctl(core, &s, VMSIG_CAP_MEMWRITE, 10);
|
||||
void* B = add_ctl(core, &s, VMSIG_CAP_MEMWRITE, 100); /* higher prio: preempts */
|
||||
CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx");
|
||||
|
||||
uint8_t pat[4] = { 1, 2, 3, 4 };
|
||||
CHECK(acquire_mw(A) == 0, "A acquires");
|
||||
/* A queues a write (corr=55): passes the gate (A owns), lands in the DOWN queue. */
|
||||
CHECK(send_write(A, 0x1000, 4, VMSIG_MW_SRC_INLINE, pat, 55) == 0, "A queues write 55");
|
||||
/* B preempts SYNCHRONOUSLY (acquire does not go through ctx). */
|
||||
CHECK(acquire_mw(B) == 0, "B preempts");
|
||||
/* B's own write (corr=66) — should actuate. */
|
||||
CHECK(send_write(B, 0x2000, 4, VMSIG_MW_SRC_INLINE, pat, 66) == 0, "B queues write 66");
|
||||
|
||||
run_until_acks(&s, 1); /* B's 66 acks; A's 55 must be fenced (no ack) */
|
||||
|
||||
int saw55 = 0, saw66 = 0;
|
||||
for (int i = 0; i < s.nack; i++) {
|
||||
if (s.ack_corr[i] == 55) saw55 = 1;
|
||||
if (s.ack_corr[i] == 66) saw66 = 1;
|
||||
}
|
||||
CHECK(!saw55, "5: ex-owner A's in-flight write is dropped by the fence");
|
||||
CHECK(saw66, "5: new owner B's write actuates after preemption");
|
||||
CHECK(s.granted == 2, "5: A and B each got GRANTED");
|
||||
|
||||
vmsig_core_free(core);
|
||||
vmsig_ctx_free(ctx);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("test_memwrite\n");
|
||||
test_path_and_deny();
|
||||
test_cap_gate();
|
||||
test_inflight_fence();
|
||||
cref_free_all();
|
||||
printf("memwrite tests: %s\n", g_fail ? "FAIL" : "PASS");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user