Gateway interaction in Smack API

Gateway interaction is described by XEP-0100. The query xmlns (namespace) used for this is same as IN-Band registration, that is “jabber:iq:register”. Smack API provides an easy way handle gateway interaction with the server. It has predefined classes for the required namespace and operations which one can easily use and do the thing. In this article I will explain how one can register, edit, unregister and retrieve gateway information using Smack library. I have listed both XML and code using Smack API for the purpose. I have used Openfire server with Kraken plugin in it for gateway related operations described in this article.

1. Gateway service discovery

First you have to send an IQ-get with xmlns of the service discovery info to the Gateway. The XML would look like this:

<iq id="uLQX1-3" to="gtalk.mtHost" type="get">
	<query xmlns="http://jabber.org/protocol/disco#info"></query>
</iq>

And in return Gateway returns identity information like this:

<iq id="uLQX1-3" to="user1@myHost/Smack" from="gtalk.myHost" type="result">
	<query xmlns="http://jabber.org/protocol/disco#info">
		<identity category="gateway" name="Google Talk Transport" type="xmpp"/>
		<feature var="http://jabber.org/protocol/disco#info"/>
		<feature var="http://jabber.org/protocol/disco#items"/>
		<feature var="jabber:iq:gateway"/>
		<feature var="jabber:iq:register"/>
		<feature var="jabber:iq:version"/>
		<feature var="vcard-temp"/>
	</query>
</iq>

By this you can identify which namespace features are supported by the gateway. To register and/or unregister the gateway “jabber:iq:register” support is required. This response can differ at your side based on your server and Gateway support. Now the code using Smack API for this operation is as follows:

DiscoverInfo iq = new DiscoverInfo();
iq.setType(IQ.Type.GET);
iq.setTo("gtalk.myHost");
PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
connection.sendPacket(iq);
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
System.out.println(response.toXML());

And to check for specific feature is supported or not and getting information of items in response do the following:

if (response != null && !response.getType().equals(IQ.Type.ERROR) && response instanceof DiscoverInfo) {
	DiscoverInfo info = (DiscoverInfo) response;
	Iterator<Identity> identities = info.getIdentities();
	while (identities.hasNext()) {
		DiscoverInfo.Identity identity = (DiscoverInfo.Identity) identities.next();
		System.out.println("identity name -> " + identity.getName());
		System.out.println("identity category -> " + identity.getCategory());
		System.out.println("identity type -> " + identity.getType());
	}
	System.out.println(info.containsFeature("jabber:iq:register"));
}

2. Get information of gateway

To query gateway id about which information is required to register, you need to send following xml:

<iq id="VT12T-4" to="gtalk.myHost" type="get">
	<query xmlns="jabber:iq:register"></query>
</iq>

In response server will respond something like this:

<iq id="VT12T-4" to="user1@myHost/Smack" from="gtalk.myHost" type="result">
	<query xmlns="jabber:iq:register">
		<instructions>Please enter your e-mail address and password used with GMail and GTalk</instructions>
		<username></username>
		<password></password>
		<x xmlns="jabber:x:data" type="form">
			<instructions>Please enter your e-mail address and password used with GMail and GTalk.</instructions>
			<field label="Address" var="username" type="text-single"></field>
			<field label="Password" var="password" type="text-private"></field>
		</x>
		<x xmlns="jabber:iq:gateway:register"></x>
	</query>
</iq>

By this you will know that which fields are required for gateway registration. Here in this example 2 fields are require, one is username and another is password.
Code to do this in smack is:
1) Send GET request to gateway

Registration iq = new Registration();
iq.setType(IQ.Type.GET);
iq.setTo("gtalk.myHost");
connection.sendPacket(iq);

2) Get the response.

PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
System.out.println(response.toXML());

3) Process the response for extracting required information like this:

if (response != null && !response.getType().equals(IQ.Type.ERROR) && response instanceof Registration) {
Registration registration = (Registration) response;
System.out.println(registration.getInstructions());
	if (registration.getAttributes() != null && !registration.getAttributes().isEmpty()) {
		Map<String, String> map = registration.getAttributes();
		System.out.println(map);
	}
}

3. Register gateway:

To register a gateway using one has to send IQ SET with the required attributes:

<iq id="WiISc-2" to="gtalk.myHost" type="set">
	<query xmlns="jabber:iq:register">
		<username>user</username>
		<password>pass</password>
	</query>
