1/**************************************************************************/
2/* stream_peer_tcp.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 "stream_peer_tcp.h"
32
33#include "core/config/project_settings.h"
34
35Error StreamPeerTCP::poll() {
36 if (status == STATUS_CONNECTED) {
37 Error err;
38 err = _sock->poll(NetSocket::POLL_TYPE_IN, 0);
39 if (err == OK) {
40 // FIN received
41 if (_sock->get_available_bytes() == 0) {
42 disconnect_from_host();
43 return OK;
44 }
45 }
46 // Also poll write
47 err = _sock->poll(NetSocket::POLL_TYPE_IN_OUT, 0);
48 if (err != OK && err != ERR_BUSY) {
49 // Got an error
50 disconnect_from_host();
51 status = STATUS_ERROR;
52 return err;
53 }
54 } else if (status != STATUS_CONNECTING) {
55 return OK;
56 }
57
58 Error err = _sock->connect_to_host(peer_host, peer_port);
59
60 if (err == OK) {
61 status = STATUS_CONNECTED;
62 return OK;
63 } else if (err == ERR_BUSY) {
64 // Check for connect timeout
65 if (OS::get_singleton()->get_ticks_msec() > timeout) {
66 disconnect_from_host();
67 status = STATUS_ERROR;
68 return ERR_CONNECTION_ERROR;
69 }
70 // Still trying to connect
71 return OK;
72 }
73
74 disconnect_from_host();
75 status = STATUS_ERROR;
76 return ERR_CONNECTION_ERROR;
77}
78
79void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port) {
80 _sock = p_sock;
81 _sock->set_blocking_enabled(false);
82
83 timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
84 status = STATUS_CONNECTED;
85
86 peer_host = p_host;
87 peer_port = p_port;
88}
89
90Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) {
91 ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
92 ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE);
93 ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive).");
94
95 IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
96 if (p_host.is_wildcard()) {
97 ip_type = IP::TYPE_ANY;
98 }
99 Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
100 if (err != OK) {
101 return err;
102 }
103 _sock->set_blocking_enabled(false);
104 return _sock->bind(p_host, p_port);
105}
106
107Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) {
108 ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
109 ERR_FAIL_COND_V(status != STATUS_NONE, ERR_ALREADY_IN_USE);
110 ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER);
111 ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, ERR_INVALID_PARAMETER, "The remote port number must be between 1 and 65535 (inclusive).");
112
113 if (!_sock->is_open()) {
114 IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
115 Error err = _sock->open(NetSocket::TYPE_TCP, ip_type);
116 if (err != OK) {
117 return err;
118 }
119 _sock->set_blocking_enabled(false);
120 }
121
122 timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000);
123 Error err = _sock->connect_to_host(p_host, p_port);
124
125 if (err == OK) {
126 status = STATUS_CONNECTED;
127 } else if (err == ERR_BUSY) {
128 status = STATUS_CONNECTING;
129 } else {
130 ERR_PRINT("Connection to remote host failed!");
131 disconnect_from_host();
132 return FAILED;
133 }
134
135 peer_host = p_host;
136 peer_port = p_port;
137
138 return OK;
139}
140
141Error StreamPeerTCP::write(const uint8_t *p_data, int p_bytes, int &r_sent, bool p_block) {
142 ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE);
143
144 if (status != STATUS_CONNECTED) {
145 return FAILED;
146 }
147
148 Error err;
149 int data_to_send = p_bytes;
150 const uint8_t *offset = p_data;
151 int total_sent = 0;
152
153 while (data_to_send) {
154 int sent_amount = 0;
155 err = _sock->send(offset, data_to_send, sent_amount);
156
157 if (err != OK) {
158 if (err != ERR_BUSY) {
159 disconnect_from_host();
160 return FAILED;
161 }
162
163 if (!p_block) {
164 r_sent = total_sent;
165 return OK;
166 }
167
168 // Block and wait for the socket to accept more data
169 err = _sock->poll(NetSocket::POLL_TYPE_OUT, -1);
170 if (err != OK) {
171 disconnect_from_host();
172 return FAILED;
173 }
174 } else {
175 data_to_send -= sent_amount;
176 offset += sent_amount;
177 total_sent += sent_amount;
178 }
179 }
180
181 r_sent = total_sent;
182
183 return OK;
184}
185
186Error StreamPeerTCP::read(uint8_t *p_buffer, int p_bytes, int &r_received, bool p_block) {
187 if (status != STATUS_CONNECTED) {
188 return FAILED;
189 }
190
191 Error err;
192 int to_read = p_bytes;
193 int total_read = 0;
194 r_received = 0;
195
196 while (to_read) {
197 int read = 0;
198 err = _sock->recv(p_buffer + total_read, to_read, read);
199
200 if (err != OK) {
201 if (err != ERR_BUSY) {
202 disconnect_from_host();
203 return FAILED;
204 }
205
206 if (!p_block) {
207 r_received = total_read;
208 return OK;
209 }
210
211 err = _sock->poll(NetSocket::POLL_TYPE_IN, -1);
212
213 if (err != OK) {
214 disconnect_from_host();
215 return FAILED;
216 }
217
218 } else if (read == 0) {
219 disconnect_from_host();
220 r_received = total_read;
221 return ERR_FILE_EOF;
222
223 } else {
224 to_read -= read;
225 total_read += read;
226
227 if (!p_block) {
228 r_received = total_read;
229 return OK;
230 }
231 }
232 }
233
234 r_received = total_read;
235
236 return OK;
237}
238
239void StreamPeerTCP::set_no_delay(bool p_enabled) {
240 ERR_FAIL_COND(!_sock.is_valid() || !_sock->is_open());
241 _sock->set_tcp_no_delay_enabled(p_enabled);
242}
243
244StreamPeerTCP::Status StreamPeerTCP::get_status() const {
245 return status;
246}
247
248void StreamPeerTCP::disconnect_from_host() {
249 if (_sock.is_valid() && _sock->is_open()) {
250 _sock->close();
251 }
252
253 timeout = 0;
254 status = STATUS_NONE;
255 peer_host = IPAddress();
256 peer_port = 0;
257}
258
259Error StreamPeerTCP::wait(NetSocket::PollType p_type, int p_timeout) {
260 ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE);
261 return _sock->poll(p_type, p_timeout);
262}
263
264Error StreamPeerTCP::put_data(const uint8_t *p_data, int p_bytes) {
265 int total;
266 return write(p_data, p_bytes, total, true);
267}
268
269Error StreamPeerTCP::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) {
270 return write(p_data, p_bytes, r_sent, false);
271}
272
273Error StreamPeerTCP::get_data(uint8_t *p_buffer, int p_bytes) {
274 int total;
275 return read(p_buffer, p_bytes, total, true);
276}
277
278Error StreamPeerTCP::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) {
279 return read(p_buffer, p_bytes, r_received, false);
280}
281
282int StreamPeerTCP::get_available_bytes() const {
283 ERR_FAIL_COND_V(!_sock.is_valid(), -1);
284 return _sock->get_available_bytes();
285}
286
287IPAddress StreamPeerTCP::get_connected_host() const {
288 return peer_host;
289}
290
291int StreamPeerTCP::get_connected_port() const {
292 return peer_port;
293}
294
295int StreamPeerTCP::get_local_port() const {
296 uint16_t local_port;
297 _sock->get_socket_address(nullptr, &local_port);
298 return local_port;
299}
300
301Error StreamPeerTCP::_connect(const String &p_address, int p_port) {
302 IPAddress ip;
303 if (p_address.is_valid_ip_address()) {
304 ip = p_address;
305 } else {
306 ip = IP::get_singleton()->resolve_hostname(p_address);
307 if (!ip.is_valid()) {
308 return ERR_CANT_RESOLVE;
309 }
310 }
311
312 return connect_to_host(ip, p_port);
313}
314
315void StreamPeerTCP::_bind_methods() {
316 ClassDB::bind_method(D_METHOD("bind", "port", "host"), &StreamPeerTCP::bind, DEFVAL("*"));
317 ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &StreamPeerTCP::_connect);
318 ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTCP::poll);
319 ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTCP::get_status);
320 ClassDB::bind_method(D_METHOD("get_connected_host"), &StreamPeerTCP::get_connected_host);
321 ClassDB::bind_method(D_METHOD("get_connected_port"), &StreamPeerTCP::get_connected_port);
322 ClassDB::bind_method(D_METHOD("get_local_port"), &StreamPeerTCP::get_local_port);
323 ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerTCP::disconnect_from_host);
324 ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &StreamPeerTCP::set_no_delay);
325
326 BIND_ENUM_CONSTANT(STATUS_NONE);
327 BIND_ENUM_CONSTANT(STATUS_CONNECTING);
328 BIND_ENUM_CONSTANT(STATUS_CONNECTED);
329 BIND_ENUM_CONSTANT(STATUS_ERROR);
330}
331
332StreamPeerTCP::StreamPeerTCP() :
333 _sock(Ref<NetSocket>(NetSocket::create())) {
334}
335
336StreamPeerTCP::~StreamPeerTCP() {
337 disconnect_from_host();
338}
339