OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / swarmd / src / daemon.h

124 lines · c ref:
#ifndef SWARMD_DAEMON_H
#define SWARMD_DAEMON_H

// swarmd — the daemon proper: one UdxTransport, one local socket, one control
// plane, and a queue between them.
//
// ⛔ THE UDX THREAD NEVER TOUCHES A CLIENT SOCKET. UdxTransport runs its own
// libuv loop on its own thread; every callback it fires (peer up, peer down, a
// frame off a channel) arrives THERE. Writing to a client from that thread is
// what session-daemon does, and §4 of the design says why it must not: the fds
// belong to the main thread, which owns the buffering. So callbacks enqueue,
// and poll() drains on the main thread.
//
// The other half of the same rule: commands from a client (join a topic, open a
// channel) must not touch udx/protomux handles from the main thread either.
// UdxTransport::run_on_loop exists for that and is the only way in.

#include <atomic>
#include <cstdint>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

#include "content.h"
#include "control.h"
#include "uds_server.h"
#include "../../src/udx_transport.h"

namespace swarmd {

class Daemon {
public:
	// `storage_dir` may be null, in which case the content plane stays off and
	// every content command is refused rather than half-answered.
	bool start(const char *socket_path, const char *bootstrap, const char *storage_dir);
	void stop();

	// One pass: drain the cross-thread queue, then serve the local socket.
	void poll(int timeout_ms);

	bool dht_active() const { return _t.is_dht_active(); }
	bool dht_bootstrapped() const { return _t.is_dht_bootstrapped(); }
	int local_clients() const { return _uds.client_count(); }
	int peers() const;

private:
	// Everything the UDX thread hands over crosses as one of these.
	struct Event {
		enum Kind { PEER_UP, PEER_DOWN, MESSAGE } kind;
		int32_t peer_id;
		uint8_t pubkey[32];
		uint64_t channel = 0;
		uint64_t slot = 0;
		std::vector<uint8_t> bytes;
	};

	// A channel a client asked for. One spec, N live ProtomuxChannels — one per
	// peer — because Protomux is per-connection and a topic is not.
	struct ChannelSpec {
		uint64_t handle = 0;
		std::string protocol;
		std::vector<uint8_t> id;
		uint32_t slots = 1;
		uint64_t owner = 0;   // the client that opened it
		std::map<int32_t, ProtomuxChannel *> per_peer;

		// How many peers currently have this channel FULLY open — written on the
		// UDX thread, read by SEND on the main thread. Shared rather than owned
		// because the map entry it lives in is UDX-thread-only.
		std::shared_ptr<std::atomic<int>> open_peers;
	};

	// UDX-THREAD ONLY. Creates this spec's channel on one peer's mux.
	void _open_on_peer(ChannelSpec &spec, int32_t peer_id, Protomux *mux);

	// UDX-THREAD ONLY. Recomputes how many peers have `handle` open and
	// publishes it. Recomputed rather than incremented: a channel can go away
	// through open, close, or the whole mux vanishing, and three call sites
	// each adjusting a counter is three chances to drift.
	void _publish_open(uint64_t handle);
	void _push(const Event &e);
	void _drain();

	void _fill_status(swarmd_status_t *out);
	void _drain_content();

	UdxTransport _t;
	UdsServer _uds;
	Control _control;
	Content _content;

	// UDX-THREAD ONLY, except where a lock is taken: the peer's mux, so a
	// channel opened after the swarm meshed can still reach existing peers.
	std::map<int32_t, Protomux *> _muxes;
	std::map<uint64_t, ChannelSpec> _channels;
	uint64_t _next_channel = 1;

	// The only part of a channel the main thread may look at: does this handle
	// exist, and is it open on anyone? SEND needs a truthful answer to that
	// BEFORE it queues, because everything past the queue is out of earshot.
	std::mutex _chan_m;
	std::map<uint64_t, std::shared_ptr<std::atomic<int>>> _chan_open;
	uint64_t _dropped_sends = 0;

	std::mutex _m;
	std::deque<Event> _events;
	// Bounded: a swarm that churns faster than the socket drains must drop
	// notifications rather than grow without limit.
	static constexpr size_t MAX_EVENTS = 4096;
	uint64_t _dropped_events = 0;

	bool _started = false;
	std::string _socket_path;
	// ⚠ Generated per start; see ControlBackend::daemon_pubkey.
	uint8_t _pubkey[32] = { 0 };
};

} // namespace swarmd

#endif