fix: synchronize late-joining players
This commit is contained in:
parent
5c3eba1ad2
commit
2aeaf1d44d
6 changed files with 371 additions and 49 deletions
|
|
@ -5,6 +5,7 @@ const BURST_COUNT: int = 3
|
|||
const WINDOW_COUNT: int = 5
|
||||
const WINDOW_SECONDS: float = 10.0
|
||||
const CALL_COOLDOWN_MILLISECONDS: int = 90
|
||||
const MAX_PENDING_MESSAGES_PER_PEER: int = 8
|
||||
const DEDICATED_CHAT_WARNING: String = (
|
||||
"Privacy notice: This dedicated server records and retains chat messages."
|
||||
)
|
||||
|
|
@ -40,6 +41,7 @@ var _last_call_msec: Dictionary[int, int] = {}
|
|||
var _call_variant_indices: Dictionary[int, int] = {}
|
||||
var _sequence: int = 0
|
||||
var _peer_names: Dictionary[int, String] = {}
|
||||
var _pending_peer_messages: Dictionary[int, Array] = {}
|
||||
var _relationships: PlayerRelationshipStore
|
||||
var _dedicated_history_enabled: bool = false
|
||||
var _chat_log_path: String = ""
|
||||
|
|
@ -418,9 +420,11 @@ func _apply_message(
|
|||
else:
|
||||
var sender_id := int(data["sender_peer_id"])
|
||||
var record := _session.get_peer_record(sender_id)
|
||||
if record == null:
|
||||
_queue_pending_peer_message(sender_id, data, emit_live_signals)
|
||||
return
|
||||
valid_signature = (
|
||||
record != null
|
||||
and record.identity_fingerprint == str(data["sender_fingerprint"])
|
||||
record.identity_fingerprint == str(data["sender_fingerprint"])
|
||||
and _session.verify_peer_action(
|
||||
sender_id,
|
||||
"chat_send",
|
||||
|
|
@ -466,7 +470,47 @@ func receive_chat_rejection(message: String) -> void:
|
|||
send_rejected.emit(message.left(80))
|
||||
|
||||
|
||||
func _queue_pending_peer_message(
|
||||
peer_id: int,
|
||||
message: Dictionary,
|
||||
emit_live_signals: bool,
|
||||
) -> void:
|
||||
if peer_id <= 0:
|
||||
return
|
||||
var pending: Array = _pending_peer_messages.get(peer_id, [])
|
||||
var message_id := str(message.get("message_id", ""))
|
||||
for value: Variant in pending:
|
||||
if (
|
||||
typeof(value) == TYPE_DICTIONARY
|
||||
and str(value.get("message", {}).get("message_id", "")) == message_id
|
||||
):
|
||||
return
|
||||
pending.append({
|
||||
"message": message.duplicate(true),
|
||||
"emit_live_signals": emit_live_signals,
|
||||
})
|
||||
while pending.size() > MAX_PENDING_MESSAGES_PER_PEER:
|
||||
pending.pop_front()
|
||||
_pending_peer_messages[peer_id] = pending
|
||||
|
||||
|
||||
func _flush_pending_peer_messages(peer_id: int) -> void:
|
||||
var pending: Array = _pending_peer_messages.get(peer_id, [])
|
||||
_pending_peer_messages.erase(peer_id)
|
||||
for value: Variant in pending:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var queued: Dictionary = value
|
||||
if typeof(queued.get("message")) != TYPE_DICTIONARY:
|
||||
continue
|
||||
_apply_message(
|
||||
queued["message"],
|
||||
bool(queued.get("emit_live_signals", true)),
|
||||
)
|
||||
|
||||
|
||||
func _on_peer_authenticated(peer_id: int, display_name: String) -> void:
|
||||
_flush_pending_peer_messages(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
_peer_names[peer_id] = display_name
|
||||
|
|
@ -500,6 +544,7 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_rate_times.erase(peer_id)
|
||||
_last_call_msec.erase(peer_id)
|
||||
_call_variant_indices.erase(peer_id)
|
||||
_pending_peer_messages.erase(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
||||
|
|
@ -601,6 +646,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_last_call_msec.clear()
|
||||
_call_variant_indices.clear()
|
||||
_peer_names.clear()
|
||||
_pending_peer_messages.clear()
|
||||
_sequence = 0
|
||||
history_replaced.emit([])
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ func preview_appearance(appearance: Dictionary) -> bool:
|
|||
_preferences.display_name,
|
||||
snapshot,
|
||||
)
|
||||
var authorization := _make_appearance_preview_authorization(snapshot)
|
||||
var local_record := _session.get_peer_record(local_peer_id)
|
||||
if local_record != null:
|
||||
local_record.profile_authorization = authorization.duplicate(true)
|
||||
if _session.is_host():
|
||||
_broadcast_appearance_preview_to_supported_peers(
|
||||
local_peer_id,
|
||||
|
|
@ -105,20 +109,27 @@ func preview_appearance(appearance: Dictionary) -> bool:
|
|||
elif _session.supports_server_capability(
|
||||
NetworkProtocol.APPEARANCE_PREVIEW_CAPABILITY
|
||||
):
|
||||
var request: Dictionary = {
|
||||
"request_id": _new_id(),
|
||||
"session_id": _session.get_session_id(),
|
||||
"appearance": snapshot.duplicate(true),
|
||||
"sender_fingerprint": _session.get_local_identity_fingerprint(),
|
||||
}
|
||||
request["sender_signature"] = _session.sign_local_action(
|
||||
"appearance_preview",
|
||||
NetworkProfileProtocol.appearance_preview_signature_fields(request),
|
||||
)
|
||||
submit_appearance_preview.rpc_id(1, request)
|
||||
submit_appearance_preview.rpc_id(1, authorization)
|
||||
return true
|
||||
|
||||
|
||||
func _make_appearance_preview_authorization(
|
||||
appearance: Dictionary,
|
||||
) -> Dictionary:
|
||||
var request: Dictionary = {
|
||||
"domain": "appearance_preview",
|
||||
"request_id": _new_id(),
|
||||
"session_id": _session.get_session_id(),
|
||||
"appearance": appearance.duplicate(true),
|
||||
"sender_fingerprint": _session.get_local_identity_fingerprint(),
|
||||
}
|
||||
request["sender_signature"] = _session.sign_local_action(
|
||||
"appearance_preview",
|
||||
NetworkProfileProtocol.appearance_preview_signature_fields(request),
|
||||
)
|
||||
return request
|
||||
|
||||
|
||||
func restore_persisted_appearance() -> void:
|
||||
preview_appearance(_appearance_store.get_snapshot())
|
||||
|
||||
|
|
@ -274,6 +285,9 @@ func submit_appearance_preview(data: Dictionary) -> void:
|
|||
var appearance := CharacterCustomizationCatalog.sanitized_snapshot(
|
||||
data["appearance"]
|
||||
)
|
||||
var authorization := data.duplicate(true)
|
||||
authorization["domain"] = "appearance_preview"
|
||||
record.profile_authorization = authorization
|
||||
_session.apply_canonical_profile(
|
||||
sender_id,
|
||||
record.display_name,
|
||||
|
|
@ -482,23 +496,16 @@ func broadcast_profile_snapshot(
|
|||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
):
|
||||
return
|
||||
if not authorization.is_empty():
|
||||
var signed := authorization.duplicate(true)
|
||||
signed["display_name"] = display_name
|
||||
signed["appearance"] = appearance
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
if (
|
||||
record == null
|
||||
or record.identity_fingerprint
|
||||
!= str(signed.get("sender_fingerprint", ""))
|
||||
or not _session.verify_peer_action(
|
||||
peer_id,
|
||||
"profile_update",
|
||||
NetworkProfileProtocol.signature_fields(signed),
|
||||
signed.get("sender_signature", PackedByteArray()),
|
||||
)
|
||||
):
|
||||
return
|
||||
if (
|
||||
not authorization.is_empty()
|
||||
and not _session.verify_profile_authorization(
|
||||
peer_id,
|
||||
display_name,
|
||||
appearance,
|
||||
authorization,
|
||||
)
|
||||
):
|
||||
return
|
||||
_session.apply_canonical_profile(peer_id, display_name, appearance)
|
||||
_apply_to_avatar(peer_id, appearance)
|
||||
profile_snapshot_changed.emit(peer_id, display_name, appearance.duplicate(true))
|
||||
|
|
|
|||
|
|
@ -1559,8 +1559,9 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
own_snapshot["visual_yaw"] = float(entry["yaw"])
|
||||
own_avatar.apply_network_teleport(own_snapshot)
|
||||
return
|
||||
var peer_was_added: bool = false
|
||||
if not _registry.has_peer(peer_id):
|
||||
_registry.add_peer(
|
||||
if not _registry.add_peer(
|
||||
peer_id,
|
||||
entry["profile_id"],
|
||||
entry["display_name"],
|
||||
|
|
@ -1568,8 +1569,14 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
str(entry.get("identity_fingerprint", "")),
|
||||
str(entry.get("identity_public_key", "")),
|
||||
_sanitized_capabilities(entry.get("capability_flags", [])),
|
||||
)
|
||||
):
|
||||
return
|
||||
peer_was_added = true
|
||||
var added_record := _registry.get_peer(peer_id)
|
||||
if added_record != null:
|
||||
added_record.profile_authorization = Dictionary(
|
||||
entry.get("profile_authorization", {})
|
||||
).duplicate(true)
|
||||
if added_record != null and added_record.identity_authenticated:
|
||||
_archive_authenticated_identity(added_record)
|
||||
var status := _known_players.observe(
|
||||
|
|
@ -1596,6 +1603,8 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
var record := _registry.get_peer(peer_id)
|
||||
if avatar != null and record != null:
|
||||
avatar.apply_appearance_snapshot(record.appearance_snapshot)
|
||||
if peer_was_added and record != null:
|
||||
peer_authenticated.emit(peer_id, record.display_name)
|
||||
|
||||
|
||||
func _build_spawn_list() -> Array[Dictionary]:
|
||||
|
|
@ -1695,25 +1704,62 @@ func _update_local_operator() -> void:
|
|||
_local_operator = bool(_operator_peer_ids.get(local_peer_id, false))
|
||||
|
||||
|
||||
func _verify_spawn_identity(entry: Dictionary) -> bool:
|
||||
var fingerprint := str(entry.get("identity_fingerprint", ""))
|
||||
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
||||
str(entry.get("identity_public_key", ""))
|
||||
)
|
||||
if (
|
||||
NetworkIdentityCrypto.fingerprint_public_pem(public_pem) != fingerprint
|
||||
or typeof(entry.get("profile_authorization")) != TYPE_DICTIONARY
|
||||
):
|
||||
func verify_profile_authorization(
|
||||
peer_id: int,
|
||||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
authorization: Dictionary,
|
||||
) -> bool:
|
||||
var record := _registry.get_peer(peer_id)
|
||||
if record == null:
|
||||
return false
|
||||
return _verify_profile_authorization_fields(
|
||||
peer_id,
|
||||
record.profile_id,
|
||||
display_name,
|
||||
appearance,
|
||||
record.identity_fingerprint,
|
||||
record.identity_public_key,
|
||||
authorization,
|
||||
)
|
||||
|
||||
|
||||
func _verify_spawn_identity(entry: Dictionary) -> bool:
|
||||
if typeof(entry.get("profile_authorization")) != TYPE_DICTIONARY:
|
||||
return false
|
||||
return _verify_profile_authorization_fields(
|
||||
int(entry.get("peer_id", 0)),
|
||||
str(entry.get("profile_id", "")),
|
||||
str(entry.get("display_name", "")),
|
||||
Dictionary(entry.get("appearance", {})),
|
||||
str(entry.get("identity_fingerprint", "")),
|
||||
str(entry.get("identity_public_key", "")),
|
||||
Dictionary(entry.get("profile_authorization", {})),
|
||||
)
|
||||
|
||||
|
||||
func _verify_profile_authorization_fields(
|
||||
peer_id: int,
|
||||
profile_id: String,
|
||||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
fingerprint: String,
|
||||
identity_public_key: String,
|
||||
authorization: Dictionary,
|
||||
) -> bool:
|
||||
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
||||
identity_public_key
|
||||
)
|
||||
if NetworkIdentityCrypto.fingerprint_public_pem(public_pem) != fingerprint:
|
||||
return false
|
||||
var authorization: Dictionary = entry["profile_authorization"]
|
||||
if authorization.is_empty():
|
||||
return int(entry.get("peer_id", 0)) == 1
|
||||
return peer_id == 1
|
||||
if authorization.get("domain") == "handshake_client_profile":
|
||||
var hello := NetworkProtocol.make_client_hello(
|
||||
str(entry.get("profile_id", "")),
|
||||
str(entry.get("display_name", "")),
|
||||
profile_id,
|
||||
display_name,
|
||||
str(authorization.get("client_nonce", "")),
|
||||
Dictionary(entry.get("appearance", {})),
|
||||
appearance,
|
||||
fingerprint,
|
||||
authorization.get("signature", PackedByteArray()),
|
||||
)
|
||||
|
|
@ -1723,10 +1769,23 @@ func _verify_spawn_identity(entry: Dictionary) -> bool:
|
|||
NetworkProtocol.client_profile_fields(hello),
|
||||
hello["identity_signature"],
|
||||
)
|
||||
if authorization.get("domain") == "appearance_preview":
|
||||
if str(authorization.get("sender_fingerprint", "")) != fingerprint:
|
||||
return false
|
||||
var preview := authorization.duplicate(true)
|
||||
preview["appearance"] = appearance
|
||||
return NetworkIdentityCrypto.verify_fields(
|
||||
NetworkIdentityCrypto.load_public_key(public_pem),
|
||||
"appearance_preview",
|
||||
NetworkProfileProtocol.appearance_preview_signature_fields(preview),
|
||||
preview.get("sender_signature", PackedByteArray()),
|
||||
)
|
||||
if authorization.has("sender_signature"):
|
||||
if str(authorization.get("sender_fingerprint", "")) != fingerprint:
|
||||
return false
|
||||
var signed := authorization.duplicate(true)
|
||||
signed["display_name"] = entry.get("display_name", "")
|
||||
signed["appearance"] = entry.get("appearance", {})
|
||||
signed["display_name"] = display_name
|
||||
signed["appearance"] = appearance
|
||||
return NetworkIdentityCrypto.verify_fields(
|
||||
NetworkIdentityCrypto.load_public_key(public_pem),
|
||||
"profile_update",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue