JabberWerxC Library Getting Started Guide

C XMPP-based Presence and Instant Messaging

Introduction

This tutorial will step you through the process of creating a basic XMPP client using JabberWerxC. For simplicity most error handling is omitted.

Some basic requirements before proceeding are:

Examples in this document use the following header files:

#include <jabberwerx/jabberwerx.h>
#include <jabberwerx/client.h>
#include <jabberwerx/crypto/tls.h>
#include <jabberwerx/util/log.h>

#include <event2/event.h>

Prerequisites

In order to build and run your project, the JWC library and its dependencies need to be in the appropriate paths. To aid in deployment, the JWC package includes all necessary dependencies. If JWC is installed in /usr/local/jabberwerxc (JABBERWERXC_HOME), you will usually have the following for Linux:

LD_LIBRARY_PATH=$JABBERWERXC_HOME/lib:$LD_LIBRARY_PATH
and for Mac OS X:
DYLD_LIBRARY_PATH=$JABBERWERXC_HOME/lib:$DYLD_LIBRARY_PATH

Also, the the following compiler and linker flags are necessary:

CFLAGS=-I${JABBERWERXC_HOME}/include
LDFLAGS=-L${JABBERWERXC_HOME}/lib -levent_core -levent_extra -ljabberwerx

The Connection Base

JabberWerxC uses libevent for underlying connectivity and eventing. libevent uses a struct event_base* to drive this process. Application writers must create the base and let libevent continually dispatch until terminated by jw_client when the connection is closed.

Add this code to your main before using the jw_client:

    struct event_base *base = event_base_new();

Connecting to XMPP

JabberWerxC uses a jw_client to connect and authenticate with an XMPP server.

    jw_htable *config;
    jw_workq  *workq;
    jw_client *client;
    jw_err    err;

    jw_htable_create(0, jw_strcase_hashcode, jw_strcase_compare,
                     &config, NULL);
    jw_htable_put(config, JW_STREAM_CONFIG_SELECTOR, base, NULL, NULL);

    jw_workq_create(config, &workq);

    jw_client_create(workq, &client, &err);

A connection attempt is made using the jw_client_connect function. Connection configuration is passed to the function using a jw_htable. The same config htable can be used for all related objects, such as the previously-created workq. For simplicity in the code below, the origins of the strings stored in the hashtable are not explained here.

    // user jid
    jw_htable_put(config, JW_CLIENT_CONFIG_USERJID, jid_str, NULL, NULL);

    // password
    jw_htable_put(config, JW_CLIENT_CONFIG_USERPW, password_str, NULL, NULL);

    // attempt to connect
    if (!jw_client_connect(client, config, &err))
    {
        jw_err_log(JW_LOG_ERROR, &err, "could not connect");
    }

Since jw_client is completely asynchronous, nothing beyond validation has been done. Libevent needs to be told to continue to process network events, usually by calling event_base_dispatch. Add the following code after jw_client_connect:

    // this will return once the client disconnects
    event_base_dispatch(base);
    
    // release resources
    jw_workq_destroy(workq);
    event_base_free(base);

TLS and BOSH over https require an acceptance callback configuration option. This callback gives users a chance to allow a connection that would fail because of an invalid certificate.

static void clientTLSAcceptCertCB(jw_tls_session *sess)
{
    // always proceed with a connection by calling jw_tls_proceed
    jw_tls_proceed(sess, true);
}
And in main, as part of the configuration but before the connection attempt, add the following:
    jw_tls_accept_cb_htable_value *acbv;

    acbv = jw_data_malloc(sizeof(jw_tls_accept_cb_htable_value));
    acbv->cb = clientTLSAcceptCertCB;
    jw_htable_put(config, JW_TLS_CONFIG_ACCEPT_CB, acbv,
                  jw_htable_free_data_cleaner, NULL);

Connection events

jw_client_connect connects asynchronously. jw_client defines connection events where the user should perform post connection work. This is true of the entire library: most operations are asynchronous and users should always implement consequent actions in event handlers.

static void clientConnectedCallback(jw_event_data event, void *arg)
{
    jw_log(JW_LOG_INFO, "client connected");
}

static void clientDisconnectedCallback(jw_event_data event, void *arg)
{
    jw_log(JW_LOG_INFO, "client disconnected");

    jw_dom_node *errDom = event->data;
    if (errDom)
    {
        // examine errDom for error information
    }
}
In order to connect the previous functions to their events, make the following calls in your main before calling jw_client_connect:
    jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_CONNECTED),
                  clientConnectedCallback, NULL);
    jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_DISCONNECTED),
                  clientDisconnectedCallback, NULL);

