diff options
Diffstat (limited to 'modules/websocket')
20 files changed, 1169 insertions, 1206 deletions
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index d9e60eb6f1..033169411f 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -5,89 +5,26 @@ Import('env_modules') # Thirdparty source files -env_lws = env_modules.Clone() - -if env['builtin_libwebsockets'] and not env["platform"] == "javascript": # already builtin for javascript - thirdparty_dir = "#thirdparty/libwebsockets/" - helper_dir = "win32helpers/" - thirdparty_sources = [ - - "core/alloc.c", - "core/context.c", - "core/libwebsockets.c", - "core/output.c", - "core/pollfd.c", - "core/service.c", - - "event-libs/poll/poll.c", - - "misc/base64-decode.c", - "misc/lejp.c", - "misc/sha-1.c", - - "roles/h1/ops-h1.c", - "roles/http/header.c", - "roles/http/client/client.c", - "roles/http/client/client-handshake.c", - "roles/http/server/fops-zip.c", - "roles/http/server/lejp-conf.c", - "roles/http/server/parsers.c", - "roles/http/server/server.c", - "roles/listen/ops-listen.c", - "roles/pipe/ops-pipe.c", - "roles/raw/ops-raw.c", - - "roles/ws/client-ws.c", - "roles/ws/client-parser-ws.c", - "roles/ws/ops-ws.c", - "roles/ws/server-ws.c", - - "tls/tls.c", - "tls/tls-client.c", - "tls/tls-server.c", - - "tls/mbedtls/wrapper/library/ssl_cert.c", - "tls/mbedtls/wrapper/library/ssl_pkey.c", - "tls/mbedtls/wrapper/library/ssl_stack.c", - "tls/mbedtls/wrapper/library/ssl_methods.c", - "tls/mbedtls/wrapper/library/ssl_lib.c", - "tls/mbedtls/wrapper/library/ssl_x509.c", - "tls/mbedtls/wrapper/platform/ssl_port.c", - "tls/mbedtls/wrapper/platform/ssl_pm.c", - "tls/mbedtls/lws-genhash.c", - "tls/mbedtls/mbedtls-client.c", - "tls/mbedtls/lws-genrsa.c", - "tls/mbedtls/ssl.c", - "tls/mbedtls/mbedtls-server.c" +env_ws = env_modules.Clone() + +if env['builtin_wslay'] and not env["platform"] == "javascript": # already builtin for javascript + wslay_dir = "#thirdparty/wslay/" + wslay_sources = [ + "wslay_net.c", + "wslay_event.c", + "wslay_queue.c", + "wslay_stack.c", + "wslay_frame.c", ] - - if env["platform"] == "android": # Builtin getifaddrs - thirdparty_sources += ["misc/getifaddrs.c"] - - if env["platform"] == "windows" or env["platform"] == "uwp": # Winsock - thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"] - else: # Unix socket - thirdparty_sources += ["plat/lws-plat-unix.c"] - - thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - - env_lws.Prepend(CPPPATH=[thirdparty_dir]) - - if env['builtin_mbedtls']: - mbedtls_includes = "#thirdparty/mbedtls/include" - env_lws.Prepend(CPPPATH=[mbedtls_includes]) - - wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] - env_lws.Prepend(CPPPATH=wrapper_includes) - + wslay_sources = [wslay_dir + s for s in wslay_sources] + env_ws.Prepend(CPPPATH=[wslay_dir + "includes/"]) + env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"]) if env["platform"] == "windows" or env["platform"] == "uwp": - env_lws.Prepend(CPPPATH=[thirdparty_dir + helper_dir]) - - if env["platform"] == "uwp": - env_lws.Append(CPPFLAGS=["/DLWS_MINGW_SUPPORT"]) - - env_thirdparty = env_lws.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) - -env_lws.add_source_files(env.modules_sources, "*.cpp") + env_ws.Append(CPPDEFINES=["HAVE_WINSOCK2_H"]) + else: + env_ws.Append(CPPDEFINES=["HAVE_NETINET_IN_H"]) + env_wslay = env_ws.Clone() + env_wslay.disable_warnings() + env_wslay.add_source_files(env.modules_sources, wslay_sources) + +env_ws.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 26122dfad4..c3baf9de83 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" category="Core" version="3.2"> <brief_description> - A WebSocket client implementation + A WebSocket client implementation. </brief_description> <description> - This class implements a WebSocket client compatible with any RFC 6455 complaint WebSocket server. + 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]). - You will received appropriate signals when connecting, disconnecting, or when new data is available. + You will receive appropriate signals when connecting, disconnecting, or when new data is available. </description> <tutorials> </tutorials> @@ -22,8 +22,8 @@ <argument index="2" name="gd_mp_api" type="bool" default="false"> </argument> <description> - Connect to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. - 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. + 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]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]). </description> </method> @@ -35,13 +35,14 @@ <argument index="1" name="reason" type="String" default=""""> </argument> <description> - Disconnect this client from the connected host. See [method WebSocketPeer.close] for more info. + Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information. </description> </method> </methods> <members> <member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled"> - Enable or disable SSL certificate verification. Note: You must specify the certificates to be used in the project settings for it to work when exported. + If [code]true[/code], SSL certificate verification is enabled. + [b]Note:[/b] You must specify the certificates to be used in the Project Settings for it to work when exported. </member> </members> <signals> @@ -66,7 +67,8 @@ </signal> <signal name="data_received"> <description> - Emitted when a WebSocket message is received. Note: This signal is NOT emitted when used as high level multiplayer peer. + Emitted when a WebSocket message is received. + [b]Note:[/b] This signal is [i]not[/i] emitted when used as high-level multiplayer peer. </description> </signal> <signal name="server_close_request"> diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml index 103dbd771b..7070cfbdab 100644 --- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -30,19 +30,24 @@ <argument index="3" name="output_max_packets" type="int"> </argument> <description> - Configure the buffers sizes for this WebSocket peer. Default values can be specified in project settings under [code]network/limits[/code]. For server, values are meant per connected peer. + Configures the buffer sizes for this WebSocket peer. Default values can be specified in the Project Settings under [code]network/limits[/code]. For server, values are meant per connected peer. The first two parameters define the size and queued packets limits of the input buffer, the last two of the output buffer. Buffer sizes are expressed in KiB, so [code]4 = 2^12 = 4096 bytes[/code]. All parameters will be rounded up to the nearest power of two. - NOTE: HTML5 exports only use the input buffer since the output one is managed by browsers. + [b]Note:[/b] HTML5 exports only use the input buffer since the output one is managed by browsers. </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> <description> - Emitted when a packet is received from a peer. Note: this signal is only emitted when the client or server is configured to use Godot multiplayer API. + 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> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 8a6868d311..dd95f7432e 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -18,30 +18,32 @@ <argument index="1" name="reason" type="String" default=""""> </argument> <description> - Close this WebSocket connection. [code]code[/code] is the status code for the closure (see RFC6455 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 UTF8 string, must be less than 123 bytes). - Note: To achieve a clean close, you will need to keep polling until either [signal WebSocketClient.connection_closed] or [signal WebSocketServer.client_disconnected] is received. - Note: HTML5 export might not support all status codes. Please refer to browsers-specific documentation for more details. + 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. + [b]Note:[/b] The HTML5 export might not support all status codes. Please refer to browser-specific documentation for more details. </description> </method> <method name="get_connected_host" qualifiers="const"> <return type="String"> </return> <description> - Returns the IP Address of the connected peer. (Not available in HTML5 export) + 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> <description> - Returns the remote port of the connected peer. (Not available in HTML5 export) + Returns the remote port of the connected peer. + [b]Note:[/b] Not available in the HTML5 export. </description> </method> <method name="get_write_mode" qualifiers="const"> <return type="int" enum="WebSocketPeer.WriteMode"> </return> <description> - Get the current selected write mode. See [enum WriteMode]. + Gets the current selected write mode. See [enum WriteMode]. </description> </method> <method name="is_connected_to_host" qualifiers="const"> @@ -70,10 +72,10 @@ </methods> <constants> <constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode"> - Specify that WebSockets messages should be transferred as text payload (only valid UTF-8 is allowed). + Specifies that WebSockets messages should be transferred as text payload (only valid UTF-8 is allowed). </constant> <constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode"> - Specify that WebSockets messages should be transferred as binary payload (any byte combination is allowed). + Specifies that WebSockets messages should be transferred as binary payload (any byte combination is allowed). </constant> </constants> </class> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 51945d410a..63318e5874 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" category="Core" version="3.2"> <brief_description> - A WebSocket server implementation + A WebSocket server implementation. </brief_description> <description> - This class implements a WebSocket server that can also support the high level multiplayer API. + 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. - Note: This class will not work in HTML5 exports due to browser restrictions. + [b]Note:[/b] This class will not work in HTML5 exports due to browser restrictions. </description> <tutorials> </tutorials> @@ -21,7 +21,7 @@ <argument index="2" name="reason" type="String" default=""""> </argument> <description> - Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more info. + 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"> @@ -68,17 +68,17 @@ <argument index="2" name="gd_mp_api" type="bool" default="false"> </argument> <description> - Start listening on the given port. - You can specify the desired subprotocols via the "protocols" array. If the list empty (default), "binary" will be used. - 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]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]). + 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]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> <description> - Stop the server and clear its state. + Stops the server and clear its state. </description> </method> </methods> @@ -116,7 +116,8 @@ <argument index="0" name="id" type="int"> </argument> <description> - Emitted when a new message is received. Note: This signal is NOT emitted when used as high level multiplayer peer. + 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> diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index a9f38f1a82..58d4c52688 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -131,14 +131,12 @@ void EMWSPeer::close(int p_code, String p_reason) { IP_Address EMWSPeer::get_connected_host() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(IP_Address()); + ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); }; uint16_t EMWSPeer::get_connected_port() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(0); + ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); }; EMWSPeer::EMWSPeer() { diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp deleted file mode 100644 index f139a4ef66..0000000000 --- a/modules/websocket/lws_client.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************/ -/* lws_client.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_client.h" -#include "core/io/ip.h" -#include "core/project_settings.h" -#if defined(LWS_OPENSSL_SUPPORT) -#include "core/io/stream_peer_ssl.h" -#include "tls/mbedtls/wrapper/include/openssl/ssl.h" -#endif - -Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - IP_Address addr; - - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); - } else { - addr = p_host; - } - - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); - - // Prepare protocols - _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); - - // Init lws client - struct lws_context_creation_info info; - struct lws_client_connect_info i; - - memset(&i, 0, sizeof i); - memset(&info, 0, sizeof info); - - info.port = CONTEXT_PORT_NO_LISTEN; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - info.user = _lws_ref; -#if defined(LWS_OPENSSL_SUPPORT) - info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; -#endif - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create lws context"); - ERR_FAIL_V(FAILED); - } - - i.context = context; - if (p_protocols.size() > 0) - i.protocol = _lws_ref->lws_names; - else - i.protocol = NULL; - - if (p_ssl) { - i.ssl_connection = LCCSCF_USE_SSL; - if (!verify_ssl) - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - } else { - i.ssl_connection = 0; - } - - // These CharStrings needs to survive till we call lws_client_connect_via_info - CharString addr_ch = ((String)addr).ascii(); - CharString host_ch = p_host.utf8(); - CharString path_ch = p_path.utf8(); - i.address = addr_ch.get_data(); - i.host = host_ch.get_data(); - i.path = path_ch.get_data(); - i.port = p_port; - - lws_client_connect_via_info(&i); - - return OK; -}; - -int LWSClient::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -void LWSClient::poll() { - - _lws_poll(); -} - -int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer); - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { -#if defined(LWS_OPENSSL_SUPPORT) - case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: { - PoolByteArray arr = StreamPeerSSL::get_project_cert_array(); - if (arr.size() > 0) - SSL_CTX_add_client_CA((SSL_CTX *)user, d2i_X509(NULL, &arr.read()[0], arr.size())); - else if (verify_ssl) - WARN_PRINTS("No CA cert specified in project settings, SSL will not work"); - } break; -#endif - case LWS_CALLBACK_CLIENT_ESTABLISHED: - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - peer_data->peer_id = 0; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(lws_get_protocol(wsi)->name); - break; - - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - _on_error(); - destroy_context(); - return -1; // We should close the connection (would probably happen anyway) - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - int code; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(code, reason2); - return 0; - } - - case LWS_CALLBACK_CLIENT_CLOSED: - peer->close(); - destroy_context(); - _on_disconnect(peer_data->clean_close); - return 0; // We can end here - - case LWS_CALLBACK_CLIENT_RECEIVE: - peer->read_wsi(in, len); - if (peer->get_available_packet_count() > 0) - _on_peer_packet(); - break; - - case LWS_CALLBACK_CLIENT_WRITEABLE: - if (peer_data->force_close) { - peer->send_close_status(wsi); - return -1; - } - - peer->write_wsi(); - break; - - default: - break; - } - - return 0; -} - -Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const { - - return _peer; -} - -NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const { - - if (context == NULL) - return CONNECTION_DISCONNECTED; - - if (_peer->is_connected_to_host()) - return CONNECTION_CONNECTED; - - return CONNECTION_CONNECTING; -} - -void LWSClient::disconnect_from_host(int p_code, String p_reason) { - - if (context == NULL) - return; - - _peer->close(p_code, p_reason); -}; - -IP_Address LWSClient::get_connected_host() const { - - return IP_Address(); -}; - -uint16_t LWSClient::get_connected_port() const { - - return 1025; -}; - -Error LWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSClient::LWSClient() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); - - context = NULL; - _lws_ref = NULL; - _peer = Ref<LWSPeer>(memnew(LWSPeer)); -}; - -LWSClient::~LWSClient() { - - invalidate_lws_ref(); // We do not want any more callback - disconnect_from_host(); - destroy_context(); - _peer = Ref<LWSPeer>(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_helper.cpp b/modules/websocket/lws_helper.cpp deleted file mode 100644 index a652779960..0000000000 --- a/modules/websocket/lws_helper.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/*************************************************************************/ -/* lws_helper.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#if !defined(JAVASCRIPT_ENABLED) - -#include "lws_helper.h" - -_LWSRef *_lws_create_ref(void *obj) { - - _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef)); - out->is_destroying = false; - out->free_context = false; - out->is_polling = false; - out->obj = obj; - out->is_valid = true; - out->lws_structs = NULL; - out->lws_names = NULL; - return out; -} - -void _lws_free_ref(_LWSRef *ref) { - // Free strings and structs - memfree(ref->lws_structs); - memfree(ref->lws_names); - // Free ref - memfree(ref); -} - -bool _lws_destroy(struct lws_context *context, _LWSRef *ref) { - if (context == NULL || ref->is_destroying) - return false; - - if (ref->is_polling) { - ref->free_context = true; - return false; - } - - ref->is_destroying = true; - lws_context_destroy(context); - _lws_free_ref(ref); - return true; -} - -bool _lws_poll(struct lws_context *context, _LWSRef *ref) { - - ERR_FAIL_COND_V(context == NULL, false); - ERR_FAIL_COND_V(ref == NULL, false); - - ref->is_polling = true; - lws_service(context, 0); - ref->is_polling = false; - - if (!ref->free_context) - return false; // Nothing to do - - bool is_valid = ref->is_valid; // Might have been destroyed by poll - - _lws_destroy(context, ref); // Will destroy context and ref - - return is_valid; // If the object should NULL its context and ref -} - -/* - * Prepare the protocol_structs to be fed to context. - * Also prepare the protocol string used by the client. - */ -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) { - // The input strings might go away after this call, we need to copy them. - // We will clear them when destroying the context. - int i; - int len = p_names.size(); - size_t data_size = sizeof(struct LWSPeer::PeerData); - PoolVector<String>::Read pnr = p_names.read(); - - // This is a reference connecting the object with lws keep track of status, mallocs, etc. - // Must survive as long the context. - // Must be freed manually when context creation fails. - _LWSRef *ref = _lws_create_ref(p_obj); - - // LWS protocol structs. - ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); - memset(ref->lws_structs, 0, sizeof(struct lws_protocols) * (len + 2)); - - CharString strings = p_names.join(",").ascii(); - int str_len = strings.length(); - - // Joined string of protocols, double the size: comma separated first, NULL separated last - ref->lws_names = (char *)memalloc((str_len + 1) * 2); // Plus the terminator - - char *names_ptr = ref->lws_names; - struct lws_protocols *structs_ptr = ref->lws_structs; - - // Comma separated protocols string to be used in client Sec-WebSocket-Protocol header - if (str_len > 0) - copymem(names_ptr, strings.get_data(), str_len); - names_ptr[str_len] = '\0'; // NULL terminator - - // NULL terminated protocol strings to be used in protocol structs - if (str_len > 0) - copymem(&names_ptr[str_len + 1], strings.get_data(), str_len); - names_ptr[(str_len * 2) + 1] = '\0'; // NULL terminator - int pos = str_len + 1; - - // The first protocol is the default for any http request (before upgrade). - // It is also used as the websocket protocol when no subprotocol is specified. - structs_ptr[0].name = "default"; - structs_ptr[0].callback = p_callback; - structs_ptr[0].per_session_data_size = data_size; - structs_ptr[0].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[0].tx_packet_size = LWS_PACKET_SIZE; - // Add user defined protocols - for (i = 0; i < len; i++) { - structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; - structs_ptr[i + 1].callback = p_callback; - structs_ptr[i + 1].per_session_data_size = data_size; - structs_ptr[i + 1].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[i + 1].tx_packet_size = LWS_PACKET_SIZE; - pos += pnr[i].ascii().length() + 1; - names_ptr[pos - 1] = '\0'; - } - // Add protocols terminator - structs_ptr[len + 1].name = NULL; - structs_ptr[len + 1].callback = NULL; - structs_ptr[len + 1].per_session_data_size = 0; - structs_ptr[len + 1].rx_buffer_size = 0; - - *r_lws_ref = ref; -} - -#endif diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h deleted file mode 100644 index 265dc4e6ad..0000000000 --- a/modules/websocket/lws_helper.h +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************/ -/* lws_helper.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef LWS_HELPER_H -#define LWS_HELPER_H - -#define LWS_BUF_SIZE 65536 -#define LWS_PACKET_SIZE LWS_BUF_SIZE - -#include "core/io/stream_peer.h" -#include "core/os/os.h" -#include "core/reference.h" -#include "core/ring_buffer.h" -#include "lws_peer.h" - -struct _LWSRef { - bool free_context; - bool is_polling; - bool is_valid; - bool is_destroying; - void *obj; - struct lws_protocols *lws_structs; - char *lws_names; -}; - -_LWSRef *_lws_create_ref(void *obj); -void _lws_free_ref(_LWSRef *ref); -bool _lws_destroy(struct lws_context *context, _LWSRef *ref); -bool _lws_poll(struct lws_context *context, _LWSRef *ref); -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref); - -/* clang-format off */ -#define LWS_HELPER(CNAME) \ -protected: \ - struct _LWSRef *_lws_ref; \ - struct lws_context *context; \ - bool _keep_servicing; \ - \ - static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \ - \ - if (wsi == NULL) { \ - return 0; \ - } \ - \ - struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \ - if (!ref->is_valid) \ - return 0; \ - CNAME *helper = (CNAME *)ref->obj; \ - helper->_keep_servicing = true; \ - return helper->_handle_cb(wsi, reason, user, in, len); \ - } \ - \ - void invalidate_lws_ref() { \ - if (_lws_ref != NULL) \ - _lws_ref->is_valid = false; \ - } \ - \ - void destroy_context() { \ - if (_lws_destroy(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - } \ - } \ - \ -public: \ - virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ - \ - void _lws_poll() { \ - ERR_FAIL_COND(context == NULL); \ - do { \ - _keep_servicing = false; \ - if (::_lws_poll(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - break; \ - } \ - } while (_keep_servicing); \ - } \ - \ -protected: - -/* clang-format on */ - -#endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp deleted file mode 100644 index a7c85450fa..0000000000 --- a/modules/websocket/lws_peer.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* lws_peer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_peer.h" - -#include "core/io/ip.h" - -// Needed for socket_helpers on Android at least. UNIXes has it, just include if not windows -#if !defined(WINDOWS_ENABLED) -#include <netinet/in.h> -#include <sys/socket.h> -#endif - -#include "drivers/unix/net_socket_posix.h" - -void LWSPeer::set_wsi(struct lws *p_wsi, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { - ERR_FAIL_COND(wsi != NULL); - - _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _out_buffer.resize(p_out_pkt_size, p_out_buf_size); - _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size)) + LWS_PRE); - wsi = p_wsi; -}; - -void LWSPeer::set_write_mode(WriteMode p_mode) { - write_mode = p_mode; -} - -LWSPeer::WriteMode LWSPeer::get_write_mode() const { - return write_mode; -} - -Error LWSPeer::read_wsi(void *in, size_t len) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (lws_is_first_fragment(wsi)) - _in_size = 0; - else if (_in_size == -1) // Trash this frame - return ERR_FILE_CORRUPT; - - Error err = _in_buffer.write_packet((const uint8_t *)in, len, NULL); - - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - - _in_size += len; - - if (lws_is_final_fragment(wsi)) { - uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; - err = _in_buffer.write_packet(NULL, _in_size, &is_string); - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - } - - return OK; -} - -Error LWSPeer::write_wsi() { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - PoolVector<uint8_t> tmp; - int count = _out_buffer.packets_left(); - - if (count == 0) - return OK; - - int read = 0; - uint8_t is_string = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _out_buffer.read_packet(&(rw[LWS_PRE]), _packet_buffer.size() - LWS_PRE, &is_string, read); - - enum lws_write_protocol mode = is_string ? LWS_WRITE_TEXT : LWS_WRITE_BINARY; - lws_write(wsi, &(rw[LWS_PRE]), read, mode); - - if (count > 1) - lws_callback_on_writable(wsi); // we want to write more! - - return OK; -} - -Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - uint8_t is_string = write_mode == WRITE_MODE_TEXT; - _out_buffer.write_packet(p_buffer, p_buffer_size, &is_string); - lws_callback_on_writable(wsi); // notify that we want to write - return OK; -}; - -Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - - r_buffer_size = 0; - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (_in_buffer.packets_left() == 0) - return ERR_UNAVAILABLE; - - int read = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); - - *r_buffer = rw.ptr(); - r_buffer_size = read; - - return OK; -}; - -int LWSPeer::get_available_packet_count() const { - - if (!is_connected_to_host()) - return 0; - - return _in_buffer.packets_left(); -}; - -bool LWSPeer::was_string_packet() const { - - return _is_string; -}; - -bool LWSPeer::is_connected_to_host() const { - - return wsi != NULL; -}; - -String LWSPeer::get_close_reason(void *in, size_t len, int &r_code) { - String s; - r_code = 0; - if (len < 2) // From docs this should not happen - return s; - - const uint8_t *b = (const uint8_t *)in; - r_code = b[0] << 8 | b[1]; - - if (len > 2) { - const char *utf8 = (const char *)&b[2]; - s.parse_utf8(utf8, len - 2); - } - return s; -} - -void LWSPeer::send_close_status(struct lws *p_wsi) { - if (close_code == -1) - return; - - int len = close_reason.size(); - ERR_FAIL_COND(len > 123); // Maximum allowed reason size in bytes - - lws_close_status code = (lws_close_status)close_code; - unsigned char *reason = len > 0 ? (unsigned char *)close_reason.utf8().ptrw() : NULL; - - lws_close_reason(p_wsi, code, reason, len); - - close_code = -1; - close_reason = ""; -} - -void LWSPeer::close(int p_code, String p_reason) { - if (wsi != NULL) { - close_code = p_code; - close_reason = p_reason; - PeerData *data = ((PeerData *)lws_wsi_user(wsi)); - data->force_close = true; - data->clean_close = true; - lws_callback_on_writable(wsi); // Notify that we want to disconnect - } else { - close_code = -1; - close_reason = ""; - } - wsi = NULL; - _in_buffer.clear(); - _out_buffer.clear(); - _in_size = 0; - _is_string = 0; - _packet_buffer.resize(0); -}; - -IP_Address LWSPeer::get_connected_host() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, IP_Address()); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, IP_Address()); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return ip; -}; - -uint16_t LWSPeer::get_connected_port() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), 0); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, 0); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, 0); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return port; -}; - -LWSPeer::LWSPeer() { - wsi = NULL; - write_mode = WRITE_MODE_BINARY; - close(); -}; - -LWSPeer::~LWSPeer() { - - close(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp deleted file mode 100644 index 482c02dc60..0000000000 --- a/modules/websocket/lws_server.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/*************************************************************************/ -/* lws_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_server.h" -#include "core/os/os.h" -#include "core/project_settings.h" - -Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - _is_multiplayer = gd_mp_api; - - struct lws_context_creation_info info; - memset(&info, 0, sizeof info); - - // Prepare lws protocol structs - _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); - - info.port = p_port; - info.user = _lws_ref; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create LWS context"); - ERR_FAIL_V(FAILED); - } - - return OK; -} - -bool LWSServer::is_listening() const { - return context != NULL; -} - -int LWSServer::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { - case LWS_CALLBACK_HTTP: - // no http for now - // closing immediately returning -1; - return -1; - - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - // check header here? - break; - - case LWS_CALLBACK_ESTABLISHED: { - int32_t id = _gen_unique_id(); - - Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - _peer_map[id] = peer; - - peer_data->peer_id = id; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(id, lws_get_protocol(wsi)->name); - break; - } - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - if (peer_data == NULL) - return 0; - - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - int code; - Ref<LWSPeer> peer = _peer_map[id]; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(id, code, reason2); - } - return 0; - } - - case LWS_CALLBACK_CLOSED: { - if (peer_data == NULL) - return 0; - int32_t id = peer_data->peer_id; - bool clean = peer_data->clean_close; - if (_peer_map.has(id)) { - _peer_map[id]->close(); - _peer_map.erase(id); - } - _on_disconnect(id, clean); - return 0; // we can end here - } - - case LWS_CALLBACK_RECEIVE: { - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len); - if (_peer_map[id]->get_available_packet_count() > 0) - _on_peer_packet(id); - } - break; - } - - case LWS_CALLBACK_SERVER_WRITEABLE: { - int id = peer_data->peer_id; - if (peer_data->force_close) { - if (_peer_map.has(id)) { - Ref<LWSPeer> peer = _peer_map[id]; - peer->send_close_status(wsi); - } - return -1; - } - - if (_peer_map.has(id)) - static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); - break; - } - - default: - break; - } - - return 0; -} - -void LWSServer::stop() { - if (context == NULL) - return; - - _peer_map.clear(); - destroy_context(); - context = NULL; -} - -bool LWSServer::has_peer(int p_id) const { - return _peer_map.has(p_id); -} - -Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const { - ERR_FAIL_COND_V(!has_peer(p_id), NULL); - return _peer_map[p_id]; -} - -IP_Address LWSServer::get_peer_address(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); - - return _peer_map[p_peer_id]->get_connected_host(); -} - -int LWSServer::get_peer_port(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); - - return _peer_map[p_peer_id]->get_connected_port(); -} - -void LWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { - ERR_FAIL_COND(!has_peer(p_peer_id)); - - get_peer(p_peer_id)->close(p_code, p_reason); -} - -Error LWSServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSServer::LWSServer() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); - context = NULL; - _lws_ref = NULL; -} - -LWSServer::~LWSServer() { - invalidate_lws_ref(); // we do not want any more callbacks - stop(); -} - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 39bf3de982..1c808f0d5c 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -37,9 +37,8 @@ #include "emws_peer.h" #include "emws_server.h" #else -#include "lws_client.h" -#include "lws_peer.h" -#include "lws_server.h" +#include "wsl_client.h" +#include "wsl_server.h" #endif void register_websocket_types() { @@ -64,9 +63,9 @@ void register_websocket_types() { EMWSClient::make_default(); EMWSServer::make_default(); #else - LWSPeer::make_default(); - LWSClient::make_default(); - LWSServer::make_default(); + WSLPeer::make_default(); + WSLClient::make_default(); + WSLServer::make_default(); #endif ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 23cbf916eb..dd86c758bf 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -98,16 +98,14 @@ void WebSocketMultiplayerPeer::_bind_methods() { // int WebSocketMultiplayerPeer::get_available_packet_count() const { - ERR_EXPLAIN("Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); return _incoming_packets.size(); } Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); r_buffer_size = 0; @@ -127,8 +125,7 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); @@ -160,8 +157,7 @@ void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { int WebSocketMultiplayerPeer::get_packet_peer() const { - ERR_EXPLAIN("This function is not available when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, 1); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, 1, "This function is not available when not using the MultiplayerAPI."); ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); return _incoming_packets.front()->get().source; @@ -191,7 +187,7 @@ void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_ty p_peer->put_packet(&(message.read()[0]), message.size()); } -PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { +PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint8_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { PoolVector<uint8_t> out; out.resize(PROTO_SIZE + p_data_size); @@ -269,7 +265,10 @@ Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, cons ERR_FAIL_COND_V(p_to == p_from, FAILED); - return get_peer(p_to)->put_packet(p_buffer, p_buffer_size); // Sending to specific peer + Ref<WebSocketPeer> peer_to = get_peer(p_to); + ERR_FAIL_COND_V(peer_to.is_null(), FAILED); + + return peer_to->put_packet(p_buffer, p_buffer_size); // Sending to specific peer } } @@ -300,8 +299,6 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages ERR_FAIL_COND(from != p_peer_id); // Someone is cheating - _server_relay(from, to, in_buffer, size); // Relay if needed - if (to == 1) { // This is for the server _store_pkt(from, to, in_buffer, data_size); @@ -316,13 +313,9 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u // All but one, for us if not excluded if (_peer_id != -(int32_t)p_peer_id) _store_pkt(from, to, in_buffer, data_size); - - } else { - - // Send to specific peer - ERR_FAIL_COND(!_peer_map.has(to)); - get_peer(to)->put_packet(in_buffer, size); } + // Relay if needed (i.e. "to" includes a peer that is not the server) + _server_relay(from, to, in_buffer, size); } else { @@ -354,8 +347,7 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u _peer_id = id; break; default: - ERR_EXPLAIN("Invalid multiplayer message"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid multiplayer message."); break; } } diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 089bc25fe9..e3ab0784ab 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -41,7 +41,7 @@ class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer { GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer); private: - PoolVector<uint8_t> _make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); + PoolVector<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); void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size); Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size); @@ -82,7 +82,7 @@ public: /* NetworkedMultiplayerPeer */ void set_transfer_mode(TransferMode p_mode); TransferMode get_transfer_mode() const; - void set_target_peer(int p_peer_id); + void set_target_peer(int p_target_peer); int get_packet_peer() const; int get_unique_id() const; virtual bool is_server() const = 0; diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp new file mode 100644 index 0000000000..0006a057e0 --- /dev/null +++ b/modules/websocket/wsl_client.cpp @@ -0,0 +1,344 @@ +/*************************************************************************/ +/* wsl_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_client.h" +#include "core/io/ip.h" +#include "core/project_settings.h" + +void WSLClient::_do_handshake() { + if (_requested < _request.size() - 1) { + int sent = 0; + Error err = _connection->put_partial_data(((const uint8_t *)_request.get_data() + _requested), _request.size() - _requested - 1, sent); + // Sending handshake failed + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + _requested += sent; + + } else { + int read = 0; + while (true) { + if (_resp_pos >= WSL_MAX_HEADER_SIZE) { + // Header is too big + disconnect_from_host(); + _on_error(); + ERR_FAIL_MSG("Response headers too big."); + } + Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); + if (err == ERR_FILE_EOF) { + // We got a disconnect. + disconnect_from_host(); + _on_error(); + return; + } else if (err != OK) { + // Got some error. + disconnect_from_host(); + _on_error(); + return; + } else if (read != 1) { + // Busy, wait next poll. + break; + } + // Check "\r\n\r\n" header terminator + char *r = (char *)_resp_buf; + int l = _resp_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + String protocol; + // Response is over, verify headers and create peer. + if (!_verify_headers(protocol)) { + disconnect_from_host(); + _on_error(); + ERR_FAIL_MSG("Invalid response headers."); + } + // Create peer. + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = _connection; + data->is_server = false; + data->id = 1; + _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + _on_connect(protocol); + break; + } + _resp_pos += 1; + } + } +} + +bool WSLClient::_verify_headers(String &r_protocol) { + String s = (char *)_resp_buf; + Vector<String> psa = s.split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); + + Vector<String> req = psa[0].split(" ", false); + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1" || req[1] != "101", false, "Invalid protocol or status code."); + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + "."); + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } + +#define _WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define _WSL_CHECK_NC(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); + _WSL_CHECK("connection", "upgrade"); + _WSL_CHECK("upgrade", "websocket"); + _WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); +#undef _WSL_CHECK_NC +#undef _WSL_CHECK + if (_protocols.size() == 0) { + // We didn't request a custom protocol + ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); + } else { + ERR_FAIL_COND_V(!headers.has("sec-websocket-protocol"), false); + r_protocol = headers["sec-websocket-protocol"]; + bool valid = false; + for (int i = 0; i < _protocols.size(); i++) { + if (_protocols[i] != r_protocol) + continue; + valid = true; + break; + } + if (!valid) + return false; + } + return true; +} + +Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); + + _peer = Ref<WSLPeer>(memnew(WSLPeer)); + IP_Address addr; + + if (!p_host.is_valid_ip_address()) { + addr = IP::get_singleton()->resolve_hostname(p_host); + } else { + addr = p_host; + } + + 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); + } + + Error err = _tcp->connect_to_host(addr, p_port); + if (err != OK) { + _tcp->disconnect_from_host(); + _on_error(); + return err; + } + _connection = _tcp; + _use_ssl = p_ssl; + _host = p_host; + _protocols = p_protocols; + + _key = WSLPeer::generate_key(); + // TODO custom extra headers (allow overriding this too?) + String request = "GET " + p_path + " HTTP/1.1\r\n"; + request += "Host: " + p_host + port + "\r\n"; + request += "Upgrade: websocket\r\n"; + request += "Connection: Upgrade\r\n"; + request += "Sec-WebSocket-Key: " + _key + "\r\n"; + request += "Sec-WebSocket-Version: 13\r\n"; + if (p_protocols.size() > 0) { + request += "Sec-WebSocket-Protocol: "; + for (int i = 0; i < p_protocols.size(); i++) { + if (i != 0) + request += ","; + request += p_protocols[i]; + } + request += "\r\n"; + } + request += "\r\n"; + _request = request.utf8(); + + return OK; +} + +int WSLClient::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLClient::poll() { + if (_peer->is_connected_to_host()) { + _peer->poll(); + if (!_peer->is_connected_to_host()) { + disconnect_from_host(); + _on_disconnect(_peer->close_code != -1); + } + return; + } + + if (_connection.is_null()) + return; // Not connected. + + switch (_tcp->get_status()) { + case StreamPeerTCP::STATUS_NONE: + // Clean close + disconnect_from_host(); + _on_error(); + break; + case StreamPeerTCP::STATUS_CONNECTED: { + Ref<StreamPeerSSL> ssl; + if (_use_ssl) { + if (_connection == _tcp) { + // Start SSL handshake + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ERR_FAIL_COND_MSG(ssl.is_null(), "SSL is not available in this build."); + ssl->set_blocking_handshake_enabled(false); + if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) { + disconnect_from_host(); + _on_error(); + return; + } + _connection = ssl; + } else { + ssl = static_cast<Ref<StreamPeerSSL> >(_connection); + ERR_FAIL_COND(ssl.is_null()); // Bug? + ssl->poll(); + } + if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) + return; // Need more polling. + else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + disconnect_from_host(); + _on_error(); + return; // Error. + } + } + // Do websocket handshake. + _do_handshake(); + } break; + case StreamPeerTCP::STATUS_ERROR: + disconnect_from_host(); + _on_error(); + break; + case StreamPeerTCP::STATUS_CONNECTING: + break; // Wait for connection + } +} + +Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const { + + ERR_FAIL_COND_V(p_peer_id != 1, NULL); + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + if (_tcp->is_connected_to_host()) + return CONNECTION_CONNECTING; + + return CONNECTION_DISCONNECTED; +} + +void WSLClient::disconnect_from_host(int p_code, String p_reason) { + + _peer->close(p_code, p_reason); + _connection = Ref<StreamPeer>(NULL); + _tcp = Ref<StreamPeerTCP>(memnew(StreamPeerTCP)); + + _key = ""; + _host = ""; + _protocols.resize(0); + _use_ssl = false; + + _request = ""; + _requested = 0; + + memset(_resp_buf, 0, sizeof(_resp_buf)); + _resp_pos = 0; +} + +IP_Address WSLClient::get_connected_host() const { + + return IP_Address(); +} + +uint16_t WSLClient::get_connected_port() const { + + return 1025; +} + +Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_FAIL_COND_V_MSG(_connection.is_valid(), FAILED, "Buffers sizes can only be set before listening or connecting."); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLClient::WSLClient() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); + + _peer.instance(); + _tcp.instance(); + disconnect_from_host(); +} + +WSLClient::~WSLClient() { + + _peer->close_now(); + _peer->invalidate(); + disconnect_from_host(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_client.h b/modules/websocket/wsl_client.h index df4aabec7f..57dfd635b7 100644 --- a/modules/websocket/lws_client.h +++ b/modules/websocket/wsl_client.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_client.h */ +/* wsl_client.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSCLIENT_H -#define LWSCLIENT_H +#ifndef WSLCLIENT_H +#define WSLCLIENT_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" -#include "lws_helper.h" -#include "lws_peer.h" +#include "core/io/stream_peer_ssl.h" +#include "core/io/stream_peer_tcp.h" #include "websocket_client.h" +#include "wsl_peer.h" +#include "wslay/wslay.h" -class LWSClient : public WebSocketClient { +class WSLClient : public WebSocketClient { - GDCIIMPL(LWSClient, WebSocketClient); - - LWS_HELPER(LWSClient); + GDCIIMPL(WSLClient, WebSocketClient); private: int _in_buf_size; @@ -50,6 +50,26 @@ private: int _out_buf_size; int _out_pkt_size; + Ref<WSLPeer> _peer; + Ref<StreamPeerTCP> _tcp; + Ref<StreamPeer> _connection; + + CharString _request; + int _requested; + + uint8_t _resp_buf[WSL_MAX_HEADER_SIZE]; + int _resp_pos; + + String _response; + + String _key; + String _host; + PoolVector<String> _protocols; + bool _use_ssl; + + void _do_handshake(); + bool _verify_headers(String &r_protocol); + public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); @@ -61,10 +81,10 @@ public: virtual ConnectionStatus get_connection_status() const; virtual void poll(); - LWSClient(); - ~LWSClient(); + WSLClient(); + ~WSLClient(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSCLIENT_H +#endif // WSLCLIENT_H diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp new file mode 100644 index 0000000000..74fb901232 --- /dev/null +++ b/modules/websocket/wsl_peer.cpp @@ -0,0 +1,339 @@ +/*************************************************************************/ +/* wsl_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_peer.h" + +#include "wsl_client.h" +#include "wsl_server.h" + +#include "core/crypto/crypto_core.h" +#include "core/math/random_number_generator.h" +#include "core/os/os.h" + +String WSLPeer::generate_key() { + // Random key + RandomNumberGenerator rng; + rng.set_seed(OS::get_singleton()->get_unix_time()); + PoolVector<uint8_t> bkey; + int len = 16; // 16 bytes, as per RFC + bkey.resize(len); + PoolVector<uint8_t>::Write w = bkey.write(); + for (int i = 0; i < len; i++) { + w[i] = (uint8_t)rng.randi_range(0, 255); + } + return CryptoCore::b64_encode_str(&w[0], len); +} + +String WSLPeer::compute_key_response(String p_key) { + String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC + Vector<uint8_t> sha = key.sha1_buffer(); + return CryptoCore::b64_encode_str(sha.ptr(), sha.size()); +} + +void WSLPeer::_wsl_destroy(struct PeerData **p_data) { + if (!p_data || !(*p_data)) + return; + struct PeerData *data = *p_data; + if (data->polling) { + data->destroy = true; + return; + } + wslay_event_context_free(data->ctx); + memdelete(data); + *p_data = NULL; +} + +bool WSLPeer::_wsl_poll(struct PeerData *p_data) { + p_data->polling = true; + int err = 0; + if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) { + print_verbose("Websocket (wslay) poll error: " + itos(err)); + p_data->destroy = true; + } + p_data->polling = false; + + if (p_data->destroy || (wslay_event_get_close_sent(p_data->ctx) && wslay_event_get_close_received(p_data->ctx))) { + bool valid = p_data->valid; + _wsl_destroy(&p_data); + return valid; + } + return false; +} + +ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int read = 0; + Error err = conn->get_partial_data(data, len, read); + if (err != OK) { + print_verbose("Websocket get data error: " + itos(err) + ", read (should be 0!): " + itos(read)); + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (read == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return read; +} + +ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int sent = 0; + Error err = conn->put_partial_data(data, len, sent); + if (err != OK) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (sent == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return sent; +} + +int wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { + RandomNumberGenerator rng; + // TODO maybe use crypto in the future? + rng.set_seed(OS::get_singleton()->get_unix_time()); + for (unsigned int i = 0; i < len; i++) { + buf[i] = (uint8_t)rng.randi_range(0, 255); + } + return 0; +} + +void wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + return; + } + WSLPeer *peer = (WSLPeer *)peer_data->peer; + + if (peer->parse_message(arg) != OK) + return; + + if (peer_data->is_server) { + WSLServer *helper = (WSLServer *)peer_data->obj; + helper->_on_peer_packet(peer_data->id); + } else { + WSLClient *helper = (WSLClient *)peer_data->obj; + helper->_on_peer_packet(); + } +} + +wslay_event_callbacks wsl_callbacks = { + wsl_recv_callback, + wsl_send_callback, + wsl_genmask_callback, + NULL, /* on_frame_recv_start_callback */ + NULL, /* on_frame_recv_callback */ + NULL, /* on_frame_recv_end_callback */ + wsl_msg_recv_callback +}; + +Error WSLPeer::parse_message(const wslay_event_on_msg_recv_arg *arg) { + uint8_t is_string = 0; + if (arg->opcode == WSLAY_TEXT_FRAME) { + is_string = 1; + } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) { + close_code = arg->status_code; + size_t len = arg->msg_length; + close_reason = ""; + if (len > 2 /* first 2 bytes = close code */) { + close_reason.parse_utf8((char *)arg->msg + 2, len - 2); + } + if (!wslay_event_get_close_sent(_data->ctx)) { + if (_data->is_server) { + WSLServer *helper = (WSLServer *)_data->obj; + helper->_on_close_request(_data->id, close_code, close_reason); + } else { + WSLClient *helper = (WSLClient *)_data->obj; + helper->_on_close_request(close_code, close_reason); + } + } + return ERR_FILE_EOF; + } else if (arg->opcode != WSLAY_BINARY_FRAME) { + // Ping or pong + return ERR_SKIP; + } + _in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); + return OK; +} + +void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { + ERR_FAIL_COND(_data != NULL); + ERR_FAIL_COND(p_data == NULL); + + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size))); + + _data = p_data; + _data->peer = this; + _data->valid = true; + _connection = Ref<StreamPeer>(_data->conn); + + if (_data->is_server) + wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); + else + wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data); + wslay_event_config_set_max_recv_msg_length(_data->ctx, (1 << p_in_buf_size)); +} + +void WSLPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +WSLPeer::WriteMode WSLPeer::get_write_mode() const { + return write_mode; +} + +void WSLPeer::poll() { + if (!_data) + return; + + if (_wsl_poll(_data)) { + _data = NULL; + } +} + +Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + struct wslay_event_msg msg; // Should I use fragmented? + 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); + return OK; +} + +Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + r_buffer_size = 0; + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + if (_in_buffer.packets_left() == 0) + return ERR_UNAVAILABLE; + + int read = 0; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); + + *r_buffer = rw.ptr(); + r_buffer_size = read; + + return OK; +} + +int WSLPeer::get_available_packet_count() const { + + if (!is_connected_to_host()) + return 0; + + return _in_buffer.packets_left(); +} + +bool WSLPeer::was_string_packet() const { + + return _is_string; +} + +bool WSLPeer::is_connected_to_host() const { + + return _data != NULL; +} + +void WSLPeer::close_now() { + close(1000, ""); + _wsl_destroy(&_data); +} + +void WSLPeer::close(int p_code, String p_reason) { + if (_data && !wslay_event_get_close_sent(_data->ctx)) { + CharString cs = p_reason.utf8(); + wslay_event_queue_close(_data->ctx, p_code, (uint8_t *)cs.ptr(), cs.size()); + wslay_event_send(_data->ctx); + } + + _in_buffer.clear(); + _packet_buffer.resize(0); +} + +IP_Address WSLPeer::get_connected_host() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); + + IP_Address ip; + return ip; +} + +uint16_t WSLPeer::get_connected_port() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), 0); + + uint16_t port = 0; + return port; +} + +void WSLPeer::invalidate() { + if (_data) + _data->valid = false; +} + +WSLPeer::WSLPeer() { + _data = NULL; + _is_string = 0; + close_code = -1; + write_mode = WRITE_MODE_BINARY; +} + +WSLPeer::~WSLPeer() { + + close(); + invalidate(); + _wsl_destroy(&_data); + _data = NULL; +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_peer.h b/modules/websocket/wsl_peer.h index 6771c13094..d51b304fe1 100644 --- a/modules/websocket/lws_peer.h +++ b/modules/websocket/wsl_peer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_peer.h */ +/* wsl_peer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,50 +28,76 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSPEER_H -#define LWSPEER_H +#ifndef WSLPEER_H +#define WSLPEER_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" #include "core/io/packet_peer.h" #include "core/ring_buffer.h" -#include "libwebsockets.h" -#include "lws_config.h" #include "packet_buffer.h" #include "websocket_peer.h" +#include "wslay/wslay.h" -class LWSPeer : public WebSocketPeer { +#define WSL_MAX_HEADER_SIZE 4096 - GDCIIMPL(LWSPeer, WebSocketPeer); +class WSLPeer : public WebSocketPeer { + + GDCIIMPL(WSLPeer, WebSocketPeer); + +public: + struct PeerData { + bool polling; + bool destroy; + bool valid; + bool is_server; + void *obj; + void *peer; + Ref<StreamPeer> conn; + int id; + wslay_event_context_ptr ctx; + + PeerData() { + polling = false; + destroy = false; + valid = false; + is_server = false; + id = 1; + ctx = NULL; + obj = NULL; + peer = NULL; + } + }; + + static String compute_key_response(String p_key); + static String generate_key(); private: - int _in_size; + static bool _wsl_poll(struct PeerData *p_data); + static void _wsl_destroy(struct PeerData **p_data); + + Ref<StreamPeer> _connection; + struct PeerData *_data; uint8_t _is_string; // Our packet info is just a boolean (is_string), using uint8_t for it. PacketBuffer<uint8_t> _in_buffer; - PacketBuffer<uint8_t> _out_buffer; PoolVector<uint8_t> _packet_buffer; - struct lws *wsi; WriteMode write_mode; +public: int close_code; String close_reason; - -public: - struct PeerData { - uint32_t peer_id; - bool force_close; - bool clean_close; - }; + void poll(); // Used by client and server. 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 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; @@ -81,14 +107,12 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi, unsigned int _in_buf_size, unsigned int _in_pkt_size, unsigned int _out_buf_size, unsigned int _out_pkt_size); - Error read_wsi(void *in, size_t len); - Error write_wsi(); - void send_close_status(struct lws *wsi); - String get_close_reason(void *in, size_t len, int &r_code); + void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size); + Error parse_message(const wslay_event_on_msg_recv_arg *arg); + void invalidate(); - LWSPeer(); - ~LWSPeer(); + WSLPeer(); + ~WSLPeer(); }; #endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp new file mode 100644 index 0000000000..efb526eed1 --- /dev/null +++ b/modules/websocket/wsl_server.cpp @@ -0,0 +1,285 @@ +/*************************************************************************/ +/* wsl_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_server.h" +#include "core/os/os.h" +#include "core/project_settings.h" + +WSLServer::PendingPeer::PendingPeer() { + time = 0; + has_request = false; + response_sent = 0; + req_pos = 0; + memset(req_buf, 0, sizeof(req_buf)); +} + +bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { + 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."); + + Vector<String> req = psa[0].split(" ", false); + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version."); + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]); + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } +#define _WSL_CHECK(NAME, VALUE) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ + "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); +#define _WSL_CHECK_EX(NAME) \ + ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); + _WSL_CHECK("upgrade", "websocket"); + _WSL_CHECK("sec-websocket-version", "13"); + _WSL_CHECK_EX("sec-websocket-key"); + _WSL_CHECK_EX("connection"); +#undef _WSL_CHECK_EX +#undef _WSL_CHECK + key = headers["sec-websocket-key"]; + if (headers.has("sec-websocket-protocol")) { + Vector<String> protos = headers["sec-websocket-protocol"].split(","); + for (int i = 0; i < protos.size(); i++) { + // Check if we have the given protocol + for (int j = 0; j < p_protocols.size(); j++) { + if (protos[i] != p_protocols[j]) + continue; + protocol = protos[i]; + break; + } + // Found a protocol + if (protocol != "") + break; + } + if (protocol == "") // Invalid protocol(s) requested + return false; + } else if (p_protocols.size() > 0) // No protocol requested, but we need one + return false; + return true; +} + +Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { + if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) + return ERR_TIMEOUT; + 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."); + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) // Got an error + return FAILED; + else if (read != 1) // Busy, wait next poll + return ERR_BUSY; + char *r = (char *)req_buf; + int l = req_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + if (!_parse_request(p_protocols)) { + 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 != "") + s += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; + s += "\r\n"; + response = s.utf8(); + has_request = true; + break; + } + req_pos += 1; + } + } + if (has_request && response_sent < response.size() - 1) { + int sent = 0; + Error err = connection->put_partial_data((const uint8_t *)response.get_data() + response_sent, response.size() - response_sent - 1, sent); + if (err != OK) { + return err; + } + response_sent += sent; + } + if (response_sent < response.size() - 1) + return ERR_BUSY; + return OK; +} + +Error WSLServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); + + _is_multiplayer = gd_mp_api; + _protocols = p_protocols; + _server->listen(p_port); + + return OK; +} + +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(); + peer->poll(); + if (!peer->is_connected_to_host()) { + _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()); + } + 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); + if (err == ERR_BUSY) { + continue; + } else if (err != OK) { + remove_peers.push_back(ppeer); + continue; + } + // Creating new peer + int32_t id = _gen_unique_id(); + + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = ppeer->connection; + data->is_server = true; + data->id = id; + + Ref<WSLPeer> ws_peer = memnew(WSLPeer); + ws_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + + _peer_map[id] = ws_peer; + remove_peers.push_back(ppeer); + _on_connect(id, ppeer->protocol); + } + for (List<Ref<PendingPeer> >::Element *E = remove_peers.front(); E; E = E->next()) { + _pending.erase(E->get()); + } + remove_peers.clear(); + + if (!_server->is_listening()) + return; + + while (_server->is_connection_available()) { + Ref<StreamPeer> conn = _server->take_connection(); + if (is_refusing_new_connections()) + continue; // Conn will go out-of-scope and be closed. + + Ref<PendingPeer> peer = memnew(PendingPeer); + peer->connection = conn; + peer->time = OS::get_singleton()->get_ticks_msec(); + _pending.push_back(peer); + } +} + +bool WSLServer::is_listening() const { + return _server->is_listening(); +} + +int WSLServer::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLServer::stop() { + _server->stop(); + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + peer->close_now(); + } + _pending.clear(); + _peer_map.clear(); +} + +bool WSLServer::has_peer(int p_id) const { + return _peer_map.has(p_id); +} + +Ref<WebSocketPeer> WSLServer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!has_peer(p_id), NULL); + 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()); + + return _peer_map[p_peer_id]->get_connected_host(); +} + +int WSLServer::get_peer_port(int p_peer_id) const { + ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); + + return _peer_map[p_peer_id]->get_connected_port(); +} + +void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { + ERR_FAIL_COND(!has_peer(p_peer_id)); + + get_peer(p_peer_id)->close(p_code, p_reason); +} + +Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_FAIL_COND_V_MSG(_server->is_listening(), FAILED, "Buffers sizes can only be set before listening or connecting."); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLServer::WSLServer() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); + _server.instance(); +} + +WSLServer::~WSLServer() { + stop(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.h b/modules/websocket/wsl_server.h index b331852d26..2ceb941073 100644 --- a/modules/websocket/lws_server.h +++ b/modules/websocket/wsl_server.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_server.h */ +/* wsl_server.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,55 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSSERVER_H -#define LWSSERVER_H +#ifndef WSLSERVER_H +#define WSLSERVER_H #ifndef JAVASCRIPT_ENABLED -#include "core/reference.h" -#include "lws_helper.h" -#include "lws_peer.h" #include "websocket_server.h" +#include "wsl_peer.h" -class LWSServer : public WebSocketServer { +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" - GDCIIMPL(LWSServer, WebSocketServer); +#define WSL_SERVER_TIMEOUT 1000 - LWS_HELPER(LWSServer); +class WSLServer : public WebSocketServer { + + GDCIIMPL(WSLServer, WebSocketServer); private: - Map<int, Ref<LWSPeer> > peer_map; + class PendingPeer : public Reference { + + private: + bool _parse_request(const PoolStringArray p_protocols); + + public: + Ref<StreamPeer> connection; + + int time; + uint8_t req_buf[WSL_MAX_HEADER_SIZE]; + int req_pos; + String key; + String protocol; + bool has_request; + CharString response; + int response_sent; + + PendingPeer(); + + Error do_handshake(const PoolStringArray p_protocols); + }; + int _in_buf_size; int _in_pkt_size; int _out_buf_size; int _out_pkt_size; + List<Ref<PendingPeer> > _pending; + Ref<TCP_Server> _server; + PoolStringArray _protocols; + public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); @@ -62,12 +88,12 @@ public: IP_Address 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() { _lws_poll(); } + virtual void poll(); - LWSServer(); - ~LWSServer(); + WSLServer(); + ~WSLServer(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSSERVER_H +#endif // WSLSERVER_H |