~singpolyma/biboumi

0c8adc85f7373a85de8b3edc6cac87d5f7389bb3 — louiz’ 8 years ago c41d003
Move all the connect() logic from TCPSocketHandler into a subclass

This way, TCPSocketHandler only deal with the message sending/receiving, not
the connect() or anything else.  This will be used for implementing servers
(because when a client is accepted, we don’t need all the connect() and dns
resolution stuff).
M louloulibs/network/dns_socket_handler.cpp => louloulibs/network/dns_socket_handler.cpp +5 -0
@@ 40,6 40,11 @@ bool DNSSocketHandler::is_connected() const
  return true;
}

bool DNSSocketHandler::is_connecting() const
{
  return false;
}

void DNSSocketHandler::remove_from_poller()
{
  if (this->poller->is_managing_socket(this->socket))

M louloulibs/network/dns_socket_handler.hpp => louloulibs/network/dns_socket_handler.hpp +1 -0
@@ 40,6 40,7 @@ public:
   * Always true, see the comment for connect()
   */
  bool is_connected() const override final;
  bool is_connecting() const override final;
  void remove_from_poller();

private:

M louloulibs/network/socket_handler.hpp => louloulibs/network/socket_handler.hpp +1 -0
@@ 24,6 24,7 @@ public:
  virtual void on_send() = 0;
  virtual void connect() = 0;
  virtual bool is_connected() const = 0;
  virtual bool is_connecting() const = 0;

  socket_t get_socket() const
  { return this->socket; }

A louloulibs/network/tcp_client_socket_handler.cpp => louloulibs/network/tcp_client_socket_handler.cpp +232 -0
@@ 0,0 1,232 @@
#include <network/tcp_client_socket_handler.hpp>
#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <network/poller.hpp>

#include <logger/logger.hpp>

#include <unistd.h>
#include <fcntl.h>

using namespace std::string_literals;

TCPClientSocketHandler::TCPClientSocketHandler(std::shared_ptr<Poller> poller):
   TCPSocketHandler(poller),
   hostname_resolution_failed(false),
   connected(false),
   connecting(false)
{}

TCPClientSocketHandler::~TCPClientSocketHandler()
{
  this->close();
}

void TCPClientSocketHandler::init_socket(const struct addrinfo* rp)
{
  if (this->socket != -1)
    ::close(this->socket);
  if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
    throw std::runtime_error("Could not create socket: "s + strerror(errno));
  // Bind the socket to a specific address, if specified
  if (!this->bind_addr.empty())
    {
      // Convert the address from string format to a sockaddr that can be
      // used in bind()
      struct addrinfo* result;
      int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result);
      if (err != 0 || !result)
        log_error("Failed to bind socket to ", this->bind_addr, ": ",
                  gai_strerror(err));
      else
        {
          utils::ScopeGuard sg([result](){ freeaddrinfo(result); });
          struct addrinfo* rp;
          int bind_error = 0;
          for (rp = result; rp; rp = rp->ai_next)
            {
              if ((bind_error = ::bind(this->socket,
                         reinterpret_cast<const struct sockaddr*>(rp->ai_addr),
                         rp->ai_addrlen)) == 0)
                break;
            }
          if (!rp)
            log_error("Failed to bind socket to ", this->bind_addr, ": ",
                      strerror(errno));
          else
            log_info("Socket successfully bound to ", this->bind_addr);
        }
    }
  int optval = 1;
  if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1)
    log_warning("Failed to enable TCP keepalive on socket: ", strerror(errno));
  // Set the socket on non-blocking mode.  This is useful to receive a EAGAIN
  // error when connect() would block, to not block the whole process if a
  // remote is not responsive.
  const int existing_flags = ::fcntl(this->socket, F_GETFL, 0);
  if ((existing_flags == -1) ||
      (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1))
    throw std::runtime_error("Could not initialize socket: "s + strerror(errno));
}

void TCPClientSocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
{
  this->address = address;
  this->port = port;
  this->use_tls = tls;

  struct addrinfo* addr_res;

  if (!this->connecting)
    {
      // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if
      // this is the first call of this function.
      if (!this->resolver.is_resolved())
        {
          log_info("Trying to connect to ", address, ":", port);
          // Start the asynchronous process of resolving the hostname. Once
          // the addresses have been found and `resolved` has been set to true
          // (but connecting will still be false), TCPClientSocketHandler::connect()
          // needs to be called, again.
          this->resolver.resolve(address, port,
                                 [this](const struct addrinfo*)
                                 {
                                   log_debug("Resolution success, calling connect() again");
                                   this->connect();
                                 },
                                 [this](const char*)
                                 {
                                   log_debug("Resolution failed, calling connect() again");
                                   this->connect();
                                 });
          return;
        }
      else
        {
          // The c-ares resolved the hostname and the available addresses
          // where saved in the cares_addrinfo linked list. Now, just use
          // this list to try to connect.
          addr_res = this->resolver.get_result().get();
          if (!addr_res)
            {
              this->hostname_resolution_failed = true;
              const auto msg = this->resolver.get_error_message();
              this->close();
              this->on_connection_failed(msg);
              return ;
            }
        }
    }
  else
    { // This function is called again, use the saved addrinfo structure,
      // instead of re-doing the whole getaddrinfo process.
      addr_res = &this->addrinfo;
    }

  for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next)
    {
      if (!this->connecting)
        {
          try {
            this->init_socket(rp);
          }
          catch (const std::runtime_error& error) {
            log_error("Failed to init socket: ", error.what());
            break;
          }
        }

      this->display_resolved_ip(rp);

      if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0
          || errno == EISCONN)
        {
          log_info("Connection success.");
          TimedEventsManager::instance().cancel("connection_timeout"s +
                                                std::to_string(this->socket));
          this->poller->add_socket_handler(this);
          this->connected = true;
          this->connecting = false;
#ifdef BOTAN_FOUND
          if (this->use_tls)
            this->start_tls(this->address, this->port);
#endif
          this->connection_date = std::chrono::system_clock::now();

          this->on_connected();
          return ;
        }
      else if (errno == EINPROGRESS || errno == EALREADY)
        {   // retry this process later, when the socket
            // is ready to be written on.
          this->connecting = true;
          this->poller->add_socket_handler(this);
          this->poller->watch_send_events(this);
          // Save the addrinfo structure, to use it on the next call
          this->ai_addrlen = rp->ai_addrlen;
          memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen);
          memcpy(&this->addrinfo, rp, sizeof(struct addrinfo));
          this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr);
          this->addrinfo.ai_next = nullptr;
          // If the connection has not succeeded or failed in 5s, we consider
          // it to have failed
          TimedEventsManager::instance().add_event(
                                                   TimedEvent(std::chrono::steady_clock::now() + 5s,
                                                              std::bind(&TCPClientSocketHandler::on_connection_timeout, this),
                                                              "connection_timeout"s + std::to_string(this->socket)));
          return ;
        }
      log_info("Connection failed:", strerror(errno));
    }
  log_error("All connection attempts failed.");
  this->close();
  this->on_connection_failed(strerror(errno));
  return ;
}

void TCPClientSocketHandler::on_connection_timeout()
{
  this->close();
  this->on_connection_failed("connection timed out");
}

void TCPClientSocketHandler::connect()
{
  this->connect(this->address, this->port, this->use_tls);
}

void TCPClientSocketHandler::close()
{
  TimedEventsManager::instance().cancel("connection_timeout"s +
                                        std::to_string(this->socket));

  TCPSocketHandler::close();

  this->connected = false;
  this->connecting = false;
  this->port.clear();
  this->resolver.clear();
}

void TCPClientSocketHandler::display_resolved_ip(struct addrinfo* rp) const
{
  if (rp->ai_family == AF_INET)
    log_debug("Trying IPv4 address ", addr_to_string(rp));
  else if (rp->ai_family == AF_INET6)
    log_debug("Trying IPv6 address ", addr_to_string(rp));
}

