📜 ⬆️ ⬇️

Non-standard XMPP protocol support with Smack

In a recent project, we implemented the interaction of an Android application with an ejabberd server via a customized XMPP protocol.

This article provides examples on how to send / receive customized XMPP protocol packets in an Android application.

To work with the XMPP protocol, the Smack 4.1.8 library was chosen.

The first task is to send Message-packages to the server with additional attributes in the parent element and non-standard child elements.
')
Immediately make a reservation, from the point of view of the XMPP protocol, it is incorrect to change the parent element. But in this project we had to do so, because the server at the time of the development of the Android application was already implemented and it was not possible to change it.

Xml to send a message package:

<message from='userJIdFrom/Resource' to='userJIdTo/Resource' xml:lang='en' id='70720-25' company='SimbirSoft'> <read xmlns='urn:xmpp:receipts' id='ILKMe-22'/> </message> 

The attribute “company” and the “read” element are not in the XMPP protocol.

The standard implementation of the IQ, Message, and Stanza classes does not provide the ability to add anything to the xml parent element. And for classes IQ, Message, even in the case of inheritance, it is not possible to change the parent element.

The solution is to inherit from the class “Stanza” and redefine the toXML method:

 //  “ReadMessageStanza”    ,    //    public class ReadMessageStanza extends Stanza { @Override public CharSequence toXML() { XmlStringBuilder buf = new XmlStringBuilder(); //    “<”      // rootElement   “iq”, “message”, “stanza”. buf.halfOpenElement(rootElement); //   "to", "from", "id", "lang"   . //    "to"    “setTo”  “Stanza” // "id", "lang"       “Stanza” //   "from"     ,  //   XMPPTCPConnection  // “setFromMode(XMPPConnection.FromMode.USER);“ addCommonAttributes(buf); for (String key : attributes.keySet()) { //       buf.attribute(key, attributes.get(key)); } //     “/>” buf.rightAngleBracket(); //    .      “Stanza” buf.append(getChildElementXML()); //     Extensions.     //     xml buf.append(getExtensionsXML()); //    “</id>”, “</message>”, “</stanza>” buf.closeElement(rootElement); return buf; } } 

You can send such a package as an ordinary Stanza-package without processing the result:

 xmppTCPConnection.sendStanza(new ReadMessageStanza()); 

