OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / swarmd / src / control.h

90 lines · c ref:
#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