disable offline messages. postpone prekey handling until after mam catchup

This commit is contained in:
Daniel Gultsch 2018-01-19 18:17:13 +01:00
parent 036dd82698
commit 6009b8ebf0
7 changed files with 156 additions and 85 deletions

View file

@ -55,6 +55,7 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@ -77,11 +78,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private final Map<Jid, Set<Integer>> deviceIds;
private final Map<String, XmppAxolotlMessage> messageCache;
private final FetchStatusMap fetchStatusMap;
private final HashMap<Jid,List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
private final SerialSingleThreadExecutor executor;
private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false;
private int lastDeviceListNotificationHash = 0;
private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
private AtomicBoolean changeAccessMode = new AtomicBoolean(false);
@ -92,12 +94,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
&& account.getXmppConnection().getFeatures().pep()) {
publishBundlesIfNeeded(true, false);
} else {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": skipping OMEMO initialization");
}
}
public boolean fetchMapHasErrors(List<Jid> jids) {
for(Jid jid : jids) {
for (Jid jid : jids) {
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
SignalProtocolAddress address = new SignalProtocolAddress(jid.toPreppedString(), foreignId);
@ -119,7 +121,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public boolean hasVerifiedKeys(String name) {
for(XmppAxolotlSession session : this.sessions.getAll(name).values()) {
for (XmppAxolotlSession session : this.sessions.getAll(name).values()) {
if (session.getTrust().isVerified()) {
return true;
}
@ -194,14 +196,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
for (Integer deviceId : deviceIds) {
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if(Config.X509_VERIFICATION) {
if (Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
if (certificate != null) {
Bundle information = CryptoHelper.extractCertificateInformation(certificate);
try {
final String cn = information.getString("subject_cn");
final Jid jid = Jid.fromString(bareJid);
Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
account.getRoster().getContact(jid).setCommonName(cn);
} catch (final InvalidJidException ignored) {
//ignored
@ -215,7 +217,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void fillMap(SQLiteAxolotlStore store) {
List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString());
putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
for (String address : store.getKnownAddresses()) {
for (String address : store.getKnownAddresses()) {
deviceIds = store.getSubDeviceSessions(address);
putDevicesForJid(address, deviceIds, store);
}
@ -249,9 +251,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (devices == null) {
return;
}
for(Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
if (entry.getValue() == FetchStatus.ERROR) {
Log.d(Config.LOGTAG,"resetting error for "+jid.toBareJid()+"("+entry.getKey()+")");
Log.d(Config.LOGTAG, "resetting error for " + jid.toBareJid() + "(" + entry.getKey() + ")");
entry.setValue(FetchStatus.TIMEOUT);
}
}
@ -294,7 +296,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
Set<IdentityKey> keys = new HashSet<>();
for(Jid jid : jids) {
for (Jid jid : jids) {
keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status));
}
return keys;
@ -305,7 +307,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
for(Jid jid : jids) {
for (Jid jid : jids) {
if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) {
return true;
}
@ -325,7 +327,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
@ -335,7 +336,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
HashSet<XmppAxolotlSession> sessions = new HashSet<>();
for(Jid jid : conversation.getAcceptedCryptoTargets()) {
for (Jid jid : conversation.getAcceptedCryptoTargets()) {
sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
}
return sessions;
@ -368,13 +369,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void destroy() {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": destroying old axolotl service. no longer in use");
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": destroying old axolotl service. no longer in use");
mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
}
public AxolotlService makeNew() {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": make new axolotl service");
return new AxolotlService(this.account,this.mXmppConnectionService);
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": make new axolotl service");
return new AxolotlService(this.account, this.mXmppConnectionService);
}
public int getOwnDeviceId() {
@ -382,7 +383,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public SignalProtocolAddress getOwnAxolotlAddress() {
return new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(),getOwnDeviceId());
return new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(), getOwnDeviceId());
}
public Set<Integer> getOwnDeviceIds() {
@ -420,7 +421,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
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());
}
}
@ -462,7 +463,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void distrustFingerprint(final String fingerprint) {
final String fp = fingerprint.replaceAll("\\s", "");
final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
axolotlStore.setFingerprintStatus(fp,fingerprintStatus.toUntrusted());
axolotlStore.setFingerprintStatus(fp, fingerprintStatus.toUntrusted());
}
public void publishOwnDeviceIdIfNeeded() {
@ -479,8 +480,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else {
Element item = mXmppConnectionService.getIqParser().getItem(packet);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved own device list: "+deviceIds);
registerDevices(account.getJid().toBareJid(),deviceIds);
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": retrieved own device list: " + deviceIds);
registerDevices(account.getJid().toBareJid(), deviceIds);
}
}
});
@ -488,18 +489,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private Set<Integer> getExpiredDevices() {
Set<Integer> devices = new HashSet<>();
for(XmppAxolotlSession session : findOwnSessions()) {
for (XmppAxolotlSession session : findOwnSessions()) {
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 hours = Math.round(lastMessageDiff/(1000*60.0*60.0));
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().toBareJid()+": added own device " + session.getFingerprint() + " to list of expired devices. Last message received "+hours+" hours ago");
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": added own device " + session.getFingerprint() + " to list of expired devices. Last message received " + hours + " hours ago");
} else {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": own device "+session.getFingerprint()+" was active "+hours+" hours ago");
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": own device " + session.getFingerprint() + " was active " + hours + " hours ago");
}
}
}
@ -527,7 +528,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void publishDeviceIdsAndRefineAccessModel(Set<Integer> ids) {
publishDeviceIdsAndRefineAccessModel(ids,true);
publishDeviceIdsAndRefineAccessModel(ids, true);
}
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
@ -537,8 +538,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
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");
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() {
@ -551,9 +552,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
});
} else {
if (AxolotlService.this.changeAccessMode.compareAndSet(true,false)) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": done changing access mode");
account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE,false);
if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done changing access mode");
account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
mXmppConnectionService.databaseBackend.updateAccount(account);
}
if (packet.getType() == IqPacket.TYPE.ERROR) {
@ -566,39 +567,39 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
final Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter,
final boolean wipe) {
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());
Signature verifier = Signature.getInstance("sha256WithRSA");
verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG());
verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign();
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, IqPacket packet) {
String node = AxolotlService.PEP_VERIFICATION+":"+getOwnDeviceId();
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");
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");
Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
}
});
}
});
} catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
}
}
@ -612,7 +613,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
this.changeAccessMode.set(account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
} else {
if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE,true)) {
if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server doesnt support publish-options. setting for later access mode change");
mXmppConnectionService.databaseBackend.updateAccount(account);
}
@ -728,38 +729,38 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter,
final boolean wipe) {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords,announceAfter,wipe,true);
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 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(),publishOptions);
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) {
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");
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, new XmppConnectionService.OnConfigurationPushed() {
@Override
public void onPushSucceeded() {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
}
@Override
public void onPushFailed() {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
}
});
} else if (packet.getType() == IqPacket.TYPE.RESULT) {
@ -790,16 +791,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().nonanonymous() && conversation.getMucOptions().membersOnly());
}
public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
public Pair<AxolotlCapability, Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
if (conversation.getMode() == Conversation.MODE_SINGLE
|| (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
final List<Jid> jids = getCryptoTargets(conversation);
for(Jid jid : jids) {
for (Jid jid : jids) {
if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
return new Pair<>(AxolotlCapability.MISSING_KEYS,jid);
return new Pair<>(AxolotlCapability.MISSING_KEYS, jid);
} else {
return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid);
return new Pair<>(AxolotlCapability.MISSING_PRESENCE, jid);
}
}
}
@ -845,7 +846,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
if (verification != null) {
try {
Signature verifier = Signature.getInstance("sha256WithRSA");
@ -855,7 +856,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
try {
mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
String fingerprint = session.getFingerprint();
Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
@ -863,7 +864,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
try {
final String cn = information.getString("subject_cn");
final Jid jid = Jid.fromString(address.getName());
Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
account.getRoster().getContact(jid).setCommonName(cn);
} catch (final InvalidJidException ignored) {
//ignored
@ -871,14 +872,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
finishBuildingSessionsFromPEP(address);
return;
} catch (Exception e) {
Log.d(Config.LOGTAG,"could not verify certificate");
Log.d(Config.LOGTAG, "could not verify certificate");
}
}
} catch (Exception e) {
Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
}
} else {
Log.d(Config.LOGTAG,"no verification found");
Log.d(Config.LOGTAG, "no verification found");
}
fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
@ -938,7 +939,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void fetchDeviceIds(final Jid jid) {
fetchDeviceIds(jid,null);
fetchDeviceIds(jid, null);
}
public void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
@ -948,14 +949,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) {
callbacks.add(callback);
}
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching device ids for "+jid+" already running. adding callback");
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching device ids for " + jid + " already running. adding callback");
} else {
callbacks = new ArrayList<>();
if (callback != null) {
callbacks.add(callback);
}
this.fetchDeviceIdsMap.put(jid,callbacks);
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching device ids for " + jid);
this.fetchDeviceIdsMap.put(jid, callbacks);
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching device ids for " + jid);
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
@ -967,14 +968,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
registerDevices(jid, deviceIds);
if (callbacks != null) {
for(OnDeviceIdsFetched callback : callbacks) {
for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, deviceIds);
}
}
} else {
Log.d(Config.LOGTAG, packet.toString());
if (callbacks != null) {
for(OnDeviceIdsFetched callback : callbacks) {
for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, null);
}
}
@ -1086,7 +1087,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
Set<SignalProtocolAddress> addresses = new HashSet<>();
for(Jid jid : getCryptoTargets(conversation)) {
for (Jid jid : getCryptoTargets(conversation)) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
@ -1126,7 +1127,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
addresses.add(address);
} else {
Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
}
}
}
@ -1138,13 +1139,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public boolean createSessionsIfNeeded(final Conversation conversation) {
final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
for(Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext();) {
for (Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
final Jid jid = iterator.next();
if (!hasEmptyDeviceList(jid)) {
iterator.remove();
}
}
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": createSessionsIfNeeded() - jids with empty device list: "+jidsWithEmptyDeviceList);
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList);
if (jidsWithEmptyDeviceList.size() > 0) {
fetchDeviceIds(jidsWithEmptyDeviceList, new OnMultipleDeviceIdFetched() {
@Override
@ -1183,7 +1184,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
sessions.addAll(findOwnSessions());
boolean verified = false;
for(XmppAxolotlSession session : sessions) {
for (XmppAxolotlSession session : sessions) {
if (session.getTrust().isTrustedAndActive()) {
if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
verified = true;
@ -1243,7 +1244,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
return null;
}
if (!buildHeader(axolotlMessage,message.getConversation())) {
//TODO: fix this for MUC PMs - Don't encrypt to all participants
if (!buildHeader(axolotlMessage, message.getConversation())) {
return null;
}
@ -1272,7 +1274,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@Override
public void run() {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId());
if (buildHeader(axolotlMessage,conversation)) {
if (buildHeader(axolotlMessage, conversation)) {
onMessageCreatedCallback.run(axolotlMessage);
} else {
onMessageCreatedCallback.run(null);
@ -1324,7 +1326,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
}
} catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from "+message.getFrom()+": " + e.getMessage());
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom() + ": " + e.getMessage());
}
if (session.isFresh() && plaintextMessage != null) {
@ -1335,11 +1337,39 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void postPreKeyMessageHandling(final XmppAxolotlSession session, int preKeyId, final boolean postpone) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": postPreKeyMessageHandling() preKeyId="+preKeyId+", postpone="+Boolean.toString(postpone));
//TODO: do not republish if we already removed this preKeyId
publishBundlesIfNeeded(false, false);
if (postpone) {
postponedSessions.add(session);
} else {
//TODO: do not republish if we already removed this preKeyId
publishBundlesIfNeeded(false, false);
completeSession(session);
}
}
public void processPostponed() {
if (postponedSessions.size() > 0) {
publishBundlesIfNeeded(false, false);
}
Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
while (iterator.hasNext()) {
completeSession(iterator.next());
iterator.remove();
}
}
private void completeSession(XmppAxolotlSession session) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId());
axolotlMessage.addDevice(session);
try {
Jid jid = Jid.fromString(session.getRemoteAddress().getName());
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
mXmppConnectionService.sendMessagePacket(account, packet);
} catch (InvalidJidException e) {
throw new Error("Remote addresses are created from jid and should convert back to jid", e);
}
}
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
@ -1351,7 +1381,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
}
} catch (CryptoFailedException e) {
Log.d(Config.LOGTAG,"could not decrypt keyTransport message "+e.getMessage());
Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
keyTransportMessage = null;
}
@ -1363,13 +1393,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void putFreshSession(XmppAxolotlSession session) {
Log.d(Config.LOGTAG,"put fresh session");
Log.d(Config.LOGTAG, "put fresh session");
sessions.put(session);
if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) {
verifySessionWithPEP(session);
} else {
Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
Log.e(Config.LOGTAG, account.getJid().toBareJid() + ": identity key was empty after reloading for x509 verification");
}
}
}

