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.crypto.axolotl.AxolotlService;
8import eu.siacs.conversations.services.MessageArchiveService;
9import eu.siacs.conversations.services.XmppConnectionService;
10import eu.siacs.conversations.xml.Element;
11import eu.siacs.conversations.xml.Namespace;
12import eu.siacs.conversations.xmpp.Jid;
13import eu.siacs.conversations.xmpp.forms.Data;
14import im.conversations.android.xmpp.model.stanza.Iq;
15import java.security.cert.CertificateEncodingException;
16import java.security.cert.X509Certificate;
17import java.util.Set;
18import org.whispersystems.libsignal.IdentityKey;
19import org.whispersystems.libsignal.ecc.ECPublicKey;
20import org.whispersystems.libsignal.state.PreKeyRecord;
21import org.whispersystems.libsignal.state.SignedPreKeyRecord;
22
23public class IqGenerator extends AbstractGenerator {
24
25 public IqGenerator(final XmppConnectionService service) {
26 super(service);
27 }
28
29 protected Iq publish(final String node, final Element item, final Bundle options) {
30 final var packet = new Iq(Iq.Type.SET);
31 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
32 final Element publish = pubsub.addChild("publish");
33 publish.setAttribute("node", node);
34 publish.addChild(item);
35 if (options != null) {
36 final Element publishOptions = pubsub.addChild("publish-options");
37 publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
38 }
39 return packet;
40 }
41
42 protected Iq publish(final String node, final Element item) {
43 return publish(node, item, null);
44 }
45
46 private Iq retrieve(String node, Element item) {
47 final var packet = new Iq(Iq.Type.GET);
48 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
49 final Element items = pubsub.addChild("items");
50 items.setAttribute("node", node);
51 if (item != null) {
52 items.addChild(item);
53 }
54 return packet;
55 }
56
57 public Iq retrieveDeviceIds(final Jid to) {
58 final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
59 if (to != null) {
60 packet.setTo(to);
61 }
62 return packet;
63 }
64
65 public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
66 final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
67 packet.setTo(to);
68 return packet;
69 }
70
71 public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
72 final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
73 packet.setTo(to);
74 return packet;
75 }
76
77 public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
78 final Element item = new Element("item");
79 item.setAttribute("id", "current");
80 final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
81 for (Integer id : ids) {
82 final Element device = new Element("device");
83 device.setAttribute("id", id);
84 list.addChild(device);
85 }
86 return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
87 }
88
89 public Iq publishBundles(
90 final SignedPreKeyRecord signedPreKeyRecord,
91 final IdentityKey identityKey,
92 final Set<PreKeyRecord> preKeyRecords,
93 final int deviceId,
94 Bundle publishOptions) {
95 final Element item = new Element("item");
96 item.setAttribute("id", "current");
97 final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
98 final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
99 signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
100 ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
101 signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
102 final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
103 signedPreKeySignature.setContent(
104 Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
105 final Element identityKeyElement = bundle.addChild("identityKey");
106 identityKeyElement.setContent(
107 Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
108
109 final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
110 for (PreKeyRecord preKeyRecord : preKeyRecords) {
111 final Element prekey = prekeys.addChild("preKeyPublic");
112 prekey.setAttribute("preKeyId", preKeyRecord.getId());
113 prekey.setContent(
114 Base64.encodeToString(
115 preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
116 }
117
118 return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
119 }
120
121 public Iq publishVerification(
122 byte[] signature, X509Certificate[] certificates, final int deviceId) {
123 final Element item = new Element("item");
124 item.setAttribute("id", "current");
125 final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
126 final Element chain = verification.addChild("chain");
127 for (int i = 0; i < certificates.length; ++i) {
128 try {
129 Element certificate = chain.addChild("certificate");
130 certificate.setContent(
131 Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
132 certificate.setAttribute("index", i);
133 } catch (CertificateEncodingException e) {
134 Log.d(Config.LOGTAG, "could not encode certificate");
135 }
136 }
137 verification
138 .addChild("signature")
139 .setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
140 return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
141 }
142
143 public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
144 final Iq packet = new Iq(Iq.Type.SET);
145 final Element query = packet.addChild("query", mam.version.namespace);
146 query.setAttribute("queryid", mam.getQueryId());
147 final Data data = new Data();
148 data.setFormType(mam.version.namespace);
149 if (mam.muc()) {
150 packet.setTo(mam.getWith());
151 } else if (mam.getWith() != null) {
152 data.put("with", mam.getWith().toString());
153 }
154 final long start = mam.getStart();
155 final long end = mam.getEnd();
156 if (start != 0) {
157 data.put("start", getTimestamp(start));
158 }
159 if (end != 0) {
160 data.put("end", getTimestamp(end));
161 }
162 data.submit();
163 query.addChild(data);
164 Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
165 if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
166 set.addChild("before").setContent(mam.getReference());
167 } else if (mam.getReference() != null) {
168 set.addChild("after").setContent(mam.getReference());
169 }
170 set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
171 return packet;
172 }
173
174 public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
175 return pushTokenToAppServer(appServer, token, deviceId, null);
176 }
177
178 public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
179 final Iq packet = new Iq(Iq.Type.SET);
180 packet.setTo(appServer);
181 final Element command = packet.addChild("command", Namespace.COMMANDS);
182 command.setAttribute("node", "register-push-fcm");
183 command.setAttribute("action", "execute");
184 final Data data = new Data();
185 data.put("token", token);
186 data.put("android-id", deviceId);
187 if (muc != null) {
188 data.put("muc", muc.toString());
189 }
190 data.submit();
191 command.addChild(data);
192 return packet;
193 }
194
195 public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
196 final Iq packet = new Iq(Iq.Type.SET);
197 packet.setTo(appServer);
198 final Element command = packet.addChild("command", Namespace.COMMANDS);
199 command.setAttribute("node", "unregister-push-fcm");
200 command.setAttribute("action", "execute");
201 final Data data = new Data();
202 data.put("channel", channel);
203 data.put("android-id", deviceId);
204 data.submit();
205 command.addChild(data);
206 return packet;
207 }
208
209 public Iq enablePush(final Jid jid, final String node, final String secret) {
210 final Iq packet = new Iq(Iq.Type.SET);
211 Element enable = packet.addChild("enable", Namespace.PUSH);
212 enable.setAttribute("jid", jid);
213 enable.setAttribute("node", node);
214 if (secret != null) {
215 Data data = new Data();
216 data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
217 data.put("secret", secret);
218 data.submit();
219 enable.addChild(data);
220 }
221 return packet;
222 }
223
224 public Iq disablePush(final Jid jid, final String node) {
225 Iq packet = new Iq(Iq.Type.SET);
226 Element disable = packet.addChild("disable", Namespace.PUSH);
227 disable.setAttribute("jid", jid);
228 disable.setAttribute("node", node);
229 return packet;
230 }
231
232 public Iq requestPubsubConfiguration(Jid jid, String node) {
233 return pubsubConfiguration(jid, node, null);
234 }
235
236 public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
237 return pubsubConfiguration(jid, node, data);
238 }
239
240 private Iq pubsubConfiguration(Jid jid, String node, Data data) {
241 final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
242 packet.setTo(jid);
243 Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
244 Element configure = pubsub.addChild("configure").setAttribute("node", node);
245 if (data != null) {
246 configure.addChild(data);
247 }
248 return packet;
249 }
250}