netfishing/network/network_protocol.gd

124 lines
3.4 KiB
GDScript

class_name NetworkProtocol
extends RefCounted
const PROTOCOL_VERSION: int = 2
const GAME_BUILD: String = "prealpha"
const MAX_DISPLAY_NAME_LENGTH: int = 48
const MAX_PROFILE_ID_LENGTH: int = 96
const MAX_NONCE_LENGTH: int = 96
enum RejectionCode {
NONE,
MALFORMED_HANDSHAKE,
PROTOCOL_MISMATCH,
SERVER_FULL,
DUPLICATE_PROFILE,
AUTHENTICATION_TIMEOUT,
SERVER_SHUTTING_DOWN,
UNSUPPORTED_CLIENT,
}
static func make_client_hello(
profile_id: String,
display_name: String,
client_nonce: String,
) -> Dictionary:
return {
"protocol_version": PROTOCOL_VERSION,
"game_build": GAME_BUILD,
"local_profile_id": profile_id,
"display_name": display_name,
"client_nonce": client_nonce,
"capability_flags": PackedStringArray(),
"cosmetic_snapshot": {},
}
static func validate_client_hello(data: Variant) -> String:
if typeof(data) != TYPE_DICTIONARY:
return "Handshake payload is not a dictionary."
var payload: Dictionary = data
for key: String in [
"protocol_version",
"game_build",
"local_profile_id",
"display_name",
"client_nonce",
"capability_flags",
"cosmetic_snapshot",
]:
if not payload.has(key):
return "Handshake is missing %s." % key
if typeof(payload["protocol_version"]) != TYPE_INT:
return "Protocol version is invalid."
if typeof(payload["game_build"]) != TYPE_STRING:
return "Game build is invalid."
if typeof(payload["local_profile_id"]) != TYPE_STRING:
return "Profile ID is invalid."
if typeof(payload["display_name"]) != TYPE_STRING:
return "Display name is invalid."
if typeof(payload["client_nonce"]) != TYPE_STRING:
return "Client nonce is invalid."
if typeof(payload["capability_flags"]) not in [
TYPE_PACKED_STRING_ARRAY,
TYPE_ARRAY,
]:
return "Capabilities are invalid."
if typeof(payload["cosmetic_snapshot"]) != TYPE_DICTIONARY:
return "Cosmetic snapshot is invalid."
var profile_id: String = payload["local_profile_id"]
var display_name: String = payload["display_name"]
var nonce: String = payload["client_nonce"]
if (
profile_id.is_empty()
or profile_id.length() > MAX_PROFILE_ID_LENGTH
or display_name.is_empty()
or display_name.length() > MAX_DISPLAY_NAME_LENGTH
or nonce.is_empty()
or nonce.length() > MAX_NONCE_LENGTH
):
return "Handshake strings are outside allowed limits."
return ""
static func make_server_hello(
accepted: bool,
rejection_code: RejectionCode,
session_id: String,
assigned_peer_id: int,
player_count: int,
max_players: int,
) -> Dictionary:
return {
"accepted": accepted,
"rejection_code": int(rejection_code),
"protocol_version": PROTOCOL_VERSION,
"session_id": session_id,
"assigned_peer_id": assigned_peer_id,
"server_display_name": "NETFISHING",
"player_count": player_count,
"max_players": max_players,
"capability_flags": PackedStringArray([
"movement_v1",
"fishing_v1",
]),
}
static func rejection_text(code: int) -> String:
match code:
RejectionCode.PROTOCOL_MISMATCH:
return "The server uses a different network protocol."
RejectionCode.SERVER_FULL:
return "The server is full."
RejectionCode.DUPLICATE_PROFILE:
return "This local profile is already connected."
RejectionCode.AUTHENTICATION_TIMEOUT:
return "The server did not finish authentication."
RejectionCode.SERVER_SHUTTING_DOWN:
return "The server is shutting down."
RejectionCode.UNSUPPORTED_CLIENT:
return "This game build is not supported by the server."
_:
return "The server rejected the connection."