use publish-options instead of always pushing node configuration

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 85 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java         | 18 
src/main/java/eu/siacs/conversations/xml/Namespace.java                 |  2 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java           |  7 
src/main/java/eu/siacs/conversations/xmpp/forms/Data.java               | 17 
5 files changed, 93 insertions(+), 36 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java 🔗

@@ -48,6 +48,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
 import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
 import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -214,7 +215,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
 			putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
 			for (String  address : store.getKnownAddresses()) {
 				deviceIds = store.getSubDeviceSessions(address);
-				Log.d(Config.LOGTAG,account.getJid().toBareJid()+" adding device ids for "+address+" "+deviceIds);
 				putDevicesForJid(address, deviceIds, store);
 			}
 
@@ -437,16 +437,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
 		}
 		Set<Integer> deviceIds = new HashSet<>();
 		deviceIds.add(getOwnDeviceId());
-		IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
-		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
-		mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
-			@Override
-			public void onIqPacketReceived(Account account, IqPacket packet) {
-				if (packet.getType() == IqPacket.TYPE.RESULT) {
-					mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, PublishOptions.openAccess(), null);
-				}
-			}
-		});
+		publishDeviceIdsAndRefineAccessModel(deviceIds);
 	}
 
 	public void distrustFingerprint(final String fingerprint) {
@@ -513,17 +504,40 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
 			numPublishTriesOnEmptyPep = 0;
 		}
 		deviceIdsCopy.add(getOwnDeviceId());
-		IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+		publishDeviceIdsAndRefineAccessModel(deviceIdsCopy);
+	}
+
+	private void publishDeviceIdsAndRefineAccessModel(Set<Integer> ids) {
+		publishDeviceIdsAndRefineAccessModel(ids,true);
+	}
+
+	private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
+		final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
+		IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
 		ownPushPending.set(true);
 		mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
-				ownPushPending.set(false);
-				if (packet.getType() == IqPacket.TYPE.ERROR) {
-					pepBroken = true;
-					Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
-				} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
-					mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, PublishOptions.openAccess(), null);
+				Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
+				if (firstAttempt && error != null && error.hasChild("precondition-not-met",Namespace.PUBSUB_ERROR)) {
+					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": precondition wasn't met for device list. pushing node configuration");
+					mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
+						@Override
+						public void onPushSucceeded() {
+							publishDeviceIdsAndRefineAccessModel(ids, false);
+						}
+
+						@Override
+						public void onPushFailed() {
+							publishDeviceIdsAndRefineAccessModel(ids, false);
+						}
+					});
+				} else {
+					ownPushPending.set(false);
+					if (packet.getType() == IqPacket.TYPE.ERROR) {
+						pepBroken = true;
+						Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
+					}
 				}
 			}
 		});
@@ -683,32 +697,45 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
 									 Set<PreKeyRecord> preKeyRecords,
 									 final boolean announceAfter,
 									 final boolean wipe) {
+		publishDeviceBundle(signedPreKeyRecord,preKeyRecords,announceAfter,wipe,true);
+	}
+
+	private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord,
+									 final Set<PreKeyRecord> preKeyRecords,
+									 final boolean announceAfter,
+									 final boolean wipe,
+									 final boolean firstAttempt) {
+		final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
 		IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
 				signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
-				preKeyRecords, getOwnDeviceId());
+				preKeyRecords, getOwnDeviceId(),publishOptions);
 		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
 		mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 			@Override
 			public void onIqPacketReceived(final Account account, IqPacket packet) {
-				if (packet.getType() == IqPacket.TYPE.RESULT) {
+				Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
+				if (firstAttempt && error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR)) {
+					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": precondition wasn't met for bundle. pushing node configuration");
 					final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
-					mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
+					mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
 						@Override
 						public void onPushSucceeded() {
-							Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
-							if (wipe) {
-								wipeOtherPepDevices();
-							} else if (announceAfter) {
-								Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
-								publishOwnDeviceIdIfNeeded();
-							}
+							publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
 						}
 
 						@Override
 						public void onPushFailed() {
-							Log.d(Config.LOGTAG,"unable to change access model for pubsub node");
+							publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
 						}
 					});
