diff options
Diffstat (limited to 'modules/websocket')
34 files changed, 536 insertions, 471 deletions
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index 4c022c43cf..63c941c4a8 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -18,12 +18,11 @@ elif env["builtin_wslay"]: "wslay_net.c", "wslay_event.c", "wslay_queue.c", - "wslay_stack.c", "wslay_frame.c", ] thirdparty_sources = [thirdparty_dir + s for s in thirdparty_sources] - env_ws.Prepend(CPPPATH=[thirdparty_dir + "includes/"]) + env_ws.Prepend(CPPPATH=[thirdparty_dir]) env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"]) if env["platform"] == "windows" or env["platform"] == "uwp": diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 45db49c913..ad2acf8a21 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -1,59 +1,51 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" version="4.0"> +<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A WebSocket client implementation. </brief_description> <description> This class implements a WebSocket client compatible with any RFC 6455-compliant WebSocket server. - This client can be optionally used as a network peer for the [MultiplayerAPI]. - After starting the client ([method connect_to_url]), you will need to [method NetworkedMultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). + 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. </description> <tutorials> </tutorials> <methods> <method name="connect_to_url"> - <return type="int" enum="Error"> - </return> - <argument index="0" name="url" type="String"> - </argument> - <argument index="1" name="protocols" type="PackedStringArray" default="PackedStringArray( )"> - </argument> - <argument index="2" name="gd_mp_api" type="bool" default="false"> - </argument> - <argument index="3" name="custom_headers" type="PackedStringArray" default="PackedStringArray( )"> - </argument> + <return type="int" enum="Error" /> + <argument index="0" name="url" type="String" /> + <argument index="1" name="protocols" type="PackedStringArray" default="PackedStringArray()" /> + <argument index="2" name="gd_mp_api" type="bool" default="false" /> + <argument index="3" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" /> <description> 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 network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. + 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 HTML5, 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 SSL certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the SSL certificate. [b]Note:[/b] Specifying [code]custom_headers[/code] is not supported in HTML5 exports due to browsers restrictions. </description> </method> <method name="disconnect_from_host"> - <return type="void"> - </return> - <argument index="0" name="code" type="int" default="1000"> - </argument> - <argument index="1" name="reason" type="String" default=""""> - </argument> + <return type="void" /> + <argument index="0" name="code" type="int" default="1000" /> + <argument index="1" name="reason" type="String" default="""" /> <description> Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information. </description> </method> <method name="get_connected_host" qualifiers="const"> - <return type="String"> - </return> + <return type="String" /> <description> - Return the IP address of the currently connected host. + Returns the IP address of the currently connected host. </description> </method> <method name="get_connected_port" qualifiers="const"> - <return type="int"> - </return> + <return type="int" /> <description> - Return the IP port of the currently connected host. + Returns the IP port of the currently connected host. </description> </method> </methods> @@ -69,8 +61,7 @@ </members> <signals> <signal name="connection_closed"> - <argument index="0" name="was_clean_close" type="bool"> - </argument> + <argument index="0" name="was_clean_close" type="bool" /> <description> 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. </description> @@ -81,8 +72,7 @@ </description> </signal> <signal name="connection_established"> - <argument index="0" name="protocol" type="String"> - </argument> + <argument index="0" name="protocol" type="String" /> <description> Emitted when a connection with the server is established, [code]protocol[/code] will contain the sub-protocol agreed with the server. </description> @@ -94,15 +84,11 @@ </description> </signal> <signal name="server_close_request"> - <argument index="0" name="code" type="int"> - </argument> - <argument index="1" name="reason" type="String"> - </argument> + <argument index="0" name="code" type="int" /> + <argument index="1" name="reason" type="String" /> <description> 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. </description> </signal> </signals> - <constants> - </constants> </class> diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 0679acf78a..4a617f4c82 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -1,34 +1,28 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketMultiplayerPeer" inherits="NetworkedMultiplayerPeer" version="4.0"> +<class name="WebSocketMultiplayerPeer" inherits="MultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Base class for WebSocket server and client. </brief_description> <description> - Base class for WebSocket server and client, allowing them to be used as network peer for the [MultiplayerAPI]. + Base class for WebSocket server and client, allowing them to be used as multiplayer peer for the [MultiplayerAPI]. + [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. </description> <tutorials> </tutorials> <methods> <method name="get_peer" qualifiers="const"> - <return type="WebSocketPeer"> - </return> - <argument index="0" name="peer_id" type="int"> - </argument> + <return type="WebSocketPeer" /> + <argument index="0" name="peer_id" type="int" /> <description> Returns the [WebSocketPeer] associated to the given [code]peer_id[/code]. </description> </method> <method name="set_buffers"> - <return type="int" enum="Error"> - </return> - <argument index="0" name="input_buffer_size_kb" type="int"> - </argument> - <argument index="1" name="input_max_packets" type="int"> - </argument> - <argument index="2" name="output_buffer_size_kb" type="int"> - </argument> - <argument index="3" name="output_max_packets" type="int"> - </argument> + <return type="int" enum="Error" /> + <argument index="0" name="input_buffer_size_kb" type="int" /> + <argument index="1" name="input_max_packets" type="int" /> + <argument index="2" name="output_buffer_size_kb" type="int" /> + <argument index="3" name="output_max_packets" type="int" /> <description> 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. @@ -37,20 +31,13 @@ </description> </method> </methods> - <members> - <member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" override="true" default="false" /> - <member name="transfer_mode" type="int" setter="set_transfer_mode" getter="get_transfer_mode" override="true" enum="NetworkedMultiplayerPeer.TransferMode" default="2" /> - </members> <signals> <signal name="peer_packet"> - <argument index="0" name="peer_source" type="int"> - </argument> + <argument index="0" name="peer_source" type="int" /> <description> 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. </description> </signal> </signals> - <constants> - </constants> </class> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 5125956416..6466654517 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -1,22 +1,19 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketPeer" inherits="PacketPeer" version="4.0"> +<class name="WebSocketPeer" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A class representing a specific WebSocket connection. </brief_description> <description> - This class represent a specific WebSocket connection, you can do lower level operations with it. + 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. </description> <tutorials> </tutorials> <methods> <method name="close"> - <return type="void"> - </return> - <argument index="0" name="code" type="int" default="1000"> - </argument> - <argument index="1" name="reason" type="String" default=""""> - </argument> + <return type="void" /> + <argument index="0" name="code" type="int" default="1000" /> + <argument index="1" name="reason" type="String" default="""" /> <description> 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. @@ -24,57 +21,54 @@ </description> </method> <method name="get_connected_host" qualifiers="const"> - <return type="String"> - </return> + <return type="String" /> <description> Returns the IP address of the connected peer. [b]Note:[/b] Not available in the HTML5 export. </description> </method> <method name="get_connected_port" qualifiers="const"> - <return type="int"> - </return> + <return type="int" /> <description> Returns the remote port of the connected peer. [b]Note:[/b] Not available in the HTML5 export. </description> </method> + <method name="get_current_outbound_buffered_amount" qualifiers="const"> + <return type="int" /> + <description> + Returns the current amount of data in the outbound websocket buffer. [b]Note:[/b] HTML5 exports use WebSocket.bufferedAmount, while other platforms use an internal buffer. + </description> + </method> <method name="get_write_mode" qualifiers="const"> - <return type="int" enum="WebSocketPeer.WriteMode"> - </return> + <return type="int" enum="WebSocketPeer.WriteMode" /> <description> Gets the current selected write mode. See [enum WriteMode]. </description> </method> <method name="is_connected_to_host" qualifiers="const"> - <return type="bool"> - </return> + <return type="bool" /> <description> Returns [code]true[/code] if this peer is currently connected. </description> </method> <method name="set_no_delay"> - <return type="void"> - </return> - <argument index="0" name="enabled" type="bool"> - </argument> + <return type="void" /> + <argument index="0" name="enabled" type="bool" /> <description> Disable Nagle's algorithm on the underling TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information. [b]Note:[/b] Not available in the HTML5 export. </description> </method> <method name="set_write_mode"> - <return type="void"> - </return> - <argument index="0" name="mode" type="int" enum="WebSocketPeer.WriteMode"> - </argument> + <return type="void" /> + <argument index="0" name="mode" type="int" enum="WebSocketPeer.WriteMode" /> <description> Sets the socket to use the given [enum WriteMode]. </description> </method> <method name="was_string_packet" qualifiers="const"> - <return type="bool"> - </return> + <return type="bool" /> <description> Returns [code]true[/code] if the last received packet was sent as a text payload. See [enum WriteMode]. </description> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index f7805209e2..ef3279aac4 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -1,82 +1,67 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" version="4.0"> +<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> A WebSocket server implementation. </brief_description> <description> 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 NetworkedMultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). When clients connect, disconnect, or send data, you will receive the appropriate signal. + 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 HTML5 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. </description> <tutorials> </tutorials> <methods> <method name="disconnect_peer"> - <return type="void"> - </return> - <argument index="0" name="id" type="int"> - </argument> - <argument index="1" name="code" type="int" default="1000"> - </argument> - <argument index="2" name="reason" type="String" default=""""> - </argument> + <return type="void" /> + <argument index="0" name="id" type="int" /> + <argument index="1" name="code" type="int" default="1000" /> + <argument index="2" name="reason" type="String" default="""" /> <description> Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information. </description> </method> <method name="get_peer_address" qualifiers="const"> - <return type="String"> - </return> - <argument index="0" name="id" type="int"> - </argument> + <return type="String" /> + <argument index="0" name="id" type="int" /> <description> Returns the IP address of the given peer. </description> </method> <method name="get_peer_port" qualifiers="const"> - <return type="int"> - </return> - <argument index="0" name="id" type="int"> - </argument> + <return type="int" /> + <argument index="0" name="id" type="int" /> <description> Returns the remote port of the given peer. </description> </method> <method name="has_peer" qualifiers="const"> - <return type="bool"> - </return> - <argument index="0" name="id" type="int"> - </argument> + <return type="bool" /> + <argument index="0" name="id" type="int" /> <description> Returns [code]true[/code] if a peer with the given ID is connected. </description> </method> <method name="is_listening" qualifiers="const"> - <return type="bool"> - </return> + <return type="bool" /> <description> Returns [code]true[/code] if the server is actively listening on a port. </description> </method> <method name="listen"> - <return type="int" enum="Error"> - </return> - <argument index="0" name="port" type="int"> - </argument> - <argument index="1" name="protocols" type="PackedStringArray" default="PackedStringArray( )"> - </argument> - <argument index="2" name="gd_mp_api" type="bool" default="false"> - </argument> + <return type="int" enum="Error" /> + <argument index="0" name="port" type="int" /> + <argument index="1" name="protocols" type="PackedStringArray" default="PackedStringArray()" /> + <argument index="2" name="gd_mp_api" type="bool" default="false" /> <description> 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 network peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. + 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]). </description> </method> <method name="stop"> - <return type="void"> - </return> + <return type="void" /> <description> Stops the server and clear its state. </description> @@ -89,6 +74,9 @@ <member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain"> When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake. </member> + <member name="handshake_timeout" type="float" setter="set_handshake_timeout" getter="get_handshake_timeout" default="3.0"> + 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. + </member> <member name="private_key" type="CryptoKey" setter="set_private_key" getter="get_private_key"> When set to a valid [CryptoKey] (along with [member ssl_certificate]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol). </member> @@ -98,43 +86,35 @@ </members> <signals> <signal name="client_close_request"> - <argument index="0" name="id" type="int"> - </argument> - <argument index="1" name="code" type="int"> - </argument> - <argument index="2" name="reason" type="String"> - </argument> + <argument index="0" name="id" type="int" /> + <argument index="1" name="code" type="int" /> + <argument index="2" name="reason" type="String" /> <description> 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. </description> </signal> <signal name="client_connected"> - <argument index="0" name="id" type="int"> - </argument> - <argument index="1" name="protocol" type="String"> - </argument> + <argument index="0" name="id" type="int" /> + <argument index="1" name="protocol" type="String" /> + <argument index="2" name="resource_name" type="String" /> <description> - Emitted when a new client connects. "protocol" will be the sub-protocol agreed with the client. + 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. </description> </signal> <signal name="client_disconnected"> - <argument index="0" name="id" type="int"> - </argument> - <argument index="1" name="was_clean_close" type="bool"> - </argument> + <argument index="0" name="id" type="int" /> + <argument index="1" name="was_clean_close" type="bool" /> <description> Emitted when a client disconnects. [code]was_clean_close[/code] will be [code]true[/code] if the connection was shutdown cleanly. </description> </signal> <signal name="data_received"> - <argument index="0" name="id" type="int"> - </argument> + <argument index="0" name="id" type="int" /> <description> Emitted when a new message is received. [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer. </description> </signal> </signals> - <constants> - </constants> </class> diff --git a/modules/websocket/editor_debugger_server_websocket.cpp b/modules/websocket/editor_debugger_server_websocket.cpp index b02d212c42..4f1a56f00b 100644 --- a/modules/websocket/editor_debugger_server_websocket.cpp +++ b/modules/websocket/editor_debugger_server_websocket.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,8 @@ #include "editor_debugger_server_websocket.h" #include "core/config/project_settings.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "modules/websocket/remote_debugger_peer_websocket.h" @@ -48,11 +50,47 @@ void EditorDebuggerServerWebSocket::poll() { server->poll(); } -Error EditorDebuggerServerWebSocket::start() { - int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); - Vector<String> protocols; - protocols.push_back("binary"); // compatibility with EMSCRIPTEN TCP-to-WebSocket layer. - return server->listen(remote_port, protocols); +String EditorDebuggerServerWebSocket::get_uri() const { + return endpoint; +} + +Error EditorDebuggerServerWebSocket::start(const String &p_uri) { + // Default host and port + String bind_host = (String)EditorSettings::get_singleton()->get("network/debug/remote_host"); + int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + + // Optionally override + if (!p_uri.is_empty() && p_uri != "ws://") { + String scheme, path; + Error err = p_uri.parse_url(scheme, bind_host, bind_port, path); + ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER); + 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<String> 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); + if (err == OK) { + break; + } + if (attempt >= max_attempts) { + EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, remote debugging unavailable.", bind_port), EditorLog::MSG_TYPE_ERROR); + return err; + } + int last_port = bind_port++; + EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, trying %d instead.", last_port, bind_port), EditorLog::MSG_TYPE_WARNING); + } + + // Endpoint that the client should connect to + endpoint = vformat("ws://%s:%d", bind_host, bind_port); + + return OK; } void EditorDebuggerServerWebSocket::stop() { diff --git a/modules/websocket/editor_debugger_server_websocket.h b/modules/websocket/editor_debugger_server_websocket.h index 2f73b98c3d..cc14bf62ba 100644 --- a/modules/websocket/editor_debugger_server_websocket.h +++ b/modules/websocket/editor_debugger_server_websocket.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,12 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H -#define SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H - -#include "modules/websocket/websocket_server.h" +#ifndef EDITOR_DEBUGGER_SERVER_WEBSOCKET_H +#define EDITOR_DEBUGGER_SERVER_WEBSOCKET_H #include "editor/debugger/editor_debugger_server.h" +#include "modules/websocket/websocket_server.h" class EditorDebuggerServerWebSocket : public EditorDebuggerServer { GDCLASS(EditorDebuggerServerWebSocket, EditorDebuggerServer); @@ -41,6 +40,7 @@ class EditorDebuggerServerWebSocket : public EditorDebuggerServer { private: Ref<WebSocketServer> server; List<int> pending_peers; + String endpoint; public: static EditorDebuggerServer *create(const String &p_protocol); @@ -48,15 +48,16 @@ public: void _peer_connected(int p_peer, String p_protocol); void _peer_disconnected(int p_peer, bool p_was_clean); - void poll() override; - Error start() override; - void stop() override; - bool is_active() const override; - bool is_connection_available() const override; - Ref<RemoteDebuggerPeer> take_connection() override; + virtual void poll() override; + virtual String get_uri() const override; + virtual Error start(const String &p_uri = "") override; + virtual void stop() override; + virtual bool is_active() const override; + virtual bool is_connection_available() const override; + virtual Ref<RemoteDebuggerPeer> take_connection() override; EditorDebuggerServerWebSocket(); ~EditorDebuggerServerWebSocket(); }; -#endif // SCRIPT_EDITOR_DEBUGGER_WEBSOCKET_H +#endif // EDITOR_DEBUGGER_SERVER_WEBSOCKET_H diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 25b6d6ef0e..e051a3b564 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #ifdef JAVASCRIPT_ENABLED #include "emws_client.h" + #include "core/config/project_settings.h" #include "core/io/ip.h" #include "emscripten.h" @@ -45,8 +46,9 @@ void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_s EMWSClient *client = static_cast<EMWSClient *>(obj); Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); - if (err == OK) + if (err == OK) { client->_on_peer_packet(); + } } void EMWSClient::_esws_on_error(void *obj) { @@ -71,15 +73,16 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, String proto_string; for (int i = 0; i < p_protocols.size(); i++) { - if (i != 0) + 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 in HTML5 platform."); + WARN_PRINT_ONCE("Custom headers are not supported in HTML5 platform."); } if (p_ssl) { str = "wss://"; @@ -95,7 +98,7 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, return FAILED; } - static_cast<Ref<EMWSPeer>>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size); + static_cast<Ref<EMWSPeer>>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size, _out_buf_size); return OK; } @@ -107,10 +110,11 @@ Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const { return _peer; } -NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { +MultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { if (_peer->is_connected_to_host()) { - if (_is_connecting) + if (_is_connecting) { return CONNECTION_CONNECTING; + } return CONNECTION_CONNECTED; } @@ -121,8 +125,8 @@ void EMWSClient::disconnect_from_host(int p_code, String p_reason) { _peer->close(p_code, p_reason); } -IP_Address EMWSClient::get_connected_host() const { - ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); +IPAddress EMWSClient::get_connected_host() const { + ERR_FAIL_V_MSG(IPAddress(), "Not supported in HTML5 export."); } uint16_t EMWSClient::get_connected_port() const { @@ -136,6 +140,7 @@ int EMWSClient::get_max_packet_size() const { 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; } diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h index 2ab7dc83d0..61ea0002ea 100644 --- a/modules/websocket/emws_client.h +++ b/modules/websocket/emws_client.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -45,6 +45,7 @@ private: 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); @@ -56,7 +57,7 @@ public: Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()); Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); - IP_Address get_connected_host() const; + IPAddress get_connected_host() const; uint16_t get_connected_port() const; virtual ConnectionStatus get_connection_status() const; int get_max_packet_size() const; diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 5e75e10d68..86169f88e9 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,12 +31,14 @@ #ifdef JAVASCRIPT_ENABLED #include "emws_peer.h" + #include "core/io/ip.h" -void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size) { +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::set_write_mode(WriteMode p_mode) { @@ -53,14 +55,21 @@ Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_strin } 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); + int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0; - godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin); + + if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin) != 0) { + return FAILED; + } + return OK; -}; +} 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); @@ -70,19 +79,26 @@ Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { r_buffer_size = read; return OK; -}; +} int EMWSPeer::get_available_packet_count() const { return _in_buffer.packets_left(); -}; +} + +int EMWSPeer::get_current_outbound_buffered_amount() const { + if (peer_sock != -1) { + return godot_js_websocket_buffered_amount(peer_sock); + } + return 0; +} bool EMWSPeer::was_string_packet() const { return _is_string; -}; +} bool EMWSPeer::is_connected_to_host() const { return peer_sock != -1; -}; +} void EMWSPeer::close(int p_code, String p_reason) { if (peer_sock != -1) { @@ -91,15 +107,15 @@ void EMWSPeer::close(int p_code, String p_reason) { _is_string = 0; _in_buffer.clear(); peer_sock = -1; -}; +} -IP_Address EMWSPeer::get_connected_host() const { - ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); -}; +IPAddress EMWSPeer::get_connected_host() const { + ERR_FAIL_V_MSG(IPAddress(), "Not supported in HTML5 export."); +} uint16_t EMWSPeer::get_connected_port() const { ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); -}; +} void EMWSPeer::set_no_delay(bool p_enabled) { ERR_FAIL_MSG("'set_no_delay' is not supported in HTML5 export."); @@ -107,10 +123,10 @@ void EMWSPeer::set_no_delay(bool p_enabled) { EMWSPeer::EMWSPeer() { close(); -}; +} EMWSPeer::~EMWSPeer() { close(); -}; +} #endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index abe5bf2bdb..df63d2d801 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -48,6 +48,7 @@ typedef void (*WSOnError)(void *p_ref); extern int godot_js_websocket_create(void *p_ref, const char *p_url, const char *p_proto, WSOnOpen p_on_open, WSOnMessage p_on_message, WSOnError p_on_error, WSOnClose p_on_close); extern int godot_js_websocket_send(int p_id, const uint8_t *p_buf, int p_buf_len, int p_raw); +extern int godot_js_websocket_buffered_amount(int p_id); extern void godot_js_websocket_close(int p_id, int p_code, const char *p_reason); extern void godot_js_websocket_destroy(int p_id); } @@ -62,18 +63,20 @@ private: Vector<uint8_t> _packet_buffer; PacketBuffer<uint8_t> _in_buffer; uint8_t _is_string = 0; + int _out_buf_size = 0; 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); + void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size); virtual int get_available_packet_count() const; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); virtual int get_max_packet_size() const { return _packet_buffer.size(); }; + virtual int get_current_outbound_buffered_amount() const; virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; - virtual IP_Address get_connected_host() const; + virtual IPAddress get_connected_host() const; virtual uint16_t get_connected_port() const; virtual WriteMode get_write_mode() const; diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp index a35d84f372..53b4a0207d 100644 --- a/modules/websocket/emws_server.cpp +++ b/modules/websocket/emws_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -58,8 +58,8 @@ Vector<String> EMWSServer::get_protocols() const { return out; } -IP_Address EMWSServer::get_peer_address(int p_peer_id) const { - return IP_Address(); +IPAddress EMWSServer::get_peer_address(int p_peer_id) const { + return IPAddress(); } int EMWSServer::get_peer_port(int p_peer_id) const { diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h index 4179b20ffe..f310c17c9d 100644 --- a/modules/websocket/emws_server.h +++ b/modules/websocket/emws_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,7 +33,7 @@ #ifdef JAVASCRIPT_ENABLED -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "emws_peer.h" #include "websocket_server.h" @@ -47,7 +47,7 @@ public: bool is_listening() const; bool has_peer(int p_id) const; Ref<WebSocketPeer> get_peer(int p_id) const; - IP_Address get_peer_address(int p_peer_id) const; + 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 = ""); int get_max_packet_size() const; diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js index b182d1ecde..57f1f10b02 100644 --- a/modules/websocket/library_godot_websocket.js +++ b/modules/websocket/library_godot_websocket.js @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -101,6 +101,15 @@ const GodotWebSocket = { return 0; }, + // Get current bufferedAmount + bufferedAmount: function (p_id) { + const ref = IDHandler.get(p_id); + if (!ref) { + return 0; // Godot object is gone. + } + return ref.bufferedAmount; + }, + create: function (socket, p_on_open, p_on_message, p_on_error, p_on_close) { const id = IDHandler.add(socket); socket.onopen = GodotWebSocket._onopen.bind(null, id, p_on_open); @@ -126,7 +135,7 @@ const GodotWebSocket = { if (!ref) { return; } - GodotWebSocket.close(p_id, 1001, ''); + GodotWebSocket.close(p_id, 3001, 'destroyed'); IDHandler.remove(p_id); ref.onopen = null; ref.onmessage = null; @@ -171,6 +180,11 @@ const GodotWebSocket = { return GodotWebSocket.send(p_id, out); }, + godot_js_websocket_buffered_amount__sig: 'ii', + godot_js_websocket_buffered_amount: function (p_id) { + return GodotWebSocket.bufferedAmount(p_id); + }, + godot_js_websocket_close__sig: 'viii', godot_js_websocket_close: function (p_id, p_code, p_reason) { const code = p_code; diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h index ed756363cf..7b4a164576 100644 --- a/modules/websocket/packet_buffer.h +++ b/modules/websocket/packet_buffer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef PACKET_BUFFER_H #define PACKET_BUFFER_H -#include "core/os/copymem.h" #include "core/templates/ring_buffer.h" template <class T> @@ -66,7 +65,7 @@ public: if (p_info) { _Packet p; p.size = p_size; - copymem(&p.info, p_info, sizeof(T)); + memcpy(&p.info, p_info, sizeof(T)); _packets.write(p); } @@ -86,7 +85,7 @@ public: ERR_FAIL_COND_V(p_bytes < (int)p.size, ERR_OUT_OF_MEMORY); r_read = p.size; - copymem(r_info, &p.info, sizeof(T)); + memcpy(r_info, &p.info, sizeof(T)); _payload.read(r_payload, p.size); return OK; } diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 5a02509c4a..ff900f496f 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -63,7 +63,7 @@ void register_websocket_types() { WSLServer::make_default(); #endif - ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); + GDREGISTER_ABSTRACT_CLASS(WebSocketMultiplayerPeer); ClassDB::register_custom_instance_class<WebSocketServer>(); ClassDB::register_custom_instance_class<WebSocketClient>(); ClassDB::register_custom_instance_class<WebSocketPeer>(); diff --git a/modules/websocket/register_types.h b/modules/websocket/register_types.h index 3884db67b7..4ab6c0cfd3 100644 --- a/modules/websocket/register_types.h +++ b/modules/websocket/register_types.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp index c9591cc564..6319c3c664 100644 --- a/modules/websocket/remote_debugger_peer_websocket.cpp +++ b/modules/websocket/remote_debugger_peer_websocket.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h index 03c60fb480..ddf5425d81 100644 --- a/modules/websocket/remote_debugger_peer_websocket.h +++ b/modules/websocket/remote_debugger_peer_websocket.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCRIPT_DEBUGGER_WEBSOCKET_H -#define SCRIPT_DEBUGGER_WEBSOCKET_H +#ifndef REMOTE_DEBUGGER_PEER_WEBSOCKET_H +#define REMOTE_DEBUGGER_PEER_WEBSOCKET_H #ifdef JAVASCRIPT_ENABLED #include "modules/websocket/emws_client.h" @@ -62,4 +62,4 @@ public: RemoteDebuggerPeerWebSocket(Ref<WebSocketPeer> p_peer = Ref<WebSocketPeer>()); }; -#endif // SCRIPT_DEBUGGER_WEBSOCKET_H +#endif // REMOTE_DEBUGGER_PEER_WEBSOCKET_H diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index 425013f811..2734b4b88f 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,35 +42,22 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_proto _is_multiplayer = gd_mp_api; String host = p_url; - String path = "/"; - int p_len = -1; - int port = 80; + 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 ssl = false; - if (host.begins_with("wss://")) { - ssl = true; // we should implement this - host = host.substr(6, host.length() - 6); - port = 443; - } else { - ssl = false; - if (host.begins_with("ws://")) { - host = host.substr(5, host.length() - 5); - } + if (scheme == "wss://") { + ssl = true; } - - // Path - p_len = host.find("/"); - if (p_len != -1) { - path = host.substr(p_len, host.length() - p_len); - host = host.substr(0, p_len); + if (port == 0) { + port = ssl ? 443 : 80; } - - // Port - p_len = host.rfind(":"); - if (p_len != -1 && p_len == host.find(":")) { - port = host.substr(p_len, host.length() - p_len).to_int(); - host = host.substr(0, p_len); + if (path.is_empty()) { + path = "/"; } - return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers); } @@ -99,7 +86,7 @@ void WebSocketClient::_on_peer_packet() { if (_is_multiplayer) { _process_multiplayer(get_peer(1), 1); } else { - emit_signal("data_received"); + emit_signal(SNAME("data_received")); } } @@ -107,27 +94,27 @@ void WebSocketClient::_on_connect(String p_protocol) { if (_is_multiplayer) { // need to wait for ID confirmation... } else { - emit_signal("connection_established", p_protocol); + emit_signal(SNAME("connection_established"), p_protocol); } } void WebSocketClient::_on_close_request(int p_code, String p_reason) { - emit_signal("server_close_request", p_code, 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("connection_failed"); + emit_signal(SNAME("connection_failed")); } else { - emit_signal("connection_closed", p_was_clean); + emit_signal(SNAME("connection_closed"), p_was_clean); } } void WebSocketClient::_on_error() { if (_is_multiplayer) { - emit_signal("connection_failed"); + emit_signal(SNAME("connection_failed")); } else { - emit_signal("connection_error"); + emit_signal(SNAME("connection_error")); } } @@ -139,12 +126,12 @@ void WebSocketClient::_bind_methods() { ClassDB::bind_method(D_METHOD("set_verify_ssl_enabled", "enabled"), &WebSocketClient::set_verify_ssl_enabled); ClassDB::bind_method(D_METHOD("is_verify_ssl_enabled"), &WebSocketClient::is_verify_ssl_enabled); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); ClassDB::bind_method(D_METHOD("get_trusted_ssl_certificate"), &WebSocketClient::get_trusted_ssl_certificate); - ClassDB::bind_method(D_METHOD("set_trusted_ssl_certificate"), &WebSocketClient::set_trusted_ssl_certificate); + ClassDB::bind_method(D_METHOD("set_trusted_ssl_certificate", "cert"), &WebSocketClient::set_trusted_ssl_certificate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_trusted_ssl_certificate", "get_trusted_ssl_certificate"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_trusted_ssl_certificate", "get_trusted_ssl_certificate"); ADD_SIGNAL(MethodInfo("data_received")); ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h index 0225c9b3d3..d6c072ae16 100644 --- a/modules/websocket/websocket_client.h +++ b/modules/websocket/websocket_client.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -57,7 +57,7 @@ public: virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) = 0; virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0; - virtual IP_Address get_connected_host() const = 0; + virtual IPAddress get_connected_host() const = 0; virtual uint16_t get_connected_port() const = 0; virtual bool is_server() const override; diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h index d04909c97d..a01ae65c56 100644 --- a/modules/websocket/websocket_macros.h +++ b/modules/websocket/websocket_macros.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef WEBSOCKETMACTOS_H -#define WEBSOCKETMACTOS_H +#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 @@ -65,4 +65,4 @@ public:\ protected:\ /* clang-format on */ -#endif // WEBSOCKETMACTOS_H +#endif // WEBSOCKET_MACROS_H diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 011cb86535..7a3bbf1c47 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,35 +39,15 @@ WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() { _clear(); } -int WebSocketMultiplayerPeer::_gen_unique_id() const { - uint32_t hash = 0; - - while (hash == 0 || hash == 1) { - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_ticks_usec()); - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_unix_time(), hash); - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_data_path().hash64(), hash); - hash = hash_djb2_one_32( - (uint32_t)((uint64_t)this), hash); //rely on aslr heap - hash = hash_djb2_one_32( - (uint32_t)((uint64_t)&hash), hash); //rely on aslr stack - hash = hash & 0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion - } - - return hash; -} - void WebSocketMultiplayerPeer::_clear() { _peer_map.clear(); if (_current_packet.data != nullptr) { memfree(_current_packet.data); } - for (List<Packet>::Element *E = _incoming_packets.front(); E; E = E->next()) { - memfree(E->get().data); - E->get().data = nullptr; + for (Packet &E : _incoming_packets) { + memfree(E.data); + E.data = nullptr; } _incoming_packets.clear(); @@ -99,6 +79,8 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff _current_packet.data = nullptr; } + ERR_FAIL_COND_V(_incoming_packets.size() == 0, ERR_UNAVAILABLE); + _current_packet = _incoming_packets.front()->get(); _incoming_packets.pop_front(); @@ -121,17 +103,8 @@ Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer } // -// NetworkedMultiplayerPeer +// MultiplayerPeer // -void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { - // Websocket uses TCP, reliable -} - -NetworkedMultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { - // Websocket uses TCP, reliable - return TRANSFER_MODE_RELIABLE; -} - void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { _target_peer = p_target_peer; } @@ -147,14 +120,6 @@ int WebSocketMultiplayerPeer::get_unique_id() const { return _peer_id; } -void WebSocketMultiplayerPeer::set_refuse_new_connections(bool p_enable) { - _refusing = p_enable; -} - -bool WebSocketMultiplayerPeer::is_refusing_new_connections() const { - return _refusing; -} - void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> 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()); @@ -168,10 +133,10 @@ Vector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_fr out.resize(PROTO_SIZE + p_data_size); uint8_t *w = out.ptrw(); - copymem(&w[0], &p_type, 1); - copymem(&w[1], &p_from, 4); - copymem(&w[5], &p_to, 4); - copymem(&w[PROTO_SIZE], p_data, p_data_size); + memcpy(&w[0], &p_type, 1); + memcpy(&w[1], &p_from, 4); + memcpy(&w[5], &p_to, 4); + memcpy(&w[PROTO_SIZE], p_data, p_data_size); return out; } @@ -183,10 +148,10 @@ void WebSocketMultiplayerPeer::_send_add(int32_t 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); - for (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - int32_t id = E->key(); + for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + int32_t id = E.key; if (p_peer_id == id) { - continue; // Skip the newwly added peer (already confirmed) + continue; // Skip the newly added peer (already confirmed) } // Send new peer to others @@ -197,8 +162,8 @@ void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) { } void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) { - for (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - int32_t id = E->key(); + for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + int32_t id = E.key; if (p_peer_id != id) { _send_sys(get_peer(id), SYS_DEL, p_peer_id); } @@ -211,9 +176,9 @@ void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, cons packet.size = p_data_size; packet.source = p_source; packet.destination = p_dest; - copymem(packet.data, &p_data[PROTO_SIZE], p_data_size); + memcpy(packet.data, &p_data[PROTO_SIZE], p_data_size); _incoming_packets.push_back(packet); - emit_signal("peer_packet", p_source); + emit_signal(SNAME("peer_packet"), p_source); } Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) { @@ -221,17 +186,17 @@ 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 (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - if (E->key() != p_from) { - E->get()->put_packet(p_buffer, p_buffer_size); + for (KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + if (E.key != p_from) { + E.value->put_packet(p_buffer, p_buffer_size); } } return OK; // Sent to all but sender } else if (p_to < 0) { - for (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - if (E->key() != p_from && E->key() != -p_to) { - E->get()->put_packet(p_buffer, p_buffer_size); + for (KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + if (E.key != p_from && E.key != -p_to) { + E.value->put_packet(p_buffer, p_buffer_size); } } return OK; // Sent to all but sender and excluded @@ -263,9 +228,9 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u uint8_t type = 0; uint32_t from = 0; int32_t to = 0; - copymem(&type, in_buffer, 1); - copymem(&from, &in_buffer[1], 4); - copymem(&to, &in_buffer[5], 4); + memcpy(&type, in_buffer, 1); + memcpy(&from, &in_buffer[1], 4); + memcpy(&to, &in_buffer[5], 4); if (is_server()) { // Server can resend @@ -299,22 +264,22 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u // System message ERR_FAIL_COND(data_size < 4); int id = 0; - copymem(&id, &in_buffer[PROTO_SIZE], 4); + memcpy(&id, &in_buffer[PROTO_SIZE], 4); switch (type) { case SYS_ADD: // Add peer _peer_map[id] = Ref<WebSocketPeer>(); - emit_signal("peer_connected", id); + emit_signal(SNAME("peer_connected"), id); if (id == 1) { // We just connected to the server - emit_signal("connection_succeeded"); + emit_signal(SNAME("connection_succeeded")); } break; case SYS_DEL: // Remove peer _peer_map.erase(id); - emit_signal("peer_disconnected", id); + emit_signal(SNAME("peer_disconnected"), id); break; - case SYS_ID: // Helo, server assigned ID + case SYS_ID: // Hello, server assigned ID _peer_id = id; break; default: diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 48a6607d89..43d9d59f38 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,12 +32,12 @@ #define WEBSOCKET_MULTIPLAYER_PEER_H #include "core/error/error_list.h" -#include "core/io/networked_multiplayer_peer.h" +#include "core/multiplayer/multiplayer_peer.h" #include "core/templates/list.h" #include "websocket_peer.h" -class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer { - GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer); +class WebSocketMultiplayerPeer : public MultiplayerPeer { + GDCLASS(WebSocketMultiplayerPeer, MultiplayerPeer); private: Vector<uint8_t> _make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); @@ -68,24 +68,18 @@ protected: bool _is_multiplayer = false; int _target_peer = 0; int _peer_id = 0; - int _refusing = false; static void _bind_methods(); void _send_add(int32_t p_peer_id); void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id); void _send_del(int32_t p_peer_id); - int _gen_unique_id() const; public: - /* NetworkedMultiplayerPeer */ - void set_transfer_mode(TransferMode p_mode) override; - TransferMode get_transfer_mode() const override; + /* MultiplayerPeer */ void set_target_peer(int p_target_peer) override; int get_packet_peer() const override; int get_unique_id() const override; - void set_refuse_new_connections(bool p_enable) override; - bool is_refusing_new_connections() const override; /* PacketPeer */ virtual int get_available_packet_count() const override; diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index e77fdcfed2..a0af9303b8 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -47,6 +47,7 @@ void WebSocketPeer::_bind_methods() { 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("set_no_delay", "enabled"), &WebSocketPeer::set_no_delay); + ClassDB::bind_method(D_METHOD("get_current_outbound_buffered_amount"), &WebSocketPeer::get_current_outbound_buffered_amount); BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 2ba83637f9..13fef2424f 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -55,10 +55,11 @@ public: virtual void close(int p_code = 1000, String p_reason = "") = 0; virtual bool is_connected_to_host() const = 0; - virtual IP_Address get_connected_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; WebSocketPeer(); ~WebSocketPeer(); diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index f57e8d959c..b3f0140b80 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,7 @@ GDCINULL(WebSocketServer); WebSocketServer::WebSocketServer() { _peer_id = 1; - bind_ip = IP_Address("*"); + bind_ip = IPAddress("*"); } WebSocketServer::~WebSocketServer() { @@ -50,32 +50,36 @@ void WebSocketServer::_bind_methods() { 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"), &WebSocketServer::set_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"), &WebSocketServer::set_private_key); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", 0), "set_private_key", "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_ssl_certificate"), &WebSocketServer::get_ssl_certificate); - ClassDB::bind_method(D_METHOD("set_ssl_certificate"), &WebSocketServer::set_ssl_certificate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ssl_certificate", "get_ssl_certificate"); + ClassDB::bind_method(D_METHOD("set_ssl_certificate", "cert"), &WebSocketServer::set_ssl_certificate); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_ssl_certificate", "get_ssl_certificate"); ClassDB::bind_method(D_METHOD("get_ca_chain"), &WebSocketServer::get_ca_chain); - ClassDB::bind_method(D_METHOD("set_ca_chain"), &WebSocketServer::set_ca_chain); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ca_chain", "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"))); + 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"))); } -IP_Address WebSocketServer::get_bind_ip() const { +IPAddress WebSocketServer::get_bind_ip() const { return bind_ip; } -void WebSocketServer::set_bind_ip(const IP_Address &p_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; @@ -108,7 +112,16 @@ void WebSocketServer::set_ca_chain(Ref<X509Certificate> p_ca_chain) { ca_chain = p_ca_chain; } -NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { +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; } @@ -124,17 +137,17 @@ 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("data_received", p_peer_id); + emit_signal(SNAME("data_received"), p_peer_id); } } -void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol) { +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("peer_connected", p_peer_id); + emit_signal(SNAME("peer_connected"), p_peer_id); } else { - emit_signal("client_connected", p_peer_id, p_protocol); + emit_signal(SNAME("client_connected"), p_peer_id, p_protocol, p_resource_name); } } @@ -142,12 +155,12 @@ 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("peer_disconnected", p_peer_id); + emit_signal(SNAME("peer_disconnected"), p_peer_id); } else { - emit_signal("client_disconnected", p_peer_id, p_was_clean); + 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("client_close_request", p_peer_id, p_code, 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 index 3fbd5e3b95..f6f3b80045 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,7 @@ #define WEBSOCKET_H #include "core/crypto/crypto.h" -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "websocket_multiplayer_peer.h" #include "websocket_peer.h" @@ -40,7 +40,7 @@ class WebSocketServer : public WebSocketMultiplayerPeer { GDCLASS(WebSocketServer, WebSocketMultiplayerPeer); GDCICLASS(WebSocketServer); - IP_Address bind_ip; + IPAddress bind_ip; protected: static void _bind_methods(); @@ -48,6 +48,7 @@ protected: Ref<CryptoKey> private_key; Ref<X509Certificate> ssl_cert; Ref<X509Certificate> ca_chain; + uint32_t handshake_timeout = 3000; public: virtual Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) = 0; @@ -57,17 +58,17 @@ public: virtual bool is_server() const override; ConnectionStatus get_connection_status() const override; - virtual IP_Address get_peer_address(int p_peer_id) const = 0; + 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); + 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); - IP_Address get_bind_ip() const; - void set_bind_ip(const IP_Address &p_bind_ip); + IPAddress get_bind_ip() const; + void set_bind_ip(const IPAddress &p_bind_ip); Ref<CryptoKey> get_private_key() const; void set_private_key(Ref<CryptoKey> p_key); @@ -78,6 +79,9 @@ public: Ref<X509Certificate> get_ca_chain() const; void set_ca_chain(Ref<X509Certificate> p_ca_chain); + float get_handshake_timeout() const; + void set_handshake_timeout(float p_timeout); + WebSocketServer(); ~WebSocketServer(); }; diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index a075ae3982..1ef571b6ee 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -124,17 +124,17 @@ bool WSLClient::_verify_headers(String &r_protocol) { } } -#define _WSL_CHECK(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) \ +#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 + 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(headers.has("sec-websocket-protocol"), false); @@ -158,24 +158,33 @@ bool WSLClient::_verify_headers(String &r_protocol) { Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> 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<WSLPeer>(memnew(WSLPeer)); - IP_Address addr; - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); + if (p_host.is_valid_ip_address()) { + _ip_candidates.push_back(IPAddress(p_host)); } else { - addr = p_host; + // 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; + } } - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); - - String port = ""; - if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { - port = ":" + itos(p_port); + // 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; + } } - - Error err = _tcp->connect_to_host(addr, p_port); if (err != OK) { _tcp->disconnect_from_host(); _on_error(); @@ -184,6 +193,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _connection = _tcp; _use_ssl = p_ssl; _host = p_host; + _port = p_port; // Strip edges from protocols. _protocols.resize(p_protocols.size()); String *pw = _protocols.ptrw(); @@ -192,8 +202,11 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } _key = WSLPeer::generate_key(); - // TODO custom extra headers (allow overriding this too?) String request = "GET " + p_path + " HTTP/1.1\r\n"; + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } request += "Host: " + p_host + port + "\r\n"; request += "Upgrade: websocket\r\n"; request += "Connection: Upgrade\r\n"; @@ -223,6 +236,30 @@ int WSLClient::get_max_packet_size() const { } 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()) { @@ -243,6 +280,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { + _ip_candidates.clear(); Ref<StreamPeerSSL> ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -273,6 +311,12 @@ void WSLClient::poll() { _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; @@ -287,12 +331,12 @@ Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const { return _peer; } -NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { +MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { if (_peer->is_connected_to_host()) { return CONNECTION_CONNECTED; } - if (_tcp->is_connected_to_host()) { + if (_tcp->is_connected_to_host() || _resolver_id != IP::RESOLVER_INVALID_ID) { return CONNECTION_CONNECTING; } @@ -314,10 +358,17 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { 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(); } -IP_Address WSLClient::get_connected_host() const { - ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IP_Address()); +IPAddress WSLClient::get_connected_host() const { + ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IPAddress()); return _peer->get_connected_host(); } @@ -337,8 +388,8 @@ Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer } WSLClient::WSLClient() { - _peer.instance(); - _tcp.instance(); + _peer.instantiate(); + _tcp.instantiate(); disconnect_from_host(); } diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index e7c91ed333..d846e6be00 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -63,8 +63,11 @@ private: String _key; String _host; + uint16_t _port; + Array _ip_candidates; Vector<String> _protocols; bool _use_ssl = false; + IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID; void _do_handshake(); bool _verify_headers(String &r_protocol); @@ -75,7 +78,7 @@ public: int get_max_packet_size() const; Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); - IP_Address get_connected_host() const; + IPAddress get_connected_host() const; uint16_t get_connected_port() const; virtual ConnectionStatus get_connection_status() const; virtual void poll(); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index dbbf86d0da..d277eedace 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -205,7 +205,9 @@ void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigne ERR_FAIL_COND(p_data == nullptr); _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_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; @@ -239,14 +241,16 @@ void WSLPeer::poll() { 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); - struct wslay_event_msg msg; // Should I use fragmented? + struct wslay_event_msg msg; msg.opcode = write_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME; msg.msg = p_buffer; msg.msg_length = p_buffer_size; - wslay_event_queue_msg(_data->ctx, &msg); - if (wslay_event_send(_data->ctx) < 0) { + // Queue & send message. + if (wslay_event_queue_msg(_data->ctx, &msg) != 0 || wslay_event_send(_data->ctx) != 0) { close_now(); return FAILED; } @@ -280,6 +284,12 @@ int WSLPeer::get_available_packet_count() const { return _in_buffer.packets_left(); } +int WSLPeer::get_current_outbound_buffered_amount() const { + ERR_FAIL_COND_V(!_data, 0); + + return wslay_event_get_queued_msg_length(_data->ctx); +} + bool WSLPeer::was_string_packet() const { return _is_string; } @@ -305,8 +315,8 @@ void WSLPeer::close(int p_code, String p_reason) { _packet_buffer.resize(0); } -IP_Address WSLPeer::get_connected_host() const { - ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), IP_Address()); +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(); } diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index 5e6a7e8554..555559c6e1 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -77,6 +77,9 @@ private: WriteMode write_mode = WRITE_MODE_BINARY; + int _out_buf_size = 0; + int _out_pkt_size = 0; + public: int close_code = -1; String close_reason; @@ -86,11 +89,12 @@ public: virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); virtual int get_max_packet_size() const { return _packet_buffer.size(); }; + virtual int get_current_outbound_buffered_amount() const; virtual void close_now(); virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; - virtual IP_Address get_connected_host() const; + virtual IPAddress get_connected_host() const; virtual uint16_t get_connected_port() const; virtual WriteMode get_write_mode() const; diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 437eb2061b..eadd7ef7ac 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,7 @@ #include "core/config/project_settings.h" #include "core/os/os.h" -bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { +bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols, String &r_resource_name) { Vector<String> 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."); @@ -45,6 +45,7 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { // 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]; Map<String, String> headers; for (int i = 1; i < len; i++) { Vector<String> header = psa[i].split(":", false, 1); @@ -57,17 +58,17 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { headers[name] = value; } } -#define _WSL_CHECK(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) \ +#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 + 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<String> protos = headers["sec-websocket-protocol"].split(","); @@ -82,11 +83,11 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { break; } // Found a protocol - if (protocol != "") { + if (!protocol.is_empty()) { break; } } - if (protocol == "") { // Invalid protocol(s) requested + if (protocol.is_empty()) { // Invalid protocol(s) requested return false; } } else if (p_protocols.size() > 0) { // No protocol requested, but we need one @@ -95,28 +96,33 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { return true; } -Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) { - if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) { +Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols, uint64_t p_timeout, String &r_resource_name) { + 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_ssl) { Ref<StreamPeerSSL> ssl = static_cast<Ref<StreamPeerSSL>>(connection); if (ssl.is_null()) { - return FAILED; + ERR_FAIL_V_MSG(ERR_BUG, "Couldn't get StreamPeerSSL for WebSocket handshake."); } ssl->poll(); if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) { return ERR_BUSY; } else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + print_verbose(vformat("WebSocket SSL connection error during handshake (StreamPeerSSL status code %d).", ssl->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, "Response headers too big."); + 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; @@ -125,14 +131,14 @@ Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) { 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)) { + 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 != "") { + if (!protocol.is_empty()) { s += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; } s += "\r\n"; @@ -143,17 +149,21 @@ Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) { 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; } @@ -172,23 +182,24 @@ Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp void WSLServer::poll() { List<int> remove_ids; - for (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + Ref<WSLPeer> peer = (WSLPeer *)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()); + _on_disconnect(E.key, peer->close_code != -1); + remove_ids.push_back(E.key); } } - for (List<int>::Element *E = remove_ids.front(); E; E = E->next()) { - _peer_map.erase(E->get()); + for (int &E : remove_ids) { + _peer_map.erase(E); } remove_ids.clear(); List<Ref<PendingPeer>> remove_peers; - for (List<Ref<PendingPeer>>::Element *E = _pending.front(); E; E = E->next()) { - Ref<PendingPeer> ppeer = E->get(); - Error err = ppeer->do_handshake(_protocols); + for (const Ref<PendingPeer> &E : _pending) { + String resource_name; + Ref<PendingPeer> ppeer = E; + Error err = ppeer->do_handshake(_protocols, handshake_timeout, resource_name); if (err == ERR_BUSY) { continue; } else if (err != OK) { @@ -196,7 +207,7 @@ void WSLServer::poll() { continue; } // Creating new peer - int32_t id = _gen_unique_id(); + int32_t id = generate_unique_id(); WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); data->obj = this; @@ -211,10 +222,10 @@ void WSLServer::poll() { _peer_map[id] = ws_peer; remove_peers.push_back(ppeer); - _on_connect(id, ppeer->protocol); + _on_connect(id, ppeer->protocol, resource_name); } - for (List<Ref<PendingPeer>>::Element *E = remove_peers.front(); E; E = E->next()) { - _pending.erase(E->get()); + for (const Ref<PendingPeer> &E : remove_peers) { + _pending.erase(E); } remove_peers.clear(); @@ -254,8 +265,8 @@ int WSLServer::get_max_packet_size() const { void WSLServer::stop() { _server->stop(); - for (Map<int, Ref<WebSocketPeer>>::Element *E = _peer_map.front(); E; E = E->next()) { - Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + for (const KeyValue<int, Ref<WebSocketPeer>> &E : _peer_map) { + Ref<WSLPeer> peer = (WSLPeer *)E.value.ptr(); peer->close_now(); } _pending.clear(); @@ -272,8 +283,8 @@ Ref<WebSocketPeer> WSLServer::get_peer(int p_id) const { return _peer_map[p_id]; } -IP_Address WSLServer::get_peer_address(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); +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(); } @@ -301,7 +312,7 @@ Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer } WSLServer::WSLServer() { - _server.instance(); + _server.instantiate(); } WSLServer::~WSLServer() { diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h index 75669e12ee..221cae4793 100644 --- a/modules/websocket/wsl_server.h +++ b/modules/websocket/wsl_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -40,22 +40,20 @@ #include "core/io/stream_peer_tcp.h" #include "core/io/tcp_server.h" -#define WSL_SERVER_TIMEOUT 1000 - class WSLServer : public WebSocketServer { GDCIIMPL(WSLServer, WebSocketServer); private: - class PendingPeer : public Reference { + class PendingPeer : public RefCounted { private: - bool _parse_request(const Vector<String> p_protocols); + bool _parse_request(const Vector<String> p_protocols, String &r_resource_name); public: Ref<StreamPeerTCP> tcp; Ref<StreamPeer> connection; bool use_ssl = false; - int time = 0; + uint64_t time = 0; uint8_t req_buf[WSL_MAX_HEADER_SIZE] = {}; int req_pos = 0; String key; @@ -64,7 +62,7 @@ private: CharString response; int response_sent = 0; - Error do_handshake(const Vector<String> p_protocols); + Error do_handshake(const Vector<String> p_protocols, uint64_t p_timeout, String &r_resource_name); }; int _in_buf_size = DEF_BUF_SHIFT; @@ -73,7 +71,7 @@ private: int _out_pkt_size = DEF_PKT_SHIFT; List<Ref<PendingPeer>> _pending; - Ref<TCP_Server> _server; + Ref<TCPServer> _server; Vector<String> _protocols; public: @@ -84,7 +82,7 @@ public: int get_max_packet_size() const; bool has_peer(int p_id) const; Ref<WebSocketPeer> get_peer(int p_id) const; - IP_Address get_peer_address(int p_peer_id) const; + 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 = ""); virtual void poll(); |