summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/ipc/tcp_server.cpp
blob: 2c8f11e384bb03e17cad03d27bd664686c12d4f1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// license:BSD-3-Clause
// copyright-holders:Inaki Baz Castillo,Miodrag Milanovic

#include "emu.h"
#include "tcp_server.h"

/* Static methods for UV callbacks. */

static inline void on_connection(uv_stream_t* handle, int status)
{
	static_cast<tcp_server*>(handle->data)->on_uv_connection(status);
}

static inline void on_close(uv_handle_t* handle)
{
	static_cast<tcp_server*>(handle->data)->on_uv_closed();
}

static inline void on_error_close(uv_handle_t* handle)
{
	delete handle;
}

/* Instance methods. */

tcp_server::tcp_server(uv_loop_t* loop, const std::string &ip, uint16_t port, int backlog)
	: m_uv_handle(nullptr),
	m_loop(nullptr),
	m_is_closing(false),
	m_local_port(0)
{
	int err;
	int flags = 0;

	m_uv_handle = new uv_tcp_t;
	m_uv_handle->data = (void*)this;
	m_loop = loop;

	err = uv_tcp_init(loop, m_uv_handle);
	if (err)
	{
		delete m_uv_handle;
		m_uv_handle = nullptr;
		throw emu_fatalerror("uv_tcp_init() failed: %s", uv_strerror(err));
	}

	struct sockaddr_storage bind_addr;


	err = uv_ip4_addr(ip.c_str(), (int)port, (struct sockaddr_in*)&bind_addr);
	if (err)
		throw emu_fatalerror("uv_ipv4_addr() failed: %s", uv_strerror(err));

	err = uv_tcp_bind(m_uv_handle, (const struct sockaddr*)&bind_addr, flags);
	if (err)
	{
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_error_close);
		throw emu_fatalerror("uv_tcp_bind() failed: %s", uv_strerror(err));
	}

	err = uv_listen((uv_stream_t*)m_uv_handle, backlog, (uv_connection_cb)on_connection);
	if (err)
	{
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_error_close);
		throw emu_fatalerror("uv_listen() failed: %s", uv_strerror(err));
	}

	// Set local address.
	if (!set_local_address())
	{
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_error_close);
		throw emu_fatalerror("error setting local IP and port");
	}
}

tcp_server::~tcp_server()
{
	if (m_uv_handle)
		delete m_uv_handle;
}

void tcp_server::close()
{
	if (m_is_closing)
		return;

	m_is_closing = true;

	// If there are no connections then close now.
	if (m_connections.empty())
	{
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_close);
	}
	// Otherwise close all the connections (but not the TCP server).
	else
	{
		osd_printf_verbose("closing %d active connections\n", (int)m_connections.size());

		for (auto it = m_connections.begin(); it != m_connections.end(); ++it)
		{
			tcp_connection* connection = *it;
			connection->close();
		}
	}
}

void tcp_server::terminate()
{
	if (m_is_closing)
		return;

	m_is_closing = true;

	// If there are no connections then close now.
	if (m_connections.empty())
	{
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_close);
	}
	// Otherwise close all the connections (but not the TCP server).
	else
	{
		osd_printf_verbose("closing %d active connections\n", (int)m_connections.size());

		for (auto it = m_connections.begin(); it != m_connections.end(); ++it)
		{
			tcp_connection* connection = *it;
			connection->terminate();
		}
	}
}

void tcp_server::send_to_all(const uint8_t* data, size_t len)
{
	// If there are no connections then close now.
	if (!m_connections.empty())
	{
		for (auto it = m_connections.begin(); it != m_connections.end(); ++it)
		{
			tcp_connection* connection = *it;
			connection->write(data, len);
		}
	}
}

void tcp_server::dump()
{
	osd_printf_verbose("[TCP, local:%s :%d, status:%s, connections:%d]",
		m_local_ip.c_str(), (uint16_t)m_local_port,
		(!m_is_closing) ? "open" : "closed",
		(int)m_connections.size());
}

const sockaddr* tcp_server::get_local_address()
{
	return (const struct sockaddr*)&m_local_addr;
}

const std::string& tcp_server::get_local_ip()
{
	return m_local_ip;
}

uint16_t tcp_server::get_local_port()
{
	return m_local_port;
}

bool tcp_server::set_local_address()
{
	int err;
	int len = sizeof(m_local_addr);

	err = uv_tcp_getsockname(m_uv_handle, (struct sockaddr*)&m_local_addr, &len);
	if (err)
	{
		osd_printf_error("uv_tcp_getsockname() failed: %s\n", uv_strerror(err));

		return false;
	}

	int family;
	GetAddressInfo((const struct sockaddr*)&m_local_addr, &family, m_local_ip, &m_local_port);

	return true;
}

inline void tcp_server::on_uv_connection(int status)
{
	if (m_is_closing)
		return;

	int err;

	if (status)
	{
		osd_printf_error("error while receiving a new TCP connection: %s\n", uv_strerror(status));

		return;
	}

	// Notify the subclass so it provides an allocated derived class of TCPConnection.
	tcp_connection* connection = nullptr;
	user_on_tcp_connection_alloc(&connection);
	if (connection == nullptr)
		osd_printf_error("tcp_server pointer was not allocated by the user\n");

	try
	{
		connection->setup(m_loop, this, &(m_local_addr), m_local_ip, m_local_port);
	}
	catch (...)
	{
		delete connection;
		return;
	}

	// Accept the connection.
	err = uv_accept((uv_stream_t*)m_uv_handle, (uv_stream_t*)connection->get_uv_handle());
	if (err)
		throw emu_fatalerror("uv_accept() failed: %s\n", uv_strerror(err));

	// Insert the TCPConnection in the set.
	m_connections.insert(connection);

	// Start receiving data.
	try
	{
		connection->start();
	}
	catch (emu_exception &error)
	{
		osd_printf_error("cannot run the TCP connection, closing the connection: %s\n", error.what());
		connection->close();
		// NOTE: Don't return here so the user won't be notified about a "onclose" for a TCP connection
		// for which there was not a previous "onnew" event.
	}

	osd_printf_verbose("new TCP connection:\n");
	connection->dump();

	// Notify the subclass.
	user_on_new_tcp_connection(connection);
}

void tcp_server::on_uv_closed()
{
	// Motify the subclass.
	user_on_tcp_server_closed();
}

void tcp_server::on_tcp_connection_closed(tcp_connection* connection, bool is_closed_by_peer)
{
	// NOTE:
	// Worst scenario is that in which this is the latest connection,
	// which is remotely closed (no tcp_server.Close() was called) and the user
	// call tcp_server.Close() on userOnTCPConnectionClosed() callback, so Close()
	// is called with zero connections and calls uv_close(), but then
	// onTCPConnectionClosed() continues and finds that isClosing is true and
	// there are zero connections, so calls uv_close() again and get a crash.
	//
	// SOLUTION:
	// Check isClosing value *before* onTCPConnectionClosed() callback.

	bool wasClosing = m_is_closing;

	osd_printf_verbose("TCP connection closed:\n");
	connection->dump();

	// Remove the TCPConnection from the set.
	m_connections.erase(connection);

	// Notify the subclass.
	user_on_tcp_connection_closed(connection, is_closed_by_peer);

	// Check if the server was closing connections, and if this is the last
	// connection then close the server now.
	if (wasClosing && m_connections.empty())
		uv_close((uv_handle_t*)m_uv_handle, (uv_close_cb)on_close);
}