1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "connection.h"
24
25#include <framework/core/application.h>
26#include <framework/core/eventdispatcher.h>
27#include <boost/asio.hpp>
28#include <framework/util/stats.h>
29#include <framework/util/extras.h>
30#include <chrono>
31
32asio::io_service g_ioService;
33std::list<std::shared_ptr<asio::streambuf>> Connection::m_outputStreams;
34
35Connection::Connection() :
36 m_readTimer(g_ioService),
37 m_writeTimer(g_ioService),
38 m_delayedWriteTimer(g_ioService),
39 m_resolver(g_ioService),
40 m_socket(g_ioService)
41{
42 m_connected = false;
43 m_connecting = false;
44}
45
46Connection::~Connection()
47{
48 VALIDATE(!g_app.isTerminated());
49 close();
50}
51
52void Connection::poll()
53{
54 AutoStat s(STATS_MAIN, "PollConnection");
55 // reset must always be called prior to poll
56 g_ioService.reset();
57 g_ioService.poll();
58}
59
60void Connection::terminate()
61{
62 g_ioService.stop();
63 m_outputStreams.clear();
64}
65
66void Connection::close()
67{
68 if(!m_connected && !m_connecting)
69 return;
70
71 // flush send data before disconnecting on clean connections
72 if(m_connected && !m_error && m_outputStream)
73 internal_write();
74
75 m_connecting = false;
76 m_connected = false;
77 m_connectCallback = nullptr;
78 m_errorCallback = nullptr;
79 m_recvCallback = nullptr;
80
81 m_resolver.cancel();
82 m_readTimer.cancel();
83 m_writeTimer.cancel();
84 m_delayedWriteTimer.cancel();
85
86 if(m_socket.is_open()) {
87 boost::system::error_code ec;
88 m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
89 m_socket.close();
90 }
91}
92
93void Connection::connect(const std::string& host, uint16 port, const std::function<void()>& connectCallback)
94{
95 m_connected = false;
96 m_connecting = true;
97 m_error.clear();
98 m_connectCallback = connectCallback;
99
100 asio::ip::tcp::resolver::query query(host, stdext::unsafe_cast<std::string>(port));
101 m_resolver.async_resolve(query, std::bind(&Connection::onResolve, asConnection(), std::placeholders::_1, std::placeholders::_2));
102
103 m_readTimer.cancel();
104 m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT));
105 m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
106}
107
108void Connection::internal_connect(asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
109{
110 m_socket.async_connect(*endpointIterator, std::bind(&Connection::onConnect, asConnection(), std::placeholders::_1));
111
112 m_readTimer.cancel();
113 m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT));
114 m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
115}
116
117void Connection::write(uint8* buffer, size_t size)
118{
119 if(!m_connected)
120 return;
121
122 // we can't send the data right away, otherwise we could create tcp congestion
123 if(!m_outputStream) {
124 if(!m_outputStreams.empty()) {
125 m_outputStream = m_outputStreams.front();
126 m_outputStreams.pop_front();
127 } else
128 m_outputStream = std::shared_ptr<asio::streambuf>(new asio::streambuf);
129
130 m_delayedWriteTimer.cancel();
131 m_delayedWriteTimer.expires_from_now(std::chrono::milliseconds(0));
132 m_delayedWriteTimer.async_wait(std::bind(&Connection::onCanWrite, asConnection(), std::placeholders::_1));
133 }
134
135 std::ostream os(m_outputStream.get());
136 os.write((const char*)buffer, size);
137 os.flush();
138}
139
140void Connection::internal_write()
141{
142 if(!m_connected)
143 return;
144
145 std::shared_ptr<asio::streambuf> outputStream = m_outputStream;
146 m_outputStream = nullptr;
147
148 asio::async_write(m_socket,
149 *outputStream,
150 std::bind(&Connection::onWrite, asConnection(), std::placeholders::_1, std::placeholders::_2, outputStream));
151
152 m_writeTimer.cancel();
153 m_writeTimer.expires_from_now(std::chrono::seconds(WRITE_TIMEOUT));
154 m_writeTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
155}
156
157void Connection::read(uint32 bytes, const RecvCallback& callback)
158{
159 if(!m_connected)
160 return;
161
162 m_recvCallback = callback;
163
164 asio::async_read(m_socket,
165 asio::buffer(m_inputStream.prepare(bytes)),
166 std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
167
168 m_readTimer.cancel();
169 m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT));
170 m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
171}
172
173void Connection::read_until(const std::string& what, const RecvCallback& callback)
174{
175 if(!m_connected)
176 return;
177
178 m_recvCallback = callback;
179
180 asio::async_read_until(m_socket,
181 m_inputStream,
182 what.c_str(),
183 std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
184
185 m_readTimer.cancel();
186 m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT));
187 m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
188}
189
190void Connection::read_some(const RecvCallback& callback)
191{
192 if(!m_connected)
193 return;
194
195 m_recvCallback = callback;
196
197 m_socket.async_read_some(asio::buffer(m_inputStream.prepare(RECV_BUFFER_SIZE)),
198 std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2));
199
200 m_readTimer.cancel();
201 m_readTimer.expires_from_now(std::chrono::seconds(READ_TIMEOUT));
202 m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1));
203}
204
205void Connection::onResolve(const boost::system::error_code& error, asio::ip::basic_resolver<asio::ip::tcp>::iterator endpointIterator)
206{
207 m_readTimer.cancel();
208
209 if(error == asio::error::operation_aborted)
210 return;
211
212 if(!error)
213 internal_connect(endpointIterator);
214 else
215 handleError(error);
216}
217
218void Connection::onConnect(const boost::system::error_code& error)
219{
220 m_readTimer.cancel();
221 m_activityTimer.restart();
222
223 if(error == asio::error::operation_aborted)
224 return;
225
226 if(!error) {
227 m_connected = true;
228
229 // disable nagle's algorithm, this make the game play smoother
230 boost::asio::ip::tcp::no_delay option(true);
231 m_socket.set_option(option);
232 boost::system::error_code ecc;
233 m_socket.set_option(boost::asio::socket_base::send_buffer_size(524288), ecc);
234 m_socket.set_option(boost::asio::socket_base::receive_buffer_size(524288), ecc);
235
236 if(m_connectCallback)
237 m_connectCallback();
238 } else
239 handleError(error);
240
241 m_connecting = false;
242}
243
244void Connection::onCanWrite(const boost::system::error_code& error)
245{
246 m_delayedWriteTimer.cancel();
247
248 if(error == asio::error::operation_aborted)
249 return;
250
251 if(m_connected)
252 internal_write();
253}
254
255void Connection::onWrite(const boost::system::error_code& error, size_t writeSize, std::shared_ptr<asio::streambuf> outputStream)
256{
257 m_writeTimer.cancel();
258
259 if(error == asio::error::operation_aborted)
260 return;
261
262 // free output stream and store for using it again later
263 outputStream->consume(outputStream->size());
264 m_outputStreams.push_back(outputStream);
265
266 if(m_connected && error)
267 handleError(error);
268}
269
270void Connection::onRecv(const boost::system::error_code& error, size_t recvSize)
271{
272 m_readTimer.cancel();
273 m_activityTimer.restart();
274
275 if(error == asio::error::operation_aborted)
276 return;
277
278 if(m_connected) {
279 if(!error) {
280 if(m_recvCallback) {
281 const char* header = boost::asio::buffer_cast<const char*>(m_inputStream.data());
282 m_recvCallback((uint8*)header, recvSize);
283 }
284 } else
285 handleError(error);
286 }
287
288 if(!error)
289 m_inputStream.consume(recvSize);
290}
291
292void Connection::onTimeout(const boost::system::error_code& error)
293{
294 if(error == asio::error::operation_aborted)
295 return;
296
297 handleError(asio::error::timed_out);
298}
299
300void Connection::handleError(const boost::system::error_code& error)
301{
302 if(error == asio::error::operation_aborted)
303 return;
304
305 m_error = error;
306 if(m_errorCallback)
307 m_errorCallback(error);
308 if(m_connected || m_connecting)
309 close();
310}
311
312int Connection::getIp()
313{
314 boost::system::error_code error;
315 const boost::asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error);
316 if(!error)
317 return boost::asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong());
318
319 g_logger.error("Getting remote ip");
320 return 0;
321}
322