+				} if (packet.getType() == IqPacket.TYPE.RESULT) {
+					Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
+					if (wipe) {
+						wipeOtherPepDevices();
+					} else if (announceAfter) {
+						Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+						publishOwnDeviceIdIfNeeded();
+					}
 				} else if (packet.getType() == IqPacket.TYPE.ERROR) {
 					pepBroken = true;
 					Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -91,16 +91,24 @@ public class IqGenerator extends AbstractGenerator {
 		return packet;
 	}
 
-	protected IqPacket publish(final String node, final Element item) {
+	protected IqPacket publish(final String node, final Element item, final Bundle options) {
 		final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
 		final Element pubsub = packet.addChild("pubsub",
 				"http://jabber.org/protocol/pubsub");
 		final Element publish = pubsub.addChild("publish");
 		publish.setAttribute("node", node);
 		publish.addChild(item);
+		if (options != null) {
+			final Element publishOptions = pubsub.addChild("publish-options");
+			publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
+		}
 		return packet;
 	}
 
+	protected IqPacket publish(final String node, final Element item) {
+		return publish(node,item,null);
+	}
+
 	protected IqPacket retrieve(String node, Element item) {
 		final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
 		final Element pubsub = packet.addChild("pubsub",
@@ -184,7 +192,7 @@ public class IqGenerator extends AbstractGenerator {
 		return packet;
 	}
 
-	public IqPacket publishDeviceIds(final Set<Integer> ids) {
+	public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
 		final Element item = new Element("item");
 		final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
 		for(Integer id:ids) {
@@ -192,11 +200,11 @@ public class IqGenerator extends AbstractGenerator {
 			device.setAttribute("id", id);
 			list.addChild(device);
 		}
-		return publish(AxolotlService.PEP_DEVICE_LIST, item);
+		return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
 	}
 
 	public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
-	                               final Set<PreKeyRecord> preKeyRecords, final int deviceId) {
+								   final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
 		final Element item = new Element("item");
 		final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
 		final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
@@ -215,7 +223,7 @@ public class IqGenerator extends AbstractGenerator {
 			prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
 		}
 
-		return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
+		return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item, publishOptions);
 	}
 
 	public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {

src/main/java/eu/siacs/conversations/xml/Namespace.java 🔗

@@ -13,4 +13,6 @@ public final class Namespace {
 	public static final String OOB = "jabber:x:oob";
 	public static final String SASL = "urn:ietf:params:xml:ns:xmpp-sasl";
 	public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
+	public static final String PUBSUB_PUBLISH_OPTIONS = "http://jabber.org/protocol/pubsub#publish-options";
+	public static final String PUBSUB_ERROR = "http://jabber.org/protocol/pubsub#errors";
 }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -1671,6 +1671,13 @@ public class XmppConnection implements Runnable {
 			}
 		}
 
+		public boolean pepPublishOptions() {
+			synchronized (XmppConnection.this.disco) {
+				ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
+				return info != null && info.getFeatures().contains(Namespace.PUBSUB_PUBLISH_OPTIONS);
+			}
+		}
+
 		public boolean mam() {
 			return hasDiscoFeature(account.getJid().toBareJid(), Namespace.MAM)
 					|| hasDiscoFeature(account.getJid().toBareJid(), Namespace.MAM_LEGACY);

src/main/java/eu/siacs/conversations/xmpp/forms/Data.java 🔗

@@ -39,13 +39,14 @@ public class Data extends Element {
 		return null;
 	}
 
-	public void put(String name, String value) {
+	public Field put(String name, String value) {
 		Field field = getFieldByName(name);
 		if (field == null) {
 			field = new Field(name);
 			this.addChild(field);
 		}
 		field.setValue(value);
+		return field;
 	}
 
 	public void put(String name, Collection<String> values) {
@@ -91,7 +92,8 @@ public class Data extends Element {
 	}
 
 	public void setFormType(String formType) {
-		this.put(FORM_TYPE, formType);
+		Field field = this.put(FORM_TYPE, formType);
+		field.setAttribute("type","hidden");
 	}
 
 	public String getFormType() {
@@ -108,4 +110,15 @@ public class Data extends Element {
 		return findChildContent("title");
 	}
 
+
+	public static Data create(String type, Bundle bundle) {
+		Data data = new Data();
+		data.setFormType(type);
+		data.setAttribute("type","submit");
+		for(String key : bundle.keySet()) {
+			data.put(key,bundle.getString(key));
+		}
+		return data;
+	}
+
 }