</iq>

And as a successful response server will send back following xml:

<iq type='result' from='gtalk.myHost' to=user1@myHost/Smack' id='WiIsc-2'/>

And here is the code to do registration using Smack API:
1) Send an IQ set packet. For jabber:iq:register we will use Registration class from smack library:

Map<String, String> attributes = new HashMap<String, String>();
attributes.put("username", "user");
attributes.put("password", "pass");
Registration registration = new Registration();
registration.setType(IQ.Type.SET);
registration.setTo("aim.myHost");
registration.setAttributes(attributes);
connection.sendPacket(registration);

2) Get the response:

PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(registration.getPacketID()));
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();

4. Retrieve registered gateway information:

To retrieve registered gateway information one has to send an IQ GET packet with “jabber:iq:register” query to the gateway id (i.e. gtalk.myHost). This is similar what we did to get gateway information. In short when you can query gateway for information it will return the information with currently registered information, if any and if there there are no registered information then response is just about the required field names to register.

<iq id="VT12T-4" to="gtalk.myHost" type="get">
	<query xmlns="jabber:iq:register"></query>
</iq>

The gateway returns registration requirements with already registered information like following:

<iq type='result' id='VT12T-4'>
   <query xmlns='jabber:iq:register'>
      <registered/>
      <username>user</username>
      <password>pass</password>
   </query>
</iq>

One can do this thing in Smack API as follows:
1) Send IQ GET packet to gateway:

Registration message = new Registration();
message.setTo("gtalk.myHost");
message.setType(IQ.Type.GET);
connection.sendPacket(message);

2) Get and process the response returned by gateway:

PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(message.getPacketID()));
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
if (response != null && !response.getType().equals(IQ.Type.ERROR) && response instanceof Registration) {
Registration registration = (Registration) response;
if (registration.getAttributes() != null && !registration.getAttributes().isEmpty()) {
Map<String, String> map = registration.getAttributes();
// map contains information about registration.
}
}

5. Edit registered information:

To edit registered information one has to first fetch information as shown in above section and then follow the same code for registering gateway. In simple words do point 3 & 4 form this article in reverse order.

6. Unregister gateway

To unregister a gateway first query for the registered gateway to see if any gateway information is registered or not. If it is registered then to unregister you just have to add a “remove” attribute in the IQ set as follows:

<iq type='set' from=user1@myHost/Smack' to='gtalk.myHost' id='VTiLk-4'>
   <query xmlns='jabber:iq:register'>
      <remove/> <!—remove is for unregisterting the gateway -->
   </query>
</iq>

This is very similar to the register gateway xml so difference in code is very small. Instead of adding username and password as attributes in Map you just add a “remove” attribute and everything else is same.

Remove these 2 lines from register gateway code:

attributes.put("username", "user");
attributes.put("password", "pass");

And add following line instead:

attributes.put("remove", ""); // no need to provide any value as remove tag is an empty tag

Everything else in the code is same. Response is also similar to the register gateway response.

This is it for now!!! 🙂

Advertisements

File transfer in android with asmack and Openfire

I have seen many peoples struggling from file transfer in asmackAndroid build environment and patches for smack. I tried same library with Openfire server after some debugging got success in it. So I will like to share my problem and solution for it.  I found that the problem is in Byte Stream host, it tries to connect to wrong host for initiating file transfer using byte streams.  If you have not configured the file transfer proxy properly then it will choose 127.0.0.1 or localhost or any internally configured host/ip as a bytestream host, which in android is not considered as proper ip for communicating.  But wait then why it is working properly with desktop client with same server. The reason is desktop clients can easily identify this ips and can use it to open socket for file transfer.

The variable names used in following discussions are:

Name Meaning
myHost The hostname of machine on which Openfire is running.
user1@myHost File transfer initiator user
user2@myHost Recipient of file

How to know which host is chosen for byte streams: (Following XMLs are shown as they are sent and received to the file transfer initiator.)

  • First query other client for information about supported features:

Sent:

<iq id="x1ixz-13" to="user2@myHost/Smack" type="get">
   <query xmlns="http://jabber.org/protocol/disco#info"></query>
</iq>

Received:

<iq id="x1ixz-13" to="user1@myHost/Smack" type="result" from="user2@myHost/Smack">
   <query xmlns="http://jabber.org/protocol/disco#info">
      <!—some other items -->
      <feature var="http://jabber.org/protocol/bytestreams"/>
      <!—some other items -->
   </query>
