diff --git a/CHANGELOG.md b/CHANGELOG.md index 274c4d6b..41f2ad6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# Build 242 (2.2.1) +2024-06-10 + +- Fix a multi-profile issue with bookmarked messages + +# ~~Build 241 (2.2.1)~~ +2024-06-06 + +- Display an explanation of the message status in message details +- Fix message count taking too long in global search + +# ~~Build 239 (2.2.1)~~ +2024-06-04 + +- Better handling of messages received out-of-order (typically via WebSocket while a listing is in progress) +- Implement a few tweaks in cryptographic engine +- Some engine db optimizations + # Build 238 (2.2) 2024-06-03 diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/coordinators/ChannelCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/coordinators/ChannelCoordinator.java index 38514a59..a9a9b485 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/coordinators/ChannelCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/coordinators/ChannelCoordinator.java @@ -69,7 +69,7 @@ public void decryptAndProcess(NetworkReceivedMessage networkReceivedMessage) { // we were not able to decrypt the message -> we delete it if (channelManagerSession.networkFetchDelegate != null) { Logger.d("The message cannot be decrypted."); - channelManagerSession.networkFetchDelegate.deleteMessageAndAttachments(channelManagerSession.session, networkReceivedMessage.getOwnedIdentity(), networkReceivedMessage.getMessageUid()); + channelManagerSession.networkFetchDelegate.messageCannotBeDecrypted(channelManagerSession.session, networkReceivedMessage.getOwnedIdentity(), networkReceivedMessage.getMessageUid()); channelManagerSession.session.commit(); } else { Logger.w("Unable to delete a networkReceivedMessage because the NetworkFetchDelegate is not set yet."); @@ -87,7 +87,7 @@ private void decryptAndProcess(ChannelManagerSession channelManagerSession, Netw } ChannelReceivedMessage channelReceivedMessage; try { - channelReceivedMessage = new ChannelReceivedMessage(networkReceivedMessage, authEncKeyAndChannelInfo.getAuthEncKey(), authEncKeyAndChannelInfo.getReceptionChannelInfo()); + channelReceivedMessage = new ChannelReceivedMessage(channelManagerSession, networkReceivedMessage, authEncKeyAndChannelInfo.getAuthEncKey(), authEncKeyAndChannelInfo.getReceptionChannelInfo()); } catch (Exception e) { channelManagerSession.networkFetchDelegate.deleteMessageAndAttachments(channelManagerSession.session, networkReceivedMessage.getOwnedIdentity(), networkReceivedMessage.getMessageUid()); return; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/databases/ObliviousChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/databases/ObliviousChannel.java index c47fc2f5..8697883e 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/databases/ObliviousChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/databases/ObliviousChannel.java @@ -101,6 +101,14 @@ public class ObliviousChannel extends NetworkChannel implements ObvDatabase { private boolean fullRatchetOfTheSendSeedInProgress; static final String FULL_RATCHET_OF_THE_SEND_SEED_IN_PROGRESS = "full_ratchet_of_the_send_seed_in_progress"; + // info for GKMV2 + private boolean supportsGKMV2; + static final String SUPPORTS_GKMV_2 = "supports_gkmv_2"; + private int fullRatchetingCountForGkmv2Support; + static final String FULL_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT = "full_ratcheting_count_with_gkmv_2_support"; + private int selfRatchetingCountForGkmv2Support; + static final String SELF_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT = "self_ratcheting_count_with_gkmv_2_support"; + public UID getCurrentDeviceUid() { return currentDeviceUid; @@ -118,6 +126,12 @@ public ReceptionChannelInfo getReceptionChannelInfo() { return ReceptionChannelInfo.createObliviousChannelInfo(remoteDeviceUid, remoteIdentity); } + private boolean supportsGKMV2(int fullRatchetingCount, int selfRatchetingCount) { + return supportsGKMV2 + && (fullRatchetingCount > fullRatchetingCountForGkmv2Support + || (fullRatchetingCount == fullRatchetingCountForGkmv2Support && selfRatchetingCount > selfRatchetingCountForGkmv2Support)); + } + public int getNumberOfEncryptedMessagesSinceLastFullRatchet() { return numberOfEncryptedMessages - numberOfEncryptedMessagesAtTheTimeOfTheLastFullRatchet; } @@ -203,6 +217,28 @@ public void confirm() throws SQLException { } } + public static void setSupportsGKMV2(ChannelManagerSession channelManagerSession, UID currentDeviceUid, UID remoteDeviceUid, Identity remoteIdentity, int fullRatchetingCount, int selfRatchetingCount) throws SQLException { + ObliviousChannel obliviousChannel = get(channelManagerSession, currentDeviceUid, remoteDeviceUid, remoteIdentity, false); + if (obliviousChannel.supportsGKMV2(fullRatchetingCount, selfRatchetingCount)) { + // the oblivious channel is already tagged as supporting GKMV2 at an older full/self ratcheting count + return; + } + try (PreparedStatement statement = channelManagerSession.session.prepareStatement("UPDATE " + TABLE_NAME + " SET " + + SUPPORTS_GKMV_2 + " = 1, " + + FULL_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT + " = ?," + + SELF_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT + " = ? " + + " WHERE " + CURRENT_DEVICE_UID + " = ? " + + " AND " + REMOTE_DEVICE_UID + " = ? " + + " AND " + REMOTE_IDENTITY + " = ?;")) { + statement.setInt(1, fullRatchetingCount); + statement.setInt(2, selfRatchetingCount); + statement.setBytes(3, currentDeviceUid.getBytes()); + statement.setBytes(4, remoteDeviceUid.getBytes()); + statement.setBytes(5, remoteIdentity.getBytes()); + statement.executeUpdate(); + } + } + // This method is called after a send full ratchet public void updateSendSeed(Seed seed, int obliviousEngineVersion) { Seed sendSeed = generateDiversifiedSeed(seed, currentDeviceUid, obliviousEngineVersion); @@ -326,6 +362,10 @@ private ObliviousChannel(ChannelManagerSession channelManagerSession, this.timestampOfLastFullRatchet = System.currentTimeMillis(); this.timestampOfLastFullRatchetSentMessage = this.timestampOfLastFullRatchet; this.fullRatchetOfTheSendSeedInProgress = false; + this.supportsGKMV2 = false; + + this.fullRatchetingCountForGkmv2Support = -1; + this.selfRatchetingCountForGkmv2Support = -1; } private ObliviousChannel(ChannelManagerSession channelManagerSession, ResultSet res) throws SQLException { @@ -351,6 +391,10 @@ private ObliviousChannel(ChannelManagerSession channelManagerSession, ResultSet this.timestampOfLastFullRatchet = res.getLong(TIMESTAMP_OF_LAST_FULL_RATCHET); this.timestampOfLastFullRatchetSentMessage = res.getLong(TIMESTAMP_OF_LAST_FULL_RATCHET_SENT_MESSAGE); this.fullRatchetOfTheSendSeedInProgress = res.getBoolean(FULL_RATCHET_OF_THE_SEND_SEED_IN_PROGRESS); + this.supportsGKMV2 = res.getBoolean(SUPPORTS_GKMV_2); + + this.fullRatchetingCountForGkmv2Support = res.getInt(FULL_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT); + this.selfRatchetingCountForGkmv2Support = res.getInt(SELF_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT); } @@ -372,16 +416,28 @@ public static void createTable(Session session) throws SQLException { TIMESTAMP_OF_LAST_FULL_RATCHET + " BIGINT NOT NULL, " + TIMESTAMP_OF_LAST_FULL_RATCHET_SENT_MESSAGE + " BIGINT NOT NULL, " + FULL_RATCHET_OF_THE_SEND_SEED_IN_PROGRESS + " BIT NOT NULL, " + + SUPPORTS_GKMV_2 + " BIT NOT NULL, " + + FULL_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT + " INT NOT NULL, " + + SELF_RATCHETING_COUNT_FOR_GKMV_2_SUPPORT + " INT NOT NULL, " + "CONSTRAINT PK_" + TABLE_NAME + " PRIMARY KEY(" + CURRENT_DEVICE_UID + ", " + REMOTE_DEVICE_UID +", " + REMOTE_IDENTITY + "));"); } } public static void upgradeTable(Session session, int oldVersion, int newVersion) throws SQLException { + if (oldVersion < 39 && newVersion >= 39) { + Logger.d("MIGRATING `oblivious_channel` DATABASE FROM VERSION " + oldVersion + " TO 39"); + try (Statement statement = session.createStatement()) { + statement.execute("ALTER TABLE oblivious_channel ADD COLUMN `supports_gkmv_2` BIT NOT NULL DEFAULT 0"); + statement.execute("ALTER TABLE oblivious_channel ADD COLUMN `full_ratcheting_count_with_gkmv_2_support` INT NOT NULL DEFAULT -1"); + statement.execute("ALTER TABLE oblivious_channel ADD COLUMN `self_ratcheting_count_with_gkmv_2_support` INT NOT NULL DEFAULT -1"); + } + oldVersion = 39; + } } @Override public void insert() throws SQLException { - try (PreparedStatement statement = channelManagerSession.session.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?,?);")) { + try (PreparedStatement statement = channelManagerSession.session.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?,?);")) { statement.setBytes(1, currentDeviceUid.getBytes()); statement.setBytes(2, remoteDeviceUid.getBytes()); statement.setBytes(3, remoteIdentity.getBytes()); @@ -398,6 +454,10 @@ public void insert() throws SQLException { statement.setLong(12, timestampOfLastFullRatchet); statement.setLong(13, timestampOfLastFullRatchetSentMessage); statement.setBoolean(14, fullRatchetOfTheSendSeedInProgress); + statement.setBoolean(15, supportsGKMV2); + + statement.setInt(16, fullRatchetingCountForGkmv2Support); + statement.setInt(17, selfRatchetingCountForGkmv2Support); statement.executeUpdate(); } } @@ -430,14 +490,12 @@ public static ObliviousChannel get(ChannelManagerSession channelManagerSession, try (ResultSet res = statement.executeQuery()) { if (res.next()) { return new ObliviousChannel(channelManagerSession, res); - } else { - return null; } } } catch (SQLException e) { e.printStackTrace(); - return null; } + return null; } public static ObliviousChannel[] getAll(ChannelManagerSession channelManagerSession) { @@ -823,7 +881,10 @@ public static AuthEncKeyAndChannelInfo unwrapMessageKey(ChannelManagerSession ch } catch (SQLException e) { e.printStackTrace(); } - return new AuthEncKeyAndChannelInfo(messageKey, obliviousChannel.getReceptionChannelInfo()); + ReceptionChannelInfo receptionChannelInfo = obliviousChannel.getReceptionChannelInfo(); + // add information about GKMV2 in receptionChannelInfo + receptionChannelInfo.enrichWithGKMV2Info(provisionedKey.getProvisionFullRatchetingCount(), provisionedKey.getSelfRatchetingCount(), obliviousChannel.supportsGKMV2(provisionedKey.getProvisionFullRatchetingCount(), provisionedKey.getSelfRatchetingCount())); + return new AuthEncKeyAndChannelInfo(messageKey, receptionChannelInfo); } catch (InvalidKeyException | DecryptionException | DecodingException | ClassCastException e) { // nothing to do } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ChannelReceivedMessage.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ChannelReceivedMessage.java index fc48bbaf..0c2fb0f7 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ChannelReceivedMessage.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ChannelReceivedMessage.java @@ -20,6 +20,8 @@ package io.olvid.engine.channel.datatypes; +import io.olvid.engine.Logger; +import io.olvid.engine.channel.databases.ObliviousChannel; import io.olvid.engine.crypto.AuthEnc; import io.olvid.engine.crypto.PRNG; import io.olvid.engine.crypto.Suite; @@ -39,12 +41,30 @@ public class ChannelReceivedMessage { private final ReceptionChannelInfo receptionChannelInfo; private final NetworkReceivedMessage message; - public ChannelReceivedMessage(NetworkReceivedMessage message, AuthEncKey messageKey, ReceptionChannelInfo receptionChannelInfo) throws Exception { + public ChannelReceivedMessage(ChannelManagerSession channelManagerSession, NetworkReceivedMessage message, AuthEncKey messageKey, ReceptionChannelInfo receptionChannelInfo) throws Exception { try { // decrypt AuthEnc authEnc = Suite.getAuthEnc(messageKey); Encoded decryptedMessage = new Encoded(authEnc.decrypt(messageKey, message.getEncryptedContent())); + // verify the messageKey is properly formatted + boolean messageKeyCheckPassed = authEnc.verifyMessageKey(messageKey, decryptedMessage.getBytes()); + Logger.d("MessageKey check: " + (messageKeyCheckPassed ? "PASSED" : "FAILED")); + if (receptionChannelInfo.getChannelType() == ReceptionChannelInfo.OBLIVIOUS_CHANNEL_TYPE) { + // check the GKMV2 info in receptionChannelInfo + if (receptionChannelInfo.obliviousChannelsSupportsGKMV2() && !messageKeyCheckPassed) { + Logger.e("Received a message not passing the messageKey check on an oblivious channel that supports GKMV2. Discarding it!!!!"); + throw new Exception(); + } else if (messageKeyCheckPassed && !receptionChannelInfo.obliviousChannelsSupportsGKMV2()) { + // received a message that passes the GKMV2 messageKey check --> tag the ObliviousChannel + UID currentDeviceUid = channelManagerSession.identityDelegate.getCurrentDeviceUidOfOwnedIdentity(channelManagerSession.session, message.getHeader().getOwnedIdentity()); + if (currentDeviceUid != null) { + Logger.i("Tagging an oblivious channel as supporting GKMV2"); + ObliviousChannel.setSupportsGKMV2(channelManagerSession, currentDeviceUid, receptionChannelInfo.getRemoteDeviceUid(), receptionChannelInfo.getRemoteIdentity(), receptionChannelInfo.getFullRatchetCount(), receptionChannelInfo.getSelfRatchetCount()); + } + } + } + // if needed, compute the extended payload key if (message.hasExtendedPayload()) { PRNG extendedPayloadPRNG = Suite.getDefaultPRNG(0, Seed.of(messageKey)); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/NetworkChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/NetworkChannel.java index 42cae481..3e099a3a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/NetworkChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/NetworkChannel.java @@ -79,22 +79,6 @@ public static UID post(ChannelManagerSession channelManagerSession, ChannelMessa } AuthEnc authEnc = Suite.getDefaultAuthEnc(suiteVersion); - AuthEncKey messageKey = authEnc.generateKey(prng); - - MessageToSend.Header[] headers = new MessageToSend.Header[networkChannels.length]; - boolean partOfFullRatchetProtocol = (message instanceof ChannelProtocolMessageToSend) && ((ChannelProtocolMessageToSend) message).isPartOfFullRatchetProtocolOfTheSendSeed(); - for (int i=0; i= 0) { + return false; + } EdwardCurvePoint[] points = curve.mulAdd(y, curve.G, e, A); byte[] hashInput = new byte[message.length + 2*l]; System.arraycopy(Encoded.bytesFromBigUInt(publicKey.getAy(), l), 0, hashInput, l, l); System.arraycopy(message, 0, hashInput, 2*l, message.length); - for (EdwardCurvePoint point: points) { System.arraycopy(Encoded.bytesFromBigUInt(point.getY(), l), 0, hashInput, 0, l); - byte[] recomputedHash = new HashSHA256().digest(hashInput); + byte[] recomputedHash = isSha512 ? new HashSHA512().digest(hashInput) : new HashSHA256().digest(hashInput);; if (Arrays.equals(hash, recomputedHash)) { return true; } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/crypto/Suite.java b/obv_engine/engine/src/main/java/io/olvid/engine/crypto/Suite.java index 0216bc9d..0814aea1 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/crypto/Suite.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/crypto/Suite.java @@ -55,6 +55,8 @@ public static AuthEnc getDefaultAuthEnc(int obliviousEngineVersion) { public static Hash getHash(String hashName) { switch (hashName) { + case Hash.SHA512: + return new HashSHA512(); case Hash.SHA256: default: return new HashSHA256(); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/crypto/SymEnc.java b/obv_engine/engine/src/main/java/io/olvid/engine/crypto/SymEnc.java index 1f2c0e13..731b3a76 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/crypto/SymEnc.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/crypto/SymEnc.java @@ -28,6 +28,7 @@ import io.olvid.engine.datatypes.EncryptedBytes; import io.olvid.engine.datatypes.key.symmetric.SymEncCTRAES256Key; +import io.olvid.engine.datatypes.key.symmetric.SymmetricKey; interface SymEnc { String CTR_AES256 = "ctr-aes-256"; @@ -40,6 +41,20 @@ interface SymEnc { byte[] decrypt(EncryptedBytes ciphertext); } +class KDFDelegateForSymEncCtrAES256 implements KDF.Delegate { + @Override + public int getKeyLength() { + return SymEncCTRAES256Key.KEY_BYTE_LENGTH; + } + + @Override + public SymmetricKey[] processBytes(byte[] bytes) { + return new SymmetricKey[]{ + SymEncCTRAES256Key.of(bytes) + }; + } +} + class SymEncCtrAES256 implements SymEnc { private Cipher aes; private SymEncCTRAES256Key key; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Constants.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Constants.java index dd1aa937..b285f988 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Constants.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Constants.java @@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets; public abstract class Constants { - public static final int CURRENT_ENGINE_DB_SCHEMA_VERSION = 38; + public static final int CURRENT_ENGINE_DB_SCHEMA_VERSION = 40; public static final int SERVER_API_VERSION = 17; public static final int CURRENT_BACKUP_JSON_VERSION = 0; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/EdwardCurvePoint.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/EdwardCurvePoint.java index 30ddd264..873fbaf9 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/EdwardCurvePoint.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/EdwardCurvePoint.java @@ -64,6 +64,14 @@ public static EdwardCurvePoint noCheckFactory(BigInteger X, BigInteger Y, Edward return new EdwardCurvePoint(X, Y, curve, true); } + public boolean isLowOrderPoint() { + if (X != null) { + return curve.scalarMultiplicationWithX(curve.nu, this).getY().equals(BigInteger.ONE); + } else { + return curve.scalarMultiplication(curve.nu, Y).equals(BigInteger.ONE); + } + } + public boolean equals(Object o) { if (o instanceof EdwardCurvePoint) { EdwardCurvePoint Q = (EdwardCurvePoint) o; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/ServerMethod.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/ServerMethod.java index 443156e2..920157d5 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/ServerMethod.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/ServerMethod.java @@ -61,6 +61,7 @@ public abstract class ServerMethod { public static final byte GROUP_NOT_LOCKED = 0x15; public static final byte INVALID_API_KEY = 0x16; public static final byte LISTING_TRUNCATED = 0x17; + public static final byte PAYLOAD_TOO_LARGE = (byte) 0x18; public static final byte GENERAL_ERROR = (byte) 0xff; @@ -68,7 +69,6 @@ public abstract class ServerMethod { public static final byte SERVER_CONNECTION_ERROR = (byte) 0x81; public static final byte MALFORMED_SERVER_RESPONSE = (byte) 0x82; public static final byte OK_WITH_MALFORMED_SERVER_RESPONSE = (byte) 0x83; - public static final byte PAYLOAD_TOO_LARGE = (byte) 0x84; public static final byte IDENTITY_IS_NOT_ACTIVE = (byte) 0x8e; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ReceptionChannelInfo.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ReceptionChannelInfo.java index 94a29057..56162d3e 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ReceptionChannelInfo.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ReceptionChannelInfo.java @@ -36,6 +36,13 @@ public class ReceptionChannelInfo { private final UID remoteDeviceUid; private final Identity remoteIdentity; + /////// + // GKMV2 related info --> not serialized + private int fullRatchetCount; + private int selfRatchetCount; + private boolean obliviousChannelsSupportsGKMV2; + + private ReceptionChannelInfo(int channelType) { this(channelType, null, null); } @@ -46,6 +53,13 @@ private ReceptionChannelInfo(int channelType, UID remoteDeviceUid, Identity remo this.remoteIdentity = remoteIdentity; } + // add information about GKMV2 in receptionChannelInfo, this information is not serialized + public void enrichWithGKMV2Info(int fullRatchetCount, int selfRatchetCount, boolean obliviousChannelsSupportsGKMV2) { + this.fullRatchetCount = fullRatchetCount; + this.selfRatchetCount = selfRatchetCount; + this.obliviousChannelsSupportsGKMV2 = obliviousChannelsSupportsGKMV2; + } + public static ReceptionChannelInfo createObliviousChannelInfo(UID remoteDeviceUid, Identity remoteIdentity) { return new ReceptionChannelInfo(OBLIVIOUS_CHANNEL_TYPE, remoteDeviceUid, remoteIdentity); } @@ -126,6 +140,18 @@ public Identity getRemoteIdentity() { return remoteIdentity; } + public boolean obliviousChannelsSupportsGKMV2() { + return obliviousChannelsSupportsGKMV2; + } + + public int getFullRatchetCount() { + return fullRatchetCount; + } + + public int getSelfRatchetCount() { + return selfRatchetCount; + } + @Override public boolean equals(Object other) { if (!(other instanceof ReceptionChannelInfo)) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesCurve25519KeyPair.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesCurve25519KeyPair.java index 2782d5b8..3ec13b15 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesCurve25519KeyPair.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesCurve25519KeyPair.java @@ -48,10 +48,14 @@ public EncryptionEciesCurve25519KeyPair(EncryptionEciesCurve25519PublicKey publi public static EncryptionEciesCurve25519KeyPair generate(PRNG prng) { EdwardCurve curve25519 = Suite.getCurve(EdwardCurve.CURVE_25519); BigInteger a; + EdwardCurvePoint A; + // check we do not generate a low order public key do { - a = prng.bigInt(curve25519.q); - } while (a.equals(BigInteger.ONE) || a.equals(BigInteger.ZERO)); - EdwardCurvePoint A = curve25519.scalarMultiplicationWithX(a, curve25519.G); + do { + a = prng.bigInt(curve25519.q); + } while (a.equals(BigInteger.ZERO) || a.equals(BigInteger.ONE)); + A = curve25519.scalarMultiplicationWithX(a, curve25519.G); + } while (A.isLowOrderPoint()); HashMap publicKeyDictionary = new HashMap<>(); HashMap privateKeyDictionary = new HashMap<>(); try { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesMDCKeyPair.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesMDCKeyPair.java index 80675af1..48854011 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesMDCKeyPair.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionEciesMDCKeyPair.java @@ -49,10 +49,14 @@ public EncryptionEciesMDCKeyPair(EncryptionEciesMDCPublicKey publicKey, Encrypti public static EncryptionEciesMDCKeyPair generate(PRNGService prng) { EdwardCurve mdc = Suite.getCurve(EdwardCurve.MDC); BigInteger a; + EdwardCurvePoint A; + // check we do not generate a low order public key do { - a = prng.bigInt(mdc.q); - } while (a.equals(BigInteger.ONE) || a.equals(BigInteger.ZERO)); - EdwardCurvePoint A = mdc.scalarMultiplicationWithX(a, mdc.G); + do { + a = prng.bigInt(mdc.q); + } while (a.equals(BigInteger.ZERO) || a.equals(BigInteger.ONE)); + A = mdc.scalarMultiplicationWithX(a, mdc.G); + } while (A.isLowOrderPoint()); HashMap publicKeyDictionary = new HashMap<>(); HashMap privateKeyDictionary = new HashMap<>(); try { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionPublicKey.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionPublicKey.java index dbc8c177..73e7f86e 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionPublicKey.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/EncryptionPublicKey.java @@ -22,6 +22,8 @@ import java.security.InvalidParameterException; import java.util.HashMap; +import io.olvid.engine.crypto.EdwardCurve; +import io.olvid.engine.crypto.Suite; import io.olvid.engine.datatypes.DictionaryKey; import io.olvid.engine.datatypes.key.CryptographicKey; import io.olvid.engine.encoder.DecodingException; @@ -70,6 +72,17 @@ public static EncryptionPublicKey of(byte[] compactKeyBytes) throws DecodingExce throw new DecodingException(); } - public abstract byte[] getCompactKey(); + protected EdwardCurve getCurve() { + switch (algorithmImplementation) { + case ALGO_IMPL_KEM_ECIES_MDC_AND_DEM_CTR_AES256_THEN_HMAC_SHA256: { + return Suite.getCurve(EdwardCurve.MDC); + } + case ALGO_IMPL_KEM_ECIES_CURVE25519_AND_DEM_CTR_AES256_THEN_HMAC_SHA256: { + return Suite.getCurve(EdwardCurve.CURVE_25519); + } + } + return null; + } + public abstract byte[] getCompactKey(); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaCurve25519KeyPair.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaCurve25519KeyPair.java index ab35f222..564b80c3 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaCurve25519KeyPair.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaCurve25519KeyPair.java @@ -49,10 +49,14 @@ public ServerAuthenticationECSdsaCurve25519KeyPair(ServerAuthenticationECSdsaCur public static ServerAuthenticationECSdsaCurve25519KeyPair generate(PRNGService prng) { EdwardCurve curve25519 = Suite.getCurve(EdwardCurve.CURVE_25519); BigInteger a; + EdwardCurvePoint A; + // check we do not generate a low order public key do { - a = prng.bigInt(curve25519.q); - } while (a.equals(BigInteger.ONE) || a.equals(BigInteger.ZERO)); - EdwardCurvePoint A = curve25519.scalarMultiplicationWithX(a, curve25519.G); + do { + a = prng.bigInt(curve25519.q); + } while (a.equals(BigInteger.ZERO) || a.equals(BigInteger.ONE)); + A = curve25519.scalarMultiplicationWithX(a, curve25519.G); + } while (A.isLowOrderPoint()); HashMap publicKeyDictionary = new HashMap<>(); HashMap privateKeyDictionary = new HashMap<>(); try { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaMDCKeyPair.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaMDCKeyPair.java index ee172351..5f062785 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaMDCKeyPair.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/ServerAuthenticationECSdsaMDCKeyPair.java @@ -49,10 +49,14 @@ public ServerAuthenticationECSdsaMDCKeyPair(ServerAuthenticationECSdsaMDCPublicK public static ServerAuthenticationECSdsaMDCKeyPair generate(PRNGService prng) { EdwardCurve mdc = Suite.getCurve(EdwardCurve.MDC); BigInteger a; + EdwardCurvePoint A; + // check we do not generate a low order public key do { - a = prng.bigInt(mdc.q); - } while (a.equals(BigInteger.ONE) || a.equals(BigInteger.ZERO)); - EdwardCurvePoint A = mdc.scalarMultiplicationWithX(a, mdc.G); + do { + a = prng.bigInt(mdc.q); + } while (a.equals(BigInteger.ZERO) || a.equals(BigInteger.ONE)); + A = mdc.scalarMultiplicationWithX(a, mdc.G); + } while (A.isLowOrderPoint()); HashMap publicKeyDictionary = new HashMap<>(); HashMap privateKeyDictionary = new HashMap<>(); try { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/SignaturePublicKey.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/SignaturePublicKey.java index 7d928f92..4b4f3b6a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/SignaturePublicKey.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/key/asymmetric/SignaturePublicKey.java @@ -21,6 +21,8 @@ import java.util.HashMap; +import io.olvid.engine.crypto.EdwardCurve; +import io.olvid.engine.crypto.Suite; import io.olvid.engine.datatypes.DictionaryKey; import io.olvid.engine.datatypes.key.CryptographicKey; import io.olvid.engine.encoder.Encoded; @@ -42,4 +44,16 @@ public static SignaturePublicKey of(byte algorithmImplementation, HashMap userInfo) engine.postEngineNotification(EngineNotifications.PUSH_REGISTER_FAILED_BAD_DEVICE_UID_TO_REPLACE, engineInfo); break; } + case DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER: { + Identity ownedIdentity = (Identity) userInfo.get(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_OWNED_IDENTITY_KEY); + boolean inProgress = (boolean) userInfo.get(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_IN_PROGRESS_KEY); + if (ownedIdentity == null) { + break; + } + + HashMap engineInfo = new HashMap<>(); + engineInfo.put(EngineNotifications.OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_BYTES_OWNED_IDENTITY_KEY, ownedIdentity.getBytes()); + engineInfo.put(EngineNotifications.OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_IN_PROGRESS_KEY, inProgress); + + engine.postEngineNotification(EngineNotifications.OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER, engineInfo); + break; + } default: Logger.w("Received notification " + notificationName + " but no handler is set."); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineNotifications.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineNotifications.java index 332bba0c..39eae628 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineNotifications.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineNotifications.java @@ -389,6 +389,11 @@ public abstract class EngineNotifications { public static final String CONTACT_INTRODUCTION_INVITATION_RESPONSE_BYTES_MEDIATOR_IDENTITY_KEY = "bytes_mediator_identity"; // byte[] public static final String CONTACT_INTRODUCTION_INVITATION_RESPONSE_CONTACT_SERIALIZED_DETAILS_KEY = "contact_serialized_Details"; // String public static final String CONTACT_INTRODUCTION_INVITATION_RESPONSE_ACCEPTED_KEY = "accepted"; // boolean + public static final String PUSH_REGISTER_FAILED_BAD_DEVICE_UID_TO_REPLACE = "engine_notification_push_register_failed_bad_device_uid_to_replace"; - public static final String PUSH_REGISTER_FAILED_BAD_DEVICE_UID_TO_REPLACE_BYTES_OWNED_IDENTITY_KEY = "bytes_owned_identity"; + public static final String PUSH_REGISTER_FAILED_BAD_DEVICE_UID_TO_REPLACE_BYTES_OWNED_IDENTITY_KEY = "bytes_owned_identity"; // byte[] + + public static final String OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER = "engine_notification_owned_identity_synchronizing_with_server"; + public static final String OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_BYTES_OWNED_IDENTITY_KEY = "bytes_owned_identity"; // byte[] + public static final String OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_IN_PROGRESS_KEY = "in_progress"; // boolean } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/NetworkFetchDelegate.java b/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/NetworkFetchDelegate.java index 066b34d8..3043e0c3 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/NetworkFetchDelegate.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/NetworkFetchDelegate.java @@ -45,9 +45,9 @@ public interface NetworkFetchDelegate { ReceivedAttachment[] getMessageAttachments(Identity ownedIdentity, UID messageUid); boolean isInboxAttachmentReceived(Session session, Identity ownedIdentity, UID uid, int engineNumber) throws Exception; + void messageCannotBeDecrypted(Session session, Identity ownedIdentity, UID messageUid); void deleteMessageAndAttachments(Session session, Identity ownedIdentity, UID messageUid); void deleteMessage(Session session, Identity ownedIdentity, UID messageUid); - void deleteAttachment(Session session, Identity ownedIdentity, UID messageUid, int attachmentNumber) throws SQLException; void resendAllDownloadedAttachmentNotifications() throws Exception; @@ -69,4 +69,5 @@ public interface NetworkFetchDelegate { void queryServerWellKnown(String server); List getOsmStyles(String server); String getAddressServerUrl(String server); + } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/FetchManager.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/FetchManager.java index a6980864..cefd1da9 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/FetchManager.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/FetchManager.java @@ -23,7 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.sql.SQLException; +import java.sql.Statement; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -73,7 +75,6 @@ import io.olvid.engine.networkfetch.databases.CachedWellKnown; import io.olvid.engine.networkfetch.databases.InboxAttachment; import io.olvid.engine.networkfetch.databases.InboxMessage; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.databases.PendingServerQuery; import io.olvid.engine.networkfetch.databases.PushNotificationConfiguration; import io.olvid.engine.networkfetch.databases.ServerSession; @@ -100,9 +101,11 @@ public class FetchManager implements FetchManagerSessionFactory, NetworkFetchDel private final WellKnownCoordinator wellKnownCoordinator; private NotificationPostingDelegate notificationPostingDelegate; private IdentityDelegate identityDelegate; - + private ProcessDownloadedMessageDelegate processDownloadedMessageDelegate; private CreateSessionDelegate createSessionDelegate; + private final HashSet ownedIdentitiesUpToDateRegardingServerListing; + public FetchManager(MetaManager metaManager, SSLSocketFactory sslSocketFactory, String engineBaseDirectory, PRNGService prng, ObjectMapper jsonObjectMapper) { this.engineBaseDirectory = engineBaseDirectory; this.prng = prng; @@ -122,6 +125,8 @@ public FetchManager(MetaManager metaManager, SSLSocketFactory sslSocketFactory, this.websocketCoordinator = new WebsocketCoordinator(this, sslSocketFactory, createServerSessionCoordinator, downloadMessagesAndListAttachmentsCoordinator, wellKnownCoordinator, jsonObjectMapper); this.getTurnCredentialsCoordinator = new GetTurnCredentialsCoordinator(this, sslSocketFactory, createServerSessionCoordinator, wellKnownCoordinator); + ownedIdentitiesUpToDateRegardingServerListing = new HashSet<>(); + metaManager.requestDelegate(this, CreateSessionDelegate.class); metaManager.requestDelegate(this, SolveChallengeDelegate.class); metaManager.requestDelegate(this, ProcessDownloadedMessageDelegate.class); @@ -138,7 +143,6 @@ public FetchManager(MetaManager metaManager, SSLSocketFactory sslSocketFactory, @Override public void initialisationComplete() { wellKnownCoordinator.initialQueueing(); - deleteMessageAndAttachmentsCoordinator.initialQueueing(); serverPushNotificationsCoordinator.initialQueueing(); downloadAttachmentCoordinator.initialQueueing(); serverQueryCoordinator.initialQueueing(); @@ -149,6 +153,7 @@ public void initialisationComplete() { createServerSessionCoordinator.initialQueueing(); } + @SuppressWarnings("unused") public void setDelegate(CreateSessionDelegate createSessionDelegate) { this.createSessionDelegate = createSessionDelegate; @@ -156,7 +161,6 @@ public void setDelegate(CreateSessionDelegate createSessionDelegate) { CachedWellKnown.createTable(fetchManagerSession.session); ServerSession.createTable(fetchManagerSession.session); PushNotificationConfiguration.createTable(fetchManagerSession.session); - PendingDeleteFromServer.createTable(fetchManagerSession.session); InboxMessage.createTable(fetchManagerSession.session); InboxAttachment.createTable(fetchManagerSession.session); PendingServerQuery.createTable(fetchManagerSession.session); @@ -171,21 +175,30 @@ public static void upgradeTables(Session session, int oldVersion, int newVersion CachedWellKnown.upgradeTable(session, oldVersion, newVersion); ServerSession.upgradeTable(session, oldVersion, newVersion); PushNotificationConfiguration.upgradeTable(session, oldVersion, newVersion); - PendingDeleteFromServer.upgradeTable(session, oldVersion, newVersion); InboxMessage.upgradeTable(session, oldVersion, newVersion); InboxAttachment.upgradeTable(session, oldVersion, newVersion); PendingServerQuery.upgradeTable(session, oldVersion, newVersion); + if (oldVersion < 40 && newVersion >= 40) { + Logger.d("DROPPING `pending_delete_from_server` DATABASE FOR VERSION 40"); + try (Statement statement = session.createStatement()) { + statement.execute("DROP TABLE `pending_delete_from_server`"); + } + } } + @SuppressWarnings("unused") public void setDelegate(SolveChallengeDelegate solveChallengeDelegate) { this.createServerSessionCoordinator.setSolveChallengeDelegate(solveChallengeDelegate); } + @SuppressWarnings("unused") public void setDelegate(ProcessDownloadedMessageDelegate processDownloadedMessageDelegate) { + this.processDownloadedMessageDelegate = processDownloadedMessageDelegate; this.downloadMessagesAndListAttachmentsCoordinator.setProcessDownloadedMessageDelegate(processDownloadedMessageDelegate); } + @SuppressWarnings("unused") public void setDelegate(NotificationListeningDelegate notificationListeningDelegate) { this.serverPushNotificationsCoordinator.setNotificationListeningDelegate(notificationListeningDelegate); this.websocketCoordinator.setNotificationListeningDelegate(notificationListeningDelegate); @@ -199,6 +212,7 @@ public void setDelegate(NotificationListeningDelegate notificationListeningDeleg this.verifyReceiptCoordinator.setNotificationListeningDelegate(notificationListeningDelegate); } + @SuppressWarnings("unused") public void setDelegate(NotificationPostingDelegate notificationPostingDelegate) { this.notificationPostingDelegate = notificationPostingDelegate; this.serverPushNotificationsCoordinator.setNotificationPostingDelegate(notificationPostingDelegate); @@ -212,14 +226,17 @@ public void setDelegate(NotificationPostingDelegate notificationPostingDelegate) this.getTurnCredentialsCoordinator.setNotificationPostingDelegate(notificationPostingDelegate); } + @SuppressWarnings("unused") public void setDelegate(ChannelDelegate channelDelegate) { this.serverQueryCoordinator.setChannelDelegate(channelDelegate); } + @SuppressWarnings("unused") public void setDelegate(IdentityDelegate identityDelegate) { this.identityDelegate = identityDelegate; } + @SuppressWarnings("unused") public void setDelegate(ProtocolStarterDelegate protocolStarterDelegate) { this.websocketCoordinator.setProtocolStarterDelegate(protocolStarterDelegate); } @@ -232,7 +249,6 @@ public void deleteOwnedIdentity(Session session, Identity ownedIdentity, boolean for (InboxMessage inboxMessage: inboxMessages) { inboxMessage.delete(); } - PendingDeleteFromServer.deleteAllForOwnedIdentity(wrapSession(session), ownedIdentity); for (PendingServerQuery pendingServerQuery: PendingServerQuery.getAll(wrapSession(session))) { try { ServerQuery serverQuery = ServerQuery.of(pendingServerQuery.getEncodedQuery()); @@ -262,7 +278,7 @@ public FetchManagerSession getSession() throws SQLException { downloadMessageExtendedPayloadCoordinator, deleteMessageAndAttachmentsCoordinator, downloadAttachmentCoordinator, - deleteMessageAndAttachmentsCoordinator, +// deleteMessageAndAttachmentsCoordinator, serverPushNotificationsCoordinator, serverQueryCoordinator, identityDelegate, @@ -277,7 +293,7 @@ private FetchManagerSession wrapSession(Session session) { downloadMessageExtendedPayloadCoordinator, deleteMessageAndAttachmentsCoordinator, downloadAttachmentCoordinator, - deleteMessageAndAttachmentsCoordinator, +// deleteMessageAndAttachmentsCoordinator, serverPushNotificationsCoordinator, serverQueryCoordinator, identityDelegate, @@ -452,6 +468,58 @@ public boolean isInboxAttachmentReceived(Session session, Identity ownedIdentity return (inboxAttachment == null) || (inboxAttachment.getExpectedLength() == inboxAttachment.getReceivedLength()); } + + // this method is called when a received message cannot be decrypted. + // If we are still listing messages on the server, we may be late on self-ratchet so we simply postpone the processing of this message by doing nothing :) + @Override + public void messageCannotBeDecrypted(Session session, Identity ownedIdentity, UID messageUid) { + synchronized (ownedIdentitiesUpToDateRegardingServerListing) { + if (ownedIdentitiesUpToDateRegardingServerListing.contains(ownedIdentity)) { + deleteMessageAndAttachments(session, ownedIdentity, messageUid); + } + } + } + + @Override + public void markOwnedIdentityAsUpToDate(Identity ownedIdentity) { + synchronized (ownedIdentitiesUpToDateRegardingServerListing) { + // mark the identity as up to date + ownedIdentitiesUpToDateRegardingServerListing.add(ownedIdentity); + + // notify app that syncing is finished + if (notificationPostingDelegate != null) { + HashMap userInfo = new HashMap<>(); + userInfo.put(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_OWNED_IDENTITY_KEY, ownedIdentity); + userInfo.put(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_IN_PROGRESS_KEY, false); + notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER, userInfo); + } + + // reprocess all unprocessed messages + try (FetchManagerSession fetchManagerSession = getSession()) { + // retry processing messages that were downloaded but never decrypted nor marked for deletion + InboxMessage[] unprocessedMessages = InboxMessage.getUnprocessedMessagesForOwnedIdentity(fetchManagerSession, ownedIdentity); + for (InboxMessage inboxMessage : unprocessedMessages) { + processDownloadedMessageDelegate.processDownloadedMessage(inboxMessage.getNetworkReceivedMessage()); + } + } catch (SQLException ignored) { } + } + } + + @Override + public void markOwnedIdentityAsNotUpToDate(Identity ownedIdentity) { + synchronized (ownedIdentitiesUpToDateRegardingServerListing) { + ownedIdentitiesUpToDateRegardingServerListing.remove(ownedIdentity); + + // notify app that syncing is in progress + if (notificationPostingDelegate != null) { + HashMap userInfo = new HashMap<>(); + userInfo.put(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_OWNED_IDENTITY_KEY, ownedIdentity); + userInfo.put(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER_IN_PROGRESS_KEY, true); + notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_OWNED_IDENTITY_SYNCHRONIZING_WITH_SERVER, userInfo); + } + } + } + // This method marks a message and all its attachments for deletion @Override public void deleteMessageAndAttachments(Session session, Identity ownedIdentity, UID messageUid) { @@ -464,10 +532,10 @@ public void deleteMessageAndAttachments(Session session, Identity ownedIdentity, for (InboxAttachment inboxAttachment: inboxMessage.getAttachments()) { inboxAttachment.markForDeletion(); } - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); } - // This method marks a message for deletion and creates a PendingDeleteFromServer if needed + // This method marks a message for deletion and queues the operation to delete it from server @Override public void deleteMessage(Session session, Identity ownedIdentity, UID messageUid) { FetchManagerSession fetchManagerSession = wrapSession(session); @@ -477,11 +545,11 @@ public void deleteMessage(Session session, Identity ownedIdentity, UID messageUi } inboxMessage.markForDeletion(); if (inboxMessage.canBeDeleted()) { - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); } } - // This method marks an attachment for deletion and creates a PendingDeleteFromServer if needed + // This method marks an attachment for deletion and queues the operation to delete it from server @Override public void deleteAttachment(Session session, Identity ownedIdentity, UID messageUid, int attachmentNumber) throws SQLException { FetchManagerSession fetchManagerSession = wrapSession(session); @@ -491,7 +559,7 @@ public void deleteAttachment(Session session, Identity ownedIdentity, UID messag } inboxAttachment.markForDeletion(); if (inboxAttachment.getMessage().canBeDeleted()) { - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DeleteMessageAndAttachmentsCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DeleteMessageAndAttachmentsCoordinator.java index 6e52079c..c665679f 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DeleteMessageAndAttachmentsCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DeleteMessageAndAttachmentsCoordinator.java @@ -44,13 +44,11 @@ import io.olvid.engine.datatypes.notifications.DownloadNotifications; import io.olvid.engine.metamanager.NotificationListeningDelegate; import io.olvid.engine.networkfetch.databases.InboxMessage; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.datatypes.CreateServerSessionDelegate; -import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; import io.olvid.engine.networkfetch.datatypes.FetchManagerSessionFactory; import io.olvid.engine.networkfetch.operations.DeleteMessageAndAttachmentFromServerAndLocalInboxesOperation; -public class DeleteMessageAndAttachmentsCoordinator implements Operation.OnCancelCallback, PendingDeleteFromServer.PendingDeleteFromServerListener, InboxMessage.MarkAsListedOnServerListener, Operation.OnFinishCallback { +public class DeleteMessageAndAttachmentsCoordinator implements Operation.OnCancelCallback, InboxMessage.MarkAsListedAndDeleteOnServerListener, Operation.OnFinishCallback { private final FetchManagerSessionFactory fetchManagerSessionFactory; private final SSLSocketFactory sslSocketFactory; private final CreateServerSessionDelegate createServerSessionDelegate; @@ -65,8 +63,6 @@ public class DeleteMessageAndAttachmentsCoordinator implements Operation.OnCance private final Lock awaitingServerSessionOperationsLock; private final ServerSessionCreatedNotificationListener serverSessionCreatedNotificationListener; - private boolean initialQueueingPerformed = false; - private final Object lock = new Object(); public DeleteMessageAndAttachmentsCoordinator(FetchManagerSessionFactory fetchManagerSessionFactory, SSLSocketFactory sslSocketFactory, @@ -93,23 +89,6 @@ public void setNotificationListeningDelegate(NotificationListeningDelegate notif this.notificationListeningDelegate.addListener(DownloadNotifications.NOTIFICATION_SERVER_SESSION_CREATED, serverSessionCreatedNotificationListener); } - public void initialQueueing() { - synchronized (lock) { - if (initialQueueingPerformed) { - return; - } - try (FetchManagerSession fetchManagerSession = fetchManagerSessionFactory.getSession()) { - PendingDeleteFromServer[] pendingDeletes = PendingDeleteFromServer.getAll(fetchManagerSession); - for (PendingDeleteFromServer pendingDelete: pendingDeletes) { - queueNewDeleteMessageAndAttachmentsFromServerOperation(pendingDelete.getOwnedIdentity(), pendingDelete.getMessageUid(), false); - } - initialQueueingPerformed = true; - } catch (Exception e) { - e.printStackTrace(); - // Fail silently: an exception is supposed to occur if the CreateSessionDelegate of the sendManagerSessionFactory is not set yet. - } - } - } private void queueNewDeleteMessageAndAttachmentsFromServerOperation(Identity ownedIdentity, UID messageUid, boolean markAsListed) { if (messageUid != null) { @@ -232,13 +211,12 @@ public void callback(String notificationName, HashMap userInfo) } - // Notifications received from PendingDeleteFromServer database + // Notifications received from MessageInbox when the message and its attachments can be deleted @Override - public void newPendingDeleteFromServer(Identity ownedIdentity, UID messageUid) { + public void messageCanBeDeletedFromServer(Identity ownedIdentity, UID messageUid) { queueNewDeleteMessageAndAttachmentsFromServerOperation(ownedIdentity, messageUid, false); } - // Notifications received from MessageInbox when the payload is set and there are some attachments @Override public void messageCanBeMarkedAsListedOnServer(Identity ownedIdentity, UID messageUid) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadAttachmentCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadAttachmentCoordinator.java index 78298a8e..c5b3a307 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadAttachmentCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadAttachmentCoordinator.java @@ -42,7 +42,6 @@ import io.olvid.engine.metamanager.NotificationListeningDelegate; import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.networkfetch.databases.InboxAttachment; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.datatypes.DownloadAttachmentPriorityCategory; import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; import io.olvid.engine.networkfetch.datatypes.FetchManagerSessionFactory; @@ -197,7 +196,7 @@ public void onCancelCallback(Operation operation) { fetchManagerSession.session.startTransaction(); attachment.markForDeletion(); if (attachment.getMessage().canBeDeleted()) { - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); } fetchManagerSession.session.commit(); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadMessagesAndListAttachmentsCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadMessagesAndListAttachmentsCoordinator.java index 4cfcba8f..c4be44e7 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadMessagesAndListAttachmentsCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/DownloadMessagesAndListAttachmentsCoordinator.java @@ -41,7 +41,6 @@ import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.metamanager.ProcessDownloadedMessageDelegate; import io.olvid.engine.networkfetch.databases.InboxMessage; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.datatypes.CreateServerSessionDelegate; import io.olvid.engine.networkfetch.datatypes.DownloadMessagesAndListAttachmentsDelegate; import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; @@ -123,17 +122,9 @@ public void initialQueueing() { InboxMessage[] markedForDeletionMessages = InboxMessage.getMarkedForDeletionMessages(fetchManagerSession); for (InboxMessage inboxMessage: markedForDeletionMessages) { if (inboxMessage.canBeDeleted()) { - PendingDeleteFromServer.create(fetchManagerSession, inboxMessage.getOwnedIdentity(), inboxMessage.getUid()); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(inboxMessage.getOwnedIdentity(), inboxMessage.getUid()); } } - fetchManagerSession.session.commit(); - - // check all decrypted messages, with attachments, that are not yet marked as listed on the server - InboxMessage[] messagesToMarkAsListedOnServer = InboxMessage.getMessagesThatCanBeMarkedAsListedOnServer(fetchManagerSession); - for (InboxMessage inboxMessage : messagesToMarkAsListedOnServer) { - fetchManagerSession.markAsListedOnServerListener.messageCanBeMarkedAsListedOnServer(inboxMessage.getOwnedIdentity(), inboxMessage.getUid()); - } - } catch (SQLException e) { e.printStackTrace(); } @@ -205,18 +196,20 @@ public void callback(String notificationName, HashMap userInfo) @Override public void onFinishCallback(Operation operation) { - Identity identity = ((DownloadMessagesAndListAttachmentsOperation) operation).getOwnedIdentity(); + Identity ownedIdentity = ((DownloadMessagesAndListAttachmentsOperation) operation).getOwnedIdentity(); UID deviceUid = ((DownloadMessagesAndListAttachmentsOperation) operation).getDeviceUid(); boolean listingTruncated = ((DownloadMessagesAndListAttachmentsOperation) operation).getListingTruncated(); - scheduler.clearFailedCount(identity); + scheduler.clearFailedCount(ownedIdentity); if (listingTruncated) { // if listing was truncated --> trigger a new list in 10 seconds, once messages are processed and deleted from server - scheduler.schedule(identity, () -> queueNewDownloadMessagesAndListAttachmentsOperation(identity, deviceUid), "DownloadMessagesAndListAttachmentsOperation [relist]", Constants.RELIST_DELAY); + scheduler.schedule(ownedIdentity, () -> queueNewDownloadMessagesAndListAttachmentsOperation(ownedIdentity, deviceUid), "DownloadMessagesAndListAttachmentsOperation [relist]", Constants.RELIST_DELAY); + } else { + fetchManagerSessionFactory.markOwnedIdentityAsUpToDate(ownedIdentity); } HashMap userInfo = new HashMap<>(); - userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_OWNED_IDENTITY_KEY, identity); + userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_OWNED_IDENTITY_KEY, ownedIdentity); userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_SUCCESS_KEY, true); userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_TRUNCATED_KEY, listingTruncated); notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_SERVER_POLLED, userInfo); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/RefreshInboxAttachmentSignedUrlCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/RefreshInboxAttachmentSignedUrlCoordinator.java index 106cf23a..4b962256 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/RefreshInboxAttachmentSignedUrlCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/RefreshInboxAttachmentSignedUrlCoordinator.java @@ -42,7 +42,6 @@ import io.olvid.engine.metamanager.NotificationListeningDelegate; import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.networkfetch.databases.InboxAttachment; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; import io.olvid.engine.networkfetch.datatypes.FetchManagerSessionFactory; import io.olvid.engine.networkfetch.datatypes.RefreshInboxAttachmentSignedUrlDelegate; @@ -149,7 +148,7 @@ public void onCancelCallback(Operation operation) { fetchManagerSession.session.startTransaction(); attachment.markForDeletion(); if (attachment.getMessage().canBeDeleted()) { - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); } fetchManagerSession.session.commit(); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WebsocketCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WebsocketCoordinator.java index 51179bb5..b630e908 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WebsocketCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WebsocketCoordinator.java @@ -630,9 +630,12 @@ private class WebSocketClient extends WebSocketListener { private static final int INTERNAL_CLOSING_CODE = 4547; + private int currentConnectionState; + WebSocketClient(String server, String wsUrl) { this.wsUrl = wsUrl; this.server = server; + this.currentConnectionState = 0; synchronized (existingWebsockets) { existingWebsockets.put(server, this); } @@ -650,9 +653,12 @@ public void onOpen(WebSocket webSocket, Response response) { websocketConnected = true; Logger.d("Websocket connected to " + wsUrl); if (notificationPostingDelegate != null) { - HashMap userInfo = new HashMap<>(); - userInfo.put(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED_STATE_KEY, 1); - notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED, userInfo); + if (currentConnectionState != 1) { + currentConnectionState = 1; + HashMap userInfo = new HashMap<>(); + userInfo.put(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED_STATE_KEY, 1); + notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED, userInfo); + } if (relyOnWebsocketForNetworkDetection) { notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_DETECTED_SOME_NETWORK, new HashMap<>()); } @@ -673,7 +679,8 @@ public void onMessage(WebSocket webSocket, String message) { // we received a message, so the connection is functioning properly, we can reset the connection failed count scheduler.clearFailedCount(server); - if (notificationPostingDelegate != null) { + if (notificationPostingDelegate != null && currentConnectionState != 2) { + currentConnectionState = 2; HashMap userInfo = new HashMap<>(); userInfo.put(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED_STATE_KEY, 2); notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED, userInfo); @@ -757,11 +764,14 @@ public void onMessage(WebSocket webSocket, String message) { try { byte[] messagePayload = Base64.decode((String) messageObject); downloadMessagesAndListAttachmentsDelegate.processWebsocketDownloadedMessage(identity, deviceUid, messagePayload); + // we break, no listing required break; } catch (Exception e) { // if base64 decoding fails, revert to usual list } } + // uf we receive this notification, we might have many pending messages on the server --> mark own identity as not up to date + fetchManagerSessionFactory.markOwnedIdentityAsNotUpToDate(identity); downloadMessagesAndListAttachmentsDelegate.downloadMessagesAndListAttachments(identity, deviceUid); } catch (IOException | DecodingException e) { Logger.d("Error decoding identity"); @@ -847,9 +857,6 @@ public void onMessage(WebSocket webSocket, String message) { userInfo.put(DownloadNotifications.NOTIFICATION_PING_RECEIVED_DELAY_KEY, delay); notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_PING_RECEIVED, userInfo); } - HashMap userInfo = new HashMap<>(); - userInfo.put(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED_STATE_KEY, 2); - notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED, userInfo); } } break; @@ -963,7 +970,8 @@ public void onFailure(WebSocket webSocket, Throwable t, Response response) { } void close() { - if (notificationPostingDelegate != null) { + if (notificationPostingDelegate != null && currentConnectionState != 0) { + currentConnectionState = 0; HashMap userInfo = new HashMap<>(); userInfo.put(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED_STATE_KEY, 0); notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WEBSOCKET_CONNECTION_STATE_CHANGED, userInfo); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WellKnownCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WellKnownCoordinator.java index 94b3edbd..c8b128b8 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WellKnownCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/WellKnownCoordinator.java @@ -108,7 +108,7 @@ public void initialQueueing() { notificationPostingDelegate.postNotification(DownloadNotifications.NOTIFICATION_WELL_KNOWN_CACHE_INITIALIZED, new HashMap<>()); - wellKnownDownloadTimer.scheduleAtFixedRate(new TimerTask() { + wellKnownDownloadTimer.schedule(new TimerTask() { @Override public void run() { try (FetchManagerSession fetchManagerSession = fetchManagerSessionFactory.getSession()) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/InboxMessage.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/InboxMessage.java index c53471f5..044e941b 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/InboxMessage.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/InboxMessage.java @@ -81,8 +81,6 @@ public class InboxMessage implements ObvDatabase { static final String EXTENDED_PAYLOAD_KEY = "extended_payload_key"; private byte[] extendedPayload; static final String EXTENDED_PAYLOAD = "extended_payload"; - private boolean markedAsListedOnServer; - static final String MARKED_AS_LISTED_ON_SERVER = "marked_as_listed_on_server"; public Identity getOwnedIdentity() { @@ -125,10 +123,6 @@ public byte[] getExtendedPayload() { return extendedPayload; } - public boolean isMarkedAsListedOnServer() { - return markedAsListedOnServer; - } - public InboxAttachment[] getAttachments() { return InboxAttachment.getAll(fetchManagerSession, ownedIdentity, uid); } @@ -184,18 +178,6 @@ public void markForDeletion() { } } - public void markAsListedOnServer() { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("UPDATE " + TABLE_NAME + - " SET " + MARKED_AS_LISTED_ON_SERVER + " = 1 " + - " WHERE " + OWNED_IDENTITY + " = ? " + - " AND " + UID_ + " = ?;")) { - statement.setBytes(1, ownedIdentity.getBytes()); - statement.setBytes(2, uid.getBytes()); - statement.executeUpdate(); - this.markedAsListedOnServer = true; - } catch (SQLException ignored) { - } - } public void setPayloadAndFromIdentity(byte[] payload, Identity fromIdentity, AuthEncKey extendedPayloadKey) { try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("UPDATE " + TABLE_NAME + @@ -260,10 +242,7 @@ public static InboxMessage create(FetchManagerSession fetchManagerSession, Ident } if (deletedMessageUids.containsKey(new IdentityAndUid(ownedIdentity, messageUid))) { // we listed again a message that was deleted, just to be sure, create a pendingDelete - try { - PendingDeleteFromServer.create(fetchManagerSession, ownedIdentity, messageUid); - } catch (Exception ignored) { - } + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeDeletedFromServer(ownedIdentity, messageUid); return null; } try { @@ -291,7 +270,6 @@ private InboxMessage(FetchManagerSession fetchManagerSession, Identity ownedIden this.hasExtendedPayload = hasExtendedContent; this.extendedPayloadKey = null; this.extendedPayload = null; - this.markedAsListedOnServer = false; } private InboxMessage(FetchManagerSession fetchManagerSession, ResultSet res) throws SQLException { @@ -328,7 +306,6 @@ private InboxMessage(FetchManagerSession fetchManagerSession, ResultSet res) thr this.extendedPayloadKey = null; } this.extendedPayload = res.getBytes(EXTENDED_PAYLOAD); - this.markedAsListedOnServer = res.getBoolean(MARKED_AS_LISTED_ON_SERVER); } // endregion @@ -369,11 +346,29 @@ public static InboxMessage[] getAllForOwnedIdentity(FetchManagerSession fetchMan } } + public static InboxMessage[] getUnprocessedMessagesForOwnedIdentity(FetchManagerSession fetchManagerSession, Identity ownedIdentity) throws SQLException { + try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("SELECT * FROM " + TABLE_NAME + + " WHERE " + OWNED_IDENTITY + " = ?" + + " AND " + PAYLOAD + " IS NULL " + + " AND " + MARKED_FOR_DELETION + " = 0" + + " ORDER BY " + SERVER_TIMESTAMP + " ASC;")) { + statement.setBytes(1, ownedIdentity.getBytes()); + try (ResultSet res = statement.executeQuery()) { + List list = new ArrayList<>(); + while (res.next()) { + list.add(new InboxMessage(fetchManagerSession, res)); + } + return list.toArray(new InboxMessage[0]); + } + } + } + public static InboxMessage[] getUnprocessedMessages(FetchManagerSession fetchManagerSession) { try (PreparedStatement statement = fetchManagerSession.session.prepareStatement( "SELECT * FROM " + TABLE_NAME + " WHERE " + PAYLOAD + " IS NULL " + - " AND " + MARKED_FOR_DELETION + " = 0;")) { + " AND " + MARKED_FOR_DELETION + " = 0" + + " ORDER BY " + SERVER_TIMESTAMP + " ASC;")) { try (ResultSet res = statement.executeQuery()) { List list = new ArrayList<>(); while (res.next()) { @@ -390,7 +385,8 @@ public static InboxMessage[] getDecryptedMessages(FetchManagerSession fetchManag try (PreparedStatement statement = fetchManagerSession.session.prepareStatement( "SELECT * FROM " + TABLE_NAME + " WHERE " + PAYLOAD + " NOT NULL " + - " AND " + MARKED_FOR_DELETION + " = 0;")) { + " AND " + MARKED_FOR_DELETION + " = 0" + + " ORDER BY " + SERVER_TIMESTAMP + " ASC;")) { try (ResultSet res = statement.executeQuery()) { List list = new ArrayList<>(); while (res.next()) { @@ -452,29 +448,6 @@ public static InboxMessage[] getMissingExtendedPayloadMessages(FetchManagerSessi } } - // get all messages that have been decrypted, have attachments and are not yet makredAsListedOnServer - public static InboxMessage[] getMessagesThatCanBeMarkedAsListedOnServer(FetchManagerSession fetchManagerSession) { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement( - "SELECT m.* FROM " + TABLE_NAME + " AS m " + - " INNER JOIN " + InboxAttachment.TABLE_NAME + " AS a " + - " ON a." + InboxAttachment.MESSAGE_UID + " = m." + UID_ + - " AND a." + InboxAttachment.OWNED_IDENTITY + " = m." + OWNED_IDENTITY + - " WHERE m." + PAYLOAD + " IS NOT NULL" + - " AND m." + MARKED_AS_LISTED_ON_SERVER + " = 0 " + - " GROUP BY m." + UID_ + ";")) { - try (ResultSet res = statement.executeQuery()) { - List list = new ArrayList<>(); - while (res.next()) { - list.add(new InboxMessage(fetchManagerSession, res)); - } - return list.toArray(new InboxMessage[0]); - } - } catch (SQLException e) { - return new InboxMessage[0]; - } - } - - // endregion // region database @@ -497,7 +470,6 @@ public static void createTable(Session session) throws SQLException { HAS_EXTENDED_PAYLOAD + " BIT NOT NULL, " + EXTENDED_PAYLOAD_KEY + " BLOB, " + EXTENDED_PAYLOAD + " BLOB, " + - MARKED_AS_LISTED_ON_SERVER + " BIT NOT NULL, " + " CONSTRAINT PK_" + TABLE_NAME + " PRIMARY KEY(" + OWNED_IDENTITY + ", " + UID_ + "));" ); } @@ -593,12 +565,19 @@ public static void upgradeTable(Session session, int oldVersion, int newVersion) } oldVersion = 38; } + if (oldVersion < 40 && newVersion >= 40) { + Logger.d("MIGRATING `inbox_message` DATABASE FROM VERSION " + oldVersion + " TO 40"); + try (Statement statement = session.createStatement()) { + statement.execute("ALTER TABLE inbox_message DROP COLUMN `marked_as_listed_on_server`"); + } + oldVersion = 40; + } } @Override public void insert() throws SQLException { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES(?,?,?,?,?, ?,?,?,?,?, ?,?,?,?);")) { + try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES(?,?,?,?,?, ?,?,?,?,?, ?,?,?);")) { statement.setBytes(1, ownedIdentity.getBytes()); statement.setBytes(2, uid.getBytes()); statement.setBytes(3, wrappedKey.getBytes()); @@ -614,7 +593,6 @@ public void insert() throws SQLException { statement.setBoolean(11, hasExtendedPayload); statement.setBytes(12, (extendedPayloadKey == null) ? null : Encoded.of(extendedPayloadKey).getBytes()); statement.setBytes(13, extendedPayload); - statement.setBoolean(14, markedAsListedOnServer); statement.executeUpdate(); this.commitHookBits |= HOOK_BIT_INSERT; @@ -677,8 +655,9 @@ public interface ExtendedPayloadListener { void messageExtendedPayloadDownloaded(Identity ownedIdentity, UID uid, byte[] extendedPayload); } - public interface MarkAsListedOnServerListener { + public interface MarkAsListedAndDeleteOnServerListener { void messageCanBeMarkedAsListedOnServer(Identity ownedIdentity, UID messageUid); + void messageCanBeDeletedFromServer(Identity ownedIdentity, UID messageUid); } @@ -702,8 +681,8 @@ public void wasCommitted() { } } // for application messages, we always mark as listed on server, even if there are no attachments --> this way we do not rely on the app properly processing the message to avoid relisting - if (fetchManagerSession.markAsListedOnServerListener != null) { - fetchManagerSession.markAsListedOnServerListener.messageCanBeMarkedAsListedOnServer(ownedIdentity, uid); + if (fetchManagerSession.markAsListedAndDeleteOnServerListener != null) { + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeMarkedAsListedOnServer(ownedIdentity, uid); } } if ((commitHookBits & HOOK_BIT_EXTENDED_PAYLOAD_SET) != 0) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingDeleteFromServer.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingDeleteFromServer.java deleted file mode 100644 index 638d2bc6..00000000 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingDeleteFromServer.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Olvid for Android - * Copyright © 2019-2024 Olvid SAS - * - * This file is part of Olvid for Android. - * - * Olvid is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * Olvid is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Olvid. If not, see . - */ - -package io.olvid.engine.networkfetch.databases; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; - -import io.olvid.engine.Logger; -import io.olvid.engine.datatypes.Identity; -import io.olvid.engine.datatypes.ObvDatabase; -import io.olvid.engine.datatypes.Session; -import io.olvid.engine.datatypes.UID; -import io.olvid.engine.encoder.DecodingException; -import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; - - -public class PendingDeleteFromServer implements ObvDatabase { - static final String TABLE_NAME = "pending_delete_from_server"; - - private final FetchManagerSession fetchManagerSession; - - private Identity ownedIdentity; - static final String OWNED_IDENTITY = "owned_identity"; - private UID messageUid; - static final String MESSAGE_UID = "message_uid"; - - public Identity getOwnedIdentity() { - return ownedIdentity; - } - - public UID getMessageUid() { - return messageUid; - } - - - - public static PendingDeleteFromServer create(FetchManagerSession fetchManagerSession, Identity ownedIdentity, UID messageUid) { - if (ownedIdentity == null || messageUid == null) { - return null; - } - try { - PendingDeleteFromServer pendingDeleteFromServer = new PendingDeleteFromServer(fetchManagerSession, ownedIdentity, messageUid); - pendingDeleteFromServer.insert(); - return pendingDeleteFromServer; - } catch (SQLException e) { - // no log, exception may be normal in case of duplicate delete during initial queueing - return null; - } - } - - private PendingDeleteFromServer(FetchManagerSession fetchManagerSession, Identity ownedIdentity, UID messageUid) { - this.fetchManagerSession = fetchManagerSession; - this.ownedIdentity = ownedIdentity; - this.messageUid = messageUid; - } - - private PendingDeleteFromServer(FetchManagerSession fetchManagerSession, ResultSet res) throws SQLException { - this.fetchManagerSession = fetchManagerSession; - try { - this.ownedIdentity = Identity.of(res.getBytes(OWNED_IDENTITY)); - } catch (DecodingException e) { - e.printStackTrace(); - } - this.messageUid = new UID(res.getBytes(MESSAGE_UID)); - } - - public static PendingDeleteFromServer get(FetchManagerSession fetchManagerSession, Identity ownedIdentity, UID messageUid) { - if (ownedIdentity == null || messageUid == null) { - return null; - } - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("SELECT * FROM " + TABLE_NAME + - " WHERE " + OWNED_IDENTITY + " = ? " + - " AND " + MESSAGE_UID + " = ?;")) { - statement.setBytes(1, ownedIdentity.getBytes()); - statement.setBytes(2, messageUid.getBytes()); - try (ResultSet res = statement.executeQuery()) { - if (res.next()) { - return new PendingDeleteFromServer(fetchManagerSession, res); - } else { - return null; - } - } - } catch (SQLException e) { - e.printStackTrace(); - return null; - } - } - - public static PendingDeleteFromServer[] getAll(FetchManagerSession fetchManagerSession) { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("SELECT * FROM " + TABLE_NAME + ";")) { - try (ResultSet res = statement.executeQuery()) { - List list = new ArrayList<>(); - while (res.next()) { - list.add(new PendingDeleteFromServer(fetchManagerSession, res)); - } - return list.toArray(new PendingDeleteFromServer[0]); - } - } catch (SQLException e) { - e.printStackTrace(); - return new PendingDeleteFromServer[0]; - } - } - - public static void deleteAllForOwnedIdentity(FetchManagerSession protocolManagerSession, Identity ownedIdentity) throws SQLException { - try (PreparedStatement statement = protocolManagerSession.session.prepareStatement("DELETE FROM " + TABLE_NAME + " WHERE " + OWNED_IDENTITY + " = ?;")) { - statement.setBytes(1, ownedIdentity.getBytes()); - statement.executeUpdate(); - } - } - - // region database - - public static void createTable(Session session) throws SQLException { - try (Statement statement = session.createStatement()) { - statement.execute("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + - OWNED_IDENTITY + " BLOB NOT NULL, " + - MESSAGE_UID + " BLOB NOT NULL, " + - " CONSTRAINT PK_" + TABLE_NAME + " PRIMARY KEY(" + OWNED_IDENTITY + ", " + MESSAGE_UID + "));"); - } - } - - public static void upgradeTable(Session session, int oldVersion, int newVersion) throws SQLException { - if (oldVersion < 15 && newVersion >= 15) { - Logger.d("MIGRATING `pending_delete_from_server` DATABASE FROM VERSION " + oldVersion + " TO 15"); - try (Statement statement = session.createStatement()) { - statement.execute("ALTER TABLE pending_delete_from_server RENAME TO old_pending_delete_from_server"); - statement.execute("CREATE TABLE IF NOT EXISTS pending_delete_from_server (" + - " owned_identity BLOB NOT NULL, " + - " message_uid BLOB NOT NULL, " + - " CONSTRAINT PK_pending_delete_from_server PRIMARY KEY(owned_identity, message_uid));"); - statement.execute("INSERT INTO pending_delete_from_server SELECT m.to_identity, p.message_uid " + - " FROM old_pending_delete_from_server AS p " + - " INNER JOIN inbox_message AS m ON p.message_uid = m.uid"); - statement.execute("DROP TABLE old_pending_delete_from_server"); - } - oldVersion = 15; - } - } - - @Override - public void insert() throws SQLException { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES(?,?);")) { - statement.setBytes(1, ownedIdentity.getBytes()); - statement.setBytes(2, messageUid.getBytes()); - statement.executeUpdate(); - this.commitHookBits |= HOOK_BIT_INSERT; - fetchManagerSession.session.addSessionCommitListener(this); - } - } - - @Override - public void delete() throws SQLException { - try (PreparedStatement statement = fetchManagerSession.session.prepareStatement("DELETE FROM " + TABLE_NAME + - " WHERE " + OWNED_IDENTITY + " = ? " + - " AND " + MESSAGE_UID + " = ?;")) { - statement.setBytes(1, ownedIdentity.getBytes()); - statement.setBytes(2, messageUid.getBytes()); - statement.executeUpdate(); - } - } - - // endregion - - // region hooks - - public interface PendingDeleteFromServerListener { - void newPendingDeleteFromServer(Identity ownedIdentity, UID messageUid); - } - - private long commitHookBits = 0; - private static final long HOOK_BIT_INSERT = 0x1; - - @Override - public void wasCommitted() { - if ((commitHookBits & HOOK_BIT_INSERT) != 0) { - if (fetchManagerSession.pendingDeleteFromServerListener != null) { - fetchManagerSession.pendingDeleteFromServerListener.newPendingDeleteFromServer(ownedIdentity, messageUid); - } - } - commitHookBits = 0; - } - - // endregion -} diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSession.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSession.java index 9eb69a28..8b9e68f2 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSession.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSession.java @@ -27,7 +27,6 @@ import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.networkfetch.databases.InboxAttachment; import io.olvid.engine.networkfetch.databases.InboxMessage; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.databases.PendingServerQuery; import io.olvid.engine.networkfetch.databases.PushNotificationConfiguration; @@ -36,9 +35,8 @@ public class FetchManagerSession implements AutoCloseable { public final Session session; public final InboxMessage.InboxMessageListener inboxMessageListener; public final InboxMessage.ExtendedPayloadListener extendedPayloadListener; - public final InboxMessage.MarkAsListedOnServerListener markAsListedOnServerListener; + public final InboxMessage.MarkAsListedAndDeleteOnServerListener markAsListedAndDeleteOnServerListener; public final InboxAttachment.InboxAttachmentListener inboxAttachmentListener; - public final PendingDeleteFromServer.PendingDeleteFromServerListener pendingDeleteFromServerListener; public final PushNotificationConfiguration.NewPushNotificationConfigurationListener newPushNotificationConfigurationListener; public final PendingServerQuery.PendingServerQueryListener pendingServerQueryListener; public final IdentityDelegate identityDelegate; @@ -49,9 +47,8 @@ public class FetchManagerSession implements AutoCloseable { public FetchManagerSession(Session session, InboxMessage.InboxMessageListener inboxMessageListener, InboxMessage.ExtendedPayloadListener extendedPayloadListener, - InboxMessage.MarkAsListedOnServerListener markAsListedOnServerListener, + InboxMessage.MarkAsListedAndDeleteOnServerListener markAsListedAndDeleteOnServerListener, InboxAttachment.InboxAttachmentListener inboxAttachmentListener, - PendingDeleteFromServer.PendingDeleteFromServerListener pendingDeleteFromServerListener, PushNotificationConfiguration.NewPushNotificationConfigurationListener newPushNotificationConfigurationListener, PendingServerQuery.PendingServerQueryListener pendingServerQueryListener, IdentityDelegate identityDelegate, @@ -61,9 +58,8 @@ public FetchManagerSession(Session session, this.session = session; this.inboxMessageListener = inboxMessageListener; this.extendedPayloadListener = extendedPayloadListener; - this.markAsListedOnServerListener = markAsListedOnServerListener; + this.markAsListedAndDeleteOnServerListener = markAsListedAndDeleteOnServerListener; this.inboxAttachmentListener = inboxAttachmentListener; - this.pendingDeleteFromServerListener = pendingDeleteFromServerListener; this.newPushNotificationConfigurationListener = newPushNotificationConfigurationListener; this.pendingServerQueryListener = pendingServerQueryListener; this.identityDelegate = identityDelegate; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSessionFactory.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSessionFactory.java index 7a0dfbed..53126355 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSessionFactory.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/datatypes/FetchManagerSessionFactory.java @@ -22,6 +22,11 @@ import java.sql.SQLException; +import io.olvid.engine.datatypes.Identity; + public interface FetchManagerSessionFactory { FetchManagerSession getSession() throws SQLException; + void markOwnedIdentityAsUpToDate(Identity ownedIdentity); + void markOwnedIdentityAsNotUpToDate(Identity ownedIdentity); + } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DeleteMessageAndAttachmentFromServerAndLocalInboxesOperation.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DeleteMessageAndAttachmentFromServerAndLocalInboxesOperation.java index 32d6ea1b..f0de2fcb 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DeleteMessageAndAttachmentFromServerAndLocalInboxesOperation.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DeleteMessageAndAttachmentFromServerAndLocalInboxesOperation.java @@ -33,7 +33,6 @@ import io.olvid.engine.encoder.Encoded; import io.olvid.engine.networkfetch.coordinators.DeleteMessageAndAttachmentsCoordinator; import io.olvid.engine.networkfetch.databases.InboxMessage; -import io.olvid.engine.networkfetch.databases.PendingDeleteFromServer; import io.olvid.engine.networkfetch.databases.ServerSession; import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; import io.olvid.engine.networkfetch.datatypes.FetchManagerSessionFactory; @@ -79,29 +78,22 @@ public void doExecute() { try { this.messageUidsAndMarkAsListed = messageBatchProvider.getBatchOFMessageUids(); - - List messageAndPendingDeletes = new ArrayList<>(); + List messageAndPendingDeletes = new ArrayList<>(); for (UidAndBoolean messageUidAndMarkAsListed : messageUidsAndMarkAsListed) { // message may be null in case of re-list of an already deleted message InboxMessage message = InboxMessage.get(fetchManagerSession, ownedIdentity, messageUidAndMarkAsListed.uid); - PendingDeleteFromServer pendingDeleteFromServer = null; if (messageUidAndMarkAsListed.bool) { - if (message == null || message.isMarkedAsListedOnServer()) { + if (message == null) { continue; } } else { if (message != null && !message.canBeDeleted()) { continue; } - - pendingDeleteFromServer = PendingDeleteFromServer.get(fetchManagerSession, ownedIdentity, messageUidAndMarkAsListed.uid); - if (pendingDeleteFromServer == null) { - continue; - } } - messageAndPendingDeletes.add(new MessageAndPendingDeleteAndMarkAsListed(message, pendingDeleteFromServer, messageUidAndMarkAsListed)); + messageAndPendingDeletes.add(new MessageAndMarkAsListed(message, messageUidAndMarkAsListed)); } if (messageAndPendingDeletes.isEmpty()) { @@ -142,14 +134,9 @@ public void doExecute() { fetchManagerSession.session.startTransaction(); switch (returnStatus) { case ServerMethod.OK: - for (MessageAndPendingDeleteAndMarkAsListed messageAndPendingDeleteAndMarkAsListed : messageAndPendingDeletes) { - if (messageAndPendingDeleteAndMarkAsListed.messageUidAndMarkAsListed.bool) { - messageAndPendingDeleteAndMarkAsListed.message.markAsListedOnServer(); - } else { - messageAndPendingDeleteAndMarkAsListed.pendingDeleteFromServer.delete(); - if (messageAndPendingDeleteAndMarkAsListed.message != null) { - messageAndPendingDeleteAndMarkAsListed.message.delete(); - } + for (MessageAndMarkAsListed messageAndMarkAsListed : messageAndPendingDeletes) { + if (!messageAndMarkAsListed.messageUidAndMarkAsListed.bool && messageAndMarkAsListed.message != null) { + messageAndMarkAsListed.message.delete(); } } finished = true; @@ -237,14 +224,12 @@ protected boolean isActiveIdentityRequired() { } } -class MessageAndPendingDeleteAndMarkAsListed { +class MessageAndMarkAsListed { final InboxMessage message; - final PendingDeleteFromServer pendingDeleteFromServer; final UidAndBoolean messageUidAndMarkAsListed; - public MessageAndPendingDeleteAndMarkAsListed(InboxMessage message, PendingDeleteFromServer pendingDeleteFromServer, UidAndBoolean messageUidAndMarkAsListed) { + public MessageAndMarkAsListed(InboxMessage message, UidAndBoolean messageUidAndMarkAsListed) { this.message = message; - this.pendingDeleteFromServer = pendingDeleteFromServer; this.messageUidAndMarkAsListed = messageUidAndMarkAsListed; } } \ No newline at end of file diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DownloadMessagesAndListAttachmentsOperation.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DownloadMessagesAndListAttachmentsOperation.java index 7a21dada..4649e061 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DownloadMessagesAndListAttachmentsOperation.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/DownloadMessagesAndListAttachmentsOperation.java @@ -164,6 +164,9 @@ public void doExecute() { ); } } + } else { + // we relisted a message --> mark it as listed + fetchManagerSession.markAsListedAndDeleteOnServerListener.messageCanBeMarkedAsListedOnServer(ownedIdentity, messageAndAttachmentLengths.messageUid); } } Logger.d("DownloadMessagesAndListAttachmentsOperation found " + messageAndAttachmentLengthsArray.length + " messages (" + count + " new) on the server."); diff --git a/obv_engine/engine/src/test/java/io/olvid/engine/crypto/AsymmetricCryptoUnitTest.java b/obv_engine/engine/src/test/java/io/olvid/engine/crypto/AsymmetricCryptoUnitTest.java index 8950b9e7..914dbdc6 100644 --- a/obv_engine/engine/src/test/java/io/olvid/engine/crypto/AsymmetricCryptoUnitTest.java +++ b/obv_engine/engine/src/test/java/io/olvid/engine/crypto/AsymmetricCryptoUnitTest.java @@ -20,6 +20,7 @@ package io.olvid.engine.crypto; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.fasterxml.jackson.core.JsonFactory; @@ -36,9 +37,12 @@ import io.olvid.engine.Logger; import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.Seed; +import io.olvid.engine.datatypes.containers.CiphertextAndKey; +import io.olvid.engine.datatypes.key.asymmetric.EncryptionEciesMDCKeyPair; import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationECSdsaPrivateKey; import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationECSdsaPublicKey; import io.olvid.engine.datatypes.key.asymmetric.SignaturePublicKey; +import io.olvid.engine.datatypes.key.symmetric.AuthEncKey; import io.olvid.engine.encoder.Encoded; public class AsymmetricCryptoUnitTest { @@ -174,4 +178,18 @@ public void test_ServerAuthentication() throws Exception { } } } + + @Test + public void test_kemDecrypt() throws Exception { + PRNGService prng = Suite.getDefaultPRNGService(0); + for (int i=0; i<10; i++) { + EncryptionEciesMDCKeyPair pair = EncryptionEciesMDCKeyPair.generate(prng); + KemEcies256Kem512MDC kem = new KemEcies256Kem512MDC(); + for (int j = 0; j<100; j++) { + CiphertextAndKey ciphertextAndKey = kem.encrypt(pair.getPublicKey(), prng); + AuthEncKey dec = kem.decrypt(pair.getPrivateKey(), ciphertextAndKey.getCiphertext().getBytes()); + assertEquals(ciphertextAndKey.getKey(), dec); + } + } + } } diff --git a/obv_messenger/app/build.gradle b/obv_messenger/app/build.gradle index 99182f49..7ecd4951 100644 --- a/obv_messenger/app/build.gradle +++ b/obv_messenger/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "io.olvid.messenger" minSdkVersion 21 targetSdk 34 - versionCode 238 - versionName "2.2" + versionCode 242 + versionName "2.2.1" vectorDrawables.useSupportLibrary true multiDexEnabled true resourceConfigurations += ['en', 'fr'] @@ -226,7 +226,7 @@ dependencies { // google map integration fullImplementation 'com.google.android.gms:play-services-maps:18.2.0' - fullImplementation 'com.google.android.gms:play-services-location:21.2.0' + fullImplementation 'com.google.android.gms:play-services-location:21.3.0' // Google Drive fullImplementation 'com.google.android.gms:play-services-auth:21.2.0' @@ -241,7 +241,7 @@ dependencies { } // opensource license page - fullImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' + fullImplementation 'com.google.android.gms:play-services-oss-licenses:17.1.0' // ML kit QR-code scanning fullImplementation 'com.google.mlkit:barcode-scanning:17.2.0' diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/EngineNotificationProcessorForMessages.java b/obv_messenger/app/src/main/java/io/olvid/messenger/EngineNotificationProcessorForMessages.java index 8af5b911..51e0e2da 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/EngineNotificationProcessorForMessages.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/EngineNotificationProcessorForMessages.java @@ -194,8 +194,8 @@ public void callback(String notificationName, final HashMap user byte[] messageIdentifier = (byte[]) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_MESSAGE_IDENTIFIER_KEY); Integer attachmentNumber = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_ATTACHMENT_NUMBER_KEY); Float progress = (Float) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_PROGRESS_KEY); - Float speed = (Float) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_SPEED_BPS_KEY); - Integer eta = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_ETA_SECONDS_KEY); +// Float speed = (Float) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_SPEED_BPS_KEY); +// Integer eta = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_DOWNLOAD_PROGRESS_ETA_SECONDS_KEY); if (attachmentNumber == null || progress == null) { break; } @@ -231,14 +231,14 @@ public void callback(String notificationName, final HashMap user } List messageRecipientInfos = db.messageRecipientInfoDao().getAllByEngineMessageIdentifier(bytesOwnedIdentity, engineMessageIdentifier); - if (messageRecipientInfos.size() > 0) { + if (!messageRecipientInfos.isEmpty()) { long timestamp = System.currentTimeMillis(); long messageId = messageRecipientInfos.get(0).messageId; List updatedMessageRecipientInfos = new ArrayList<>(messageRecipientInfos.size()); boolean complete = false; for (MessageRecipientInfo messageRecipientInfo : messageRecipientInfos) { if (messageRecipientInfo.markAttachmentSent(engineNumber)) { - if (messageRecipientInfo.unsentAttachmentNumbers == null) { + if (messageRecipientInfo.unsentAttachmentNumbers == null && messageRecipientInfo.timestampSent == null) { messageRecipientInfo.timestampSent = timestamp; complete = true; } @@ -246,7 +246,7 @@ public void callback(String notificationName, final HashMap user } } - if (updatedMessageRecipientInfos.size() > 0) { + if (!updatedMessageRecipientInfos.isEmpty()) { db.messageRecipientInfoDao().update(updatedMessageRecipientInfos.toArray(new MessageRecipientInfo[0])); Message message = db.messageDao().get(messageId); @@ -271,8 +271,8 @@ public void callback(String notificationName, final HashMap user byte[] messageIdentifier = (byte[]) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_MESSAGE_IDENTIFIER_KEY); Integer attachmentNumber = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_ATTACHMENT_NUMBER_KEY); Float progress = (Float) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_PROGRESS_KEY); - Float speed = (Float) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_SPEED_BPS_KEY); - Integer eta = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_ETA_SECONDS_KEY); +// Float speed = (Float) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_SPEED_BPS_KEY); +// Integer eta = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_PROGRESS_ETA_SECONDS_KEY); if (attachmentNumber == null || progress == null) { break; } @@ -283,6 +283,7 @@ public void callback(String notificationName, final HashMap user break; } case EngineNotifications.ATTACHMENT_UPLOAD_CANCELLED: { + // TODO: handle this differently than ATTACHMENT_UPLOADED to show users that the attachment was never actually sent. byte[] bytesOwnedIdentity = (byte[]) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_CANCELLED_BYTES_OWNED_IDENTITY_KEY); byte[] engineMessageIdentifier = (byte[]) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_CANCELLED_MESSAGE_IDENTIFIER_KEY); Integer engineNumber = (Integer) userInfo.get(EngineNotifications.ATTACHMENT_UPLOAD_CANCELLED_ATTACHMENT_NUMBER_KEY); @@ -295,7 +296,7 @@ public void callback(String notificationName, final HashMap user } List messageRecipientInfos = db.messageRecipientInfoDao().getAllByEngineMessageIdentifier(bytesOwnedIdentity, engineMessageIdentifier); - if (messageRecipientInfos.size() > 0) { + if (!messageRecipientInfos.isEmpty()) { long timestamp = System.currentTimeMillis(); long messageId = messageRecipientInfos.get(0).messageId; List updatedMessageRecipientInfos = new ArrayList<>(messageRecipientInfos.size()); @@ -310,7 +311,7 @@ public void callback(String notificationName, final HashMap user } } - if (updatedMessageRecipientInfos.size() > 0) { + if (!updatedMessageRecipientInfos.isEmpty()) { db.messageRecipientInfoDao().update(updatedMessageRecipientInfos.toArray(new MessageRecipientInfo[0])); Message message = db.messageDao().get(messageId); @@ -336,7 +337,7 @@ public void callback(String notificationName, final HashMap user Long timestampFromServer = (Long) userInfo.get(EngineNotifications.MESSAGE_UPLOADED_TIMESTAMP_FROM_SERVER); List messageRecipientInfos = db.messageRecipientInfoDao().getAllByEngineMessageIdentifier(bytesOwnedIdentity, engineMessageIdentifier); - if (messageRecipientInfos.size() > 0) { + if (!messageRecipientInfos.isEmpty()) { long messageId = messageRecipientInfos.get(0).messageId; List updatedMessageRecipientInfos = new ArrayList<>(messageRecipientInfos.size()); for (MessageRecipientInfo messageRecipientInfo : messageRecipientInfos) { @@ -346,7 +347,7 @@ public void callback(String notificationName, final HashMap user } } - if (updatedMessageRecipientInfos.size() > 0) { + if (!updatedMessageRecipientInfos.isEmpty()) { db.messageRecipientInfoDao().update(updatedMessageRecipientInfos.toArray(new MessageRecipientInfo[0])); Message message = db.messageDao().get(messageId); @@ -370,7 +371,7 @@ public void callback(String notificationName, final HashMap user byte[] engineMessageIdentifier = (byte[]) userInfo.get(EngineNotifications.MESSAGE_UPLOAD_FAILED_IDENTIFIER_KEY); List messageRecipientInfos = db.messageRecipientInfoDao().getAllByEngineMessageIdentifier(bytesOwnedIdentity, engineMessageIdentifier); - if (messageRecipientInfos.size() > 0) { + if (!messageRecipientInfos.isEmpty()) { long messageId = messageRecipientInfos.get(0).messageId; boolean hasUnsentMessageRecipientInfo = false; diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/activities/MessageDetailsActivity.java b/obv_messenger/app/src/main/java/io/olvid/messenger/activities/MessageDetailsActivity.java index c1d63450..b0d2bbb1 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/activities/MessageDetailsActivity.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/activities/MessageDetailsActivity.java @@ -146,6 +146,7 @@ public class MessageDetailsActivity extends LockableActivity { RecyclerView attachmentsRecyclerView; MessageAttachmentAdapter attachmentAdapter; LiveData repliedToMessage; + TextView messageStatusTextView; private Timer updateTimer; private Long expirationTimestamp; @@ -241,6 +242,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { messageCardView.setSizeChangeListener(this::recomputeMessageLayout); + // metadata metadataRecyclerView = findViewById(R.id.message_metadata_recycler_view); if (isInbound || sendFromOtherDevice) { @@ -276,6 +278,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { recipientInfosRecyclerView.addItemDecoration(recipientInfoHeaderAndSeparatorDecoration); messageDetailsViewModel.getMessageRecipientInfos().observe(this, recipientInfosAdapter); } + + // outbound status + messageStatusTextView = findViewById(R.id.message_status); + View statusIndicator = findViewById(R.id.message_details_status_indicator); + if (statusIndicator != null) { + statusIndicator.setOnClickListener(this::statusClicked); + } } @@ -361,6 +370,22 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void statusClicked(View view) { + View dialogView = getLayoutInflater().inflate(R.layout.dialog_view_message_status_explanation, null); + final AlertDialog alertDialog = new SecureAlertDialogBuilder(this, R.style.CustomAlertDialog) + .setView(dialogView) + .create(); + dialogView.findViewById(R.id.ok_button).setOnClickListener(v -> { + alertDialog.dismiss(); + }); + Window window = alertDialog.getWindow(); + if (window != null) { + window.setBackgroundDrawableResource(android.R.color.transparent); + } + alertDialog.show(); + } + + boolean messageWasNonNull = false; private void displayMessage(Message message) { @@ -504,6 +529,55 @@ private void displayMessage(Message message) { messageStatusImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_message_status_processing)); } } + if (messageStatusTextView != null) { + switch (message.status) { + case Message.STATUS_SENT: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_sent, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_sent); + break; + } + case Message.STATUS_DELIVERED: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_delivered_one, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_delivered); + break; + } + case Message.STATUS_DELIVERED_AND_READ: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_delivered_and_read_one, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_delivered_and_read); + break; + } + case Message.STATUS_DELIVERED_ALL: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_delivered_all, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_delivered_all); + break; + } + case Message.STATUS_DELIVERED_ALL_READ_ONE: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_delivered_all_read_one, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_delivered_all_read_one); + break; + } + case Message.STATUS_DELIVERED_ALL_READ_ALL: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_delivered_all_read_all, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_delivered_all_read_all); + break; + } + case Message.STATUS_UNDELIVERED: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_undelivered, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_undelivered); + break; + } + case Message.STATUS_SENT_FROM_ANOTHER_DEVICE: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_sent_from_other_device, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_sent_from_another_device); + break; + } + default: { + messageStatusTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_message_status_processing, 0, R.drawable.ic_info, 0); + messageStatusTextView.setText(R.string.explanation_message_status_processing); + } + } + } + if ((message.status == Message.STATUS_UNDELIVERED) != this.messageIsUndelivered) { this.messageIsUndelivered = message.status == Message.STATUS_UNDELIVERED; recipientInfosRecyclerView.invalidate(); diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/FyleMessageJoinWithStatusDao.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/FyleMessageJoinWithStatusDao.java index 9ce8911f..7e6e0840 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/FyleMessageJoinWithStatusDao.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/FyleMessageJoinWithStatusDao.java @@ -411,7 +411,7 @@ public interface FyleMessageJoinWithStatusDao { List globalSearch(byte[] bytesOwnedIdentity, String filter, int limit); @Query("SELECT COUNT(*) " + - " FROM " + FyleMessageJoinWithStatus.TABLE_NAME + " AS FMjoin " + + " FROM (SELECT " + FyleMessageJoinWithStatus.FYLE_ID + " FROM " + FyleMessageJoinWithStatus.TABLE_NAME + " AS FMjoin " + " INNER JOIN " + Fyle.TABLE_NAME + " AS fyle " + " ON fyle.id = FMjoin." + FyleMessageJoinWithStatus.FYLE_ID + " INNER JOIN " + Message.TABLE_NAME + " AS mess " + @@ -421,9 +421,9 @@ public interface FyleMessageJoinWithStatusDao { " ON disc.id = mess." + Message.DISCUSSION_ID + " AND disc." + Discussion.BYTES_OWNED_IDENTITY + " = :bytesOwnedIdentity " + " JOIN " + FyleMessageJoinWithStatus.FTS_TABLE_NAME + " ON FMJoin.rowid = " + FyleMessageJoinWithStatus.FTS_TABLE_NAME + ".rowid " + - " WHERE " + FyleMessageJoinWithStatus.FTS_TABLE_NAME + " MATCH :filter" + " WHERE " + FyleMessageJoinWithStatus.FTS_TABLE_NAME + " MATCH :filter LIMIT :limit)" ) - int globalSearchCount(byte[] bytesOwnedIdentity, String filter); + int globalSearchCount(byte[] bytesOwnedIdentity, String filter, int limit); @Query(MEDIA_FYLE_AND_ORIGIN_QUERY + " ORDER BY FMjoin." + FyleMessageJoinWithStatus.SIZE + " ASC ") LiveData> getMediaFyleAndOriginSizeAsc(byte[] bytesOwnedIdentity); diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/MessageDao.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/MessageDao.java index 35648ea8..9ca5805d 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/MessageDao.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/dao/MessageDao.java @@ -94,13 +94,13 @@ public interface MessageDao { " AND " + Message.FTS_TABLE_NAME + " MATCH :query ORDER BY m." + Message.TIMESTAMP + " DESC LIMIT :limit ") List globalSearch(byte[] bytesOwnedIdentity, String query, int limit); - @Query("SELECT COUNT(*) FROM " + Message.TABLE_NAME + " AS m " + + @Query("SELECT COUNT(*) FROM (SELECT m.id FROM " + Message.TABLE_NAME + " AS m " + " INNER JOIN " + Discussion.TABLE_NAME + " AS disc ON disc.id = m." + Message.DISCUSSION_ID + " AND disc." + Discussion.BYTES_OWNED_IDENTITY + " = :bytesOwnedIdentity " + " JOIN " + Message.FTS_TABLE_NAME + " ON m.id = " + Message.FTS_TABLE_NAME + ".rowid" + " WHERE m." + Message.MESSAGE_TYPE + " != " + Message.TYPE_INBOUND_EPHEMERAL_MESSAGE + - " AND " + Message.FTS_TABLE_NAME + " MATCH :query") - int globalSearchCount(byte[] bytesOwnedIdentity, String query); + " AND " + Message.FTS_TABLE_NAME + " MATCH :query LIMIT :limit)") + int globalSearchCount(byte[] bytesOwnedIdentity, String query, int limit); class DiscussionAndMessage { @Embedded(prefix = "disc_") @@ -299,18 +299,18 @@ class DiscussionAndMessage { @Query("SELECT * FROM " + Message.TABLE_NAME + " WHERE id IN (:selectedMessageIds)") List getMany(List selectedMessageIds); - @Query("SELECT " + PREFIX_DISCUSSION_COLUMNS + ",m.* FROM " + Message.TABLE_NAME + " AS m " + - " LEFT JOIN " + Discussion.TABLE_NAME + " AS disc ON disc.id = m." + Message.DISCUSSION_ID + + @Query("SELECT " + PREFIX_DISCUSSION_COLUMNS + ", m.* FROM " + Message.TABLE_NAME + " AS m " + + " JOIN " + Discussion.TABLE_NAME + " AS disc ON disc.id = m." + Message.DISCUSSION_ID + " AND disc." + Discussion.BYTES_OWNED_IDENTITY + " = :bytesOwnedIdentity " + " WHERE m." + Message.BOOKMARKED + " = 1 " + - "ORDER BY m." + Message.SORT_INDEX + " ASC") + " ORDER BY m." + Message.SORT_INDEX + " ASC") List getAllBookmarked(byte[] bytesOwnedIdentity); - @Query("SELECT " + PREFIX_DISCUSSION_COLUMNS + ",m.* FROM " + Message.TABLE_NAME + " AS m " + - " LEFT JOIN " + Discussion.TABLE_NAME + " AS disc ON disc.id = m." + Message.DISCUSSION_ID + + @Query("SELECT " + PREFIX_DISCUSSION_COLUMNS + ", m.* FROM " + Message.TABLE_NAME + " AS m " + + " JOIN " + Discussion.TABLE_NAME + " AS disc ON disc.id = m." + Message.DISCUSSION_ID + " AND disc." + Discussion.BYTES_OWNED_IDENTITY + " = :bytesOwnedIdentity " + " WHERE m." + Message.BOOKMARKED + " = 1 " + - "ORDER BY m." + Message.SORT_INDEX + " ASC") + " ORDER BY m." + Message.SORT_INDEX + " ASC") LiveData> getAllBookmarkedLiveData(byte[] bytesOwnedIdentity); diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/HandleNewMessageNotificationTask.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/HandleNewMessageNotificationTask.java index 39e1441a..d0359704 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/HandleNewMessageNotificationTask.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/HandleNewMessageNotificationTask.java @@ -877,6 +877,8 @@ private void handleDeleteMessages(JsonDeleteMessages jsonDeleteMessages, @NonNul return; } + // TODO: handle the case where discussion is locked and request comes from myself + Discussion discussion = getDiscussion(jsonDeleteMessages.getGroupUid(), jsonDeleteMessages.getGroupOwner(), jsonDeleteMessages.getGroupV2Identifier(), jsonDeleteMessages.getOneToOneIdentifier(), messageSender, null); if (discussion == null) { return; diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/MessageLongPressPopUp.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/MessageLongPressPopUp.kt index 3c4fc042..eecef00c 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/MessageLongPressPopUp.kt +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/MessageLongPressPopUp.kt @@ -485,6 +485,7 @@ class MessageLongPressPopUp( && message?.wipeStatus != Message.WIPE_STATUS_REMOTE_DELETED && message?.isLocationMessage == false ) { + // TODO: check EDIT_OR_DELETE_OWNED_MESSAGES permission for group V2 add(EDIT) } if (discussion?.isNormal == true diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchViewModel.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchViewModel.kt index edf096c5..6b793054 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchViewModel.kt +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchViewModel.kt @@ -183,8 +183,10 @@ class GlobalSearchViewModel : ViewModel() { messageLimitReachedCount = if (messages.size >= MESSAGE_SEARCH_LIMIT) { - val count = AppDatabase.getInstance().messageDao().globalSearchCount(bytesOwnedIdentity, tokenizedQuery) - if (count > MESSAGE_SEARCH_LIMIT) { + val count = AppDatabase.getInstance().messageDao().globalSearchCount(bytesOwnedIdentity, tokenizedQuery, 5 * MESSAGE_SEARCH_LIMIT + 1) + if (count > 5 * MESSAGE_SEARCH_LIMIT) { + "${5 * MESSAGE_SEARCH_LIMIT}+" + } else if (count > MESSAGE_SEARCH_LIMIT) { count.toString() } else { null @@ -202,8 +204,10 @@ class GlobalSearchViewModel : ViewModel() { attachmentLimitReachedCount = if (fyles.size >= ATTACHMENT_SEARCH_LIMIT) { - val count = AppDatabase.getInstance().fyleMessageJoinWithStatusDao().globalSearchCount(bytesOwnedIdentity, tokenizedQuery) - if (count > ATTACHMENT_SEARCH_LIMIT) { + val count = AppDatabase.getInstance().fyleMessageJoinWithStatusDao().globalSearchCount(bytesOwnedIdentity, tokenizedQuery, 5 * ATTACHMENT_SEARCH_LIMIT + 1) + if (count > 5 * ATTACHMENT_SEARCH_LIMIT) { + "${5 * ATTACHMENT_SEARCH_LIMIT}+" + } else if (count > ATTACHMENT_SEARCH_LIMIT) { count.toString() } else { null diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/services/UnifiedForegroundService.java b/obv_messenger/app/src/main/java/io/olvid/messenger/services/UnifiedForegroundService.java index f407a543..53d6f68b 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/services/UnifiedForegroundService.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/services/UnifiedForegroundService.java @@ -804,7 +804,9 @@ private static void scheduleBackgroundLockIfNeeded(Context context) { noExactAlarmTimer.schedule(new TimerTask() { @Override public void run() { - App.getContext().startService(lockIntent); + try { + App.getContext().startService(lockIntent); + } catch (Exception ignored) {} } }, 1000L * timeout); } diff --git a/obv_messenger/app/src/main/jniLibs/arm64-v8a/libcrypto_1_1.so b/obv_messenger/app/src/main/jniLibs/arm64-v8a/libcrypto_1_1.so old mode 100755 new mode 100644 diff --git a/obv_messenger/app/src/main/jniLibs/armeabi-v7a/libcrypto_1_1.so b/obv_messenger/app/src/main/jniLibs/armeabi-v7a/libcrypto_1_1.so old mode 100755 new mode 100644 diff --git a/obv_messenger/app/src/main/jniLibs/x86/libcrypto_1_1.so b/obv_messenger/app/src/main/jniLibs/x86/libcrypto_1_1.so old mode 100755 new mode 100644 diff --git a/obv_messenger/app/src/main/jniLibs/x86_64/libcrypto_1_1.so b/obv_messenger/app/src/main/jniLibs/x86_64/libcrypto_1_1.so old mode 100755 new mode 100644 diff --git a/obv_messenger/app/src/main/res/drawable/ic_message_status_sent_from_other_device.xml b/obv_messenger/app/src/main/res/drawable/ic_message_status_sent_from_other_device.xml index 75995dad..1831db8f 100644 --- a/obv_messenger/app/src/main/res/drawable/ic_message_status_sent_from_other_device.xml +++ b/obv_messenger/app/src/main/res/drawable/ic_message_status_sent_from_other_device.xml @@ -1,4 +1,4 @@ - + diff --git a/obv_messenger/app/src/main/res/drawable/ic_message_status_undelivered.xml b/obv_messenger/app/src/main/res/drawable/ic_message_status_undelivered.xml index 991a8441..f6c6bc29 100644 --- a/obv_messenger/app/src/main/res/drawable/ic_message_status_undelivered.xml +++ b/obv_messenger/app/src/main/res/drawable/ic_message_status_undelivered.xml @@ -1,6 +1,6 @@ - + android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/obv_messenger/app/src/main/res/layout/activity_message_details.xml b/obv_messenger/app/src/main/res/layout/activity_message_details.xml index 39f15d84..489844ef 100644 --- a/obv_messenger/app/src/main/res/layout/activity_message_details.xml +++ b/obv_messenger/app/src/main/res/layout/activity_message_details.xml @@ -64,6 +64,13 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + +