# libantpeer Specification v1.0.0 *Peer patterns over the Antheos protocol: transport abstraction, service endpoint, service consumer, and the trusted key store for Level 2 auth.* **Status:** current. Describes `include/antpeer.hpp` and `include/Ed25519Utils.hpp` as shipped. ## 0. Scope, and what this document deliberately does not contain libantpeer defines a **C++ shape over a wire someone else specifies**. The frame format, the verb set, the identity model and the Level 2 Z-verb challenge-response are all normative in [`ANTHEOS_PROTOCOL_SPEC.md`](https://langsyn.org/libantheos/latest/docs/ANTHEOS_PROTOCOL_SPEC.md); this document does not restate them, and where the two disagree the protocol specification wins. What is here is the part the protocol leaves to an implementation: - the **transport abstraction** (§2) — one interface over three buses; - the **Server and Client lifecycles** (§3, §4) — callback contracts, session semantics, and threading rules that are API guarantees rather than wire behaviour; - the **trusted key store** (§5) — which Appendix A §A.5 states explicitly is "out of scope for the protocol. It may be a local config file, a key registry service, or any other mechanism". libantpeer chooses a local file, and the format is a real contract with no other home; - **relay routing for auth** (§6) — how a Z-challenge reaches a peer that is not on the challenger's bus. ## 1. Constants Defined in `namespace antpeer`: | Constant | Value | Meaning | |---|---|---| | `MEMBUS_DEFAULT_SIZE` | `256 * 1024` | default ring size for `membus_open` | | `BUS_MAX_NAME` | `256` | bus-name length cap | | `MAX_SESSIONS` | `64` | concurrent sessions a Server tracks | | `MAX_PEERS` | `64` | peer identities a Server caches | | `MAX_OFFERS` | `16` | additional service offers per Server | ## 2. Transport abstraction `Bus` is an abstract RAII handle: non-copyable, virtual destructor, and five operations — `write`, `read`, `reopen`, `is_open`, `name`. `read` and `write` return `ssize_t` and carry the underlying transport's error convention. Three factories return `std::unique_ptr` and **throw `std::system_error` on failure**: | Factory | Transport | Creation behaviour | |---|---|---| | `membus_open(name, size)` | POSIX shared memory | detects stale SHM and creates if needed | | `sockbus_open(name)` | TCP socket broker | creates the broker if absent, ignoring `EEXIST` | | `blebus_open(name)` | BLE L2CAP CoC | creates the broker if absent, ignoring `EEXIST` | A `Bus` is passed by reference to `Server::init` / `Client::init` and outlives them; neither takes ownership. ## 3. Server — service endpoint ### 3.1 Lifecycle ``` Server s; s.init(bus, oid, did, iid, service_name); // identity + advertised service s.on_request(...); s.on_session(...); ... // register callbacks s.require_auth("trusted_keys.conf"); // optional, Level 2 s.run(); // poll loop, blocks ``` `init` returns `0` on success and non-zero on error. All callback registration and `set_dispatch_threads` MUST happen before `run()`. `stop()` is the only method safe to call from a signal handler or another thread while `run()` is executing. ### 3.2 Callbacks | Setter | Fires when | |---|---| | `on_request` | a peer sends a command on a session | | `on_session` | a session-scoped message arrives (carries `mid`) | | `on_finish` | a session ends | | `on_notify` | an event is received | | `on_accept` | a peer BID is accepted onto a session | | `on_verify` | a peer's identity resolves via the V exchange | | `on_auth` | a Z exchange completes, with the boolean outcome | `on_verify` fires when identity is *known*; `on_auth` fires when identity is *proved*. A server that requires auth MUST treat only the second as authorisation — this is the distinction Appendix A §A.1 draws between "I am X" and "prove you are X". ### 3.3 Replies and events `reply`, `reply_with_blob`, `notify` and `finish` are session-scoped and take a `sid`. `broadcast` is not. `offer_additional` advertises a further service name on the same endpoint, up to `MAX_OFFERS`. ### 3.4 Threading `set_dispatch_threads(n)`: - `n == 0` (default) — `on_request` runs inline in the poll loop. - `n > 0` — `on_request` runs on a worker pool. Workers queue replies back to the poll loop, and **all server context access remains single-threaded**. Consequently `last_blob_tail()` is valid during the `on_request` callback in either mode, and nowhere else. `last_trace_id()` reflects the most recently received message. ### 3.5 Identity queries `peer_identity(bid)` returns a cached `PeerIdentity` or `nullptr`; `peer_bid_for_session(sid)` resolves a session to its peer; `is_authenticated(bid)` reports whether a peer has passed the Z exchange. `PeerIdentity::verified()` is true once an OID is known. ## 4. Client — service consumer ``` Client c; c.init(bus, oid, did, iid); c.set_auth_key("peer.key"); // optional, Level 2 c.discover("service-name", 5000); std::string r = c.call("command", 30000); ``` `call` has two forms: a caller-supplied buffer returning `int`, and a `std::string` form. `fire` sends without awaiting a reply. `ensure_connected` re-establishes on demand; `state()` reports `Disconnected`, `Connected` or `Suspended`. `set_required(true)` marks the service as mandatory to the caller. Trace propagation is explicit: `set_trace_id` applies to the next `call` or `fire` until `clear_trace_id`. ## 5. Trusted key store **This section is the one part of Level 2 the protocol leaves undefined.** ### 5.1 Key material All keys and signatures are hex strings: | Item | Length | Bytes | |---|---|---| | private key | 64 hex chars | 32 (Ed25519 seed) | | public key | 64 hex chars | 32 | | `key_id` | 32 hex chars | 16 (random identifier) | | signature | 128 hex chars | 64 | ### 5.2 Keypair file — `save_key` / `load_key` ``` private_key = public_key = key_id = ``` Passed to `Client::set_auth_key(path)`. A private key file is a secret and its protection is the deployer's responsibility; this library does not enforce a mode. ### 5.3 Trusted key file — `load_trusted_keys` One record per line, whitespace-separated, three fields: ``` ``` - Lines beginning with `#` are comments; empty lines are skipped. - Leading and trailing whitespace is stripped; fields separate on space or tab. - A line with fewer than three fields is **skipped, not an error** — a truncated store loads what it can rather than refusing to start. - Maximum line length is 512 bytes. - `load_trusted_keys` throws `std::runtime_error` if the file cannot be opened. `Server::require_auth` converts that to a `-1` return. Lookup is `find_trusted_key(keys, oid, key_id)`: matching on OID alone when `key_id` is empty, and on both when it is not. **A peer may therefore hold several keys under one OID**, which is what makes rotation possible without a flag day — publish the new row, let both be trusted, retire the old row. ## 6. Relay routing for auth Appendix A assumes challenger and peer share a bus. When they do not, the Z-challenge is wrapped in a Relay frame. - `Server::add_route(target_bid, path, my_index)` — register the path to a peer the server will need to challenge. - `Client::set_route(path, my_index)` — the path back, used when a relayed Z-challenge arrives. `Route::path` is a dot-separated BID path; `my_index` is the holder's own position within it. Both sides must be configured: a relayed challenge with no return route is answered by nobody, and the server sees an auth timeout rather than a failure. ## 7. FrameExtractor For byte streams that are not already framed. `feed()` accepts arbitrary chunks and invokes the `on_frame` callback once per complete Antheos frame, head plus optional BLOB tail. Head parsing and BLOB-word detection use `antheos::Parser`; tail bytes are then consumed against the declared size, so **there is no internal buffer limit on tail size**. `reset()` discards partial state. `total_frames()` and `parse_errors()` are cumulative counters. `FrameExtractor` is movable but not copyable. ## 8. Conformance An implementation conforms to this document if: 1. `Bus` implementations throw `std::system_error` from their factory on failure rather than returning a null handle. 2. `on_auth` is never reported true for a peer whose signature did not verify against a key found in the trusted store. 3. A trusted-key line with fewer than three fields is skipped without aborting the load. 4. Worker-pool dispatch does not expose server context to more than one thread. Wire conformance is governed by [`ANTHEOS_PROTOCOL_SPEC.md`](https://langsyn.org/libantheos/latest/docs/ANTHEOS_PROTOCOL_SPEC.md), not by this document.