bool TCPClientSocketHandler::is_connected() const
{
  return this->connected;
}

bool TCPClientSocketHandler::is_connecting() const
{
  return this->connecting || this->resolver.is_resolving();
}

std::string TCPClientSocketHandler::get_port() const
{
  return this->port;
}

A louloulibs/network/tcp_client_socket_handler.hpp => louloulibs/network/tcp_client_socket_handler.hpp +75 -0
@@ 0,0 1,75 @@
#pragma once

#include <network/tcp_socket_handler.hpp>

class TCPClientSocketHandler: public TCPSocketHandler
{
 public:
  TCPClientSocketHandler(std::shared_ptr<Poller> poller);
  ~TCPClientSocketHandler();
  /**
   * Connect to the remote server, and call on_connected() if this
   * succeeds. If tls is true, we set use_tls to true and will also call
   * start_tls() when the connection succeeds.
   */
  void connect(const std::string& address, const std::string& port, const bool tls);
  void connect() override final;
  /**
   * Called by a TimedEvent, when the connection did not succeed or fail
   * after a given time.
   */
  void on_connection_timeout();
  /**
   * Called when the connection is successful.
   */
  virtual void on_connected() = 0;
  bool is_connected() const override;
  bool is_connecting() const;

  std::string get_port() const;

  void close() override final;
  std::chrono::system_clock::time_point connection_date;

 protected:
  bool hostname_resolution_failed;
  /**
   * Address to bind the socket to, before calling connect().
   * If empty, it’s equivalent to binding to INADDR_ANY.
   */
  std::string bind_addr;
  /**
   * Display the resolved IP, just for information purpose.
   */
  void display_resolved_ip(struct addrinfo* rp) const;
 private:
  /**
   * Initialize the socket with the parameters contained in the given
   * addrinfo structure.
   */
  void init_socket(const struct addrinfo* rp);
  /**
   * DNS resolver
   */
  Resolver resolver;
  /**
   * Keep the details of the addrinfo returned by the resolver that
   * triggered a EINPROGRESS error when connect()ing to it, to reuse it
   * directly when connect() is called again.
   */
  struct addrinfo addrinfo;
  struct sockaddr_in6 ai_addr;
  socklen_t ai_addrlen;

  /**
   * Hostname we are connected/connecting to
   */
  std::string address;
  /**
   * Port we are connected/connecting to
   */
  std::string port;

  bool connected;
  bool connecting;
};

M louloulibs/network/tcp_socket_handler.cpp => louloulibs/network/tcp_socket_handler.cpp +16 -214
@@ 1,8 1,6 @@
#include <network/tcp_socket_handler.hpp>
#include <network/dns_handler.hpp>

#include <utils/timed_events.hpp>
#include <utils/scopeguard.hpp>
#include <network/poller.hpp>

#include <logger/logger.hpp>


@@ 12,7 10,6 @@
#include <unistd.h>
#include <errno.h>
#include <cstring>
#include <fcntl.h>

#ifdef BOTAN_FOUND
# include <botan/hex.h>


@@ 35,10 32,7 @@ namespace ph = std::placeholders;

TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):
  SocketHandler(poller, -1),
  use_tls(false),
  connected(false),
  connecting(false),
  hostname_resolution_failed(false)
  use_tls(false)
#ifdef BOTAN_FOUND
  ,credential_manager(this)
#endif


@@ 46,181 40,13 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller> poller):

TCPSocketHandler::~TCPSocketHandler()
{
  this->close();
}