In the handler for outgoing packets of the “xmppTCPConnection” object “xmppTCPConnection” class type will be “ReadMessageStanza” :

 xmppTCPConnection.addPacketSendingListener(new StanzaListener() { @Override public void processPacket(Stanza packet) throws SmackException.NotConnectedException { Map<String, String> map =((ReadMessageStanza )packet).getAttributes(); //     “ReadMessageStanza”... } }, new StanzaFilter() { @Override public boolean accept(Stanza stanza) { //    return stanza instanceof ReadMessageStanza; } }); 
The implementation of “ReadMessageStanza” shown above for demonstrative purposes. It is more correct to put the code into the “CustomStanza” base class or use the “Builder” pattern to build packages.

If the message did not reach the server or the server returned an error, the sent message is returned with the error information. The parser in Smack will not be able to handle this data format and will generate an error. This problem can be solved only by making changes to the sources of the Smack library.

The second task is parsing incoming Message-packages from the above xml.

To solve this problem, it is necessary to create and register a provider (parser).

For the class "ReadMessageStanza" provider will be the following:

 public class ReadMessageProvider extends ExtensionElementProvider<ReadMessageProvider.Element> { //    public static final String ELEMENT_NAME = ”read”; // namespace    public static final String NAMESPACE = ”urn:xmpp:receipts”; //        // “ExtensionElement”  Smack. //   toXML,        // “Extensions”   ReadMessageStanza- public static class Element implements ExtensionElement { private final String id; Element(String id) { this.id = id; } public String getId() { return id; } //           “Extension” //   ,    null   toXML @Override public CharSequence toXML() { return null; } @Override public String getNamespace() { return NAMESPACE; } @Override public String getElementName() { return ELEMENT_NAME; } } //     @Override public ReadMessageProvider .Element parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException { //     return new ReadMessageProvider .Element(parser.getAttributeValue("", "id")); } } 

Register your provider:

 static { ProviderManager.addExtensionProvider(ReadMessageProvider.ELEMENT_NAME, ReadMessageProvider.NAMESPACE, new ReadMessageProvider()); } 

Create an incoming packet handler:

 private StanzaListener inComingChatListener = new StanzaListener() { @Override public void processPacket(Stanza packet) throws SmackException.NotConnectedException{ Message message = (Message) packet; // ,       if(message.hasExtension(ReadMessageProvider.ELEMENT_NAME, ReadMessageProvider.NAMESPACE)) { ReadMessageProvider.Element element = message.getExtension(ReadMessageProvider.ELEMENT_NAME, ReadMessageProvider.NAMESPACE); int id = element.getId(); //   ... } }; } 

We register the handler of incoming messages using the standard MessageTypeFilter.NORMAL_OR_CHAT filter:

 xmppTCPConnection.addSyncStanzaListener(inComingChatListener, MessageTypeFilter.NORMAL_OR_CHAT); 

The third task is to send and receive customized IQ packets.

Xml for sending IQ-package:

 <iq xmlns='xep:mymessages' to='server' from='userJIdFrom/Resource' id='J8OPC-50' type='history'> <query count='50' offset='0'>'userJIdTo/Resource'</query> </iq> 

Here the attributes “xmlns” and “type” accept values ​​that are not in the XMPP protocol. Such a package can be formed by analogy with “ReadMessageStanza” .

Incoming IQ Package Xml:

 <iq xmlns='xep:mymessages' type='result' to='userJIdFrom/Resource' id='Ji3H1-43'> <result> <message id='cfd6fce4-2f30-d1e3-349e-11eab92bc3fa' from='userJIdFrom/Resource' to='userJIdTo/Resource' type='chat'> <body>Message</body> <query xmlns='jabber:iq:time'> <utc>1482729259000000</utc> </query> </message> </result> </iq> 

To parse child elements, you need to create and register a provider:

 //    IQ-    public class MyMessagesProvider extends IQProvider<MyMessagesProvider.Result> { //   .     enum   Smack public static final String ELEMENT_NAME = IQ.Type.result.name(); // namespace   public static final String NAMESPACE = ”xep:mymessages”; //     public static class Result extends IQ { //    private List<CustomMessage> mItems = new ArrayList<>(); private Result() { super("items"); } @Override protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { return null; } public List<CustomMessage> getValue() { return mItems; } } @Override public MyMessagesProvider.Result parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException { MyMessagesProvider.Result result = new MyMessagesProvider.Result(); result.mItems = new ArrayList<>(); //   “message”  parser // ... return result; } } 

We register provider:

 static { ProviderManager.addIQProvider(MyMessagesProvider.ELEMENT_NAME, MyMessagesProvider.NAMESPACE, new MyMessagesProvider()); } 

We send an IQ-package with the processing of the result:

 xmppTCPConnection.sendStanzaWithResponseCallback( //  IQ- new CustomStanza(), //    IQ-.     ,   //           . new StanzaFilter() { @Override public boolean accept(Stanza stanza) { return stanza instanceof MyMessagesProvider.Result; } }, //   IQ-,    new StanzaListener() { @Override public void processPacket(Stanza packet) throws SmackException.NotConnectedException { List<CustomMessage> value = ((MyMessagesProvider.Result) packet).getValue(); //    } }, //   new ExceptionCallback() { @Override public void processException(Exception exception) { } } ); 

Total: we sent customized IQ and Message packages to the server, received and parsed customized IQ and Message packages without changing the source of the Smack library.

All the above code is for demonstration purposes. In the project we use retrolambda, RxJava and additional classes so that the code is universal and beautiful.

Source: https://habr.com/ru/post/319150/


All Articles