MatriX developer guide

About

MatriX

MatriX is a library for the eXtensible Messaging and Presence Protocol (XMPP) for the Microsoft .NET platform. MatriX can be used to build high quality and high performance XMPP Software products. Like XMPP MatriX is designed for easy extendibility.

MatriX is the successor of our successful agsXMPP library. It was rewritten from scratch to include all new features of the latest .NET platforms. Requirements for MatriX are .NET 3.5 and newer.

If you have not downloaded MatriX yet then download it from here.

This document

Logging

When you have a specific problem then in many cases we request a Xml log from you. This Xml log is all incoming and outgoing Xml of the XMPP session. You get this information from the OnReceiveXml and OnSendXml handlers in all XmppStream classes.

xmppClient.OnReceiveXml += new EventHandler<TextEventArgs>(XmppClientOnReceiveXml);
xmppClient.OnSendXml += new EventHandler<TextEventArgs>(XmppClientOnSendXml);

void XmppClientOnSendXml(object sender, TextEventArgs e)
{
	AddDebug(e.Text);
}

void XmppClientOnReceiveXml(object sender, TextEventArgs e)
{
	AddDebug(e.Text);
}

void AddDebug(string debug)
{
	// write the debug data to a file, textbox etc...
}		

Client connection

Client connections are handled with the XmppClient class in MatriX.

Open

Connecting to a XMPP server is very simple. All you have to do is set the Username, Password and XMPP server. After that call the Open method to establish the connection.

The Open method is asnychronous. This means when the Open call returns you are not connected, but MatriX in executing the process to connect. You have to use the MatriX events. Use the error events to find out if connection failed.

If you want to execute an action right after the Login we suggest to start the action from the OnRosterEnd, OnPresence, OnLogin or OnBind event.

private void Connect()
{
	xmppClient.SetUsername("username");
	xmppClient.SetXmppDomain("server.com");
	xmppClient.Password = "secret";

	xmppClient.Status = "I'm chatty";
	xmppClient.Show = Matrix.Xmpp.Show.chat;

	xmppClient.Open();
}		

Transport

XMPP sessions are persistent TCP socket connections by default on port 5222. The socket gets connected on the start of the sessions and disconnected when the session (XMPP stream) ends.

SRV-Records

XMPP is using SRV records to find the hostname and port of the XMPP service. MatriX for .NET automatically looks up the SRV records and is using the data to establish the connection.

You can also manual specify the hostname and port and disable the SRV lookups.

private void Connect()
{
	xmppClient.SetUsername("username");
	xmppClient.SetXmppDomain("gmail.com");
	xmppClient.Password = "secret";

	// disable SRV lookups and specify hostname manual
	xmppClient.ResolveSrvRecords = false;
	xmppClient.Hostname = "talk.google.com";

	xmppClient.Status = "I'm chatty";
	xmppClient.Show = Matrix.Xmpp.Show.chat;

	xmppClient.Open();
}		

MartriX Mobile and MatriX for Silverlight don't support SRV lookups because this API's are missing in the underlying .NET Framework editions.

BOSH

Bidirectional-streams Over Synchronous HTTP (BOSH) is an extensions to transport XMPP streams over the HTTP protocol. BOSH was developed for constrained clients like web browsers which can't open sockets any only communicate over the XMPP protocol.

Even Silverlight is able to create sockets it makes sense in many cases to choose the BOSH Transport for Silverlight applications.

Many enterprise companies use HTTP proxies as the only connection to the Internet. In this scenario the only way to establish a XMPP connection will be BOSH as well.

The following example show how you choose BOSH as the transport layer and setup a XMPP client stream.

private void Connect()
{
	xmppClient.SetUsername("username");
	xmppClient.SetXmppDomain("ag-software.de");
	xmppClient.Password = "secret";

	xmppClient.Transport = Matrix.Net.Transport.BOSH;
	xmppClient.Uri = new System.Uri("http://ag-software.de:5280/http-bind/");

	xmppClient.Status = "I'm chatty";
	xmppClient.Show = Matrix.Xmpp.Show.chat;

	xmppClient.Open();
}

Authentication

A XMPP session can be authenticated with the following mechanisms in MatriX.

Single Sign On

Using Kerberos you can authenticate with your Windows logon credentials in MatriX. If you have a working XMPP server which supports Kerberos authentication you don't have to provide username and password. Just set UseSso to true and login. You can get your full Jid which includes the username in the OnBind event when required.

