src/irc_net_connect.c
author Tero Marttila <terom@fixme.fi>
Thu, 28 May 2009 01:17:36 +0300
branchnew-lib-errors
changeset 219 cefec18b8268
parent 180 22967b165692
permissions -rw-r--r--
some of the lib/transport stuff compiles

#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);
}