diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ae0652..bf75d04e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Build 236 (2.1.1) +2024-05-01 + +- Hotfix: when sqlcipher migration fails, continue using the old un-encrypted database +- Fix some edge case where one-to-one status could be lost in multi-device + # Build 235 (2.1) 2024-04-25 diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/AsymmetricChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/AsymmetricChannel.java index 36e9311f..3f83bb7c 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/AsymmetricChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/AsymmetricChannel.java @@ -65,6 +65,9 @@ public static AuthEncKeyAndChannelInfo unwrapMessageKey(ChannelManagerSession ch return null; } AuthEncKey messageKey = channelManagerSession.encryptionForIdentityDelegate.unwrap(channelManagerSession.session, header.getWrappedKey(), header.getOwnedIdentity()); + if (messageKey == null) { + return null; + } return new AuthEncKeyAndChannelInfo(messageKey, ReceptionChannelInfo.createAsymmetricChannelInfo()); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Session.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Session.java index c8dfc455..0c60607c 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Session.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Session.java @@ -109,7 +109,7 @@ public static Session getSession(String dbPath, String dbKey) throws SQLExceptio sessionPoolLock.lock(); List sessionList = sessionPool.get(dbPath); - if ((sessionList == null) || (sessionList.size() == 0)) { + if ((sessionList == null) || (sessionList.isEmpty())) { sessionPoolLock.unlock(); session = new Session(dbPath, dbKey, false); } else { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/Engine.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/Engine.java index 2f028f06..6acc9c1e 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/Engine.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/Engine.java @@ -195,6 +195,10 @@ public Engine(File baseDirectory, ObvBackupAndSyncDelegate appBackupAndSyncDeleg File dbFile = new File(baseDirectory, Constants.ENGINE_DB_FILENAME); File tmpEncryptedDbFile = new File(baseDirectory, Constants.TMP_ENGINE_ENCRYPTED_DB_FILENAME); + if (tmpEncryptedDbFile.exists()) { + //noinspection ResultOfMethodCallIgnored + tmpEncryptedDbFile.delete(); + } try (Session session = Session.getUpgradeTablesSession(dbPath, null)) { try (Statement statement = session.createStatement()) { @@ -217,8 +221,9 @@ public Engine(File baseDirectory, ObvBackupAndSyncDelegate appBackupAndSyncDeleg throw new RuntimeException("Engine database encryption error: unable to delete unencrypted database!"); } } catch (Exception fatal) { - // database is encrypted but not with the provided dbKey! - throw new RuntimeException("Database seems encrypted but cannot be opened with provided dbKey", fatal); + // database is encrypted but not with the provided dbKey, or database encryption failed --> try disabling encryption to use a plain database + Logger.e("Engine database encryption failed, falling back to un-encrypted database"); + dbKey = null; } } @@ -260,7 +265,8 @@ public Engine(File baseDirectory, ObvBackupAndSyncDelegate appBackupAndSyncDeleg MetaManager metaManager = new MetaManager(); - this.createSessionDelegate = () -> Session.getSession(dbPath, dbKey); + String finalDbKey = dbKey; + this.createSessionDelegate = () -> Session.getSession(dbPath, finalDbKey); metaManager.registerImplementedDelegates(this.createSessionDelegate); metaManager.registerImplementedDelegates(this); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/IdentityManager.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/IdentityManager.java index 24461e20..4f1a879b 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/IdentityManager.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/IdentityManager.java @@ -1653,11 +1653,20 @@ public boolean isIdentityAOneToOneContactOfOwnedIdentity(Session session, Identi return (contactIdentityObject != null && contactIdentityObject.isOneToOne()); } + @Override + public boolean isIdentityANotOneToOneContactOfOwnedIdentity(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException { + ContactIdentity contactIdentityObject = ContactIdentity.get(wrapSession(session), ownedIdentity, contactIdentity); + return (contactIdentityObject != null && contactIdentityObject.isNotOneToOne()); + } + + + // this method always sets to ONE_TO_ONE_STATUS_TRUE or ONE_TO_ONE_STATUS_FALSE, but never leaves in ONE_TO_ONE_STATUS_UNKNOWN @Override public void setContactOneToOne(Session session, Identity ownedIdentity, Identity contactIdentity, boolean oneToOne) throws SQLException { ContactIdentity contactIdentityObject = ContactIdentity.get(wrapSession(session), ownedIdentity, contactIdentity); - // only actually call the setter if the oneToOne is changed - if (contactIdentityObject != null && contactIdentityObject.isOneToOne() != oneToOne) { + // only actually call the setter if the oneToOne is changed (or if contact oneToOne was unknown) + if (contactIdentityObject != null + && ((oneToOne && !contactIdentityObject.isOneToOne()) || (!oneToOne && !contactIdentityObject.isNotOneToOne()))) { contactIdentityObject.setOneToOne(oneToOne); } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentity.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentity.java index 569186eb..919b5b6a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentity.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentity.java @@ -79,11 +79,16 @@ public class ContactIdentity implements ObvDatabase { static final String REVOKED_AS_COMPROMISED = "revoked_as_compromised"; public boolean forcefullyTrustedByUser; static final String FORCEFULLY_TRUSTED_BY_USER = "forcefully_trusted_by_user"; - private boolean oneToOne; + private int oneToOne; static final String ONE_TO_ONE = "one_to_one"; private long lastNoDeviceContactDeviceDiscovery; static final String LAST_NO_DEVICE_CONTACT_DEVICE_DISCOVERY = "last_no_device_contact_device_discovery"; + + public static final int ONE_TO_ONE_STATUS_FALSE = 0; + public static final int ONE_TO_ONE_STATUS_TRUE = 1; + public static final int ONE_TO_ONE_STATUS_UNKNOWN = 2; + public Identity getContactIdentity() { return contactIdentity; } @@ -121,7 +126,10 @@ public boolean isActive() { } public boolean isOneToOne() { - return oneToOne; + return oneToOne == ONE_TO_ONE_STATUS_TRUE; + } + public boolean isNotOneToOne() { + return oneToOne == ONE_TO_ONE_STATUS_FALSE; } public long getLastNoDeviceContactDeviceDiscovery() { @@ -338,18 +346,22 @@ public void setCertifiedByOwnKeycloak(boolean certifiedByOwnKeycloak, String las } } + // this method always sets to ONE_TO_ONE_STATUS_TRUE or ONE_TO_ONE_STATUS_FALSE, but never leaves in ONE_TO_ONE_STATUS_UNKNOWN public void setOneToOne(boolean oneToOne) throws SQLException { try (PreparedStatement statement = identityManagerSession.session.prepareStatement("UPDATE " + TABLE_NAME + " SET " + ONE_TO_ONE + " = ? " + " WHERE " + CONTACT_IDENTITY + " = ? " + " AND " + OWNED_IDENTITY + " = ?;")) { - statement.setBoolean(1, oneToOne); + statement.setInt(1, oneToOne ? ONE_TO_ONE_STATUS_TRUE : ONE_TO_ONE_STATUS_FALSE); statement.setBytes(2, contactIdentity.getBytes()); statement.setBytes(3, ownedIdentity.getBytes()); statement.executeUpdate(); - this.oneToOne = oneToOne; - commitHookBits |= HOOK_BIT_ONE_TO_ONE_CHANGED; - identityManagerSession.session.addSessionCommitListener(this); + // do not notify when changing from unknown to false (normally this setter is not called in that case, but let's make sure! + if (isOneToOne() != oneToOne) { + commitHookBits |= HOOK_BIT_ONE_TO_ONE_CHANGED; + identityManagerSession.session.addSessionCommitListener(this); + } + this.oneToOne = oneToOne ? ONE_TO_ONE_STATUS_TRUE : ONE_TO_ONE_STATUS_FALSE; } } @@ -577,8 +589,8 @@ public static ContactIdentity create(IdentityManagerSession identityManagerSessi throw new SQLException(); } - - ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, contactIdentityDetails.getVersion(), new TrustLevel(0, 0), oneToOne); + // when creating a not one-to-one contact, set their one-to-one status as unknown + ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, contactIdentityDetails.getVersion(), new TrustLevel(0, 0), oneToOne ? ONE_TO_ONE_STATUS_TRUE : ONE_TO_ONE_STATUS_UNKNOWN); contactIdentityObject.revokedAsCompromised = revokedAsCompromised; contactIdentityObject.insert(); @@ -609,7 +621,7 @@ public static ContactIdentity create(IdentityManagerSession identityManagerSessi } } - public ContactIdentity(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, int version, TrustLevel trustLevel, boolean oneToOne) { + public ContactIdentity(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, int version, TrustLevel trustLevel, int oneToOne) { this.identityManagerSession = identityManagerSession; this.contactIdentity = contactIdentity; this.ownedIdentity = ownedIdentity; @@ -637,7 +649,7 @@ private ContactIdentity(IdentityManagerSession identityManagerSession, ResultSet this.certifiedByOwnKeycloak = res.getBoolean(CERTIFIED_BY_OWN_KEYCLOAK); this.revokedAsCompromised = res.getBoolean(REVOKED_AS_COMPROMISED); this.forcefullyTrustedByUser = res.getBoolean(FORCEFULLY_TRUSTED_BY_USER); - this.oneToOne = res.getBoolean(ONE_TO_ONE); + this.oneToOne = res.getInt(ONE_TO_ONE); this.lastNoDeviceContactDeviceDiscovery = res.getLong(LAST_NO_DEVICE_CONTACT_DEVICE_DISCOVERY); } @@ -882,7 +894,7 @@ public void insert() throws SQLException { statement.setBoolean(6, certifiedByOwnKeycloak); statement.setBoolean(7, revokedAsCompromised); statement.setBoolean(8, forcefullyTrustedByUser); - statement.setBoolean(9, oneToOne); + statement.setInt(9, oneToOne); statement.setLong(10, lastNoDeviceContactDeviceDiscovery); statement.executeUpdate(); commitHookBits |= HOOK_BIT_INSERTED; @@ -1179,7 +1191,21 @@ Pojo_0 backup() throws SQLException { pojo.trust_level = trustLevel.toString(); pojo.revoked = revokedAsCompromised; pojo.forcefully_trusted = forcefullyTrustedByUser; - pojo.one_to_one = oneToOne; + switch (oneToOne) { + case ONE_TO_ONE_STATUS_TRUE: { + pojo.one_to_one = true; + break; + } + case ONE_TO_ONE_STATUS_FALSE: { + pojo.one_to_one = false; + break; + } + case ONE_TO_ONE_STATUS_UNKNOWN: + default: { + pojo.one_to_one = null; + break; + } + } pojo.trust_origins = ContactTrustOrigin.backupAll(identityManagerSession, ownedIdentity, contactIdentity); pojo.contact_groups = ContactGroup.backupAllForOwner(identityManagerSession, ownedIdentity, contactIdentity); @@ -1205,7 +1231,7 @@ private static void restoreContact(IdentityManagerSession identityManagerSession published_details = ContactIdentityDetails.restore(identityManagerSession, ownedIdentity, contactIdentity, pojo.published_details); } - ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, trusted_details.getVersion(), TrustLevel.of(pojo.trust_level), (pojo.one_to_one == null || pojo.one_to_one)); + ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, trusted_details.getVersion(), TrustLevel.of(pojo.trust_level), pojo.one_to_one == null ? ONE_TO_ONE_STATUS_UNKNOWN : (pojo.one_to_one ? ONE_TO_ONE_STATUS_TRUE : ONE_TO_ONE_STATUS_FALSE)); if (published_details != null) { contactIdentityObject.publishedDetailsVersion = published_details.getVersion(); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/ContactSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/ContactSyncSnapshot.java index e6935636..ec4b1a41 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/ContactSyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/ContactSyncSnapshot.java @@ -75,7 +75,7 @@ public static ContactSyncSnapshot of(IdentityManagerSession identityManagerSessi contactSyncSnapshot.published_details = IdentityDetailsSyncSnapshot.of(identityManagerSession, publishedDetails); } - contactSyncSnapshot.one_to_one = contact.isOneToOne() ? true : null; + contactSyncSnapshot.one_to_one = contact.isOneToOne() ? Boolean.TRUE : (contact.isNotOneToOne() ? Boolean.FALSE : null); contactSyncSnapshot.revoked = contact.isRevokedAsCompromised() ? true : null; @@ -109,7 +109,7 @@ public ContactIdentity restore(IdentityManagerSession identityManagerSession, Id } TrustLevel trustLevel = (domain.contains(TRUST_LEVEL) && trust_level != null) ? TrustLevel.of(trust_level) : new TrustLevel(0, 0); - boolean oneToOne = (domain.contains(ONE_TO_ONE) && one_to_one != null) ? one_to_one : !domain.contains(ONE_TO_ONE); + int oneToOne = (domain.contains(ONE_TO_ONE) && one_to_one != null) ? (one_to_one ? ContactIdentity.ONE_TO_ONE_STATUS_TRUE : ContactIdentity.ONE_TO_ONE_STATUS_FALSE) : ContactIdentity.ONE_TO_ONE_STATUS_UNKNOWN; ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, trustedDetails.getVersion(), trustLevel, oneToOne); if (publishedDetails != null) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/IdentityDelegate.java b/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/IdentityDelegate.java index 6263c7c0..e0316243 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/IdentityDelegate.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/metamanager/IdentityDelegate.java @@ -140,6 +140,7 @@ public interface IdentityDelegate { boolean isIdentityAnActiveContactOfOwnedIdentity(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; boolean isIdentityAContactOfOwnedIdentity(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; boolean isIdentityAOneToOneContactOfOwnedIdentity(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; + boolean isIdentityANotOneToOneContactOfOwnedIdentity(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; void setContactOneToOne(Session session, Identity ownedIdentity, Identity contactIdentity, boolean oneToOne) throws SQLException; // TrustLevel getContactIdentityTrustLevel(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; EnumSet getContactActiveOrInactiveReasons(Session session, Identity ownedIdentity, Identity contactIdentity) throws SQLException; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OneToOneContactInvitationProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OneToOneContactInvitationProtocol.java index 7ae6e40a..55888fb5 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OneToOneContactInvitationProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OneToOneContactInvitationProtocol.java @@ -896,11 +896,6 @@ public ConcreteProtocolState executeStep() throws Exception { ChannelMessageToSend messageToSend = new AbortMessage(coreProtocolMessage).generateChannelProtocolMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } - - { - // mark Bob as not oneToOne - protocolManagerSession.identityDelegate.setContactOneToOne(protocolManagerSession.session, getOwnedIdentity(), startState.contactIdentity, false); - } } { @@ -947,11 +942,6 @@ public ConcreteProtocolState executeStep() throws Exception { return startState; } - { - // mark Alice as not oneToOne - protocolManagerSession.identityDelegate.setContactOneToOne(protocolManagerSession.session, getOwnedIdentity(), startState.contactIdentity, false); - } - { // remove the dialog CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createDeleteDialog(), startState.dialogUuid)); @@ -1104,11 +1094,6 @@ public AliceProcessesPropagatedAbortStep(InvitationSentState startState, Propaga public ConcreteProtocolState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); - { - // mark Bob as not oneToOne - protocolManagerSession.identityDelegate.setContactOneToOne(protocolManagerSession.session, getOwnedIdentity(), startState.contactIdentity, false); - } - { // remove the dialog CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createDeleteDialog(), startState.dialogUuid)); @@ -1139,15 +1124,13 @@ public ConcreteProtocolState executeStep() throws Exception { { - // check whether the response message received from Bob matches the oneToOne status we have for Bob in db. - // if this is the case, do nothing - if (protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.getReceptionChannelInfo().getRemoteIdentity()) == receivedMessage.invitationAccepted) { + // if Bob accepted the invitation, there is nothing to do: we never upgrade him for now reason, and won't tell him to downgrade. + if (receivedMessage.invitationAccepted) { return new FinishedState(); } } - // one of Alice or Bob does not want to be oneToOne --> downgrade locally and start a delete protocol to downgrade remotely - + // Bob sent us an invitation rejected response, we downgrade him { // mark Bob as not oneToOne protocolManagerSession.identityDelegate.setContactOneToOne(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.getReceptionChannelInfo().getRemoteIdentity(), false); @@ -1191,7 +1174,15 @@ public ConcreteProtocolState executeStep() throws Exception { // send a sync message to all contacts, within a try as some contacts without channel may fail for (Identity contactIdentity : contactIdentities) { try { - boolean oneToOne = protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), contactIdentity); + final boolean oneToOne; + if (protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), contactIdentity)) { + oneToOne = true; + } else if (protocolManagerSession.identityDelegate.isIdentityANotOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), contactIdentity)) { + oneToOne = false; + } else { + // if oneToOne status is unknown, do nothing + continue; + } CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createAllConfirmedObliviousChannelsInfo(contactIdentity, getOwnedIdentity())); ChannelMessageToSend messageToSend = new OneToOneStatusSyncRequestMessage(coreProtocolMessage, oneToOne).generateChannelProtocolMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); @@ -1226,7 +1217,16 @@ public ConcreteProtocolState executeStep() throws Exception { { // send a sync message to specific contact. He should have a channel, so fail in case of Exception - boolean oneToOne = protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.contactIdentity); + final boolean oneToOne; + if (protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.contactIdentity)) { + oneToOne = true; + } else if (protocolManagerSession.identityDelegate.isIdentityANotOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.contactIdentity)) { + oneToOne = false; + } else { + // if oneToOne status is unknown, do nothing + return new FinishedState(); + } + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createAllConfirmedObliviousChannelsInfo(receivedMessage.contactIdentity, getOwnedIdentity())); ChannelMessageToSend messageToSend = new OneToOneStatusSyncRequestMessage(coreProtocolMessage, oneToOne).generateChannelProtocolMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); @@ -1252,7 +1252,15 @@ public BobProcessesSyncRequestStep(InitialProtocolState startState, OneToOneStat public ConcreteProtocolState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); - boolean aliceIsOneToOne = protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.getReceptionChannelInfo().getRemoteIdentity()); + final boolean aliceIsOneToOne; + if (protocolManagerSession.identityDelegate.isIdentityAOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.getReceptionChannelInfo().getRemoteIdentity())) { + aliceIsOneToOne = true; + } else if (protocolManagerSession.identityDelegate.isIdentityANotOneToOneContactOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity(), receivedMessage.getReceptionChannelInfo().getRemoteIdentity())) { + aliceIsOneToOne = false; + } else { + // if oneToOne status is unknown, do nothing + return new FinishedState(); + } if (aliceIsOneToOne != receivedMessage.aliceConsidersBobAsOneToOne) { if (aliceIsOneToOne) { diff --git a/obv_messenger/app/build.gradle b/obv_messenger/app/build.gradle index 9da95df7..39fb1c8d 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 235 - versionName "2.1" + versionCode 236 + versionName "2.1.1" vectorDrawables.useSupportLibrary true multiDexEnabled true resourceConfigurations += ['en', 'fr'] diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/AppDatabase.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/AppDatabase.java index 544df7f2..9d6628c7 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/AppDatabase.java +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/AppDatabase.java @@ -212,6 +212,10 @@ public static AppDatabase getInstance() { try { int oldUserVersion = -1; File tmpEncryptedDbFile = new File(App.absolutePathFromRelative(TMP_ENCRYPTED_DB_FILE_NAME)); + if (tmpEncryptedDbFile.exists()) { + //noinspection ResultOfMethodCallIgnored + tmpEncryptedDbFile.delete(); + } try (SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile.getPath(), "", null, SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.CREATE_IF_NECESSARY, null)) { db.rawExecSQL("ATTACH DATABASE '" + tmpEncryptedDbFile.getPath() + "' AS encrypted KEY \"" + dbKey + "\";"); db.rawExecSQL("SELECT sqlcipher_export('encrypted');"); @@ -243,8 +247,9 @@ public static AppDatabase getInstance() { throw new RuntimeException("App database encryption error: unable to delete unencrypted database!"); } } catch (Exception fatal) { - // database is encrypted but not with the provided dbKey! - throw new RuntimeException("Database seems encrypted but cannot be opened with provided dbKey", fatal); + // database is encrypted but not with the provided dbKey, or database encryption failed --> try disabling encryption to use a plain database + Logger.e("App database encryption failed, falling back to un-encrypted database"); + dbKey = ""; } } 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 6f9470c3..70646d2e 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 @@ -642,7 +642,7 @@ public void run() { engine.markMessageForDeletion(obvMessage.getBytesToIdentity(), obvMessage.getIdentifier()); - if (fyleMessageJoinWithStatusesToDownload.size() > 0) { + if (!fyleMessageJoinWithStatusesToDownload.isEmpty()) { AvailableSpaceHelper.refreshAvailableSpace(false); } diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchScreen.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchScreen.kt index 0ece44ab..ce6e23e1 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchScreen.kt +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/search/GlobalSearchScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -204,13 +205,14 @@ fun GlobalSearchScreen( }, ) Text( + modifier = Modifier.weight(1f), text = AppSingleton.getContactCustomDisplayName(fyle.message.senderIdentifier) ?: stringResource(id = string.text_deleted_contact), fontSize = 14.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Medium ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(Modifier.width(8.dp)) Text( text = StringUtils.getNiceDateString( context, diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/webrtc/WebrtcPeerConnectionHolder.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/webrtc/WebrtcPeerConnectionHolder.kt index 6975bc0d..9136d998 100644 --- a/obv_messenger/app/src/main/java/io/olvid/messenger/webrtc/WebrtcPeerConnectionHolder.kt +++ b/obv_messenger/app/src/main/java/io/olvid/messenger/webrtc/WebrtcPeerConnectionHolder.kt @@ -120,6 +120,8 @@ class WebrtcPeerConnectionHolder( private var audioReceiver: RtpReceiver? = null private var peerAudioLevelListener: Timer? = null + + private var renegociationNeededQueued: Boolean = false var peerAudioLevel by mutableDoubleStateOf(0.0) private set @@ -508,8 +510,8 @@ class WebrtcPeerConnectionHolder( } } - fun createLocalDescription() { - Logger.d("☎ createLocalDescription") + fun createLocalDescription(reason: String) { + Logger.d("☎ createLocalDescription " + reason) // only called from onRenegotiationNeeded when (peerConnection?.signalingState()) { STABLE -> { @@ -604,8 +606,8 @@ class WebrtcPeerConnectionHolder( ) ) - if (shouldCreateLocalDescription) { - createLocalDescription() + if (shouldCreateLocalDescription && !renegociationNeededQueued) { + createLocalDescription("[set remote offer when stable]") } } @@ -987,8 +989,10 @@ class WebrtcPeerConnectionHolder( override fun onRenegotiationNeeded() { // called whenever a peerConnection is created, a track is added, or after a restartICE. May not be called if a negotiation is already in progress Logger.d("☎ onRenegotiationNeeded") + renegociationNeededQueued = true webrtcCallService.synchronizeOnExecutor { - createLocalDescription() + renegociationNeededQueued = false + createLocalDescription("[onRenegotiationNeeded]") } } @@ -1051,7 +1055,7 @@ class WebrtcPeerConnectionHolder( GATHER_CONTINUOUSLY -> peerConnection?.setLocalDescription(object : SdpSetObserver() { override fun onSetSuccess() { - Logger.d("☎ onSetSuccess") + Logger.d("☎ onSetSuccess 1") if (sdp.type == OFFER) { webrtcCallService.sendLocalDescriptionToPeer( callParticipant, @@ -1075,7 +1079,7 @@ class WebrtcPeerConnectionHolder( } override fun onSetSuccess() { - Logger.d("☎ onSetSuccess") + Logger.d("☎ onSetSuccess 2") // called when local or remote description are set // This automatically triggers ICE gathering or connection establishment --> nothing to do for GATHER_ONCE }