View file

@ -45,8 +45,7 @@ public class IqGenerator extends AbstractGenerator {
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
packet.setId(request.getId());
packet.setTo(request.getFrom());
final Element query = packet.addChild("query",
"http://jabber.org/protocol/disco#info");
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
query.setAttribute("node", request.query().getAttribute("node"));
final Element identity = query.addChild("identity");
identity.setAttribute("category", "client");
@ -91,6 +90,12 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket purgeOfflineMessages() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.addChild("offline",Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet;
}
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",

View file

@ -91,6 +91,15 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_CHAT);
packet.setTo(to);
packet.setAxolotlMessage(axolotlMessage.toElement());
packet.addChild("store", "urn:xmpp:hints");
return packet;
}
private static boolean recipientSupportsOmemo(Message message) {
Contact c = message.getContact();
return c != null && c.getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY);

View file

@ -11,6 +11,7 @@ import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.generator.AbstractGenerator;
@ -222,6 +223,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
public boolean inCatchup(Account account) {
synchronized (this.queries) {
for(Query query : queries) {
if (query.account == account && query.isCatchup() && query.getWith() == null) {
return true;
}
}
}
return false;
}
public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) {
synchronized (this.queries) {
for(Query query : queries) {
@ -268,6 +280,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (query.isCatchup() && query.getActualMessageCount() > 0) {
mXmppConnectionService.getNotificationService().finishBacklog(true,query.getAccount());
}
query.account.getAxolotlService().processPostponed();
} else {
final Query nextQuery;
if (query.getPagingOrder() == PagingOrder.NORMAL) {

View file

@ -303,6 +303,15 @@ public class XmppConnectionService extends Service {
mJingleConnectionManager.cancelInTransmission();
fetchRosterFromServer(account);
fetchBookmarks(account);
final boolean flexible= account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
final boolean catchup = getMessageArchiveService().inCatchup(account);
if (flexible && catchup) {
sendIqPacket(account, mIqGenerator.purgeOfflineMessages(), (acc, packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, acc.getJid().toBareJid()+": successfully purged offline messages");
}
});
}
sendPresence(account);
if (mPushManagementService.available(account)) {
mPushManagementService.registerPushTokenOnServer(account);

View file

@ -16,4 +16,5 @@ public final class Namespace {
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";
public static final String NICK = "http://jabber.org/protocol/nick";
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
}

View file

@ -1681,6 +1681,10 @@ public class XmppConnection implements Runnable {
return hasDiscoFeature(account.getServer(), "urn:xmpp:reporting:reason:spam:0");
}
public boolean flexibleOfflineMessageRetrieval() {
return hasDiscoFeature(account.getServer(), Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL);
}
public boolean register() {
return hasDiscoFeature(account.getServer(), Namespace.REGISTER);
}