The Battle for Wesnoth  1.15.0+dev
network_asio.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2018 by Sergey Popov <loonycyborg@gmail.com>
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 as published by
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 #include "network_asio.hpp"
16 
17 #include "log.hpp"
18 #include "serialization/parser.hpp"
19 #include "utils/functional.hpp"
20 
21 #include <algorithm>
22 #include <cstdint>
23 #include <deque>
24 
25 static lg::log_domain log_network("network");
26 #define DBG_NW LOG_STREAM(debug, log_network)
27 #define LOG_NW LOG_STREAM(info, log_network)
28 #define WRN_NW LOG_STREAM(warn, log_network)
29 #define ERR_NW LOG_STREAM(err, log_network)
30 
31 namespace
32 {
33 std::deque<boost::asio::const_buffer> split_buffers(boost::asio::streambuf::const_buffers_type source_buffers)
34 {
35  const unsigned int chunk_size = 4096;
36 
37  std::deque<boost::asio::const_buffer> buffers;
38  for(boost::asio::const_buffer b : source_buffers) {
39  unsigned int remaining_size = boost::asio::buffer_size(b);
40  const uint8_t* data = boost::asio::buffer_cast<const uint8_t*>(b);
41 
42  while(remaining_size > 0u) {
43  unsigned int size = std::min(remaining_size, chunk_size);
44  buffers.emplace_back(data, size);
45  data += size;
46  remaining_size -= size;
47  }
48  }
49 
50  return buffers;
51 }
52 }
53 
54 namespace network_asio
55 {
56 using boost::system::system_error;
57 
58 connection::connection(const std::string& host, const std::string& service)
59  : io_service_()
60  , resolver_(io_service_)
61  , socket_(io_service_)
62  , done_(false)
63  , write_buf_()
64  , read_buf_()
65  , handshake_response_()
66  , payload_size_(0)
67  , bytes_to_write_(0)
68  , bytes_written_(0)
69  , bytes_to_read_(0)
70  , bytes_read_(0)
71 {
72  resolver_.async_resolve(
73  boost::asio::ip::tcp::resolver::query(host, service), std::bind(&connection::handle_resolve, this, _1, _2));
74 
75  LOG_NW << "Resolving hostname: " << host << '\n';
76 }
77 
78 void connection::handle_resolve(const boost::system::error_code& ec, resolver::iterator iterator)
79 {
80  if(ec) {
81  throw system_error(ec);
82  }
83 
84  connect(iterator);
85 }
86 
88 {
89  socket_.async_connect(*iterator, std::bind(&connection::handle_connect, this, _1, iterator));
90  LOG_NW << "Connecting to " << iterator->endpoint().address() << '\n';
91 }
92 
93 void connection::handle_connect(const boost::system::error_code& ec, resolver::iterator iterator)
94 {
95  if(ec) {
96  WRN_NW << "Failed to connect to " << iterator->endpoint().address() << ": " << ec.message() << '\n';
97  socket_.close();
98 
99  if(++iterator == resolver::iterator()) {
100  ERR_NW << "Tried all IPs. Giving up" << std::endl;
101  throw system_error(ec);
102  } else {
103  connect(iterator);
104  }
105  } else {
106  LOG_NW << "Connected to " << iterator->endpoint().address() << '\n';
107  handshake();
108  }
109 }
110 
112 {
113  static const uint32_t handshake = 0;
114 
115  boost::asio::async_write(socket_, boost::asio::buffer(reinterpret_cast<const char*>(&handshake), 4),
116  std::bind(&connection::handle_write, this, _1, _2));
117 
118  boost::asio::async_read(socket_, boost::asio::buffer(&handshake_response_.binary, 4),
119  std::bind(&connection::handle_handshake, this, _1));
120 }
121 
122 void connection::handle_handshake(const boost::system::error_code& ec)
123 {
124  if(ec) {
125  throw system_error(ec);
126  }
127 
128  done_ = true;
129 }
130 
131 void connection::transfer(const config& request, config& response)
132 {
133  io_service_.reset();
134  done_ = false;
135 
136  write_buf_.reset(new boost::asio::streambuf);
137  read_buf_.reset(new boost::asio::streambuf);
138  std::ostream os(write_buf_.get());
139  write_gz(os, request);
140 
141  bytes_to_write_ = write_buf_->size() + 4;
142  bytes_written_ = 0;
143  payload_size_ = htonl(bytes_to_write_ - 4);
144 
145  boost::asio::streambuf::const_buffers_type gzipped_data = write_buf_->data();
146  auto bufs = split_buffers(gzipped_data);
147 
148  bufs.push_front(boost::asio::buffer(reinterpret_cast<const char*>(&payload_size_), 4));
149 
150  boost::asio::async_write(socket_, bufs, std::bind(&connection::is_write_complete, this, _1, _2),
151  std::bind(&connection::handle_write, this, _1, _2));
152 
153  boost::asio::async_read(socket_, *read_buf_, std::bind(&connection::is_read_complete, this, _1, _2),
154  std::bind(&connection::handle_read, this, _1, _2, std::ref(response)));
155 }
156 
158 {
159  if(socket_.is_open()) {
160  boost::system::error_code ec;
161 
162 #ifdef _MSC_VER
163 // Silence warning about boost::asio::basic_socket<Protocol>::cancel always
164 // returning an error on XP, which we don't support anymore.
165 #pragma warning(push)
166 #pragma warning(disable:4996)
167 #endif
168  socket_.cancel(ec);
169 #ifdef _MSC_VER
170 #pragma warning(pop)
171 #endif
172 
173  if(ec) {
174  WRN_NW << "Failed to cancel network operations: " << ec.message() << std::endl;
175  }
176  }
177  bytes_to_write_ = 0;
178  bytes_written_ = 0;
179  bytes_to_read_ = 0;
180  bytes_read_ = 0;
181 }
182 
183 std::size_t connection::is_write_complete(const boost::system::error_code& ec, std::size_t bytes_transferred)
184 {
185  if(ec) {
186  throw system_error(ec);
187  }
188 
189  bytes_written_ = bytes_transferred;
190  return bytes_to_write_ - bytes_transferred;
191 }
192 
193 void connection::handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred)
194 {
195  DBG_NW << "Written " << bytes_transferred << " bytes.\n";
196  if(write_buf_)
197  write_buf_->consume(bytes_transferred);
198 
199  if(ec) {
200  throw system_error(ec);
201  }
202 }
203 
204 std::size_t connection::is_read_complete(const boost::system::error_code& ec, std::size_t bytes_transferred)
205 {
206  if(ec) {
207  throw system_error(ec);
208  }
209 
210  bytes_read_ = bytes_transferred;
211  if(bytes_transferred < 4) {
212  return 4;
213  }
214 
215  if(!bytes_to_read_) {
216  std::istream is(read_buf_.get());
217  data_union data_size;
218 
219  is.read(data_size.binary, 4);
220  bytes_to_read_ = ntohl(data_size.num) + 4;
221 
222  // Close immediately if we receive an invalid length
223  if(bytes_to_read_ < 4) {
224  bytes_to_read_ = bytes_transferred;
225  }
226  }
227 
228  return bytes_to_read_ - bytes_transferred;
229 }
230 
231 void connection::handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred, config& response)
232 {
233  DBG_NW << "Read " << bytes_transferred << " bytes.\n";
234 
235  bytes_to_read_ = 0;
236  bytes_to_write_ = 0;
237  done_ = true;
238 
239  if(ec && ec != boost::asio::error::eof) {
240  throw system_error(ec);
241  }
242 
243  std::istream is(read_buf_.get());
244  read_gz(response, is);
245 }
246 }
void handle_read(const boost::system::error_code &ec, std::size_t bytes_transferred, config &response)
connection(const std::string &host, const std::string &service)
Constructor.
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:681
#define ERR_NW
void handle_connect(const boost::system::error_code &ec, resolver::iterator iterator)
std::size_t is_write_complete(const boost::system::error_code &error, std::size_t bytes_transferred)
#define b
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:781
#define WRN_NW
void handle_write(const boost::system::error_code &ec, std::size_t bytes_transferred)
std::size_t is_read_complete(const boost::system::error_code &error, std::size_t bytes_transferred)
void connect(resolver::iterator iterator)
static lg::log_domain log_network("network")
void transfer(const config &request, config &response)
boost::asio::io_service io_service_
#define LOG_NW
std::unique_ptr< boost::asio::streambuf > read_buf_
std::unique_ptr< boost::asio::streambuf > write_buf_
Standard logging facilities (interface).
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
void handle_resolve(const boost::system::error_code &ec, resolver::iterator iterator)
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
void handle_handshake(const boost::system::error_code &ec)
#define DBG_NW