# Events

---

# Events overview

_Peios / Developing for Peios / Events_

> How KMES works from a developer's seat — the single event path, per-CPU ring buffers, trusted metadata, and MessagePack payloads.

KMES is Peios's event system, and it is the **sole** event path on the system. Audit records, subsystem events, and your own application events all travel the same way: the kernel stamps each event with trusted metadata and writes it into a per-CPU, lock-free ring buffer, and consumers drain those rings. This section teaches both sides — producing and consuming — via [`event.h`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md) and [`msgpack.h`](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md).

## What an event is

An event has two parts:

1. **Kernel-stamped metadata** you cannot forge — a `CLOCK_REALTIME` timestamp, a per-CPU monotonic sequence number, the CPU id, an origin class, and identity GUIDs (the effective token, the true token, and the process). This is the trustworthy skeleton: when you consume an event, you *know* who emitted it and when, because the kernel wrote that, not the emitter.
2. **A payload** — a single [MessagePack](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md) value that you define. This is your event's actual content.

The `event_type` is a short UTF-8 string you choose, like `"my.app.login"`, that names the kind of event.

## Payloads are MessagePack, and you own them

The kernel does not build or interpret payloads — it only *structurally validates* them on emit (one well-formed MessagePack value, within size and nesting limits). So userspace owns encoding and decoding, and the SDK ships a [MessagePack codec](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md) whose validator's acceptance is matched to the kernel's check. Build a payload with the writer, and a successful `peios_mp_writer_bytes` (or `peios_mp_validate`) means the emit call will accept it.

## Per-CPU rings and lost events

Events live in **per-CPU** ring buffers — one ring per logical CPU, lock-free so producers never block on consumers. Two consequences shape how you consume:

- **You drain per CPU.** To see everything, run a reader per CPU (discover the count by attaching upward from CPU 0 until it fails).
- **Rings can lap.** If you don't drain fast enough, new events overwrite old ones you haven't read. The per-CPU `sequence` numbers are contiguous, so a **gap in the sequence means events were lost** — and the reader tracks that count for you.

## Privileges

The two sides are gated separately:

- **Emitting** requires `SeAuditPrivilege`.
- **Consuming** (attaching to a ring) requires `SeSecurityPrivilege`.

## Two ways to consume

The SDK offers a **high-level reader** that hides the entire lock-free drain — barriers, lapping recovery, lost-event accounting, buffer-resize handling, and the wait — behind a simple `next`/`wait` loop. That's what almost everyone should use. There is also a **low-level ring API** for callers who need to drive the drain inside their own event loop. Both are covered in [consuming events](/peios/developing-for-peios/sdk-events/consuming-events.md).

## Where to go in this section

- **[Emitting events](/peios/developing-for-peios/sdk-events/emitting-events.md)** — build a payload and emit, singly or in batches.
- **[Consuming events](/peios/developing-for-peios/sdk-events/consuming-events.md)** — drain the rings with the high-level reader (and, briefly, the low-level ring).
- **[`event.h`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md)** and **[`msgpack.h`](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md)** — the exhaustive reference.

---

# Emitting events

_Peios / Developing for Peios / Events_

> Build a MessagePack payload and emit an event — singly, and in batches for high-rate producers.

Emitting an event is two steps: build a MessagePack payload, then hand it and an event type to the kernel. This guide shows both; [`event.h`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md) and [`msgpack.h`](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md) are the full references. Emitting requires `SeAuditPrivilege`.

## Build the payload

