Files
SpaceBots/addons/netfox.noray/noray.gd

208 lines
5.7 KiB
GDScript3
Raw Normal View History

2026-01-21 23:40:20 +01:00
extends Node
## A noray client for Godot.
##
## See: https://github.com/foxssake/noray
var _peer: StreamPeerTCP = StreamPeerTCP.new()
var _protocol: NorayProtocolHandler = NorayProtocolHandler.new()
var _address: String = ""
var _oid: String = ""
var _pid: String = ""
var _local_port: int = -1
static var _logger: _NetfoxLogger = _NetfoxLogger.for_noray("Noray")
## Open ID.
##
## [i]read-only[/i], this is set after registering as host.
var oid: String:
get: return _oid
set(v): push_error("Trying to set read-only variable oid")
## Private ID.
##
## [i]read-only[/i], this is set after registering as host.
var pid: String:
get: return _pid
set(v): push_error("Trying to set read-only variable pid")
## Registered local port.
##
## This is the port that servers should listen on and the port that clients
## should bind to ( i.e. use as local port ), since this port has been
## registered with noray as part of this machine's external address, and this
## is the port over which any handshake happens.
##
## [i]read-only[/i], this is set after registering remote.
var local_port: int:
get: return _local_port
set(v): push_error("Trying to set read-only variable local_port")
## Emitted for any command received from noray.
signal on_command(command: String, data: String)
## Emitted when connected to noray.
signal on_connect_to_host()
## Emitted when disconnected from noray.
signal on_disconnect_from_host()
## Emitted when an OpenID is received from noray.
signal on_oid(oid: String)
## Emitted when a PrivateID is received from noray.
signal on_pid(pid: String)
## Emitted when a connect over NAT command is received from noray.
signal on_connect_nat(address: String, port: int)
## Emitted when a connect over relay command is received from noray.
signal on_connect_relay(address: String, port: int)
func _enter_tree():
_protocol.on_command.connect(func (cmd, data): on_command.emit(cmd, data))
on_command.connect(_handle_commands)
## Connect to noray at host.
func connect_to_host(hostname: String, port: int = 8890) -> Error:
if is_connected_to_host():
disconnect_from_host()
_logger.info("Trying to connect to noray at %s:%s", [hostname, port])
var address = IP.resolve_hostname(hostname, IP.TYPE_IPV4)
if address:
_logger.debug("Resolved noray host to %s", [address])
else:
_logger.error("Couldn't resolve hostname %s", [hostname])
var err = _peer.connect_to_host(address, port)
if err != Error.OK:
return err
_peer.set_no_delay(true)
_protocol.reset()
while _peer.get_status() < 2:
_peer.poll()
await get_tree().process_frame
if _peer.get_status() == _peer.STATUS_CONNECTED:
_address = address
_logger.info("Connected to noray at %s:%s", [address, port])
on_connect_to_host.emit()
return OK
else:
_logger.error("Connection failed to noray at %s:%s, connection status %s", [address, port, _peer.get_status()])
disconnect_from_host()
return ERR_CONNECTION_ERROR
## Check if connected to any host.
func is_connected_to_host() -> bool:
return _peer.get_status() == _peer.STATUS_CONNECTED
## Disconnect from noray.
##
## Does nothing if already disconnected.
func disconnect_from_host():
if is_connected_to_host():
on_disconnect_from_host.emit()
_peer.disconnect_from_host()
## Register as host.
func register_host() -> Error:
return _put_command("register-host")
## Register remote address.
func register_remote(registrar_port: int = 8809, timeout: float = 8, interval: float = 0.1) -> Error:
if not is_connected_to_host():
return ERR_CONNECTION_ERROR
if not pid:
return ERR_UNAUTHORIZED
var result = ERR_TIMEOUT
var udp = PacketPeerUDP.new()
udp.bind(0)
udp.set_dest_address(_address, registrar_port)
_logger.debug("Bound UDP to port %s", [udp.get_local_port()])
var packet = pid.to_utf8_buffer()
while timeout > 0:
udp.put_packet(packet)
while udp.get_available_packet_count() > 0:
var recv = udp.get_packet().get_string_from_utf8()
if recv == "OK":
_local_port = udp.get_local_port()
_logger.info("Registered local port %s to remote", [_local_port])
result = OK
timeout = 0 # Break outer loop
break
else:
_logger.error("Failed to register local port!")
result = FAILED
timeout = 0 # Break outer loop
break
# Sleep
await get_tree().create_timer(interval).timeout
timeout -= interval
udp.close()
return result
## Connect to a given host by OID over NAT.
func connect_nat(host_oid: String) -> Error:
return _put_command("connect", host_oid)
## Connect to a given host by OID over relay.
func connect_relay(host_oid: String) -> Error:
return _put_command("connect-relay", host_oid)
func _process(_delta):
if not is_connected_to_host():
return
_peer.poll()
var available = _peer.get_available_bytes()
if available <= 0:
return
_protocol.ingest(_peer.get_utf8_string(available))
func _put_command(command: String, data = null) -> Error:
if not is_connected_to_host():
return ERR_CONNECTION_ERROR
if data != null:
_peer.put_data(("%s %s\n" % [command, data]).to_utf8_buffer())
else:
_peer.put_data((command + "\n").to_utf8_buffer())
return OK
func _handle_commands(command: String, data: String):
if command == "set-oid":
_oid = data
on_oid.emit(oid)
_logger.debug("Saved OID: %s", [oid])
elif command == "set-pid":
_pid = data
on_pid.emit(pid)
_logger.debug("Saved PID: %s", [pid])
elif command == "connect":
var parts = data.split(":")
var host = parts[0]
var port = parts[1].to_int()
_logger.debug("Received connect command to %s:%s", [host, port])
on_connect_nat.emit(host, port)
elif command == "connect-relay":
var host = _address
var port = data.to_int()
_logger.debug("Received connect relay command to %s:%s", [host, port])
on_connect_relay.emit(host, port)
else:
_logger.trace("Received command %s %s", [command, data])