feat: fence expired allocated reconnects

This commit is contained in:
Josh Creek
2026-09-01 08:52:12 +01:00
parent e25d61d80e
commit 3aad68a6e6
3 changed files with 41 additions and 5 deletions
+31 -1
View File
@@ -19,6 +19,7 @@ signal rejected(reason: String) # client-side only: the server refused our hel
signal welcomed() # client-side only: our hello was accepted
const TEAM_COUNT := 2
const RECONNECT_GRACE_SECONDS := 60.0
# player_name is the one client-supplied value in _hello that gets broadcast
# verbatim to every other peer (protocol_version/tick_hz are checked, never
@@ -55,6 +56,7 @@ var join_authorisation := ""
var require_join_authorisation := false
var _allowed_join_authorisations: Dictionary = {}
var _active_join_peers: Dictionary = {} # opaque authorisation -> peer_id
var _join_history: Dictionary = {} # token -> {generation, lost_at}
var _join_authorisation_context: Dictionary = {}
var _join_signing_key := PackedByteArray()
@@ -92,6 +94,7 @@ func _on_shutting_down() -> void:
roster.clear()
_allowed_join_authorisations.clear()
_active_join_peers.clear()
_join_history.clear()
_join_authorisation_context.clear()
_join_signing_key = PackedByteArray()
require_join_authorisation = false
@@ -119,6 +122,7 @@ func configure_join_authorisations(tokens: Array, context: Dictionary, signing_k
func _on_peer_disconnected(peer_id: int) -> void:
if not multiplayer.is_server():
return
NetworkManager.invalidate_peer(peer_id)
_remove_player(peer_id)
@@ -126,6 +130,9 @@ func _remove_player(peer_id: int) -> void:
for token in _active_join_peers.keys():
if int(_active_join_peers[token]) == peer_id:
_active_join_peers.erase(token)
var history: Dictionary = _join_history.get(token, {})
history["lost_at"] = Time.get_unix_time_from_system()
_join_history[token] = history
break
if not roster.has(peer_id):
return
@@ -197,6 +204,12 @@ func _hello(protocol_version: int, tick_hz: int, player_name: String, supplied_j
if require_join_authorisation and _active_join_peers.has(supplied_join_authorisation):
await _reject(peer_id, "join authorisation already in use")
return
var join_generation := 1
if require_join_authorisation:
join_generation = _reserve_join_authorisation(supplied_join_authorisation, peer_id)
if join_generation < 0:
await _reject(peer_id, "join authorisation reclaim expired")
return
if player_name.length() > MAX_INPUT_LENGTH:
await _reject(peer_id, "player name too long")
return
@@ -212,7 +225,10 @@ func _hello(protocol_version: int, tick_hz: int, player_name: String, supplied_j
var team := _pick_balanced_team()
roster[peer_id] = PlayerInfo.new(peer_id, clean_name, team, false)
if require_join_authorisation:
_active_join_peers[supplied_join_authorisation] = peer_id
# _reserve_join_authorisation already owns the active peer reservation;
# keeping the generation in the history makes fencing auditable without
# exposing it to the client.
_join_history[supplied_join_authorisation]["generation"] = join_generation
player_joined.emit(peer_id, clean_name) # local: the broadcast below is call_remote, never loops back to the server itself
_welcome.rpc_id(peer_id)
_player_joined.rpc(peer_id, clean_name, team, false) # broadcast, includes the new peer itself
@@ -268,6 +284,20 @@ func is_join_authorisation_active(token: String) -> bool:
return not token.is_empty() and _active_join_peers.has(token)
func _reserve_join_authorisation(token: String, peer_id: int) -> int:
if token.is_empty() or _active_join_peers.has(token):
return -1
var now := Time.get_unix_time_from_system()
var history: Dictionary = _join_history.get(token, {})
var lost_at := float(history.get("lost_at", 0.0))
if lost_at > 0.0 and now - lost_at > RECONNECT_GRACE_SECONDS:
return -1
var generation := int(history.get("generation", 0)) + 1
_join_history[token] = {"generation": generation, "lost_at": 0.0}
_active_join_peers[token] = peer_id
return generation
# Strips control/formatting characters (so a name can't corrupt a log line
# or blow out UI layout with e.g. embedded newlines) and clamps to display
# length. Input is already bounded to MAX_INPUT_LENGTH by the caller before