#include "irc_net_internal.h"
#include "tcp.h"
#include "ssl.h"
#include "log.h"
#include <time.h>
#include <assert.h>
void irc_net_disconnect (struct irc_net *net)
{
struct irc_chan *chan = NULL;
// mark
net->connected = false;
// destroy connection and set NULL
irc_conn_destroy(net->conn);
net->conn = NULL;
// update channel state
TAILQ_FOREACH(chan, &net->channels, net_channels) {
// XXX: notify channel somehow
}
}
/**
* We have succesfully established a connection to our server with the given transport, so create the irc_conn and bind
* it to us.
*
* If this fails, this will clean up any partial state, including \a transport.
*/
static err_t irc_net_connected (struct irc_net *net, transport_t *transport, struct error_info *err)
{
// mark state
net->connecting = false;
// create the irc connection state
if (irc_conn_create(&net->conn, transport, &irc_net_conn_callbacks, net, err))
goto error;
// add our command handlers
if ((ERROR_CODE(err) = irc_conn_add_cmd_handlers (net->conn, irc_net_cmd_handlers, net)))
goto error;
// register
if ((ERROR_CODE(err) = irc_conn_register(net->conn, &net->info.register_info)))
goto error;
// great, we're alive now
net->connected = true;
net->connected_ts = time(NULL);
return SUCCESS;
error:
if (!net->conn) {
// cleanup transport ourselves
transport_destroy(transport);
} else {
// cleanup the partial stuff
irc_conn_destroy(net->conn);
net->conn = NULL;
}
return ERROR_CODE(err);
}
/**
* Our sock_*_connect_async callback. If the connect ended up failing, then try and reconnect later. Otherwise, do
* irc_net_connected().
*/
static void irc_net_on_connect (transport_t *transport, void *arg)
{
struct irc_net *net = arg;
error_t err;
// yay
if (irc_net_connected(net, transport, &err))
log_error(&err, "irc_net_connected");
}
static void irc_net_on_connect_error (transport_t *transport, const error_t *conn_err, void *arg)
{
struct irc_net *net = arg;
error_t err;
// clean up
transport_destroy(transport);
// attempt reconnect later
log_error(conn_err, "connect failed");
if (irc_net_connect(net, false, &err))
log_error(&err, "unable to reconnect");
}
static const struct transport_callbacks irc_net_transport_callbacks = {
.on_connect = irc_net_on_connect,
.on_error = irc_net_on_connect_error,
};
/**
* The low-level connect() implementation, connects based on irc_net::info, calling irc_net_connected/irc_net_reconnect
* later if this succeeds.
*/
static err_t irc_net_do_connect (struct irc_net *net, struct error_info *err)
{
struct irc_net_info *info = &net->info;
struct transport_info transport_info = { &irc_net_transport_callbacks, net, TRANSPORT_READ | TRANSPORT_WRITE };
transport_t *transport = NULL;
// sanity check
assert(!net->connecting && !net->connected);
// connect based on what's known
if (info->transport) {
log_debug("connected using raw transport: %p", info->transport);
// direct transport connection
transport = info->transport;
// invalidate it from info since it will get destroyed later
info->transport = NULL;
// then create the transport right away
if (irc_net_connected(net, transport, err))
goto error;
} else if (info->ssl_cred) {
// aquire a ref
// NOTE: before any error handling
ssl_client_cred_get(info->ssl_cred);
log_debug("connecting to [%s]:%s using SSL", info->hostname, info->service);
// connect
if (ssl_connect(&transport_info, &transport, info->hostname, info->service, info->ssl_cred, err))
goto error;
net->connecting = true;
} else if (info->hostname || info->service) {
log_debug("connecting to [%s]:%s", info->hostname, info->service);
// begin async connect
if (tcp_connect(&transport_info, &transport, info->hostname, info->service, err))
goto error;
net->connecting = true;
} else {
RETURN_SET_ERROR_STR(err, ERR_MISC, "no connection info specified");
}
return SUCCESS;
error:
return ERROR_CODE(err);
}
/**
* Reconnect timer callback
*/
static void irc_net_reconnect_timer_cb (int fd, short what, void *arg)
{
struct irc_net *net = arg;
struct error_info err;
(void) fd;
(void) what;
// execute it?
if (irc_net_connect(net, true, &err))
log_error(&err, "unable to reconnect");
}
/**
* Schedule a reconnection attempt in IRC_NET_RECONNECT_INTERVAL.
*/
static err_t irc_net_schedule_reconnect (struct irc_net *net, struct error_info *err)
{
// ...for x seconds
struct timeval tv = { IRC_NET_RECONNECT_INTERVAL, 0 };
// schedule
if (event_add(net->reconnect_timer, &tv))
return SET_ERROR(err, ERR_EVENT_ADD);
// oke
return SUCCESS;
}
err_t irc_net_connect (struct irc_net *net, bool now, struct error_info *err)
{
// attempt to reconnect now?
if (now) {
if (irc_net_do_connect(net, err))
// log error and continue below with schedule_reconnect
log_error(err, "reconnect failed");
else
// connecting, done
return SUCCESS;
}
// schedule for later
if (irc_net_schedule_reconnect(net, err))
goto error;
// ok
return SUCCESS;
error:
return ERROR_CODE(err);
}
// XXX: to get the ev_base
#include "sock_internal.h"
err_t irc_net_connect_init (struct irc_net *net, struct error_info *err)
{
// look up the ev_base
struct event_base *ev_base = _sock_stream_ctx.ev_base;
// reconnect timer
if ((net->reconnect_timer = evtimer_new(ev_base, irc_net_reconnect_timer_cb, net)) == NULL)
return SET_ERROR(err, ERR_EVENT_NEW);
// ok
return SUCCESS;
}
void irc_net_connect_destroy (struct irc_net *net)
{
event_free(net->reconnect_timer);
}