void TCPSocketHandler::init_socket(const struct addrinfo* rp)
{
  if (this->poller->is_managing_socket(this->get_socket()))
    this->poller->remove_socket_handler(this->get_socket());
  if (this->socket != -1)
    ::close(this->socket);
  if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
    throw std::runtime_error("Could not create socket: "s + strerror(errno));
  // Bind the socket to a specific address, if specified
  if (!this->bind_addr.empty())
    {
      // Convert the address from string format to a sockaddr that can be
      // used in bind()
      struct addrinfo* result;
      int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result);
      if (err != 0 || !result)
        log_error("Failed to bind socket to ", this->bind_addr, ": ",
                  gai_strerror(err));
      else
        {
          utils::ScopeGuard sg([result](){ freeaddrinfo(result); });
          struct addrinfo* rp;
          int bind_error = 0;
          for (rp = result; rp; rp = rp->ai_next)
            {
              if ((bind_error = ::bind(this->socket,
                         reinterpret_cast<const struct sockaddr*>(rp->ai_addr),
                         rp->ai_addrlen)) == 0)
                break;
            }
          if (!rp)
            log_error("Failed to bind socket to ", this->bind_addr, ": ",
                      strerror(errno));
          else
            log_info("Socket successfully bound to ", this->bind_addr);
        }
    }
  int optval = 1;
  if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1)
    log_warning("Failed to enable TCP keepalive on socket: ", strerror(errno));
  // Set the socket on non-blocking mode.  This is useful to receive a EAGAIN
  // error when connect() would block, to not block the whole process if a
  // remote is not responsive.
  const int existing_flags = ::fcntl(this->socket, F_GETFL, 0);
  if ((existing_flags == -1) ||
      (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1))
    throw std::runtime_error("Could not initialize socket: "s + strerror(errno));
}

void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls)
{
  this->address = address;
  this->port = port;
  this->use_tls = tls;

  struct addrinfo* addr_res;

  if (!this->connecting)
    {
      // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if
      // this is the first call of this function.
      if (!this->resolver.is_resolved())
        {
          log_info("Trying to connect to ", address, ":", port);
          // Start the asynchronous process of resolving the hostname. Once
          // the addresses have been found and `resolved` has been set to true
          // (but connecting will still be false), TCPSocketHandler::connect()
          // needs to be called, again.
          this->resolver.resolve(address, port,
                                 [this](const struct addrinfo*)
                                 {
                                   log_debug("Resolution success, calling connect() again");
                                   this->connect();
                                 },
                                 [this](const char*)
                                 {
                                   log_debug("Resolution failed, calling connect() again");
                                   this->connect();
                                 });
          return;
        }
      else
        {
          // The c-ares resolved the hostname and the available addresses
          // where saved in the cares_addrinfo linked list. Now, just use
          // this list to try to connect.
          addr_res = this->resolver.get_result().get();
          if (!addr_res)
            {
              this->hostname_resolution_failed = true;
              const auto msg = this->resolver.get_error_message();
              this->close();
              this->on_connection_failed(msg);
              return ;
            }
        }
    }
  else
    { // This function is called again, use the saved addrinfo structure,
      // instead of re-doing the whole getaddrinfo process.
      addr_res = &this->addrinfo;
    }

  for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next)
    {
      if (!this->connecting)
        {
          try {
            this->init_socket(rp);
          }
          catch (const std::runtime_error& error) {
            log_error("Failed to init socket: ", error.what());
            break;
          }
        }

      this->display_resolved_ip(rp);

      if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0
          || errno == EISCONN)
        {
          log_info("Connection success.");
          TimedEventsManager::instance().cancel("connection_timeout"s +
                                                std::to_string(this->socket));
          this->poller->add_socket_handler(this);
          this->connected = true;
          this->connecting = false;
#ifdef BOTAN_FOUND
          if (this->use_tls)
            this->start_tls();
#endif
          this->connection_date = std::chrono::system_clock::now();

          this->on_connected();
          return ;
        }
      else if (errno == EINPROGRESS || errno == EALREADY)
        {   // retry this process later, when the socket
            // is ready to be written on.
          this->connecting = true;
          this->poller->add_socket_handler(this);
          this->poller->watch_send_events(this);
          // Save the addrinfo structure, to use it on the next call
          this->ai_addrlen = rp->ai_addrlen;
          memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen);
          memcpy(&this->addrinfo, rp, sizeof(struct addrinfo));
          this->addrinfo.ai_addr = reinterpret_cast<struct sockaddr*>(&this->ai_addr);
          this->addrinfo.ai_next = nullptr;
          // If the connection has not succeeded or failed in 5s, we consider
          // it to have failed
          TimedEventsManager::instance().add_event(
                                                   TimedEvent(std::chrono::steady_clock::now() + 5s,
                                                              std::bind(&TCPSocketHandler::on_connection_timeout, this),
                                                              "connection_timeout"s + std::to_string(this->socket)));
          return ;
        }
      log_info("Connection failed:", strerror(errno));
      ::close(this->socket);
      this->socket = -1;
    }
  log_error("All connection attempts failed.");
  this->close();
  this->on_connection_failed(strerror(errno));
  return ;
}

