implement self healing omemo
after receiving a SignalMessage that can’t be decrypted because of broken sessions Conversations will attempt to grab a new pre key bundle and send a new PreKeySignalMessage wrapped in a key transport message.
This commit is contained in:
		
							parent
							
								
									f1e1c4a78d
								
							
						
					
					
						commit
						c02676ea4c
					
				|  | @ -82,9 +82,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 	private final SerialSingleThreadExecutor executor; | ||||
| 	private int numPublishTriesOnEmptyPep = 0; | ||||
| 	private boolean pepBroken = false; | ||||
| 	private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>(); | ||||
| 	private int lastDeviceListNotificationHash = 0; | ||||
| 	private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>(); | ||||
| 	private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment | ||||
| 	private Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup | ||||
| 
 | ||||
| 	private AtomicBoolean changeAccessMode = new AtomicBoolean(false); | ||||
| 
 | ||||
|  | @ -390,6 +392,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 		this.pepBroken = false; | ||||
| 		this.numPublishTriesOnEmptyPep = 0; | ||||
| 		this.lastDeviceListNotificationHash = 0; | ||||
| 		this.healingAttempts.clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	public void clearErrorsInFetchStatusMap(Jid jid) { | ||||
|  | @ -1071,7 +1074,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	interface OnSessionBuildFromPep { | ||||
| 		void onSessionBuildSuccessful(); | ||||
| 		void onSessionBuildFailed(); | ||||
| 	} | ||||
| 
 | ||||
| 	private void buildSessionFromPEP(final SignalProtocolAddress address) { | ||||
| 		buildSessionFromPEP(address, null); | ||||
| 	} | ||||
| 
 | ||||
| 	private void buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) { | ||||
| 		Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString()); | ||||
| 		if (address.equals(getOwnAxolotlAddress())) { | ||||
| 			throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); | ||||
|  | @ -1092,6 +1104,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 					Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); | ||||
| 					fetchStatusMap.put(address, FetchStatus.ERROR); | ||||
| 					finishBuildingSessionsFromPEP(address); | ||||
| 					if (callback != null) { | ||||
| 						callback.onSessionBuildFailed(); | ||||
| 					} | ||||
| 					return; | ||||
| 				} | ||||
| 				Random random = new Random(); | ||||
|  | @ -1100,6 +1115,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 					//should never happen | ||||
| 					fetchStatusMap.put(address, FetchStatus.ERROR); | ||||
| 					finishBuildingSessionsFromPEP(address); | ||||
| 					if (callback != null) { | ||||
| 						callback.onSessionBuildFailed(); | ||||
| 					} | ||||
| 					return; | ||||
| 				} | ||||
| 
 | ||||
|  | @ -1114,7 +1132,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 					XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); | ||||
| 					sessions.put(address, session); | ||||
| 					if (Config.X509_VERIFICATION) { | ||||
| 						verifySessionWithPEP(session); | ||||
| 						verifySessionWithPEP(session); //TODO; maybe inject callback in here too | ||||
| 					} else { | ||||
| 						FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize())); | ||||
| 						FetchStatus fetchStatus; | ||||
|  | @ -1127,6 +1145,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 						} | ||||
| 						fetchStatusMap.put(address, fetchStatus); | ||||
| 						finishBuildingSessionsFromPEP(address); | ||||
| 						if (callback != null) { | ||||
| 							callback.onSessionBuildSuccessful(); | ||||
| 						} | ||||
| 					} | ||||
| 				} catch (UntrustedIdentityException | InvalidKeyException e) { | ||||
| 					Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " | ||||
|  | @ -1136,6 +1157,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 					if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) { | ||||
| 						removeFromDeviceAnnouncement(address.getDeviceId()); | ||||
| 					} | ||||
| 					if (callback != null) { | ||||
| 						callback.onSessionBuildFailed(); | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				fetchStatusMap.put(address, FetchStatus.ERROR); | ||||
|  | @ -1146,6 +1170,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 				if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) { | ||||
| 					removeFromDeviceAnnouncement(address.getDeviceId()); | ||||
| 				} | ||||
| 				if (callback != null) { | ||||
| 					callback.onSessionBuildFailed(); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | @ -1391,11 +1418,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 	} | ||||
| 
 | ||||
