mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 21:51:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			341 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*************************************************************************/
 | |
| /*  wsl_peer.cpp                                                         */
 | |
| /*************************************************************************/
 | |
| /*                       This file is part of:                           */
 | |
| /*                           GODOT ENGINE                                */
 | |
| /*                      https://godotengine.org                          */
 | |
| /*************************************************************************/
 | |
| /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
 | |
| /* Copyright (c) 2014-2021 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());
 | |
| 	Vector<uint8_t> bkey;
 | |
| 	int len = 16; // 16 bytes, as per RFC
 | |
| 	bkey.resize(len);
 | |
| 	uint8_t *w = bkey.ptrw();
 | |
| 	for (int i = 0; i < len; i++) {
 | |
| 		w[i] = (uint8_t)rng.randi_range(0, 255);
 | |
| 	}
 | |
| 	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 = nullptr;
 | |
| }
 | |
| 
 | |
| bool WSLPeer::_wsl_poll(struct PeerData *p_data) {
 | |
| 	p_data->polling = true;
 | |
| 	int err = 0;
 | |
| 	if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) {
 | |
| 		print_verbose("Websocket (wslay) poll error: " + itos(err));
 | |
| 		p_data->destroy = true;
 | |
| 	}
 | |
| 	p_data->polling = false;
 | |
| 
 | |
| 	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 || peer_data->closing) {
 | |
| 		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,
 | |
| 	nullptr, /* on_frame_recv_start_callback */
 | |
| 	nullptr, /* on_frame_recv_callback */
 | |
| 	nullptr, /* 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 != nullptr);
 | |
| 	ERR_FAIL_COND(p_data == nullptr);
 | |
| 
 | |
| 	_in_buffer.resize(p_in_pkt_size, p_in_buf_size);
 | |
| 	_packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size)));
 | |
| 
 | |
| 	_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 = nullptr;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	if (wslay_event_send(_data->ctx) < 0) {
 | |
| 		close_now();
 | |
| 		return FAILED;
 | |
| 	}
 | |
| 	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;
 | |
| 	uint8_t *rw = _packet_buffer.ptrw();
 | |
| 	_in_buffer.read_packet(rw, _packet_buffer.size(), &_is_string, read);
 | |
| 
 | |
| 	*r_buffer = rw;
 | |
| 	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 != nullptr;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 		_data->closing = true;
 | |
| 	}
 | |
| 
 | |
| 	_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::set_no_delay(bool p_enabled) {
 | |
| 	ERR_FAIL_COND(!is_connected_to_host() || _data->tcp.is_null());
 | |
| 	_data->tcp->set_no_delay(p_enabled);
 | |
| }
 | |
| 
 | |
| void WSLPeer::invalidate() {
 | |
| 	if (_data) {
 | |
| 		_data->valid = false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| WSLPeer::WSLPeer() {
 | |
| }
 | |
| 
 | |
| WSLPeer::~WSLPeer() {
 | |
| 	close();
 | |
| 	invalidate();
 | |
| 	_wsl_destroy(&_data);
 | |
| 	_data = nullptr;
 | |
| }
 | |
| 
 | |
| #endif // JAVASCRIPT_ENABLED
 | 