void TCPSocketHandler::on_connection_timeout()
{
  this->close();
  this->on_connection_failed("connection timed out");
}

void TCPSocketHandler::connect()
{
  this->connect(this->address, this->port, this->use_tls);
}

void TCPSocketHandler::on_recv()


@@ 267,13 93,13 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size)
    }
  else if (-1 == size)
    {
      if (this->connecting)
      if (this->is_connecting())
        log_warning("Error connecting: ", strerror(errno));
      else
        log_warning("Error while reading from socket: ", strerror(errno));
      // Remember if we were connecting, or already connected when this
      // happened, because close() sets this->connecting to false
      const auto were_connecting = this->connecting;
      const auto were_connecting = this->is_connecting();
      this->close();
      if (were_connecting)
        this->on_connection_failed(strerror(errno));


@@ 333,29 159,15 @@ void TCPSocketHandler::on_send()

void TCPSocketHandler::close()
{
  TimedEventsManager::instance().cancel("connection_timeout"s +
                                        std::to_string(this->socket));
  if (this->connected || this->connecting)
  if (this->is_connected() || this->is_connecting())
    this->poller->remove_socket_handler(this->get_socket());
  if (this->socket != -1)
    {
      ::close(this->socket);
      this->socket = -1;
    }
  this->connected = false;
  this->connecting = false;
  this->in_buf.clear();
  this->out_buf.clear();
  this->port.clear();
  this->resolver.clear();
}

void TCPSocketHandler::display_resolved_ip(struct addrinfo* rp) const
{
  if (rp->ai_family == AF_INET)
    log_debug("Trying IPv4 address ", addr_to_string(rp));
  else if (rp->ai_family == AF_INET6)
    log_debug("Trying IPv6 address ", addr_to_string(rp));
}

void TCPSocketHandler::send_data(std::string&& data)


@@ 379,45 191,35 @@ void TCPSocketHandler::raw_send(std::string&& data)
  if (data.empty())
    return ;
  this->out_buf.emplace_back(std::move(data));
  if (this->connected)
  if (this->is_connected())
    this->poller->watch_send_events(this);
}

void TCPSocketHandler::send_pending_data()
{
  if (this->connected && !this->out_buf.empty())
  if (this->is_connected() && !this->out_buf.empty())
    this->poller->watch_send_events(this);
}

bool TCPSocketHandler::is_connected() const
{
  return this->connected;
}

bool TCPSocketHandler::is_connecting() const
{
  return this->connecting || this->resolver.is_resolving();
}

bool TCPSocketHandler::is_using_tls() const
{
  return this->use_tls;
}

std::string TCPSocketHandler::get_port() const
void* TCPSocketHandler::get_receive_buffer(const size_t) const
{
  return this->port;
  return nullptr;
}

void* TCPSocketHandler::get_receive_buffer(const size_t) const
void TCPSocketHandler::consume_in_buffer(const std::size_t size)
{
  return nullptr;
  this->in_buf = this->in_buf.substr(size, std::string::npos);
}

#ifdef BOTAN_FOUND
void TCPSocketHandler::start_tls()
void TCPSocketHandler::start_tls(const std::string& address, const std::string& port)
{
  Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port));
  Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port));
  this->tls = std::make_unique<Botan::TLS::Client>(
      std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2),
      std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2),

