Skip to content

Erlang/Elixir XMPP parsing and serialization library on top of Fast XML

License

Notifications You must be signed in to change notification settings

processone/xmpp

Repository files navigation

Erlang/Elixir XMPP library

CI Coverage Status Hex version

The library provides comprehensive representation of XMPP elements as well as tools to work with them. Every such element is represented by an Erlang record. Most of the library's code is auto generated and thus considered to be bug free and efficient.

The approach is very similar to ASN.1, Google Protocol Buffers or Apache Thrift: an XML element is transformed into internal language structure (an Erlang record in our case) - the process known as "decoding". During decoding, validation is also performed, thus well-typed structures are generated, potentially decreasing bugs related to handcrafted parsing. A reverse process known as "encoding" is applied for transforming an Erlang record into an XML element.

The library should be used along with fast_xml library, because it is only able to decode from and encode to structures generated by that library (that is, xmlel() elements).

Table of Contents:

  1. Status
  2. Compiling
  3. API
  4. Usage
    1. Initialization and header files
    2. XMPP elements
    3. Decoding and encoding
    4. Stanzas
      1. Common fields
      2. Constructing stanza responses
    5. Error elements
    6. Text elements
    7. Pretty printer
    8. Namespaces
    9. XMPP addresses
    10. Language translation
  5. Supported XMPP elements

Status

The library is considered as production ready and has been used in ejabberd XMPP server since version 16.12. However, the API is quite unstable so far and incompatibilities may be introduced from release to release. The stable API will be denoted by 2.x tag in the future.

Dependency

You need at least Erlang OTP 19.0.

Compiling

As usual, the following commands are used to obtain and compile the library:

$ git clone https://github.com/processone/xmpp.git
$ cd xmpp
$ make

API

The full API is documented in doc/API.md

Usage

Initialization and header files

Before calling any function from the library, xmpp application should be started.

Although there are several header files which a developer might find useful to look into, they should not be included directly in the code. Only include/xmpp.hrl file should be included, because it already includes all needed headers and also defines some useful macros. So the typical code should look like:

%% file: foo.erl
-module(foo).
-include_lib("xmpp/include/xmpp.hrl").
...
start() ->
    application:start(xmpp),
    ...

XMPP elements

All XMPP elements (records) are defined in include/xmpp_codec.hrl file. For convenience, every record has the corresponding type spec. There is also predefined xmpp_element() type which is a container for all defined record types: so sometimes we will refer to an arbitrary XMPP element as xmpp_element() in the rest of this document. These records are generated automatically by XML generator from specification file specs/xmpp_codec.spec. The specification file contains information about XML elements defined within XMPP related namespace.

TODO: writing specs for new elements will be documented later. For now you can learn by example: pick up a spec of any element which is very close to the one you want to add a spec for and use it as a pattern

WARNING: you should not include xmpp_codec.hrl in your erlang code. Include xmpp.hrl instead. xmpp_codec.hrl should be only used to consult definitions of existing XMPP elements.

Decoding and encoding

Once an xmlel() element is obtained (either using fxml_stream:parse_element/1 function or by any other means), it can be decoded using either decode/1 or decode/3 functions. The result will be an XMPP element. Note that decoding might fail if there is no known XMPP element for the provided xmlel() element, or xmlel() is invalid, so you should call these functions inside try ... catch. Exceptions returned during decoding can be formatted using format_error/1 or io_format_error/1 functions.

Example:

handle(#iq{type = get, sub_els = [El]} = IQ) ->
    try xmpp:decode(El) of
        Pkt -> handle_iq_child_element(Pkt)
    catch _:{xmpp_codec, Reason} ->
        Txt = xmpp:format_error(Reason),
	io:format("iq has malformed child element: ~s", [Txt]),
	handle_iq_with_malformed_child_element(IQ)
    end.

encode/1 and encode/2 functions can be used for reverse transformation, i.e. to encode an XMPP element into xmlel() element. Encoding would never fail as long as provided XMPP element is valid.

Stanzas

Amongst all XMPP elements defined in the library, the most notable ones are stanzas: message(), presence() and iq() elements. A large part of xmpp module deals with stanzas.

Common fields

Records of all stanzas have several common fields: id, type, lang, from, to, sub_els and meta. Although it's acceptable to manipulate with these fields directly (which is useful in pattern matching, for example), xmpp module provides several functions to work with them:

Constructing stanza responses

For creating iq() replies the following functions exist: make_iq_result/1 and make_iq_result/2.

These two functions are iq() specific and create iq() elements of type result only. To create an error response from an arbitrary stanza (including iq()) make_error/2 function can be used.

Error elements

A series of functions is provided by xmpp module for constructing stanza_error() or stream_error() elements. To construct stanza_error() elements the functions with err_ prefix can be used, such as err_bad_request/0 or err_internal_server_error/2. To construct stream_error() elements the functions with serr_ prefix can be used, such as serr_not_well_formed/0 or serr_invalid_from/2.

Text elements

The text element is represented by #text{} record (of text() type). Some record fields, such as #message.body or #presence.status, contain a list of text elements (i.e. [text()]). To avoid writing a lot of extracting code the following functions can be used to manipulate with text() elements: get_text/1, get_text/2, mk_text/1 and mk_text/2.

Pretty printer

For pretty printing of XMPP elements (for example, for dumping elements in the log in a more readable form), pp/1 function can be used.

Namespaces

There are many predefined macros for XMPP specific XML namespaces defined in include/ns.hrl such as ?NS_CLIENT or ?NS_ROSTER.

WARNING: you should not include ns.hrl in your erlang code. Include xmpp.hrl instead. Consult this file only to obtain a macro name for the required namespace.

There is also get_ns/1 function which can be used to obtain a namespace of xmpp_element() or from xmlel() element.

XMPP addresses

An XMPP address (aka Jabber ID or JID) can be represented using two types:

  • jid(): a JID is represented by a record #jid{}. This type is used to represent JIDs in XMPP elements.
  • ljid(): a JID is represented by a tuple {User, Server, Resource} where User, Server and Resource are stringprepped version of a nodepart, namepart and resourcepart of a JID respectively. This representation is useful for JIDs comparison and when a JID should be used as a key (in a Mnesia database, ETS table, etc.)

Both types and the record are defined in include/jid.hrl.

WARNING: you should not include jid.hrl in your erlang code. Include xmpp.hrl instead.

Functions for working with JIDs are provided by jid module.

Language translation

The library uses a callback function in order to perform language translation. The translation is applied when constructing text or error elements. To set the callback one can use set_tr_callback/1 function. By default, no translation is performed.

Supported XMPP elements

XMPP elements from the following documents are supported. For more details check the file xmpp.doap and its nice display in Erlang/Elixir XMPP at xmpp.org.

As well as some proprietary extensions from ProcessOne