Iris Library

From PsiWiki

Contents

Overview

Iris is a Qt-based Jabber library written in C++. It is the foundation of the Psi Jabber client, and is leveraged by the Jabber-plugin for KDE's flagship multi-protocol IM client Kopete. It was originally written by Justin Karneges, and is developed under the LGPL.

Iris uses the QCA (Qt Cryptographic Architecture) library as its crypto library for XMPP related encryption standards, which in turn relies on underlying providers such as OpenSSL and Cyrus-SASL (wrapped as plugins).

XMPP Support

JEP Support

Building Blocks

The following is an overview of the core API components of the Iris library.

XMPP::AdvancedConnector

The XMPP::AdvancedConnector class is used to handle the properties and physical connection between the client and the Jabber server. It performs asynchronous DNS lookups, and can connect through HTTP and Socks5 proxies if desired. All of this is handled behind the scenes and abstracted quite nicely.

Currently the underlying network classes utilized by the XMPP::AdvancedConnector reside in the cutestuff sub-library (also LGPL). During the move to a native-Qt4 this dependency will most likely disappear.

The XMPP::AdvancedConnector::connectToServer() method should not called directly under normal circumstances. An instance of the XMPP::ClientStream class, which in turn is utilized by an instance of a XMPP::Client class, will initiate the actual connection.

XMPP::ClientStream

The XMPP::ClientStream class is responsible for managing the XML Stream details for a client connection, including authorization credentials and associated TLS/SASL negotiation (using a TLS subprovider). During its lifecycle a XMPP::ClientStream object will emit signals on various interesting events, such as authentication success, negotiation warnings, and stream errors.

An important functional note: very few jabber servers (jabber.org included) fully support XMPP 1.0 streams which demand TLS handshakes and SASL authentication. The method XMPP::ClientStream::setOldOnly( bool ) chooses whether the stream should use XMPP 0.9 or 1.0 during negotiation.

If TLS encryption is desired (or required), a XMPP::TLSHandler must be instantiated and properly initialized with a keystore for the XMPP::ClientStream to utilize.

XMPP::Client

The XMPP::Client is the real meat of the Iris Library, encapsulating all things an abstract Jabber client needs to be. It emits signals indicating roster, subscription, and message events. In addition, it has methods that handle common XMPP::Tasks (explained below) such as setting presence, sending messages and subscription requests.

As previously stated, the XMPP::Client relies on a XMPP::AdvancedConnector and a XMPP::ClientStream. Here is a delta-dev archive snippet from the author:

> is XMPP::Client still the class I should use for dealing with all the
> protocol-related stuff? If so, should I just use the AdvancedConnector and
> ClientStream classes, plug them into a XMPP::Client instance and then
> proceed as before?

Yes.  Create an AdvancedConnector object, then pass it to ClientStream's 
constructor.  Then call Client::connectToServer() to pass the ClientStream to 
Client and cause the connection to happen.  Call Client::close() to free the 
dependency on ClientStream (this will allow you to delete the ClientStream 
and later call Client::connectToServer again with a new one).  If you shut 
down, just remember to delete the objects in backwards order:  Client, 
ClientStream, then AdvancedConnector.

And there you have it.

A side note: don't rely on parented QObject garbage collection for this sequence to occur, delete these instances explicitly in this order and all will be well.

XMPP::Task

The XMPP::Task class serves as a base class for a number of common Jabber tasks, such as subscription, presence, messaging, and roster management. All subclasses are of the form JT_TaskName, and should be fairly self-explanitory.

