IqGenerator.java

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