| 	private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { | ||||
| 		SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), | ||||
| 				message.getSenderDeviceId()); | ||||
| 		SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId()); | ||||
| 		return getReceivingSession(senderAddress); | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) { | ||||
| 		XmppAxolotlSession session = sessions.get(senderAddress); | ||||
| 		if (session == null) { | ||||
| 			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); | ||||
| 			//Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); | ||||
| 			session = recreateUncachedSession(senderAddress); | ||||
| 			if (session == null) { | ||||
| 				session = new XmppAxolotlSession(account, axolotlStore, senderAddress); | ||||
|  | @ -1404,7 +1435,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 		return session; | ||||
| 	} | ||||
| 
 | ||||
| 	public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException { | ||||
| 	public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException { | ||||
| 		XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; | ||||
| 
 | ||||
| 		XmppAxolotlSession session = getReceivingSession(message); | ||||
|  | @ -1421,8 +1452,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 			} else { | ||||
| 				throw e; | ||||
| 			} | ||||
| 		} catch (final BrokenSessionException e) { | ||||
| 			throw 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); | ||||
| 		} | ||||
| 
 | ||||
| 		if (session.isFresh() && plaintextMessage != null) { | ||||
|  | @ -1432,6 +1465,35 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 		return plaintextMessage; | ||||
| 	} | ||||
| 
 | ||||
