diff options
Diffstat (limited to 'modules/websocket/wsl_peer.cpp')
-rw-r--r-- | modules/websocket/wsl_peer.cpp | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp new file mode 100644 index 0000000000..9d610109ed --- /dev/null +++ b/modules/websocket/wsl_peer.cpp @@ -0,0 +1,336 @@ +/*************************************************************************/ +/* 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; + + if (_data->is_server) + wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); + else + wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data); + wslay_event_config_set_max_recv_msg_length(_data->ctx, (1ULL << p_in_buf_size)); +} + +void WSLPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +WSLPeer::WriteMode WSLPeer::get_write_mode() const { + return write_mode; +} + +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() || _data->tcp.is_null(), IP_Address()); + + return _data->tcp->get_connected_host(); +} + +uint16_t WSLPeer::get_connected_port() const { + + ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), 0); + + return _data->tcp->get_connected_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 |