private void Connect()
{
	xmppClient.OnBind += new System.EventHandler<JidEventArgs>(xmppClient_OnBind);

	xmppClient.Status = "I'm chatty";
	xmppClient.Show = Matrix.Xmpp.Show.chat;

	xmppClient.UseSso = true;

	xmppClient.Open();
}

private void xmppClient_OnBind(object sender, JidEventArgs e)
{
	// get our own Jid in the bind event.
	Debug.WriteLine(e.Jid);
}

Certificates

When you sign on using certificates you have to provide a X509Certificate2 object to the XmppClient object. When you provide a certificate no username ad password is required. When you set a username then this username gets used at the authz id during the SASL EXTERNAL authentication.

When no username is provided and the authentication was successful you can get your full Jid which includes the username from the OnBind event.

The following example uses a certificate file to login. No username is provided.

private void Connect()
{
	xmppClient.OnBind += new System.EventHandler<JidEventArgs>(xmppClient_OnBind);

	xmppClient.Status = "I'm chatty";
	xmppClient.Show = Matrix.Xmpp.Show.chat;

	xmppClient.ClientCertificate = new X509Certificate2(@"C:\certs\xmpp\alex.ag-software.de.p12", "secret");

	xmppClient.Open();
}

private void xmppClient_OnBind(object sender, JidEventArgs e)
{
	// get our own Jid in the bind event.
	Debug.WriteLine(e.Jid);
}

Roster

In XMPP the contact list aka buddy listâ„¢ is called roster. When AutoRoster is true (default) MatriX requests the roster automatically on each login. We call this the full roster. If you don't want to receive the roster on each login set AutoRoster to false. The most existing clients request the roster on each login. Here is a small list where setting AutoRoster to false makes sense.

Events

The following events get raised while the contact list is received

OnRosterStart  occurs before the first OnRosterItem gets fired. This event is often used to stop GUI updates (BeginUpdate) of the roster control while receiving the complete contact list for faster GUI updates.
OnRosterItem gets raised for each contact in the contact list when the full roster gets received. This event gets also fired when a single contact is added, removed or updated.
OnRosterEnd occurs when all contacts are received. This is also the place where you call EndUpdate when you used BeginUpdate before. When AutoRoster is true this event also indicates that the XMPP session is ready to execute actions in your business logic.

You have to consider all contacts as offline until you get a presence from them with another show type.

OnRosterStart and OnRosterEvent get only fired when the full roster is received, not for single contact updates.

Management

You can manage your contacts (roster) with the RosterManager class.

Add contacts

Add the user with the Jid joe@server.com under the name Joe Jones to the roster.

var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Add(jid, "Joe Jones");		

Add the user with the Jid joe@server.com under the name Joe Jones to the roster and put it in the group Work.

var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Add(jid, "Joe Jones", "Work");		

Add the user with the Jid joe@server.com under the name Joe Jones to the roster and put it in the 2 groups Work and Friends.

var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Add(jid, "Joe Jones", string[] {"Work", "Friends"});

Add and subscribe

When we add a contact then in the most cases we also want to exchange presence with the contact. To exchange presence we must subscribe to the contacts presence. The following example describes how to add a contact and subscribe to the contacts presence at the same time.

var rm = new RosterManager(xmppClient);
var pm = new PresenceManager(xmppClient);

Jid jid = "joe@server.com";
rm.Add(jid, "Jones", "Friends");
pm.Subscribe(jid);

Update contacts

We have the user joe@server.com with no name on no groups in the contact list. Now we want to add the name "Joe" to the contact and put him in the group "Friends".

var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Update(jid, "Joe", "Friends");		

When we update a contact we always have the pass the complete contact information, not only a diff of the properties which we want to add, change or upate.

Remove contacts

Remove the contact with the Jid joe@server.com.

var rm = new RosterManager(xmppClient);
Jid jid = "joe@server.com";
rm.Remove(jid);

Filters

Filters can be used to get notifications (events) for stanzas you are interested at. Using filters you code is much easier to read, because you get rid of lots of nested if-then-else statements.

Currently there are 3 filter classes:

IqFilter

In XMPP we have a request response mechanism with the Iq stanzas. Its similar to HTTP GET and HTTP PUT.

<!-- Example 1 -->

<!-- Client request -->
<iq from='juliet@example.com/balcony' id='rg1' type='get'>
	<query xmlns='jabber:iq:roster'/>
</iq>

