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