#include "hyperswarm_peer.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include "noise_ik.h"
#include <cstdio>
#include <sodium.h>
namespace godot {
void HyperswarmMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_server", "port"), &HyperswarmMultiplayerPeer::create_server);
ClassDB::bind_method(D_METHOD("create_client", "address", "port"), &HyperswarmMultiplayerPeer::create_client);
ClassDB::bind_method(D_METHOD("generate_keypair"), &HyperswarmMultiplayerPeer::generate_keypair);
ClassDB::bind_method(D_METHOD("create_server_with_keypair", "port", "public_key", "secret_key"),
&HyperswarmMultiplayerPeer::create_server_with_keypair);
ClassDB::bind_method(D_METHOD("create_client_with_key", "address", "port", "remote_public_key"),
&HyperswarmMultiplayerPeer::create_client_with_key);
ClassDB::bind_method(D_METHOD("get_peer_public_key", "peer_id"),
&HyperswarmMultiplayerPeer::get_peer_public_key);
ClassDB::bind_method(D_METHOD("get_stats"), &HyperswarmMultiplayerPeer::get_stats);
ClassDB::bind_method(D_METHOD("get_peer_rtt", "peer_id"),
&HyperswarmMultiplayerPeer::get_peer_rtt);
// DHT API
ClassDB::bind_method(D_METHOD("generate_dht_keypair"), &HyperswarmMultiplayerPeer::generate_dht_keypair);
ClassDB::bind_method(D_METHOD("join_dht", "public_key", "secret_key", "bootstrap_nodes"),
&HyperswarmMultiplayerPeer::join_dht,
DEFVAL(PackedStringArray()));
ClassDB::bind_method(D_METHOD("connect_to_peer", "remote_public_key"),
&HyperswarmMultiplayerPeer::connect_to_peer);
ClassDB::bind_method(D_METHOD("listen_on_dht"), &HyperswarmMultiplayerPeer::listen_on_dht);
ClassDB::bind_method(D_METHOD("announce_topic", "topic"),
&HyperswarmMultiplayerPeer::announce_topic);
ClassDB::bind_method(D_METHOD("lookup_topic", "topic"),
&HyperswarmMultiplayerPeer::lookup_topic);
ClassDB::bind_method(D_METHOD("join_topic", "topic", "server", "client"),
&HyperswarmMultiplayerPeer::join_topic);
ClassDB::bind_method(D_METHOD("leave_topic", "topic"),
&HyperswarmMultiplayerPeer::leave_topic);
ClassDB::bind_method(D_METHOD("get_dht_status"), &HyperswarmMultiplayerPeer::get_dht_status);
// Swarm Manager API
ClassDB::bind_method(D_METHOD("swarm_join", "topic", "server", "client"),
&HyperswarmMultiplayerPeer::swarm_join);
ClassDB::bind_method(D_METHOD("swarm_leave", "topic"),
&HyperswarmMultiplayerPeer::swarm_leave);
ClassDB::bind_method(D_METHOD("swarm_join_peer", "public_key"),
&HyperswarmMultiplayerPeer::swarm_join_peer);
ClassDB::bind_method(D_METHOD("swarm_leave_peer", "public_key"),
&HyperswarmMultiplayerPeer::swarm_leave_peer);
ClassDB::bind_method(D_METHOD("get_swarm_status"),
&HyperswarmMultiplayerPeer::get_swarm_status);
ClassDB::bind_method(D_METHOD("get_connection_diagnostics"),
&HyperswarmMultiplayerPeer::get_connection_diagnostics);
// DHT Storage API
ClassDB::bind_method(D_METHOD("dht_put", "data"), &HyperswarmMultiplayerPeer::dht_put);
ClassDB::bind_method(D_METHOD("dht_get", "key"), &HyperswarmMultiplayerPeer::dht_get);
ClassDB::bind_method(D_METHOD("dht_mutable_put", "data", "seq"),
&HyperswarmMultiplayerPeer::dht_mutable_put);
ClassDB::bind_method(D_METHOD("dht_mutable_get", "public_key", "min_seq"),
&HyperswarmMultiplayerPeer::dht_mutable_get);
ADD_SIGNAL(MethodInfo("swarm_connection",
PropertyInfo(Variant::INT, "peer_id"),
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "public_key"),
PropertyInfo(Variant::BOOL, "is_client")));
ADD_SIGNAL(MethodInfo("dht_peer_found",
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "public_key")));
ADD_SIGNAL(MethodInfo("dht_put_complete",
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "key")));
ADD_SIGNAL(MethodInfo("dht_get_complete",
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "key"),
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "value")));
ADD_SIGNAL(MethodInfo("dht_mutable_put_complete",
PropertyInfo(Variant::BOOL, "success")));
ADD_SIGNAL(MethodInfo("dht_mutable_get_complete",
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "public_key"),
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "value"),
PropertyInfo(Variant::INT, "seq")));
}
HyperswarmMultiplayerPeer::HyperswarmMultiplayerPeer()
: _unique_id(0),
_target_peer(0),
_transfer_channel(0),
_transfer_mode(TRANSFER_MODE_RELIABLE),
_connection_status(CONNECTION_DISCONNECTED),
_is_server_peer(false),
_refuse_connections(false),
_has_current_packet(false) {
}
HyperswarmMultiplayerPeer::~HyperswarmMultiplayerPeer() {
_close();
}
// ---- Custom Methods ----
Error HyperswarmMultiplayerPeer::create_server(int port) {
if (_connection_status != CONNECTION_DISCONNECTED) {
return ERR_ALREADY_IN_USE;
}
_is_server_peer = true;
_unique_id = 1; // Server is always peer 1.
if (!_transport.start_server(port)) {
return ERR_CANT_CREATE;
}
_connection_status = CONNECTION_CONNECTED;
UtilityFunctions::print("[HyperswarmPeer] Server started on port ", _transport.bound_port());
return OK;
}
Error HyperswarmMultiplayerPeer::create_client(const String &address, int port) {
if (_connection_status != CONNECTION_DISCONNECTED) {
return ERR_ALREADY_IN_USE;
}
_is_server_peer = false;
// Generate a random peer ID (2 to INT32_MAX).
_unique_id = 2 + (rand() % 2147483645);
CharString addr_utf8 = address.utf8();
if (!_transport.start_client(addr_utf8.get_data(), port)) {
return ERR_CANT_CONNECT;
}
_connection_status = CONNECTION_CONNECTING;
UtilityFunctions::print("[HyperswarmPeer] Connecting to ", address, ":", port);
return OK;
}
Dictionary HyperswarmMultiplayerPeer::generate_keypair() { // NOLINT - non-static for Godot binding
NoiseKeypair kp = NoiseHandshake::generate_keypair();
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), kp.public_key, 32);
PackedByteArray sk;
sk.resize(32);
memcpy(sk.ptrw(), kp.secret_key, 32);
Dictionary result;
result["public_key"] = pk;
result["secret_key"] = sk;
return result;
}
Error HyperswarmMultiplayerPeer::create_server_with_keypair(int port,
const PackedByteArray &public_key, const PackedByteArray &secret_key) {
if (_connection_status != CONNECTION_DISCONNECTED) {
return ERR_ALREADY_IN_USE;
}
if (public_key.size() != 32 || secret_key.size() != 32) {
return ERR_INVALID_PARAMETER;
}
_is_server_peer = true;
_unique_id = 1;
NoiseKeypair kp;
memcpy(kp.public_key, public_key.ptr(), 32);
memcpy(kp.secret_key, secret_key.ptr(), 32);
if (!_transport.start_server_encrypted(port, kp)) {
return ERR_CANT_CREATE;
}
_connection_status = CONNECTION_CONNECTED;
UtilityFunctions::print("[HyperswarmPeer] Encrypted server started on port ", _transport.bound_port());
return OK;
}
Error HyperswarmMultiplayerPeer::create_client_with_key(const String &address, int port,
const PackedByteArray &remote_public_key) {
if (_connection_status != CONNECTION_DISCONNECTED) {
return ERR_ALREADY_IN_USE;
}
if (remote_public_key.size() != 32) {
return ERR_INVALID_PARAMETER;
}
_is_server_peer = false;
_unique_id = 2 + (rand() % 2147483645);
CharString addr_utf8 = address.utf8();
if (!_transport.start_client_encrypted(addr_utf8.get_data(), port, remote_public_key.ptr())) {
return ERR_CANT_CONNECT;
}
_connection_status = CONNECTION_CONNECTING;
UtilityFunctions::print("[HyperswarmPeer] Encrypted connection to ", address, ":", port);
return OK;
}
PackedByteArray HyperswarmMultiplayerPeer::get_peer_public_key(int peer_id) const {
PackedByteArray result;
uint8_t pk[32];
if (_transport.get_peer_public_key(peer_id, pk)) {
result.resize(32);
memcpy(result.ptrw(), pk, 32);
}
return result;
}
Dictionary HyperswarmMultiplayerPeer::get_stats() const {
Dictionary stats;
stats["unique_id"] = _unique_id;
stats["is_server"] = _is_server_peer;
stats["connection_status"] = static_cast<int>(_connection_status);
stats["incoming_packets"] = _transport.incoming().size();
stats["bound_port"] = _transport.bound_port();
return stats;
}
Dictionary HyperswarmMultiplayerPeer::get_peer_rtt(int peer_id) const {
Dictionary result;
UdxTransport::PeerRttStats stats;
if (_transport.get_peer_rtt(peer_id, stats)) {
result["srtt"] = stats.srtt_ms;
result["rttvar"] = stats.rttvar_ms;
result["rto"] = stats.rto_ms;
result["min_rtt"] = stats.min_rtt_ms;
}
return result;
}
// ---- DHT Methods ----
Dictionary HyperswarmMultiplayerPeer::generate_dht_keypair() { // NOLINT
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), kp.public_key, 32);
PackedByteArray sk;
sk.resize(64);
memcpy(sk.ptrw(), kp.secret_key, 64);
Dictionary result;
result["public_key"] = pk;
result["secret_key"] = sk;
return result;
}
Error HyperswarmMultiplayerPeer::join_dht(const PackedByteArray &public_key,
const PackedByteArray &secret_key,
const PackedStringArray &bootstrap_nodes) {
if (public_key.size() != 32 || secret_key.size() != 64) {
return ERR_INVALID_PARAMETER;
}
Ed25519KeyPair kp;
memcpy(kp.public_key, public_key.ptr(), 32);
memcpy(kp.secret_key, secret_key.ptr(), 64);
// The default list lives in UdxTransport, which applies it whenever a caller
// names no nodes. A SECOND copy here is what let the session-daemon ship with
// no bootstrap at all: it never went through this function, so it never got
// the default, and "omit --bootstrap to use the public DHT" was true only of
// the Godot path.
const char **nodes = nullptr;
int count = 0;
// Use custom bootstrap if provided.
//
// LOCAL, and in two passes, both deliberately.
//
// These were `static`, so every HyperswarmMultiplayerPeer on the machine shared
// one list: the second peer's join_dht cleared and refilled the vectors the
// first peer's transport was still pointing at, and start_dht does not read the
// list — it queues an init the UDX thread performs later. Locals are correct
// now that start_dht copies.
//
// And the pointers are taken in a SECOND pass because a vector of short strings
// stores them inside its own element block: taking c_str() as you push frees,
// on the next growth, the block every earlier pointer points into. Measured as
// a heap-use-after-free at two nodes; one node never grew, which is why an
// integration suite that passes exactly one bootstrap address never saw it.
std::vector<std::string> custom_strs;
std::vector<const char *> custom_ptrs;
if (bootstrap_nodes.size() > 0) {
custom_strs.reserve(bootstrap_nodes.size());
for (int i = 0; i < bootstrap_nodes.size(); i++) {
custom_strs.push_back(bootstrap_nodes[i].utf8().get_data());
}
custom_ptrs.reserve(custom_strs.size());
for (const std::string &s : custom_strs) {
custom_ptrs.push_back(s.c_str());
}
nodes = custom_ptrs.data();
count = static_cast<int>(custom_ptrs.size());
}
_transport.start_dht(kp, nodes, count);
// Derive unique ID from our own public key so it's globally consistent.
// Every machine computes the same peer_id for the same Ed25519 key.
if (_unique_id == 0) {
_unique_id = pk_to_peer_id(kp.public_key);
}
_connection_status = CONNECTION_CONNECTING;
UtilityFunctions::print("[HyperswarmPeer] Joined DHT, bootstrapping...");
return OK;
}
Error HyperswarmMultiplayerPeer::connect_to_peer(const PackedByteArray &remote_public_key) {
if (remote_public_key.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_running()) {
return ERR_UNCONFIGURED;
}
_transport.dht_connect(remote_public_key.ptr());
UtilityFunctions::print("[HyperswarmPeer] Connecting to peer via DHT...");
return OK;
}
Error HyperswarmMultiplayerPeer::listen_on_dht() {
if (!_transport.is_running()) {
return ERR_UNCONFIGURED;
}
// Don't override unique_id if already set by join_dht (mesh mode).
if (_unique_id == 0) {
_unique_id = 1;
}
_is_server_peer = true;
_connection_status = CONNECTION_CONNECTED;
_transport.dht_listen();
UtilityFunctions::print("[HyperswarmPeer] Listening for DHT connections");
return OK;
}
Error HyperswarmMultiplayerPeer::announce_topic(const PackedByteArray &topic) {
if (topic.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_announce(topic.ptr());
return OK;
}
Error HyperswarmMultiplayerPeer::lookup_topic(const PackedByteArray &topic) {
if (topic.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_lookup(topic.ptr());
return OK;
}
Error HyperswarmMultiplayerPeer::join_topic(const PackedByteArray &topic,
bool server, bool client) {
if (topic.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!server && !client) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_join_topic(topic.ptr(), server, client);
return OK;
}
Error HyperswarmMultiplayerPeer::leave_topic(const PackedByteArray &topic) {
if (topic.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_leave_topic(topic.ptr());
return OK;
}
Dictionary HyperswarmMultiplayerPeer::get_dht_status() const {
Dictionary status;
status["active"] = _transport.is_dht_active();
status["bootstrapped"] = _transport.is_dht_bootstrapped();
status["running"] = _transport.is_running();
status["bound_port"] = _transport.bound_port();
// Include punch statistics if available.
const PunchStats *ps = _transport.punch_stats();
if (ps) {
Dictionary punches;
punches["total"] = ps->total;
punches["consistent"] = ps->consistent_ok;
punches["random"] = ps->random_ok;
punches["open"] = ps->open_ok;
punches["timeouts"] = ps->timeouts;
status["punches"] = punches;
}
return status;
}
// ---- Swarm Manager Methods ----
Error HyperswarmMultiplayerPeer::swarm_join(const PackedByteArray &topic,
bool server, bool client) {
if (topic.size() != 32) return ERR_INVALID_PARAMETER;
if (!server && !client) return ERR_INVALID_PARAMETER;
if (!_transport.is_dht_active()) return ERR_UNCONFIGURED;
// Swarm join = mesh mode: all peers are equal (no server).
// Preserve unique_id from join_dht(). is_server() returns false for all.
if (server || client) {
_connection_status = CONNECTION_CONNECTED;
}
_transport.swarm_join(topic.ptr(), server, client);
return OK;
}
Error HyperswarmMultiplayerPeer::swarm_leave(const PackedByteArray &topic) {
if (topic.size() != 32) return ERR_INVALID_PARAMETER;
if (!_transport.is_dht_active()) return ERR_UNCONFIGURED;
_transport.swarm_leave(topic.ptr());
return OK;
}
Error HyperswarmMultiplayerPeer::swarm_join_peer(const PackedByteArray &public_key) {
if (public_key.size() != 32) return ERR_INVALID_PARAMETER;
if (!_transport.is_dht_active()) return ERR_UNCONFIGURED;
_transport.swarm_join_peer(public_key.ptr());
return OK;
}
Error HyperswarmMultiplayerPeer::swarm_leave_peer(const PackedByteArray &public_key) {
if (public_key.size() != 32) return ERR_INVALID_PARAMETER;
if (!_transport.is_dht_active()) return ERR_UNCONFIGURED;
_transport.swarm_leave_peer(public_key.ptr());
return OK;
}
Dictionary HyperswarmMultiplayerPeer::get_swarm_status() const {
SwarmStatus ss = _transport.get_swarm_status();
Dictionary result;
result["peers_known"] = ss.peers_known;
result["peers_connected"] = ss.peers_connected;
result["connecting"] = ss.connecting;
result["queue_size"] = ss.queue_size;
result["max_peers"] = ss.max_peers;
result["max_parallel"] = ss.max_parallel;
result["explicit_count"] = ss.explicit_count;
result["all_connections"] = ss.all_connections;
result["client_connections"] = ss.client_connections;
result["flushed"] = ss.flushed;
return result;
}
// ---- Connection Diagnostics ----
static String _ipv4_to_string(const IPv4Address &addr) {
char buf[32];
snprintf(buf, sizeof(buf), "%u.%u.%u.%u:%u",
addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port);
return String(buf);
}
static const char *_conn_state_name(DhtConnectionState s) {
switch (s) {
case DHT_CONN_IDLE: return "IDLE";
case DHT_CONN_FINDING: return "FINDING";
case DHT_CONN_HANDSHAKING: return "HANDSHAKING";
case DHT_CONN_HOLEPUNCHING: return "HOLEPUNCHING";
case DHT_CONN_PUNCHING: return "PUNCHING";
case DHT_CONN_CONNECTED: return "CONNECTED";
case DHT_CONN_FAILED: return "FAILED";
default: return "UNKNOWN";
}
}
Array HyperswarmMultiplayerPeer::get_connection_diagnostics() const {
Array result;
DhtConnectionInfo infos[DHT_MAX_CONNECTIONS];
int count = _transport.get_connection_diagnostics(infos, DHT_MAX_CONNECTIONS);
for (int i = 0; i < count; i++) {
const DhtConnectionInfo &info = infos[i];
Dictionary d;
d["id"] = info.id;
d["state"] = String(_conn_state_name(info.state));
d["is_initiator"] = info.is_initiator;
// Remote public key as hex (first 16 chars for readability).
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), info.remote_public_key, 32);
d["remote_pk"] = pk.hex_encode().left(16);
d["relay"] = info.has_relay ? _ipv4_to_string(info.relay_address) : String("");
d["server"] = info.has_server_address ? _ipv4_to_string(info.server_address) : String("");
d["punched_address"] = _ipv4_to_string(info.punched_address);
d["punched"] = info.punched;
d["holepunch_round"] = info.holepunch_round;
d["firewall_local"] = info.local_firewall;
d["firewall_remote"] = info.remote_firewall;
d["elapsed_ms"] = static_cast<int64_t>(info.elapsed_ms);
d["error"] = info.error ? String(info.error) : String("");
Array addrs;
for (int j = 0; j < info.remote_address_count; j++) {
addrs.append(_ipv4_to_string(info.remote_addresses[j]));
}
d["remote_addresses"] = addrs;
result.append(d);
}
return result;
}
// ---- DHT Storage Methods ----
Error HyperswarmMultiplayerPeer::dht_put(const PackedByteArray &data) {
if (data.size() == 0) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_immutable_put(data.ptr(), data.size());
return OK;
}
Error HyperswarmMultiplayerPeer::dht_get(const PackedByteArray &key) {
if (key.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_immutable_get(key.ptr());
return OK;
}
Error HyperswarmMultiplayerPeer::dht_mutable_put(const PackedByteArray &data, int seq) {
if (data.size() == 0) {
return ERR_INVALID_PARAMETER;
}
if (seq < 0) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_mutable_put(data.ptr(), data.size(), static_cast<uint64_t>(seq));
return OK;
}
Error HyperswarmMultiplayerPeer::dht_mutable_get(const PackedByteArray &public_key, int min_seq) {
if (public_key.size() != 32) {
return ERR_INVALID_PARAMETER;
}
if (!_transport.is_dht_active()) {
return ERR_UNCONFIGURED;
}
_transport.dht_mutable_get(public_key.ptr(), static_cast<uint64_t>(min_seq));
return OK;
}
// ---- Required Overrides ----
void HyperswarmMultiplayerPeer::_close() {
_transport.stop();
_connection_status = CONNECTION_DISCONNECTED;
_unique_id = 0;
_has_current_packet = false;
}
void HyperswarmMultiplayerPeer::_disconnect_peer(int32_t p_peer, bool p_force) {
_transport.disconnect_peer(p_peer, p_force);
}
Error HyperswarmMultiplayerPeer::_get_packet(const uint8_t **r_buffer, int32_t *r_buffer_size) {
if (!_transport.incoming().pop(_current_packet)) {
return ERR_UNAVAILABLE;
}
_has_current_packet = true;
*r_buffer = _current_packet.data.data();
*r_buffer_size = static_cast<int32_t>(_current_packet.data.size());
return OK;
}
Error HyperswarmMultiplayerPeer::_put_packet(const uint8_t *p_buffer, int32_t p_buffer_size) {
if (_connection_status != CONNECTION_CONNECTED) {
return ERR_UNCONFIGURED;
}
PacketTransferMode mode;
switch (_transfer_mode) {
case TRANSFER_MODE_UNRELIABLE:
mode = PACKET_UNRELIABLE;
break;
case TRANSFER_MODE_UNRELIABLE_ORDERED:
mode = PACKET_UNRELIABLE_ORDERED;
break;
case TRANSFER_MODE_RELIABLE:
default:
mode = PACKET_RELIABLE;
break;
}
_transport.send(_target_peer, p_buffer, p_buffer_size, mode, _transfer_channel);
return OK;
}
int32_t HyperswarmMultiplayerPeer::_get_available_packet_count() const {
return _transport.incoming().size();
}
int32_t HyperswarmMultiplayerPeer::_get_max_packet_size() const {
// UDX max payload: MTU (1200) - UDX header (20) - our header (1) = 1179
// But for reliable streams, libudx handles fragmentation.
return 1 << 20; // 1 MB — libudx handles fragmentation for reliable writes.
}
int32_t HyperswarmMultiplayerPeer::_get_packet_peer() const {
if (_has_current_packet) {
return _current_packet.peer_id;
}
// Peek at next packet without removing it.
Packet pkt;
if (_transport.incoming().peek(pkt)) {
return pkt.peer_id;
}
return 0;
}
int32_t HyperswarmMultiplayerPeer::_get_packet_channel() const {
if (_has_current_packet) {
return _current_packet.channel;
}
Packet pkt;
if (_transport.incoming().peek(pkt)) {
return pkt.channel;
}
return 0;
}
MultiplayerPeer::TransferMode HyperswarmMultiplayerPeer::_get_packet_mode() const {
PacketTransferMode mode = PACKET_RELIABLE;
if (_has_current_packet) {
mode = _current_packet.mode;
} else {
Packet pkt;
if (_transport.incoming().peek(pkt)) {
mode = pkt.mode;
}
}
switch (mode) {
case PACKET_UNRELIABLE:
return TRANSFER_MODE_UNRELIABLE;
case PACKET_UNRELIABLE_ORDERED:
return TRANSFER_MODE_UNRELIABLE_ORDERED;
case PACKET_RELIABLE:
default:
return TRANSFER_MODE_RELIABLE;
}
}
MultiplayerPeer::ConnectionStatus HyperswarmMultiplayerPeer::_get_connection_status() const {
return _connection_status;
}
int32_t HyperswarmMultiplayerPeer::_get_unique_id() const {
return _unique_id;
}
bool HyperswarmMultiplayerPeer::_is_server() const {
return _is_server_peer;
}
void HyperswarmMultiplayerPeer::_set_target_peer(int32_t p_peer) {
_target_peer = p_peer;
}
void HyperswarmMultiplayerPeer::_set_transfer_channel(int32_t p_channel) {
_transfer_channel = p_channel;
}
void HyperswarmMultiplayerPeer::_set_transfer_mode(MultiplayerPeer::TransferMode p_mode) {
_transfer_mode = p_mode;
}
int32_t HyperswarmMultiplayerPeer::_get_transfer_channel() const {
return _transfer_channel;
}
MultiplayerPeer::TransferMode HyperswarmMultiplayerPeer::_get_transfer_mode() const {
return _transfer_mode;
}
void HyperswarmMultiplayerPeer::_poll() {
// Process peer events from the UDX thread and emit Godot signals.
PeerEvent event;
while (_transport.pop_peer_event(event)) {
if (event.connected) {
if (!_is_server_peer && _connection_status == CONNECTION_CONNECTING) {
_connection_status = CONNECTION_CONNECTED;
}
emit_signal("peer_connected", event.peer_id);
} else {
emit_signal("peer_disconnected", event.peer_id);
}
}
// Process DHT peer found events.
PeerFoundEvent found;
while (_transport.pop_peer_found_event(found)) {
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), found.public_key, 32);
emit_signal("dht_peer_found", pk);
}
// Process swarm events (auto-connect notifications).
SwarmEvent swarm_evt;
while (_transport.pop_swarm_event(swarm_evt)) {
if (swarm_evt.connected) {
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), swarm_evt.public_key, 32);
emit_signal("swarm_connection", swarm_evt.peer_id, pk, swarm_evt.is_client);
}
}
// Process DHT storage events.
DhtStorageEvent storage_evt;
while (_transport.pop_storage_event(storage_evt)) {
switch (storage_evt.type) {
case DhtStorageEvent::IMMUTABLE_PUT_COMPLETE: {
PackedByteArray key;
key.resize(32);
memcpy(key.ptrw(), storage_evt.key, 32);
emit_signal("dht_put_complete", key);
break;
}
case DhtStorageEvent::IMMUTABLE_GET_COMPLETE: {
PackedByteArray key;
key.resize(32);
memcpy(key.ptrw(), storage_evt.key, 32);
PackedByteArray value;
if (storage_evt.success && !storage_evt.value.empty()) {
value.resize(static_cast<int64_t>(storage_evt.value.size()));
memcpy(value.ptrw(), storage_evt.value.data(), storage_evt.value.size());
}
emit_signal("dht_get_complete", key, value);
break;
}
case DhtStorageEvent::MUTABLE_PUT_COMPLETE: {
emit_signal("dht_mutable_put_complete", storage_evt.success);
break;
}
case DhtStorageEvent::MUTABLE_GET_COMPLETE: {
PackedByteArray pk;
pk.resize(32);
memcpy(pk.ptrw(), storage_evt.key, 32);
PackedByteArray value;
if (storage_evt.success && !storage_evt.value.empty()) {
value.resize(static_cast<int64_t>(storage_evt.value.size()));
memcpy(value.ptrw(), storage_evt.value.data(), storage_evt.value.size());
}
emit_signal("dht_mutable_get_complete", pk, value,
static_cast<int64_t>(storage_evt.seq));
break;
}
}
}
}
// ---- Optional Overrides ----
void HyperswarmMultiplayerPeer::_set_refuse_new_connections(bool p_enable) {
_refuse_connections = p_enable;
}
bool HyperswarmMultiplayerPeer::_is_refusing_new_connections() const {
return _refuse_connections;
}
bool HyperswarmMultiplayerPeer::_is_server_relay_supported() const {
// P2P mesh — no server relay needed.
return false;
}
} // namespace godot