#ifndef SWARMD_CONTROL_H
#define SWARMD_CONTROL_H
// The control plane: generated RPC on top of the local socket.
//
// A UDS frame is one bare-rpc message. librpc decodes it, the GENERATED
// dispatcher routes it to a handler, and any reply goes back as another frame.
// Nothing here parses a payload by hand — that is the entire point of
// generating the wire (docs/SWARMD-DESIGN.md §3).
//
// ⛔ TWO OWNERSHIP RULES, both from hrpc-c's README and both the opposite of
// the careless assumption:
// 1. every *_encode_* HEAP-ALLOCATES its output and THE CALLER FREES IT
// 2. decoded views point INTO the inbound frame buffer, so anything kept
// past the handler must be copied out
// They are enforced at the two places they matter, marked below.
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <vector>
#include "uds_server.h"
extern "C" {
#include "swarmd_hrpc.h"
#include "swarmd_schema.h"
}
namespace swarmd {
// What the daemon can actually answer. Filled in by the daemon; kept behind an
// interface so the control plane can be gated with no swarm at all.
struct ControlBackend {
std::function<void(swarmd_status_t *out)> status;
std::function<bool(const uint8_t topic[32], bool server, bool client)> swarm_join;
std::function<bool(const uint8_t topic[32])> swarm_leave;
// Returns the channel handle, or 0.
std::function<uint64_t(const std::string &protocol, const std::vector<uint8_t> &id,
uint32_t slots, uint64_t owner)> channel_open;
std::function<bool(uint64_t handle)> channel_close;
std::function<bool(uint64_t handle, uint32_t slot, uint64_t target,
const uint8_t *data, size_t len, bool *writable)> send;
// The daemon's own transport key, for HELLO. ⚠ It is generated per start
// today — a stored seed is §7 item 5 and is not built — so it identifies
// this RUN of the daemon, not this machine.
std::function<void(uint8_t out[32])> daemon_pubkey;
// Content. The hash is 32 raw bytes on the wire; liboverlay speaks hex, and
// the conversion belongs on the daemon side of this seam, not in the codec.
std::function<bool(const std::string &path, uint8_t out_hash[32])> content_hash;
std::function<bool(const uint8_t hash[32], const std::string &path)> content_serve;
std::function<bool(const uint8_t hash[32], const std::string &dest_path)> content_fetch;
std::function<bool(const uint8_t hash[32], bool discard)> content_cancel;
};
class Control {
public:
void attach(UdsServer *uds, ControlBackend backend);
// Per-client session state. A client that has not said HELLO has no
// identity, and identity is what echo-suppression and membership need.
struct Session {
bool hello = false;
uint8_t app_identity[32] = { 0 };
std::string name;
};
const Session *session(uint64_t client_id) const;
uint64_t hellos() const { return _hellos; }
// Daemon -> client events. Encoded with the generated encoders.
void push_status(uint64_t client_id);
private:
void _on_frame(uint64_t client_id, const uint8_t *data, size_t len);
UdsServer *_uds = nullptr;
ControlBackend _backend;
std::map<uint64_t, Session> _sessions;
uint64_t _current_client = 0; // the client being dispatched, for handlers
uint64_t _hellos = 0;
};
} // namespace swarmd
#endif