<!-- Server response -->
<iq id='rg1' to='juliet@example.com/chamber' type='result'>
	<query xmlns='jabber:iq:roster' ver='ver7'>
		<item jid='nurse@example.com'/>
		<item jid='romeo@example.net'/>
	</query>
</iq>

The example above is a roster query. The client requests the contact list from the server. The type of the request is get because the client wants to retrieve information. The server responds with a type of result and sets the id to the same id as in the request. In this case 'rg1'. You can use your own logic with many if-then-else clauses to assign the response to the associated request or the IqFilter class of MatriX.

 When you send a request with the IqFilter you can define a callback which handles the response. The IqFilter handles all processing and raises your callback automatically when the result is received. The logic of the IqFilter is based on the unique id IQ's.

 

Asynchronous Iq Filter

This example describes the asynchronous usage of the IqFilter. A RosterIq query gets created with the RosterIq class. The RosterIq class automatically assigns a unique id to the request. You pass the query stanza and your defined callback to the SendIq member of the IqFilter. Once Matrix receives the packet the callback gets raised and removed from the IqFilter collection.

// Example 2

private void RequestRoster()
{
	var riq = new RosterIq(IqType.get);
	xmppClient.IqFilter.SendIq(riq, RosterResponse);
}

private void RosterResponse(object sender, IqEventArgs e)
{
	var iq = e.Iq;
    
	if (iq.Type == IqType.result)
	{
		// process result here
	}
    else if (iq.Type == IqType.error)
    {
		// process errors here
    }
}

Synchronous Iq Filter

We normally suggest to use asynchronous patterns. But often developers prefer synchronous programming and in some environments synchronous programming is required. You can also do synchronous requests in the IqFilter with the SendIqSynchronous member, when required with a given timeout.
The default timeout is 5000ms. When no response was received from the server within the given timeout then the return value is null, otherwise the resulting Iq stanza.

the following example requests a user VCard using an synchronous IqFilter with a timeout of 2 seconds.

Because the synchronous calls are using AutoResetEvents they cannot be executed from the MatriX thread. Doing this would block until you reach the timeout and return always null.

// Example 3
private void RequestVcardSynchronous()
{
	var viq = new VcardIq {To = "user@server.com", Type = IqType.get};
	Iq result = xmppClient.IqFilter.SendIqSynchronous(viq, 2000);
	if (result != null)
	{
		// process result here
	}
}

XPathFilter

The XPath filter can be used to filter stanzas using XPath expressions. The XPathFilter is based on the XPathSelectElement extension of System.Xml.XPath Namespace.

Of course you can do filtering based on powerful LinQ statements and the other filter classes in MatriX. But in many cases it's required to build expressions dynamically on the fly. Also many programmers are familiar with XPath and prefer XPath over LinQ statements. The XPathFilter makes it also much easier to filter big complex stanzas with many nested elements.

The following example describes the usage of the XPathFilter. We setup a filter that matches all presence stanzas from the full Jid 'user@jabber.org/MatriX'. Because MatriX and XPath in .Net are namespace aware we have to define prefixes in the XmlNamespaceManager. Otherwise we would get no results.

e.Stanza is the complete stanza which matches the expression
e.Result is the result of the XPath expression. This is useful when you are interested only in fragments of the complete stanza.

void XPathFilter()
{  
	xmppClient.XPathFilter.XmlNamespaceManager.AddNamespace("JC", "jabber:client");            
	xmppClient.XPathFilter.Add("/JC:presence[@from='user@jabber.org/MatriX']", XPathCallback);       
}

void XPathCallback(object sender, XPathEventArgs e)
{
	Debug.WriteLine("Stanza: " + e.Stanza);
	Debug.WriteLine("Result: " + e.Result);
}

Here are some other XPath example expressions to filter stanzas:

Registration

Register new account

ItIt is possible to register new accounts over XMPP and with MatriX. But for most services its not recommended to enable automatic account creation over XMPP because it invites spammers which can use the accounts as "throw away" addresses. Its also easier in the most cases to write new accounts directly to the SQL database of the server using scripting language for HTML like ASP, PHP and others.

Below is a code snippet which shows how to register a new account. In the OnRegisterInformation event required the Register object must be populated with the data. Most severs offer simple fields like username, password and email, or extended registration over XData. In our example we use the simple fields for username and password and remove XData from.

var XmppClient = new XmppClient();