M louloulibs/network/tcp_socket_handler.hpp => louloulibs/network/tcp_socket_handler.hpp +17 -76
@@ 20,10 20,8 @@
#include <list>

/**
 * An interface, with a series of callbacks that should be implemented in
 * subclasses that deal with a socket. These callbacks are called on various events
 * (read/write/timeout, etc) when they are notified to a poller
 * (select/poll/epoll etc)
 * Does all the read/write, buffering etc. With optional tls.
 * But doesn’t do any connect() or accept() or anything else.
 */
class TCPSocketHandler: public SocketHandler
{


@@ 37,13 35,6 @@ public:
  TCPSocketHandler& operator=(TCPSocketHandler&&) = delete;

  /**
   * Connect to the remote server, and call on_connected() if this
   * succeeds. If tls is true, we set use_tls to true and will also call
   * start_tls() when the connection succeeds.
   */
  void connect(const std::string& address, const std::string& port, const bool tls);
  void connect() override final;
  /**
   * Reads raw data from the socket. And pass it to parse_in_buffer()
   * If we are using TLS on this connection, we call tls_recv()
   */


@@ 67,25 58,7 @@ public:
  /**
   * Close the connection, remove us from the poller
   */
  void close();
  /**
   * Called by a TimedEvent, when the connection did not succeed or fail
   * after a given time.
   */
  void on_connection_timeout();
  /**
   * Called when the connection is successful.
   */
  virtual void on_connected() = 0;
  /**
   * Called when the connection fails. Not when it is closed later, just at
   * the connect() call.
   */
  virtual void on_connection_failed(const std::string& reason) = 0;
  /**
   * Called when we detect a disconnection from the remote host.
   */
  virtual void on_connection_close(const std::string& error) = 0;
  virtual void close();
  /**
   * Handle/consume (some of) the data received so far.  The data to handle
   * may be in the in_buf buffer, or somewhere else, depending on what


@@ 93,6 66,9 @@ public:
   * should be truncated, only the unused data should be left untouched.
   *
   * The size argument is the size of the last chunk of data that was added to the buffer.
   *
   * The function should call consume_in_buffer, with the size that was consumed by the
   * “parsing”, and thus to be removed from the input buffer.
   */
  virtual void parse_in_buffer(const size_t size) = 0;
#ifdef BOTAN_FOUND


@@ 105,19 81,10 @@ public:
    return true;
  }
#endif
  bool is_connected() const override final;
  bool is_connecting() const;
  bool is_using_tls() const;
  std::string get_port() const;
  std::chrono::system_clock::time_point connection_date;

private:
  /**
   * Initialize the socket with the parameters contained in the given
   * addrinfo structure.
   */
  void init_socket(const struct addrinfo* rp);
  /**
   * Reads from the socket into the provided buffer.  If an error occurs
   * (read returns <= 0), the handling of the error is done here (close the
   * connection, log a message, etc).


@@ 137,12 104,14 @@ private:
  void raw_send(std::string&& data);

#ifdef BOTAN_FOUND
 protected:
  /**
   * Create the TLS::Client object, with all the callbacks etc. This must be
   * called only when we know we are able to send TLS-encrypted data over
   * the socket.
   */
  void start_tls();
  void start_tls(const std::string& address, const std::string& port);
 private:
  /**
   * An additional step to pass the data into our tls object to decrypt it
   * before passing it to parse_in_buffer.


@@ 185,20 154,11 @@ private:
   * Where data is added, when we want to send something to the client.
   */
  std::vector<std::string> out_buf;
protected:
  /**
   * DNS resolver
   */
  Resolver resolver;
  /**
   * Keep the details of the addrinfo returned by the resolver that
   * triggered a EINPROGRESS error when connect()ing to it, to reuse it
   * directly when connect() is called again.
   * Whether we are using TLS on this connection or not.
   */
  struct addrinfo addrinfo;
  struct sockaddr_in6 ai_addr;
  socklen_t ai_addrlen;

