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 )