irccd  3.0.3
stream.hpp
1 /*
2  * stream.hpp -- abstract stream interface
3  *
4  * Copyright (c) 2013-2019 David Demelier <markand@malikania.fr>
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #ifndef IRCCD_STREAM_HPP
20 #define IRCCD_STREAM_HPP
21 
27 #include <irccd/sysconfig.hpp>
28 
29 #include <cassert>
30 #include <cstddef>
31 #include <functional>
32 #include <ostream>
33 #include <string>
34 #include <system_error>
35 #include <utility>
36 
37 #include <boost/asio.hpp>
38 
39 #if defined(IRCCD_HAVE_SSL)
40 # include <boost/asio/ssl.hpp>
41 #endif
42 
43 #include "json.hpp"
44 
45 namespace irccd {
46 
47 // {{{ stream
48 
58 class stream {
59 public:
63  using recv_handler = std::function<void (std::error_code, nlohmann::json)>;
64 
68  using send_handler = std::function<void (std::error_code)>;
69 
73  stream() = default;
74 
78  virtual ~stream() = default;
79 
87  virtual void recv(recv_handler handler) = 0;
88 
98  virtual void send(const nlohmann::json& json, send_handler handler) = 0;
99 };
100 
101 // }}}
102 
103 // {{{ basic_socket_stream
104 
110 template <typename Socket>
111 class basic_socket_stream : public stream {
112 private:
113  boost::asio::streambuf input_{2048};
114  boost::asio::streambuf output_;
115 
116 #if !defined(NDEBUG)
117  bool is_receiving_{false};
118  bool is_sending_{false};
119 #endif
120 
121  void handle_recv(boost::system::error_code, std::size_t, recv_handler);
122  void handle_send(boost::system::error_code, std::size_t, send_handler);
123 
124  Socket socket_;
125 
126 public:
132  template <typename... Args>
133  basic_socket_stream(Args&&... args);
134 
140  auto get_socket() const noexcept -> const Socket&;
141 
147  auto get_socket() noexcept -> Socket&;
148 
156  void recv(recv_handler handler) override;
157 
167  void send(const nlohmann::json& json, send_handler handler) override;
168 };
169 
170 template <typename Socket>
171 template <typename... Args>
173  : socket_(std::forward<Args>(args)...)
174 {
175 }
176 
177 template <typename Socket>
178 inline auto basic_socket_stream<Socket>::get_socket() const noexcept -> const Socket&
179 {
180  return socket_;
181 }
182 
183 template <typename Socket>
184 inline auto basic_socket_stream<Socket>::get_socket() noexcept -> Socket&
185 {
186  return socket_;
187 }
188 
189 template <typename Socket>
190 inline void basic_socket_stream<Socket>::handle_recv(boost::system::error_code code,
191  std::size_t xfer,
192  recv_handler handler)
193 {
194 #if !defined(NDEBUG)
195  is_receiving_ = false;
196 #endif
197 
198  if (code == boost::asio::error::not_found) {
199  handler(make_error_code(std::errc::argument_list_too_long), nullptr);
200  return;
201  }
202  if (code == boost::asio::error::eof || xfer == 0) {
203  handler(make_error_code(std::errc::connection_reset), nullptr);
204  return;
205  }
206  if (code) {
207  handler(std::move(code), nullptr);
208  return;
209  }
210 
211  // 1. Convert the buffer safely.
212  std::string buffer;
213 
214  try {
215  buffer = std::string(
216  boost::asio::buffers_begin(input_.data()),
217  boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4
218  );
219 
220  input_.consume(xfer);
221  } catch (const std::bad_alloc&) {
222  handler(make_error_code(std::errc::not_enough_memory), nullptr);
223  return;
224  }
225 
226  // 2. Convert to JSON.
227  nlohmann::json doc;
228 
229  try {
230  doc = nlohmann::json::parse(buffer);
231  } catch (const std::exception&) {
232  handler(make_error_code(std::errc::invalid_argument), nullptr);
233  return;
234  }
235 
236  if (!doc.is_object())
237  handler(make_error_code(std::errc::invalid_argument), nullptr);
238  else
239  handler(std::error_code(), std::move(doc));
240 }
241 
242 template <typename Socket>
243 inline void basic_socket_stream<Socket>::handle_send(boost::system::error_code code,
244  std::size_t xfer,
245  send_handler handler)
246 {
247 #if !defined(NDEBUG)
248  is_sending_ = false;
249 #endif
250 
251  if (code == boost::asio::error::eof || xfer == 0) {
252  handler(make_error_code(std::errc::connection_reset));
253  return;
254  }
255  else
256  handler(std::move(code));
257 }
258 
259 template <typename Socket>
261 {
262 #if !defined(NDEBUG)
263  assert(!is_receiving_);
264 
265  is_receiving_ = true;
266 #endif
267 
268  assert(handler);
269 
270  async_read_until(socket_, input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
271  handle_recv(code, xfer, handler);
272  });
273 }
274 
275 template <typename Socket>
276 inline void basic_socket_stream<Socket>::send(const nlohmann::json& json, send_handler handler)
277 {
278 #if !defined(NDEBUG)
279  assert(!is_sending_);
280 
281  is_sending_ = true;
282 #endif
283 
284  assert(json.is_object());
285  assert(handler);
286 
287  std::ostream out(&output_);
288 
289  out << json.dump(0);
290  out << "\r\n\r\n";
291  out << std::flush;
292 
293  async_write(socket_, output_, [this, handler] (auto code, auto xfer) {
294  handle_send(code, xfer, handler);
295  });
296 }
297 
298 // }}}
299 
300 // {{{ ip_stream
301 
307 
308 // }}}
309 
310 // {{{ local_stream
311 
312 #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
313 
319 
320 #endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS
321 
322 // }}}
323 
324 // {{{ tls_stream
325 
326 #if defined(IRCCD_HAVE_SSL)
327 
333 template <typename Socket>
334 class tls_stream : public basic_socket_stream<boost::asio::ssl::stream<Socket>> {
335 private:
336  std::shared_ptr<boost::asio::ssl::context> context_;
337 
340 
341 public:
348  tls_stream(boost::asio::io_context& service, std::shared_ptr<boost::asio::ssl::context> ctx);
349 };
350 
351 template <typename Socket>
352 inline tls_stream<Socket>::tls_stream(boost::asio::io_context& service, std::shared_ptr<boost::asio::ssl::context> ctx)
353  : basic_socket_stream<boost::asio::ssl::stream<Socket>>(service, *ctx)
354  , context_(std::move(ctx))
355 {
356 }
357 
362 
363 #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
364 
369 
370 #endif // !BOOST_ASIO_HAS_LOCAL_SOCKETS
371 
372 #endif // !IRCCD_HAVE_SSL
373 
374 // }}}
375 
376 } // !irccd
377 
378 #endif // !IRCCD_STREAM_HPP
irccd::stream::send
virtual void send(const nlohmann::json &json, send_handler handler)=0
irccd::basic_socket_stream
Complete implementation for basic sockets.
Definition: stream.hpp:111
irccd::stream::recv
virtual void recv(recv_handler handler)=0
irccd::tls_stream
TLS/SSL streams.
Definition: stream.hpp:334
irccd::daemon::make_error_code
auto make_error_code(bot_error::error e) noexcept -> std::error_code
irccd::stream::recv_handler
std::function< void(std::error_code, nlohmann::json)> recv_handler
Read completion handler.
Definition: stream.hpp:63
irccd::stream::send_handler
std::function< void(std::error_code)> send_handler
Write completion handler.
Definition: stream.hpp:68
irccd::stream
Abstract stream interface.
Definition: stream.hpp:58
irccd::stream::~stream
virtual ~stream()=default
irccd::basic_socket_stream::get_socket
auto get_socket() const noexcept -> const Socket &
Definition: stream.hpp:178
irccd
Parent namespace.
Definition: acceptor.hpp:43
std
Definition: bot.hpp:253
irccd::basic_socket_stream::send
void send(const nlohmann::json &json, send_handler handler) override
Definition: stream.hpp:276
irccd::basic_socket_stream::recv
void recv(recv_handler handler) override
Definition: stream.hpp:260
irccd::basic_socket_stream::basic_socket_stream
basic_socket_stream(Args &&... args)
Definition: stream.hpp:172
irccd::stream::stream
stream()=default