@@ -6,10 +6,8 @@ import android.os.Bundle;
import android.security.KeyChain;
import android.util.Log;
import android.util.Pair;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -17,38 +15,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.IdentityKeyPair;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.InvalidKeyIdException;
-import org.whispersystems.libsignal.SessionBuilder;
-import org.whispersystems.libsignal.SignalProtocolAddress;
-import org.whispersystems.libsignal.UntrustedIdentityException;
-import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.state.PreKeyBundle;
-import org.whispersystems.libsignal.state.PreKeyRecord;
-import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.KeyHelper;
-
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.Signature;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -71,6 +37,35 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportIn
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
import im.conversations.android.xmpp.model.stanza.Iq;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.InvalidKeyIdException;
+import org.whispersystems.libsignal.SessionBuilder;
+import org.whispersystems.libsignal.SignalProtocolAddress;
+import org.whispersystems.libsignal.UntrustedIdentityException;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+import org.whispersystems.libsignal.state.PreKeyBundle;
+import org.whispersystems.libsignal.state.PreKeyRecord;
+import org.whispersystems.libsignal.state.SignedPreKeyRecord;
+import org.whispersystems.libsignal.util.KeyHelper;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@@ -102,8 +97,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false;
private int lastDeviceListNotificationHash = 0;
- private final Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
- private final Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
+ private final Set<XmppAxolotlSession> postponedSessions =
+ new HashSet<>(); // sessions stored here will receive after mam catchup treatment
+ private final Set<SignalProtocolAddress> postponedHealing =
+ new HashSet<>(); // addresses stored here will need a healing notification after mam
+ // catchup
private final AtomicBoolean changeAccessMode = new AtomicBoolean(false);
public AxolotlService(Account account, XmppConnectionService connectionService) {
@@ -156,7 +154,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
for (Jid jid : jids) {
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
+ SignalProtocolAddress address =
+ new SignalProtocolAddress(jid.toString(), foreignId);
if (fetchStatusMap.getAll(address.getName()).containsValue(FetchStatus.ERROR)) {
return true;
}
@@ -167,11 +166,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void preVerifyFingerprint(Contact contact, String fingerprint) {
- axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().asBareJid().toString(), fingerprint);
+ axolotlStore.preVerifyFingerprint(
+ contact.getAccount(), contact.getJid().asBareJid().toString(), fingerprint);
}
public void preVerifyFingerprint(Account account, String fingerprint) {
- axolotlStore.preVerifyFingerprint(account, account.getJid().asBareJid().toString(), fingerprint);
+ axolotlStore.preVerifyFingerprint(
+ account, account.getJid().asBareJid().toString(), fingerprint);
}
public boolean hasVerifiedKeys(String name) {
@@ -184,11 +185,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public String getOwnFingerprint() {
- return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
+ return CryptoHelper.bytesToHex(
+ axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
}
public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
- return axolotlStore.getContactKeysWithTrust(account.getJid().asBareJid().toString(), status);
+ return axolotlStore.getContactKeysWithTrust(
+ account.getJid().asBareJid().toString(), status);
}
public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
@@ -226,21 +229,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Collection<XmppAxolotlSession> findOwnSessions() {
SignalProtocolAddress ownAddress = getAddressForJid(account.getJid().asBareJid());
- ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress.getName()).values());
+ ArrayList<XmppAxolotlSession> s =
+ new ArrayList<>(this.sessions.getAll(ownAddress.getName()).values());
Collections.sort(s);
return s;
}
public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
- ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
+ ArrayList<XmppAxolotlSession> s =
+ new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
Collections.sort(s);
return s;
}
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
if (conversation.getContact().isSelf()) {
- //will be added in findOwnSessions()
+ // will be added in findOwnSessions()
return Collections.emptySet();
}
HashSet<XmppAxolotlSession> sessions = new HashSet<>();
@@ -280,7 +285,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void destroy() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": destroying old axolotl service. no longer in use");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": destroying old axolotl service. no longer in use");
mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
}
@@ -306,7 +314,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final boolean me = jid.asBareJid().equals(account.getJid().asBareJid());
if (me) {
if (hash != 0 && hash == this.lastDeviceListNotificationHash) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring duplicate own device id list");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": ignoring duplicate own device id list");
return;
}
this.lastDeviceListNotificationHash = hash;
@@ -315,10 +325,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (me) {
deviceIds.remove(getOwnDeviceId());
}
- final Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.asBareJid().toString()));
+ final Set<Integer> expiredDevices =
+ new HashSet<>(axolotlStore.getSubDeviceSessions(jid.asBareJid().toString()));
expiredDevices.removeAll(deviceIds);
for (Integer deviceId : expiredDevices) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
+ SignalProtocolAddress address =
+ new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null) {
if (session.getTrust().isActive()) {
@@ -328,11 +340,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
final Set<Integer> newDevices = ImmutableSet.copyOf(deviceIds);
for (final Integer deviceId : newDevices) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
+ SignalProtocolAddress address =
+ new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null) {
if (!session.getTrust().isActive()) {
- Log.d(Config.LOGTAG, "reactivating device with fingerprint " + session.getFingerprint());
+ Log.d(
+ Config.LOGTAG,
+ "reactivating device with fingerprint " + session.getFingerprint());
session.setTrust(session.getTrust().toActive());
}
}
@@ -343,7 +358,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
needsPublishing |= this.changeAccessMode.get();
for (final Integer deviceId : deviceIds) {
- SignalProtocolAddress ownDeviceAddress = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
+ SignalProtocolAddress ownDeviceAddress =
+ new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
if (sessions.get(ownDeviceAddress) == null) {
FetchStatus status = fetchStatusMap.get(ownDeviceAddress);
if (status == null || status == FetchStatus.TIMEOUT) {
@@ -363,7 +379,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final boolean changed = oldSet == null || oldSet.hashCode() != hash;
this.deviceIds.put(jid, deviceIds);
if (changed) {
- mXmppConnectionService.updateConversationUi(); //update the lock icon
+ mXmppConnectionService.updateConversationUi(); // update the lock icon
mXmppConnectionService.keyStatusUpdated(null);
if (me) {
mXmppConnectionService.updateAccountUi();
@@ -375,7 +391,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void wipeOtherPepDevices() {
if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
return;
}
Set<Integer> deviceIds = new HashSet<>();
@@ -391,22 +410,39 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void publishOwnDeviceIdIfNeeded() {
if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
return;
}
- Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, response -> {
- if (response.getType() == Iq.Type.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
- final Element item = IqParser.getItem(response);
- final Set<Integer> deviceIds = IqParser.deviceIds(item);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
- registerDevices(account.getJid().asBareJid(), deviceIds);
- }
-
- });
+ Iq packet =
+ mXmppConnectionService
+ .getIqGenerator()
+ .retrieveDeviceIds(account.getJid().asBareJid());
+ mXmppConnectionService.sendIqPacket(
+ account,
+ packet,
+ response -> {
+ if (response.getType() == Iq.Type.TIMEOUT) {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Timeout received while retrieving own Device Ids.");
+ } else {
+ // TODO consider calling registerDevices only after item-not-found to
+ // account for broken PEPs
+ // TODO use new API
+ final Element item = IqParser.getItem(response);
+ final Set<Integer> deviceIds = IqParser.deviceIds(item);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": retrieved own device list: "
+ + deviceIds);
+ registerDevices(account.getJid().asBareJid(), deviceIds);
+ }
+ });
}
private Set<Integer> getExpiredDevices() {
@@ -415,16 +451,34 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (session.getTrust().isActive()) {
long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
if (diff > Config.OMEMO_AUTO_EXPIRY) {
- long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account, session.getFingerprint());
+ long lastMessageDiff =
+ System.currentTimeMillis()
+ - mXmppConnectionService.databaseBackend
+ .getLastTimeFingerprintUsed(
+ account, session.getFingerprint());
long hours = Math.round(lastMessageDiff / (1000 * 60.0 * 60.0));
if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
devices.add(session.getRemoteAddress().getDeviceId());
session.setTrust(session.getTrust().toInactive());
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added own device " + session.getFingerprint() + " to list of expired devices. Last message received " + hours + " hours ago");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": added own device "
+ + session.getFingerprint()
+ + " to list of expired devices. Last message received "
+ + hours
+ + " hours ago");
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": own device " + session.getFingerprint() + " was active " + hours + " hours ago");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": own device "
+ + session.getFingerprint()
+ + " was active "
+ + hours
+ + " hours ago");
}
- } //TODO print last activation diff
+ } // TODO print last activation diff
}
}
return devices;
@@ -435,12 +489,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "publishing own device ids");
if (deviceIdsCopy.isEmpty()) {
if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
+ Log.w(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Own device publish attempt threshold exceeded, aborting...");
pepBroken = true;
return;
} else {
numPublishTriesOnEmptyPep++;
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
+ Log.w(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Own device list empty, attempting to publish (try "
+ + numPublishTriesOnEmptyPep
+ + ")");
}
} else {
numPublishTriesOnEmptyPep = 0;
@@ -453,74 +515,136 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
publishDeviceIdsAndRefineAccessModel(ids, true);
}
- private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
- final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
- final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
- mXmppConnectionService.sendIqPacket(account, publish, response -> {
- final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
- final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
- if (firstAttempt && preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": 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);
+ private void publishDeviceIdsAndRefineAccessModel(
+ final Set<Integer> ids, final boolean firstAttempt) {
+ final Bundle publishOptions =
+ account.getXmppConnection().getFeatures().pepPublishOptions()
+ ? PublishOptions.openAccess()
+ : null;
+ final var publish =
+ mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
+ mXmppConnectionService.sendIqPacket(
+ account,
+ publish,
+ response -> {
+ final Element error =
+ response.getType() == Iq.Type.ERROR
+ ? response.findChild("error")
+ : null;
+ final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
+ if (firstAttempt && preConditionNotMet) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": 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 {
+ if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": done changing access mode");
+ account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ }
+ if (response.getType() == Iq.Type.ERROR) {
+ if (preConditionNotMet) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": device list pre condition still not met on"
+ + " second attempt");
+ } else if (error != null) {
+ pepBroken = true;
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Error received while publishing own device id"
+ + response.findChild("error"));
+ }
+ }
}
});
- } else {
- if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode");
- account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
- mXmppConnectionService.databaseBackend.updateAccount(account);
- }
- if (response.getType() == Iq.Type.ERROR) {
- if (preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
- } else if (error != null) {
- pepBroken = true;
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + response.findChild("error"));
- }
-
- }
- }
- });
}
- public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
+ public void publishDeviceVerificationAndBundle(
+ final SignedPreKeyRecord signedPreKeyRecord,
+ final Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
try {
IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
- PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
- X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
+ PrivateKey x509PrivateKey =
+ KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
+ X509Certificate[] chain =
+ KeyChain.getCertificateChain(
+ mXmppConnectionService, account.getPrivateKeyAlias());
Signature verifier = Signature.getInstance("sha256WithRSA");
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign();
- final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, response -> {
- String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
-
- @Override
- public void onPushFailed() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- });
+ final Iq packet =
+ mXmppConnectionService
+ .getIqGenerator()
+ .publishVerification(signature, chain, getOwnDeviceId());
+ Log.d(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + ": publish verification for device "
+ + getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(
+ account,
+ packet,
+ response -> {
+ String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
+ mXmppConnectionService.pushNodeConfiguration(
+ account,
+ node,
+ PublishOptions.openAccess(),
+ new XmppConnectionService.OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "configured verification node to be world"
+ + " readable");
+ publishDeviceBundle(
+ signedPreKeyRecord,
+ preKeyRecords,
+ announceAfter,
+ wipe);
+ }
+
+ @Override
+ public void onPushFailed() {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "unable to set access model on"
+ + " verification node");
+ publishDeviceBundle(
+ signedPreKeyRecord,
+ preKeyRecords,
+ announceAfter,
+ wipe);
+ }
+ });
+ });
} catch (Exception e) {
e.printStackTrace();
}
@@ -528,175 +652,310 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
return;
}
if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
- this.changeAccessMode.set(account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
+ this.changeAccessMode.set(
+ account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
} else {
if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server doesnβt support publish-options. setting for later access mode change");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": server doesnβt support publish-options. setting for later"
+ + " access mode change");
mXmppConnectionService.databaseBackend.updateAccount(account);
}
}
if (this.changeAccessMode.get()) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
- }
- final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, response -> {
-
- if (response.getType() == Iq.Type.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
-
- if (response.getType() == Iq.Type.ERROR) {
- Element error = response.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + response);
- return;
- }
- }
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": server gained publish-options capabilities. changing access"
+ + " model");
+ }
+ final Iq packet =
+ mXmppConnectionService
+ .getIqGenerator()
+ .retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(
+ account,
+ packet,
+ response -> {
+ if (response.getType() == Iq.Type.TIMEOUT) {
+ return; // ignore timeout. do nothing
+ }
- PreKeyBundle bundle = IqParser.bundle(response);
- final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + response);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + response);
- }
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
+ if (response.getType() == Iq.Type.ERROR) {
+ Element error = response.findChild("error");
+ if (error == null || !error.hasChild("item-not-found")) {
+ pepBroken = true;
+ Log.w(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "request for device bundles came back with something"
+ + " other than item-not-found"
+ + response);
+ return;
+ }
+ }
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
+ PreKeyBundle bundle = IqParser.bundle(response);
+ final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
+ boolean flush = false;
+ if (bundle == null) {
+ Log.w(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Received invalid bundle:"
+ + response);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
+ flush = true;
}
- } catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
+ if (keys == null) {
+ Log.w(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Received invalid prekeys:"
+ + response);
+ }
+ try {
+ boolean changed = false;
+ // Validate IdentityKey
+ IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
+ if (flush
+ || !identityKeyPair
+ .getPublicKey()
+ .equals(bundle.getIdentityKey())) {
+ Log.i(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Adding own IdentityKey "
+ + identityKeyPair.getPublicKey()
+ + " to PEP.");
+ changed = true;
+ }
- // Validate PreKeys
- Set<PreKeyRecord> preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
+ // Validate signedPreKeyRecord + ID
+ SignedPreKeyRecord signedPreKeyRecord;
+ int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
+ signedPreKeyRecord =
+ axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
+ if (flush
+ || !bundle.getSignedPreKey()
+ .equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ || !Arrays.equals(
+ bundle.getSignedPreKeySignature(),
+ signedPreKeyRecord.getSignature())) {
+ Log.i(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Adding new signedPreKey with ID "
+ + (numSignedPreKeys + 1)
+ + " to PEP.");
+ signedPreKeyRecord =
+ KeyHelper.generateSignedPreKey(
+ identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(
+ signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
}
- } catch (InvalidKeyIdException ignored) {
+ } catch (InvalidKeyIdException e) {
+ Log.i(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Adding new signedPreKey with ID "
+ + (numSignedPreKeys + 1)
+ + " to PEP.");
+ signedPreKeyRecord =
+ KeyHelper.generateSignedPreKey(
+ identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(
+ signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
}
- }
- }
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
- }
+ // Validate PreKeys
+ Set<PreKeyRecord> preKeyRecords = new HashSet<>();
+ if (keys != null) {
+ for (Integer id : keys.keySet()) {
+ try {
+ PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
+ if (preKeyRecord
+ .getKeyPair()
+ .getPublicKey()
+ .equals(keys.get(id))) {
+ preKeyRecords.add(preKeyRecord);
+ }
+ } catch (InvalidKeyIdException ignored) {
+ }
+ }
+ }
+ int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
+ if (newKeys > 0) {
+ List<PreKeyRecord> newRecords =
+ KeyHelper.generatePreKeys(
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
+ preKeyRecords.addAll(newRecords);
+ for (PreKeyRecord record : newRecords) {
+ axolotlStore.storePreKey(record.getId(), record);
+ }
+ changed = true;
+ Log.i(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Adding "
+ + newKeys
+ + " new preKeys to PEP.");
+ }
- if (changed || changeAccessMode.get()) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
+ if (changed || changeAccessMode.get()) {
+ if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
+ mXmppConnectionService.publishDisplayName(account);
+ publishDeviceVerificationAndBundle(
+ signedPreKeyRecord, preKeyRecords, announce, wipe);
+ } else {
+ publishDeviceBundle(
+ signedPreKeyRecord, preKeyRecords, announce, wipe);
+ }
+ } else {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Bundle "
+ + getOwnDeviceId()
+ + " in PEP was current");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announce) {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Announcing device "
+ + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ }
+ } catch (InvalidKeyException e) {
+ Log.e(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Failed to publish bundle "
+ + getOwnDeviceId()
+ + ", reason: "
+ + e.getMessage());
}
- }
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- }
- });
+ });
}
- private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
- Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
+ private void publishDeviceBundle(
+ SignedPreKeyRecord signedPreKeyRecord,
+ 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;
- final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles(
- signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
- preKeyRecords, getOwnDeviceId(), publishOptions);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
- mXmppConnectionService.sendIqPacket(account, publish, response -> {
- final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
- if (firstAttempt && preconditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
- final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
- }
-
- @Override
- public void onPushFailed() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
+ 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;
+ final Iq publish =
+ mXmppConnectionService
+ .getIqGenerator()
+ .publishBundles(
+ signedPreKeyRecord,
+ axolotlStore.getIdentityKeyPair().getPublicKey(),
+ preKeyRecords,
+ getOwnDeviceId(),
+ publishOptions);
+ Log.d(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + ": Bundle "
+ + getOwnDeviceId()
+ + " in PEP not current. Publishing...");
+ mXmppConnectionService.sendIqPacket(
+ account,
+ publish,
+ response -> {
+ final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
+ if (firstAttempt && preconditionNotMet) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": precondition wasn't met for bundle. pushing node"
+ + " configuration");
+ final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
+ mXmppConnectionService.pushNodeConfiguration(
+ account,
+ node,
+ publishOptions,
+ new XmppConnectionService.OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ publishDeviceBundle(
+ signedPreKeyRecord,
+ preKeyRecords,
+ announceAfter,
+ wipe,
+ false);
+ }
+
+ @Override
+ public void onPushFailed() {
+ publishDeviceBundle(
+ signedPreKeyRecord,
+ preKeyRecords,
+ announceAfter,
+ wipe,
+ false);
+ }
+ });
+ } else if (response.getType() == Iq.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 (response.getType() == Iq.Type.ERROR) {
+ if (preconditionNotMet) {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "bundle precondition still not met after second"
+ + " attempt");
+ } else {
+ Log.d(
+ Config.LOGTAG,
+ getLogprefix(account)
+ + "Error received while publishing bundle: "
+ + response.toString());
+ }
+ pepBroken = true;
}
});
- } else if (response.getType() == Iq.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 (response.getType() == Iq.Type.ERROR) {
- if (preconditionNotMet) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + response.toString());
- }
- pepBroken = true;
- }
- });
}
public void deleteOmemoIdentity() {
@@ -2,6 +2,7 @@ package eu.siacs.conversations.parser;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import eu.siacs.conversations.AppSettings;
@@ -37,15 +38,23 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.pep.Avatar;
import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.avatar.Metadata;
+import im.conversations.android.xmpp.model.axolotl.DeviceList;
import im.conversations.android.xmpp.model.axolotl.Encrypted;
+import im.conversations.android.xmpp.model.bookmark.Storage;
+import im.conversations.android.xmpp.model.bookmark2.Conference;
import im.conversations.android.xmpp.model.carbons.Received;
import im.conversations.android.xmpp.model.carbons.Sent;
import im.conversations.android.xmpp.model.correction.Replace;
import im.conversations.android.xmpp.model.forward.Forwarded;
import im.conversations.android.xmpp.model.markers.Displayed;
+import im.conversations.android.xmpp.model.nick.Nick;
import im.conversations.android.xmpp.model.occupant.OccupantId;
import im.conversations.android.xmpp.model.oob.OutOfBandData;
+import im.conversations.android.xmpp.model.pubsub.Items;
+import im.conversations.android.xmpp.model.pubsub.event.Delete;
import im.conversations.android.xmpp.model.pubsub.event.Event;
+import im.conversations.android.xmpp.model.pubsub.event.Purge;
import im.conversations.android.xmpp.model.reactions.Reactions;
import im.conversations.android.xmpp.model.receipts.Request;
import im.conversations.android.xmpp.model.unique.StanzaId;
@@ -53,6 +62,7 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -244,11 +254,13 @@ public class MessageParser extends AbstractParser
return null;
}
- private void parseEvent(final Event event, final Jid from, final Account account) {
- final Element items = event.findChild("items");
- final String node = items == null ? null : items.getAttribute("node");
+ private void parseEvent(final Items items, final Jid from, final Account account) {
+ final String node = items.getNode();
if ("urn:xmpp:avatar:metadata".equals(node)) {
- Avatar avatar = Avatar.parseMetadata(items);
+ // TODO support retract
+ final var entry = items.getFirstItemWithId(Metadata.class);
+ final var avatar =
+ entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue());
if (avatar != null) {
avatar.owner = from.asBareJid();
if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
@@ -274,24 +286,27 @@ public class MessageParser extends AbstractParser
}
}
} else if (Namespace.NICK.equals(node)) {
- final Element i = items.findChild("item");
- final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
+ final var nickItem = items.getFirstItem(Nick.class);
+ final String nick = nickItem == null ? null : nickItem.getContent();
if (nick != null) {
setNick(account, from, nick);
}
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
- Element item = items.findChild("item");
- final Set<Integer> deviceIds = IqParser.deviceIds(item);
- Log.d(
- Config.LOGTAG,
- AxolotlService.getLogprefix(account)
- + "Received PEP device list "
- + deviceIds
- + " update from "
- + from
- + ", processing... ");
- final AxolotlService axolotlService = account.getAxolotlService();
- axolotlService.registerDevices(from, deviceIds);
+ final var deviceList = items.getFirstItem(DeviceList.class);
+ if (deviceList != null) {
+ final Set<Integer> deviceIds = deviceList.getDeviceIds();
+ Log.d(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(account)
+ + "Received PEP device list "
+ + deviceIds
+ + " update from "
+ + from
+ + ", processing... ");
+ final AxolotlService axolotlService = account.getAxolotlService();
+ axolotlService.registerDevices(from, new HashSet<>(deviceIds));
+ }
+
} else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
final var connection = account.getXmppConnection();
if (connection.getFeatures().bookmarksConversion()) {
@@ -302,9 +317,7 @@ public class MessageParser extends AbstractParser
+ ": received storage:bookmark notification even though we"
+ " opted into bookmarks:1");
}
- final Element i = items.findChild("item");
- final Element storage =
- i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
+ final var storage = items.getFirstItem(Storage.class);
final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
Log.d(
@@ -318,17 +331,19 @@ public class MessageParser extends AbstractParser
+ " not detected");
}
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
- final Element item = items.findChild("item");
- final Element retract = items.findChild("retract");
- if (item != null) {
- final Bookmark bookmark = Bookmark.parseFromItem(item, account);
- if (bookmark != null) {
- account.putBookmark(bookmark);
- mXmppConnectionService.processModifiedBookmark(bookmark);
- mXmppConnectionService.updateConversationUi();
+ final var retractions = items.getRetractions();
+ ;
+ for (final var item : items.getItemMap(Conference.class).entrySet()) {
+ final Bookmark bookmark =
+ Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
+ if (bookmark == null) {
+ continue;
}
+ account.putBookmark(bookmark);
+ mXmppConnectionService.processModifiedBookmark(bookmark);
+ mXmppConnectionService.updateConversationUi();
}
- if (retract != null) {
+ for (final var retract : retractions) {
final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id"));
if (id != null) {
account.removeBookmark(id);
@@ -342,35 +357,37 @@ public class MessageParser extends AbstractParser
} else if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION
&& Namespace.MDS_DISPLAYED.equals(node)
&& account.getJid().asBareJid().equals(from)) {
- final Element item = items.findChild("item");
- mXmppConnectionService.processMdsItem(account, item);
- } else {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + " received pubsub notification for node="
- + node);
+ for (final var item :
+ items.getItemMap(im.conversations.android.xmpp.model.mds.Displayed.class)
+ .entrySet()) {
+ mXmppConnectionService.processMdsItem(account, item);
+ }
}
}
- private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
- final Element delete = event.findChild("delete");
- final String node = delete == null ? null : delete.getAttribute("node");
+ private void parseDeleteEvent(final Delete delete, final Jid from, final Account account) {
+ final String node = delete.getNode();
if (Namespace.NICK.equals(node)) {
- Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
setNick(account, from, null);
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
deleteAllBookmarks(account);
- } else if (Namespace.AVATAR_METADATA.equals(node)
- && account.getJid().asBareJid().equals(from)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted avatar metadata node");
+ } else if (Namespace.AVATAR_METADATA.equals(node)) {
+ final boolean isAccount = account.getJid().asBareJid().equals(from);
+ if (isAccount) {
+ account.setAvatar(null);
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ mXmppConnectionService.getAvatarService().clear(account);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": deleted avatar metadata node");
+ }
}
}
- private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
- final Element purge = event.findChild("purge");
- final String node = purge == null ? null : purge.getAttribute("node");
+ private void parsePurgeEvent(
+ @NonNull final Purge purge, final Jid from, final Account account) {
+ final String node = purge.getNode();
if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
deleteAllBookmarks(account);
@@ -1394,12 +1411,20 @@ public class MessageParser extends AbstractParser
final var event = original.getExtension(Event.class);
if (event != null && Jid.Invalid.hasValidFrom(original) && original.getFrom().isBareJid()) {
- if (event.hasChild("items")) {
- parseEvent(event, original.getFrom(), account);
- } else if (event.hasChild("delete")) {
- parseDeleteEvent(event, original.getFrom(), account);
- } else if (event.hasChild("purge")) {
- parsePurgeEvent(event, original.getFrom(), account);
+ final var action = event.getAction();
+ final var node = action == null ? null : action.getNode();
+ if (node == null) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": no node found in PubSub event from "
+ + original.getFrom());
+ } else if (action instanceof Items items) {
+ parseEvent(items, original.getFrom(), account);
+ } else if (action instanceof Purge purge) {
+ parsePurgeEvent(purge, original.getFrom(), account);
+ } else if (action instanceof Delete delete) {
+ parseDeleteEvent(delete, from, account);
}
}