// A session channel, exposed to a local process over a Unix socket.
//
// blender-bridge/docs/P2P_BRIDGE_SPEC.md §4, §6.2.
//
// local app <--UDS, length-prefixed--> daemon <--Protomux/swarm--> peers
//
// ⛔ IT MOVES FRAMES AND HOLDS NO LOGIC. It never parses a frame. Grab
// arbitration lives in blender-bridge/session/grab.py with its own gates; a
// second implementation here would be another place for the rule to drift.
//
// ⛔ NOT BLENDER-SPECIFIC ON PURPOSE. "Expose a Protomux channel over a swarm
// topic to a local socket" has nothing to do with Blender, and it lives beside
// the transport because that is where its #includes are — the rule liboverlay
// states for itself ("no #include reaches outside liboverlay/") and the reason a
// cross-repo include would turn extraction into a refactor.
#ifndef HYPERSWARM_SESSION_DAEMON_H
#define HYPERSWARM_SESSION_DAEMON_H
#include <cstdint>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <vector>
#include "protomux.h"
#include "udx_transport.h"
namespace session {
// The protocol name carries its version, as keet-call-presence/v3 does: a peer on
// another version never pairs, and no message negotiates one.
extern const char *PROTOCOL;
class SessionDaemon {
public:
SessionDaemon();
~SessionDaemon();
// topic: 32 bytes. Every peer in a session joins the same one.
bool start(const uint8_t *topic, const char *bootstrap, const char *socket_path);
void stop();
// Accept/serve local clients and pump the swarm. Call in a loop.
void poll(int timeout_ms);
int peer_count();
int local_client_count();
// ⛔ THE DAEMON WAS UNOBSERVABLE AND THAT IS HOW A DEAD DHT LOOKED HEALTHY.
// Both peers reported joined "<topic>" while neither had ever sent a packet:
// the socket bound, the local client attached, the UI went green. Nothing in
// the process could say whether the swarm existed. These two are the
// difference between "joined" (a local fact) and "reachable" (the one that
// matters).
bool dht_active() { return _t.is_dht_active(); }
bool dht_bootstrapped() { return _t.is_dht_bootstrapped(); }
// Diagnostics only — the daemon does not interpret frames.
uint64_t frames_to_peers() { return _to_peers; }
uint64_t frames_to_local() { return _to_local; }
private:
void _on_mux_ready(int32_t id, Protomux *mux);
void _on_mux_gone(int32_t id);
void _broadcast_to_peers(const uint8_t *data, size_t len);
void _broadcast_to_local(const uint8_t *data, size_t len, int except_fd);
void _accept_local();
void _read_local(int fd);
void _drop_local(int fd);
UdxTransport _t;
std::vector<uint8_t> _topic;
std::string _socket_path;
int _listen_fd = -1;
std::mutex _m;
std::map<int32_t, ProtomuxChannel *> _channels;
// Local clients, each with its own read buffer: a stream socket coalesces
// writes, so a frame boundary only exists because the length prefix says so.
struct Local {
std::vector<uint8_t> buf;
};
std::map<int, Local> _locals;
uint64_t _to_peers = 0;
uint64_t _to_local = 0;
bool _started = false;
bool _joined = false; // the topic join is DEFERRED — see poll()
};
} // namespace session
#endif