#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