clientConnectedCallback is triggered when the connection completes successfully and clientDisconnectedCallback when the client disconnects. Note that the JW_CLIENT_EVENT_DISCONNECTED event is fired when a connection attempt ends in error, but only if the call to jw_client_connect originally returned true.

More jw_client Events

A full list of events JabberWerxC supports can be found at Events. Here is an example of using some of the more common events.
static void clientPresenceReceivedCB(jw_event_data event, void *arg)
{
    jw_log(JW_LOG_INFO, "presence received");

    jw_dom_node *node = event->data;
    if (node)
    {
        // examine node for presence information
    }
}

static void clientMessageReceivedCB(jw_event_data event, void *arg)
{
    jw_log(JW_LOG_INFO, "message received");

    jw_dom_node *node = event->data;
    if (node)
    {
        // examine node for message information
    }
}
And once again, these events must be bound before the connection attempt:
    jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_PRESENCE_RECEIVED),
                  clientPresenceReceivedCB, NULL);
    jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_MESSAGE_RECEIVED),
                  clientMessageReceivedCB, NULL);

Sending XMPP Stanzas

jw_client exposes a function to send an XMPP stanza.

    JABBERWERX_API bool jw_client_send_stanza(jw_client *client,
                                              jw_dom_node *stanza,
                                              jw_err *err);
Here is a JW_CLIENT_EVENT_MESSAGE_RECEIVED handler that echoes chat messages back to the sender.
static void clientMessageReceivedCB(jw_event_data event, void *arg)
{
    jw_dom_node *node = event->data;
    jw_client *client = arg;

    const char *type = jw_dom_get_attribute(node, "type");
    if (0 == jw_strcmp(type, "chat"))
    {
        jw_dom_ctx *dom_ctx;
        jw_dom_node *dup;

        jw_dom_context_create(&dom_ctx, NULL);
        jw_dom_import(dom_ctx, node, true, &dup, NULL);

        const char *to = jw_dom_get_attribute(dup, "{}to");
        const char *from = jw_dom_get_attribute(dup, "{}from");

        jw_dom_set_attribute(dup, "{}from", to, NULL);
        jw_dom_set_attribute(dup, "{}to", from, NULL);

        jw_client_send_stanza(client, dup, NULL);
    }
}

Cleanup

To clean up, you can call jw_client_destroy in your JW_CLIENT_EVENT_DISCONNECTED event (or any time after the disconnected event is fired):

static void clientDisconnectedCallback(jw_event_data event, void *arg)
{
    jw_log(JW_LOG_INFO, "client disconnected");

    jw_dom_node *errDom = event->data;
    if (errDom)
    {
        // examine errDom for error information
    }
    
    jw_client_destroy(event->source);
}

Cleaning up the configuration and other client-related data is done asynchronously in (or after) a JW_CLIENT_EVENT_DESTROYED event:

static void clientDestroyedCallback(jw_event_data event, void *arg)
{
    // be sure to register config as the arg when binding the event
    jw_htable *config = arg;
    
    jw_htable_destroy(config);
    
    // additional cleanup
}

The only structures that cannot be destroyed in this callback are the ones that may be in use at the time the callback is running, such as the libevent event_base* and the workq. Instead, these should be destroyed after event_base_dispatch returns.

Then bind the event by adding the following after your other callback bindings:
    jw_event_bind(jw_client_event(client, JW_CLIENT_EVENT_DESTROYED),
                  clientDestroyedCallback,
                  config);

To cleanup the values added to the configuration table, specify a cleaner function when calling jw_htable_put. If the key is static and the value is created with one of the JabberWerxC memory allocation functions (e.g. jw_data_malloc, jw_strdup, etc), the convenience function jw_htable_free_data_cleaner can be used. Assuming jid_str and password_str are allocated using jw_strdup, replace the configuration setup for USERJID and USERPW calls with the following:

    // user jid
    jw_htable_put(config, JW_CLIENT_CONFIG_USERJID, jid_str,
                  jw_htable_free_data_cleaner, NULL);
    // password
    jw_htable_put(config, JW_CLIENT_CONFIG_USERPW, password_str,
                  jw_htable_free_data_cleaner, NULL);

Conclusion

Using the examples above you should now be able to create a client, connect to an XMPP server, display and respond to presence and messages. See the simpleclient implementation at simpleclient for a more sophisticated example of a JabberWerxC client.
© 2012 Cisco Systems, Inc. All rights reserved.