straywild/network/network_home_protocol.gd

176 lines
5.3 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 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 _valid_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 _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
)