protected:
  bool use_tls;
  /**
   * Where data read from the socket is added until we can extract a full
   * and meaningful “message” from it.


@@ 207,9 167,9 @@ protected:
   */
  std::string in_buf;
  /**
   * Whether we are using TLS on this connection or not.
   * Remove the given “size” first bytes from our in_buf.
   */
  bool use_tls;
  void consume_in_buffer(const std::size_t size);
  /**
   * Provide a buffer in which data can be directly received. This can be
   * used to avoid copying data into in_buf before using it. If no buffer


@@ 219,31 179,12 @@ protected:
   */
  virtual void* get_receive_buffer(const size_t size) const;
  /**
   * Hostname we are connected/connecting to
   */
  std::string address;
  /**
   * Port we are connected/connecting to
   */
  std::string port;

  bool connected;
  bool connecting;

  bool hostname_resolution_failed;

  /**
   * Address to bind the socket to, before calling connect().
   * If empty, it’s equivalent to binding to INADDR_ANY.
   * Called when we detect a disconnection from the remote host.
   */
  std::string bind_addr;
  virtual void on_connection_close(const std::string& error) = 0;
  virtual void on_connection_failed(const std::string& error) = 0;

private:
  /**
   * Display the resolved IP, just for information purpose.
   */
  void display_resolved_ip(struct addrinfo* rp) const;

#ifdef BOTAN_FOUND
  /**
   * Botan stuff to manipulate a TLS session.

M louloulibs/xmpp/xmpp_component.cpp => louloulibs/xmpp/xmpp_component.cpp +1 -1
@@ 39,7 39,7 @@ static std::set<std::string> kickable_errors{
    };

XmppComponent::XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret):
  TCPSocketHandler(poller),
  TCPClientSocketHandler(poller),
  ever_auth(false),
  first_connection_try(true),
  secret(secret),

M louloulibs/xmpp/xmpp_component.hpp => louloulibs/xmpp/xmpp_component.hpp +2 -2
@@ 2,7 2,7 @@


#include <xmpp/adhoc_commands_handler.hpp>
#include <network/tcp_socket_handler.hpp>
#include <network/tcp_client_socket_handler.hpp>
#include <xmpp/xmpp_parser.hpp>
#include <xmpp/body.hpp>



@@ 40,7 40,7 @@
 *
 * TODO: implement XEP-0225: Component Connections
 */
class XmppComponent: public TCPSocketHandler
class XmppComponent: public TCPClientSocketHandler
{
public:
  explicit XmppComponent(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& secret);

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +2 -2
@@ 131,7 131,7 @@ IrcClient::IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname
                     const std::string& nickname, const std::string& username,
                     const std::string& realname, const std::string& user_hostname,
                     Bridge& bridge):
  TCPSocketHandler(poller),
  TCPClientSocketHandler(poller),
  hostname(hostname),
  user_hostname(user_hostname),
  username(username),


@@ 338,7 338,7 @@ void IrcClient::parse_in_buffer(const size_t)
      if (pos == std::string::npos)
        break ;
      IrcMessage message(this->in_buf.substr(0, pos));
      this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
      this->consume_in_buffer(pos + 2);
      log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);

      // Call the standard callback (if any), associated with the command

M src/irc/irc_client.hpp => src/irc/irc_client.hpp +2 -2
@@ 5,7 5,7 @@
#include <irc/irc_channel.hpp>
#include <irc/iid.hpp>

#include <network/tcp_socket_handler.hpp>
#include <network/tcp_client_socket_handler.hpp>
#include <network/resolver.hpp>

#include <unordered_map>


@@ 23,7 23,7 @@ class Bridge;
 * Represent one IRC client, i.e. an endpoint connected to a single IRC
 * server, through a TCP socket, receiving and sending commands to it.
 */
class IrcClient: public TCPSocketHandler
class IrcClient: public TCPClientSocketHandler
{
public:
  explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,