From a8950f98dd9809f5da370185d086d7027e14b76a Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 24 Sep 2022 22:44:44 +0200 Subject: [PATCH] [WebSocket] Refactor websocket module. This commit is a huge refactor of the websocket module. The module is really old, and some design choices had to be re-evaluated. The WebSocketClient and WebSocketServer classes are now gone, and WebSocketPeer can act as either client or server. The WebSocketMultiplayerPeer class is no longer abstract, and implements the Multiplayer API on top of the lower level WebSocketPeer. WebSocketPeer is now a "raw" peer, like StreamPeerTCP and StreamPeerTLS, so it emits no signal, and just needs polling to update its internal state. To use it as a client, simply call WebSocketPeer.coonect_to_url, then frequently poll the peer until STATE_OPEN is reached and then you can write or read from it, or STATE_CLOSED and then you can check the disconnect code and reason). To implement a server instead, a TCPServer must be created, and the accepted connections needs to be provided to WebSocketPeer.accept_stream (which will perform the HTTP handshake). A full example of a WebSocketServer using TLS will be provided in the demo repository. --- .../websocket/doc_classes/WebSocketClient.xml | 94 -- .../doc_classes/WebSocketMultiplayerPeer.xml | 86 +- .../websocket/doc_classes/WebSocketPeer.xml | 143 ++- .../websocket/doc_classes/WebSocketServer.xml | 127 --- .../editor_debugger_server_websocket.cpp | 54 +- .../editor/editor_debugger_server_websocket.h | 9 +- modules/websocket/emws_client.cpp | 159 ---- modules/websocket/emws_client.h | 71 -- modules/websocket/emws_peer.cpp | 174 +++- modules/websocket/emws_peer.h | 48 +- modules/websocket/packet_buffer.h | 58 +- modules/websocket/register_types.cpp | 23 +- .../remote_debugger_peer_websocket.cpp | 42 +- .../remote_debugger_peer_websocket.h | 7 +- modules/websocket/websocket_client.cpp | 141 --- modules/websocket/websocket_client.h | 75 -- modules/websocket/websocket_macros.h | 66 -- .../websocket/websocket_multiplayer_peer.cpp | 427 +++++++-- .../websocket/websocket_multiplayer_peer.h | 73 +- modules/websocket/websocket_peer.cpp | 107 ++- modules/websocket/websocket_peer.h | 69 +- modules/websocket/websocket_server.cpp | 167 ---- modules/websocket/websocket_server.h | 90 -- modules/websocket/wsl_client.cpp | 407 --------- modules/websocket/wsl_client.h | 91 -- modules/websocket/wsl_peer.cpp | 853 ++++++++++++++---- modules/websocket/wsl_peer.h | 134 ++- modules/websocket/wsl_server.cpp | 329 ------- modules/websocket/wsl_server.h | 98 -- 29 files changed, 1827 insertions(+), 2395 deletions(-) delete mode 100644 modules/websocket/doc_classes/WebSocketClient.xml delete mode 100644 modules/websocket/doc_classes/WebSocketServer.xml delete mode 100644 modules/websocket/emws_client.cpp delete mode 100644 modules/websocket/emws_client.h delete mode 100644 modules/websocket/websocket_client.cpp delete mode 100644 modules/websocket/websocket_client.h delete mode 100644 modules/websocket/websocket_macros.h delete mode 100644 modules/websocket/websocket_server.cpp delete mode 100644 modules/websocket/websocket_server.h delete mode 100644 modules/websocket/wsl_client.cpp delete mode 100644 modules/websocket/wsl_client.h delete mode 100644 modules/websocket/wsl_server.cpp delete mode 100644 modules/websocket/wsl_server.h diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml deleted file mode 100644 index 1978d2e7c68..00000000000 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - A WebSocket client implementation. - - - This class implements a WebSocket client compatible with any RFC 6455-compliant WebSocket server. - This client can be optionally used as a multiplayer peer for the [MultiplayerAPI]. - After starting the client ([method connect_to_url]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). - You will receive appropriate signals when connecting, disconnecting, or when new data is available. - [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android. - - - - - - - - - - - - Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a multiplayer peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. - If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). - You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request. - [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate. - [b]Note:[/b] Specifying [code]custom_headers[/code] is not supported in Web exports due to browsers' restrictions. - - - - - - - - Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information. - - - - - - Returns the IP address of the currently connected host. - - - - - - Returns the IP port of the currently connected host. - - - - - - If specified, this [X509Certificate] will be the only one accepted when connecting to an TLS host. Any other certificate provided by the server will be regarded as invalid. - [b]Note:[/b] Specifying a custom [code]trusted_tls_certificate[/code] is not supported in Web exports due to browsers' restrictions. - - - If [code]true[/code], TLS certificate verification is enabled. - [b]Note:[/b] You must specify the certificates to be used in the Project Settings for it to work when exported. - - - - - - - Emitted when the connection to the server is closed. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly. - - - - - Emitted when the connection to the server fails. - - - - - - Emitted when a connection with the server is established, [code]protocol[/code] will contain the sub-protocol agreed with the server. - - - - - Emitted when a WebSocket message is received. - [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer. - - - - - - - Emitted when the server requests a clean close. You should keep polling until you get a [signal connection_closed] signal to achieve the clean close. See [method WebSocketPeer.close] for more details. - - - - diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 4cc4d515e7d..c4481b046b8 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -10,6 +10,42 @@ + + + + Closes this [MultiplayerPeer], resetting the state to [constant MultiplayerPeer.CONNECTION_CONNECTED]. + [b]Note:[/b] To make sure remote peers receive a clean close prefer disconnecting clients via [method disconnect_peer]. + + + + + + + + + Starts a new multiplayer client connecting to the given [param url]. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param tls_certificate] will be used to verify the TLS host. + [b]Note[/b]: It is recommended to specify the scheme part of the URL, i.e. the [param url] should start with either [code]ws://[/code] or [code]wss://[/code]. + + + + + + + + + + Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS. + + + + + + + + + Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information. + + @@ -17,27 +53,39 @@ Returns the [WebSocketPeer] associated to the given [code]peer_id[/code]. - - - - - - + + + - Configures the buffer sizes for this WebSocket peer. Default values can be specified in the Project Settings under [code]network/limits[/code]. For server, values are meant per connected peer. - The first two parameters define the size and queued packets limits of the input buffer, the last two of the output buffer. - Buffer sizes are expressed in KiB, so [code]4 = 2^12 = 4096 bytes[/code]. All parameters will be rounded up to the nearest power of two. - [b]Note:[/b] Web exports only use the input buffer since the output one is managed by browsers. + Returns the IP address of the given peer. + + + + + + + Returns the remote port of the given peer. - - - - - Emitted when a packet is received from a peer. - [b]Note:[/b] This signal is only emitted when the client or server is configured to use Godot multiplayer API. - - - + + + The extra headers to use during handshake. See [member WebSocketPeer.handshake_headers] for more details. + + + The maximum time each peer can stay in a connecting state before being dropped. + + + The inbound buffer size for connected peers. See [member WebSocketPeer.inbound_buffer_size] for more details. + + + The maximum number of queued packets for connected peers. See [member WebSocketPeer.max_queued_packets] for more details. + + + The outbound buffer size for connected peers. See [member WebSocketPeer.outbound_buffer_size] for more details. + + + The supported WebSocket sub-protocols. See [member WebSocketPeer.supported_protocols] for more details. + + diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 627b9c607cb..fe0aae412eb 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -1,25 +1,82 @@ - A class representing a specific WebSocket connection. + A WebSocket connection. - This class represents a specific WebSocket connection, allowing you to do lower level operations with it. - You can choose to write to the socket in binary or text mode, and you can recognize the mode used for writing by the other peer. + This class represents WebSocket connection, and can be used as a WebSocket client (RFC 6455-compliant) or as a remote peer of a WebSocket server. + You can send WebSocket binary frames using [method PacketPeer.put_packet], and WebSocket text frames using [method send] (prefer text frames when interacting with text-based API). You can check the frame type of the last packet via [method was_string_packet]. + To start a WebSocket client, first call [method connect_to_url], then regularly call [method poll] (e.g. during [Node] process). You can query the socket state via [method get_ready_state], get the number of pending packets using [method PacketPeer.get_available_packet_count], and retrieve them via [method PacketPeer.get_packet]. + [codeblocks] + [gdscript] + extends Node + + var socket = WebSocketPeer.new() + + func _ready(): + socket.connect_to_url("wss://example.com") + + func _process(delta): + socket.poll() + var state = socket.get_ready_state() + if state == WebSocketPeer.STATE_OPEN: + while socket.get_available_packet_count(): + print("Packet: ", socket.get_packet()) + elif state == WebSocketPeer.STATE_CLOSING: + # Keep polling to achieve proper close. + pass + elif state == WebSocketPeer.STATE_CLOSED: + var code = socket.get_close_code() + var reason = socket.get_close_reason() + print("WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1]) + set_process(false) # Stop processing. + [/gdscript] + [/codeblocks] + To use the peer as part of a WebSocket server refer to [method accept_stream] and the online tutorial. + + + + + Accepts a peer connection performing the HTTP handshake as a WebSocket server. The [param stream] must be a valid TCP stream retrieved via [method TCPServer.take_connection], or a TLS stream accepted via [method StreamPeerTLS.accept_stream]. + [b]Note:[/b] Not supported in Web exports due to browsers' restrictions. + + - Closes this WebSocket connection. [code]code[/code] is the status code for the closure (see RFC 6455 section 7.4 for a list of valid status codes). [code]reason[/code] is the human readable reason for closing the connection (can be any UTF-8 string that's smaller than 123 bytes). - [b]Note:[/b] To achieve a clean close, you will need to keep polling until either [signal WebSocketClient.connection_closed] or [signal WebSocketServer.client_disconnected] is received. + Closes this WebSocket connection. [param code] is the status code for the closure (see RFC 6455 section 7.4 for a list of valid status codes). [param reason] is the human readable reason for closing the connection (can be any UTF-8 string that's smaller than 123 bytes). If [param code] is negative, the connection will be closed immediately without notifying the remote peer. + [b]Note:[/b] To achieve a clean close, you will need to keep polling until [constant STATE_CLOSED] is reached. [b]Note:[/b] The Web export might not support all status codes. Please refer to browser-specific documentation for more details. + + + + + + + Connects to the given URL. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param trusted_tls_certificate] will be the only one accepted when connecting to a TLS host. + [b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate. + + + + + + Returns the received WebSocket close frame status code, or [code]-1[/code] when the connection was not cleanly closed. Only call this method when [method get_ready_state] returns [constant STATE_CLOSED]. + + + + + + Returns the received WebSocket close frame status reason string. Only call this method when [method get_ready_state] returns [constant STATE_CLOSED]. + + @@ -40,16 +97,43 @@ Returns the current amount of data in the outbound websocket buffer. [b]Note:[/b] Web exports use WebSocket.bufferedAmount, while other platforms use an internal buffer. - - + + - Gets the current selected write mode. See [enum WriteMode]. + Returns the ready state of the connection. See [enum State]. - - + + - Returns [code]true[/code] if this peer is currently connected. + Returns the URL requested by this peer. The URL is derived from the [code]url[/code] passed to [method connect_to_url] or from the HTTP headers when acting as server (i.e. when using [method accept_stream]). + + + + + + Returns the selected WebSocket sub-protocol for this connection or an empty string if the sub-protocol has not been selected yet. + + + + + + Updates the connection state and receive incoming packets. Call this function regularly to keep it in a clean state. + + + + + + + + Sends the given [param message] using the desired [param write_mode]. When sending a [String], prefer using [method send_text]. + + + + + + + Sends the given [param message] using WebSocket text mode. Perfer this method over [method PacketPeer.put_packet] when interacting with third-party text-based API (e.g. when using [JSON] formatted messages). @@ -60,13 +144,6 @@ [b]Note:[/b] Not available in the Web export. - - - - - Sets the socket to use the given [enum WriteMode]. - - @@ -74,6 +151,24 @@ + + + The extra HTTP headers to be sent during the WebSocket handshake. + [b]Note:[/b] Not supported in Web exports due to browsers' restrictions. + + + The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the inbound packets). + + + The maximum amount of packets that will be allowed in the queues (both inbound and outbound). + + + The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the outbound packets). + + + The WebSocket sub-protocols allowed during the WebSocket handshake. + + Specifies that WebSockets messages should be transferred as text payload (only valid UTF-8 is allowed). @@ -81,5 +176,17 @@ Specifies that WebSockets messages should be transferred as binary payload (any byte combination is allowed). + + Socket has been created. The connection is not yet open. + + + The connection is open and ready to communicate. + + + The connection is in the process of closing. This means a close request has been sent to the remote peer but confirmation has not been received. + + + The connection is closed or couldn't be opened. + diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml deleted file mode 100644 index 07a55b73f11..00000000000 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - A WebSocket server implementation. - - - This class implements a WebSocket server that can also support the high-level multiplayer API. - After starting the server ([method listen]), you will need to [method MultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). When clients connect, disconnect, or send data, you will receive the appropriate signal. - [b]Note:[/b] Not available in Web exports. - [b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android. - - - - - - - - - - - Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information. - - - - - - - Returns the IP address of the given peer. - - - - - - - Returns the remote port of the given peer. - - - - - - - Returns [code]true[/code] if a peer with the given ID is connected. - - - - - - Returns [code]true[/code] if the server is actively listening on a port. - - - - - - - - - Starts listening on the given port. - You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested. - If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a multiplayer peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. - If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]). - - - - - - - Sets additional headers to be sent to clients during the HTTP handshake. - - - - - - Stops the server and clear its state. - - - - - - When not set to [code]*[/code] will restrict incoming connections to the specified IP address. Setting [code]bind_ip[/code] to [code]127.0.0.1[/code] will cause the server to listen only to the local host. - - - When using TLS (see [member private_key] and [member tls_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the TLS handshake. - - - The time in seconds before a pending client (i.e. a client that has not yet finished the HTTP handshake) is considered stale and forcefully disconnected. - - - When set to a valid [CryptoKey] (along with [member tls_certificate]) will cause the server to require TLS instead of regular TCP (i.e. the [code]wss://[/code] protocol). - - - When set to a valid [X509Certificate] (along with [member private_key]) will cause the server to require TLS instead of regular TCP (i.e. the [code]wss://[/code] protocol). - - - - - - - - - Emitted when a client requests a clean close. You should keep polling until you get a [signal client_disconnected] signal with the same [code]id[/code] to achieve the clean close. See [method WebSocketPeer.close] for more details. - - - - - - - - Emitted when a new client connects. "protocol" will be the sub-protocol agreed with the client, and "resource_name" will be the resource name of the URI the peer used. - "resource_name" is a path (at the very least a single forward slash) and potentially a query string. - - - - - - - Emitted when a client disconnects. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly. - - - - - - Emitted when a new message is received. - [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer. - - - - diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp index 0443147d982..1c4ebd0f55c 100644 --- a/modules/websocket/editor/editor_debugger_server_websocket.cpp +++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp @@ -38,18 +38,25 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" -void EditorDebuggerServerWebSocket::_peer_connected(int p_id, String _protocol) { - pending_peers.push_back(p_id); -} - -void EditorDebuggerServerWebSocket::_peer_disconnected(int p_id, bool p_was_clean) { - if (pending_peers.find(p_id)) { - pending_peers.erase(p_id); - } -} - void EditorDebuggerServerWebSocket::poll() { - server->poll(); + if (pending_peer.is_null() && tcp_server->is_connection_available()) { + Ref peer; + Error err = peer->accept_stream(tcp_server->take_connection()); + if (err == OK) { + pending_timer = OS::get_singleton()->get_ticks_msec(); + pending_peer = peer; + } + } + if (pending_peer.is_valid() && pending_peer->get_ready_state() != WebSocketPeer::STATE_OPEN) { + pending_peer->poll(); + WebSocketPeer::State ready_state = pending_peer->get_ready_state(); + if (ready_state != WebSocketPeer::STATE_CONNECTING && ready_state != WebSocketPeer::STATE_OPEN) { + pending_peer.unref(); // Failed. + } + if (ready_state == WebSocketPeer::STATE_CONNECTING && OS::get_singleton()->get_ticks_msec() - pending_timer > 3000) { + pending_peer.unref(); // Timeout. + } + } } String EditorDebuggerServerWebSocket::get_uri() const { @@ -69,15 +76,10 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) { ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER); } - // Set up the server - server->set_bind_ip(bind_host); - Vector compatible_protocols; - compatible_protocols.push_back("binary"); // compatibility with EMSCRIPTEN TCP-to-WebSocket layer. - // Try listening on ports const int max_attempts = 5; for (int attempt = 1;; ++attempt) { - const Error err = server->listen(bind_port, compatible_protocols); + const Error err = tcp_server->listen(bind_port, bind_host); if (err == OK) { break; } @@ -96,31 +98,27 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) { } void EditorDebuggerServerWebSocket::stop() { - server->stop(); - pending_peers.clear(); + pending_peer.unref(); + tcp_server->stop(); } bool EditorDebuggerServerWebSocket::is_active() const { - return server->is_listening(); + return tcp_server->is_listening(); } bool EditorDebuggerServerWebSocket::is_connection_available() const { - return pending_peers.size() > 0; + return pending_peer.is_valid() && pending_peer->get_ready_state() == WebSocketPeer::STATE_OPEN; } Ref EditorDebuggerServerWebSocket::take_connection() { ERR_FAIL_COND_V(!is_connection_available(), Ref()); - RemoteDebuggerPeer *peer = memnew(RemoteDebuggerPeerWebSocket(server->get_peer(pending_peers[0]))); - pending_peers.pop_front(); + RemoteDebuggerPeer *peer = memnew(RemoteDebuggerPeerWebSocket(pending_peer)); + pending_peer.unref(); return peer; } EditorDebuggerServerWebSocket::EditorDebuggerServerWebSocket() { - server = Ref(WebSocketServer::create()); - int max_pkts = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages"); - server->set_buffers(8192, max_pkts, 8192, max_pkts); - server->connect("client_connected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_connected)); - server->connect("client_disconnected", callable_mp(this, &EditorDebuggerServerWebSocket::_peer_disconnected)); + tcp_server.instantiate(); } EditorDebuggerServerWebSocket::~EditorDebuggerServerWebSocket() { diff --git a/modules/websocket/editor/editor_debugger_server_websocket.h b/modules/websocket/editor/editor_debugger_server_websocket.h index 7c0705302dc..31e54cb5dfa 100644 --- a/modules/websocket/editor/editor_debugger_server_websocket.h +++ b/modules/websocket/editor/editor_debugger_server_websocket.h @@ -33,15 +33,18 @@ #ifdef TOOLS_ENABLED -#include "../websocket_server.h" +#include "../websocket_peer.h" + +#include "core/io/tcp_server.h" #include "editor/debugger/editor_debugger_server.h" class EditorDebuggerServerWebSocket : public EditorDebuggerServer { GDCLASS(EditorDebuggerServerWebSocket, EditorDebuggerServer); private: - Ref server; - List pending_peers; + Ref tcp_server; + Ref pending_peer; + uint64_t pending_timer = 0; String endpoint; public: diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp deleted file mode 100644 index 933a1f43e9f..00000000000 --- a/modules/websocket/emws_client.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/*************************************************************************/ -/* emws_client.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifdef WEB_ENABLED - -#include "emws_client.h" - -#include "core/config/project_settings.h" -#include "core/io/ip.h" -#include "emscripten.h" - -void EMWSClient::_esws_on_connect(void *obj, char *proto) { - EMWSClient *client = static_cast(obj); - client->_is_connecting = false; - client->_on_connect(String(proto)); -} - -void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string) { - EMWSClient *client = static_cast(obj); - - Error err = static_cast(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); - if (err == OK) { - client->_on_peer_packet(); - } -} - -void EMWSClient::_esws_on_error(void *obj) { - EMWSClient *client = static_cast(obj); - client->_is_connecting = false; - client->_on_error(); -} - -void EMWSClient::_esws_on_close(void *obj, int code, const char *reason, int was_clean) { - EMWSClient *client = static_cast(obj); - client->_on_close_request(code, String(reason)); - client->_is_connecting = false; - client->disconnect_from_host(); - client->_on_disconnect(was_clean != 0); -} - -Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector p_protocols, const Vector p_custom_headers) { - if (_js_id) { - godot_js_websocket_destroy(_js_id); - _js_id = 0; - } - - String proto_string; - for (int i = 0; i < p_protocols.size(); i++) { - if (i != 0) { - proto_string += ","; - } - proto_string += p_protocols[i]; - } - - String str = "ws://"; - - if (p_custom_headers.size()) { - WARN_PRINT_ONCE("Custom headers are not supported in Web platform."); - } - if (p_tls) { - str = "wss://"; - if (tls_cert.is_valid()) { - WARN_PRINT_ONCE("Custom SSL certificate is not supported in Web platform."); - } - } - str += p_host + ":" + itos(p_port) + p_path; - _is_connecting = true; - - _js_id = godot_js_websocket_create(this, str.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close); - if (!_js_id) { - return FAILED; - } - - static_cast>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size, _out_buf_size); - - return OK; -} - -void EMWSClient::poll() { -} - -Ref EMWSClient::get_peer(int p_peer_id) const { - return _peer; -} - -MultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { - if (_peer->is_connected_to_host()) { - if (_is_connecting) { - return CONNECTION_CONNECTING; - } - return CONNECTION_CONNECTED; - } - - return CONNECTION_DISCONNECTED; -} - -void EMWSClient::disconnect_from_host(int p_code, String p_reason) { - _peer->close(p_code, p_reason); -} - -IPAddress EMWSClient::get_connected_host() const { - ERR_FAIL_V_MSG(IPAddress(), "Not supported in Web export."); -} - -uint16_t EMWSClient::get_connected_port() const { - ERR_FAIL_V_MSG(0, "Not supported in Web export."); -} - -int EMWSClient::get_max_packet_size() const { - return (1 << _in_buf_size) - PROTO_SIZE; -} - -Error EMWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - return OK; -} - -EMWSClient::EMWSClient() { - _peer = Ref(memnew(EMWSPeer)); -} - -EMWSClient::~EMWSClient() { - disconnect_from_host(); - _peer = Ref(); - if (_js_id) { - godot_js_websocket_destroy(_js_id); - } -} - -#endif // WEB_ENABLED diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h deleted file mode 100644 index cdcec31e19e..00000000000 --- a/modules/websocket/emws_client.h +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* emws_client.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef EMWS_CLIENT_H -#define EMWS_CLIENT_H - -#ifdef WEB_ENABLED - -#include "core/error/error_list.h" -#include "emws_peer.h" -#include "websocket_client.h" - -class EMWSClient : public WebSocketClient { - GDCIIMPL(EMWSClient, WebSocketClient); - -private: - int _js_id = 0; - bool _is_connecting = false; - int _in_buf_size = DEF_BUF_SHIFT; - int _in_pkt_size = DEF_PKT_SHIFT; - int _out_buf_size = DEF_BUF_SHIFT; - - static void _esws_on_connect(void *obj, char *proto); - static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string); - static void _esws_on_error(void *obj); - static void _esws_on_close(void *obj, int code, const char *reason, int was_clean); - -public: - Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override; - Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector p_protocol = Vector(), const Vector p_custom_headers = Vector()) override; - Ref get_peer(int p_peer_id) const override; - void disconnect_from_host(int p_code = 1000, String p_reason = "") override; - IPAddress get_connected_host() const override; - uint16_t get_connected_port() const override; - virtual ConnectionStatus get_connection_status() const override; - int get_max_packet_size() const override; - virtual void poll() override; - EMWSClient(); - ~EMWSClient(); -}; - -#endif // WEB_ENABLED - -#endif // EMWS_CLIENT_H diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 859c92b4579..5f3cb768520 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -34,55 +34,116 @@ #include "core/io/ip.h" -void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size) { - peer_sock = p_sock; - _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _packet_buffer.resize((1 << p_in_buf_size)); - _out_buf_size = p_out_buf_size; +void EMWSPeer::_esws_on_connect(void *p_obj, char *p_proto) { + EMWSPeer *peer = static_cast(p_obj); + peer->ready_state = STATE_OPEN; + peer->selected_protocol.parse_utf8(p_proto); } -void EMWSPeer::set_write_mode(WriteMode p_mode) { - write_mode = p_mode; -} - -EMWSPeer::WriteMode EMWSPeer::get_write_mode() const { - return write_mode; -} - -Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string) { +void EMWSPeer::_esws_on_message(void *p_obj, const uint8_t *p_data, int p_data_size, int p_is_string) { + EMWSPeer *peer = static_cast(p_obj); uint8_t is_string = p_is_string ? 1 : 0; - return _in_buffer.write_packet(p_data, p_size, &is_string); + peer->in_buffer.write_packet(p_data, p_data_size, &is_string); } -Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_FAIL_COND_V(_out_buf_size && ((uint64_t)godot_js_websocket_buffered_amount(peer_sock) + p_buffer_size >= (1ULL << _out_buf_size)), ERR_OUT_OF_MEMORY); +void EMWSPeer::_esws_on_error(void *p_obj) { + EMWSPeer *peer = static_cast(p_obj); + peer->ready_state = STATE_CLOSED; +} - int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0; +void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int p_was_clean) { + EMWSPeer *peer = static_cast(p_obj); + peer->close_code = p_code; + peer->close_reason.parse_utf8(p_reason); + peer->ready_state = STATE_CLOSED; +} - if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin) != 0) { - return FAILED; +Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref p_tls_certificate) { + ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE); + _clear(); + + String host; + String path; + String scheme; + int port = 0; + Error err = p_url.parse_url(scheme, host, port, path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); + + if (scheme.is_empty()) { + scheme = "ws://"; + } + ERR_FAIL_COND_V_MSG(scheme != "ws://" && scheme != "wss://", ERR_INVALID_PARAMETER, vformat("Invalid protocol: \"%s\" (must be either \"ws://\" or \"wss://\").", scheme)); + + String proto_string; + for (int i = 0; i < supported_protocols.size(); i++) { + if (i != 0) { + proto_string += ","; + } + proto_string += supported_protocols[i]; } + if (handshake_headers.size()) { + WARN_PRINT_ONCE("Custom headers are not supported in Web platform."); + } + if (p_tls_certificate.is_valid()) { + WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform."); + } + + requested_url = scheme + host; + + if (port && ((scheme == "ws://" && port != 80) || (scheme == "wss://" && port != 443))) { + requested_url += ":" + String::num(port); + } + + peer_sock = godot_js_websocket_create(this, requested_url.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close); + if (peer_sock == -1) { + return FAILED; + } + in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets); + packet_buffer.resize(inbound_buffer_size); + ready_state = STATE_CONNECTING; return OK; } +Error EMWSPeer::accept_stream(Ref p_stream) { + WARN_PRINT_ONCE("Acting as WebSocket server is not supported in Web platforms."); + return ERR_UNAVAILABLE; +} + +Error EMWSPeer::_send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary) { + ERR_FAIL_COND_V(outbound_buffer_size > 0 && (get_current_outbound_buffered_amount() + p_buffer_size >= outbound_buffer_size), ERR_OUT_OF_MEMORY); + + if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, p_binary ? 1 : 0) != 0) { + return FAILED; + } + return OK; +} + +Error EMWSPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) { + return _send(p_buffer, p_buffer_size, p_mode == WRITE_MODE_TEXT); +} + +Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + return _send(p_buffer, p_buffer_size, true); +} + Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - if (_in_buffer.packets_left() == 0) { + if (in_buffer.packets_left() == 0) { return ERR_UNAVAILABLE; } int read = 0; - Error err = _in_buffer.read_packet(_packet_buffer.ptrw(), _packet_buffer.size(), &_is_string, read); + Error err = in_buffer.read_packet(packet_buffer.ptrw(), packet_buffer.size(), &was_string, read); ERR_FAIL_COND_V(err != OK, err); - *r_buffer = _packet_buffer.ptr(); + *r_buffer = packet_buffer.ptr(); r_buffer_size = read; return OK; } int EMWSPeer::get_available_packet_count() const { - return _in_buffer.packets_left(); + return in_buffer.packets_left(); } int EMWSPeer::get_current_outbound_buffered_amount() const { @@ -93,20 +154,66 @@ int EMWSPeer::get_current_outbound_buffered_amount() const { } bool EMWSPeer::was_string_packet() const { - return _is_string; + return was_string; } -bool EMWSPeer::is_connected_to_host() const { - return peer_sock != -1; +void EMWSPeer::_clear() { + if (peer_sock != -1) { + godot_js_websocket_destroy(peer_sock); + peer_sock = -1; + } + ready_state = STATE_CLOSED; + was_string = 0; + close_code = -1; + close_reason.clear(); + selected_protocol.clear(); + requested_url.clear(); + in_buffer.clear(); + packet_buffer.clear(); } void EMWSPeer::close(int p_code, String p_reason) { - if (peer_sock != -1) { - godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data()); + if (p_code < 0) { + if (peer_sock != -1) { + godot_js_websocket_destroy(peer_sock); + peer_sock = -1; + } + ready_state = STATE_CLOSED; } - _is_string = 0; - _in_buffer.clear(); - peer_sock = -1; + if (ready_state == STATE_CONNECTING || ready_state == STATE_OPEN) { + ready_state = STATE_CLOSING; + if (peer_sock != -1) { + godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data()); + } else { + ready_state = STATE_CLOSED; + } + } + in_buffer.clear(); + packet_buffer.clear(); +} + +void EMWSPeer::poll() { + // Automatically polled by the navigator. +} + +WebSocketPeer::State EMWSPeer::get_ready_state() const { + return ready_state; +} + +int EMWSPeer::get_close_code() const { + return close_code; +} + +String EMWSPeer::get_close_reason() const { + return close_reason; +} + +String EMWSPeer::get_selected_protocol() const { + return selected_protocol; +} + +String EMWSPeer::get_requested_url() const { + return requested_url; } IPAddress EMWSPeer::get_connected_host() const { @@ -122,11 +229,10 @@ void EMWSPeer::set_no_delay(bool p_enabled) { } EMWSPeer::EMWSPeer() { - close(); } EMWSPeer::~EMWSPeer() { - close(); + _clear(); } #endif // WEB_ENABLED diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index cdbc9212a58..322cc3b7008 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -54,33 +54,53 @@ extern void godot_js_websocket_destroy(int p_id); } class EMWSPeer : public WebSocketPeer { - GDCIIMPL(EMWSPeer, WebSocketPeer); - private: int peer_sock = -1; - WriteMode write_mode = WRITE_MODE_BINARY; - Vector _packet_buffer; - PacketBuffer _in_buffer; - uint8_t _is_string = 0; - int _out_buf_size = 0; + State ready_state = STATE_CLOSED; + Vector packet_buffer; + PacketBuffer in_buffer; + uint8_t was_string = 0; + int close_code = -1; + String close_reason; + String selected_protocol; + String requested_url; + + static WebSocketPeer *_create() { return memnew(EMWSPeer); } + static void _esws_on_connect(void *obj, char *proto); + static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string); + static void _esws_on_error(void *obj); + static void _esws_on_close(void *obj, int code, const char *reason, int was_clean); + + void _clear(); + Error _send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary); public: - Error read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string); - void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size); + static void initialize() { WebSocketPeer::_create = EMWSPeer::_create; } + + // PacketPeer virtual int get_available_packet_count() const override; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; - virtual int get_max_packet_size() const override { return _packet_buffer.size(); }; + virtual int get_max_packet_size() const override { return packet_buffer.size(); }; + + // WebSocketPeer + virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override; + virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref p_cert = Ref()) override; + virtual Error accept_stream(Ref p_stream) override; + virtual void close(int p_code = 1000, String p_reason = "") override; + virtual void poll() override; + + virtual State get_ready_state() const override; + virtual int get_close_code() const override; + virtual String get_close_reason() const override; virtual int get_current_outbound_buffered_amount() const override; - virtual void close(int p_code = 1000, String p_reason = "") override; - virtual bool is_connected_to_host() const override; virtual IPAddress get_connected_host() const override; virtual uint16_t get_connected_port() const override; + virtual String get_selected_protocol() const override; + virtual String get_requested_url() const override; - virtual WriteMode get_write_mode() const override; - virtual void set_write_mode(WriteMode p_mode) override; virtual bool was_string_packet() const override; virtual void set_no_delay(bool p_enabled) override; diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h index 7b4a164576f..cd81dc43cdc 100644 --- a/modules/websocket/packet_buffer.h +++ b/modules/websocket/packet_buffer.h @@ -41,32 +41,29 @@ private: T info; } _Packet; - RingBuffer<_Packet> _packets; + Vector<_Packet> _packets; + int _queued = 0; + int _write_pos = 0; + int _read_pos = 0; RingBuffer _payload; public: Error write_packet(const uint8_t *p_payload, uint32_t p_size, const T *p_info) { -#ifdef TOOLS_ENABLED - // Verbose buffer warnings - if (p_payload && _payload.space_left() < (int32_t)p_size) { - ERR_PRINT("Buffer payload full! Dropping data."); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); - } - if (p_info && _packets.space_left() < 1) { - ERR_PRINT("Too many packets in queue! Dropping data."); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); - } -#else - ERR_FAIL_COND_V(p_payload && (uint32_t)_payload.space_left() < p_size, ERR_OUT_OF_MEMORY); - ERR_FAIL_COND_V(p_info && _packets.space_left() < 1, ERR_OUT_OF_MEMORY); -#endif + ERR_FAIL_COND_V_MSG(p_payload && (uint32_t)_payload.space_left() < p_size, ERR_OUT_OF_MEMORY, "Buffer payload full! Dropping data."); + ERR_FAIL_COND_V_MSG(p_info && _queued >= _packets.size(), ERR_OUT_OF_MEMORY, "Too many packets in queue! Dropping data."); // If p_info is nullptr, only the payload is written if (p_info) { + ERR_FAIL_COND_V(_write_pos > _packets.size(), ERR_OUT_OF_MEMORY); _Packet p; p.size = p_size; - memcpy(&p.info, p_info, sizeof(T)); - _packets.write(p); + p.info = *p_info; + _packets.write[_write_pos] = p; + _queued += 1; + _write_pos++; + if (_write_pos >= _packets.size()) { + _write_pos = 0; + } } // If p_payload is nullptr, only the packet information is written. @@ -78,9 +75,14 @@ public: } Error read_packet(uint8_t *r_payload, int p_bytes, T *r_info, int &r_read) { - ERR_FAIL_COND_V(_packets.data_left() < 1, ERR_UNAVAILABLE); - _Packet p; - _packets.read(&p, 1); + ERR_FAIL_COND_V(_queued < 1, ERR_UNAVAILABLE); + _Packet p = _packets[_read_pos]; + _read_pos += 1; + if (_read_pos >= _packets.size()) { + _read_pos = 0; + } + _queued -= 1; + ERR_FAIL_COND_V(_payload.data_left() < (int)p.size, ERR_BUG); ERR_FAIL_COND_V(p_bytes < (int)p.size, ERR_OUT_OF_MEMORY); @@ -90,22 +92,24 @@ public: return OK; } - void discard_payload(int p_size) { - _packets.decrease_write(p_size); - } - - void resize(int p_pkt_shift, int p_buf_shift) { - _packets.resize(p_pkt_shift); + void resize(int p_buf_shift, int p_max_packets) { _payload.resize(p_buf_shift); + _packets.resize(p_max_packets); + _read_pos = 0; + _write_pos = 0; + _queued = 0; } int packets_left() const { - return _packets.data_left(); + return _queued; } void clear() { _payload.resize(0); _packets.resize(0); + _read_pos = 0; + _write_pos = 0; + _queued = 0; } PacketBuffer() { diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index faa7021b2f6..691b031bbd9 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -33,16 +33,13 @@ #include "core/config/project_settings.h" #include "core/error/error_macros.h" -#include "websocket_client.h" -#include "websocket_server.h" +#include "websocket_multiplayer_peer.h" +#include "websocket_peer.h" #ifdef WEB_ENABLED -#include "emscripten.h" -#include "emws_client.h" #include "emws_peer.h" #else -#include "wsl_client.h" -#include "wsl_server.h" +#include "wsl_peer.h" #endif #ifdef TOOLS_ENABLED @@ -60,17 +57,12 @@ static void _editor_init_callback() { void initialize_websocket_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { #ifdef WEB_ENABLED - EMWSPeer::make_default(); - EMWSClient::make_default(); + EMWSPeer::initialize(); #else - WSLPeer::make_default(); - WSLClient::make_default(); - WSLServer::make_default(); + WSLPeer::initialize(); #endif - GDREGISTER_ABSTRACT_CLASS(WebSocketMultiplayerPeer); - ClassDB::register_custom_instance_class(); - ClassDB::register_custom_instance_class(); + GDREGISTER_CLASS(WebSocketMultiplayerPeer); ClassDB::register_custom_instance_class(); } @@ -85,4 +77,7 @@ void uninitialize_websocket_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } +#ifndef WEB_ENABLED + WSLPeer::deinitialize(); +#endif } diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp index f703873cbfa..fc4f51b59b7 100644 --- a/modules/websocket/remote_debugger_peer_websocket.cpp +++ b/modules/websocket/remote_debugger_peer_websocket.cpp @@ -36,27 +36,35 @@ Error RemoteDebuggerPeerWebSocket::connect_to_host(const String &p_uri) { Vector protocols; protocols.push_back("binary"); // Compatibility for emscripten TCP-to-WebSocket. - ws_client->connect_to_url(p_uri, protocols); - ws_client->poll(); + ws_peer = Ref(WebSocketPeer::create()); + ws_peer->set_supported_protocols(protocols); - if (ws_client->get_connection_status() == WebSocketClient::CONNECTION_DISCONNECTED) { - ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(ws_client->get_connection_status()) + "."); + ws_peer->set_max_queued_packets(max_queued_messages); + ws_peer->set_inbound_buffer_size((1 << 23) - 1); + ws_peer->set_outbound_buffer_size((1 << 23) - 1); + + Error err = ws_peer->connect_to_url(p_uri); + ERR_FAIL_COND_V(err != OK, err); + + ws_peer->poll(); + WebSocketPeer::State ready_state = ws_peer->get_ready_state(); + if (ready_state != WebSocketPeer::STATE_CONNECTING && ready_state != WebSocketPeer::STATE_OPEN) { + ERR_PRINT(vformat("Remote Debugger: Unable to connect. State: %s.", ws_peer->get_ready_state())); return FAILED; } - ws_peer = ws_client->get_peer(1); - return OK; } bool RemoteDebuggerPeerWebSocket::is_peer_connected() { - return ws_peer.is_valid() && ws_peer->is_connected_to_host(); + return ws_peer.is_valid() && (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN || ws_peer->get_ready_state() == WebSocketPeer::STATE_CONNECTING); } void RemoteDebuggerPeerWebSocket::poll() { - ws_client->poll(); + ERR_FAIL_COND(ws_peer.is_null()); + ws_peer->poll(); - while (ws_peer->is_connected_to_host() && ws_peer->get_available_packet_count() > 0 && in_queue.size() < max_queued_messages) { + while (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN && ws_peer->get_available_packet_count() > 0 && in_queue.size() < max_queued_messages) { Variant var; Error err = ws_peer->get_var(var); ERR_CONTINUE(err != OK); @@ -64,7 +72,7 @@ void RemoteDebuggerPeerWebSocket::poll() { in_queue.push_back(var); } - while (ws_peer->is_connected_to_host() && out_queue.size() > 0) { + while (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN && out_queue.size() > 0) { Array var = out_queue[0]; Error err = ws_peer->put_var(var); ERR_BREAK(err != OK); // Peer buffer full? @@ -73,7 +81,7 @@ void RemoteDebuggerPeerWebSocket::poll() { } int RemoteDebuggerPeerWebSocket::get_max_message_size() const { - return 8 << 20; // 8 Mib + return ws_peer->get_max_packet_size(); } bool RemoteDebuggerPeerWebSocket::has_message() { @@ -99,7 +107,6 @@ void RemoteDebuggerPeerWebSocket::close() { if (ws_peer.is_valid()) { ws_peer.unref(); } - ws_client->disconnect_from_host(); } bool RemoteDebuggerPeerWebSocket::can_block() const { @@ -111,14 +118,13 @@ bool RemoteDebuggerPeerWebSocket::can_block() const { } RemoteDebuggerPeerWebSocket::RemoteDebuggerPeerWebSocket(Ref p_peer) { -#ifdef WEB_ENABLED - ws_client = Ref(memnew(EMWSClient)); -#else - ws_client = Ref(memnew(WSLClient)); -#endif max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages"); - ws_client->set_buffers(8192, max_queued_messages, 8192, max_queued_messages); ws_peer = p_peer; + if (ws_peer.is_valid()) { + ws_peer->set_max_queued_packets(max_queued_messages); + ws_peer->set_inbound_buffer_size((1 << 23) - 1); + ws_peer->set_outbound_buffer_size((1 << 23) - 1); + } } RemoteDebuggerPeer *RemoteDebuggerPeerWebSocket::create(const String &p_uri) { diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h index 0292de68ad3..4d496ae8916 100644 --- a/modules/websocket/remote_debugger_peer_websocket.h +++ b/modules/websocket/remote_debugger_peer_websocket.h @@ -33,14 +33,9 @@ #include "core/debugger/remote_debugger_peer.h" -#ifdef WEB_ENABLED -#include "emws_client.h" -#else -#include "wsl_client.h" -#endif +#include "websocket_peer.h" class RemoteDebuggerPeerWebSocket : public RemoteDebuggerPeer { - Ref ws_client; Ref ws_peer; List in_queue; List out_queue; diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp deleted file mode 100644 index 0b2d5d19189..00000000000 --- a/modules/websocket/websocket_client.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************/ -/* websocket_client.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "websocket_client.h" - -GDCINULL(WebSocketClient); - -WebSocketClient::WebSocketClient() { -} - -WebSocketClient::~WebSocketClient() { -} - -Error WebSocketClient::connect_to_url(String p_url, const Vector p_protocols, bool gd_mp_api, const Vector p_custom_headers) { - _is_multiplayer = gd_mp_api; - - String host = p_url; - String path; - String scheme; - int port = 0; - Error err = p_url.parse_url(scheme, host, port, path); - ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); - - bool tls = false; - if (scheme == "wss://") { - tls = true; - } - if (port == 0) { - port = tls ? 443 : 80; - } - if (path.is_empty()) { - path = "/"; - } - return connect_to_host(host, path, port, tls, p_protocols, p_custom_headers); -} - -void WebSocketClient::set_verify_tls_enabled(bool p_verify_tls) { - verify_tls = p_verify_tls; -} - -bool WebSocketClient::is_verify_tls_enabled() const { - return verify_tls; -} - -Ref WebSocketClient::get_trusted_tls_certificate() const { - return tls_cert; -} - -void WebSocketClient::set_trusted_tls_certificate(Ref p_cert) { - ERR_FAIL_COND(get_connection_status() != CONNECTION_DISCONNECTED); - tls_cert = p_cert; -} - -bool WebSocketClient::is_server() const { - return false; -} - -void WebSocketClient::_on_peer_packet() { - if (_is_multiplayer) { - _process_multiplayer(get_peer(1), 1); - } else { - emit_signal(SNAME("data_received")); - } -} - -void WebSocketClient::_on_connect(String p_protocol) { - if (_is_multiplayer) { - // need to wait for ID confirmation... - } else { - emit_signal(SNAME("connection_established"), p_protocol); - } -} - -void WebSocketClient::_on_close_request(int p_code, String p_reason) { - emit_signal(SNAME("server_close_request"), p_code, p_reason); -} - -void WebSocketClient::_on_disconnect(bool p_was_clean) { - if (_is_multiplayer) { - emit_signal(SNAME("connection_failed")); - } else { - emit_signal(SNAME("connection_closed"), p_was_clean); - } -} - -void WebSocketClient::_on_error() { - if (_is_multiplayer) { - emit_signal(SNAME("connection_failed")); - } else { - emit_signal(SNAME("connection_error")); - } -} - -void WebSocketClient::_bind_methods() { - ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api", "custom_headers"), &WebSocketClient::connect_to_url, DEFVAL(Vector()), DEFVAL(false), DEFVAL(Vector())); - ClassDB::bind_method(D_METHOD("disconnect_from_host", "code", "reason"), &WebSocketClient::disconnect_from_host, DEFVAL(1000), DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketClient::get_connected_host); - ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketClient::get_connected_port); - ClassDB::bind_method(D_METHOD("set_verify_tls_enabled", "enabled"), &WebSocketClient::set_verify_tls_enabled); - ClassDB::bind_method(D_METHOD("is_verify_tls_enabled"), &WebSocketClient::is_verify_tls_enabled); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_tls", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_verify_tls_enabled", "is_verify_tls_enabled"); - - ClassDB::bind_method(D_METHOD("get_trusted_tls_certificate"), &WebSocketClient::get_trusted_tls_certificate); - ClassDB::bind_method(D_METHOD("set_trusted_tls_certificate", "cert"), &WebSocketClient::set_trusted_tls_certificate); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_tls_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_trusted_tls_certificate", "get_trusted_tls_certificate"); - - ADD_SIGNAL(MethodInfo("data_received")); - ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); - ADD_SIGNAL(MethodInfo("server_close_request", PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); - ADD_SIGNAL(MethodInfo("connection_closed", PropertyInfo(Variant::BOOL, "was_clean_close"))); - ADD_SIGNAL(MethodInfo("connection_error")); -} diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h deleted file mode 100644 index e747aee4e4b..00000000000 --- a/modules/websocket/websocket_client.h +++ /dev/null @@ -1,75 +0,0 @@ -/*************************************************************************/ -/* websocket_client.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WEBSOCKET_CLIENT_H -#define WEBSOCKET_CLIENT_H - -#include "core/crypto/crypto.h" -#include "core/error/error_list.h" -#include "websocket_multiplayer_peer.h" -#include "websocket_peer.h" - -class WebSocketClient : public WebSocketMultiplayerPeer { - GDCLASS(WebSocketClient, WebSocketMultiplayerPeer); - GDCICLASS(WebSocketClient); - -protected: - Ref _peer; - bool verify_tls = true; - Ref tls_cert; - - static void _bind_methods(); - -public: - Error connect_to_url(String p_url, const Vector p_protocols = Vector(), bool gd_mp_api = false, const Vector p_custom_headers = Vector()); - - void set_verify_tls_enabled(bool p_verify_tls); - bool is_verify_tls_enabled() const; - Ref get_trusted_tls_certificate() const; - void set_trusted_tls_certificate(Ref p_cert); - - virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector p_protocol = Vector(), const Vector p_custom_headers = Vector()) = 0; - virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0; - virtual IPAddress get_connected_host() const = 0; - virtual uint16_t get_connected_port() const = 0; - - virtual bool is_server() const override; - - void _on_peer_packet(); - void _on_connect(String p_protocol); - void _on_close_request(int p_code, String p_reason); - void _on_disconnect(bool p_was_clean); - void _on_error(); - - WebSocketClient(); - ~WebSocketClient(); -}; - -#endif // WEBSOCKET_CLIENT_H diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h deleted file mode 100644 index b03bd8f45c9..00000000000 --- a/modules/websocket/websocket_macros.h +++ /dev/null @@ -1,66 +0,0 @@ -/*************************************************************************/ -/* websocket_macros.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WEBSOCKET_MACROS_H -#define WEBSOCKET_MACROS_H - -// Defaults per peer buffers, 1024 packets with a shared 65536 bytes payload. -#define DEF_PKT_SHIFT 10 -#define DEF_BUF_SHIFT 16 - -#define GDCICLASS(CNAME) \ -public: \ - static CNAME *(*_create)(); \ - \ - static Ref create_ref() { \ - if (!_create) \ - return Ref(); \ - return Ref(_create()); \ - } \ - \ - static CNAME *create() { \ - if (!_create) \ - return nullptr; \ - return _create(); \ - } \ - \ -protected: - -#define GDCINULL(CNAME) \ - CNAME *(*CNAME::_create)() = nullptr; - -#define GDCIIMPL(IMPNAME, CNAME) \ -public: \ - static CNAME *_create() { return memnew(IMPNAME); } \ - static void make_default() { CNAME::_create = IMPNAME::_create; } \ - \ -protected: - -#endif // WEBSOCKET_MACROS_H diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 7a3bbf1c47b..00b96ec5872 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -33,70 +33,118 @@ #include "core/os/os.h" WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() { + peer_config = Ref(WebSocketPeer::create()); } WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() { _clear(); } +Ref WebSocketMultiplayerPeer::_create_peer() { + Ref peer = Ref(WebSocketPeer::create()); + peer->set_supported_protocols(get_supported_protocols()); + peer->set_handshake_headers(get_handshake_headers()); + peer->set_inbound_buffer_size(get_inbound_buffer_size()); + peer->set_outbound_buffer_size(get_outbound_buffer_size()); + peer->set_max_queued_packets(get_max_queued_packets()); + return peer; +} + void WebSocketMultiplayerPeer::_clear() { - _peer_map.clear(); - if (_current_packet.data != nullptr) { - memfree(_current_packet.data); + connection_status = CONNECTION_DISCONNECTED; + unique_id = 0; + peers_map.clear(); + use_tls = false; + tcp_server.unref(); + pending_peers.clear(); + tls_certificate.unref(); + tls_key.unref(); + if (current_packet.data != nullptr) { + memfree(current_packet.data); } - for (Packet &E : _incoming_packets) { + for (Packet &E : incoming_packets) { memfree(E.data); E.data = nullptr; } - _incoming_packets.clear(); + incoming_packets.clear(); } void WebSocketMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_buffers", "input_buffer_size_kb", "input_max_packets", "output_buffer_size_kb", "output_max_packets"), &WebSocketMultiplayerPeer::set_buffers); - ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); + ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref()), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("close"), &WebSocketMultiplayerPeer::close); - ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "peer_source"))); + ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); + ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address); + ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketMultiplayerPeer::get_peer_port); + ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketMultiplayerPeer::disconnect_peer, DEFVAL(1000), DEFVAL("")); + + ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketMultiplayerPeer::get_supported_protocols); + ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketMultiplayerPeer::set_supported_protocols); + + ClassDB::bind_method(D_METHOD("get_handshake_headers"), &WebSocketMultiplayerPeer::get_handshake_headers); + ClassDB::bind_method(D_METHOD("set_handshake_headers", "protocols"), &WebSocketMultiplayerPeer::set_handshake_headers); + + ClassDB::bind_method(D_METHOD("get_inbound_buffer_size"), &WebSocketMultiplayerPeer::get_inbound_buffer_size); + ClassDB::bind_method(D_METHOD("set_inbound_buffer_size", "buffer_size"), &WebSocketMultiplayerPeer::set_inbound_buffer_size); + + ClassDB::bind_method(D_METHOD("get_outbound_buffer_size"), &WebSocketMultiplayerPeer::get_outbound_buffer_size); + ClassDB::bind_method(D_METHOD("set_outbound_buffer_size", "buffer_size"), &WebSocketMultiplayerPeer::set_outbound_buffer_size); + + ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketMultiplayerPeer::get_handshake_timeout); + ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketMultiplayerPeer::set_handshake_timeout); + + ClassDB::bind_method(D_METHOD("set_max_queued_packets", "max_queued_packets"), &WebSocketMultiplayerPeer::set_max_queued_packets); + ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketMultiplayerPeer::get_max_queued_packets); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "inbound_buffer_size"), "set_inbound_buffer_size", "get_inbound_buffer_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outbound_buffer_size"), "set_outbound_buffer_size", "get_outbound_buffer_size"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets"); } // // PacketPeer // int WebSocketMultiplayerPeer::get_available_packet_count() const { - ERR_FAIL_COND_V_MSG(!_is_multiplayer, 0, "Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); - - return _incoming_packets.size(); + return incoming_packets.size(); } Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); + ERR_FAIL_COND_V(get_connection_status() != CONNECTION_CONNECTED, ERR_UNCONFIGURED); r_buffer_size = 0; - if (_current_packet.data != nullptr) { - memfree(_current_packet.data); - _current_packet.data = nullptr; + if (current_packet.data != nullptr) { + memfree(current_packet.data); + current_packet.data = nullptr; } - ERR_FAIL_COND_V(_incoming_packets.size() == 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(incoming_packets.size() == 0, ERR_UNAVAILABLE); - _current_packet = _incoming_packets.front()->get(); - _incoming_packets.pop_front(); + current_packet = incoming_packets.front()->get(); + incoming_packets.pop_front(); - *r_buffer = _current_packet.data; - r_buffer_size = _current_packet.size; + *r_buffer = current_packet.data; + r_buffer_size = current_packet.size; return OK; } Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); + ERR_FAIL_COND_V(get_connection_status() != CONNECTION_CONNECTED, ERR_UNCONFIGURED); - Vector buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); + Vector buffer = _make_pkt(SYS_NONE, get_unique_id(), target_peer, p_buffer, p_buffer_size); if (is_server()) { - return _server_relay(1, _target_peer, &(buffer.ptr()[0]), buffer.size()); + return _server_relay(1, target_peer, &(buffer.ptr()[0]), buffer.size()); } else { return get_peer(1)->put_packet(&(buffer.ptr()[0]), buffer.size()); } @@ -106,23 +154,225 @@ Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer // MultiplayerPeer // void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { - _target_peer = p_target_peer; + target_peer = p_target_peer; } int WebSocketMultiplayerPeer::get_packet_peer() const { - ERR_FAIL_COND_V_MSG(!_is_multiplayer, 1, "This function is not available when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); + ERR_FAIL_COND_V(incoming_packets.size() == 0, 1); - return _incoming_packets.front()->get().source; + return incoming_packets.front()->get().source; } int WebSocketMultiplayerPeer::get_unique_id() const { - return _peer_id; + return unique_id; +} + +int WebSocketMultiplayerPeer::get_max_packet_size() const { + return get_outbound_buffer_size() - PROTO_SIZE; +} + +Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref p_tls_key, Ref p_tls_certificate) { + ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + _clear(); + tcp_server.instantiate(); + Error err = tcp_server->listen(p_port, p_bind_ip); + if (err != OK) { + tcp_server.unref(); + return err; + } + unique_id = 1; + connection_status = CONNECTION_CONNECTED; + // TLS config + tls_key = p_tls_key; + tls_certificate = p_tls_certificate; + if (tls_key.is_valid() && tls_certificate.is_valid()) { + use_tls = true; + } + return OK; +} + +Error WebSocketMultiplayerPeer::create_client(const String &p_url, bool p_verify_tls, Ref p_tls_certificate) { + ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE); + _clear(); + Ref peer = _create_peer(); + Error err = peer->connect_to_url(p_url, p_verify_tls, p_tls_certificate); + if (err != OK) { + return err; + } + PendingPeer pending; + pending.time = OS::get_singleton()->get_ticks_msec(); + pending_peers[1] = pending; + peers_map[1] = peer; + connection_status = CONNECTION_CONNECTING; + return OK; +} + +bool WebSocketMultiplayerPeer::is_server() const { + return tcp_server.is_valid(); +} + +void WebSocketMultiplayerPeer::_poll_client() { + ERR_FAIL_COND(connection_status == CONNECTION_DISCONNECTED); // Bug. + ERR_FAIL_COND(!peers_map.has(1) || peers_map[1].is_null()); // Bug. + Ref peer = peers_map[1]; + peer->poll(); // Update state and fetch packets. + WebSocketPeer::State ready_state = peer->get_ready_state(); + if (ready_state == WebSocketPeer::STATE_OPEN) { + while (peer->get_available_packet_count()) { + _process_multiplayer(peer, 1); + } + } else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) { + if (connection_status == CONNECTION_CONNECTED) { + emit_signal(SNAME("server_disconnected")); + } else { + emit_signal(SNAME("connection_failed")); + } + _clear(); + return; + } + if (connection_status == CONNECTION_CONNECTING) { + // Still connecting + ERR_FAIL_COND(!pending_peers.has(1)); // Bug. + if (OS::get_singleton()->get_ticks_msec() - pending_peers[1].time > handshake_timeout) { + print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001)); + emit_signal(SNAME("connection_failed")); + _clear(); + return; + } + } +} + +void WebSocketMultiplayerPeer::_poll_server() { + ERR_FAIL_COND(connection_status != CONNECTION_CONNECTED); // Bug. + ERR_FAIL_COND(tcp_server.is_null() || !tcp_server->is_listening()); // Bug. + + // Accept new connections. + if (!is_refusing_new_connections() && tcp_server->is_connection_available()) { + PendingPeer peer; + peer.time = OS::get_singleton()->get_ticks_msec(); + peer.tcp = tcp_server->take_connection(); + peer.connection = peer.tcp; + pending_peers[generate_unique_id()] = peer; + } + + // Process pending peers. + HashSet to_remove; + for (KeyValue &E : pending_peers) { + PendingPeer &peer = E.value; + int id = E.key; + + if (OS::get_singleton()->get_ticks_msec() - peer.time > handshake_timeout) { + print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001)); + to_remove.insert(id); + continue; + } + + if (peer.ws.is_valid()) { + peer.ws->poll(); + WebSocketPeer::State state = peer.ws->get_ready_state(); + if (state == WebSocketPeer::STATE_OPEN) { + // Connected. + to_remove.insert(id); + if (is_refusing_new_connections()) { + // The user does not want new connections, dropping it. + continue; + } + peers_map[id] = peer.ws; + _send_ack(peer.ws, id); + emit_signal("peer_connected", id); + continue; + } else if (state == WebSocketPeer::STATE_CONNECTING) { + continue; // Still connecting. + } + to_remove.insert(id); // Error. + continue; + } + if (peer.tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + to_remove.insert(id); // Error. + continue; + } + if (!use_tls) { + peer.ws = _create_peer(); + peer.ws->accept_stream(peer.tcp); + continue; + } else { + if (peer.connection == peer.tcp) { + Ref tls = Ref(StreamPeerTLS::create()); + Error err = tls->accept_stream(peer.tcp, tls_key, tls_certificate); + if (err != OK) { + to_remove.insert(id); + continue; + } + } + Ref tls = static_cast>(peer.connection); + tls->poll(); + if (tls->get_status() == StreamPeerTLS::STATUS_CONNECTED) { + peer.ws = _create_peer(); + peer.ws->accept_stream(peer.connection); + continue; + } else if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { + // Still connecting. + continue; + } else { + // Error + to_remove.insert(id); + } + } + } + + // Remove disconnected pending peers. + for (const int &pid : to_remove) { + pending_peers.erase(pid); + } + to_remove.clear(); + + // Process connected peers. + for (KeyValue> &E : peers_map) { + Ref ws = E.value; + int id = E.key; + ws->poll(); + if (ws->get_ready_state() != WebSocketPeer::STATE_OPEN) { + to_remove.insert(id); // Disconnected. + continue; + } + // Fetch packets + int pkts = ws->get_available_packet_count(); + while (pkts && ws->get_ready_state() == WebSocketPeer::STATE_OPEN) { + _process_multiplayer(ws, id); + pkts--; + } + } + + // Remove disconnected peers. + for (const int &pid : to_remove) { + emit_signal(SNAME("peer_disconnected"), pid); + peers_map.erase(pid); + } +} + +void WebSocketMultiplayerPeer::poll() { + if (connection_status == CONNECTION_DISCONNECTED) { + return; + } + if (is_server()) { + _poll_server(); + } else { + _poll_client(); + } +} + +MultiplayerPeer::ConnectionStatus WebSocketMultiplayerPeer::get_connection_status() const { + return connection_status; +} + +Ref WebSocketMultiplayerPeer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!peers_map.has(p_id), Ref()); + return peers_map[p_id]; } void WebSocketMultiplayerPeer::_send_sys(Ref p_peer, uint8_t p_type, int32_t p_peer_id) { ERR_FAIL_COND(!p_peer.is_valid()); - ERR_FAIL_COND(!p_peer->is_connected_to_host()); + ERR_FAIL_COND(p_peer->get_ready_state() != WebSocketPeer::STATE_OPEN); Vector message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4); p_peer->put_packet(&(message.ptr()[0]), message.size()); @@ -141,31 +391,34 @@ Vector WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_fr return out; } -void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) { +void WebSocketMultiplayerPeer::_send_ack(Ref p_peer, int32_t p_peer_id) { + ERR_FAIL_COND(p_peer.is_null()); // First of all, confirm the ID! - _send_sys(get_peer(p_peer_id), SYS_ID, p_peer_id); + _send_sys(p_peer, SYS_ID, p_peer_id); // Then send the server peer (which will trigger connection_succeded in client) - _send_sys(get_peer(p_peer_id), SYS_ADD, 1); + _send_sys(p_peer, SYS_ADD, 1); + + for (const KeyValue> &E : peers_map) { + ERR_CONTINUE(E.value.is_null()); - for (const KeyValue> &E : _peer_map) { int32_t id = E.key; if (p_peer_id == id) { continue; // Skip the newly added peer (already confirmed) } // Send new peer to others - _send_sys(get_peer(id), SYS_ADD, p_peer_id); + _send_sys(E.value, SYS_ADD, p_peer_id); // Send others to new peer - _send_sys(get_peer(p_peer_id), SYS_ADD, id); + _send_sys(E.value, SYS_ADD, id); } } void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) { - for (const KeyValue> &E : _peer_map) { + for (const KeyValue> &E : peers_map) { int32_t id = E.key; if (p_peer_id != id) { - _send_sys(get_peer(id), SYS_DEL, p_peer_id); + _send_sys(E.value, SYS_DEL, p_peer_id); } } } @@ -177,8 +430,7 @@ void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, cons packet.source = p_source; packet.destination = p_dest; memcpy(packet.data, &p_data[PROTO_SIZE], p_data_size); - _incoming_packets.push_back(packet); - emit_signal(SNAME("peer_packet"), p_source); + incoming_packets.push_back(packet); } Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) { @@ -186,7 +438,7 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons return OK; // Will not send to self } else if (p_to == 0) { - for (KeyValue> &E : _peer_map) { + for (KeyValue> &E : peers_map) { if (E.key != p_from) { E.value->put_packet(p_buffer, p_buffer_size); } @@ -194,7 +446,7 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons return OK; // Sent to all but sender } else if (p_to < 0) { - for (KeyValue> &E : _peer_map) { + for (KeyValue> &E : peers_map) { if (E.key != p_from && E.key != -p_to) { E.value->put_packet(p_buffer, p_buffer_size); } @@ -237,26 +489,24 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref p_peer, u ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages ERR_FAIL_COND(from != p_peer_id); // Someone is cheating - if (to == 1) { // This is for the server - + if (to == 1) { + // This is for the server _store_pkt(from, to, in_buffer, data_size); } else if (to == 0) { // Broadcast, for us too _store_pkt(from, to, in_buffer, data_size); - } else if (to < 0) { + } else if (to < -1) { // All but one, for us if not excluded - if (_peer_id != -(int32_t)p_peer_id) { - _store_pkt(from, to, in_buffer, data_size); - } + _store_pkt(from, to, in_buffer, data_size); } // Relay if needed (i.e. "to" includes a peer that is not the server) _server_relay(from, to, in_buffer, size); } else { - if (type == SYS_NONE) { // Payload message - + if (type == SYS_NONE) { + // Payload message _store_pkt(from, to, in_buffer, data_size); return; } @@ -268,7 +518,12 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref p_peer, u switch (type) { case SYS_ADD: // Add peer - _peer_map[id] = Ref(); + if (id != 1) { + peers_map[id] = Ref(); + } else { + pending_peers.clear(); + connection_status = CONNECTION_CONNECTED; + } emit_signal(SNAME("peer_connected"), id); if (id == 1) { // We just connected to the server emit_signal(SNAME("connection_succeeded")); @@ -276,11 +531,11 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref p_peer, u break; case SYS_DEL: // Remove peer - _peer_map.erase(id); emit_signal(SNAME("peer_disconnected"), id); + peers_map.erase(id); break; case SYS_ID: // Hello, server assigned ID - _peer_id = id; + unique_id = id; break; default: ERR_FAIL_MSG("Invalid multiplayer message."); @@ -288,3 +543,71 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref p_peer, u } } } + +void WebSocketMultiplayerPeer::set_supported_protocols(const Vector &p_protocols) { + peer_config->set_supported_protocols(p_protocols); +} + +Vector WebSocketMultiplayerPeer::get_supported_protocols() const { + return peer_config->get_supported_protocols(); +} + +void WebSocketMultiplayerPeer::set_handshake_headers(const Vector &p_headers) { + peer_config->set_handshake_headers(p_headers); +} + +Vector WebSocketMultiplayerPeer::get_handshake_headers() const { + return peer_config->get_handshake_headers(); +} + +void WebSocketMultiplayerPeer::set_outbound_buffer_size(int p_buffer_size) { + peer_config->set_outbound_buffer_size(p_buffer_size); +} + +int WebSocketMultiplayerPeer::get_outbound_buffer_size() const { + return peer_config->get_outbound_buffer_size(); +} + +void WebSocketMultiplayerPeer::set_inbound_buffer_size(int p_buffer_size) { + peer_config->set_inbound_buffer_size(p_buffer_size); +} + +int WebSocketMultiplayerPeer::get_inbound_buffer_size() const { + return peer_config->get_inbound_buffer_size(); +} + +void WebSocketMultiplayerPeer::set_max_queued_packets(int p_max_queued_packets) { + peer_config->set_max_queued_packets(p_max_queued_packets); +} + +int WebSocketMultiplayerPeer::get_max_queued_packets() const { + return peer_config->get_max_queued_packets(); +} + +float WebSocketMultiplayerPeer::get_handshake_timeout() const { + return handshake_timeout / 1000.0; +} + +void WebSocketMultiplayerPeer::set_handshake_timeout(float p_timeout) { + ERR_FAIL_COND(p_timeout <= 0.0); + handshake_timeout = p_timeout * 1000; +} + +IPAddress WebSocketMultiplayerPeer::get_peer_address(int p_peer_id) const { + ERR_FAIL_COND_V(!peers_map.has(p_peer_id), IPAddress()); + return peers_map[p_peer_id]->get_connected_host(); +} + +int WebSocketMultiplayerPeer::get_peer_port(int p_peer_id) const { + ERR_FAIL_COND_V(!peers_map.has(p_peer_id), 0); + return peers_map[p_peer_id]->get_connected_port(); +} + +void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { + ERR_FAIL_COND(!peers_map.has(p_peer_id)); + peers_map[p_peer_id]->close(p_code, p_reason); +} + +void WebSocketMultiplayerPeer::close() { + _clear(); +} diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 3259e78b3b1..8e7b118faa0 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -32,6 +32,8 @@ #define WEBSOCKET_MULTIPLAYER_PEER_H #include "core/error/error_list.h" +#include "core/io/stream_peer_tls.h" +#include "core/io/tcp_server.h" #include "core/templates/list.h" #include "scene/main/multiplayer_peer.h" #include "websocket_peer.h" @@ -43,6 +45,7 @@ private: Vector _make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size); Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size); + Ref _create_peer(); protected: enum { @@ -61,19 +64,40 @@ protected: uint32_t size = 0; }; - List _incoming_packets; - HashMap> _peer_map; - Packet _current_packet; + struct PendingPeer { + uint64_t time = 0; + Ref tcp; + Ref connection; + Ref ws; + }; - bool _is_multiplayer = false; - int _target_peer = 0; - int _peer_id = 0; + uint64_t handshake_timeout = 3000; + Ref peer_config; + HashMap pending_peers; + Ref tcp_server; + bool use_tls = false; + Ref tls_certificate; + Ref tls_key; + + ConnectionStatus connection_status = CONNECTION_DISCONNECTED; + + List incoming_packets; + HashMap> peers_map; + Packet current_packet; + + int target_peer = 0; + int unique_id = 0; static void _bind_methods(); - void _send_add(int32_t p_peer_id); + void _send_ack(Ref p_peer, int32_t p_peer_id); void _send_sys(Ref p_peer, uint8_t p_type, int32_t p_peer_id); void _send_del(int32_t p_peer_id); + void _process_multiplayer(Ref p_peer, uint32_t p_peer_id); + + void _poll_client(); + void _poll_server(); + void _clear(); public: /* MultiplayerPeer */ @@ -81,17 +105,44 @@ public: int get_packet_peer() const override; int get_unique_id() const override; + virtual int get_max_packet_size() const override; + virtual bool is_server() const override; + virtual void poll() override; + virtual ConnectionStatus get_connection_status() const override; + /* PacketPeer */ virtual int get_available_packet_count() const override; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; /* WebSocketPeer */ - virtual Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) = 0; - virtual Ref get_peer(int p_peer_id) const = 0; + virtual Ref get_peer(int p_peer_id) const; - void _process_multiplayer(Ref p_peer, uint32_t p_peer_id); - void _clear(); + Error create_client(const String &p_url, bool p_verify_tls, Ref p_tls_certificate); + Error create_server(int p_port, IPAddress p_bind_ip, Ref p_tls_key, Ref p_tls_certificate); + + void set_supported_protocols(const Vector &p_protocols); + Vector get_supported_protocols() const; + + void set_handshake_headers(const Vector &p_headers); + Vector get_handshake_headers() const; + + void set_outbound_buffer_size(int p_buffer_size); + int get_outbound_buffer_size() const; + + void set_inbound_buffer_size(int p_buffer_size); + int get_inbound_buffer_size() const; + + float get_handshake_timeout() const; + void set_handshake_timeout(float p_timeout); + + IPAddress get_peer_address(int p_peer_id) const; + int get_peer_port(int p_peer_id) const; + void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); + void close(); + + void set_max_queued_packets(int p_max_queued_packets); + int get_max_queued_packets() const; WebSocketMultiplayerPeer(); ~WebSocketMultiplayerPeer(); diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index a0af9303b8b..b46b20bef24 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -30,7 +30,7 @@ #include "websocket_peer.h" -GDCINULL(WebSocketPeer); +WebSocketPeer *(*WebSocketPeer::_create)() = nullptr; WebSocketPeer::WebSocketPeer() { } @@ -39,16 +39,115 @@ WebSocketPeer::~WebSocketPeer() { } void WebSocketPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_write_mode"), &WebSocketPeer::get_write_mode); - ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode); - ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host); + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "verify_tls", "trusted_tls_certificate"), &WebSocketPeer::connect_to_url, DEFVAL(true), DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &WebSocketPeer::accept_stream); + ClassDB::bind_method(D_METHOD("send", "message", "write_mode"), &WebSocketPeer::_send_bind, DEFVAL(WRITE_MODE_BINARY)); + ClassDB::bind_method(D_METHOD("send_text", "message"), &WebSocketPeer::send_text); ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet); + ClassDB::bind_method(D_METHOD("poll"), &WebSocketPeer::poll); ClassDB::bind_method(D_METHOD("close", "code", "reason"), &WebSocketPeer::close, DEFVAL(1000), DEFVAL("")); ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketPeer::get_connected_host); ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketPeer::get_connected_port); + ClassDB::bind_method(D_METHOD("get_selected_protocol"), &WebSocketPeer::get_selected_protocol); + ClassDB::bind_method(D_METHOD("get_requested_url"), &WebSocketPeer::get_requested_url); ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &WebSocketPeer::set_no_delay); ClassDB::bind_method(D_METHOD("get_current_outbound_buffered_amount"), &WebSocketPeer::get_current_outbound_buffered_amount); + ClassDB::bind_method(D_METHOD("get_ready_state"), &WebSocketPeer::get_ready_state); + ClassDB::bind_method(D_METHOD("get_close_code"), &WebSocketPeer::get_close_code); + ClassDB::bind_method(D_METHOD("get_close_reason"), &WebSocketPeer::get_close_reason); + + ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketPeer::_get_supported_protocols); + ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketPeer::set_supported_protocols); + ClassDB::bind_method(D_METHOD("get_handshake_headers"), &WebSocketPeer::_get_handshake_headers); + ClassDB::bind_method(D_METHOD("set_handshake_headers", "protocols"), &WebSocketPeer::set_handshake_headers); + + ClassDB::bind_method(D_METHOD("get_inbound_buffer_size"), &WebSocketPeer::get_inbound_buffer_size); + ClassDB::bind_method(D_METHOD("set_inbound_buffer_size", "buffer_size"), &WebSocketPeer::set_inbound_buffer_size); + ClassDB::bind_method(D_METHOD("get_outbound_buffer_size"), &WebSocketPeer::get_outbound_buffer_size); + ClassDB::bind_method(D_METHOD("set_outbound_buffer_size", "buffer_size"), &WebSocketPeer::set_outbound_buffer_size); + + ClassDB::bind_method(D_METHOD("set_max_queued_packets", "buffer_size"), &WebSocketPeer::set_max_queued_packets); + ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketPeer::get_max_queued_packets); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "inbound_buffer_size"), "set_inbound_buffer_size", "get_inbound_buffer_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outbound_buffer_size"), "set_outbound_buffer_size", "get_outbound_buffer_size"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets"); + BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); + + BIND_ENUM_CONSTANT(STATE_CONNECTING); + BIND_ENUM_CONSTANT(STATE_OPEN); + BIND_ENUM_CONSTANT(STATE_CLOSING); + BIND_ENUM_CONSTANT(STATE_CLOSED); +} + +Error WebSocketPeer::_send_bind(const PackedByteArray &p_message, WriteMode p_mode) { + return send(p_message.ptr(), p_message.size(), p_mode); +} + +Error WebSocketPeer::send_text(const String &p_text) { + const CharString cs = p_text.utf8(); + return send((const uint8_t *)cs.ptr(), cs.length(), WRITE_MODE_TEXT); +} + +void WebSocketPeer::set_supported_protocols(const Vector &p_protocols) { + // Strip edges from protocols. + supported_protocols.resize(p_protocols.size()); + for (int i = 0; i < p_protocols.size(); i++) { + supported_protocols.write[i] = p_protocols[i].strip_edges(); + } +} + +const Vector WebSocketPeer::get_supported_protocols() const { + return supported_protocols; +} + +Vector WebSocketPeer::_get_supported_protocols() const { + Vector out; + out.append_array(supported_protocols); + return out; +} + +void WebSocketPeer::set_handshake_headers(const Vector &p_headers) { + handshake_headers = p_headers; +} + +const Vector WebSocketPeer::get_handshake_headers() const { + return handshake_headers; +} + +Vector WebSocketPeer::_get_handshake_headers() const { + Vector out; + out.append_array(handshake_headers); + return out; +} + +void WebSocketPeer::set_outbound_buffer_size(int p_buffer_size) { + outbound_buffer_size = p_buffer_size; +} + +int WebSocketPeer::get_outbound_buffer_size() const { + return outbound_buffer_size; +} + +void WebSocketPeer::set_inbound_buffer_size(int p_buffer_size) { + inbound_buffer_size = p_buffer_size; +} + +int WebSocketPeer::get_inbound_buffer_size() const { + return inbound_buffer_size; +} + +void WebSocketPeer::set_max_queued_packets(int p_max_queued_packets) { + max_queued_packets = p_max_queued_packets; +} + +int WebSocketPeer::get_max_queued_packets() const { + return max_queued_packets; } diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 22099f7258d..db969dd08e6 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -31,40 +31,97 @@ #ifndef WEBSOCKET_PEER_H #define WEBSOCKET_PEER_H +#include "core/crypto/crypto.h" #include "core/error/error_list.h" #include "core/io/packet_peer.h" -#include "websocket_macros.h" class WebSocketPeer : public PacketPeer { GDCLASS(WebSocketPeer, PacketPeer); - GDCICLASS(WebSocketPeer); public: + enum State { + STATE_CONNECTING, + STATE_OPEN, + STATE_CLOSING, + STATE_CLOSED + }; + enum WriteMode { WRITE_MODE_TEXT, WRITE_MODE_BINARY, }; + enum { + DEFAULT_BUFFER_SIZE = 65535, + }; + +private: + virtual Error _send_bind(const PackedByteArray &p_data, WriteMode p_mode = WRITE_MODE_BINARY); + protected: + static WebSocketPeer *(*_create)(); + static void _bind_methods(); -public: - virtual WriteMode get_write_mode() const = 0; - virtual void set_write_mode(WriteMode p_mode) = 0; + Vector supported_protocols; + Vector handshake_headers; + Vector _get_supported_protocols() const; + Vector _get_handshake_headers() const; + + int outbound_buffer_size = DEFAULT_BUFFER_SIZE; + int inbound_buffer_size = DEFAULT_BUFFER_SIZE; + int max_queued_packets = 2048; + +public: + static WebSocketPeer *create() { + if (!_create) { + return nullptr; + } + return _create(); + } + + virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref p_cert = Ref()) { return ERR_UNAVAILABLE; }; + virtual Error accept_stream(Ref p_stream) = 0; + + virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0; virtual void close(int p_code = 1000, String p_reason = "") = 0; - virtual bool is_connected_to_host() const = 0; virtual IPAddress get_connected_host() const = 0; virtual uint16_t get_connected_port() const = 0; virtual bool was_string_packet() const = 0; virtual void set_no_delay(bool p_enabled) = 0; virtual int get_current_outbound_buffered_amount() const = 0; + virtual String get_selected_protocol() const = 0; + virtual String get_requested_url() const = 0; + + virtual void poll() = 0; + virtual State get_ready_state() const = 0; + virtual int get_close_code() const = 0; + virtual String get_close_reason() const = 0; + + Error send_text(const String &p_text); + + void set_supported_protocols(const Vector &p_protocols); + const Vector get_supported_protocols() const; + + void set_handshake_headers(const Vector &p_headers); + const Vector get_handshake_headers() const; + + void set_outbound_buffer_size(int p_buffer_size); + int get_outbound_buffer_size() const; + + void set_inbound_buffer_size(int p_buffer_size); + int get_inbound_buffer_size() const; + + void set_max_queued_packets(int p_max_queued_packets); + int get_max_queued_packets() const; WebSocketPeer(); ~WebSocketPeer(); }; VARIANT_ENUM_CAST(WebSocketPeer::WriteMode); +VARIANT_ENUM_CAST(WebSocketPeer::State); #endif // WEBSOCKET_PEER_H diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp deleted file mode 100644 index 25a6e420fca..00000000000 --- a/modules/websocket/websocket_server.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/*************************************************************************/ -/* websocket_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "websocket_server.h" - -GDCINULL(WebSocketServer); - -WebSocketServer::WebSocketServer() { - _peer_id = 1; - bind_ip = IPAddress("*"); -} - -WebSocketServer::~WebSocketServer() { -} - -void WebSocketServer::_bind_methods() { - ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening); - ClassDB::bind_method(D_METHOD("set_extra_headers", "headers"), &WebSocketServer::set_extra_headers, DEFVAL(Vector())); - ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(Vector()), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop); - ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); - ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address); - ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port); - ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL("")); - - ClassDB::bind_method(D_METHOD("get_bind_ip"), &WebSocketServer::get_bind_ip); - ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &WebSocketServer::set_bind_ip); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "bind_ip"), "set_bind_ip", "get_bind_ip"); - - ClassDB::bind_method(D_METHOD("get_private_key"), &WebSocketServer::get_private_key); - ClassDB::bind_method(D_METHOD("set_private_key", "key"), &WebSocketServer::set_private_key); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", PROPERTY_USAGE_NONE), "set_private_key", "get_private_key"); - - ClassDB::bind_method(D_METHOD("get_tls_certificate"), &WebSocketServer::get_tls_certificate); - ClassDB::bind_method(D_METHOD("set_tls_certificate", "cert"), &WebSocketServer::set_tls_certificate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tls_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_tls_certificate", "get_tls_certificate"); - - ClassDB::bind_method(D_METHOD("get_ca_chain"), &WebSocketServer::get_ca_chain); - ClassDB::bind_method(D_METHOD("set_ca_chain", "ca_chain"), &WebSocketServer::set_ca_chain); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_ca_chain", "get_ca_chain"); - - ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketServer::get_handshake_timeout); - ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketServer::set_handshake_timeout); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout"); - - ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); - ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close"))); - ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"), PropertyInfo(Variant::STRING, "resource_name"))); - ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); -} - -IPAddress WebSocketServer::get_bind_ip() const { - return bind_ip; -} - -void WebSocketServer::set_bind_ip(const IPAddress &p_bind_ip) { - ERR_FAIL_COND(is_listening()); - ERR_FAIL_COND(!p_bind_ip.is_valid() && !p_bind_ip.is_wildcard()); - bind_ip = p_bind_ip; -} - -Ref WebSocketServer::get_private_key() const { - return private_key; -} - -void WebSocketServer::set_private_key(Ref p_key) { - ERR_FAIL_COND(is_listening()); - private_key = p_key; -} - -Ref WebSocketServer::get_tls_certificate() const { - return tls_cert; -} - -void WebSocketServer::set_tls_certificate(Ref p_cert) { - ERR_FAIL_COND(is_listening()); - tls_cert = p_cert; -} - -Ref WebSocketServer::get_ca_chain() const { - return ca_chain; -} - -void WebSocketServer::set_ca_chain(Ref p_ca_chain) { - ERR_FAIL_COND(is_listening()); - ca_chain = p_ca_chain; -} - -float WebSocketServer::get_handshake_timeout() const { - return handshake_timeout / 1000.0; -} - -void WebSocketServer::set_handshake_timeout(float p_timeout) { - ERR_FAIL_COND(p_timeout <= 0.0); - handshake_timeout = p_timeout * 1000; -} - -MultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { - if (is_listening()) { - return CONNECTION_CONNECTED; - } - - return CONNECTION_DISCONNECTED; -} - -bool WebSocketServer::is_server() const { - return true; -} - -void WebSocketServer::_on_peer_packet(int32_t p_peer_id) { - if (_is_multiplayer) { - _process_multiplayer(get_peer(p_peer_id), p_peer_id); - } else { - emit_signal(SNAME("data_received"), p_peer_id); - } -} - -void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol, String p_resource_name) { - if (_is_multiplayer) { - // Send add to clients - _send_add(p_peer_id); - emit_signal(SNAME("peer_connected"), p_peer_id); - } else { - emit_signal(SNAME("client_connected"), p_peer_id, p_protocol, p_resource_name); - } -} - -void WebSocketServer::_on_disconnect(int32_t p_peer_id, bool p_was_clean) { - if (_is_multiplayer) { - // Send delete to clients - _send_del(p_peer_id); - emit_signal(SNAME("peer_disconnected"), p_peer_id); - } else { - emit_signal(SNAME("client_disconnected"), p_peer_id, p_was_clean); - } -} - -void WebSocketServer::_on_close_request(int32_t p_peer_id, int p_code, String p_reason) { - emit_signal(SNAME("client_close_request"), p_peer_id, p_code, p_reason); -} diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h deleted file mode 100644 index de23ee884d9..00000000000 --- a/modules/websocket/websocket_server.h +++ /dev/null @@ -1,90 +0,0 @@ -/*************************************************************************/ -/* websocket_server.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WEBSOCKET_SERVER_H -#define WEBSOCKET_SERVER_H - -#include "core/crypto/crypto.h" -#include "core/object/ref_counted.h" -#include "websocket_multiplayer_peer.h" -#include "websocket_peer.h" - -class WebSocketServer : public WebSocketMultiplayerPeer { - GDCLASS(WebSocketServer, WebSocketMultiplayerPeer); - GDCICLASS(WebSocketServer); - - IPAddress bind_ip; - -protected: - static void _bind_methods(); - - Ref private_key; - Ref tls_cert; - Ref ca_chain; - uint32_t handshake_timeout = 3000; - -public: - virtual void set_extra_headers(const Vector &p_headers) = 0; - virtual Error listen(int p_port, const Vector p_protocols = Vector(), bool gd_mp_api = false) = 0; - virtual void stop() = 0; - virtual bool is_listening() const = 0; - virtual bool has_peer(int p_id) const = 0; - virtual bool is_server() const override; - ConnectionStatus get_connection_status() const override; - - virtual IPAddress get_peer_address(int p_peer_id) const = 0; - virtual int get_peer_port(int p_peer_id) const = 0; - virtual void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "") = 0; - - void _on_peer_packet(int32_t p_peer_id); - void _on_connect(int32_t p_peer_id, String p_protocol, String p_resource_name); - void _on_disconnect(int32_t p_peer_id, bool p_was_clean); - void _on_close_request(int32_t p_peer_id, int p_code, String p_reason); - - IPAddress get_bind_ip() const; - void set_bind_ip(const IPAddress &p_bind_ip); - - Ref get_private_key() const; - void set_private_key(Ref p_key); - - Ref get_tls_certificate() const; - void set_tls_certificate(Ref p_cert); - - Ref get_ca_chain() const; - void set_ca_chain(Ref p_ca_chain); - - float get_handshake_timeout() const; - void set_handshake_timeout(float p_timeout); - - WebSocketServer(); - ~WebSocketServer(); -}; - -#endif // WEBSOCKET_SERVER_H diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp deleted file mode 100644 index 50ef53e267f..00000000000 --- a/modules/websocket/wsl_client.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/*************************************************************************/ -/* wsl_client.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WEB_ENABLED - -#include "wsl_client.h" -#include "core/config/project_settings.h" -#include "core/io/ip.h" - -void WSLClient::_do_handshake() { - if (_requested < _request.size() - 1) { - int sent = 0; - Error err = _connection->put_partial_data(((const uint8_t *)_request.get_data() + _requested), _request.size() - _requested - 1, sent); - // Sending handshake failed - if (err != OK) { - disconnect_from_host(); - _on_error(); - return; - } - _requested += sent; - - } else { - int read = 0; - while (true) { - if (_resp_pos >= WSL_MAX_HEADER_SIZE) { - // Header is too big - disconnect_from_host(); - _on_error(); - ERR_FAIL_MSG("Response headers too big."); - } - Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); - if (err == ERR_FILE_EOF) { - // We got a disconnect. - disconnect_from_host(); - _on_error(); - return; - } else if (err != OK) { - // Got some error. - disconnect_from_host(); - _on_error(); - return; - } else if (read != 1) { - // Busy, wait next poll. - break; - } - // Check "\r\n\r\n" header terminator - char *r = (char *)_resp_buf; - int l = _resp_pos; - if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { - r[l - 3] = '\0'; - String protocol; - // Response is over, verify headers and create peer. - if (!_verify_headers(protocol)) { - disconnect_from_host(); - _on_error(); - ERR_FAIL_MSG("Invalid response headers."); - } - // Create peer. - WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); - data->obj = this; - data->conn = _connection; - data->tcp = _tcp; - data->is_server = false; - data->id = 1; - _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - _peer->set_no_delay(true); - _status = CONNECTION_CONNECTED; - _on_connect(protocol); - break; - } - _resp_pos += 1; - } - } -} - -bool WSLClient::_verify_headers(String &r_protocol) { - String s = (char *)_resp_buf; - Vector psa = s.split("\r\n"); - int len = psa.size(); - ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers. Got: " + itos(len) + ", expected >= 4."); - - Vector req = psa[0].split(" ", false); - ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code. Got '" + psa[0] + "', expected 'HTTP/1.1 101'."); - - // Wrong protocol - ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1", false, "Invalid protocol. Got: '" + req[0] + "', expected 'HTTP/1.1'."); - ERR_FAIL_COND_V_MSG(req[1] != "101", false, "Invalid status code. Got: '" + req[1] + "', expected '101'."); - - HashMap headers; - for (int i = 1; i < len; i++) { - Vector header = psa[i].split(":", false, 1); - ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + "."); - String name = header[0].to_lower(); - String value = header[1].strip_edges(); - if (headers.has(name)) { - headers[name] += "," + value; - } else { - headers[name] = value; - } - } - -#define WSL_CHECK(NAME, VALUE) \ - ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ - "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define WSL_CHECK_NC(NAME, VALUE) \ - ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ - "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); - WSL_CHECK("connection", "upgrade"); - WSL_CHECK("upgrade", "websocket"); - WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); -#undef WSL_CHECK_NC -#undef WSL_CHECK - if (_protocols.size() == 0) { - // We didn't request a custom protocol - ERR_FAIL_COND_V_MSG(headers.has("sec-websocket-protocol"), false, "Received unrequested sub-protocol -> " + headers["sec-websocket-protocol"]); - } else { - // We requested at least one custom protocol but didn't receive one - ERR_FAIL_COND_V_MSG(!headers.has("sec-websocket-protocol"), false, "Requested sub-protocol(s) but received none."); - // Check received sub-protocol was one of those requested. - r_protocol = headers["sec-websocket-protocol"]; - bool valid = false; - for (int i = 0; i < _protocols.size(); i++) { - if (_protocols[i] != r_protocol) { - continue; - } - valid = true; - break; - } - if (!valid) { - ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + r_protocol); - return false; - } - } - return true; -} - -Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector p_protocols, const Vector p_custom_headers) { - ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); - ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); - - _peer = Ref(memnew(WSLPeer)); - - if (p_host.is_valid_ip_address()) { - _ip_candidates.push_back(IPAddress(p_host)); - } else { - // Queue hostname for resolution. - _resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host); - ERR_FAIL_COND_V(_resolver_id == IP::RESOLVER_INVALID_ID, ERR_INVALID_PARAMETER); - // Check if it was found in cache. - IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); - if (ip_status == IP::RESOLVER_STATUS_DONE) { - _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); - IP::get_singleton()->erase_resolve_item(_resolver_id); - _resolver_id = IP::RESOLVER_INVALID_ID; - } - } - - // We assume OK while hostname resolution is pending. - Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED; - while (_ip_candidates.size()) { - err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port); - if (err == OK) { - break; - } - } - if (err != OK) { - _tcp->disconnect_from_host(); - _on_error(); - return err; - } - _connection = _tcp; - _use_tls = p_tls; - _host = p_host; - _port = p_port; - // Strip edges from protocols. - _protocols.resize(p_protocols.size()); - String *pw = _protocols.ptrw(); - for (int i = 0; i < p_protocols.size(); i++) { - pw[i] = p_protocols[i].strip_edges(); - } - - _key = WSLPeer::generate_key(); - String request = "GET " + p_path + " HTTP/1.1\r\n"; - String port = ""; - if ((p_port != 80 && !p_tls) || (p_port != 443 && p_tls)) { - port = ":" + itos(p_port); - } - request += "Host: " + p_host + port + "\r\n"; - request += "Upgrade: websocket\r\n"; - request += "Connection: Upgrade\r\n"; - request += "Sec-WebSocket-Key: " + _key + "\r\n"; - request += "Sec-WebSocket-Version: 13\r\n"; - if (p_protocols.size() > 0) { - request += "Sec-WebSocket-Protocol: "; - for (int i = 0; i < p_protocols.size(); i++) { - if (i != 0) { - request += ","; - } - request += p_protocols[i]; - } - request += "\r\n"; - } - for (int i = 0; i < p_custom_headers.size(); i++) { - request += p_custom_headers[i] + "\r\n"; - } - request += "\r\n"; - _request = request.utf8(); - _status = CONNECTION_CONNECTING; - - return OK; -} - -int WSLClient::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -void WSLClient::poll() { - if (_resolver_id != IP::RESOLVER_INVALID_ID) { - IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); - if (ip_status == IP::RESOLVER_STATUS_WAITING) { - return; - } - // Anything else is either a candidate or a failure. - Error err = FAILED; - if (ip_status == IP::RESOLVER_STATUS_DONE) { - _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); - while (_ip_candidates.size()) { - err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port); - if (err == OK) { - break; - } - } - } - IP::get_singleton()->erase_resolve_item(_resolver_id); - _resolver_id = IP::RESOLVER_INVALID_ID; - if (err != OK) { - disconnect_from_host(); - _on_error(); - return; - } - } - if (_peer->is_connected_to_host()) { - _peer->poll(); - if (!_peer->is_connected_to_host()) { - disconnect_from_host(); - _on_disconnect(_peer->close_code != -1); - } - return; - } - - if (_connection.is_null()) { - return; // Not connected. - } - - _tcp->poll(); - switch (_tcp->get_status()) { - case StreamPeerTCP::STATUS_NONE: - // Clean close - disconnect_from_host(); - _on_error(); - break; - case StreamPeerTCP::STATUS_CONNECTED: { - _ip_candidates.clear(); - Ref tls; - if (_use_tls) { - if (_connection == _tcp) { - // Start SSL handshake - tls = Ref(StreamPeerTLS::create()); - ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build."); - tls->set_blocking_handshake_enabled(false); - if (tls->connect_to_stream(_tcp, verify_tls, _host, tls_cert) != OK) { - disconnect_from_host(); - _on_error(); - return; - } - _connection = tls; - } else { - tls = static_cast>(_connection); - ERR_FAIL_COND(tls.is_null()); // Bug? - tls->poll(); - } - if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { - return; // Need more polling. - } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { - disconnect_from_host(); - _on_error(); - return; // Error. - } - } - // Do websocket handshake. - _do_handshake(); - } break; - case StreamPeerTCP::STATUS_ERROR: - while (_ip_candidates.size() > 0) { - _tcp->disconnect_from_host(); - if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) { - return; - } - } - disconnect_from_host(); - _on_error(); - break; - case StreamPeerTCP::STATUS_CONNECTING: - break; // Wait for connection - } -} - -Ref WSLClient::get_peer(int p_peer_id) const { - ERR_FAIL_COND_V(p_peer_id != 1, nullptr); - - return _peer; -} - -MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { - // This is surprising, but keeps the current behaviour to allow clean close requests. - // TODO Refactor WebSocket and split Client/Server/Multiplayer like done in other peers. - if (_peer->is_connected_to_host()) { - return CONNECTION_CONNECTED; - } - return _status; -} - -void WSLClient::disconnect_from_host(int p_code, String p_reason) { - _peer->close(p_code, p_reason); - _connection = Ref(nullptr); - _tcp = Ref(memnew(StreamPeerTCP)); - _status = CONNECTION_DISCONNECTED; - - _key = ""; - _host = ""; - _protocols.clear(); - _use_tls = false; - - _request = ""; - _requested = 0; - - memset(_resp_buf, 0, sizeof(_resp_buf)); - _resp_pos = 0; - - if (_resolver_id != IP::RESOLVER_INVALID_ID) { - IP::get_singleton()->erase_resolve_item(_resolver_id); - _resolver_id = IP::RESOLVER_INVALID_ID; - } - - _ip_candidates.clear(); -} - -IPAddress WSLClient::get_connected_host() const { - ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IPAddress()); - return _peer->get_connected_host(); -} - -uint16_t WSLClient::get_connected_port() const { - ERR_FAIL_COND_V(!_peer->is_connected_to_host(), 0); - return _peer->get_connected_port(); -} - -Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_FAIL_COND_V_MSG(_connection.is_valid(), FAILED, "Buffers sizes can only be set before listening or connecting."); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -WSLClient::WSLClient() { - _peer.instantiate(); - _tcp.instantiate(); - disconnect_from_host(); -} - -WSLClient::~WSLClient() { - _peer->close_now(); - _peer->invalidate(); - disconnect_from_host(); -} - -#endif // WEB_ENABLED diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h deleted file mode 100644 index dfb989fdd35..00000000000 --- a/modules/websocket/wsl_client.h +++ /dev/null @@ -1,91 +0,0 @@ -/*************************************************************************/ -/* wsl_client.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WSL_CLIENT_H -#define WSL_CLIENT_H - -#ifndef WEB_ENABLED - -#include "core/error/error_list.h" -#include "core/io/stream_peer_tcp.h" -#include "core/io/stream_peer_tls.h" -#include "websocket_client.h" -#include "wsl_peer.h" -#include "wslay/wslay.h" - -class WSLClient : public WebSocketClient { - GDCIIMPL(WSLClient, WebSocketClient); - -private: - int _in_buf_size = DEF_BUF_SHIFT; - int _in_pkt_size = DEF_PKT_SHIFT; - int _out_buf_size = DEF_BUF_SHIFT; - int _out_pkt_size = DEF_PKT_SHIFT; - - Ref _peer; - Ref _tcp; - Ref _connection; - ConnectionStatus _status = CONNECTION_DISCONNECTED; - - CharString _request; - int _requested = 0; - - uint8_t _resp_buf[WSL_MAX_HEADER_SIZE]; - int _resp_pos = 0; - - String _key; - String _host; - uint16_t _port = 0; - Array _ip_candidates; - Vector _protocols; - bool _use_tls = false; - IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID; - - void _do_handshake(); - bool _verify_headers(String &r_protocol); - -public: - Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override; - Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_tls, const Vector p_protocol = Vector(), const Vector p_custom_headers = Vector()) override; - int get_max_packet_size() const override; - Ref get_peer(int p_peer_id) const override; - void disconnect_from_host(int p_code = 1000, String p_reason = "") override; - IPAddress get_connected_host() const override; - uint16_t get_connected_port() const override; - virtual ConnectionStatus get_connection_status() const override; - virtual void poll() override; - - WSLClient(); - ~WSLClient(); -}; - -#endif // WEB_ENABLED - -#endif // WSL_CLIENT_H diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 97bd87a5268..4c8e661f675 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -32,71 +32,533 @@ #include "wsl_peer.h" -#include "wsl_client.h" -#include "wsl_server.h" +#include "wsl_peer.h" -#include "core/crypto/crypto_core.h" -#include "core/math/random_number_generator.h" -#include "core/os/os.h" +#include "core/io/stream_peer_tls.h" -String WSLPeer::generate_key() { - // Random key - RandomNumberGenerator rng; - rng.set_seed(OS::get_singleton()->get_unix_time()); - Vector bkey; - int len = 16; // 16 bytes, as per RFC - bkey.resize(len); - uint8_t *w = bkey.ptrw(); - for (int i = 0; i < len; i++) { - w[i] = (uint8_t)rng.randi_range(0, 255); +CryptoCore::RandomGenerator *WSLPeer::_static_rng = nullptr; + +void WSLPeer::initialize() { + WebSocketPeer::_create = WSLPeer::_create; + _static_rng = memnew(CryptoCore::RandomGenerator); + _static_rng->init(); +} + +void WSLPeer::deinitialize() { + if (_static_rng) { + memdelete(_static_rng); + _static_rng = nullptr; } - return CryptoCore::b64_encode_str(&w[0], len); } -String WSLPeer::compute_key_response(String p_key) { - String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC - Vector sha = key.sha1_buffer(); - return CryptoCore::b64_encode_str(sha.ptr(), sha.size()); +/// +/// Resolver +/// +void WSLPeer::Resolver::start(const String &p_host, int p_port) { + stop(); + + port = p_port; + if (p_host.is_valid_ip_address()) { + ip_candidates.push_back(IPAddress(p_host)); + } else { + // Queue hostname for resolution. + resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host); + ERR_FAIL_COND(resolver_id == IP::RESOLVER_INVALID_ID); + // Check if it was found in cache. + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(resolver_id); + if (ip_status == IP::RESOLVER_STATUS_DONE) { + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolver_id); + IP::get_singleton()->erase_resolve_item(resolver_id); + resolver_id = IP::RESOLVER_INVALID_ID; + } + } } -void WSLPeer::_wsl_destroy(struct PeerData **p_data) { - if (!p_data || !(*p_data)) { +void WSLPeer::Resolver::stop() { + if (resolver_id != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(resolver_id); + resolver_id = IP::RESOLVER_INVALID_ID; + } + port = 0; +} + +void WSLPeer::Resolver::try_next_candidate(Ref &p_tcp) { + // Check if we still need resolving. + if (resolver_id != IP::RESOLVER_INVALID_ID) { + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(resolver_id); + if (ip_status == IP::RESOLVER_STATUS_WAITING) { + return; + } + if (ip_status == IP::RESOLVER_STATUS_DONE) { + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolver_id); + } + IP::get_singleton()->erase_resolve_item(resolver_id); + resolver_id = IP::RESOLVER_INVALID_ID; + } + + // Try the current candidate if we have one. + if (p_tcp->get_status() != StreamPeerTCP::STATUS_NONE) { + p_tcp->poll(); + StreamPeerTCP::Status status = p_tcp->get_status(); + if (status == StreamPeerTCP::STATUS_CONNECTED) { + p_tcp->set_no_delay(true); + ip_candidates.clear(); + return; + } else { + p_tcp->disconnect_from_host(); + } + } + + // Keep trying next candidate. + while (ip_candidates.size()) { + Error err = p_tcp->connect_to_host(ip_candidates.pop_front(), port); + if (err == OK) { + return; + } else { + p_tcp->disconnect_from_host(); + } + } +} + +/// +/// Server functions +/// +Error WSLPeer::accept_stream(Ref p_stream) { + ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_stream.is_null(), ERR_INVALID_PARAMETER); + + _clear(); + + if (p_stream->is_class_ptr(StreamPeerTCP::get_class_ptr_static())) { + tcp = p_stream; + connection = p_stream; + use_tls = false; + } else if (p_stream->is_class_ptr(StreamPeerTLS::get_class_ptr_static())) { + Ref base_stream = static_cast>(p_stream)->get_stream(); + ERR_FAIL_COND_V(base_stream.is_null() || !base_stream->is_class_ptr(StreamPeerTCP::get_class_ptr_static()), ERR_INVALID_PARAMETER); + tcp = static_cast>(base_stream); + connection = p_stream; + use_tls = true; + } + ERR_FAIL_COND_V(connection.is_null() || tcp.is_null(), ERR_INVALID_PARAMETER); + is_server = true; + ready_state = STATE_CONNECTING; + handshake_buffer->resize(WSL_MAX_HEADER_SIZE); + handshake_buffer->seek(0); + return OK; +} + +bool WSLPeer::_parse_client_request() { + Vector psa = String((const char *)handshake_buffer->get_data_array().ptr(), handshake_buffer->get_position() - 4).split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); + + Vector req = psa[0].split(" ", false); + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version."); + + HashMap headers; + for (int i = 1; i < len; i++) { + Vector header = psa[i].split(":", false, 1); + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]); + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) { + headers[name] += "," + value; + } else { + headers[name] = value; + } + } + requested_host = headers.has("host") ? headers.get("host") : ""; + requested_url = (use_tls ? "wss://" : "ws://") + requested_host + req[1]; +#define WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define WSL_CHECK_EX(NAME) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK("sec-websocket-version", "13"); + WSL_CHECK_EX("sec-websocket-key"); + WSL_CHECK_EX("connection"); +#undef WSL_CHECK_EX +#undef WSL_CHECK + session_key = headers["sec-websocket-key"]; + if (headers.has("sec-websocket-protocol")) { + Vector protos = headers["sec-websocket-protocol"].split(","); + for (int i = 0; i < protos.size(); i++) { + String proto = protos[i].strip_edges(); + // Check if we have the given protocol + for (int j = 0; j < supported_protocols.size(); j++) { + if (proto != supported_protocols[j]) { + continue; + } + selected_protocol = proto; + break; + } + // Found a protocol + if (!selected_protocol.is_empty()) { + break; + } + } + if (selected_protocol.is_empty()) { // Invalid protocol(s) requested + return false; + } + } else if (supported_protocols.size() > 0) { // No protocol requested, but we need one + return false; + } + return true; +} + +Error WSLPeer::_do_server_handshake() { + if (use_tls) { + Ref tls = static_cast>(connection); + if (tls.is_null()) { + ERR_FAIL_V_MSG(ERR_BUG, "Couldn't get StreamPeerTLS for WebSocket handshake."); + close(-1); + return FAILED; + } + tls->poll(); + if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { + return OK; // Pending handshake + } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { + print_verbose(vformat("WebSocket SSL connection error during handshake (StreamPeerTLS status code %d).", tls->get_status())); + close(-1); + return FAILED; + } + } + + if (pending_request) { + int read = 0; + while (true) { + ERR_FAIL_COND_V_MSG(handshake_buffer->get_available_bytes() < 1, ERR_OUT_OF_MEMORY, "WebSocket response headers are too big."); + int pos = handshake_buffer->get_position(); + uint8_t byte; + Error err = connection->get_partial_data(&byte, 1, read); + if (err != OK) { // Got an error + print_verbose(vformat("WebSocket error while getting partial data (StreamPeer error code %d).", err)); + close(-1); + return FAILED; + } else if (read != 1) { // Busy, wait next poll + return OK; + } + handshake_buffer->put_u8(byte); + const char *r = (const char *)handshake_buffer->get_data_array().ptr(); + int l = pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + if (!_parse_client_request()) { + close(-1); + return FAILED; + } + String s = "HTTP/1.1 101 Switching Protocols\r\n"; + s += "Upgrade: websocket\r\n"; + s += "Connection: Upgrade\r\n"; + s += "Sec-WebSocket-Accept: " + _compute_key_response(session_key) + "\r\n"; + if (!selected_protocol.is_empty()) { + s += "Sec-WebSocket-Protocol: " + selected_protocol + "\r\n"; + } + for (int i = 0; i < handshake_headers.size(); i++) { + s += handshake_headers[i] + "\r\n"; + } + s += "\r\n"; + CharString cs = s.utf8(); + handshake_buffer->clear(); + handshake_buffer->put_data((const uint8_t *)cs.get_data(), cs.length()); + handshake_buffer->seek(0); + pending_request = false; + break; + } + } + } + + if (pending_request) { // Still pending. + return OK; + } + + int left = handshake_buffer->get_available_bytes(); + if (left) { + Vector data = handshake_buffer->get_data_array(); + int pos = handshake_buffer->get_position(); + int sent = 0; + Error err = connection->put_partial_data(data.ptr() + pos, left, sent); + if (err != OK) { + print_verbose(vformat("WebSocket error while putting partial data (StreamPeer error code %d).", err)); + close(-1); + return err; + } + handshake_buffer->seek(pos + sent); + left -= sent; + if (left == 0) { + resolver.stop(); + // Response sent, initialize wslay context. + wslay_event_context_server_init(&wsl_ctx, &_wsl_callbacks, this); + wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size); + in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets); + packet_buffer.resize(inbound_buffer_size); + ready_state = STATE_OPEN; + } + } + + return OK; +} + +/// +/// Client functions +/// +void WSLPeer::_do_client_handshake() { + ERR_FAIL_COND(tcp.is_null()); + + // Try to connect to candidates. + if (resolver.has_more_candidates()) { + resolver.try_next_candidate(tcp); + if (resolver.has_more_candidates()) { + return; // Still pending. + } + } + + tcp->poll(); + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + close(-1); // Failed to connect. return; } - struct PeerData *data = *p_data; - if (data->polling) { - data->destroy = true; - return; + + if (use_tls) { + Ref tls; + if (connection == tcp) { + // Start SSL handshake + tls = Ref(StreamPeerTLS::create()); + ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build."); + tls->set_blocking_handshake_enabled(false); + if (tls->connect_to_stream(tcp, verify_tls, requested_host, tls_cert) != OK) { + close(-1); + return; // Error. + } + connection = tls; + } else { + tls = static_cast>(connection); + ERR_FAIL_COND(tls.is_null()); + tls->poll(); + } + if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { + return; // Need more polling. + } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { + close(-1); + return; // Error. + } + } + + // Do websocket handshake. + if (pending_request) { + int left = handshake_buffer->get_available_bytes(); + int pos = handshake_buffer->get_position(); + const Vector data = handshake_buffer->get_data_array(); + int sent = 0; + Error err = connection->put_partial_data(data.ptr() + pos, left, sent); + // Sending handshake failed + if (err != OK) { + close(-1); + return; // Error. + } + handshake_buffer->seek(pos + sent); + if (handshake_buffer->get_available_bytes() == 0) { + pending_request = false; + handshake_buffer->clear(); + handshake_buffer->resize(WSL_MAX_HEADER_SIZE); + handshake_buffer->seek(0); + } + } else { + int read = 0; + while (true) { + int left = handshake_buffer->get_available_bytes(); + int pos = handshake_buffer->get_position(); + if (left == 0) { + // Header is too big + close(-1); + ERR_FAIL_MSG("Response headers too big."); + return; + } + + uint8_t byte; + Error err = connection->get_partial_data(&byte, 1, read); + if (err != OK) { + // Got some error. + close(-1); + return; + } else if (read != 1) { + // Busy, wait next poll. + break; + } + handshake_buffer->put_u8(byte); + + // Check "\r\n\r\n" header terminator + const char *r = (const char *)handshake_buffer->get_data_array().ptr(); + int l = pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + // Response is over, verify headers and initialize wslay context/ + if (!_verify_server_response()) { + close(-1); + ERR_FAIL_MSG("Invalid response headers."); + return; + } + wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this); + wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size); + in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets); + packet_buffer.resize(inbound_buffer_size); + ready_state = STATE_OPEN; + break; + } + } } - wslay_event_context_free(data->ctx); - memdelete(data); - *p_data = nullptr; } -bool WSLPeer::_wsl_poll(struct PeerData *p_data) { - p_data->polling = true; - int err = 0; - if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) { - print_verbose("Websocket (wslay) poll error: " + itos(err)); - p_data->destroy = true; - } - p_data->polling = false; +bool WSLPeer::_verify_server_response() { + Vector psa = String((const char *)handshake_buffer->get_data_array().ptr(), handshake_buffer->get_position() - 4).split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers. Got: " + itos(len) + ", expected >= 4."); - if (p_data->destroy || (wslay_event_get_close_sent(p_data->ctx) && wslay_event_get_close_received(p_data->ctx))) { - bool valid = p_data->valid; - _wsl_destroy(&p_data); - return valid; + Vector req = psa[0].split(" ", false); + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code. Got '" + psa[0] + "', expected 'HTTP/1.1 101'."); + + // Wrong protocol + ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1", false, "Invalid protocol. Got: '" + req[0] + "', expected 'HTTP/1.1'."); + ERR_FAIL_COND_V_MSG(req[1] != "101", false, "Invalid status code. Got: '" + req[1] + "', expected '101'."); + + HashMap headers; + for (int i = 1; i < len; i++) { + Vector header = psa[i].split(":", false, 1); + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + "."); + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) { + headers[name] += "," + value; + } else { + headers[name] = value; + } } - return false; + +#define WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define WSL_CHECK_NC(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); + WSL_CHECK("connection", "upgrade"); + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK_NC("sec-websocket-accept", _compute_key_response(session_key)); +#undef WSL_CHECK_NC +#undef WSL_CHECK + if (supported_protocols.size() == 0) { + // We didn't request a custom protocol + ERR_FAIL_COND_V_MSG(headers.has("sec-websocket-protocol"), false, "Received unrequested sub-protocol -> " + headers["sec-websocket-protocol"]); + } else { + // We requested at least one custom protocol but didn't receive one + ERR_FAIL_COND_V_MSG(!headers.has("sec-websocket-protocol"), false, "Requested sub-protocol(s) but received none."); + // Check received sub-protocol was one of those requested. + selected_protocol = headers["sec-websocket-protocol"]; + bool valid = false; + for (int i = 0; i < supported_protocols.size(); i++) { + if (supported_protocols[i] != selected_protocol) { + continue; + } + valid = true; + break; + } + if (!valid) { + ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + selected_protocol); + return false; + } + } + return true; } -ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) { - struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; - if (!peer_data->valid) { +Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref p_cert) { + ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); + + _clear(); + + String host; + String path; + String scheme; + int port = 0; + Error err = p_url.parse_url(scheme, host, port, path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); + if (scheme.is_empty()) { + scheme = "ws://"; + } + ERR_FAIL_COND_V_MSG(scheme != "ws://" && scheme != "wss://", ERR_INVALID_PARAMETER, vformat("Invalid protocol: \"%s\" (must be either \"ws://\" or \"wss://\").", scheme)); + + use_tls = false; + if (scheme == "wss://") { + use_tls = true; + } + if (port == 0) { + port = use_tls ? 443 : 80; + } + if (path.is_empty()) { + path = "/"; + } + + requested_url = p_url; + requested_host = host; + verify_tls = p_verify_tls; + tls_cert = p_cert; + tcp.instantiate(); + + resolver.start(host, port); + resolver.try_next_candidate(tcp); + + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTING && !resolver.has_more_candidates()) { + _clear(); + return FAILED; + } + connection = tcp; + + // Prepare handshake request. + session_key = _generate_key(); + String request = "GET " + path + " HTTP/1.1\r\n"; + String port_string; + if ((port != 80 && !use_tls) || (port != 443 && use_tls)) { + port_string = ":" + itos(port); + } + request += "Host: " + host + port_string + "\r\n"; + request += "Upgrade: websocket\r\n"; + request += "Connection: Upgrade\r\n"; + request += "Sec-WebSocket-Key: " + session_key + "\r\n"; + request += "Sec-WebSocket-Version: 13\r\n"; + if (supported_protocols.size() > 0) { + request += "Sec-WebSocket-Protocol: "; + for (int i = 0; i < supported_protocols.size(); i++) { + if (i != 0) { + request += ","; + } + request += supported_protocols[i]; + } + request += "\r\n"; + } + for (int i = 0; i < handshake_headers.size(); i++) { + request += handshake_headers[i] + "\r\n"; + } + request += "\r\n"; + CharString cs = request.utf8(); + handshake_buffer->put_data((const uint8_t *)cs.get_data(), cs.length()); + handshake_buffer->seek(0); + ready_state = STATE_CONNECTING; + is_server = false; + return OK; +} + +/// +/// Callback functions. +/// +ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) { + WSLPeer *peer = (WSLPeer *)user_data; + Ref conn = peer->connection; + if (conn.is_null()) { wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); return -1; } - Ref conn = peer_data->conn; int read = 0; Error err = conn->get_partial_data(data, len, read); if (err != OK) { @@ -111,13 +573,13 @@ ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len return read; } -ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) { - struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; - if (!peer_data->valid) { +ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) { + WSLPeer *peer = (WSLPeer *)user_data; + Ref conn = peer->connection; + if (conn.is_null()) { wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); return -1; } - Ref conn = peer_data->conn; int sent = 0; Error err = conn->put_partial_data(data, len, sent); if (err != OK) { @@ -131,144 +593,142 @@ ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size return sent; } -int wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { - RandomNumberGenerator rng; - // TODO maybe use crypto in the future? - rng.set_seed(OS::get_singleton()->get_unix_time()); - for (unsigned int i = 0; i < len; i++) { - buf[i] = (uint8_t)rng.randi_range(0, 255); - } +int WSLPeer::_wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { + ERR_FAIL_COND_V(!_static_rng, WSLAY_ERR_CALLBACK_FAILURE); + Error err = _static_rng->get_random_bytes(buf, len); + ERR_FAIL_COND_V(err != OK, WSLAY_ERR_CALLBACK_FAILURE); return 0; } -void wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { - struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; - if (!peer_data->valid || peer_data->closing) { - return; - } - WSLPeer *peer = static_cast(peer_data->peer); +void WSLPeer::_wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { + WSLPeer *peer = (WSLPeer *)user_data; + uint8_t op = arg->opcode; - if (peer->parse_message(arg) != OK) { + if (op == WSLAY_CONNECTION_CLOSE) { + // Close request or confirmation. + peer->close_code = arg->status_code; + size_t len = arg->msg_length; + peer->close_reason = ""; + if (len > 2 /* first 2 bytes = close code */) { + peer->close_reason.parse_utf8((char *)arg->msg + 2, len - 2); + } + if (peer->ready_state == STATE_OPEN) { + peer->ready_state = STATE_CLOSING; + } return; } - if (peer_data->is_server) { - WSLServer *helper = static_cast(peer_data->obj); - helper->_on_peer_packet(peer_data->id); - } else { - WSLClient *helper = static_cast(peer_data->obj); - helper->_on_peer_packet(); + if (peer->ready_state == STATE_CLOSING) { + return; } + + if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) { + // Message. + uint8_t is_string = arg->opcode == WSLAY_TEXT_FRAME ? 1 : 0; + peer->in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); + } + // Ping or pong. } -wslay_event_callbacks wsl_callbacks = { - wsl_recv_callback, - wsl_send_callback, - wsl_genmask_callback, +wslay_event_callbacks WSLPeer::_wsl_callbacks = { + _wsl_recv_callback, + _wsl_send_callback, + _wsl_genmask_callback, nullptr, /* on_frame_recv_start_callback */ nullptr, /* on_frame_recv_callback */ nullptr, /* on_frame_recv_end_callback */ - wsl_msg_recv_callback + _wsl_msg_recv_callback }; -Error WSLPeer::parse_message(const wslay_event_on_msg_recv_arg *arg) { - uint8_t is_string = 0; - if (arg->opcode == WSLAY_TEXT_FRAME) { - is_string = 1; - } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) { - close_code = arg->status_code; - size_t len = arg->msg_length; - close_reason = ""; - if (len > 2 /* first 2 bytes = close code */) { - close_reason.parse_utf8((char *)arg->msg + 2, len - 2); - } - if (!wslay_event_get_close_sent(_data->ctx)) { - if (_data->is_server) { - WSLServer *helper = static_cast(_data->obj); - helper->_on_close_request(_data->id, close_code, close_reason); - } else { - WSLClient *helper = static_cast(_data->obj); - helper->_on_close_request(close_code, close_reason); - } - } - return ERR_FILE_EOF; - } else if (arg->opcode != WSLAY_BINARY_FRAME) { - // Ping or pong - return ERR_SKIP; - } - _in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); - return OK; +String WSLPeer::_generate_key() { + // Random key + Vector bkey; + int len = 16; // 16 bytes, as per RFC + bkey.resize(len); + _wsl_genmask_callback(nullptr, bkey.ptrw(), len, nullptr); + return CryptoCore::b64_encode_str(bkey.ptrw(), len); } -void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { - ERR_FAIL_COND(_data != nullptr); - ERR_FAIL_COND(p_data == nullptr); - - _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _packet_buffer.resize(1 << p_in_buf_size); - _out_buf_size = p_out_buf_size; - _out_pkt_size = p_out_pkt_size; - - _data = p_data; - _data->peer = this; - _data->valid = true; - - if (_data->is_server) { - wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); - } else { - wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data); - } - wslay_event_config_set_max_recv_msg_length(_data->ctx, (1ULL << p_in_buf_size)); -} - -void WSLPeer::set_write_mode(WriteMode p_mode) { - write_mode = p_mode; -} - -WSLPeer::WriteMode WSLPeer::get_write_mode() const { - return write_mode; +String WSLPeer::_compute_key_response(String p_key) { + String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC + Vector sha = key.sha1_buffer(); + return CryptoCore::b64_encode_str(sha.ptr(), sha.size()); } void WSLPeer::poll() { - if (!_data) { + // Nothing to do. + if (ready_state == STATE_CLOSED) { return; } - if (_wsl_poll(_data)) { - _data = nullptr; + if (ready_state == STATE_CONNECTING) { + if (is_server) { + _do_server_handshake(); + } else { + _do_client_handshake(); + } + } + + if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) { + ERR_FAIL_COND(!wsl_ctx); + int err = 0; + if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) { + // Error close. + print_verbose("Websocket (wslay) poll error: " + itos(err)); + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } + if (wslay_event_get_close_sent(wsl_ctx) && wslay_event_get_close_received(wsl_ctx)) { + // Clean close. + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + close(-1); + return; + } } } -Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - ERR_FAIL_COND_V(_out_pkt_size && (wslay_event_get_queued_msg_count(_data->ctx) >= (1ULL << _out_pkt_size)), ERR_OUT_OF_MEMORY); - ERR_FAIL_COND_V(_out_buf_size && (wslay_event_get_queued_msg_length(_data->ctx) + p_buffer_size >= (1ULL << _out_buf_size)), ERR_OUT_OF_MEMORY); +Error WSLPeer::_send(const uint8_t *p_buffer, int p_buffer_size, wslay_opcode p_opcode) { + ERR_FAIL_COND_V(ready_state != STATE_OPEN, FAILED); + ERR_FAIL_COND_V(wslay_event_get_queued_msg_count(wsl_ctx) >= (uint32_t)max_queued_packets, ERR_OUT_OF_MEMORY); + ERR_FAIL_COND_V(outbound_buffer_size > 0 && (wslay_event_get_queued_msg_length(wsl_ctx) + p_buffer_size > (uint32_t)outbound_buffer_size), ERR_OUT_OF_MEMORY); struct wslay_event_msg msg; - msg.opcode = write_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME; + msg.opcode = p_opcode; msg.msg = p_buffer; msg.msg_length = p_buffer_size; // Queue & send message. - if (wslay_event_queue_msg(_data->ctx, &msg) != 0 || wslay_event_send(_data->ctx) != 0) { - close_now(); + if (wslay_event_queue_msg(wsl_ctx, &msg) != 0 || wslay_event_send(wsl_ctx) != 0) { + close(-1); return FAILED; } return OK; } +Error WSLPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) { + wslay_opcode opcode = p_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME; + return _send(p_buffer, p_buffer_size, opcode); +} + +Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + return _send(p_buffer, p_buffer_size, WSLAY_BINARY_FRAME); +} + Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { r_buffer_size = 0; - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + ERR_FAIL_COND_V(ready_state != STATE_OPEN, FAILED); - if (_in_buffer.packets_left() == 0) { + if (in_buffer.packets_left() == 0) { return ERR_UNAVAILABLE; } int read = 0; - uint8_t *rw = _packet_buffer.ptrw(); - _in_buffer.read_packet(rw, _packet_buffer.size(), &_is_string, read); + uint8_t *rw = packet_buffer.ptrw(); + in_buffer.read_packet(rw, packet_buffer.size(), &was_string, read); *r_buffer = rw; r_buffer_size = read; @@ -277,75 +737,106 @@ Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } int WSLPeer::get_available_packet_count() const { - if (!is_connected_to_host()) { + if (ready_state != STATE_OPEN) { return 0; } - return _in_buffer.packets_left(); + return in_buffer.packets_left(); } int WSLPeer::get_current_outbound_buffered_amount() const { - ERR_FAIL_COND_V(!_data, 0); + if (ready_state != STATE_OPEN) { + return 0; + } - return wslay_event_get_queued_msg_length(_data->ctx); -} - -bool WSLPeer::was_string_packet() const { - return _is_string; -} - -bool WSLPeer::is_connected_to_host() const { - return _data != nullptr; -} - -void WSLPeer::close_now() { - close(1000, ""); - _wsl_destroy(&_data); + return wslay_event_get_queued_msg_length(wsl_ctx); } void WSLPeer::close(int p_code, String p_reason) { - if (_data && !wslay_event_get_close_sent(_data->ctx)) { - CharString cs = p_reason.utf8(); - wslay_event_queue_close(_data->ctx, p_code, (uint8_t *)cs.ptr(), cs.size()); - wslay_event_send(_data->ctx); - _data->closing = true; + if (p_code < 0) { + // Force immediate close. + ready_state = STATE_CLOSED; } - _in_buffer.clear(); - _packet_buffer.resize(0); + if (ready_state == STATE_OPEN && !wslay_event_get_close_sent(wsl_ctx)) { + CharString cs = p_reason.utf8(); + wslay_event_queue_close(wsl_ctx, p_code, (uint8_t *)cs.ptr(), cs.length()); + wslay_event_send(wsl_ctx); + ready_state = STATE_CLOSING; + } else if (ready_state == STATE_CONNECTING || ready_state == STATE_CLOSED) { + ready_state = STATE_CLOSED; + connection.unref(); + if (tcp.is_valid()) { + tcp->disconnect_from_host(); + tcp.unref(); + } + } + + in_buffer.clear(); + packet_buffer.resize(0); } IPAddress WSLPeer::get_connected_host() const { - ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), IPAddress()); - - return _data->tcp->get_connected_host(); + ERR_FAIL_COND_V(tcp.is_null(), IPAddress()); + return tcp->get_connected_host(); } uint16_t WSLPeer::get_connected_port() const { - ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), 0); + ERR_FAIL_COND_V(tcp.is_null(), 0); + return tcp->get_connected_port(); +} - return _data->tcp->get_connected_port(); +String WSLPeer::get_selected_protocol() const { + return selected_protocol; +} + +String WSLPeer::get_requested_url() const { + return requested_url; } void WSLPeer::set_no_delay(bool p_enabled) { - ERR_FAIL_COND(!is_connected_to_host() || _data->tcp.is_null()); - _data->tcp->set_no_delay(p_enabled); + ERR_FAIL_COND(tcp.is_null()); + tcp->set_no_delay(p_enabled); } -void WSLPeer::invalidate() { - if (_data) { - _data->valid = false; +void WSLPeer::_clear() { + // Connection info. + ready_state = STATE_CLOSED; + is_server = false; + connection.unref(); + if (tcp.is_valid()) { + tcp->disconnect_from_host(); + tcp.unref(); } + if (wsl_ctx) { + wslay_event_context_free(wsl_ctx); + wsl_ctx = nullptr; + } + + resolver.stop(); + requested_url.clear(); + requested_host.clear(); + pending_request = true; + handshake_buffer->clear(); + selected_protocol.clear(); + session_key.clear(); + + // Pending packets info. + was_string = 0; + in_buffer.clear(); + packet_buffer.clear(); + + // Close code info. + close_code = -1; + close_reason.clear(); } WSLPeer::WSLPeer() { + handshake_buffer.instantiate(); } WSLPeer::~WSLPeer() { - close(); - invalidate(); - _wsl_destroy(&_data); - _data = nullptr; + close(-1); } #endif // WEB_ENABLED diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index 92672eb2c45..379002739cc 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -33,79 +33,123 @@ #ifndef WEB_ENABLED +#include "websocket_peer.h" + +#include "packet_buffer.h" + +#include "core/crypto/crypto_core.h" #include "core/error/error_list.h" #include "core/io/packet_peer.h" #include "core/io/stream_peer_tcp.h" #include "core/templates/ring_buffer.h" -#include "packet_buffer.h" -#include "websocket_peer.h" #include "wslay/wslay.h" #define WSL_MAX_HEADER_SIZE 4096 class WSLPeer : public WebSocketPeer { - GDCIIMPL(WSLPeer, WebSocketPeer); +private: + static CryptoCore::RandomGenerator *_static_rng; + static WebSocketPeer *_create() { return memnew(WSLPeer); } -public: - struct PeerData { - bool polling = false; - bool destroy = false; - bool valid = false; - bool is_server = false; - bool closing = false; - void *obj = nullptr; - void *peer = nullptr; - Ref conn; - Ref tcp; - int id = 1; - wslay_event_context_ptr ctx = nullptr; + // Callbacks. + static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data); + static ssize_t _wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data); + static int _wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data); + static void _wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data); + + static wslay_event_callbacks _wsl_callbacks; + + // Helpers + static String _compute_key_response(String p_key); + static String _generate_key(); + + // Client IP resolver. + class Resolver { + Array ip_candidates; + IP::ResolverID resolver_id = IP::RESOLVER_INVALID_ID; + int port = 0; + + public: + bool has_more_candidates() { + return ip_candidates.size() > 0 || resolver_id != IP::RESOLVER_INVALID_ID; + } + + void try_next_candidate(Ref &p_tcp); + void start(const String &p_host, int p_port); + void stop(); + Resolver() {} }; - static String compute_key_response(String p_key); - static String generate_key(); + Resolver resolver; -private: - static bool _wsl_poll(struct PeerData *p_data); - static void _wsl_destroy(struct PeerData **p_data); + // WebSocket connection state. + WebSocketPeer::State ready_state = WebSocketPeer::STATE_CLOSED; + bool is_server = false; + Ref tcp; + Ref connection; + wslay_event_context_ptr wsl_ctx = nullptr; - struct PeerData *_data = nullptr; - uint8_t _is_string = 0; - // Our packet info is just a boolean (is_string), using uint8_t for it. - PacketBuffer _in_buffer; + String requested_url; + String requested_host; + bool pending_request = true; + Ref handshake_buffer; + String selected_protocol; + String session_key; - Vector _packet_buffer; - - WriteMode write_mode = WRITE_MODE_BINARY; - - int _out_buf_size = 0; - int _out_pkt_size = 0; - -public: int close_code = -1; String close_reason; - void poll(); // Used by client and server. + uint8_t was_string = 0; + // WebSocket configuration. + bool use_tls = true; + bool verify_tls = true; + Ref tls_cert; + + // Packet buffers. + Vector packet_buffer; + // Our packet info is just a boolean (is_string), using uint8_t for it. + PacketBuffer in_buffer; + + Error _send(const uint8_t *p_buffer, int p_buffer_size, wslay_opcode p_opcode); + + Error _do_server_handshake(); + bool _parse_client_request(); + + void _do_client_handshake(); + bool _verify_server_response(); + + void _clear(); + +public: + static void initialize(); + static void deinitialize(); + + // PacketPeer virtual int get_available_packet_count() const override; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; - virtual int get_max_packet_size() const override { return _packet_buffer.size(); }; + virtual int get_max_packet_size() const override { return packet_buffer.size(); }; + + // WebSocketPeer + virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override; + virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref p_cert = Ref()) override; + virtual Error accept_stream(Ref p_stream) override; + virtual void close(int p_code = 1000, String p_reason = "") override; + virtual void poll() override; + + virtual State get_ready_state() const override { return ready_state; } + virtual int get_close_code() const override { return close_code; } + virtual String get_close_reason() const override { return close_reason; } virtual int get_current_outbound_buffered_amount() const override; - virtual void close_now(); - virtual void close(int p_code = 1000, String p_reason = "") override; - virtual bool is_connected_to_host() const override; virtual IPAddress get_connected_host() const override; virtual uint16_t get_connected_port() const override; + virtual String get_selected_protocol() const override; + virtual String get_requested_url() const override; - virtual WriteMode get_write_mode() const override; - virtual void set_write_mode(WriteMode p_mode) override; - virtual bool was_string_packet() const override; + virtual bool was_string_packet() const override { return was_string; } virtual void set_no_delay(bool p_enabled) override; - void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size); - Error parse_message(const wslay_event_on_msg_recv_arg *arg); - void invalidate(); - WSLPeer(); ~WSLPeer(); }; diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp deleted file mode 100644 index 01dcd53839f..00000000000 --- a/modules/websocket/wsl_server.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/*************************************************************************/ -/* wsl_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WEB_ENABLED - -#include "wsl_server.h" -#include "core/config/project_settings.h" -#include "core/os/os.h" - -bool WSLServer::PendingPeer::_parse_request(const Vector p_protocols, String &r_resource_name) { - Vector psa = String((char *)req_buf).split("\r\n"); - int len = psa.size(); - ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); - - Vector req = psa[0].split(" ", false); - ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); - - // Wrong protocol - ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version."); - - r_resource_name = req[1]; - HashMap headers; - for (int i = 1; i < len; i++) { - Vector header = psa[i].split(":", false, 1); - ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]); - String name = header[0].to_lower(); - String value = header[1].strip_edges(); - if (headers.has(name)) { - headers[name] += "," + value; - } else { - headers[name] = value; - } - } -#define WSL_CHECK(NAME, VALUE) \ - ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ - "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define WSL_CHECK_EX(NAME) \ - ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); - WSL_CHECK("upgrade", "websocket"); - WSL_CHECK("sec-websocket-version", "13"); - WSL_CHECK_EX("sec-websocket-key"); - WSL_CHECK_EX("connection"); -#undef WSL_CHECK_EX -#undef WSL_CHECK - key = headers["sec-websocket-key"]; - if (headers.has("sec-websocket-protocol")) { - Vector protos = headers["sec-websocket-protocol"].split(","); - for (int i = 0; i < protos.size(); i++) { - String proto = protos[i].strip_edges(); - // Check if we have the given protocol - for (int j = 0; j < p_protocols.size(); j++) { - if (proto != p_protocols[j]) { - continue; - } - protocol = proto; - break; - } - // Found a protocol - if (!protocol.is_empty()) { - break; - } - } - if (protocol.is_empty()) { // Invalid protocol(s) requested - return false; - } - } else if (p_protocols.size() > 0) { // No protocol requested, but we need one - return false; - } - return true; -} - -Error WSLServer::PendingPeer::do_handshake(const Vector p_protocols, uint64_t p_timeout, String &r_resource_name, const Vector &p_extra_headers) { - if (OS::get_singleton()->get_ticks_msec() - time > p_timeout) { - print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", p_timeout * 0.001)); - return ERR_TIMEOUT; - } - - if (use_tls) { - Ref tls = static_cast>(connection); - if (tls.is_null()) { - ERR_FAIL_V_MSG(ERR_BUG, "Couldn't get StreamPeerTLS for WebSocket handshake."); - } - tls->poll(); - if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { - return ERR_BUSY; - } else if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { - print_verbose(vformat("WebSocket SSL connection error during handshake (StreamPeerTLS status code %d).", tls->get_status())); - return FAILED; - } - } - - if (!has_request) { - int read = 0; - while (true) { - ERR_FAIL_COND_V_MSG(req_pos >= WSL_MAX_HEADER_SIZE, ERR_OUT_OF_MEMORY, "WebSocket response headers are too big."); - Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); - if (err != OK) { // Got an error - print_verbose(vformat("WebSocket error while getting partial data (StreamPeer error code %d).", err)); - return FAILED; - } else if (read != 1) { // Busy, wait next poll - return ERR_BUSY; - } - char *r = (char *)req_buf; - int l = req_pos; - if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { - r[l - 3] = '\0'; - if (!_parse_request(p_protocols, r_resource_name)) { - return FAILED; - } - String s = "HTTP/1.1 101 Switching Protocols\r\n"; - s += "Upgrade: websocket\r\n"; - s += "Connection: Upgrade\r\n"; - s += "Sec-WebSocket-Accept: " + WSLPeer::compute_key_response(key) + "\r\n"; - if (!protocol.is_empty()) { - s += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; - } - for (int i = 0; i < p_extra_headers.size(); i++) { - s += p_extra_headers[i] + "\r\n"; - } - s += "\r\n"; - response = s.utf8(); - has_request = true; - break; - } - req_pos += 1; - } - } - - if (has_request && response_sent < response.size() - 1) { - int sent = 0; - Error err = connection->put_partial_data((const uint8_t *)response.get_data() + response_sent, response.size() - response_sent - 1, sent); - if (err != OK) { - print_verbose(vformat("WebSocket error while putting partial data (StreamPeer error code %d).", err)); - return err; - } - response_sent += sent; - } - - if (response_sent < response.size() - 1) { - return ERR_BUSY; - } - - return OK; -} - -void WSLServer::set_extra_headers(const Vector &p_headers) { - _extra_headers = p_headers; -} - -Error WSLServer::listen(int p_port, const Vector p_protocols, bool gd_mp_api) { - ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); - - _is_multiplayer = gd_mp_api; - // Strip edges from protocols. - _protocols.resize(p_protocols.size()); - String *pw = _protocols.ptrw(); - for (int i = 0; i < p_protocols.size(); i++) { - pw[i] = p_protocols[i].strip_edges(); - } - return _server->listen(p_port, bind_ip); -} - -void WSLServer::poll() { - List remove_ids; - for (const KeyValue> &E : _peer_map) { - Ref peer = const_cast(static_cast(E.value.ptr())); - peer->poll(); - if (!peer->is_connected_to_host()) { - _on_disconnect(E.key, peer->close_code != -1); - remove_ids.push_back(E.key); - } - } - for (int &E : remove_ids) { - _peer_map.erase(E); - } - remove_ids.clear(); - - List> remove_peers; - for (const Ref &E : _pending) { - String resource_name; - Ref ppeer = E; - Error err = ppeer->do_handshake(_protocols, handshake_timeout, resource_name, _extra_headers); - if (err == ERR_BUSY) { - continue; - } else if (err != OK) { - remove_peers.push_back(ppeer); - continue; - } - // Creating new peer - int32_t id = generate_unique_id(); - - WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); - data->obj = this; - data->conn = ppeer->connection; - data->tcp = ppeer->tcp; - data->is_server = true; - data->id = id; - - Ref ws_peer = memnew(WSLPeer); - ws_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - ws_peer->set_no_delay(true); - - _peer_map[id] = ws_peer; - remove_peers.push_back(ppeer); - _on_connect(id, ppeer->protocol, resource_name); - } - for (const Ref &E : remove_peers) { - _pending.erase(E); - } - remove_peers.clear(); - - if (!_server->is_listening()) { - return; - } - - while (_server->is_connection_available()) { - Ref conn = _server->take_connection(); - if (is_refusing_new_connections()) { - continue; // Conn will go out-of-scope and be closed. - } - - Ref peer = memnew(PendingPeer); - if (private_key.is_valid() && tls_cert.is_valid()) { - Ref tls = Ref(StreamPeerTLS::create()); - tls->set_blocking_handshake_enabled(false); - tls->accept_stream(conn, private_key, tls_cert, ca_chain); - peer->connection = tls; - peer->use_tls = true; - } else { - peer->connection = conn; - } - peer->tcp = conn; - peer->time = OS::get_singleton()->get_ticks_msec(); - _pending.push_back(peer); - } -} - -bool WSLServer::is_listening() const { - return _server->is_listening(); -} - -int WSLServer::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -void WSLServer::stop() { - _server->stop(); - for (const KeyValue> &E : _peer_map) { - Ref peer = const_cast(static_cast(E.value.ptr())); - peer->close_now(); - } - _pending.clear(); - _peer_map.clear(); - _protocols.clear(); -} - -bool WSLServer::has_peer(int p_id) const { - return _peer_map.has(p_id); -} - -Ref WSLServer::get_peer(int p_id) const { - ERR_FAIL_COND_V(!has_peer(p_id), nullptr); - return _peer_map[p_id]; -} - -IPAddress WSLServer::get_peer_address(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), IPAddress()); - - return _peer_map[p_peer_id]->get_connected_host(); -} - -int WSLServer::get_peer_port(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); - - return _peer_map[p_peer_id]->get_connected_port(); -} - -void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { - ERR_FAIL_COND(!has_peer(p_peer_id)); - - get_peer(p_peer_id)->close(p_code, p_reason); -} - -Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_FAIL_COND_V_MSG(_server->is_listening(), FAILED, "Buffers sizes can only be set before listening or connecting."); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -WSLServer::WSLServer() { - _server.instantiate(); -} - -WSLServer::~WSLServer() { - stop(); -} - -#endif // WEB_ENABLED diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h deleted file mode 100644 index df0c1dc68a0..00000000000 --- a/modules/websocket/wsl_server.h +++ /dev/null @@ -1,98 +0,0 @@ -/*************************************************************************/ -/* wsl_server.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef WSL_SERVER_H -#define WSL_SERVER_H - -#ifndef WEB_ENABLED - -#include "websocket_server.h" -#include "wsl_peer.h" - -#include "core/io/stream_peer_tcp.h" -#include "core/io/stream_peer_tls.h" -#include "core/io/tcp_server.h" - -class WSLServer : public WebSocketServer { - GDCIIMPL(WSLServer, WebSocketServer); - -private: - class PendingPeer : public RefCounted { - private: - bool _parse_request(const Vector p_protocols, String &r_resource_name); - - public: - Ref tcp; - Ref connection; - bool use_tls = false; - - uint64_t time = 0; - uint8_t req_buf[WSL_MAX_HEADER_SIZE] = {}; - int req_pos = 0; - String key; - String protocol; - bool has_request = false; - CharString response; - int response_sent = 0; - - Error do_handshake(const Vector p_protocols, uint64_t p_timeout, String &r_resource_name, const Vector &p_extra_headers); - }; - - int _in_buf_size = DEF_BUF_SHIFT; - int _in_pkt_size = DEF_PKT_SHIFT; - int _out_buf_size = DEF_BUF_SHIFT; - int _out_pkt_size = DEF_PKT_SHIFT; - - List> _pending; - Ref _server; - Vector _protocols; - Vector _extra_headers; - -public: - Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) override; - void set_extra_headers(const Vector &p_headers) override; - Error listen(int p_port, const Vector p_protocols = Vector(), bool gd_mp_api = false) override; - void stop() override; - bool is_listening() const override; - int get_max_packet_size() const override; - bool has_peer(int p_id) const override; - Ref get_peer(int p_id) const override; - IPAddress get_peer_address(int p_peer_id) const override; - int get_peer_port(int p_peer_id) const override; - void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "") override; - virtual void poll() override; - - WSLServer(); - ~WSLServer(); -}; - -#endif // WEB_ENABLED - -#endif // WSL_SERVER_H