The Battle for Wesnoth  1.15.0-dev
send_receive_wml_helpers.ipp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 by Sergey Popov <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License 2
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #ifndef SEND_RECEIVE_WML_HELPERS_HPP
16 #define SEND_RECEIVE_WML_HELPERS_HPP
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #ifdef HAVE_SENDFILE
23 #include <sys/sendfile.h>
24 #endif
25 
26 #ifdef _WIN32
27 #include <windows.h>
28 #endif
29 
30 #include "server_base.hpp"
31 #include "simple_wml.hpp"
32 #include "filesystem.hpp"
33 #include "serialization/unicode_cast.hpp" //only used in windows specific code.
34 
35 #include <memory>
36 #include <stdexcept>
37 
38 template<typename Handler, typename ErrorHandler>
39 struct handle_doc
40 {
41  Handler handler;
42  ErrorHandler error_handler;
44  union DataSize
45  {
46  uint32_t size;
47  char buf[4];
48  };
49  std::shared_ptr<DataSize> data_size;
50  std::shared_ptr<simple_wml::document> doc;
51  boost::shared_array<char> buffer;
52  handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler, uint32_t size, std::shared_ptr<simple_wml::document> doc) :
53  handler(handler), error_handler(error_handler), socket(socket), data_size(new DataSize), doc(doc)
54  {
55  data_size->size = htonl(size);
56  }
57  handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler) :
58  handler(handler), error_handler(error_handler), socket(socket), data_size(new DataSize)
59  {
60  }
61  void operator()(const boost::system::error_code& error, std::size_t)
62  {
63  if(check_error(error, socket)) {
64  error_handler(socket);
65  return;
66  }
67  handler(socket);
68  }
69 };
70 
71 template<typename Handler, typename ErrorHandler>
73 {
74  try {
75  std::shared_ptr<simple_wml::document> doc_ptr(doc.clone());
76  simple_wml::string_span s = doc_ptr->output_compressed();
77  std::vector<boost::asio::const_buffer> buffers;
78 
79  handle_doc<Handler, ErrorHandler> handle_send_doc(socket, handler, error_handler, s.size(), doc_ptr);
80  buffers.push_back(boost::asio::buffer(handle_send_doc.data_size->buf, 4));
81  buffers.push_back(boost::asio::buffer(s.begin(), s.size()));
82  async_write(*socket, buffers, handle_send_doc);
83  } catch (simple_wml::error& e) {
84  WRN_CONFIG << __func__ << ": simple_wml error: " << e.message << std::endl;
85  }
86 }
87 
89 {
90 }
91 
92 #ifdef HAVE_SENDFILE
93 
94 template <typename Handler, typename ErrorHandler>
95 struct sendfile_op
96 {
97  socket_ptr sock_;
98  int fd_;
99  Handler handler_;
100  ErrorHandler error_handler_;
101  off_t offset_;
102  std::size_t total_bytes_transferred_;
103 
104  // Function call operator meeting WriteHandler requirements.
105  // Used as the handler for the async_write_some operation.
106  void operator()(boost::system::error_code ec, std::size_t)
107  {
108  // Put the underlying socket into non-blocking mode.
109  if (!ec)
110  if (!sock_->native_non_blocking())
111  sock_->native_non_blocking(true, ec);
112 
113  if (!ec)
114  {
115  for (;;)
116  {
117  // Try the system call.
118  errno = 0;
119  int n = ::sendfile(sock_->native_handle(), fd_, &offset_, 65536);
120  ec = boost::system::error_code(n < 0 ? errno : 0,
121  boost::asio::error::get_system_category());
122  total_bytes_transferred_ += ec ? 0 : n;
123 
124  // Retry operation immediately if interrupted by signal.
125  if (ec == boost::asio::error::interrupted)
126  continue;
127 
128  // Check if we need to run the operation again.
129  if (ec == boost::asio::error::would_block
130  || ec == boost::asio::error::try_again)
131  {
132  // We have to wait for the socket to become ready again.
133  sock_->async_write_some(boost::asio::null_buffers(), *this);
134  return;
135  }
136 
137  if (ec || n == 0)
138  {
139  // An error occurred, or we have reached the end of the file.
140  // Either way we must exit the loop so we can call the handler.
141  break;
142  }
143 
144  // Loop around to try calling sendfile again.
145  }
146  }
147 
148  close(fd_);
149 
150  if(ec)
151  error_handler_(sock_);
152  else
153  handler_(sock_);
154  }
155 };
156 
157 template<typename Handler, typename ErrorHandler>
158 void async_send_file(socket_ptr socket, const std::string& filename, Handler handler, ErrorHandler error_handler)
159 {
160  std::vector<boost::asio::const_buffer> buffers;
161 
162  std::size_t filesize = filesystem::file_size(filename);
163  int in_file(open(filename.c_str(), O_RDONLY));
164 
165  sendfile_op<Handler, ErrorHandler> op = { socket, in_file, handler, error_handler, 0, 0 };
166 
167  handle_doc<Handler, ErrorHandler> handle_send_doc(socket, handler, error_handler, filesize, nullptr);
168  buffers.push_back(boost::asio::buffer(handle_send_doc.data_size->buf, 4));
169  async_write(*socket, buffers, op);
170 }
171 
172 #elif defined(_WIN32)
173 
174 template<typename Handler, typename ErrorHandler>
175 struct sendfile_op
176 {
177  socket_ptr sock_;
178  HANDLE file_;
179  OVERLAPPED overlap_;
180  Handler handler_;
181  ErrorHandler error_handler_;
182  bool pending_;
183  std::shared_ptr<handle_doc<Handler, ErrorHandler>> handle_send_doc_;
184 
185  void operator()(boost::system::error_code, std::size_t)
186  {
187  bool failed = false;
188  if (!pending_)
189  {
190  BOOL success = TransmitFile(sock_->native_handle(), file_, 0, 0, &overlap_, nullptr, 0);
191  if (!success)
192  {
193  int winsock_ec = WSAGetLastError();
194  if (winsock_ec == WSA_IO_PENDING || winsock_ec == ERROR_IO_PENDING)
195  {
196  // The request is pending. Wait until it completes.
197  pending_ = true;
198  sock_->async_write_some(boost::asio::null_buffers(), *this);
199  return;
200  }
201  else
202  {
203  failed = true;
204  }
205  }
206  }
207  else
208  {
209  DWORD win_ec = GetLastError();
210  if (win_ec != ERROR_IO_PENDING && win_ec != ERROR_SUCCESS)
211  {
212  failed = true;
213  }
214  else if (!HasOverlappedIoCompleted(&overlap_))
215  {
216  // Keep waiting.
217  sock_->async_write_some(boost::asio::null_buffers(), *this);
218  return;
219  }
220  }
221 
222  CloseHandle(file_);
223  CloseHandle(overlap_.hEvent);
224 
225  if (!failed)
226  {
227  handler_(sock_);
228  }
229  else
230  {
231  error_handler_(sock_);
232  }
233  }
234 };
235 
236 template<typename Handler, typename ErrorHandler>
237 void async_send_file(socket_ptr socket, const std::string& filename, Handler handler, ErrorHandler error_handler)
238 {
239  std::vector<boost::asio::const_buffer> buffers;
240 
241  SetLastError(ERROR_SUCCESS);
242 
243  std::size_t filesize = filesystem::file_size(filename);
244  std::wstring filename_ucs2 = unicode_cast<std::wstring>(filename);
245  HANDLE in_file = CreateFileW(filename_ucs2.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
246  FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
247  if (GetLastError() != ERROR_SUCCESS)
248  {
249  throw std::runtime_error("Failed to open the file");
250  }
251 
252  sendfile_op<Handler, ErrorHandler> op = { socket, in_file, OVERLAPPED(), handler, error_handler, false, nullptr };
253 
254  HANDLE event = CreateEvent(nullptr, TRUE, TRUE, nullptr);
255  if (GetLastError() != ERROR_SUCCESS)
256  {
257  throw std::runtime_error("Failed to create an event");
258  }
259 
260  op.overlap_.hEvent = event;
261  op.handle_send_doc_.reset(new handle_doc<Handler, ErrorHandler>(socket, handler, error_handler, filesize, nullptr));
262 
263  buffers.push_back(boost::asio::buffer(op.handle_send_doc_->data_size->buf, 4));
264  async_write(*socket, buffers, op);
265 }
266 
267 #else
268 
269 // TODO: Implement this for systems without sendfile()
270 template<typename Handler, typename ErrorHandler>
271 void async_send_file(socket_ptr, const std::string&, Handler, ErrorHandler)
272 {
273  assert(false && "Not implemented yet");
274 }
275 
276 #endif
277 
278 template<typename Handler>
280 {
281  async_send_doc(socket, doc, handler, null_handler);
282 }
283 
285 {
287 }
288 
289 template<typename Handler, typename ErrorHandler>
290 struct handle_receive_doc : public handle_doc<Handler, ErrorHandler>
291 {
292  std::size_t buf_size;
294  handle_doc<Handler, ErrorHandler>(socket, handler, error_handler),
295  buf_size(0)
296  {
297  }
298  void operator()(const boost::system::error_code& error, std::size_t size)
299  {
300  if(check_error(error, this->socket)) {
301  this->error_handler(this->socket);
302  return;
303  }
304  if(!this->buffer) {
305  assert(size == 4);
306  buf_size = ntohl(this->data_size->size);
307 
308  if(buf_size == 0) {
309  ERR_SERVER <<
310  client_address(this->socket) <<
311  "\treceived invalid packet with payload size 0" << std::endl;
312  this->error_handler(this->socket);
313  return;
314  }
316  ERR_SERVER <<
317  client_address(this->socket) <<
318  "\treceived packet with payload size over size limit" << std::endl;
319  this->error_handler(this->socket);
320  return;
321  }
322 
323  this->buffer = boost::shared_array<char>(new char[buf_size]);
324  async_read(*(this->socket), boost::asio::buffer(this->buffer.get(), buf_size), *this);
325  } else {
326  simple_wml::string_span compressed_buf(this->buffer.get(), buf_size);
327  try {
328  this->doc.reset(new simple_wml::document(compressed_buf));
329  } catch (simple_wml::error& e) {
330  ERR_SERVER <<
331  client_address(this->socket) <<
332  "\tsimple_wml error in received data: " << e.message << std::endl;
333  async_send_error(this->socket, "Invalid WML received: " + e.message);
334  this->error_handler(this->socket);
335  return;
336  }
337  this->handler(this->socket, this->doc);
338  }
339  }
340 };
341 
342 template<typename Handler, typename ErrorHandler>
343 inline void async_receive_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler)
344 {
345  handle_receive_doc<Handler, ErrorHandler> handle_receive_doc(socket, handler, error_handler);
346  async_read(*socket, boost::asio::buffer(handle_receive_doc.data_size->buf, 4), handle_receive_doc);
347 }
348 
349 template<typename Handler>
351 {
352  async_receive_doc(socket, handler, null_handler);
353 }
354 
355 #endif
static std::unique_ptr< class sdl_event_handler > handler_
Definition: handler.cpp:54
bool check_error(const boost::system::error_code &error, socket_ptr socket)
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
void operator()(const boost::system::error_code &error, std::size_t)
static void null_handler(socket_ptr)
void async_send_file(socket_ptr, const std::string &, Handler, ErrorHandler)
void operator()(const boost::system::error_code &error, std::size_t size)
std::shared_ptr< simple_wml::document > doc
ErrorHandler error_handler
handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler, uint32_t size, std::shared_ptr< simple_wml::document > doc)
handle_receive_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler)
static std::size_t document_size_limit
Definition: simple_wml.hpp:294
std::shared_ptr< DataSize > data_size
static map_location::DIRECTION s
std::string client_address(const socket_ptr socket)
#define WRN_CONFIG
Declarations for File-IO.
boost::shared_array< char > buffer
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn&#39;t exist.
void async_send_doc(socket_ptr socket, simple_wml::document &doc, Handler handler, ErrorHandler error_handler)
void async_receive_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler)
void async_send_error(socket_ptr socket, const std::string &msg, const char *error_code)
std::string message
Definition: exceptions.hpp:31
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:28
#define e
handle_doc(socket_ptr socket, Handler handler, ErrorHandler error_handler)
static map_location::DIRECTION n
#define ERR_SERVER
Base class for servers using Wesnoth&#39;s WML over TCP protocol.