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.jid.Jid;
import eu.siacs.conversations.xmpp.pep.PublishOptions; import eu.siacs.conversations.xmpp.pep.PublishOptions;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@ -77,11 +78,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private final Map<Jid, Set<Integer>> deviceIds; private final Map<Jid, Set<Integer>> deviceIds;
private final Map<String, XmppAxolotlMessage> messageCache; private final Map<String, XmppAxolotlMessage> messageCache;
private final FetchStatusMap fetchStatusMap; 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 final SerialSingleThreadExecutor executor;
private int numPublishTriesOnEmptyPep = 0; private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false; private boolean pepBroken = false;
private int lastDeviceListNotificationHash = 0; 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); private AtomicBoolean changeAccessMode = new AtomicBoolean(false);
@ -92,12 +94,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
&& account.getXmppConnection().getFeatures().pep()) { && account.getXmppConnection().getFeatures().pep()) {
publishBundlesIfNeeded(true, false); publishBundlesIfNeeded(true, false);
} else { } 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) { public boolean fetchMapHasErrors(List<Jid> jids) {
for(Jid jid : jids) { for (Jid jid : jids) {
if (deviceIds.get(jid) != null) { if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) { for (Integer foreignId : this.deviceIds.get(jid)) {
SignalProtocolAddress address = new SignalProtocolAddress(jid.toPreppedString(), foreignId); SignalProtocolAddress address = new SignalProtocolAddress(jid.toPreppedString(), foreignId);
@ -119,7 +121,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public boolean hasVerifiedKeys(String name) { 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()) { if (session.getTrust().isVerified()) {
return true; return true;
} }
@ -194,14 +196,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
for (Integer deviceId : deviceIds) { for (Integer deviceId : deviceIds) {
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId); SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if(Config.X509_VERIFICATION) { if (Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize())); X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
if (certificate != null) { if (certificate != null) {
Bundle information = CryptoHelper.extractCertificateInformation(certificate); Bundle information = CryptoHelper.extractCertificateInformation(certificate);
try { try {
final String cn = information.getString("subject_cn"); final String cn = information.getString("subject_cn");
final Jid jid = Jid.fromString(bareJid); 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); account.getRoster().getContact(jid).setCommonName(cn);
} catch (final InvalidJidException ignored) { } catch (final InvalidJidException ignored) {
//ignored //ignored
@ -215,7 +217,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void fillMap(SQLiteAxolotlStore store) { private void fillMap(SQLiteAxolotlStore store) {
List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString()); List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString());
putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store); putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
for (String address : store.getKnownAddresses()) { for (String address : store.getKnownAddresses()) {
deviceIds = store.getSubDeviceSessions(address); deviceIds = store.getSubDeviceSessions(address);
putDevicesForJid(address, deviceIds, store); putDevicesForJid(address, deviceIds, store);
} }
@ -249,9 +251,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (devices == null) { if (devices == null) {
return; return;
} }
for(Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) { for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
if (entry.getValue() == FetchStatus.ERROR) { 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); entry.setValue(FetchStatus.TIMEOUT);
} }
} }
@ -294,7 +296,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) { public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
Set<IdentityKey> keys = new HashSet<>(); Set<IdentityKey> keys = new HashSet<>();
for(Jid jid : jids) { for (Jid jid : jids) {
keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status)); keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status));
} }
return keys; return keys;
@ -305,7 +307,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) { public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
for(Jid jid : jids) { for (Jid jid : jids) {
if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) { if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) {
return true; return true;
} }
@ -325,7 +327,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) { public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid()); 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());
@ -335,7 +336,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) { private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
HashSet<XmppAxolotlSession> sessions = new HashSet<>(); HashSet<XmppAxolotlSession> sessions = new HashSet<>();
for(Jid jid : conversation.getAcceptedCryptoTargets()) { for (Jid jid : conversation.getAcceptedCryptoTargets()) {
sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values()); sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
} }
return sessions; return sessions;
@ -368,13 +369,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public void destroy() { 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); mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
} }
public AxolotlService makeNew() { public AxolotlService makeNew() {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": make new axolotl service"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": make new axolotl service");
return new AxolotlService(this.account,this.mXmppConnectionService); return new AxolotlService(this.account, this.mXmppConnectionService);
} }
public int getOwnDeviceId() { public int getOwnDeviceId() {
@ -382,7 +383,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public SignalProtocolAddress getOwnAxolotlAddress() { public SignalProtocolAddress getOwnAxolotlAddress() {
return new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(),getOwnDeviceId()); return new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(), getOwnDeviceId());
} }
public Set<Integer> getOwnDeviceIds() { public Set<Integer> getOwnDeviceIds() {
@ -420,7 +421,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
XmppAxolotlSession session = sessions.get(address); XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null) { if (session != null && session.getFingerprint() != null) {
if (!session.getTrust().isActive()) { 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()); session.setTrust(session.getTrust().toActive());
} }
} }
@ -462,7 +463,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void distrustFingerprint(final String fingerprint) { public void distrustFingerprint(final String fingerprint) {
final String fp = fingerprint.replaceAll("\\s", ""); final String fp = fingerprint.replaceAll("\\s", "");
final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp); final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
axolotlStore.setFingerprintStatus(fp,fingerprintStatus.toUntrusted()); axolotlStore.setFingerprintStatus(fp, fingerprintStatus.toUntrusted());
} }
public void publishOwnDeviceIdIfNeeded() { public void publishOwnDeviceIdIfNeeded() {
@ -479,8 +480,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else { } else {
Element item = mXmppConnectionService.getIqParser().getItem(packet); Element item = mXmppConnectionService.getIqParser().getItem(packet);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved own device list: "+deviceIds); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": retrieved own device list: " + deviceIds);
registerDevices(account.getJid().toBareJid(),deviceIds); registerDevices(account.getJid().toBareJid(), deviceIds);
} }
} }
}); });
@ -488,18 +489,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private Set<Integer> getExpiredDevices() { private Set<Integer> getExpiredDevices() {
Set<Integer> devices = new HashSet<>(); Set<Integer> devices = new HashSet<>();
for(XmppAxolotlSession session : findOwnSessions()) { for (XmppAxolotlSession session : findOwnSessions()) {
if (session.getTrust().isActive()) { if (session.getTrust().isActive()) {
long diff = System.currentTimeMillis() - session.getTrust().getLastActivation(); long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
if (diff > Config.OMEMO_AUTO_EXPIRY) { 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)); long hours = Math.round(lastMessageDiff / (1000 * 60.0 * 60.0));
if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) { if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
devices.add(session.getRemoteAddress().getDeviceId()); devices.add(session.getRemoteAddress().getDeviceId());
session.setTrust(session.getTrust().toInactive()); 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 { } 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) { private void publishDeviceIdsAndRefineAccessModel(Set<Integer> ids) {
publishDeviceIdsAndRefineAccessModel(ids,true); publishDeviceIdsAndRefineAccessModel(ids, true);
} }
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) { private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
@ -537,8 +538,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket packet) { public void onIqPacketReceived(Account account, IqPacket packet) {
Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
if (firstAttempt && error != null && error.hasChild("precondition-not-met",Namespace.PUBSUB_ERROR)) { 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"); 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() { mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
@Override @Override
public void onPushSucceeded() { public void onPushSucceeded() {
@ -551,9 +552,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
}); });
} else { } else {
if (AxolotlService.this.changeAccessMode.compareAndSet(true,false)) { if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": done changing access mode"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done changing access mode");
account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE,false); account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
mXmppConnectionService.databaseBackend.updateAccount(account); mXmppConnectionService.databaseBackend.updateAccount(account);
} }
if (packet.getType() == IqPacket.TYPE.ERROR) { if (packet.getType() == IqPacket.TYPE.ERROR) {
@ -566,39 +567,39 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord, public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
final Set<PreKeyRecord> preKeyRecords, final Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter, final boolean announceAfter,
final boolean wipe) { final boolean wipe) {
try { try {
IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey(); IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias()); PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias()); X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
Signature verifier = Signature.getInstance("sha256WithRSA"); Signature verifier = Signature.getInstance("sha256WithRSA");
verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG()); verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG());
verifier.update(axolotlPublicKey.serialize()); verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign(); byte[] signature = verifier.sign();
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); 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() { mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(final Account account, IqPacket packet) { 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() { mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
@Override @Override
public void onPushSucceeded() { 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); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
} }
@Override @Override
public void onPushFailed() { 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); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
} }
}); });
} }
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -612,7 +613,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (account.getXmppConnection().getFeatures().pepPublishOptions()) { 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 { } 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"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server doesnt support publish-options. setting for later access mode change");
mXmppConnectionService.databaseBackend.updateAccount(account); mXmppConnectionService.databaseBackend.updateAccount(account);
} }
@ -728,38 +729,38 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord, private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
Set<PreKeyRecord> preKeyRecords, Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter, final boolean announceAfter,
final boolean wipe) { final boolean wipe) {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords,announceAfter,wipe,true); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, true);
} }
private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord, private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord,
final Set<PreKeyRecord> preKeyRecords, final Set<PreKeyRecord> preKeyRecords,
final boolean announceAfter, final boolean announceAfter,
final boolean wipe, final boolean wipe,
final boolean firstAttempt) { final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
preKeyRecords, getOwnDeviceId(),publishOptions); preKeyRecords, getOwnDeviceId(), publishOptions);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(final Account account, IqPacket packet) { public void onIqPacketReceived(final Account account, IqPacket packet) {
Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null; Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
if (firstAttempt && error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR)) { 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(); final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() { mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
@Override @Override
public void onPushSucceeded() { public void onPushSucceeded() {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
} }
@Override @Override
public void onPushFailed() { public void onPushFailed() {
publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
} }
}); });
} else if (packet.getType() == IqPacket.TYPE.RESULT) { } 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()); 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 if (conversation.getMode() == Conversation.MODE_SINGLE
|| (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) { || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
final List<Jid> jids = getCryptoTargets(conversation); 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 (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) { if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
return new Pair<>(AxolotlCapability.MISSING_KEYS,jid); return new Pair<>(AxolotlCapability.MISSING_KEYS, jid);
} else { } 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() { mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override @Override
public void onIqPacketReceived(Account account, IqPacket packet) { 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) { if (verification != null) {
try { try {
Signature verifier = Signature.getInstance("sha256WithRSA"); Signature verifier = Signature.getInstance("sha256WithRSA");
@ -855,7 +856,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
try { try {
mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
String fingerprint = session.getFingerprint(); 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)); setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
@ -863,7 +864,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
try { try {
final String cn = information.getString("subject_cn"); final String cn = information.getString("subject_cn");
final Jid jid = Jid.fromString(address.getName()); 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); account.getRoster().getContact(jid).setCommonName(cn);
} catch (final InvalidJidException ignored) { } catch (final InvalidJidException ignored) {
//ignored //ignored
@ -871,14 +872,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
finishBuildingSessionsFromPEP(address); finishBuildingSessionsFromPEP(address);
return; return;
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG,"could not verify certificate"); Log.d(Config.LOGTAG, "could not verify certificate");
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
} }
} else { } else {
Log.d(Config.LOGTAG,"no verification found"); Log.d(Config.LOGTAG, "no verification found");
} }
fetchStatusMap.put(address, FetchStatus.SUCCESS); fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address); finishBuildingSessionsFromPEP(address);
@ -938,7 +939,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public void fetchDeviceIds(final Jid jid) { public void fetchDeviceIds(final Jid jid) {
fetchDeviceIds(jid,null); fetchDeviceIds(jid, null);
} }
public void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { public void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
@ -948,14 +949,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callbacks.add(callback); 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 { } else {
callbacks = new ArrayList<>(); callbacks = new ArrayList<>();
if (callback != null) { if (callback != null) {
callbacks.add(callback); callbacks.add(callback);
} }
this.fetchDeviceIdsMap.put(jid,callbacks); this.fetchDeviceIdsMap.put(jid, callbacks);
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching device ids for " + jid); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching device ids for " + jid);
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid); IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override @Override
@ -967,14 +968,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
registerDevices(jid, deviceIds); registerDevices(jid, deviceIds);
if (callbacks != null) { if (callbacks != null) {
for(OnDeviceIdsFetched callback : callbacks) { for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, deviceIds); callback.fetched(jid, deviceIds);
} }
} }
} else { } else {
Log.d(Config.LOGTAG, packet.toString()); Log.d(Config.LOGTAG, packet.toString());
if (callbacks != null) { if (callbacks != null) {
for(OnDeviceIdsFetched callback : callbacks) { for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, null); callback.fetched(jid, null);
} }
} }
@ -1086,7 +1087,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) { public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
Set<SignalProtocolAddress> addresses = new HashSet<>(); 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); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
if (deviceIds.get(jid) != null) { if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) { for (Integer foreignId : this.deviceIds.get(jid)) {
@ -1126,7 +1127,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (fetchStatusMap.get(address) != FetchStatus.ERROR) { if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
addresses.add(address); addresses.add(address);
} else { } 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) { public boolean createSessionsIfNeeded(final Conversation conversation) {
final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(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(); final Jid jid = iterator.next();
if (!hasEmptyDeviceList(jid)) { if (!hasEmptyDeviceList(jid)) {
iterator.remove(); 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) { if (jidsWithEmptyDeviceList.size() > 0) {
fetchDeviceIds(jidsWithEmptyDeviceList, new OnMultipleDeviceIdFetched() { fetchDeviceIds(jidsWithEmptyDeviceList, new OnMultipleDeviceIdFetched() {
@Override @Override
@ -1183,7 +1184,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation); Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
sessions.addAll(findOwnSessions()); sessions.addAll(findOwnSessions());
boolean verified = false; boolean verified = false;
for(XmppAxolotlSession session : sessions) { for (XmppAxolotlSession session : sessions) {
if (session.getTrust().isTrustedAndActive()) { if (session.getTrust().isTrustedAndActive()) {
if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
verified = true; verified = true;
@ -1243,7 +1244,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
return null; 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; return null;
} }
@ -1272,7 +1274,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@Override @Override
public void run() { public void run() {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId()); final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId());
if (buildHeader(axolotlMessage,conversation)) { if (buildHeader(axolotlMessage, conversation)) {
onMessageCreatedCallback.run(axolotlMessage); onMessageCreatedCallback.run(axolotlMessage);
} else { } else {
onMessageCreatedCallback.run(null); onMessageCreatedCallback.run(null);
@ -1324,7 +1326,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling); postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
} }
} catch (CryptoFailedException e) { } 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) { 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) { 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)); if (postpone) {
//TODO: do not republish if we already removed this preKeyId postponedSessions.add(session);
publishBundlesIfNeeded(false, false); } 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) { public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
@ -1351,7 +1381,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling); postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
} }
} catch (CryptoFailedException e) { } 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; keyTransportMessage = null;
} }
@ -1363,13 +1393,13 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
private void putFreshSession(XmppAxolotlSession session) { private void putFreshSession(XmppAxolotlSession session) {
Log.d(Config.LOGTAG,"put fresh session"); Log.d(Config.LOGTAG, "put fresh session");
sessions.put(session); sessions.put(session);
if (Config.X509_VERIFICATION) { if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) { if (session.getIdentityKey() != null) {
verifySessionWithPEP(session); verifySessionWithPEP(session);
} else { } 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); final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
packet.setId(request.getId()); packet.setId(request.getId());
packet.setTo(request.getFrom()); packet.setTo(request.getFrom());
final Element query = packet.addChild("query", final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
"http://jabber.org/protocol/disco#info");
query.setAttribute("node", request.query().getAttribute("node")); query.setAttribute("node", request.query().getAttribute("node"));
final Element identity = query.addChild("identity"); final Element identity = query.addChild("identity");
identity.setAttribute("category", "client"); identity.setAttribute("category", "client");
@ -91,6 +90,12 @@ public class IqGenerator extends AbstractGenerator {
return packet; 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) { protected IqPacket publish(final String node, final Element item, final Bundle options) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub", final Element pubsub = packet.addChild("pubsub",

View file

@ -91,6 +91,15 @@ public class MessageGenerator extends AbstractGenerator {
return packet; 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) { private static boolean recipientSupportsOmemo(Message message) {
Contact c = message.getContact(); Contact c = message.getContact();
return c != null && c.getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY); 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.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.generator.AbstractGenerator; 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) { public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) {
synchronized (this.queries) { synchronized (this.queries) {
for(Query query : queries) { for(Query query : queries) {
@ -268,6 +280,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (query.isCatchup() && query.getActualMessageCount() > 0) { if (query.isCatchup() && query.getActualMessageCount() > 0) {
mXmppConnectionService.getNotificationService().finishBacklog(true,query.getAccount()); mXmppConnectionService.getNotificationService().finishBacklog(true,query.getAccount());
} }
query.account.getAxolotlService().processPostponed();
} else { } else {
final Query nextQuery; final Query nextQuery;
if (query.getPagingOrder() == PagingOrder.NORMAL) { if (query.getPagingOrder() == PagingOrder.NORMAL) {

View file

@ -303,6 +303,15 @@ public class XmppConnectionService extends Service {
mJingleConnectionManager.cancelInTransmission(); mJingleConnectionManager.cancelInTransmission();
fetchRosterFromServer(account); fetchRosterFromServer(account);
fetchBookmarks(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); sendPresence(account);
if (mPushManagementService.available(account)) { if (mPushManagementService.available(account)) {
mPushManagementService.registerPushTokenOnServer(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_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 PUBSUB_ERROR = "http://jabber.org/protocol/pubsub#errors";
public static final String NICK = "http://jabber.org/protocol/nick"; 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"); 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() { public boolean register() {
return hasDiscoFeature(account.getServer(), Namespace.REGISTER); return hasDiscoFeature(account.getServer(), Namespace.REGISTER);
} }