All tasks should be instantiated as children of a client's XMPP::Client's rootTask (simply pass the result of XMPP::Client::rootTask() to your subtask's constructor). To perform a task, instantiate a XMPP::Task subclass, set the desired properties, and call XMPP::Task::go( bool ) (pass true to have the task delete itself on completion).

If you need to be informed of a XMPP::Task's completion, you can connect to the XMPP::Task::finished() signal. You may either maintain a pointer, or more flexibly use Qt's RTTI and call QObject::sender(), cast the result to a XMPP::Task, and check the XMPP::Task::success() and XMPP::Task::statusCode() of the task.

XMPP Data Structures

The Iris library provides many useful data structures to manage JIDs, Status, Roster items etc...

Their usage should be self-explanitory, please see im.h for a complete listing.

Simple Example

Here is a minimal example to demonstrate the use of the XMPP classes to connect to a Jabber server:

main.cpp

#include <QCoreApplication>
#include <QString>
#include <QtCrypto>
#include <iostream>

#include "im.h"
#include "xmpp.h"

class IrisExample : public QObject
{
	Q_OBJECT

public:

	IrisExample( QObject* pParent = 0 )
	: QObject( pParent)
	{
		m_jid = "yourjid@jabber.yourhost.com/XMPPNotify";
		m_pass = "yourpass";

		// Connector
		m_pConn = new XMPP::AdvancedConnector;
	
		// Stream
		m_pStream = new XMPP::ClientStream( m_pConn );
		
		// Use XMPP .9
		m_pStream->setOldOnly( true );

		connect( m_pStream, SIGNAL( warning( int ) ), SLOT( stream_warning( int ) ) );

		connect( m_pStream, SIGNAL( error( int ) ), SLOT( stream_error( int ) ) );

		connect( m_pStream, SIGNAL( needAuthParams( bool, bool, bool ) ),
          		SLOT( stream_needAuthParams( bool, bool, bool ) ) );

		connect( m_pStream, SIGNAL( authenticated() ), SLOT( stream_authenticated() ) );
	
		// Client
		m_pClient = new XMPP::Client( m_pStream );
	}

	~IrisExample()
	{
		delete m_pClient;
		delete m_pStream;
		delete m_pConn;
	}

	void goAuthenticate( void )
	{
		m_pClient->connectToServer( m_pStream, m_jid );
	}


private:

	XMPP::Jid m_jid;
	XMPP::AdvancedConnector* m_pConn;
	XMPP::ClientStream* m_pStream;
	XMPP::Client* m_pClient;
	QString m_pass;

private slots:

	void stream_warning( int warn )
	{
		// We will get two warnings ... no TLS and pre-1.0 XMPP stream
		// neither of which we care about...
		m_pStream->continueAfterWarning();
	}

	void stream_error( int err )
	{
		std::cout << "D'oh - a stream error occurred! Code: " << err << std::endl;

		QCoreApplication::instance()->quit();
	}

	void stream_authenticated() 
	{
		m_pClient->start( m_jid.host(), m_jid.user(), m_pass, m_jid.resource() );

		std::cout << "Authenticated on server - sweet!" << std::endl;
		
		// our work here is done...
		QCoreApplication::instance()->quit();
	}

	void stream_needAuthParams( bool user, bool passwd, bool )
	{

		if(user)
		{
			m_pStream->setUsername( m_jid.node() );
		}
	
		if(passwd)
		{  
			m_pStream->setPassword( m_pass );
		}
	
		std::cout << "Sending auth params ..." << std::endl;
		m_pStream->continueAfterParams();
		
	}

};

int main( int argc, char **argv )
{
	QCA::Initializer init;

	QCoreApplication app( argc, argv );

	IrisExample irisExample;

	irisExample.goAuthenticate();

	app.exec();
	
	return 0;
}

// dirty trick allows us to have Q_OBJECT definitions in our .cpp's...
#include "main.moc"

And the project file. Be sure QCA is installed with the 'crypto' feature for qmake. The project assumes the Psi source distribution is in a subdirectory called 'psi'. If Psi is elsewhere, you'll need to adjust all the include() statements to refer to the correct path.

irisexample.pro

# need these qt modules
QT += xml network qt3support

# need qca
CONFIG += crypto

# easy way to include zlib
include(psi/src/tools/zip/zip.pri)

# directly include cutestuff and iris libs
include(psi/cutestuff/cutestuff.pri)
include(psi/iris/iris.pri)

# explicitly link to winsock (not sure why this is needed)
windows:LIBS += -lws2_32

# example program
SOURCES += main.cpp

Useful Links