xmppClient.OnRegister += new EventHandler<Matrix.EventArgs>(xmppClient_OnRegister);
xmppClient.OnRegisterInformation += new EventHandler<Matrix.Xmpp.Client.RegisterEventArgs>(xmppClient_OnRegisterInformation);
xmppClient.OnRegisterError += new EventHandler<Matrix.Xmpp.Client.IqEventArgs>(xmppClient_OnRegisterError);

xmppClient.SetUsername(txtUsername.Text);
xmppClient.SetXmppDomain(txtServer.Text);
xmppClient.Password = txtPassword.Text;
xmppClient.RegisterNewAccount = true;

xmppClient.Open();

private void xmppClient_OnRegisterInformation(object sender, RegisterEventArgs e)
{
	e.Register.RemoveAll<Data>();

	e.Register.Username = xmppClient.Username;
	e.Register.Password = xmppClient.Password;
}

private void xmppClient_OnRegister(object sender, EventArgs e)
{
	// registration was successful
}

private void xmppClient_OnRegisterError(object sender, IqEventArgs e)
{
	// registration failed.
	xmppClient.Close();
}

Extending MatriX

XMPP was designed for extensibility from ground up. It's very easy to define your own subsets of XMPP protocol and send them over the wire. Its very easy to add you own custom extensions to MatriX.

In the following example we will create a simple extension for a weather service. We will request weather information like temperature and humidity from a weather service over XMPP. Because this is a request<--> response mechanism we will use the XMPP IQ stanza for this.

Protocol design

in the first step we design the Xml representation of the protocol we are going to use.

<!-- weather request -->
<iq from='user1@server.com/MatriX' to='user2@server.com/MatriX' type='get' id='weather1'>
	<weather xmlns='ag-software:weather'>
		<zip>74080</zip>
	</weather>
</iq>


<!-- weather response -->
<iq from='user2@server.com/MatriX' to='user1@server.com/MatriX' type='result' id='weather1'>
	<weather xmlns='ag-software:weather'>
		<humidity>60</humidity >
		<temperature>24</temperature> 
	</weather>
</iq>

In XMPP protocols are defined by their namespaces. I have chosen the namespace agsoftware:weather for this extensions. And I have chosen the tag name <weather/> for the root element. This makes sense because Xml is verbose and human readable. As first level children of the weather root element I defined

Class design

After the design of the protocol we can start to define our classes now. For this basic example we will create 2 classes Weather.cs and WeatherIq.cs.

using Matrix.Xml;

namespace WeatherExample
{
	public class Weather : XmppXElement
	{
		public Weather() : base("ag-software:weather", "weather")
		{
		}
	
		public int Humidity
		{
			get { return GetTagInt("humidity"); }
			set { SetTag("humidity", value); }
		}

		public int Temperature
		{
			get { return GetTagInt("temperature");}
			set { SetTag("temperature", value);}
		}
	
		public string Zip
		{
			get { return GetTag("zip"); }
			set { SetTag("zip", value); }
		}
	}
}

The weather.cs class which represents our custom Xml object must derive from XmppXElement. XmppXElement is inherited from System.Xml.Linq.XElement and is the base of all XMPP protocol classes in MatriX.

XmppXElement has many helper functions to serialize and deserialize Xml. We use GetTag, and GetTagInt here which return the value of the given Xml Tag as String or Integer, and we use SetTag to add a tag and set its value as String or Integer. There are many other helper functions which makes it very simple to build complex Xml structures. And of course you can use all members of the powerful System.Xml.LinQ namespace.

After we have written this class we can run a simple test to see if we get the desired results.

private void TestWeather()
{
	var weather = new Weather {Zip = "74080", Humidity = 60, Temperature = 22};
	Debug.WriteLine(weather);
}

The Debug Console shows the following result. This is what we expected.

<weather xmlns="ag-software:weather">
	<zip>74080</zip>
	<humidity>60</humidity>
	<temerature>22</temerature>
</weather>

Now we create the WeatherIq class. This class is only a helper to create WeatherIq's in a easier fashion. Instead of WeatherIq.cs we could also use a normal Iq object and add the weather childnode with the Add member.

using Matrix.Xmpp.Client;

namespace WeatherExample
{
	public class WeatherIq : Iq
	{
		public WeatherIq()
		{
			GenerateId();
		}
		
		public Weather Weather
		{
			get { return Element<Weather>(); }
			set { Replace(value); }
		}
	}
}

