JabberWerxC Library Developer Guide |
The JabberWerxC library is a C XMPP library that allows you to integrate basic presence and messaging capailities into other C-based applications.
JabberWerxC is comprised of the following modules:
libevent2
: an open-source library that abstracts the various platform-specific I/O eventing mechanisms (select, epoll, kqueue, et al) with a common API for scheduling asynchronous callbacks for networking or timeout operationslibjabberwerx
: a low-level API for sending and receiving XMPP stanzasjw_err
jw_htable
jw_jid
jw_dom_node
jw_event
jw_workq
jw_sasl_factory
and jw_sasl_mech
jw_stream
jw_dom_node
instances and translates to (and from) the octet stream over the network (persistent TCP or BOSH connections).
jw_client
jw_stream
to manage the connection, handles authentication and identity, and allows the user to send and receive XMPP stanzas (<iq/>, <message/>, and <presence/> elements). This is the most common interface used to communicate with an XMPP server.
JWC has a simplified DOM that represents the minimum XML required by XMPP. It supports namespaces, attributes, elements, and character data; but does not provide for comments, processing instructions, or internal or external entity references.
Names in jw_dom
use Clark notation in the form '{namespace-uri}local-name', with some convenience functions for getting just the namespace-URI and local-name.
The starting point is jw_dom_ctx
, which centralizes the memory management for DOM nodes. Multiple jw_dom_ctx
instances can be in use; memory is not shared between jw_dom_ctx
s. All nodes for a jw_dom_ctx
are freed by calling jw_dom_context_destroy()
. A node can be copied from one jw_dom_ctx
to another. All nodes are represented by the jw_dom
type.
To create nodes:
jw_dom_context_create(ctx)
: Creates the DOM context, from which all other nodes are ultimately createdjw_dom_element_create(ctx, ename)
: Creates an element node with the given namejw_dom_text_create(ctx, val)
: Creates a text node with the given character data valueThe API supports all of the basic operations for a node:
jw_dom_get_context()
: gets the owning jw_dom_ctx
of a nodejw_dom_get_nodetype()
: gets the type of a node (attribute, namespace declaration, element, text)jw_dom_get_ename()
: gets the name of a node (if any)jw_dom_get_value()
: gets the value of a node (if any)jw_dom_get_namespace_uri(e)
: gets the namespace-URI of the element or attributejw_dom_get_local_name(e)
: gets the local-part of the element or attributeNode lists are done using an iterative process. From an element, a number of lists are obtained:
jw_dom_get_first_namespace(e)
: gets the first namespace declarationjw_dom_get_first_attribute(e)
: gets the first attributejw_dom_get_first_child(e)
: gets the first child node (text or element)jw_dom_get_first_element(e, ename)
: gets the first element (optionally matching a name)The next node in the list is retrieved using jw_dom_get_sibling(n)
. The parent is retrieved using jw_dom_get_parent(n)
. A node can be removed from its parent using jw_dom_detach(n)
.
As a convenience, the following are available:
jw_dom_get_attribute(e, ename)
: retrieves the value of the named attribute (or NULL if not present)jw_dom_find_namespace_uri(e, prefix)
: retrieves the namespace-URI for the given prefix, searching ancestors if necessaryjw_dom_find_namespace_prefix(e, uri)
: retrieves the prefix for the given namespace-URI, searching ancestors if necessaryTo modify an element:
jw_dom_set_attribute(e, ename, val)
: sets the value of the attribute with the given name, or removes the attribute if the value is NULL
jw_dom_put_namespace(e, prefix, uri)
: set the namespace declaration for the given prefix to have the given URI; or removes the namespace declaration if the URI is NULLjw_dom_add_child(e, child)
: adds a child (element or text) to the given parent element, after all other childrenjw_dom_remove_child(e, child)
: removes a child (element or text) from the given parent elementDetailed API documentation can be found here.
The event loop is managed using libevent's event_base
. Every asynchronous function in JWC (e.g. jw_client_connect()
) does not actually perform its operation; instead it validates inputs and prepares for the next stage, but the operations are not performed until event_base_dispatch()
or one of its alternatives is called.
JWC supports eventing using the jw_event
type. It allows the user to bind a function to a particular event with user-specific data, and will be called when the condition is triggered.
Each time a jw_event
is triggered, each callback receives a jw_event_data
structure which has at least the following read-only information:
void *
): The source of the eventconst char *
): The name of the eventjw_event
): The notifier on which the event triggeredvoid *
): event data specific to this triggeringTypes that expose events have a type_event(eventName)
function (e.g. jw_client_event
) to return the associated jw_event
instance. The function jw_event_bind
is called to register a callback function (optionally with a user-supplied pointer that will be used as the arg
parameter of the callback), while the function jw_event_unbind
is called to unregister a callback function.
User-defined types can expose their own events by:
jw_event_dispatcher
jw_event_dispatcher_create_event(dispatcher, eventName)
for each new eventuser-type_event(eventName)
which returns the named event; jw_event_dispatcher_get_event(dispatcher, eventName)
can be used to locate the appropriate jw_event
.The function jw_event_trigger
is called to trigger an event, which in turn executes each callback registered to it. If the event dispatcher was initialized with a workq object, the callbacks will be scheduled on the queue and will be called in the next iteration of the libevent event loop. If the event dispatcher was not initialized with a workq object, the callbacks are run immediately from the current stack frame.
It is important to note that jw_event_trigger
can fail, primarily due to out-of-memory errors. Where triggering the event is critical, such as for _CLOSED
or _DESTROYED
events, users can pre-allocate the required memory at a safe time with the jw_event_prepare_trigger
and use the jw_event_trigger_prepared
function to actually trigger the event without danger of failure. Prepared triggers will be cleaned up automatically when they are used. If they are not used, they can be manually destroyed via jw_event_unprepare_trigger
.
Callbacks are executed in a "breadth-first" order: all callbacks for a given event are executed before any other events triggered by those callbacks are executed. This means jw_event_trigger
may return before the callbacks have been executed (if the event dispatcher was initialized with a workq object, jw_event_trigger
will always return before the callbacks have been executed); a jw_event_result_callback
function can be passed to jw_event_trigger
if code must be executed after all the related callbacks for a given event are complete.
JWC treats the events for received stanzas ("iqReceived", "messageReceived", and "presenceReceived") as a chain of events: a "before" event (e.g. "beforeIqReceived"), the "on" event (e.g. "iqReceived"), and the "after" event (e.g. "afterIqReceived"). For these events, all of the callbacks for a given level are triggered; if any callback sets the jw_event_data
structure's handled
flag to true, then the subsequent events in the chain are not triggered. For instance, if a callback for "beforeIqReceived" sets handled
to true, then the "iqReceived" and "afterIqReceived" events are not triggered.
Detailed API documentation can be found here.
There are two levels of error-handling in JWC: return values and the jw_err
structure. Most functions that can fail return bool
. true
means the function succeeded while false
means an error was encountered. The jw_err
(if provided by the user) provides more detail on the failure, such as a type (enumerated in jw_errcode
), and a pre-canned textual message.
In the process of establishing a connection to an XMPP server, a jw_client instance will participate in a SASL authentication sequence. Normally, users of jw_client need only provide a jid (JW_CLIENT_CONFIG_USERJID) and password (JW_CLIENT_CONFIG_USERPW) in the jw_htable config passed to the jw_client_connect() function and everything will "just work". However, the process can be customized for users that require non-default authentication mechanisms.
A jw_sasl_factory instance manages a set of SASL mechanisms and assists in choosing the "best" one given a specification for what the remote endpoint supports. As far as the jw_sasl_factory instance is concerned, the "best", or "most preferred" mechanism is the one most recently registered with the factory. The rule is: when registering mechanisms with a factory, register them in reverse order of preference. The factory should then be added to the jw_htable config passed to the jw_client_connect function with the key JW_CLIENT_CONFIG_SASL_FACTORY.
Creating a SASL mechanism is as simple as mapping the stages of the mechanism sequence to a function table. Specifically, there is an init
function, called before SASL authentication begins; a cleanup
function, called after it completes; a start
function, called upon initiation of the authentication sequence; and a step
function, called for each authentication step thereafter. The framework provides the mechanism with storage for a single void* pointer so the mechanism can keep private state. JWC already has implementations for default, "Manditory-To-Implement" mechanisms (PLAIN and MD5) which can be used in any custom factory sets that require them.
The mechanisms handle only raw, base64-decoded data in their start
and step
functions. Higher-level communications, such as <success/>and <failure/> elements, are handled by the framework and do not reach the mechanism implementations themselves. The data the mechansisms produce for output does not need to be base64-encoded -- the framework will do that automatically if not explicitly told not to. See the implementation of SASL PLAIN (src/sasl/sasl_mech_plain.c) for a complete example.
A walkthrough for creating a simple client can be seen in the Getting Started Guide.