OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / src / hyperswarm_peer.cpp

827 lines · cpp ref:
#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