1package eu.siacs.conversations.generator;
2
3
4import android.os.Bundle;
5import android.util.Base64;
6import android.util.Log;
7
8import org.whispersystems.libaxolotl.IdentityKey;
9import org.whispersystems.libaxolotl.ecc.ECPublicKey;
10import org.whispersystems.libaxolotl.state.PreKeyRecord;
11import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
12
13import java.security.cert.CertificateEncodingException;
14import java.security.cert.X509Certificate;
15import java.util.ArrayList;
16import java.util.List;
17import java.util.Set;
18import java.util.TimeZone;
19
20import eu.siacs.conversations.Config;
21import eu.siacs.conversations.crypto.axolotl.AxolotlService;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Conversation;
24import eu.siacs.conversations.entities.DownloadableFile;
25import eu.siacs.conversations.services.MessageArchiveService;
26import eu.siacs.conversations.services.XmppConnectionService;
27import eu.siacs.conversations.utils.Xmlns;
28import eu.siacs.conversations.xml.Element;
29import eu.siacs.conversations.xmpp.forms.Data;
30import eu.siacs.conversations.xmpp.jid.Jid;
31import eu.siacs.conversations.xmpp.pep.Avatar;
32import eu.siacs.conversations.xmpp.stanzas.IqPacket;
33
34public class IqGenerator extends AbstractGenerator {
35
36 public IqGenerator(final XmppConnectionService service) {
37 super(service);
38 }
39
40 public IqPacket discoResponse(final IqPacket request) {
41 final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
42 packet.setId(request.getId());
43 packet.setTo(request.getFrom());
44 final Element query = packet.addChild("query",
45 "http://jabber.org/protocol/disco#info");
46 query.setAttribute("node", request.query().getAttribute("node"));
47 final Element identity = query.addChild("identity");
48 identity.setAttribute("category", "client");
49 identity.setAttribute("type", IDENTITY_TYPE);
50 identity.setAttribute("name", getIdentityName());
51 for (final String feature : getFeatures()) {
52 query.addChild("feature").setAttribute("var", feature);
53 }
54 return packet;
55 }
56
57 public IqPacket versionResponse(final IqPacket request) {
58 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
59 Element query = packet.query("jabber:iq:version");
60 query.addChild("name").setContent(IDENTITY_NAME);
61 query.addChild("version").setContent(getIdentityVersion());
62 return packet;
63 }
64
65 public IqPacket entityTimeResponse(IqPacket request) {
66 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
67 Element time = packet.addChild("time","urn:xmpp:time");
68 final long now = System.currentTimeMillis();
69 time.addChild("utc").setContent(getTimestamp(now));
70 TimeZone ourTimezone = TimeZone.getDefault();
71 long offsetSeconds = ourTimezone.getOffset(now) / 1000;
72 long offsetMinutes = offsetSeconds % (60 * 60);
73 long offsetHours = offsetSeconds / (60 * 60);
74 time.addChild("tzo").setContent(String.format("%02d",offsetHours)+":"+String.format("%02d",offsetMinutes));
75 return packet;
76 }
77
78 protected IqPacket publish(final String node, final Element item) {
79 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
80 final Element pubsub = packet.addChild("pubsub",
81 "http://jabber.org/protocol/pubsub");
82 final Element publish = pubsub.addChild("publish");
83 publish.setAttribute("node", node);
84 publish.addChild(item);
85 return packet;
86 }
87
88 protected IqPacket retrieve(String node, Element item) {
89 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
90 final Element pubsub = packet.addChild("pubsub",
91 "http://jabber.org/protocol/pubsub");
92 final Element items = pubsub.addChild("items");
93 items.setAttribute("node", node);
94 if (item != null) {
95 items.addChild(item);
96 }
97 return packet;
98 }
99
100 public IqPacket publishNick(String nick) {
101 final Element item = new Element("item");
102 item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick);
103 return publish("http://jabber.org/protocol/nick", item);
104 }
105
106 public IqPacket publishAvatar(Avatar avatar) {
107 final Element item = new Element("item");
108 item.setAttribute("id", avatar.sha1sum);
109 final Element data = item.addChild("data", "urn:xmpp:avatar:data");
110 data.setContent(avatar.image);
111 return publish("urn:xmpp:avatar:data", item);
112 }
113
114 public IqPacket publishAvatarMetadata(final Avatar avatar) {
115 final Element item = new Element("item");
116 item.setAttribute("id", avatar.sha1sum);
117 final Element metadata = item
118 .addChild("metadata", "urn:xmpp:avatar:metadata");
119 final Element info = metadata.addChild("info");
120 info.setAttribute("bytes", avatar.size);
121 info.setAttribute("id", avatar.sha1sum);
122 info.setAttribute("height", avatar.height);
123 info.setAttribute("width", avatar.height);
124 info.setAttribute("type", avatar.type);
125 return publish("urn:xmpp:avatar:metadata", item);
126 }
127
128 public IqPacket retrievePepAvatar(final Avatar avatar) {
129 final Element item = new Element("item");
130 item.setAttribute("id", avatar.sha1sum);
131 final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
132 packet.setTo(avatar.owner);
133 return packet;
134 }
135
136 public IqPacket retrieveVcardAvatar(final Avatar avatar) {
137 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
138 packet.setTo(avatar.owner);
139 packet.addChild("vCard", "vcard-temp");
140 return packet;
141 }
142
143 public IqPacket retrieveAvatarMetaData(final Jid to) {
144 final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
145 if (to != null) {
146 packet.setTo(to);
147 }
148 return packet;
149 }
150
151 public IqPacket retrieveDeviceIds(final Jid to) {
152 final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
153 if(to != null) {
154 packet.setTo(to);
155 }
156 return packet;
157 }
158
159 public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
160 final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
161 packet.setTo(to);
162 return packet;
163 }
164
165 public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
166 final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null);
167 packet.setTo(to);
168 return packet;
169 }
170
171 public IqPacket publishDeviceIds(final Set<Integer> ids) {
172 final Element item = new Element("item");
173 final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
174 for(Integer id:ids) {
175 final Element device = new Element("device");
176 device.setAttribute("id", id);
177 list.addChild(device);
178 }
179 return publish(AxolotlService.PEP_DEVICE_LIST, item);
180 }
181
182 public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
183 final Set<PreKeyRecord> preKeyRecords, final int deviceId) {
184 final Element item = new Element("item");
185 final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
186 final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
187 signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
188 ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
189 signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT));
190 final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
191 signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT));
192 final Element identityKeyElement = bundle.addChild("identityKey");
193 identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
194
195 final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
196 for(PreKeyRecord preKeyRecord:preKeyRecords) {
197 final Element prekey = prekeys.addChild("preKeyPublic");
198 prekey.setAttribute("preKeyId", preKeyRecord.getId());
199 prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
200 }
201
202 return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
203 }
204
205 public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
206 final Element item = new Element("item");
207 final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
208 final Element chain = verification.addChild("chain");
209 for(int i = 0; i < certificates.length; ++i) {
210 try {
211 Element certificate = chain.addChild("certificate");
212 certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
213 certificate.setAttribute("index",i);
214 } catch (CertificateEncodingException e) {
215 Log.d(Config.LOGTAG, "could not encode certificate");
216 }
217 }
218 verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
219 return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item);
220 }
221
222 public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
223 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
224 final Element query = packet.query("urn:xmpp:mam:0");
225 query.setAttribute("queryid", mam.getQueryId());
226 final Data data = new Data();
227 data.setFormType("urn:xmpp:mam:0");
228 if (mam.muc()) {
229 packet.setTo(mam.getWith());
230 } else if (mam.getWith()!=null) {
231 data.put("with", mam.getWith().toString());
232 }
233 data.put("start", getTimestamp(mam.getStart()));
234 data.put("end", getTimestamp(mam.getEnd()));
235 data.submit();
236 query.addChild(data);
237 if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
238 query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference());
239 } else if (mam.getReference() != null) {
240 query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getReference());
241 }
242 return packet;
243 }
244 public IqPacket generateGetBlockList() {
245 final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
246 iq.addChild("blocklist", Xmlns.BLOCKING);
247
248 return iq;
249 }
250
251 public IqPacket generateSetBlockRequest(final Jid jid) {
252 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
253 final Element block = iq.addChild("block", Xmlns.BLOCKING);
254 block.addChild("item").setAttribute("jid", jid.toBareJid().toString());
255 return iq;
256 }
257
258 public IqPacket generateSetUnblockRequest(final Jid jid) {
259 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
260 final Element block = iq.addChild("unblock", Xmlns.BLOCKING);
261 block.addChild("item").setAttribute("jid", jid.toBareJid().toString());
262 return iq;
263 }
264
265 public IqPacket generateSetPassword(final Account account, final String newPassword) {
266 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
267 packet.setTo(account.getServer());
268 final Element query = packet.addChild("query", Xmlns.REGISTER);
269 final Jid jid = account.getJid();
270 query.addChild("username").setContent(jid.getLocalpart());
271 query.addChild("password").setContent(newPassword);
272 return packet;
273 }
274
275 public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
276 List<Jid> jids = new ArrayList<>();
277 jids.add(jid);
278 return changeAffiliation(conference,jids,affiliation);
279 }
280
281 public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
282 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
283 packet.setTo(conference.getJid().toBareJid());
284 packet.setFrom(conference.getAccount().getJid());
285 Element query = packet.query("http://jabber.org/protocol/muc#admin");
286 for(Jid jid : jids) {
287 Element item = query.addChild("item");
288 item.setAttribute("jid", jid.toString());
289 item.setAttribute("affiliation", affiliation);
290 }
291 return packet;
292 }
293
294 public IqPacket changeRole(Conversation conference, String nick, String role) {
295 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
296 packet.setTo(conference.getJid().toBareJid());
297 packet.setFrom(conference.getAccount().getJid());
298 Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
299 item.setAttribute("nick", nick);
300 item.setAttribute("role", role);
301 return packet;
302 }
303
304 public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
305 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
306 packet.setTo(host);
307 Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD);
308 request.addChild("filename").setContent(file.getName());
309 request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
310 if (mime != null) {
311 request.addChild("content-type").setContent(mime);
312 }
313 return packet;
314 }
315
316 public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
317 final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
318
319 register.setTo(account.getServer());
320 register.setId(id);
321 Element query = register.query("jabber:iq:register");
322 if (data != null) {
323 query.addChild(data);
324 }
325 return register;
326 }
327
328 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
329 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
330 packet.setTo(appServer);
331 Element command = packet.addChild("command", "http://jabber.org/protocol/commands");
332 command.setAttribute("node","register-push-gcm");
333 command.setAttribute("action","execute");
334 Data data = new Data();
335 data.put("token", token);
336 data.put("device-id", deviceId);
337 data.submit();
338 command.addChild(data);
339 return packet;
340 }
341
342 public IqPacket enablePush(Jid jid, String node, String secret) {
343 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
344 Element enable = packet.addChild("enable","urn:xmpp:push:0");
345 enable.setAttribute("jid",jid.toString());
346 enable.setAttribute("node", node);
347 Data data = new Data();
348 data.setFormType("http://jabber.org/protocol/pubsub#publish-options");
349 data.put("secret",secret);
350 data.submit();
351 enable.addChild(data);
352 return packet;
353 }
354
355 public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
356 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
357 packet.setTo(conversation.getJid().toBareJid());
358 packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation",affiliation);
359 return packet;
360 }
361
362 public static Bundle defaultRoomConfiguration() {
363 Bundle options = new Bundle();
364 options.putString("muc#roomconfig_persistentroom", "1");
365 options.putString("muc#roomconfig_membersonly", "1");
366 options.putString("muc#roomconfig_publicroom", "0");
367 options.putString("muc#roomconfig_whois", "anyone");
368 return options;
369 }
370}