IqGenerator.java

  1package eu.siacs.conversations.generator;
  2
  3import android.os.Bundle;
  4import android.util.Base64;
  5import android.util.Log;
  6import eu.siacs.conversations.Config;
  7import eu.siacs.conversations.R;
  8import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  9import eu.siacs.conversations.entities.Account;
 10import eu.siacs.conversations.entities.Bookmark;
 11import eu.siacs.conversations.entities.Conversation;
 12import eu.siacs.conversations.entities.DownloadableFile;
 13import eu.siacs.conversations.services.MessageArchiveService;
 14import eu.siacs.conversations.services.XmppConnectionService;
 15import eu.siacs.conversations.xml.Element;
 16import eu.siacs.conversations.xml.Namespace;
 17import eu.siacs.conversations.xmpp.Jid;
 18import eu.siacs.conversations.xmpp.forms.Data;
 19import eu.siacs.conversations.xmpp.pep.Avatar;
 20import im.conversations.android.xmpp.model.stanza.Iq;
 21import im.conversations.android.xmpp.model.upload.Request;
 22import java.nio.ByteBuffer;
 23import java.security.cert.CertificateEncodingException;
 24import java.security.cert.X509Certificate;
 25import java.util.ArrayList;
 26import java.util.List;
 27import java.util.Locale;
 28import java.util.Set;
 29import java.util.TimeZone;
 30import java.util.UUID;
 31import org.whispersystems.libsignal.IdentityKey;
 32import org.whispersystems.libsignal.ecc.ECPublicKey;
 33import org.whispersystems.libsignal.state.PreKeyRecord;
 34import org.whispersystems.libsignal.state.SignedPreKeyRecord;
 35
 36public class IqGenerator extends AbstractGenerator {
 37
 38    public IqGenerator(final XmppConnectionService service) {
 39        super(service);
 40    }
 41
 42    public Iq discoResponse(final Account account, final Iq request) {
 43        final var packet = new Iq(Iq.Type.RESULT);
 44        packet.setId(request.getId());
 45        packet.setTo(request.getFrom());
 46        final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
 47        query.setAttribute("node", request.query().getAttribute("node"));
 48        final Element identity = query.addChild("identity");
 49        identity.setAttribute("category", "client");
 50        identity.setAttribute("type", getIdentityType());
 51        identity.setAttribute("name", getIdentityName());
 52        for (final String feature : getFeatures(account)) {
 53            query.addChild("feature").setAttribute("var", feature);
 54        }
 55        return packet;
 56    }
 57
 58    public Iq versionResponse(final Iq request) {
 59        final var packet = request.generateResponse(Iq.Type.RESULT);
 60        Element query = packet.query("jabber:iq:version");
 61        query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
 62        query.addChild("version").setContent(getIdentityVersion());
 63        if ("chromium".equals(android.os.Build.BRAND)) {
 64            query.addChild("os").setContent("Chrome OS");
 65        } else {
 66            query.addChild("os").setContent("Android");
 67        }
 68        return packet;
 69    }
 70
 71    public Iq entityTimeResponse(final Iq request) {
 72        final Iq packet = request.generateResponse(Iq.Type.RESULT);
 73        Element time = packet.addChild("time", "urn:xmpp:time");
 74        final long now = System.currentTimeMillis();
 75        time.addChild("utc").setContent(getTimestamp(now));
 76        TimeZone ourTimezone = TimeZone.getDefault();
 77        long offsetSeconds = ourTimezone.getOffset(now) / 1000;
 78        long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60);
 79        long offsetHours = offsetSeconds / 3600;
 80        String hours;
 81        if (offsetHours < 0) {
 82            hours = String.format(Locale.US, "%03d", offsetHours);
 83        } else {
 84            hours = String.format(Locale.US, "%02d", offsetHours);
 85        }
 86        String minutes = String.format(Locale.US, "%02d", offsetMinutes);
 87        time.addChild("tzo").setContent(hours + ":" + minutes);
 88        return packet;
 89    }
 90
 91    public static Iq purgeOfflineMessages() {
 92        final Iq packet = new Iq(Iq.Type.SET);
 93        packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
 94        return packet;
 95    }
 96
 97    protected Iq publish(final String node, final Element item, final Bundle options) {
 98        final var packet = new Iq(Iq.Type.SET);
 99        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
100        final Element publish = pubsub.addChild("publish");
101        publish.setAttribute("node", node);
102        publish.addChild(item);
103        if (options != null) {
104            final Element publishOptions = pubsub.addChild("publish-options");
105            publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
106        }
107        return packet;
108    }
109
110    protected Iq publish(final String node, final Element item) {
111        return publish(node, item, null);
112    }
113
114    private Iq retrieve(String node, Element item) {
115        final var packet = new Iq(Iq.Type.GET);
116        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
117        final Element items = pubsub.addChild("items");
118        items.setAttribute("node", node);
119        if (item != null) {
120            items.addChild(item);
121        }
122        return packet;
123    }
124
125    public Iq retrieveBookmarks() {
126        return retrieve(Namespace.BOOKMARKS2, null);
127    }
128
129    public Iq retrieveMds() {
130        return retrieve(Namespace.MDS_DISPLAYED, null);
131    }
132
133    public Iq publishNick(String nick) {
134        final Element item = new Element("item");
135        item.setAttribute("id", "current");
136        item.addChild("nick", Namespace.NICK).setContent(nick);
137        return publish(Namespace.NICK, item);
138    }
139
140    public Iq deleteNode(final String node) {
141        final var packet = new Iq(Iq.Type.SET);
142        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
143        pubsub.addChild("delete").setAttribute("node", node);
144        return packet;
145    }
146
147    public Iq deleteItem(final String node, final String id) {
148        final var packet = new Iq(Iq.Type.SET);
149        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
150        final Element retract = pubsub.addChild("retract");
151        retract.setAttribute("node", node);
152        retract.setAttribute("notify", "true");
153        retract.addChild("item").setAttribute("id", id);
154        return packet;
155    }
156
157    public Iq publishAvatar(Avatar avatar, Bundle options) {
158        final Element item = new Element("item");
159        item.setAttribute("id", avatar.sha1sum);
160        final Element data = item.addChild("data", Namespace.AVATAR_DATA);
161        data.setContent(avatar.image);
162        return publish(Namespace.AVATAR_DATA, item, options);
163    }
164
165    public Iq publishElement(
166            final String namespace, final Element element, String id, final Bundle options) {
167        final Element item = new Element("item");
168        item.setAttribute("id", id);
169        item.addChild(element);
170        return publish(namespace, item, options);
171    }
172
173    public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) {
174        final Element item = new Element("item");
175        item.setAttribute("id", avatar.sha1sum);
176        final Element metadata = item.addChild("metadata", Namespace.AVATAR_METADATA);
177        final Element info = metadata.addChild("info");
178        info.setAttribute("bytes", avatar.size);
179        info.setAttribute("id", avatar.sha1sum);
180        info.setAttribute("height", avatar.height);
181        info.setAttribute("width", avatar.height);
182        info.setAttribute("type", avatar.type);
183        return publish(Namespace.AVATAR_METADATA, item, options);
184    }
185
186    public Iq retrievePepAvatar(final Avatar avatar) {
187        final Element item = new Element("item");
188        item.setAttribute("id", avatar.sha1sum);
189        final var packet = retrieve(Namespace.AVATAR_DATA, item);
190        packet.setTo(avatar.owner);
191        return packet;
192    }
193
194    public Iq retrieveVcardAvatar(final Avatar avatar) {
195        final Iq packet = new Iq(Iq.Type.GET);
196        packet.setTo(avatar.owner);
197        packet.addChild("vCard", "vcard-temp");
198        return packet;
199    }
200
201    public Iq retrieveVcardAvatar(final Jid to) {
202        final Iq packet = new Iq(Iq.Type.GET);
203        packet.setTo(to);
204        packet.addChild("vCard", "vcard-temp");
205        return packet;
206    }
207
208    public Iq retrieveAvatarMetaData(final Jid to) {
209        final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
210        if (to != null) {
211            packet.setTo(to);
212        }
213        return packet;
214    }
215
216    public Iq retrieveDeviceIds(final Jid to) {
217        final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
218        if (to != null) {
219            packet.setTo(to);
220        }
221        return packet;
222    }
223
224    public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
225        final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
226        packet.setTo(to);
227        return packet;
228    }
229
230    public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
231        final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
232        packet.setTo(to);
233        return packet;
234    }
235
236    public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
237        final Element item = new Element("item");
238        item.setAttribute("id", "current");
239        final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
240        for (Integer id : ids) {
241            final Element device = new Element("device");
242            device.setAttribute("id", id);
243            list.addChild(device);
244        }
245        return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
246    }
247
248    public Element publishBookmarkItem(final Bookmark bookmark) {
249        final String name = bookmark.getBookmarkName();
250        final String nick = bookmark.getNick();
251        final String password = bookmark.getPassword();
252        final boolean autojoin = bookmark.autojoin();
253        final Element conference = new Element("conference", Namespace.BOOKMARKS2);
254        if (name != null) {
255            conference.setAttribute("name", name);
256        }
257        if (nick != null) {
258            conference.addChild("nick").setContent(nick);
259        }
260        if (password != null) {
261            conference.addChild("password").setContent(password);
262        }
263        conference.setAttribute("autojoin", String.valueOf(autojoin));
264        conference.addChild(bookmark.getExtensions());
265        return conference;
266    }
267
268    public Element mdsDisplayed(final String stanzaId, final Conversation conversation) {
269        final Jid by;
270        if (conversation.getMode() == Conversation.MODE_MULTI) {
271            by = conversation.getJid().asBareJid();
272        } else {
273            by = conversation.getAccount().getJid().asBareJid();
274        }
275        return mdsDisplayed(stanzaId, by);
276    }
277
278    private Element mdsDisplayed(final String stanzaId, final Jid by) {
279        final Element displayed = new Element("displayed", Namespace.MDS_DISPLAYED);
280        final Element stanzaIdElement = displayed.addChild("stanza-id", Namespace.STANZA_IDS);
281        stanzaIdElement.setAttribute("id", stanzaId);
282        stanzaIdElement.setAttribute("by", by);
283        return displayed;
284    }
285
286    public Iq publishBundles(
287            final SignedPreKeyRecord signedPreKeyRecord,
288            final IdentityKey identityKey,
289            final Set<PreKeyRecord> preKeyRecords,
290            final int deviceId,
291            Bundle publishOptions) {
292        final Element item = new Element("item");
293        item.setAttribute("id", "current");
294        final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
295        final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
296        signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
297        ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
298        signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
299        final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
300        signedPreKeySignature.setContent(
301                Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
302        final Element identityKeyElement = bundle.addChild("identityKey");
303        identityKeyElement.setContent(
304                Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
305
306        final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
307        for (PreKeyRecord preKeyRecord : preKeyRecords) {
308            final Element prekey = prekeys.addChild("preKeyPublic");
309            prekey.setAttribute("preKeyId", preKeyRecord.getId());
310            prekey.setContent(
311                    Base64.encodeToString(
312                            preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
313        }
314
315        return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
316    }
317
318    public Iq publishVerification(
319            byte[] signature, X509Certificate[] certificates, final int deviceId) {
320        final Element item = new Element("item");
321        item.setAttribute("id", "current");
322        final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
323        final Element chain = verification.addChild("chain");
324        for (int i = 0; i < certificates.length; ++i) {
325            try {
326                Element certificate = chain.addChild("certificate");
327                certificate.setContent(
328                        Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
329                certificate.setAttribute("index", i);
330            } catch (CertificateEncodingException e) {
331                Log.d(Config.LOGTAG, "could not encode certificate");
332            }
333        }
334        verification
335                .addChild("signature")
336                .setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
337        return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
338    }
339
340    public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
341        final Iq packet = new Iq(Iq.Type.SET);
342        final Element query = packet.query(mam.version.namespace);
343        query.setAttribute("queryid", mam.getQueryId());
344        final Data data = new Data();
345        data.setFormType(mam.version.namespace);
346        if (mam.muc()) {
347            packet.setTo(mam.getWith());
348        } else if (mam.getWith() != null) {
349            data.put("with", mam.getWith().toString());
350        }
351        final long start = mam.getStart();
352        final long end = mam.getEnd();
353        if (start != 0) {
354            data.put("start", getTimestamp(start));
355        }
356        if (end != 0) {
357            data.put("end", getTimestamp(end));
358        }
359        data.submit();
360        query.addChild(data);
361        Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
362        if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
363            set.addChild("before").setContent(mam.getReference());
364        } else if (mam.getReference() != null) {
365            set.addChild("after").setContent(mam.getReference());
366        }
367        set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
368        return packet;
369    }
370
371    public Iq generateGetBlockList() {
372        final Iq iq = new Iq(Iq.Type.GET);
373        iq.addChild("blocklist", Namespace.BLOCKING);
374
375        return iq;
376    }
377
378    public Iq generateSetBlockRequest(
379            final Jid jid, final boolean reportSpam, final String serverMsgId) {
380        final Iq iq = new Iq(Iq.Type.SET);
381        final Element block = iq.addChild("block", Namespace.BLOCKING);
382        final Element item = block.addChild("item").setAttribute("jid", jid);
383        if (reportSpam) {
384            final Element report = item.addChild("report", Namespace.REPORTING);
385            report.setAttribute("reason", Namespace.REPORTING_REASON_SPAM);
386            if (serverMsgId != null) {
387                final Element stanzaId = report.addChild("stanza-id", Namespace.STANZA_IDS);
388                stanzaId.setAttribute("by", jid);
389                stanzaId.setAttribute("id", serverMsgId);
390            }
391        }
392        Log.d(Config.LOGTAG, iq.toString());
393        return iq;
394    }
395
396    public Iq generateSetUnblockRequest(final Jid jid) {
397        final Iq iq = new Iq(Iq.Type.SET);
398        final Element block = iq.addChild("unblock", Namespace.BLOCKING);
399        block.addChild("item").setAttribute("jid", jid);
400        return iq;
401    }
402
403    public Iq generateSetPassword(final Account account, final String newPassword) {
404        final Iq packet = new Iq(Iq.Type.SET);
405        packet.setTo(account.getDomain());
406        final Element query = packet.addChild("query", Namespace.REGISTER);
407        final Jid jid = account.getJid();
408        query.addChild("username").setContent(jid.getLocal());
409        query.addChild("password").setContent(newPassword);
410        return packet;
411    }
412
413    public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) {
414        List<Jid> jids = new ArrayList<>();
415        jids.add(jid);
416        return changeAffiliation(conference, jids, affiliation);
417    }
418
419    public Iq changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
420        final Iq packet = new Iq(Iq.Type.SET);
421        packet.setTo(conference.getJid().asBareJid());
422        packet.setFrom(conference.getAccount().getJid());
423        Element query = packet.query("http://jabber.org/protocol/muc#admin");
424        for (Jid jid : jids) {
425            Element item = query.addChild("item");
426            item.setAttribute("jid", jid);
427            item.setAttribute("affiliation", affiliation);
428        }
429        return packet;
430    }
431
432    public Iq changeRole(Conversation conference, String nick, String role) {
433        final Iq packet = new Iq(Iq.Type.SET);
434        packet.setTo(conference.getJid().asBareJid());
435        packet.setFrom(conference.getAccount().getJid());
436        Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
437        item.setAttribute("nick", nick);
438        item.setAttribute("role", role);
439        return packet;
440    }
441
442    public Iq requestHttpUploadSlot(
443            final Jid host, final DownloadableFile file, final String mime) {
444        final Iq packet = new Iq(Iq.Type.GET);
445        packet.setTo(host);
446        final var request = packet.addExtension(new Request());
447        request.setFilename(convertFilename(file.getName()));
448        request.setSize(file.getExpectedSize());
449        return packet;
450    }
451
452    private static String convertFilename(String name) {
453        int pos = name.indexOf('.');
454        if (pos != -1) {
455            try {
456                UUID uuid = UUID.fromString(name.substring(0, pos));
457                ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
458                bb.putLong(uuid.getMostSignificantBits());
459                bb.putLong(uuid.getLeastSignificantBits());
460                return Base64.encodeToString(
461                                bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP)
462                        + name.substring(pos);
463            } catch (Exception e) {
464                return name;
465            }
466        } else {
467            return name;
468        }
469    }
470
471    public static Iq generateCreateAccountWithCaptcha(
472            final Account account, final String id, final Data data) {
473        final Iq register = new Iq(Iq.Type.SET);
474        register.setFrom(account.getJid().asBareJid());
475        register.setTo(account.getDomain());
476        register.setId(id);
477        Element query = register.query(Namespace.REGISTER);
478        if (data != null) {
479            query.addChild(data);
480        }
481        return register;
482    }
483
484    public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
485        return pushTokenToAppServer(appServer, token, deviceId, null);
486    }
487
488    public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
489        final Iq packet = new Iq(Iq.Type.SET);
490        packet.setTo(appServer);
491        final Element command = packet.addChild("command", Namespace.COMMANDS);
492        command.setAttribute("node", "register-push-fcm");
493        command.setAttribute("action", "execute");
494        final Data data = new Data();
495        data.put("token", token);
496        data.put("android-id", deviceId);
497        if (muc != null) {
498            data.put("muc", muc.toString());
499        }
500        data.submit();
501        command.addChild(data);
502        return packet;
503    }
504
505    public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
506        final Iq packet = new Iq(Iq.Type.SET);
507        packet.setTo(appServer);
508        final Element command = packet.addChild("command", Namespace.COMMANDS);
509        command.setAttribute("node", "unregister-push-fcm");
510        command.setAttribute("action", "execute");
511        final Data data = new Data();
512        data.put("channel", channel);
513        data.put("android-id", deviceId);
514        data.submit();
515        command.addChild(data);
516        return packet;
517    }
518
519    public Iq enablePush(final Jid jid, final String node, final String secret) {
520        final Iq packet = new Iq(Iq.Type.SET);
521        Element enable = packet.addChild("enable", Namespace.PUSH);
522        enable.setAttribute("jid", jid);
523        enable.setAttribute("node", node);
524        if (secret != null) {
525            Data data = new Data();
526            data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
527            data.put("secret", secret);
528            data.submit();
529            enable.addChild(data);
530        }
531        return packet;
532    }
533
534    public Iq disablePush(final Jid jid, final String node) {
535        Iq packet = new Iq(Iq.Type.SET);
536        Element disable = packet.addChild("disable", Namespace.PUSH);
537        disable.setAttribute("jid", jid);
538        disable.setAttribute("node", node);
539        return packet;
540    }
541
542    public Iq queryAffiliation(Conversation conversation, String affiliation) {
543        final Iq packet = new Iq(Iq.Type.GET);
544        packet.setTo(conversation.getJid().asBareJid());
545        packet.query("http://jabber.org/protocol/muc#admin")
546                .addChild("item")
547                .setAttribute("affiliation", affiliation);
548        return packet;
549    }
550
551    public static Bundle defaultGroupChatConfiguration() {
552        Bundle options = new Bundle();
553        options.putString("muc#roomconfig_persistentroom", "1");
554        options.putString("muc#roomconfig_membersonly", "1");
555        options.putString("muc#roomconfig_publicroom", "0");
556        options.putString("muc#roomconfig_whois", "anyone");
557        options.putString("muc#roomconfig_changesubject", "0");
558        options.putString("muc#roomconfig_allowinvites", "0");
559        options.putString("muc#roomconfig_enablearchiving", "1"); // prosody
560        options.putString("mam", "1"); // ejabberd community
561        options.putString("muc#roomconfig_mam", "1"); // ejabberd saas
562        return options;
563    }
564
565    public static Bundle defaultChannelConfiguration() {
566        Bundle options = new Bundle();
567        options.putString("muc#roomconfig_persistentroom", "1");
568        options.putString("muc#roomconfig_membersonly", "0");
569        options.putString("muc#roomconfig_publicroom", "1");
570        options.putString("muc#roomconfig_whois", "moderators");
571        options.putString("muc#roomconfig_changesubject", "0");
572        options.putString("muc#roomconfig_enablearchiving", "1"); // prosody
573        options.putString("mam", "1"); // ejabberd community
574        options.putString("muc#roomconfig_mam", "1"); // ejabberd saas
575        return options;
576    }
577
578    public Iq requestPubsubConfiguration(Jid jid, String node) {
579        return pubsubConfiguration(jid, node, null);
580    }
581
582    public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
583        return pubsubConfiguration(jid, node, data);
584    }
585
586    private Iq pubsubConfiguration(Jid jid, String node, Data data) {
587        final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
588        packet.setTo(jid);
589        Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
590        Element configure = pubsub.addChild("configure").setAttribute("node", node);
591        if (data != null) {
592            configure.addChild(data);
593        }
594        return packet;
595    }
596
597    public Iq queryDiscoItems(final Jid jid) {
598        final Iq packet = new Iq(Iq.Type.GET);
599        packet.setTo(jid);
600        packet.addChild("query", Namespace.DISCO_ITEMS);
601        return packet;
602    }
603
604    public Iq queryDiscoInfo(final Jid jid) {
605        final Iq packet = new Iq(Iq.Type.GET);
606        packet.setTo(jid);
607        packet.addChild("query", Namespace.DISCO_INFO);
608        return packet;
609    }
610}