</iq>

Here you can know that whether bytestream file transfer is supported on the client other side or not. If you see a xml like above in response then it’s supported on the other side and you can go further.

  • Query server for disco items:

Sent:

<iq id="x1ixz-14" to="myHost" type="get">
   <query xmlns="http://jabber.org/protocol/disco#items"></query>
</iq>

Received:

<iq type="result" id="x1ixz-14" from="myHost" to="user1@ myHost /Smack">
   <query xmlns="http://jabber.org/protocol/disco#items">
      <!—some other items -->
      <item jid="proxy. myHost " name="Socks 5 Bytestreams Proxy"/>
      <!—some other items -->
   </query>
</iq>

Here you will receive various items for different items supported by server like , file transfer proxy, search service, conference service… The item we should look for is ` Socks 5 Bytestreams Proxy `.

  • Request for more information on proxy

Sent:

<iq id="x1ixz-19" to="proxy. myHost " type="get">
   <query xmlns="http://jabber.org/protocol/bytestreams"/>
</iq>

Received:

<iq type="result" id="x1ixz-19" from="proxy. myHost "  to="user1@ myHost /Smack">
   <query xmlns="http://jabber.org/protocol/bytestreams">
      <streamhost jid="proxy. myHost " host=" myHost " port="7777"/>
   </query>
</iq>

Here the host in the <streamhost> is the key for the file transfer. You have to make sure that this host is accessible from your android phone. It should not be like localhost, 127.0.0.1 or any company’s internal configured server which is not accessible outside of the company. It must be a publicly accessible ip address or host.

To configure proper proxy add/change these 3 properties in Openfire:

  1. xmpp.proxy.enabled                – true
  2. xmpp.proxy.port                        – 7777
  3. xmpp.proxy.externalip            – [publicly accessible host or ip]

The code for file transfer:

FileTransferManager manager = new FileTransferManager(connection);
OutgoingFileTransfer transfer = manager.createOutgoingFileTransfer("usre2@myHost/Smack");
File file = new File(filenameWithPath);
try {
   transfer.sendFile(file, "test_file");
} catch (XMPPException e) {
   e.printStackTrace();
}
while(!transfer.isDone()) {
   if(transfer.getStatus().equals(Status.error)) {
      System.out.println("ERROR!!! " + transfer.getError());
   } else if (transfer.getStatus().equals(Status.cancelled)
                    || transfer.getStatus().equals(Status.refused)) {
      System.out.println("Cancelled!!! " + transfer.getError());
   }
   try {
      Thread.sleep(1000L);
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
}
if(transfer.getStatus().equals(Status.refused) || transfer.getStatus().equals(Status.error)
 || transfer.getStatus().equals(Status.cancelled)){
   System.out.println("refused cancelled error " + transfer.getError());
} else {
   System.out.println("Success");
}

And the code for file receive:

FileTransferManager manager = new FileTransferManager(connection);
manager.addFileTransferListener(new FileTransferListener() {
   public void fileTransferRequest(final FileTransferRequest request) {
      new Thread(){
         @Override
         public void run() {
            IncomingFileTransfer transfer = request.accept();
            File mf = Environment.getExternalStorageDirectory();
            File file = new File(mf.getAbsoluteFile()+"/DCIM/Camera/" + transfer.getFileName());
            try{
                transfer.recieveFile(file);
                while(!transfer.isDone()) {
                   try{
                      Thread.sleep(1000L);
                   }catch (Exception e) {
                      Log.e("", e.getMessage());
                   }
                   if(transfer.getStatus().equals(Status.error)) {
                      Log.e("ERROR!!! ", transfer.getError() + "");
                   }
                   if(transfer.getException() != null) {
                      transfer.getException().printStackTrace();
                   }
                }
             }catch (Exception e) {
                Log.e("", e.getMessage());
            }
         };
       }.start();
    }
 });

Also configure ProviderManager to properly decode/parse bytestreams and other required xmls:

ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/bytestreams", new BytestreamsProvider());
ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
ProviderManager.getInstance().addIQProvider("query","http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());

The asmack library I used was asmack-jse-buddycloud.jar.

Let me know your feedbacks and if you have configured all things well and still face the issue the please drop a comment on this post; I will try my best to help you.  🙂

%d bloggers like this: