121 lines
6.7 KiB
Markdown
121 lines
6.7 KiB
Markdown
# Communication: the four layers
|
|
|
|
*Design, agreed 2026-07-31. The model document — the vocabulary and layering
|
|
every other communication document speaks.*
|
|
|
|
danos separates **what is said** from **how the bytes move**, so that the
|
|
mechanism is replaceable. The shape is a network stack's, cut into four
|
|
layers; a program only ever touches the top two.
|
|
|
|
```
|
|
L3 namespace /protocol/... names establishment points protocol-namespace.md
|
|
L2 protocol the language: packet schemas, verbs, targets the envelope, library/protocol/*
|
|
L1 channel two ends exchanging packets and signals the client library's Channel
|
|
L0 transport a buffer + a doorbell: moves the bytes ipc.md (kernel-ipc), later shm-ring, …
|
|
```
|
|
|
|
## Vocabulary
|
|
|
|
| Term | Meaning |
|
|
|---|---|
|
|
| **protocol** | The language: which packets exist, what their fields mean, which verbs a provider answers. Defined transport-independently in a `library/protocol/*` module. |
|
|
| **channel** | An open conversation between two processes, speaking one protocol. Established by opening a `/protocol/...` name; both ends can send and receive. |
|
|
| **packet** | The unit a protocol transmits: a bounded, atomic header+payload. Never fragmented — if it doesn't fit, it isn't a packet; bulk data rides shared memory with a packet as the doorbell. |
|
|
| **signal** | A payload-less poke below the packet layer: "something happened, come look." Coalescing — the count may collapse, the fact may not. |
|
|
| **transport** | What moves the bytes of one channel: a buffer plus a doorbell. Chosen (and upgradable) at establishment, invisible above L1. |
|
|
| **endpoint** | A termination point where a transport delivers. The kernel-ipc transport's endpoint is its kernel mailbox object. |
|
|
|
|
## Addressing: parties by channel, objects by target
|
|
|
|
There are no network-style addresses in a packet. The two questions addresses
|
|
answer are answered at different layers:
|
|
|
|
- **Who am I talking to?** The **channel**, decided once at establishment.
|
|
Opening `/protocol/input` yields a channel; every packet sent on it goes to
|
|
the peer. Nothing to route per-packet — like TCP, where no HTTP request
|
|
carries the server's IP.
|
|
- **Who sent this?** Attached to every received packet **by the channel
|
|
layer**, from identity the transport can verify — under kernel-ipc, the
|
|
kernel-stamped badge. The sender never writes a source field, which is what
|
|
makes source unforgeable (the property a network's spoofable source header
|
|
lacks).
|
|
- **Which of your things?** The packet's **`target`** field: *object*
|
|
addressing within the already-chosen peer — the vfs protocol's node id, the
|
|
display protocol's layer id, a block volume. `target = 0` addresses the
|
|
provider itself; a protocol without objects never uses it.
|
|
|
|
`target` is how instance multiplicity stays out of the namespace. Ten USB
|
|
sticks and the namespace still holds exactly one name, `/protocol/block`: a
|
|
channel to the provider, `enumerate` lists the current volumes as targets, a
|
|
`targets_changed` signal announces hotplug, and a read names its volume in
|
|
`target`. The unix `/dev/sda`,`/dev/sdb` problem is dissolved, not renamed.
|
|
|
|
If a future transport genuinely routes between machines, *it* carries real
|
|
source/destination addressing internally at L0 — the way IP runs under TCP —
|
|
and none of it surfaces into the packet header. Protocols stay ignorant of
|
|
distance.
|
|
|
|
## The transport (L0): a buffer and a doorbell
|
|
|
|
Strip any transport to its skeleton and the same two parts remain:
|
|
|
|
| Transport | Buffer | Doorbell | Status |
|
|
|---|---|---|---|
|
|
| **kernel-ipc** | kernel-owned mailbox (the `Endpoint`) | the scheduler (rendezvous wake) | the first transport — [ipc.md](../device-driver-development/ipc.md) |
|
|
| **shm-ring** | user-owned shared-memory ring | a signal | exists ad hoc (display bulk); to be formalized — the unlock for the 256-byte ceiling |
|
|
| network | NIC queue | an interrupt | someday, when danos networks |
|
|
|
|
Transports differ in their **properties**, which the channel layer exposes and
|
|
the protocol layer may depend on:
|
|
|
|
- **packet ceiling** — kernel-ipc: 256 bytes request/reply, 64 pushed. An
|
|
shm-ring's ceiling is its slot size. Kernel-ipc's 256 is the *floor* every
|
|
protocol may assume everywhere.
|
|
- **synchrony** — kernel-ipc's call is a rendezvous: natural backpressure, no
|
|
queue to size. An asynchronous transport buffers, so a channel over one
|
|
needs explicit flow control. Backpressure is a *transport property*, not a
|
|
channel guarantee — protocols that rely on it say so.
|
|
- **droppability** — pushed event packets may drop when a ring fills;
|
|
request/reply may not.
|
|
- **capability carriage** — **only kernel-ipc can move a capability.**
|
|
Handles are kernel objects; a user-space ring cannot transfer one. So
|
|
kernel-ipc is always the *establishment and control* transport — channels
|
|
are born on it, capabilities ride it — even when a channel's data is
|
|
negotiated onto something fatter.
|
|
|
|
That negotiation is the upgrade path: a channel starts on kernel-ipc; the
|
|
protocol's handshake may then delegate a shared-memory region (as a
|
|
capability, over kernel-ipc) and move its bulk traffic there. The display
|
|
path already does exactly this by hand; formalizing it in the channel layer
|
|
makes it every protocol's option.
|
|
|
|
## The channel (L1)
|
|
|
|
A channel has two ends, and **the ends are peers**: each may send packets,
|
|
each may receive, each may signal. Request/reply is a *pattern* over the
|
|
channel — a send with a correlated receive, which the kernel-ipc transport
|
|
happens to accelerate as a single rendezvous — not the definition of it. The
|
|
event stream (subscribe, then pushes) and the change signal (poke, then
|
|
re-read) are the other two patterns; all three are catalogued in
|
|
[protocol-namespace.md](protocol-namespace.md)'s wiring section.
|
|
|
|
The channel layer's obligations: deliver packets whole, attach the verified
|
|
source to every receive, expose the transport's properties, and hide the
|
|
transport's mechanics. The client library's `Channel` type is this layer made
|
|
concrete — a program holds channels that speak protocols and never touches a
|
|
raw handle.
|
|
|
|
## The protocol (L2) and the namespace (L3)
|
|
|
|
A protocol defines its packets through the envelope — every packet begins
|
|
`{operation, target}`, reserved verbs (`describe`, `enumerate`, `subscribe`,
|
|
`unsubscribe`) mean the same thing in every protocol, and `Define` checks
|
|
every packet against the transport floor at compile time. The full treatment,
|
|
including how names are granted, resolved, and restricted per process, is
|
|
[protocol-namespace.md](protocol-namespace.md).
|
|
|
|
Establishment points are named by contract — `/protocol/display`, never
|
|
`/protocol/ipc-1` — because the name must outlive the mechanism: a
|
|
transport named in the namespace could never be swapped, which would defeat
|
|
this document's premise.
|