view irccdctl/connection.h @ 69:aa58ff8b6543

Merge from stable-2
author David Demelier <markand@malikania.fr>
date Sun, 13 Mar 2016 22:34:48 +0100
parents 1158cffe5a5e
children
line wrap: on
line source

/*
 * connection.h -- value wrapper for connecting to irccd
 *
 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef _IRCCD_CONNECTION_H_
#define _IRCCD_CONNECTION_H_

#include <cassert>
#include <stdexcept>

#include <elapsed-timer.h>
#include <json.h>
#include <sockets.h>
#include <system.h>
#include <util.h>

namespace irccd {

/**
 * @class Connection
 * @brief Abstract class for connecting to irccd from Ip or Local addresses.
 */
class Connection {
protected:
	ElapsedTimer m_timer;

	/**
	 * Clamp the time to wait to be sure that it will be never less than 0.
	 */
	inline int clamp(int timeout)
	{
		return timeout < 0 ? -1 : (timeout - (int)m_timer.elapsed() < 0) ? 0 : (timeout - m_timer.elapsed());
	}

public:
	/**
	 * Wait for the next requested response.
	 *
	 * @param name the response name
	 * @param timeout the optional timeout
	 * @return the object
	 * @throw net::Error on errors or on timeout
	 */
	json::Value next(const std::string &name, int timeout = 30000);

	/**
	 * Just wait if the operation succeeded.
	 *
	 * @param name the response name
	 * @param timeout the timeout
	 */
	void verify(const std::string &name, int timeout = 30000);

	/**
	 * Check if the socket is still connected.
	 *
	 * @return true if connected
	 */
	virtual bool isConnected() const noexcept = 0;

	/**
	 * Try to connect to the host.
	 *
	 * @param timeout the maximum time in milliseconds
	 * @throw net::Error on errors or timeout
	 */
	virtual void connect(int timeout = 30000) = 0;

	/**
	 * Try to send the message in 30 seconds. The message must not end with \r\n\r\n, it is added automatically.
	 *
	 * @pre msg must not be empty
	 * @param msg the message to send
	 * @param timeout the maximum time in milliseconds
	 * @throw net::Error on errors
	 */
	virtual void send(std::string msg, int timeout = 30000) = 0;

	/**
	 * Get the next event from irccd.
	 *
	 * This functions throws if the connection is lost.
	 *
	 * @param timeout the maximum time in milliseconds
	 * @return the next event
	 * @throw net::Error on errors or disconnection
	 */
	virtual json::Value next(int timeout = 30000) = 0;
};

/**
 * @class ConnectionBase
 * @brief Implementation for Ip or Local.
 */
template <typename Address>
class ConnectionBase : public Connection {
private:
	net::SocketTcp<Address> m_socket;
	net::Listener<> m_listener;
	Address m_address;

	/* Input buffer */
	std::string m_input;

public:
	/**
	 * Construct the socket but do not connect immediately.
	 *
	 * @param address the address
	 */
	ConnectionBase(Address address)
		: m_address(std::move(address))
	{
		m_socket.set(net::option::SockBlockMode{false});
		m_listener.set(m_socket.handle(), net::Condition::Readable);
	}

	/**
	 * @copydoc Connection::isConnected
	 */
	bool isConnected() const noexcept override
	{
		return m_socket.state() == net::State::Connected;
	}

	/**
	 * @copydoc Connection::connect
	 */
	void connect(int timeout) override;

	/**
	 * @copydoc Connection::send
	 */
	void send(std::string msg, int timeout) override;

	/**
	 * @copydoc Connection::next
	 */
	json::Value next(int timeout) override;
};

template <typename Address>
void ConnectionBase<Address>::connect(int timeout)
{
	m_socket.connect(m_address);

	if (m_socket.state() == net::State::Connecting) {
		m_listener.set(m_socket.handle(), net::Condition::Writable);
		m_listener.wait(timeout);
		m_socket.connect();
		m_listener.unset(m_socket.handle(), net::Condition::Writable);
	}
}

template <typename Address>
void ConnectionBase<Address>::send(std::string msg, int timeout)
{
	assert(!msg.empty());

	/* Add termination */
	msg += "\r\n\r\n";

	m_listener.remove(m_socket.handle());
	m_listener.set(m_socket.handle(), net::Condition::Writable);
	m_timer.reset();

	while (!msg.empty()) {
		/* Do not wait the time that is already passed */
		m_listener.wait(clamp(timeout));

		/* Try to send at most as possible */
		msg.erase(0, m_socket.send(msg));
	}

	/* Timeout? */
	if (!msg.empty())
		throw std::runtime_error("operation timed out while sending to irccd");
}

template <typename Address>
json::Value ConnectionBase<Address>::next(int timeout)
{
	/* Maybe there is already something */
	std::string buffer = util::nextNetwork(m_input);

	m_listener.remove(m_socket.handle());
	m_listener.set(m_socket.handle(), net::Condition::Readable);
	m_timer.reset();

	/* Read if there is nothing */
	while (buffer.empty() && isConnected()) {
		/* Wait and read */
		m_listener.wait(clamp(timeout));
		m_input += m_socket.recv(512);

		/* Finally try */
		buffer = util::nextNetwork(m_input);
	}

	if (!isConnected())
		throw std::runtime_error("connection lost");

	json::Value value(json::Buffer{buffer});

	if (!value.isObject())
		throw std::invalid_argument("invalid message received");

	return value;
}

} // !irccd

#endif // !_IRCCD_CONNECTION_H_