summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/websocket/SCsub23
-rw-r--r--modules/websocket/doc_classes/WebSocketClient.xml2
-rw-r--r--modules/websocket/doc_classes/WebSocketServer.xml2
-rw-r--r--modules/websocket/register_types.cpp4
-rw-r--r--modules/websocket/wsl_client.cpp356
-rw-r--r--modules/websocket/wsl_client.h85
-rw-r--r--modules/websocket/wsl_peer.cpp339
-rw-r--r--modules/websocket/wsl_peer.h120
-rw-r--r--modules/websocket/wsl_server.cpp272
-rw-r--r--modules/websocket/wsl_server.h100
10 files changed, 1299 insertions, 4 deletions
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub
index be26b08a60..e8d094fd7f 100644
--- a/modules/websocket/SCsub
+++ b/modules/websocket/SCsub
@@ -5,7 +5,7 @@ Import('env_modules')
# Thirdparty source files
-env_lws = env_modules.Clone()
+env_ws = env_modules.Clone()
if env['builtin_libwebsockets'] and not env["platform"] == "javascript": # already builtin for javascript
thirdparty_dir = "#thirdparty/libwebsockets/"
@@ -90,4 +90,23 @@ if env['builtin_libwebsockets'] and not env["platform"] == "javascript": # alrea
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
-env_lws.add_source_files(env.modules_sources, "*.cpp")
+ wslay_dir = "#thirdparty/wslay/"
+ wslay_sources = [
+ "wslay_net.c",
+ "wslay_event.c",
+ "wslay_queue.c",
+ "wslay_stack.c",
+ "wslay_frame.c",
+ ]
+ wslay_sources = [wslay_dir + s for s in wslay_sources]
+ env_ws.Prepend(CPPPATH=[wslay_dir + "includes/"])
+ env_ws.Append(CPPFLAGS=["-DHAVE_CONFIG_H"])
+ if env["platform"] == "windows" or env["platform"] == "uwp":
+ env_ws.Append(CPPFLAGS=["-DHAVE_WINSOCK2_H"])
+ else:
+ env_ws.Append(CPPFLAGS=["-DHAVE_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 0d425ad1dd..c3baf9de83 100644
--- a/modules/websocket/doc_classes/WebSocketClient.xml
+++ b/modules/websocket/doc_classes/WebSocketClient.xml
@@ -22,7 +22,7 @@
<argument index="2" name="gd_mp_api" type="bool" default="false">
</argument>
<description>
- Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol.
+ 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>
diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml
index 1af5e403e6..63318e5874 100644
--- a/modules/websocket/doc_classes/WebSocketServer.xml
+++ b/modules/websocket/doc_classes/WebSocketServer.xml
@@ -69,7 +69,7 @@
</argument>
<description>
Starts listening on the given port.
- You can specify the desired subprotocols via the "protocols" array. If the list empty (default), "binary" will be used.
+ 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>
diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp
index 39bf3de982..3071c5bc9e 100644
--- a/modules/websocket/register_types.cpp
+++ b/modules/websocket/register_types.cpp
@@ -40,6 +40,8 @@
#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() {
@@ -67,6 +69,8 @@ void register_websocket_types() {
LWSPeer::make_default();
LWSClient::make_default();
LWSServer::make_default();
+ WSLClient::make_default();
+ WSLServer::make_default();
#endif
ClassDB::register_virtual_class<WebSocketMultiplayerPeer>();
diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp
new file mode 100644
index 0000000000..d695395635
--- /dev/null
+++ b/modules/websocket/wsl_client.cpp
@@ -0,0 +1,356 @@
+/*************************************************************************/
+/* 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 {
+ uint8_t byte = 0;
+ int read = 0;
+
+ while (true) {
+ Error err = _connection->get_partial_data(&byte, 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;
+ }
+ // TODO lots of allocs. Use a buffer.
+ _response += byte;
+ if (_response.size() > WSL_MAX_HEADER_SIZE) {
+ // Header is too big
+ disconnect_from_host();
+ _on_error();
+ ERR_EXPLAIN("Response headers too big");
+ ERR_FAIL();
+ }
+ if (_response.ends_with("\r\n\r\n")) {
+ String protocol;
+ // Response is over, verify headers and create peer.
+ if (!_verify_headers(protocol)) {
+ disconnect_from_host();
+ _on_error();
+ ERR_EXPLAIN("Invalid response headers");
+ ERR_FAIL();
+ }
+ // 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);
+ }
+ }
+ }
+}
+
+bool WSLClient::_verify_headers(String &r_protocol) {
+ Vector<String> psa = _response.trim_suffix("\r\n\r\n").split("\r\n");
+ int len = psa.size();
+ if (len < 4) {
+ ERR_EXPLAIN("Not enough response headers.");
+ ERR_FAIL_V(false);
+ }
+
+ Vector<String> req = psa[0].split(" ", false);
+ if (req.size() < 2) {
+ ERR_EXPLAIN("Invalid protocol or status code.");
+ ERR_FAIL_V(false);
+ }
+ // Wrong protocol
+ if (req[0] != "HTTP/1.1" || req[1] != "101") {
+ ERR_EXPLAIN("Invalid protocol or status code.");
+ ERR_FAIL_V(false);
+ }
+
+ Map<String, String> headers;
+ for (int i = 1; i < len; i++) {
+ Vector<String> header = psa[i].split(":", false, 1);
+ if (header.size() != 2) {
+ ERR_EXPLAIN("Invalid header -> " + psa[i]);
+ ERR_FAIL_V(false);
+ }
+ String name = header[0].to_lower();
+ String value = header[1].strip_edges();
+ if (headers.has(name))
+ headers[name] += "," + value;
+ else
+ headers[name] = value;
+ }
+
+#define _WLS_EXPLAIN(NAME, VALUE) \
+ ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'");
+#define _WLS_CHECK(NAME, VALUE) \
+ _WLS_EXPLAIN(NAME, VALUE); \
+ ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false);
+#define _WLS_CHECK_NC(NAME, VALUE) \
+ _WLS_EXPLAIN(NAME, VALUE); \
+ ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME] != VALUE, false);
+ _WLS_CHECK("connection", "upgrade");
+ _WLS_CHECK("upgrade", "websocket");
+ _WLS_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key));
+ 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;
+ }
+#undef _WLS_CHECK_NC
+#undef _WLS_CHECK
+#undef _WLS_EXPLAIN
+
+ 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) {
+ _on_error();
+ _tcp->disconnect_from_host();
+ 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()) {
+ _on_disconnect(_peer->close_code != -1);
+ disconnect_from_host();
+ }
+ return;
+ }
+
+ if (_connection.is_null())
+ return; // Not connected.
+
+ switch (_tcp->get_status()) {
+ case StreamPeerTCP::STATUS_NONE:
+ // Clean close
+ _on_error();
+ disconnect_from_host();
+ break;
+ case StreamPeerTCP::STATUS_CONNECTED: {
+ Ref<StreamPeerSSL> ssl;
+ if (_use_ssl) {
+ if (_connection == _tcp) {
+ // Start SSL handshake
+ ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create());
+ ERR_EXPLAIN("SSL is not available in this build");
+ ERR_FAIL_COND(ssl.is_null());
+ ssl->set_blocking_handshake_enabled(false);
+ if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) {
+ _on_error();
+ disconnect_from_host();
+ 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) {
+ _on_error();
+ disconnect_from_host();
+ return; // Error.
+ }
+ }
+ // Do websocket handshake.
+ _do_handshake();
+ } break;
+ case StreamPeerTCP::STATUS_ERROR:
+ _on_error();
+ disconnect_from_host();
+ 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));
+ _request = "";
+ _response = "";
+ _key = "";
+ _host = "";
+ _use_ssl = false;
+ _requested = 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_EXPLAIN("Buffers sizes can only be set before listening or connecting");
+ ERR_FAIL_COND_V(_ctx != 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;
+}
+
+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);
+
+ _ctx = NULL;
+ _peer.instance();
+ _tcp.instance();
+ _requested = 0;
+}
+
+WSLClient::~WSLClient() {
+
+ _peer->close_now();
+ _peer->invalidate();
+ disconnect_from_host();
+}
+
+#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h
new file mode 100644
index 0000000000..1ead88f60b
--- /dev/null
+++ b/modules/websocket/wsl_client.h
@@ -0,0 +1,85 @@
+/*************************************************************************/
+/* wsl_client.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 WSLCLIENT_H
+#define WSLCLIENT_H
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "core/error_list.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 WSLClient : public WebSocketClient {
+
+ GDCIIMPL(WSLClient, WebSocketClient);
+
+private:
+ int _in_buf_size;
+ int _in_pkt_size;
+ int _out_buf_size;
+ int _out_pkt_size;
+ wslay_event_context_ptr _ctx;
+ Ref<WSLPeer> _peer;
+ // XXX we could use HTTPClient with some hacking instead...
+ Ref<StreamPeerTCP> _tcp;
+ CharString _request;
+ String _response;
+ String _key;
+ String _host;
+ PoolVector<String> _protocols;
+ Ref<StreamPeer> _connection;
+ int _requested;
+ 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>());
+ int get_max_packet_size() const;
+ Ref<WebSocketPeer> get_peer(int p_peer_id) const;
+ void disconnect_from_host(int p_code = 1000, String p_reason = "");
+ IP_Address get_connected_host() const;
+ uint16_t get_connected_port() const;
+ virtual ConnectionStatus get_connection_status() const;
+ virtual void poll();
+
+ WSLClient();
+ ~WSLClient();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // WSLCLIENT_H
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
new file mode 100644
index 0000000000..b11bd2b70f
--- /dev/null
+++ b/modules/websocket/wsl_peer.cpp
@@ -0,0 +1,339 @@
+/*************************************************************************/
+/* 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 "wsl_peer.h"
+
+#include "wsl_client.h"
+#include "wsl_server.h"
+
+#include "core/math/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/wsl_peer.h b/modules/websocket/wsl_peer.h
new file mode 100644
index 0000000000..d51b304fe1
--- /dev/null
+++ b/modules/websocket/wsl_peer.h
@@ -0,0 +1,120 @@
+/*************************************************************************/
+/* wsl_peer.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 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 "packet_buffer.h"
+#include "websocket_peer.h"
+#include "wslay/wslay.h"
+
+#define WSL_MAX_HEADER_SIZE 4096
+
+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:
+ 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;
+
+ PoolVector<uint8_t> _packet_buffer;
+
+ WriteMode write_mode;
+
+public:
+ int close_code;
+ String close_reason;
+ 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;
+ virtual uint16_t get_connected_port() const;
+
+ virtual WriteMode get_write_mode() const;
+ virtual void set_write_mode(WriteMode p_mode);
+ virtual bool was_string_packet() const;
+
+ void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size);
+ Error parse_message(const wslay_event_on_msg_recv_arg *arg);
+ void invalidate();
+
+ WSLPeer();
+ ~WSLPeer();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // LSWPEER_H
diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp
new file mode 100644
index 0000000000..66f6a7e86e
--- /dev/null
+++ b/modules/websocket/wsl_server.cpp
@@ -0,0 +1,272 @@
+/*************************************************************************/
+/* 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 "wsl_server.h"
+#include "core/os/os.h"
+#include "core/project_settings.h"
+
+bool WSLServer::PendingPeer::_parse_request(String &r_key) {
+ Vector<String> psa = request.trim_suffix("\r\n\r\n").split("\r\n");
+ int len = psa.size();
+ if (len < 4) {
+ ERR_EXPLAIN("Not enough response headers.");
+ ERR_FAIL_V(false);
+ }
+
+ Vector<String> req = psa[0].split(" ", false);
+ if (req.size() < 2) {
+ ERR_EXPLAIN("Invalid protocol or status code.");
+ ERR_FAIL_V(false);
+ }
+ // Wrong protocol
+ if (req[0] != "GET" || req[2] != "HTTP/1.1") {
+ ERR_EXPLAIN("Invalid method or HTTP version.");
+ ERR_FAIL_V(false);
+ }
+
+ Map<String, String> headers;
+ for (int i = 1; i < len; i++) {
+ Vector<String> header = psa[i].split(":", false, 1);
+ if (header.size() != 2) {
+ ERR_EXPLAIN("Invalid header -> " + psa[i]);
+ ERR_FAIL_V(false);
+ }
+ String name = header[0].to_lower();
+ String value = header[1].strip_edges();
+ if (headers.has(name))
+ headers[name] += "," + value;
+ else
+ headers[name] = value;
+ }
+#define _WLS_CHECK(NAME, VALUE) \
+ ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); \
+ ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false);
+#define _WLS_CHECK_EX(NAME) \
+ ERR_EXPLAIN("Missing header '" + String(NAME) + "'."); \
+ ERR_FAIL_COND_V(!headers.has(NAME), false);
+ _WLS_CHECK("upgrade", "websocket");
+ _WLS_CHECK("sec-websocket-version", "13");
+ _WLS_CHECK_EX("sec-websocket-key");
+ _WLS_CHECK_EX("connection");
+#undef _WLS_CHECK_EX
+#undef _WLS_CHECK
+ r_key = headers["sec-websocket-key"];
+ return true;
+}
+
+Error WSLServer::PendingPeer::do_handshake() {
+ if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT)
+ return ERR_TIMEOUT;
+ if (!has_request) {
+ uint8_t byte = 0;
+ int read = 0;
+ while (true) {
+ Error err = connection->get_partial_data(&byte, 1, read);
+ if (err != OK) // Got an error
+ return FAILED;
+ else if (read != 1) // Busy, wait next poll
+ return ERR_BUSY;
+ request += byte;
+
+ if (request.size() > WSL_MAX_HEADER_SIZE) {
+ ERR_EXPLAIN("Response headers too big");
+ ERR_FAIL_V(ERR_OUT_OF_MEMORY);
+ }
+ if (request.ends_with("\r\n\r\n")) {
+ if (!_parse_request(key)) {
+ return FAILED;
+ }
+ String r = "HTTP/1.1 101 Switching Protocols\r\n";
+ r += "Upgrade: websocket\r\n";
+ r += "Connection: Upgrade\r\n";
+ r += "Sec-WebSocket-Accept: " + WSLPeer::compute_key_response(key) + "\r\n";
+ r += "\r\n";
+ response = r.utf8();
+ has_request = true;
+ WARN_PRINTS("Parsed, " + key);
+ break;
+ }
+ }
+ }
+ 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;
+ _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();
+ 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, "");
+ }
+ 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_EXPLAIN("Buffers sizes can only be set before listening or connecting");
+ ERR_FAIL_COND_V(_server->is_listening(), 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;
+}
+
+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/wsl_server.h b/modules/websocket/wsl_server.h
new file mode 100644
index 0000000000..ca627c1fc3
--- /dev/null
+++ b/modules/websocket/wsl_server.h
@@ -0,0 +1,100 @@
+/*************************************************************************/
+/* wsl_server.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 WSLSERVER_H
+#define WSLSERVER_H
+
+#ifndef JAVASCRIPT_ENABLED
+
+#include "websocket_server.h"
+#include "wsl_peer.h"
+
+#include "core/io/stream_peer_tcp.h"
+#include "core/io/tcp_server.h"
+
+#define WSL_SERVER_TIMEOUT 1000
+
+class WSLServer : public WebSocketServer {
+
+ GDCIIMPL(WSLServer, WebSocketServer);
+
+private:
+ class PendingPeer : public Reference {
+
+ private:
+ bool _parse_request(String &r_key);
+
+ public:
+ Ref<StreamPeer> connection;
+
+ int time;
+ String request;
+ String key;
+ bool has_request;
+ CharString response;
+ int response_sent;
+
+ PendingPeer() {
+ time = 0;
+ has_request = false;
+ response_sent = 0;
+ }
+
+ Error do_handshake();
+ };
+
+ int _in_buf_size;
+ int _in_pkt_size;
+ int _out_buf_size;
+ int _out_pkt_size;
+
+ List<Ref<PendingPeer> > _pending;
+ Ref<TCP_Server> _server;
+
+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);
+ void stop();
+ bool is_listening() const;
+ int get_max_packet_size() const;
+ bool has_peer(int p_id) const;
+ Ref<WebSocketPeer> get_peer(int p_id) const;
+ IP_Address get_peer_address(int p_peer_id) const;
+ 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();
+
+ WSLServer();
+ ~WSLServer();
+};
+
+#endif // JAVASCRIPT_ENABLED
+
+#endif // WSLSERVER_H