We write again a simple test to build a weather Iq request with the new WeatherIq class.

private void TestWeatherIq()
{
	var wiq = new WeatherIq
	{
		Type = IqType.get,
		To = "user2@server.com/MatriX",
		Weather = new Weather {Zip = "74080"}
	}
	Debug.WriteLine(wiq);
}

And again we get the result we expected. Don't care about the additional jabber:client namespace declaration. Its there because this is a Xml fragment of the complete Xml stream. When we inject the packet into the Xmpp Xml stream its automatically removed because the jabber:client namespace is already declared in the stream header. MatriX is namespace aware which is the reason why we see this namespace declarations on partial Xml fragments.

<iq id="MX_1" type="get" to="user2@server.com/MatriX" xmlns="jabber:client">
	<weather xmlns="ag-software:weather">
		<zip>74080</zip>
	</weather>
</iq>

Now we can put the pieces together.

private static void RegisterCustomElements()
{ 
	Factory.RegisterElement<Weather>("ag-software:weather", "weather");
}

private void RequestWeatherInfo(Jid from, string zip)
{
	var wiq = new WeatherIq
	{
		Type = IqType.get,
		To = from,
		Weather = new Weather { Zip = zip }
	};
	// we pass the zip code as state object to the IqFilter
	xmppClient.IqFilter.SendIq(wiq, WeatherInfoResponse, zip);
}

private void WeatherInfoResponse(object sender, IqEventArgs e)
{
	var iq = e.Iq;

	if (iq.Type == IqType.result)
	{
		var weather = iq.Element<Weather>();
		if (weather != null)
		{
			// read the zip code from the stat again because its not contained in the xml object
			var zip = e.State as string;

			Debug.WriteLine("Humidity: " + weather.Humidity);
			Debug.WriteLine("Temperature: " + weather.Temperature); 
		}
	}
}

private void xmppClient_OnIq(object sender, IqEventArgs e)
{ 
	if (e.Iq.Type == IqType.get &&
	e.Iq.Query is Weather)
	{
		var weather = e.Iq.Query as Weather;
		string zip = weather.Zip;
		// here you should lookup the weather information for the given zip code in a database or webservice
		// we just return some random numbers

		var temp = new Random().Next(-10, 40);
		var humidity = new Random().Next(10, 90);

		var wiq = new WeatherIq
		{
			To = e.Iq.From,
			Type = IqType.result,
			Weather = new Weather {Temperature = temp, Humidity = humidity}
		};
		// send the response
		xmppClient.Send(wiq);
	}
}

You should be able copy & paste the code snippets above to a new project an test the code with 2 different XMPP users. You can also login with the same user twice using different resources and send the weather packets from one resource to the other.

Don't forget to register your own protocol classes in the MatriX Factory (Matrix.Xml.Factory).
When your classes are not registered in the Factory MatriX is unable to deserialize the Xml to your custom class.

Silverlight

There are different ways to setup a XMPP connection with MatriX in Silverlight

  1. direct socket connection on a port within the Silverlight port range
  2. direct connection over a HTTP proxy with the CONNECT command (HTTP tunneling)
  3. BOSH connection

Silverlight has different network security access restrictions for sockets and web requests. If you choose option 1 or 2 then the socket policies apply, if you choose 3 then the web request policy apply because MatriX is using .NET WebRequest classes for the connections. Read more details about the Silverlight network security access restrictions here.

Silverlight allows socket connections only on ports 4502-4534. For option 1 and 2 this means you have to configure your XMPP or proxy server to listen on a port within this port range, or use other technologies like port forwarding to redirect connections to the XMPP default port 5222. Before a socket can be connected the Silverlight runtime requests the policy on the target site at port 943 and checks the permissions. You have to run a policy server which must be able to serve requests on the same domain as your XMPP or proxy server is hosted.

When using BOSH and crossing domains the Silverlight runtime requests a policy xml file from the root of the BOSH Uri. This means you can connect to other XMPP servers (not under your control) only if they host a policy file on the root of the BOSH Uri which allows you to connect. Or you run your own BOSH server which is able to connect to any XMPP server in the federated network. Punjab is a good choice for the latter.

Example:
when the BOSH Uri is http://example.com:5280/http-bind/ the Silverlight runtime request either a Flash policy file at http://example.com:5280/crossdomain.xml or a Silverlight policy file at http://example.com:5280/clientaccesspolicy.xml when crossing domains.

To avoid cross domain requests on your own server you can either