| 	public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) { | ||||
| 		Log.e(Config.LOGTAG,account.getJid().asBareJid()+": broken session with "+e.getSignalProtocolAddress().toString()+" detected", e); | ||||
| 		if (postpone) { | ||||
| 			postponedHealing.add(e.getSignalProtocolAddress()); | ||||
| 		} else { | ||||
| 			notifyRequiresHealing(e.getSignalProtocolAddress()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) { | ||||
| 		if (healingAttempts.add(signalProtocolAddress)) { | ||||
| 			Log.d(Config.LOGTAG,account.getJid().asBareJid()+": attempt to heal "+signalProtocolAddress); | ||||
| 			buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() { | ||||
| 				@Override | ||||
| 				public void onSessionBuildSuccessful() { | ||||
| 					Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session"); | ||||
| 					completeSession(getReceivingSession(signalProtocolAddress)); | ||||
| 				} | ||||
| 
 | ||||
| 				@Override | ||||
| 				public void onSessionBuildFailed() { | ||||
| 					Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session"); | ||||
| 				} | ||||
| 			}); | ||||
| 		} else { | ||||
| 			Log.d(Config.LOGTAG,account.getJid().asBareJid()+": do not attempt to heal "+signalProtocolAddress+" again"); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private void postPreKeyMessageHandling(final XmppAxolotlSession session, int preKeyId, final boolean postpone) { | ||||
| 		if (postpone) { | ||||
| 			postponedSessions.add(session); | ||||
|  | @ -1451,6 +1513,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { | |||
| 			completeSession(iterator.next()); | ||||
| 			iterator.remove(); | ||||
| 		} | ||||
| 		Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator(); | ||||
| 		while (postponedHealingAttemptsIterator.hasNext()) { | ||||
| 			notifyRequiresHealing(postponedHealingAttemptsIterator.next()); | ||||
| 			postponedHealingAttemptsIterator.remove(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private void completeSession(XmppAxolotlSession session) { | ||||
|  |  | |||
|  | @ -0,0 +1,18 @@ | |||
| package eu.siacs.conversations.crypto.axolotl; | ||||
| 
 | ||||
| import org.whispersystems.libsignal.SignalProtocolAddress; | ||||
| 
 | ||||
| public class BrokenSessionException extends CryptoFailedException { | ||||
| 
 | ||||
|     private final SignalProtocolAddress signalProtocolAddress; | ||||
| 
 | ||||
|     public BrokenSessionException(SignalProtocolAddress address, Exception e) { | ||||
|         super(e); | ||||
|         this.signalProtocolAddress = address; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public SignalProtocolAddress getSignalProtocolAddress() { | ||||
|         return signalProtocolAddress; | ||||
|     } | ||||
| } | ||||
|  | @ -6,6 +6,10 @@ public class CryptoFailedException extends Exception { | |||
| 		super(msg); | ||||
| 	} | ||||
| 
 | ||||
| 	public CryptoFailedException(String msg, Exception e) { | ||||
| 		super(msg, e); | ||||
| 	} | ||||
| 
 | ||||
| 	public CryptoFailedException(Exception e){ | ||||
| 		super(e); | ||||
| 	} | ||||
|  |  | |||
|  | @ -79,34 +79,35 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> { | |||
| 	} | ||||
| 
 | ||||
| 	@Nullable | ||||
| 	public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException { | ||||
| 	byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException { | ||||
| 		byte[] plaintext; | ||||
| 		FingerprintStatus status = getTrust(); | ||||
| 		if (!status.isCompromised()) { | ||||
| 			try { | ||||
| 				if (encryptedKey.prekey) { | ||||
| 					PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key); | ||||
| 					Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId(); | ||||
| 					IdentityKey identityKey = preKeySignalMessage.getIdentityKey(); | ||||
| 					if (!optionalPreKeyId.isPresent()) { | ||||
| 						throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId"); | ||||
| 					} | ||||
| 					preKeyId = optionalPreKeyId.get(); | ||||
| 					if (this.identityKey != null && !this.identityKey.equals(identityKey)) { | ||||
| 						throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed."); | ||||
| 					} | ||||
| 					this.identityKey = identityKey; | ||||
| 					plaintext = cipher.decrypt(preKeySignalMessage); | ||||
| 				} else { | ||||
| 					SignalMessage signalMessage = new SignalMessage(encryptedKey.key); | ||||
| 					plaintext = cipher.decrypt(signalMessage); | ||||
| 					preKeyId = null; //better safe than sorry because we use that to do special after prekey handling | ||||
| 				} | ||||
| 			} catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) { | ||||
| 				if (!(e instanceof DuplicateMessageException)) { | ||||
| 					e.printStackTrace(); | ||||
| 				} | ||||
| 				throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage()); | ||||
|                 if (encryptedKey.prekey) { | ||||
|                     PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key); | ||||
|                     Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId(); | ||||
|                     IdentityKey identityKey = preKeySignalMessage.getIdentityKey(); | ||||
|                     if (!optionalPreKeyId.isPresent()) { | ||||
|                         throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId"); | ||||
|                     } | ||||
|                     preKeyId = optionalPreKeyId.get(); | ||||
|                     if (this.identityKey != null && !this.identityKey.equals(identityKey)) { | ||||
|                         throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed."); | ||||
|                     } | ||||
|                     this.identityKey = identityKey; | ||||
|                     plaintext = cipher.decrypt(preKeySignalMessage); | ||||
|                 } else { | ||||
|                     SignalMessage signalMessage = new SignalMessage(encryptedKey.key); | ||||
|                     try { | ||||
|                         plaintext = cipher.decrypt(signalMessage); | ||||
|                     } catch (InvalidMessageException | NoSessionException e) { | ||||
|                         throw new BrokenSessionException(this.remoteAddress, e); | ||||
|                     } | ||||
|                     preKeyId = null; //better safe than sorry because we use that to do special after prekey handling | ||||
|                 } | ||||
| 			} catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | InvalidKeyIdException | UntrustedIdentityException e) { | ||||
| 				throw new CryptoFailedException("Error decrypting SignalMessage", e); | ||||
| 			} | ||||
| 			if (!status.isActive()) { | ||||
| 				setTrust(status.toActive()); | ||||
|  |  | |||
|  | @ -702,6 +702,20 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public boolean possibleDuplicate(final String serverMsgId, final String remoteMsgId) { | ||||
| 		if (serverMsgId == null || remoteMsgId == null) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		synchronized (this.messages) { | ||||
| 			for(Message message : this.messages) { | ||||
| 				if (serverMsgId.equals(message.getServerMsgId()) || remoteMsgId.equals(message.getRemoteMsgId())) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	public MamReference getLastMessageTransmitted() { | ||||
| 		final MamReference lastClear = getLastClearHistory(); | ||||
| 		MamReference lastReceived = new MamReference(0); | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ public class Message extends AbstractEntity { | |||
| 	public static final int ENCRYPTION_DECRYPTION_FAILED = 4; | ||||
| 	public static final int ENCRYPTION_AXOLOTL = 5; | ||||
| 	public static final int ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE = 6; | ||||
| 	public static final int ENCRYPTION_AXOLOTL_FAILED = 7; | ||||
| 
 | ||||
| 	public static final int TYPE_TEXT = 0; | ||||
| 	public static final int TYPE_IMAGE = 1; | ||||
|  | @ -883,7 +884,7 @@ public class Message extends AbstractEntity { | |||
| 		if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { | ||||
| 			return ENCRYPTION_PGP; | ||||
| 		} | ||||
| 		if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { | ||||
| 		if (encryption == ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || encryption == ENCRYPTION_AXOLOTL_FAILED) { | ||||
| 			return ENCRYPTION_AXOLOTL; | ||||
| 		} | ||||
| 		return encryption; | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import java.util.UUID; | |||
| import eu.siacs.conversations.Config; | ||||
| import eu.siacs.conversations.R; | ||||
| import eu.siacs.conversations.crypto.axolotl.AxolotlService; | ||||
| import eu.siacs.conversations.crypto.axolotl.BrokenSessionException; | ||||
| import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException; | ||||
| import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; | ||||
| import eu.siacs.conversations.entities.Account; | ||||
|  | @ -107,7 +108,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) { | ||||
|     private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean checkedForDuplicates, boolean postpone) { | ||||
|         final AxolotlService service = conversation.getAccount().getAxolotlService(); | ||||
|         final XmppAxolotlMessage xmppAxolotlMessage; | ||||
|         try { | ||||
|  | @ -120,6 +121,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece | |||
|             final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage; | ||||
|             try { | ||||
|                 plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone); | ||||
|             } catch (BrokenSessionException e) { | ||||
|                 if (checkedForDuplicates) { | ||||
|                     service.reportBrokenSessionException(e, postpone); | ||||
|                     return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); | ||||
|                 } else { | ||||
|                     Log.d(Config.LOGTAG,"ignoring broken session exception because checkForDuplicase failed"); | ||||
|                     return null; | ||||
|                 } | ||||
|             } catch (NotEncryptedForThisDeviceException e) { | ||||
|                 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status); | ||||
|             } | ||||
|  | @ -424,12 +433,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece | |||
|                     fallbacksBySourceId = Collections.emptySet(); | ||||
|                     origin = from; | ||||
|                 } | ||||
| 
 | ||||
|                 final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId); | ||||
| 
 | ||||
|                 if (origin != null) { | ||||
|                     message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null); | ||||
|                     message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status,  checkedForDuplicates,query != null); | ||||
|                 } else { | ||||
|                     Message trial = null; | ||||
|                     for (Jid fallback : fallbacksBySourceId) { | ||||
|                         trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, query != null); | ||||
|                         trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null); | ||||
|                         if (trial != null) { | ||||
|                             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback"); | ||||
|                             origin = fallback; | ||||
|  | @ -606,7 +618,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece | |||
| 
 | ||||
|             if (message.getEncryption() == Message.ENCRYPTION_PGP) { | ||||
|                 notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify); | ||||
|             } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { | ||||
|             } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { | ||||
|                 notify = false; | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -751,7 +751,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { | |||
| 
 | ||||
| 	public Cursor getMessageSearchCursor(List<String> term) { | ||||
| 		SQLiteDatabase db = this.getReadableDatabase(); | ||||
| 		String SQL = "SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ? ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS; | ||||
| 		String SQL = "SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ','+Message.ENCRYPTION_AXOLOTL_FAILED+") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ? ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS; | ||||
| 		Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term)); | ||||
| 		return db.rawQuery(SQL, new String[]{FtsUtils.toMatchString(term)}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -1096,7 +1096,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke | |||
|         } | ||||
|         if (m.getType() != Message.TYPE_STATUS) { | ||||
| 
 | ||||
|             if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { | ||||
|             if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || m.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -775,6 +775,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie | |||
| 			displayInfoMessage(viewHolder, activity.getString(R.string.decryption_failed), darkBackground); | ||||
| 		} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { | ||||
| 			displayInfoMessage(viewHolder, activity.getString(R.string.not_encrypted_for_this_device), darkBackground); | ||||
| 		} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { | ||||
| 			displayInfoMessage(viewHolder, activity.getString(R.string.omemo_decryption_failed), darkBackground); | ||||
| 		} else { | ||||
| 			if (message.isGeoUri()) { | ||||
| 				displayLocationMessage(viewHolder, message); | ||||
|  |  | |||
|  | @ -264,6 +264,7 @@ public final class CryptoHelper { | |||
|                 return R.string.encryption_choice_otr; | ||||
|             case Message.ENCRYPTION_AXOLOTL: | ||||
|             case Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE: | ||||
|             case Message.ENCRYPTION_AXOLOTL_FAILED: | ||||
|                 return R.string.encryption_choice_omemo; | ||||
|             case Message.ENCRYPTION_NONE: | ||||
|                 return R.string.encryption_choice_unencrypted; | ||||
|  |  | |||
|  | @ -292,6 +292,8 @@ public class UIHelper { | |||
| 			return new Pair<>(context.getString(R.string.decryption_failed), true); | ||||
| 		} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { | ||||
| 			return new Pair<>(context.getString(R.string.not_encrypted_for_this_device), true); | ||||
| 		} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) { | ||||
| 			return new Pair<>(context.getString(R.string.omemo_decryption_failed), true); | ||||
| 		} else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { | ||||
| 			return new Pair<>(getFileDescriptionString(context, message), true); | ||||
| 		} else { | ||||
|  |  | |||
|  | @ -697,6 +697,7 @@ | |||
|     <string name="medium">Medium</string> | ||||
|     <string name="large">Large</string> | ||||
|     <string name="not_encrypted_for_this_device">Message was not encrypted for this device.</string> | ||||
|     <string name="omemo_decryption_failed">Failed to decrypt OMEMO message.</string> | ||||
|     <string name="undo">undo</string> | ||||
|     <string name="location_disabled">Location sharing is disabled</string> | ||||
|     <string name="action_fix_to_location">Fix position</string> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Daniel Gultsch
						Daniel Gultsch