class_name NetworkMailProtocol extends RefCounted const CAPABILITY: StringName = &"mail_v1" const RELIABLE_CHANNEL: int = NetworkProtocol.MAIL_RELIABLE_CHANNEL const MAX_ID_LENGTH: int = 96 const MAX_BODY_CHARACTERS: int = 2000 const MAX_BODY_BYTES: int = 8000 const MAX_RECIPIENT_LETTERS: int = 100 const MAX_SESSION_LETTERS: int = 200 const GREETINGS: PackedStringArray = ["dear", "to", "hey", "greetings"] const SALUTATIONS: PackedStringArray = [ "love", "from", "cheers", "salutations", "good_luck_have_fun", ] enum State { SENT_UNREAD, READ, ACCEPTANCE_PENDING, ACCEPTED, DECLINED, ATTACHMENT_RECALLED, CANCELLED, SESSION_ENDED, } static func sanitize_body(value: Variant) -> String: if typeof(value) != TYPE_STRING: return "" var body := str(value).strip_edges(false, true) if ( body.strip_edges().is_empty() or body.length() > MAX_BODY_CHARACTERS or body.to_utf8_buffer().size() > MAX_BODY_BYTES ): return "" for index: int in body.length(): var codepoint := body.unicode_at(index) if codepoint < 32 and codepoint not in [9, 10, 13]: return "" if codepoint == 127: return "" return body.replace("\r\n", "\n").replace("\r", "\n").replace("\t", " ") 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 ) static func validate_send_request(data: Variant) -> bool: if typeof(data) != TYPE_DICTIONARY: return false var value: Dictionary = data return ( valid_id(value.get("request_id")) and valid_id(value.get("session_id")) and typeof(value.get("recipient_peer_id")) == TYPE_INT and int(value["recipient_peer_id"]) > 0 and typeof(value.get("greeting_id")) == TYPE_STRING and str(value["greeting_id"]) in GREETINGS and not sanitize_body(value.get("body")).is_empty() and typeof(value.get("salutation_id")) == TYPE_STRING and str(value["salutation_id"]) in SALUTATIONS and typeof(value.get("reservation_id")) == TYPE_STRING and str(value["reservation_id"]).length() <= MAX_ID_LENGTH and typeof(value.get("attachment")) == TYPE_DICTIONARY and Dictionary(value["attachment"]).size() <= 3 and NetworkIdentityCrypto.valid_fingerprint( value.get("sender_fingerprint") ) and NetworkIdentityCrypto.valid_fingerprint( value.get("recipient_fingerprint") ) and typeof(value.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY ) static func validate_mail(data: Variant) -> bool: if typeof(data) != TYPE_DICTIONARY: return false var value: Dictionary = data return ( valid_id(value.get("mail_id")) and valid_id(value.get("session_id")) and typeof(value.get("sequence")) == TYPE_INT and typeof(value.get("sender_peer_id")) == TYPE_INT and typeof(value.get("recipient_peer_id")) == TYPE_INT and typeof(value.get("sender_display_name")) == TYPE_STRING and str(value["sender_display_name"]).length() <= 24 and str(value.get("greeting_id", "")) in GREETINGS and not sanitize_body(value.get("body")).is_empty() and str(value.get("salutation_id", "")) in SALUTATIONS and typeof(value.get("attachment")) == TYPE_DICTIONARY and Dictionary(value["attachment"]).size() <= 3 and typeof(value.get("state")) == TYPE_INT and int(value["state"]) >= 0 and int(value["state"]) < State.size() and NetworkIdentityCrypto.valid_fingerprint( value.get("sender_fingerprint") ) and NetworkIdentityCrypto.valid_fingerprint( value.get("recipient_fingerprint") ) and typeof(value.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY and typeof(value.get("sender_public_key")) == TYPE_STRING and str(value["sender_public_key"]).to_utf8_buffer().size() <= NetworkIdentityCrypto.MAX_PUBLIC_KEY_BYTES ) static func mail_signature_fields(data: Dictionary) -> Array: var result: Array = [ str(data.get("session_id", "")), str(data.get("request_id", "")), str(data.get("sender_fingerprint", "")), str(data.get("recipient_fingerprint", "")), str(data.get("greeting_id", "")), sanitize_body(data.get("body")), str(data.get("salutation_id", "")), str(data.get("reservation_id", "")), ] result.append_array(attachment_signature_fields(data.get("attachment", {}))) return result static func attachment_signature_fields(value: Variant) -> Array: if typeof(value) != TYPE_DICTIONARY: return [0] var attachment: Dictionary = value var type := int(attachment.get("type", 0)) var result: Array = [type] match type: 1: result.append(int(attachment.get("amount", 0))) 2: var fish: Dictionary = attachment.get("catch", {}) result.append(str(attachment.get("catch_id", ""))) result.append(str(fish.get("fish_id", ""))) result.append(str(fish.get("weight_lb", ""))) result.append(str(fish.get("display_scale", ""))) result.append(int(fish.get("sale_value", 0))) result.append(bool(fish.get("is_favorited", false))) 3: result.append(str(attachment.get("item_id", ""))) result.append(int(attachment.get("quantity", 0))) return result static func acceptance_signature_fields( data: Dictionary, sender_fingerprint: String, recipient_fingerprint: String ) -> Array: return [ str(data.get("session_id", "")), str(data.get("request_id", "")), str(data.get("mail_id", "")), sender_fingerprint, recipient_fingerprint, ]