188 lines
5.6 KiB
GDScript
188 lines
5.6 KiB
GDScript
class_name NetworkHomeProtocol
|
|
extends RefCounted
|
|
|
|
const PlayerHomeStateType = preload("res://homes/player_home_state.gd")
|
|
|
|
const CAPABILITY: StringName = &"portable_home_v1"
|
|
const RELIABLE_CHANNEL: int = NetworkProtocol.HOME_RELIABLE_CHANNEL
|
|
const MAX_ID_LENGTH: int = 96
|
|
const MAX_MESSAGE_LENGTH: int = 160
|
|
const MAX_TRANSITION_COORDINATE: float = 10000.0
|
|
const WORLD_SPACE: StringName = &"world"
|
|
|
|
enum TransitionAction {
|
|
ENTER,
|
|
EXIT,
|
|
}
|
|
|
|
|
|
static func make_space_id(owner_fingerprint: String) -> StringName:
|
|
return StringName("rv:%s" % owner_fingerprint)
|
|
|
|
|
|
static func manifest_digest(home_data: Dictionary) -> String:
|
|
var sanitized: Dictionary = PlayerHomeStateType.sanitize_save_data(home_data)
|
|
if sanitized.is_empty():
|
|
return ""
|
|
var canonical_owned: Array[Dictionary] = []
|
|
var owned: Dictionary = sanitized["owned_decor"]
|
|
var owned_ids: Array[String] = []
|
|
for value: Variant in owned.keys():
|
|
owned_ids.append(str(value))
|
|
owned_ids.sort()
|
|
for decor_id: String in owned_ids:
|
|
canonical_owned.append({
|
|
"id": decor_id,
|
|
"quantity": int(owned[decor_id]),
|
|
})
|
|
var placements: Array[Dictionary] = []
|
|
for value: Variant in sanitized["placements"]:
|
|
placements.append((value as Dictionary).duplicate(true))
|
|
placements.sort_custom(func(first: Dictionary, second: Dictionary) -> bool:
|
|
return str(first["instance_id"]) < str(second["instance_id"])
|
|
)
|
|
var canonical: Dictionary = {
|
|
"schema_version": int(sanitized["schema_version"]),
|
|
"tier": int(sanitized["tier"]),
|
|
"exterior_id": str(sanitized["exterior_id"]),
|
|
"privacy": int(sanitized["privacy"]),
|
|
"owned": canonical_owned,
|
|
"placements": placements,
|
|
"revision": int(sanitized["revision"]),
|
|
"next_instance_sequence": int(sanitized["next_instance_sequence"]),
|
|
}
|
|
return JSON.stringify(canonical).sha256_text()
|
|
|
|
|
|
static func manifest_signature_fields(data: Dictionary) -> Array:
|
|
return [
|
|
str(data.get("session_id", "")),
|
|
str(data.get("request_id", "")),
|
|
str(data.get("owner_fingerprint", "")),
|
|
int((data.get("home", {}) as Dictionary).get("revision", -1)),
|
|
str(data.get("digest", "")),
|
|
]
|
|
|
|
|
|
static func validate_manifest_request(value: Variant) -> bool:
|
|
if typeof(value) != TYPE_DICTIONARY:
|
|
return false
|
|
var data: Dictionary = value
|
|
if (
|
|
not _valid_id(data.get("request_id"))
|
|
or not _valid_id(data.get("session_id"))
|
|
or not NetworkIdentityCrypto.valid_fingerprint(
|
|
data.get("owner_fingerprint")
|
|
)
|
|
or typeof(data.get("home")) != TYPE_DICTIONARY
|
|
or typeof(data.get("digest")) != TYPE_STRING
|
|
or str(data["digest"]).length() != 64
|
|
or typeof(data.get("sender_signature")) != TYPE_PACKED_BYTE_ARRAY
|
|
):
|
|
return false
|
|
var sanitized: Dictionary = PlayerHomeStateType.sanitize_save_data(
|
|
data["home"]
|
|
)
|
|
return (
|
|
not sanitized.is_empty()
|
|
and manifest_digest(sanitized) == str(data["digest"])
|
|
)
|
|
|
|
|
|
static func validate_manifest_broadcast(value: Variant) -> bool:
|
|
if typeof(value) != TYPE_DICTIONARY:
|
|
return false
|
|
var data: Dictionary = value
|
|
return (
|
|
typeof(data.get("owner_peer_id")) == TYPE_INT
|
|
and int(data["owner_peer_id"]) > 0
|
|
and NetworkIdentityCrypto.valid_fingerprint(
|
|
data.get("owner_fingerprint")
|
|
)
|
|
and typeof(data.get("home")) == TYPE_DICTIONARY
|
|
and not PlayerHomeStateType.sanitize_save_data(data["home"]).is_empty()
|
|
and _valid_transform(data.get("exterior_transform"))
|
|
and typeof(data.get("interior_index")) == TYPE_INT
|
|
and int(data["interior_index"]) >= 0
|
|
and int(data["interior_index"]) < 128
|
|
)
|
|
|
|
|
|
static func transition_signature_fields(data: Dictionary) -> Array:
|
|
return [
|
|
str(data.get("session_id", "")),
|
|
str(data.get("request_id", "")),
|
|
int(data.get("action", -1)),
|
|
str(data.get("owner_fingerprint", "")),
|
|
]
|
|
|
|
|
|
static func validate_transition_request(value: Variant) -> bool:
|
|
if typeof(value) != TYPE_DICTIONARY:
|
|
return false
|
|
var data: Dictionary = value
|
|
return (
|
|
_valid_id(data.get("request_id"))
|
|
and _valid_id(data.get("session_id"))
|
|
and typeof(data.get("action")) == TYPE_INT
|
|
and int(data["action"]) >= TransitionAction.ENTER
|
|
and int(data["action"]) <= TransitionAction.EXIT
|
|
and NetworkIdentityCrypto.valid_fingerprint(
|
|
data.get("owner_fingerprint")
|
|
)
|
|
and typeof(data.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY
|
|
)
|
|
|
|
|
|
static func transform_to_array(value: Transform3D) -> Array[float]:
|
|
return [
|
|
value.basis.x.x, value.basis.x.y, value.basis.x.z,
|
|
value.basis.y.x, value.basis.y.y, value.basis.y.z,
|
|
value.basis.z.x, value.basis.z.y, value.basis.z.z,
|
|
value.origin.x, value.origin.y, value.origin.z,
|
|
]
|
|
|
|
|
|
static func array_to_transform(value: Variant) -> Transform3D:
|
|
if not validate_transition_transform(value):
|
|
return Transform3D()
|
|
var fields: Array = value
|
|
return Transform3D(
|
|
Basis(
|
|
Vector3(float(fields[0]), float(fields[1]), float(fields[2])),
|
|
Vector3(float(fields[3]), float(fields[4]), float(fields[5])),
|
|
Vector3(float(fields[6]), float(fields[7]), float(fields[8])),
|
|
),
|
|
Vector3(float(fields[9]), float(fields[10]), float(fields[11])),
|
|
)
|
|
|
|
|
|
static func validate_transition_transform(value: Variant) -> bool:
|
|
if not _valid_transform(value):
|
|
return false
|
|
var fields: Array = value
|
|
return (
|
|
absf(float(fields[9])) <= MAX_TRANSITION_COORDINATE
|
|
and absf(float(fields[10])) <= MAX_TRANSITION_COORDINATE
|
|
and absf(float(fields[11])) <= MAX_TRANSITION_COORDINATE
|
|
)
|
|
|
|
|
|
static func _valid_transform(value: Variant) -> bool:
|
|
if typeof(value) != TYPE_ARRAY or (value as Array).size() != 12:
|
|
return false
|
|
for component: Variant in value:
|
|
if (
|
|
typeof(component) not in [TYPE_FLOAT, TYPE_INT]
|
|
or not is_finite(float(component))
|
|
):
|
|
return false
|
|
return true
|
|
|
|
|
|
static func _valid_id(value: Variant) -> bool:
|
|
return (
|
|
typeof(value) == TYPE_STRING
|
|
and not str(value).is_empty()
|
|
and str(value).length() <= MAX_ID_LENGTH
|
|
)
|