| 1 | /**************************************************************************/ |
| 2 | /* remote_debugger_peer.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "remote_debugger_peer.h" |
| 32 | |
| 33 | #include "core/config/project_settings.h" |
| 34 | #include "core/io/marshalls.h" |
| 35 | #include "core/os/os.h" |
| 36 | |
| 37 | bool RemoteDebuggerPeerTCP::is_peer_connected() { |
| 38 | return connected; |
| 39 | } |
| 40 | |
| 41 | bool RemoteDebuggerPeerTCP::has_message() { |
| 42 | return in_queue.size() > 0; |
| 43 | } |
| 44 | |
| 45 | Array RemoteDebuggerPeerTCP::get_message() { |
| 46 | MutexLock lock(mutex); |
| 47 | ERR_FAIL_COND_V(!has_message(), Array()); |
| 48 | Array out = in_queue[0]; |
| 49 | in_queue.pop_front(); |
| 50 | return out; |
| 51 | } |
| 52 | |
| 53 | Error RemoteDebuggerPeerTCP::put_message(const Array &p_arr) { |
| 54 | MutexLock lock(mutex); |
| 55 | if (out_queue.size() >= max_queued_messages) { |
| 56 | return ERR_OUT_OF_MEMORY; |
| 57 | } |
| 58 | |
| 59 | out_queue.push_back(p_arr); |
| 60 | return OK; |
| 61 | } |
| 62 | |
| 63 | int RemoteDebuggerPeerTCP::get_max_message_size() const { |
| 64 | return 8 << 20; // 8 MiB |
| 65 | } |
| 66 | |
| 67 | void RemoteDebuggerPeerTCP::close() { |
| 68 | running = false; |
| 69 | if (thread.is_started()) { |
| 70 | thread.wait_to_finish(); |
| 71 | } |
| 72 | tcp_client->disconnect_from_host(); |
| 73 | out_buf.clear(); |
| 74 | in_buf.clear(); |
| 75 | } |
| 76 | |
| 77 | RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) { |
| 78 | // This means remote debugger takes 16 MiB just because it exists... |
| 79 | in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size). |
| 80 | out_buf.resize(8 << 20); // 8 MiB should be way more than enough |
| 81 | tcp_client = p_tcp; |
| 82 | if (tcp_client.is_valid()) { // Attaching to an already connected stream. |
| 83 | connected = true; |
| 84 | running = true; |
| 85 | thread.start(_thread_func, this); |
| 86 | } else { |
| 87 | tcp_client.instantiate(); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() { |
| 92 | close(); |
| 93 | } |
| 94 | |
| 95 | void RemoteDebuggerPeerTCP::_write_out() { |
| 96 | while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED && tcp_client->wait(NetSocket::POLL_TYPE_OUT) == OK) { |
| 97 | uint8_t *buf = out_buf.ptrw(); |
| 98 | if (out_left <= 0) { |
| 99 | if (out_queue.size() == 0) { |
| 100 | break; // Nothing left to send |
| 101 | } |
| 102 | mutex.lock(); |
| 103 | Variant var = out_queue[0]; |
| 104 | out_queue.pop_front(); |
| 105 | mutex.unlock(); |
| 106 | int size = 0; |
| 107 | Error err = encode_variant(var, nullptr, size); |
| 108 | ERR_CONTINUE(err != OK || size > out_buf.size() - 4); // 4 bytes separator. |
| 109 | encode_uint32(size, buf); |
| 110 | encode_variant(var, buf + 4, size); |
| 111 | out_left = size + 4; |
| 112 | out_pos = 0; |
| 113 | } |
| 114 | int sent = 0; |
| 115 | tcp_client->put_partial_data(buf + out_pos, out_left, sent); |
| 116 | out_left -= sent; |
| 117 | out_pos += sent; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | void RemoteDebuggerPeerTCP::_read_in() { |
| 122 | while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED && tcp_client->wait(NetSocket::POLL_TYPE_IN) == OK) { |
| 123 | uint8_t *buf = in_buf.ptrw(); |
| 124 | if (in_left <= 0) { |
| 125 | if (in_queue.size() > max_queued_messages) { |
| 126 | break; // Too many messages already in queue. |
| 127 | } |
| 128 | if (tcp_client->get_available_bytes() < 4) { |
| 129 | break; // Need 4 more bytes. |
| 130 | } |
| 131 | uint32_t size = 0; |
| 132 | int read = 0; |
| 133 | Error err = tcp_client->get_partial_data((uint8_t *)&size, 4, read); |
| 134 | ERR_CONTINUE(read != 4 || err != OK || size > (uint32_t)in_buf.size()); |
| 135 | in_left = size; |
| 136 | in_pos = 0; |
| 137 | } |
| 138 | int read = 0; |
| 139 | tcp_client->get_partial_data(buf + in_pos, in_left, read); |
| 140 | in_left -= read; |
| 141 | in_pos += read; |
| 142 | if (in_left == 0) { |
| 143 | Variant var; |
| 144 | Error err = decode_variant(var, buf, in_pos, &read); |
| 145 | ERR_CONTINUE(read != in_pos || err != OK); |
| 146 | ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array." ); |
| 147 | mutex.lock(); |
| 148 | in_queue.push_back(var); |
| 149 | mutex.unlock(); |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) { |
| 155 | IPAddress ip; |
| 156 | if (p_host.is_valid_ip_address()) { |
| 157 | ip = p_host; |
| 158 | } else { |
| 159 | ip = IP::get_singleton()->resolve_hostname(p_host); |
| 160 | } |
| 161 | |
| 162 | int port = p_port; |
| 163 | |
| 164 | const int tries = 6; |
| 165 | const int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 }; |
| 166 | |
| 167 | tcp_client->connect_to_host(ip, port); |
| 168 | |
| 169 | for (int i = 0; i < tries; i++) { |
| 170 | tcp_client->poll(); |
| 171 | if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) { |
| 172 | print_verbose("Remote Debugger: Connected!" ); |
| 173 | break; |
| 174 | } else { |
| 175 | const int ms = waits[i]; |
| 176 | OS::get_singleton()->delay_usec(ms * 1000); |
| 177 | print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec." ); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { |
| 182 | ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + "." ); |
| 183 | return FAILED; |
| 184 | } |
| 185 | connected = true; |
| 186 | running = true; |
| 187 | thread.start(_thread_func, this); |
| 188 | return OK; |
| 189 | } |
| 190 | |
| 191 | void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) { |
| 192 | // Update in time for 144hz monitors |
| 193 | const uint64_t min_tick = 6900; |
| 194 | RemoteDebuggerPeerTCP *peer = static_cast<RemoteDebuggerPeerTCP *>(p_ud); |
| 195 | while (peer->running && peer->is_peer_connected()) { |
| 196 | uint64_t ticks_usec = OS::get_singleton()->get_ticks_usec(); |
| 197 | peer->_poll(); |
| 198 | if (!peer->is_peer_connected()) { |
| 199 | break; |
| 200 | } |
| 201 | ticks_usec = OS::get_singleton()->get_ticks_usec() - ticks_usec; |
| 202 | if (ticks_usec < min_tick) { |
| 203 | OS::get_singleton()->delay_usec(min_tick - ticks_usec); |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | void RemoteDebuggerPeerTCP::poll() { |
| 209 | // Nothing to do, polling is done in thread. |
| 210 | } |
| 211 | |
| 212 | void RemoteDebuggerPeerTCP::_poll() { |
| 213 | tcp_client->poll(); |
| 214 | if (connected) { |
| 215 | _write_out(); |
| 216 | _read_in(); |
| 217 | connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) { |
| 222 | ERR_FAIL_COND_V(!p_uri.begins_with("tcp://" ), nullptr); |
| 223 | |
| 224 | String debug_host = p_uri.replace("tcp://" , "" ); |
| 225 | uint16_t debug_port = 6007; |
| 226 | |
| 227 | if (debug_host.contains(":" )) { |
| 228 | int sep_pos = debug_host.rfind(":" ); |
| 229 | debug_port = debug_host.substr(sep_pos + 1).to_int(); |
| 230 | debug_host = debug_host.substr(0, sep_pos); |
| 231 | } |
| 232 | |
| 233 | RemoteDebuggerPeerTCP *peer = memnew(RemoteDebuggerPeerTCP); |
| 234 | Error err = peer->connect_to_host(debug_host, debug_port); |
| 235 | if (err != OK) { |
| 236 | memdelete(peer); |
| 237 | return nullptr; |
| 238 | } |
| 239 | return peer; |
| 240 | } |
| 241 | |
| 242 | RemoteDebuggerPeer::RemoteDebuggerPeer() { |
| 243 | max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages" ); |
| 244 | } |
| 245 | |