Use the [MessagePack writer](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md#writer) to encode a single top-level value — typically a map of fields:

```c
peios_mp_writer *w = peios_mp_writer_new();

peios_mp_write_map(w, 2);                              /* {"user":…, "ok":…} */
peios_mp_write_str(w, "user", 4);  peios_mp_write_str(w, "alice", 5);
peios_mp_write_str(w, "ok", 2);    peios_mp_write_bool(w, true);

const void *payload;
ssize_t plen = peios_mp_writer_bytes(w, &payload);     /* validates as it borrows */
if (plen < 0) { /* EINVAL: malformed/under-filled — check peios_mp_writer_error */ }
```

`peios_mp_writer_bytes` validates that what you built is exactly one well-formed value, so a non-negative return means the payload is emit-ready. (Remember a map of `n` needs `2*n` values — one per key *and* value.)

## Emit it

```c
int rc = peios_event_emit("my.app.login", 12, payload, (uint32_t)plen);
peios_mp_writer_free(w);

if (rc != 0) {
    switch (errno) {
    case EPERM:  /* no SeAuditPrivilege */          break;
    case EINVAL: /* zero-length type or bad payload */ break;
    case ENOSPC: /* payload too large */            break;
    case EAGAIN: /* rate-limited — back off */      break;
    }
}
```

The event type is length-counted UTF-8 and must be non-zero length (`"my.app.login"` is 12 bytes — not NUL-terminated on the wire). On success the kernel stamps the trusted metadata (timestamp, sequence, identity GUIDs) and sets `origin_class = userspace`; you don't provide any of that.

### Validating untrusted payloads first

If a payload's shape comes from dynamic or untrusted input, validate it in userspace before emitting so you handle the failure on your terms rather than as an `EINVAL` from the kernel:

```c
if (peios_mp_validate(payload, plen, KMES_CONFIG_MAX_NESTING_DEPTH_DEFAULT) != 0) {
    /* reject it yourself */
}
```

The validator's acceptance matches the kernel's emit-time check at that depth bound.

## Emitting in batches

A high-rate producer should batch. [`peios_event_emit_batch`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md#batch-emit) emits many events in one call, so a single timestamp capture, identity capture, and consumer wake cover the whole set:

```c
struct peios_event_entry entries[3] = {
    { "my.app.a", 8, pa, pa_len },
    { "my.app.b", 8, pb, pb_len },
    { "my.app.c", 8, pc, pc_len },
};

uint32_t emitted = 0;
int rc = peios_event_emit_batch(entries, 3, &emitted);
if (rc != 0) {
    /* errno is from entries[emitted] — the first that failed.
       entries[0..emitted) were emitted; resume from `emitted`. */
}
```

`count` must be in `[1, KMES_BATCH_MAX_ENTRIES]`. On failure, `errno` is the reason the first failing entry failed and `emitted` tells you how many succeeded before it, so you know exactly where to resume. One caveat: rate-limiting is **all-or-nothing** for a batch — an `EAGAIN` emits *none* of it, so on `EAGAIN` back off and retry the whole batch.

## Next

- **[Consuming events](/peios/developing-for-peios/sdk-events/consuming-events.md)** — the other side of the pipe.
- **[`msgpack.h` reference](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md)** — the full encoder, including containers, extensions, and raw splicing.

---

# Consuming events

_Peios / Developing for Peios / Events_

> Drain the per-CPU KMES rings with the high-level reader, parse payloads, track lost events, and know when to drop to the low-level ring API.

Consuming events means draining the per-CPU ring buffers. The SDK's high-level reader hides all the hard parts, so most consumers are a short loop. This guide shows that loop and how to parse what you read; [`event.h`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md) has the full API. Consuming requires `SeSecurityPrivilege`.

## The reader loop

Open a reader for a CPU, then loop `next`/`wait`:

```c
peios_event_reader *r = peios_event_reader_open(cpu);
if (!r) { /* errno */ }

for (;;) {
    struct peios_event ev;
    int rc = peios_event_reader_next(r, &ev);
    if (rc == 1) {
        handle_event(&ev);                       /* got one */
    } else if (rc == 0) {
        peios_event_reader_wait(r, -1);          /* none right now — sleep */
    } else {
        break;                                   /* error */
    }
}
peios_event_reader_close(r);
```

`peios_event_reader_next` returns `1` (event filled), `0` (nothing available — call `wait`), or `-1` (error). `peios_event_reader_wait` blocks until events arrive or the timeout elapses (negative = forever). The reader handles the memory barriers, lapping recovery, buffer-resize handling, and futex wait internally — you just alternate the two calls.

## Parsing an event

Each `struct peios_event` gives you the trusted metadata by value and the payload as a MessagePack value. Parse it with a [reader](/peios/developing-for-peios/sdk-reference/sdk-msgpack/msgpack-h-messagepack-codec.md#reader):

```c
void handle_event(const struct peios_event *ev)
{
    /* Metadata is trustworthy — the kernel stamped it. */
    /* ev->timestamp, ev->sequence, ev->cpu_id, ev->origin_class,
       ev->process_guid, ev->effective_token_guid, ... */

    /* event_type and payload are NOT NUL-terminated — use the lengths. */
    if (ev->event_type_len == 12 &&
        memcmp(ev->event_type, "my.app.login", 12) == 0) {

        struct peios_mp_reader mp;
        peios_mp_reader_init(&mp, ev->payload, ev->payload_len);

        ssize_t pairs = peios_mp_read_map(&mp);
        for (ssize_t i = 0; i < pairs; i++) {
            const char *key; ssize_t klen = peios_mp_read_str(&mp, &key);
            /* dispatch on key; read or peios_mp_skip(&mp) the value */
        }
    }
}
```

### The lifetime rule

`ev->event_type` and `ev->payload` point **into the ring mapping**. They are valid only until your next `peios_event_reader_next` call, and only while the slot hasn't been overwritten. So **copy out anything you need to keep** before continuing the loop — don't stash the raw pointers.

## Draining every CPU

Rings are per-CPU, so one reader sees only one CPU's events. To consume the whole machine, run a reader per CPU — typically one thread each. Discover the CPU count by attaching upward until it fails:

```c
uint32_t ncpu = 0;
for (;;) {
    uint64_t cap;
    int fd = peios_event_attach(ncpu, &cap);   /* low-level probe */
    if (fd < 0) break;                          /* EINVAL past the last CPU */
    close(fd);
    ncpu++;
}
/* now spawn one peios_event_reader per cpu in [0, ncpu) */
```

## Watching for loss

If a consumer falls behind, the producer laps it and events are lost. Poll [`peios_event_reader_lost`](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md#the-high-level-reader) to see the cumulative count of lost events (derived from sequence gaps). A rising number means you aren't draining fast enough — process events more cheaply, hand off to a worker, or accept the loss deliberately.

## The low-level ring

If the reader's loop doesn't fit your event model — you want the ring integrated into an existing `epoll`/state-machine loop, driving the read position yourself — the [low-level ring API](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md#the-low-level-ring) exposes the mapping directly: `peios_event_ring_map`, the position accessors (`write_pos`/`tail_pos`/`generation`), `peios_event_ring_event_at` to parse a slot, and `peios_event_ring_wait` to sleep. It's more bookkeeping (you own the read position and the empty/lapping/generation checks) for more control. Reach for it only when you need to; the high-level reader is the right default.

## Next

- **[`event.h` reference](/peios/developing-for-peios/sdk-reference/sdk-events-api/event-h-events-kmes.md)** — the full reader and ring APIs, and every `struct peios_event` field.
- **[Auditing](/peios/security-fundamentals/auditing/overview.md)** — the operator-side view of the event and audit stream.
