diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fe622bb..2c2e76b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,47 @@ -# Build 207 (0.15.0) +# Build 216 (1.0) +2023-10-10 + +- Fixes for compatibility with iOS and Windows versions + +# ~~Build 215 (1.0)~~ +2023-10-07 + +- Final adjustments to the transfer protocol + +# ~~Build 214 (1.0)~~ +2023-09-30 + +- Several improvements to the transfer protocol +- Notifications when a new device is added or the current device is set to be deactivated + +# ~~Build 213 (1.0)~~ +2023-09-27 + +- Fix keycloak issue in the transfer protocol + +# ~~Build 211 (1.0)~~ +2023-09-26 + +- Android 14 compatibility +- Transfer protocol: copy a profile to a new device without using a backup/restore + +# Build 210 (0.15.0.3) +2023-09-05 + +- Fix for a bug in engine backup manager +- Update SQLite JDBC to 3.42.0.1 + +# Build 209 (0.15.0.2) +2023-08-11 + +- Hotfix for a crash on weird Markdown input + +# Build 208 (0.15.0.1) +2023-08-03 + +- Hotfix for a billing library crash + +# Build 207 (0.15.0) 2023-07-31 - Minor fixes for group types diff --git a/obv_engine/engine/build.gradle b/obv_engine/engine/build.gradle index 2b8ca568..06fdaea9 100644 --- a/obv_engine/engine/build.gradle +++ b/obv_engine/engine/build.gradle @@ -5,17 +5,18 @@ repositories { } dependencies { + // do not update further: jackson >2.13 does not work on older Android APIs implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4' - implementation files('libs/sqlite-jdbc-3.41.2.1.jar') + implementation files('libs/sqlite-jdbc-3.42.0.1.jar') - implementation 'org.slf4j:slf4j-api:2.0.7' - implementation 'org.slf4j:slf4j-simple:2.0.7' + implementation 'org.slf4j:slf4j-api:2.0.9' + implementation 'org.slf4j:slf4j-simple:2.0.9' implementation 'org.bitbucket.b_c:jose4j:0.9.3' - implementation 'com.squareup.okhttp3:okhttp:4.10.0' + implementation 'com.squareup.okhttp3:okhttp:4.11.0' implementation 'net.iharder:base64:2.3.9' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.xerial:sqlite-jdbc:3.41.2.1' // only here to check if a new version is available + testImplementation 'org.xerial:sqlite-jdbc:3.42.0.1' // only here to check if a new version is available } diff --git a/obv_engine/engine/libs/sqlite-jdbc-3.41.2.1.jar b/obv_engine/engine/libs/sqlite-jdbc-3.41.2.1.jar deleted file mode 100644 index b5a928ab..00000000 Binary files a/obv_engine/engine/libs/sqlite-jdbc-3.41.2.1.jar and /dev/null differ diff --git a/obv_engine/engine/libs/sqlite-jdbc-3.42.0.1.jar b/obv_engine/engine/libs/sqlite-jdbc-3.42.0.1.jar new file mode 100644 index 00000000..4d0384d7 Binary files /dev/null and b/obv_engine/engine/libs/sqlite-jdbc-3.42.0.1.jar differ diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/backup/BackupManager.java b/obv_engine/engine/src/main/java/io/olvid/engine/backup/BackupManager.java index 47034171..c070fe80 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/backup/BackupManager.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/backup/BackupManager.java @@ -225,8 +225,7 @@ public int verifyBackupKey(String seedString) { BackupSeed backupSeed = new BackupSeed(seedString); BackupSeed.DerivedKeys derivedKeys = backupSeed.deriveKeys(); - if (derivedKeys.backupKeyUid.equals(backupKey.getUid()) && - derivedKeys.macKey.equals(backupKey.getMacKey()) && + if (derivedKeys.macKey.equals(backupKey.getMacKey()) && derivedKeys.encryptionKeyPair.getPublicKey().equals(backupKey.getEncryptionPublicKey())) { // we have the same keys, everything is fine backupKey.addSuccessfulVerification(); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/backup/databases/BackupKey.java b/obv_engine/engine/src/main/java/io/olvid/engine/backup/databases/BackupKey.java index e482ffe1..067123a4 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/backup/databases/BackupKey.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/backup/databases/BackupKey.java @@ -29,6 +29,7 @@ import io.olvid.engine.Logger; import io.olvid.engine.backup.datatypes.BackupManagerSession; +import io.olvid.engine.crypto.Suite; import io.olvid.engine.datatypes.ObvDatabase; import io.olvid.engine.datatypes.Session; import io.olvid.engine.datatypes.UID; @@ -175,13 +176,59 @@ public static void createTable(Session session) throws SQLException { UPLOADED_BACKUP_VERSION + " INTEGER, " + EXPORTED_BACKUP_VERSION + " INTEGER, " + LATEST_BACKUP_VERSION + " INTEGER, " + - "FOREIGN KEY (" + UID_ + "," + UPLOADED_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + ") ON DELETE SET NULL, " + - "FOREIGN KEY (" + UID_ + "," + EXPORTED_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + ") ON DELETE SET NULL, " + - "FOREIGN KEY (" + UID_ + "," + LATEST_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + ") ON DELETE SET NULL);"); + "FOREIGN KEY (" + UID_ + "," + UPLOADED_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + "), " + + "FOREIGN KEY (" + UID_ + "," + EXPORTED_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + "), " + + "FOREIGN KEY (" + UID_ + "," + LATEST_BACKUP_VERSION + ") REFERENCES " + Backup.TABLE_NAME + " (" + Backup.BACKUP_KEY_UID + "," + Backup.VERSION + "));"); } } public static void upgradeTable(Session session, int oldVersion, int newVersion) throws SQLException { + if (oldVersion < 36 && newVersion >= 36) { + Logger.d("MIGRATING `backup_key` DATABASE FROM VERSION " + oldVersion + " TO 36"); + try (Statement statement = session.createStatement()) { + statement.execute("CREATE TABLE backup_key_new (" + + " uid BLOB PRIMARY KEY, " + + " encryption_public_key BLOB NOT NULL, " + + " mac_key BLOB NOT NULL, " + + " key_generation_timestamp INTEGER NOT NULL, " + + " last_successful_key_verification_timestamp INTEGER NOT NULL, " + + " last_key_verification_prompt_timestamp INTEGER NOT NULL, " + + " successful_verification_count INTEGER NOT NULL, " + + " uploaded_backup_version INTEGER, " + + " exported_backup_version INTEGER, " + + " latest_backup_version INTEGER, " + + "FOREIGN KEY (uid, uploaded_backup_version) REFERENCES backup (backup_key_uid, version), " + + "FOREIGN KEY (uid, exported_backup_version) REFERENCES backup (backup_key_uid, version), " + + "FOREIGN KEY (uid, latest_backup_version) REFERENCES backup (backup_key_uid, version));"); + + ResultSet res = statement.executeQuery("SELECT * FROM backup_key WHERE uid IS NULL"); + if (res.next()) { + // we have a null primary key --> copy it partially to new table + try (PreparedStatement ps = session.prepareStatement("INSERT INTO backup_key_new VALUES (?,?,?,?,?, ?,?,?,?,?)")) { + ps.setBytes(1, new UID(Suite.getDefaultPRNGService(Suite.LATEST_VERSION)).getBytes()); + ps.setBytes(2, res.getBytes("encryption_public_key")); + ps.setBytes(3, res.getBytes("mac_key")); + ps.setLong(4, res.getLong("key_generation_timestamp")); + ps.setLong(5, res.getLong("last_successful_key_verification_timestamp")); + ps.setLong(6, res.getLong("last_key_verification_prompt_timestamp")); + ps.setInt(7, res.getInt("successful_verification_count")); + ps.setNull(8, Types.INTEGER); + ps.setNull(9, Types.INTEGER); + ps.setNull(10, Types.INTEGER); + ps.executeUpdate(); + } + // delete all existing backups (the backup_key_uid no longer exists) + statement.execute("DELETE FROM backup"); + } else { + // no null primary key, simply copy the content of the old table to the new one + statement.execute("INSERT into backup_key_new (uid, encryption_public_key, mac_key, key_generation_timestamp, last_successful_key_verification_timestamp, last_key_verification_prompt_timestamp, successful_verification_count, uploaded_backup_version, exported_backup_version, latest_backup_version) SELECT uid, encryption_public_key, mac_key, key_generation_timestamp, last_successful_key_verification_timestamp, last_key_verification_prompt_timestamp, successful_verification_count, uploaded_backup_version, exported_backup_version, latest_backup_version FROM backup_key"); + } + statement.execute("DROP TABLE backup_key"); + statement.execute("ALTER TABLE backup_key_new RENAME TO backup_key"); + + } + oldVersion = 36; + } } // delete all BackupKey and all Backup diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/LocalChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/LocalChannel.java index 4b9802b9..01f85f32 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/LocalChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/LocalChannel.java @@ -21,9 +21,11 @@ import java.sql.SQLException; +import java.util.Objects; import io.olvid.engine.Logger; import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.datatypes.UID; import io.olvid.engine.datatypes.containers.ChannelDialogResponseMessageToSend; @@ -85,7 +87,8 @@ private static LocalChannel[] acceptableChannelsForPosting(ChannelManagerSession switch (message.getSendChannelInfo().getChannelType()) { case SendChannelInfo.LOCAL_TYPE: // Check that the toIdentity is an OwnedIdentity - if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity())) { + if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity()) + || Objects.equals(message.getSendChannelInfo().getToIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { return new LocalChannel[]{ new LocalChannel(message.getSendChannelInfo().getToIdentity()) }; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ServerQueryChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ServerQueryChannel.java index 262974eb..9aa0b3d5 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ServerQueryChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/ServerQueryChannel.java @@ -20,9 +20,11 @@ package io.olvid.engine.channel.datatypes; import java.sql.SQLException; +import java.util.Objects; import io.olvid.engine.Logger; import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.UID; import io.olvid.engine.datatypes.containers.ChannelMessageToSend; import io.olvid.engine.datatypes.containers.ChannelServerQueryMessageToSend; @@ -65,7 +67,8 @@ private static ServerQueryChannel[] acceptableChannelsForPosting(ChannelManagerS switch (message.getSendChannelInfo().getChannelType()) { case SendChannelInfo.SERVER_QUERY_TYPE: // Check that the toIdentity is an OwnedIdentity - if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity())) { + if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity()) + || Objects.equals(message.getSendChannelInfo().getToIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { return new ServerQueryChannel[]{ new ServerQueryChannel() }; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/UserInterfaceChannel.java b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/UserInterfaceChannel.java index 5db17e34..9fb1d3b9 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/UserInterfaceChannel.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/channel/datatypes/UserInterfaceChannel.java @@ -22,9 +22,11 @@ import java.sql.SQLException; import java.util.HashMap; +import java.util.Objects; import io.olvid.engine.Logger; import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.UID; import io.olvid.engine.datatypes.containers.ChannelDialogMessageToSend; import io.olvid.engine.datatypes.containers.ChannelMessageToSend; @@ -65,7 +67,8 @@ private static UserInterfaceChannel[] acceptableChannelsForPosting(ChannelManage switch (message.getSendChannelInfo().getChannelType()) { case SendChannelInfo.USER_INTERFACE_TYPE: // Check that the toIdentity is an OwnedIdentity - if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity())) { + if (channelManagerSession.identityDelegate.isOwnedIdentity(channelManagerSession.session, message.getSendChannelInfo().getToIdentity()) + || Objects.equals(message.getSendChannelInfo().getToIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { return new UserInterfaceChannel[]{ new UserInterfaceChannel() }; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/BackupSeed.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/BackupSeed.java index 31cd3059..98dcb728 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/BackupSeed.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/BackupSeed.java @@ -145,7 +145,7 @@ public DerivedKeys deriveKeys() { } public static class DerivedKeys { - public final UID backupKeyUid; + public final UID backupKeyUid; // should never be used during backup: a bug fixed in Sept. 2023 may have changed the original value of this in DB public final EncryptionEciesCurve25519KeyPair encryptionKeyPair; public final MACKey macKey; 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 b7609af8..b74e56c5 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 = 35; + public static final int CURRENT_ENGINE_DB_SCHEMA_VERSION = 37; public static final int SERVER_API_VERSION = 15; public static final int CURRENT_BACKUP_JSON_VERSION = 0; @@ -69,6 +69,10 @@ public abstract class Constants { public static final long GET_USER_DATA_LOCAL_FILE_LIFESPAN = 86_400_000L * 7; // 7 days public static final long WELL_KNOWN_REFRESH_INTERVAL = 3_600_000L * 6; // 6 hours + // download message + public static final long RELIST_NEW_MESSAGE_COUNT_THRESHOLD = 20; // number of new messages in a single list operation that trigger a re-list + public static final long RELIST_DELAY = 30_000; // 30 seconds + // backups public static final long AUTOBACKUP_MAX_INTERVAL = 86_400_000L; // 1 day public static final long AUTOBACKUP_START_DELAY = 60_000L * 2; // 2 minutes @@ -91,6 +95,9 @@ public abstract class Constants { public static final byte[] ANDROID_STORE_ID = new byte[]{0x01}; public static final int DEFAULT_NUMBER_OF_DIGITS_FOR_SAS = 4; + public static final String EPHEMERAL_IDENTITY_SERVER = "ephemeral_fake_server"; + public static final String TRANSFER_WS_SERVER_URL = "wss://transfer.olvid.io"; + public static final int TRANSFER_MAX_PAYLOAD_SIZE = 10000; public static final long BASE_RESCHEDULING_TIME = 250L; @@ -104,7 +111,6 @@ public abstract class Constants { // prefixes for various types of signature public static final int SIGNATURE_PADDING_LENGTH = 16; - public enum SignatureContext { SERVER_AUTHENTICATION, MUTUAL_SCAN, diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Identity.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Identity.java index a982dd2d..45dcd608 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Identity.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/Identity.java @@ -42,7 +42,7 @@ public Identity(String server, ServerAuthenticationPublicKey serverAuthenticatio this.identityBytes = null; } - public Identity(String server, ServerAuthenticationPublicKey serverAuthenticationPublicKey, EncryptionPublicKey encryptionPublicKey, byte[] identityBytes) { + private Identity(String server, ServerAuthenticationPublicKey serverAuthenticationPublicKey, EncryptionPublicKey encryptionPublicKey, byte[] identityBytes) { this.server = server; this.serverAuthenticationPublicKey = serverAuthenticationPublicKey; this.encryptionPublicKey = encryptionPublicKey; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ChannelServerResponseMessageToSend.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ChannelServerResponseMessageToSend.java index feead037..5745265f 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ChannelServerResponseMessageToSend.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ChannelServerResponseMessageToSend.java @@ -25,13 +25,13 @@ public class ChannelServerResponseMessageToSend implements ChannelMessageToSend { private final SendChannelInfo sendChannelInfo; - private final Encoded encodedElements; private final Encoded encodedServerResponse; + private final Encoded encodedElements; public ChannelServerResponseMessageToSend(Identity toIdentity, Encoded encodedServerResponse, Encoded encodedElements) { this.sendChannelInfo = SendChannelInfo.createLocalChannelInfo(toIdentity); - this.encodedElements = encodedElements; this.encodedServerResponse = encodedServerResponse; + this.encodedElements = encodedElements; } @Override diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/DialogType.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/DialogType.java index 7f096bc0..47d63b3d 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/DialogType.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/DialogType.java @@ -22,6 +22,7 @@ import io.olvid.engine.datatypes.Identity; import io.olvid.engine.datatypes.UID; +import io.olvid.engine.engine.types.ObvTransferStep; import io.olvid.engine.engine.types.sync.ObvSyncAtom; import io.olvid.engine.engine.types.identities.ObvGroupV2; @@ -45,6 +46,7 @@ public class DialogType { public static final int ACCEPT_GROUP_V2_INVITATION_DIALOG_ID = 15; public static final int GROUP_V2_FROZEN_INVITATION_DIALOG_ID = 16; public static final int SYNC_ITEM_TO_APPLY_DIALOG_ID = 17; + public static final int TRANSFER_DIALOG_ID = 18; public final int id; @@ -60,8 +62,9 @@ public class DialogType { public final Long serverTimestamp; public final ObvGroupV2 obvGroupV2; public final ObvSyncAtom obvSyncAtom; + public final ObvTransferStep obvTransferStep; - private DialogType(int id, String contactDisplayNameOrSerializedDetails, Identity contactIdentity, byte[] sasToDisplay, byte[] sasEntered, Identity mediatorOrGroupOwnerIdentity, String serializedGroupDetails, UID groupUid, Identity[] pendingGroupMemberIdentities, String[] pendingGroupMemberSerializedDetails, Long serverTimestamp, ObvGroupV2 obvGroupV2, ObvSyncAtom obvSyncAtom) { + private DialogType(int id, String contactDisplayNameOrSerializedDetails, Identity contactIdentity, byte[] sasToDisplay, byte[] sasEntered, Identity mediatorOrGroupOwnerIdentity, String serializedGroupDetails, UID groupUid, Identity[] pendingGroupMemberIdentities, String[] pendingGroupMemberSerializedDetails, Long serverTimestamp, ObvGroupV2 obvGroupV2, ObvSyncAtom obvSyncAtom, ObvTransferStep obvTransferStep) { this.id = id; this.contactDisplayNameOrSerializedDetails = contactDisplayNameOrSerializedDetails; this.contactIdentity = contactIdentity; @@ -75,61 +78,66 @@ private DialogType(int id, String contactDisplayNameOrSerializedDetails, Identit this.serverTimestamp = serverTimestamp; this.obvGroupV2 = obvGroupV2; this.obvSyncAtom = obvSyncAtom; + this.obvTransferStep = obvTransferStep; } public static DialogType createDeleteDialog() { - return new DialogType(DELETE_DIALOG_ID, null, null, null, null, null, null, null, null, null, null, null, null); + return new DialogType(DELETE_DIALOG_ID, null, null, null, null, null, null, null, null, null, null, null, null, null); } public static DialogType createInviteSentDialog(String contactDisplayName, Identity contactIdentity) { - return new DialogType(INVITE_SENT_DIALOG_ID, contactDisplayName, contactIdentity, null, null, null, null, null, null, null, null, null, null); + return new DialogType(INVITE_SENT_DIALOG_ID, contactDisplayName, contactIdentity, null, null, null, null, null, null, null, null, null, null, null); } public static DialogType createAcceptInviteDialog(String contactSerializedDetails, Identity contactIdentity, long serverTimestamp) { - return new DialogType(ACCEPT_INVITE_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null); + return new DialogType(ACCEPT_INVITE_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null, null); } public static DialogType createSasExchangeDialog(String contactSerializedDetails, Identity contactIdentity, byte[] sasToDisplay, long serverTimestamp) { - return new DialogType(SAS_EXCHANGE_DIALOG_ID, contactSerializedDetails, contactIdentity, sasToDisplay, null, null, null, null, null, null, serverTimestamp, null, null); + return new DialogType(SAS_EXCHANGE_DIALOG_ID, contactSerializedDetails, contactIdentity, sasToDisplay, null, null, null, null, null, null, serverTimestamp, null, null, null); } public static DialogType createSasConfirmedDialog(String contactSerializedDetails, Identity contactIdentity, byte[] sasToDisplay, byte[] sasEntered) { - return new DialogType(SAS_CONFIRMED_DIALOG_ID, contactSerializedDetails, contactIdentity, sasToDisplay, sasEntered, null, null, null, null, null, null, null, null); + return new DialogType(SAS_CONFIRMED_DIALOG_ID, contactSerializedDetails, contactIdentity, sasToDisplay, sasEntered, null, null, null, null, null, null, null, null, null); } public static DialogType createInviteAcceptedDialog(String contactSerializedDetails, Identity contactIdentity) { - return new DialogType(INVITE_ACCEPTED_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, null, null, null, null, null, null, null, null); + return new DialogType(INVITE_ACCEPTED_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, null, null, null, null, null, null, null, null, null); } public static DialogType createAcceptMediatorInviteDialog(String contactSerializedDetails, Identity contactIdentity, Identity mediatorIdentity, long serverTimestamp) { - return new DialogType(ACCEPT_MEDIATOR_INVITE_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, mediatorIdentity, null, null, null, null, serverTimestamp, null, null); + return new DialogType(ACCEPT_MEDIATOR_INVITE_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, mediatorIdentity, null, null, null, null, serverTimestamp, null, null, null); } public static DialogType createMediatorInviteAcceptedDialog(String contactSerializedDetails, Identity contactIdentity, Identity mediatorIdentity) { - return new DialogType(MEDIATOR_INVITE_ACCEPTED_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, mediatorIdentity, null, null, null, null, null, null, null); + return new DialogType(MEDIATOR_INVITE_ACCEPTED_DIALOG_ID, contactSerializedDetails, contactIdentity, null, null, mediatorIdentity, null, null, null, null, null, null, null, null); } public static DialogType createAcceptGroupInviteDialog(String serializedGroupDetails, UID groupUid, Identity groupOwnerIdentity, Identity[] pendingGroupMemberIdentities, String[] pendingGroupMemberSerializedDetails, long serverTimestamp) { - return new DialogType(ACCEPT_GROUP_INVITE_DIALOG_ID, null, null, null, null, groupOwnerIdentity, serializedGroupDetails, groupUid, pendingGroupMemberIdentities, pendingGroupMemberSerializedDetails, serverTimestamp, null, null); + return new DialogType(ACCEPT_GROUP_INVITE_DIALOG_ID, null, null, null, null, groupOwnerIdentity, serializedGroupDetails, groupUid, pendingGroupMemberIdentities, pendingGroupMemberSerializedDetails, serverTimestamp, null, null, null); } public static DialogType createOneToOneInvitationSentDialog(Identity contactIdentity) { - return new DialogType(ONE_TO_ONE_INVITATION_SENT_DIALOG_ID, null, contactIdentity, null, null, null, null, null, null, null, null, null, null); + return new DialogType(ONE_TO_ONE_INVITATION_SENT_DIALOG_ID, null, contactIdentity, null, null, null, null, null, null, null, null, null, null, null); } public static DialogType createAcceptOneToOneInvitationDialog(Identity contactIdentity, long serverTimestamp) { - return new DialogType(ACCEPT_ONE_TO_ONE_INVITATION_DIALOG_ID, null, contactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null); + return new DialogType(ACCEPT_ONE_TO_ONE_INVITATION_DIALOG_ID, null, contactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null, null); } public static DialogType createGroupV2InvitationDialog(Identity inviterIdentity, ObvGroupV2 obvGroupV2) { - return new DialogType(ACCEPT_GROUP_V2_INVITATION_DIALOG_ID, null, null, null, null, inviterIdentity, null, null, null, null, null, obvGroupV2, null); + return new DialogType(ACCEPT_GROUP_V2_INVITATION_DIALOG_ID, null, null, null, null, inviterIdentity, null, null, null, null, null, obvGroupV2, null, null); } public static DialogType createGroupV2FrozenInvitationDialog(Identity inviterIdentity, ObvGroupV2 obvGroupV2) { - return new DialogType(GROUP_V2_FROZEN_INVITATION_DIALOG_ID, null, null, null, null, inviterIdentity, null, null, null, null, null, obvGroupV2, null); + return new DialogType(GROUP_V2_FROZEN_INVITATION_DIALOG_ID, null, null, null, null, inviterIdentity, null, null, null, null, null, obvGroupV2, null, null); } public static DialogType createSyncItemToApplyDialog(ObvSyncAtom obvSyncAtom) { - return new DialogType(SYNC_ITEM_TO_APPLY_DIALOG_ID, null, null, null, null, null, null, null, null, null, null, null, obvSyncAtom); + return new DialogType(SYNC_ITEM_TO_APPLY_DIALOG_ID, null, null, null, null, null, null, null, null, null, null, null, obvSyncAtom, null); + } + + public static DialogType createTransferDialog(ObvTransferStep obvTransferStep) { + return new DialogType(TRANSFER_DIALOG_ID, null, null, null, null, null, null, null, null, null, null, null, null, obvTransferStep); } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ServerQuery.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ServerQuery.java index 1a619f3d..029adfa4 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ServerQuery.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/containers/ServerQuery.java @@ -19,6 +19,9 @@ package io.olvid.engine.datatypes.containers; +import java.util.HashMap; +import java.util.Map; + import io.olvid.engine.datatypes.EncryptedBytes; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.datatypes.UID; @@ -39,6 +42,10 @@ public ServerQuery(Encoded encodedElements, Identity ownedIdentity, Type type) { this.encodedResponse = null; } + public boolean isWebSocket() { + return type.isWebSocket(); + } + public static ServerQuery of(Encoded encoded) throws DecodingException { Encoded[] list = encoded.decodeList(); if (list.length != 3) { @@ -79,424 +86,1069 @@ public Encoded getEncodedResponse() { return encodedResponse; } - public static class Type { - public static final int DEVICE_DISCOVERY_QUERY_ID = 0; - public static final int PUT_USER_DATA_QUERY_ID = 1; - public static final int GET_USER_DATA_QUERY_ID = 2; - public static final int CHECK_KEYCLOAK_REVOCATION_QUERY_ID = 3; - public static final int CREATE_GROUP_BLOB_QUERY_ID = 4; - public static final int GET_GROUP_BLOB_QUERY_ID = 5; - public static final int LOCK_GROUP_BLOB_QUERY_ID = 6; - public static final int UPDATE_GROUP_BLOB_QUERY_ID = 7; - public static final int PUT_GROUP_LOG_QUERY_ID = 8; - public static final int DELETE_GROUP_BLOB_QUERY_ID = 9; - public static final int GET_KEYCLOAK_DATA_QUERY_ID = 10; - public static final int OWNED_DEVICE_DISCOVERY_QUERY_ID = 11; - public static final int DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID = 12; - public static final int DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID = 13; - public static final int DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID = 14; - public static final int REGISTER_API_KEY_QUERY_ID = 15; - - private final int id; - private final String server; - private final Identity identity; - private final UID serverLabelOrDeviceUid; - private final String dataUrl; // always a relative path - private final AuthEncKey dataKey; - private final String signedContactDetails; // this is a JWT - private final Encoded encodedGroupAdminPublicKey; - private final EncryptedBytes encryptedBlob; - private final byte[] querySignature; - private final byte[] nonce; - private final boolean isCurrentDevice; - - public Type(int id, String server, Identity identity, UID serverLabelOrDeviceUid, String dataUrl, AuthEncKey dataKey, String signedContactDetails, Encoded encodedGroupAdminPublicKey, EncryptedBytes encryptedBlob, byte[] querySignature, byte[] nonce, boolean isCurrentDevice) { - this.id = id; - this.server = server; + + public enum TypeId { + DEVICE_DISCOVERY_QUERY_ID(0), + PUT_USER_DATA_QUERY_ID(1), + GET_USER_DATA_QUERY_ID(2), + CHECK_KEYCLOAK_REVOCATION_QUERY_ID(3), + CREATE_GROUP_BLOB_QUERY_ID(4), + GET_GROUP_BLOB_QUERY_ID(5), + LOCK_GROUP_BLOB_QUERY_ID(6), + UPDATE_GROUP_BLOB_QUERY_ID(7), + PUT_GROUP_LOG_QUERY_ID(8), + DELETE_GROUP_BLOB_QUERY_ID(9), + GET_KEYCLOAK_DATA_QUERY_ID(10), + OWNED_DEVICE_DISCOVERY_QUERY_ID(11), + DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID(12), + DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID(13), + DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID(14), + REGISTER_API_KEY_QUERY_ID(15), + TRANSFER_SOURCE_QUERY_ID(1000), + TRANSFER_TARGET_QUERY_ID(1001), + TRANSFER_RELAY_QUERY_ID(1002), + TRANSFER_WAIT_QUERY_ID(1003), + TRANSFER_CLOSE_QUERY_ID(1004); + + private static final Map valueMap = new HashMap<>(); + static { + for (TypeId step : values()) { + valueMap.put(step.value, step); + } + } + + final int value; + + TypeId(int value) { + this.value = value; + } + + static TypeId fromIntValue(int value) { + return valueMap.get(value); + } + } + + public static abstract class Type { + public abstract TypeId getId(); + + abstract String getServer(); + + abstract Encoded[] getEncodedParts(); + + abstract boolean isWebSocket(); + + public static Type of(Encoded encoded) throws DecodingException { + Encoded[] list = encoded.decodeList(); + if (list.length != 3) { + throw new DecodingException(); + } + int id = (int) list[0].decodeLong(); + String server = list[1].decodeString(); + Encoded[] encodedParts = list[2].decodeList(); + TypeId typeId = TypeId.fromIntValue(id); + if (typeId == null) { + throw new DecodingException(); + } + switch (typeId) { + case DEVICE_DISCOVERY_QUERY_ID: + return new DeviceDiscoveryQuery(server, encodedParts); + case PUT_USER_DATA_QUERY_ID: + return new PutUserDataQuery(server, encodedParts); + case GET_USER_DATA_QUERY_ID: + return new GetUserDataQuery(server, encodedParts); + case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: + return new CheckKeycloakRevocationQuery(server, encodedParts); + case CREATE_GROUP_BLOB_QUERY_ID: + return new CreateGroupBlobQuery(server, encodedParts); + case GET_GROUP_BLOB_QUERY_ID: + return new GetGroupBlobQuery(server, encodedParts); + case LOCK_GROUP_BLOB_QUERY_ID: + return new LockGroupBlobQuery(server, encodedParts); + case UPDATE_GROUP_BLOB_QUERY_ID: + return new UpdateGroupBlobQuery(server, encodedParts); + case PUT_GROUP_LOG_QUERY_ID: + return new PutGroupLogQuery(server, encodedParts); + case DELETE_GROUP_BLOB_QUERY_ID: + return new DeleteGroupBlobQuery(server, encodedParts); + case GET_KEYCLOAK_DATA_QUERY_ID: + return new GetKeycloakDataQuery(server, encodedParts); + case OWNED_DEVICE_DISCOVERY_QUERY_ID: + return new OwnedDeviceDiscoveryQuery(server, encodedParts); + case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: + return new DeviceManagementSetNicknameQuery(server, encodedParts); + case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: + return new DeviceManagementDeactivateDeviceQuery(server, encodedParts); + case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: + return new DeviceManagementSetUnexpiringDeviceQuery(server, encodedParts); + case REGISTER_API_KEY_QUERY_ID: + return new RegisterApiKeyQuery(server, encodedParts); + case TRANSFER_SOURCE_QUERY_ID: + return new TransferSourceQuery(encodedParts); + case TRANSFER_TARGET_QUERY_ID: + return new TransferTargetQuery(encodedParts); + case TRANSFER_RELAY_QUERY_ID: + return new TransferRelayQuery(encodedParts); + case TRANSFER_WAIT_QUERY_ID: + return new TransferWaitQuery(encodedParts); + case TRANSFER_CLOSE_QUERY_ID: + return new TransferCloseQuery(encodedParts); + default: + throw new DecodingException(); + } + } + + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(getId().value), + Encoded.of(getServer()), + Encoded.of(getEncodedParts()), + }); + } + } + + public static class DeviceDiscoveryQuery extends Type { + public final String server; + public final Identity identity; + + public DeviceDiscoveryQuery(Identity identity) { + this.server = identity.getServer(); this.identity = identity; - this.serverLabelOrDeviceUid = serverLabelOrDeviceUid; + } + + public DeviceDiscoveryQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.identity = encodedParts[0].decodeIdentity(); + } + + @Override + public TypeId getId() { + return TypeId.DEVICE_DISCOVERY_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(identity), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class PutUserDataQuery extends Type { + public final String server; + public final Identity ownedIdentity; + public final UID serverLabel; + public final String dataUrl; // always a relative path + public final AuthEncKey dataKey; + + public PutUserDataQuery(Identity ownedIdentity, UID serverLabel, String dataUrl, AuthEncKey dataKey) { + this.server = ownedIdentity.getServer(); + this.ownedIdentity = ownedIdentity; + this.serverLabel = serverLabel; this.dataUrl = dataUrl; this.dataKey = dataKey; + } + + public PutUserDataQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 4) { + throw new DecodingException(); + } + this.ownedIdentity = encodedParts[0].decodeIdentity(); + this.serverLabel = encodedParts[1].decodeUid(); + this.dataUrl = encodedParts[2].decodeString(); + this.dataKey = (AuthEncKey) encodedParts[3].decodeSymmetricKey(); + } + + @Override + public TypeId getId() { + return TypeId.PUT_USER_DATA_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(ownedIdentity), + Encoded.of(serverLabel), + Encoded.of(dataUrl), + Encoded.of(dataKey), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class GetUserDataQuery extends Type { + public final String server; + public final Identity identity; + public final UID serverLabel; + + public GetUserDataQuery(Identity identity, UID serverLabel) { + this.server = identity.getServer(); + this.identity = identity; + this.serverLabel = serverLabel; + } + + public GetUserDataQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 2) { + throw new DecodingException(); + } + this.identity = encodedParts[0].decodeIdentity(); + this.serverLabel = encodedParts[1].decodeUid(); + } + + @Override + public TypeId getId() { + return TypeId.GET_USER_DATA_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(identity), + Encoded.of(serverLabel), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class CheckKeycloakRevocationQuery extends Type { + public final String server; + public final String signedContactDetails; // this is a JWT + + public CheckKeycloakRevocationQuery(String keycloakServerUrl, String signedContactDetails) { + this.server = keycloakServerUrl; this.signedContactDetails = signedContactDetails; + } + + public CheckKeycloakRevocationQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 1) { + this.signedContactDetails = encodedParts[0].decodeString(); + } else if (encodedParts.length == 2) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.signedContactDetails = encodedParts[1].decodeString(); + } else { + throw new DecodingException(); + } + } + + @Override + public TypeId getId() { + return TypeId.CHECK_KEYCLOAK_REVOCATION_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(signedContactDetails), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class CreateGroupBlobQuery extends Type { + public final String server; + public final UID groupUid; + public final Encoded encodedGroupAdminPublicKey; + public final EncryptedBytes encryptedBlob; + + public CreateGroupBlobQuery(GroupV2.Identifier groupIdentifier, Encoded encodedGroupAdminPublicKey, EncryptedBytes encryptedBlob) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; this.encodedGroupAdminPublicKey = encodedGroupAdminPublicKey; this.encryptedBlob = encryptedBlob; - this.querySignature = querySignature; - this.nonce = nonce; - this.isCurrentDevice = isCurrentDevice; } - public int getId() { - return id; + public CreateGroupBlobQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 3) { + this.groupUid = encodedParts[0].decodeUid(); + this.encodedGroupAdminPublicKey = encodedParts[1]; + this.encryptedBlob = encodedParts[2].decodeEncryptedData(); + } else if (encodedParts.length == 4) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.encodedGroupAdminPublicKey = encodedParts[2]; + this.encryptedBlob = encodedParts[3].decodeEncryptedData(); + } else { + throw new DecodingException(); + } } - public String getServer() { + @Override + public TypeId getId() { + return TypeId.CREATE_GROUP_BLOB_QUERY_ID; + } + + @Override + String getServer() { return server; } - public Identity getIdentity() { - return identity; + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + encodedGroupAdminPublicKey, + Encoded.of(encryptedBlob), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class GetGroupBlobQuery extends Type { + public final String server; + public final UID groupUid; + public final byte[] serverQueryNonce; + + public GetGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] serverQueryNonce) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; + this.serverQueryNonce = serverQueryNonce; + } + + public GetGroupBlobQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 2) { + this.groupUid = encodedParts[0].decodeUid(); + this.serverQueryNonce = encodedParts[1].decodeBytes(); + } else if (encodedParts.length == 3) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.serverQueryNonce = encodedParts[2].decodeBytes(); + } else { + throw new DecodingException(); + } + } + + @Override + public TypeId getId() { + return TypeId.GET_GROUP_BLOB_QUERY_ID; } - public UID getServerLabelOrDeviceUid() { - return serverLabelOrDeviceUid; + @Override + String getServer() { + return server; } - public String getDataUrl() { - return dataUrl; + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + Encoded.of(serverQueryNonce), + }; } - public AuthEncKey getDataKey() { - return dataKey; + @Override + boolean isWebSocket() { + return false; } + } - public String getSignedContactDetails() { - return signedContactDetails; + public static class LockGroupBlobQuery extends Type { + public final String server; + public final UID groupUid; + public final byte[] signature; + public final byte[] lockNonce; + + public LockGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] lockNonce, byte[] signature) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; + this.signature = signature; + this.lockNonce = lockNonce; } - public Encoded getEncodedGroupAdminPublicKey() { - return encodedGroupAdminPublicKey; + public LockGroupBlobQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 3) { + this.groupUid = encodedParts[0].decodeUid(); + this.signature = encodedParts[1].decodeBytes(); + this.lockNonce = encodedParts[2].decodeBytes(); + } else if (encodedParts.length == 4) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.signature = encodedParts[2].decodeBytes(); + this.lockNonce = encodedParts[3].decodeBytes(); + } else { + throw new DecodingException(); + } } - public EncryptedBytes getEncryptedBlob() { - return encryptedBlob; + @Override + public TypeId getId() { + return TypeId.LOCK_GROUP_BLOB_QUERY_ID; } - public byte[] getQuerySignature() { - return querySignature; + @Override + String getServer() { + return server; } - public byte[] getNonce() { - return nonce; + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + Encoded.of(signature), + Encoded.of(lockNonce), + }; } - public boolean isCurrentDevice() { - return isCurrentDevice; + @Override + boolean isWebSocket() { + return false; + } + } + + public static class UpdateGroupBlobQuery extends Type { + public final String server; + public final UID groupUid; + public final Encoded encodedGroupAdminPublicKey; + public final EncryptedBytes encryptedBlob; + public final byte[] signature; + public final byte[] lockNonce; + + public UpdateGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] lockNonce, EncryptedBytes encryptedBlob, Encoded encodedGroupAdminPublicKey, byte[] signature) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; + this.encodedGroupAdminPublicKey = encodedGroupAdminPublicKey; + this.encryptedBlob = encryptedBlob; + this.signature = signature; + this.lockNonce = lockNonce; } + public UpdateGroupBlobQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 5) { + this.groupUid = encodedParts[0].decodeUid(); + this.encodedGroupAdminPublicKey = encodedParts[1]; + this.encryptedBlob = encodedParts[2].decodeEncryptedData(); + this.signature = encodedParts[3].decodeBytes(); + this.lockNonce = encodedParts[4].decodeBytes(); + } else if (encodedParts.length == 6) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.encodedGroupAdminPublicKey = encodedParts[2]; + this.encryptedBlob = encodedParts[3].decodeEncryptedData(); + this.signature = encodedParts[4].decodeBytes(); + this.lockNonce = encodedParts[5].decodeBytes(); + } else { + throw new DecodingException(); + } + } + @Override + public TypeId getId() { + return TypeId.UPDATE_GROUP_BLOB_QUERY_ID; + } - public static Type createDeviceDiscoveryQuery(Identity identity) { - return new Type(DEVICE_DISCOVERY_QUERY_ID, identity.getServer(), identity, null, null, null, null, null, null, null, null, false); + @Override + String getServer() { + return server; } - public static Type createPutUserDataQuery(Identity ownedIdentity, UID serverLabel, String dataUrl, AuthEncKey dataKey) { - return new Type(PUT_USER_DATA_QUERY_ID, ownedIdentity.getServer(), ownedIdentity, serverLabel, dataUrl, dataKey, null, null, null, null, null, false); + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + encodedGroupAdminPublicKey, + Encoded.of(encryptedBlob), + Encoded.of(signature), + Encoded.of(lockNonce), + }; } - public static Type createGetUserDataQuery(Identity contactIdentity, UID serverLabel) { - return new Type(GET_USER_DATA_QUERY_ID, contactIdentity.getServer(), contactIdentity, serverLabel, null, null, null, null, null, null, null, false); + @Override + boolean isWebSocket() { + return false; } + } + + public static class PutGroupLogQuery extends Type { + public final String server; + public final UID groupUid; + public final byte[] signature; - public static Type createCheckKeycloakRevocationServerQuery(String keycloakServerUrl, String signedContactDetails) { - return new Type(CHECK_KEYCLOAK_REVOCATION_QUERY_ID, keycloakServerUrl, null, null, null, null, signedContactDetails, null, null, null, null, false); + public PutGroupLogQuery(GroupV2.Identifier groupIdentifier, byte[] signature) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; + this.signature = signature; } - public static Type createCreateGroupBlobQuery(GroupV2.Identifier groupIdentifier, Encoded encodedGroupAdminPublicKey, EncryptedBytes encryptedBlob) { - return new Type(CREATE_GROUP_BLOB_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, encodedGroupAdminPublicKey, encryptedBlob, null, null, false); + public PutGroupLogQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 2) { + this.groupUid = encodedParts[0].decodeUid(); + this.signature = encodedParts[1].decodeBytes(); + } else if (encodedParts.length == 3) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.signature = encodedParts[2].decodeBytes(); + } else { + throw new DecodingException(); + } } - public static Type createGetGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] serverQueryNonce) { - return new Type(GET_GROUP_BLOB_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, null, null, null, serverQueryNonce, false); + @Override + public TypeId getId() { + return TypeId.PUT_GROUP_LOG_QUERY_ID; } - public static Type createBlobLockQuery(GroupV2.Identifier groupIdentifier, byte[] lockNonce, byte[] querySignature) { - return new Type(LOCK_GROUP_BLOB_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, null, null, querySignature, lockNonce, false); + @Override + String getServer() { + return server; } - public static Type createUpdateGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] lockNonce, EncryptedBytes encryptedBlob, Encoded encodedGroupAdminPublicKey, byte[] signature) { - return new Type(UPDATE_GROUP_BLOB_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, encodedGroupAdminPublicKey, encryptedBlob, signature, lockNonce, false); + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + Encoded.of(signature), + }; } - public static Type createPutGroupLogQuery(GroupV2.Identifier groupIdentifier, byte[] querySignature) { - return new Type(PUT_GROUP_LOG_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, null, null, querySignature, null, false); + @Override + boolean isWebSocket() { + return false; } + } + + public static class DeleteGroupBlobQuery extends Type { + public final String server; + public final UID groupUid; + public final byte[] signature; - public static Type createDeleteGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] querySignature) { - return new Type(DELETE_GROUP_BLOB_QUERY_ID, groupIdentifier.serverUrl, null, groupIdentifier.groupUid, null, null, null, null, null, querySignature, null, false); + public DeleteGroupBlobQuery(GroupV2.Identifier groupIdentifier, byte[] signature) { + this.server = groupIdentifier.serverUrl; + this.groupUid = groupIdentifier.groupUid; + this.signature = signature; } - public static Type createGetKeycloakDataQuery(String serverUrl, UID serverLabel) { - return new Type(GET_KEYCLOAK_DATA_QUERY_ID, serverUrl, null, serverLabel, null, null, null, null, null, null, null, false); + public DeleteGroupBlobQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 2) { + this.groupUid = encodedParts[0].decodeUid(); + this.signature = encodedParts[1].decodeBytes(); + } else if (encodedParts.length == 3) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.groupUid = encodedParts[1].decodeUid(); + this.signature = encodedParts[2].decodeBytes(); + } else { + throw new DecodingException(); + } } - public static Type createOwnedDeviceDiscoveryQuery(Identity identity) { - return new Type(OWNED_DEVICE_DISCOVERY_QUERY_ID, identity.getServer(), identity, null, null, null, null, null, null, null, null, false); + @Override + public TypeId getId() { + return TypeId.DELETE_GROUP_BLOB_QUERY_ID; } - public static Type createDeviceManagementSetNicknameQuery(Identity identity, UID deviceUid, EncryptedBytes encryptedDeviceName, boolean isCurrentDevice) { - return new Type(DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID, identity.getServer(), identity, deviceUid, null, null, null, null, encryptedDeviceName, null, null, isCurrentDevice); + @Override + String getServer() { + return server; } - public static Type createDeviceManagementDeactivateDeviceQuery(Identity identity, UID deviceUid) { - return new Type(DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID, identity.getServer(), identity, deviceUid, null, null, null, null, null, null, null, false); + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(groupUid), + Encoded.of(signature), + }; } - public static Type createDeviceManagementSetUnexpiringDeviceQuery(Identity identity, UID deviceUid) { - return new Type(DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID, identity.getServer(), identity, deviceUid, null, null, null, null, null, null, null, false); + @Override + boolean isWebSocket() { + return false; } + } - public static Type createRegisterApiKey(Identity ownedIdentity, byte[] serverSessionToken, String apiKeyString) { - return new Type(REGISTER_API_KEY_QUERY_ID, ownedIdentity.getServer(), null, null, apiKeyString, null, null, null, null, null, serverSessionToken, false); + public static class GetKeycloakDataQuery extends Type { + public final String server; + public final UID serverLabel; + + public GetKeycloakDataQuery(String serverUrl, UID serverLabel) { + this.server = serverUrl; + this.serverLabel = serverLabel; } - public static Type of(Encoded encoded) throws DecodingException { - Encoded[] list = encoded.decodeList(); - if (list.length != 3) { + public GetKeycloakDataQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 1) { + this.serverLabel = encodedParts[0].decodeUid(); + } else if (encodedParts.length == 2) { + // legacy encoder + // this.server = encodedParts[0].decodeString(); + this.serverLabel = encodedParts[1].decodeUid(); + } else { throw new DecodingException(); } - int id = (int) list[0].decodeLong(); - String server = list[1].decodeString(); - Identity identity = null; - UID serverLabelOrDeviceUid = null; - String dataUrl = null; - AuthEncKey dataKey = null; - String signedContactDetails = null; - Encoded encodedGroupAdminPublicKey = null; - EncryptedBytes encryptedBlob = null; - byte[] querySignature = null; - byte[] nonce = null; - boolean isCurrentDevice = false; - - Encoded[] vars = list[2].decodeList(); - switch (id) { - case DEVICE_DISCOVERY_QUERY_ID: - case OWNED_DEVICE_DISCOVERY_QUERY_ID: { - if (vars.length != 1) { - throw new DecodingException(); - } - identity = vars[0].decodeIdentity(); - break; - } - case PUT_USER_DATA_QUERY_ID: { - if (vars.length != 4) { - throw new DecodingException(); - } - identity = vars[0].decodeIdentity(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - dataUrl = vars[2].decodeString(); - dataKey = (AuthEncKey) vars[3].decodeSymmetricKey(); - break; - } - case GET_USER_DATA_QUERY_ID: { - if (vars.length != 2) { - throw new DecodingException(); - } - identity = vars[0].decodeIdentity(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - break; - } - case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: { - if (vars.length != 2) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - signedContactDetails = vars[1].decodeString(); - break; - } - case CREATE_GROUP_BLOB_QUERY_ID: { - if (vars.length != 4) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - encodedGroupAdminPublicKey = vars[2]; - encryptedBlob = vars[3].decodeEncryptedData(); - break; - } - case GET_GROUP_BLOB_QUERY_ID: { - if (vars.length != 3) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - nonce = vars[2].decodeBytes(); - break; - } - case LOCK_GROUP_BLOB_QUERY_ID: { - if (vars.length != 4) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - querySignature = vars[2].decodeBytes(); - nonce = vars[3].decodeBytes(); - break; - } - case UPDATE_GROUP_BLOB_QUERY_ID: { - if (vars.length != 6) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - encodedGroupAdminPublicKey = vars[2]; - encryptedBlob = vars[3].decodeEncryptedData(); - querySignature = vars[4].decodeBytes(); - nonce = vars[5].decodeBytes(); - break; - } - case DELETE_GROUP_BLOB_QUERY_ID: - case PUT_GROUP_LOG_QUERY_ID: { - if (vars.length != 3) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - querySignature = vars[2].decodeBytes(); - break; - } - case GET_KEYCLOAK_DATA_QUERY_ID: { - if (vars.length != 2) { - throw new DecodingException(); - } - server = vars[0].decodeString(); - serverLabelOrDeviceUid = vars[1].decodeUid(); - break; - } - case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: - case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: { - if (vars.length != 1) { - throw new DecodingException(); - } - serverLabelOrDeviceUid = vars[0].decodeUid(); - break; - } - case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: { - if (vars.length != 3) { - throw new DecodingException(); - } - serverLabelOrDeviceUid = vars[0].decodeUid(); - encryptedBlob = vars[1].decodeEncryptedData(); - isCurrentDevice = vars[2].decodeBoolean(); - break; - } - case REGISTER_API_KEY_QUERY_ID: { - if (vars.length != 2) { - throw new DecodingException(); - } - dataUrl = vars[0].decodeString(); - nonce = vars[1].decodeBytes(); - break; - } + } + + @Override + public TypeId getId() { + return TypeId.GET_KEYCLOAK_DATA_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(serverLabel), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class OwnedDeviceDiscoveryQuery extends Type { + public final String server; + + public OwnedDeviceDiscoveryQuery(Identity ownedIdentity) { + this.server = ownedIdentity.getServer(); + } + + @SuppressWarnings("StatementWithEmptyBody") + public OwnedDeviceDiscoveryQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length == 0) { + } else if (encodedParts.length == 1) { + // legacy encoder + // this.ownedIdentity = encodedParts[0].decodeIdentity(); + } else { + throw new DecodingException(); } - return new Type(id, server, identity, serverLabelOrDeviceUid, dataUrl, dataKey, signedContactDetails, encodedGroupAdminPublicKey, encryptedBlob, querySignature, nonce, isCurrentDevice); } + @Override + public TypeId getId() { + return TypeId.OWNED_DEVICE_DISCOVERY_QUERY_ID; + } + @Override + String getServer() { + return server; + } - public Encoded encode() { - Encoded encodedVars = null; - switch (id) { - case DEVICE_DISCOVERY_QUERY_ID: - case OWNED_DEVICE_DISCOVERY_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(identity) - }); - break; - } - case PUT_USER_DATA_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(identity), - Encoded.of(serverLabelOrDeviceUid), - Encoded.of(dataUrl), - Encoded.of(dataKey), - }); - break; - } - case GET_USER_DATA_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(identity), - Encoded.of(serverLabelOrDeviceUid), - }); - break; - } - case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(signedContactDetails), - }); - break; - } - case CREATE_GROUP_BLOB_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - encodedGroupAdminPublicKey, - Encoded.of(encryptedBlob), - }); - break; - } - case GET_GROUP_BLOB_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - Encoded.of(nonce), - }); - break; - } - case LOCK_GROUP_BLOB_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - Encoded.of(querySignature), - Encoded.of(nonce), - }); - break; - } - case UPDATE_GROUP_BLOB_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - encodedGroupAdminPublicKey, - Encoded.of(encryptedBlob), - Encoded.of(querySignature), - Encoded.of(nonce), - }); - break; - } - case DELETE_GROUP_BLOB_QUERY_ID: - case PUT_GROUP_LOG_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - Encoded.of(querySignature), - }); - break; - } - case GET_KEYCLOAK_DATA_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(server), - Encoded.of(serverLabelOrDeviceUid), - }); - break; - } - case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: - case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(serverLabelOrDeviceUid), - }); - break; - } - case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(serverLabelOrDeviceUid), - Encoded.of(encryptedBlob), - Encoded.of(isCurrentDevice), - }); - break; - } - case REGISTER_API_KEY_QUERY_ID: { - encodedVars = Encoded.of(new Encoded[]{ - Encoded.of(dataUrl), - Encoded.of(nonce), - }); - break; - } + @Override + Encoded[] getEncodedParts() { + return new Encoded[0]; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class DeviceManagementSetNicknameQuery extends Type { + public final String server; + public final UID deviceUid; + public final EncryptedBytes encryptedDeviceName; + public final boolean isCurrentDevice; + + public DeviceManagementSetNicknameQuery(Identity ownedIdentity, UID deviceUid, EncryptedBytes encryptedDeviceName, boolean isCurrentDevice) { + this.server = ownedIdentity.getServer(); + this.deviceUid = deviceUid; + this.encryptedDeviceName = encryptedDeviceName; + this.isCurrentDevice = isCurrentDevice; + } + + public DeviceManagementSetNicknameQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 3) { + throw new DecodingException(); } - return Encoded.of(new Encoded[]{ - Encoded.of(id), - Encoded.of(server), - encodedVars - }); + this.deviceUid = encodedParts[0].decodeUid(); + this.encryptedDeviceName = encodedParts[1].decodeEncryptedData(); + this.isCurrentDevice = encodedParts[2].decodeBoolean(); + } + + @Override + public TypeId getId() { + return TypeId.DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(deviceUid), + Encoded.of(encryptedDeviceName), + Encoded.of(isCurrentDevice), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class DeviceManagementDeactivateDeviceQuery extends Type { + public final String server; + public final UID deviceUid; + + public DeviceManagementDeactivateDeviceQuery(Identity ownedIdentity, UID deviceUid) { + this.server = ownedIdentity.getServer(); + this.deviceUid = deviceUid; + } + + public DeviceManagementDeactivateDeviceQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.deviceUid = encodedParts[0].decodeUid(); + } + + @Override + public TypeId getId() { + return TypeId.DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(deviceUid), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class DeviceManagementSetUnexpiringDeviceQuery extends Type { + public final String server; + public final UID deviceUid; + + public DeviceManagementSetUnexpiringDeviceQuery(Identity ownedIdentity, UID deviceUid) { + this.server = ownedIdentity.getServer(); + this.deviceUid = deviceUid; + } + + public DeviceManagementSetUnexpiringDeviceQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.deviceUid = encodedParts[0].decodeUid(); + } + + @Override + public TypeId getId() { + return TypeId.DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(deviceUid), + }; + } + + @Override + boolean isWebSocket() { + return false; } } -} + + public static class RegisterApiKeyQuery extends Type { + public final String server; + public final String apiKeyString; + public final byte[] serverSessionToken; + + public RegisterApiKeyQuery(Identity ownedIdentity, byte[] serverSessionToken, String apiKeyString) { + this.server = ownedIdentity.getServer(); + this.apiKeyString = apiKeyString; + this.serverSessionToken = serverSessionToken; + } + + public RegisterApiKeyQuery(String server, Encoded[] encodedParts) throws DecodingException { + this.server = server; + if (encodedParts.length != 2) { + throw new DecodingException(); + } + this.apiKeyString = encodedParts[0].decodeString(); + this.serverSessionToken = encodedParts[1].decodeBytes(); + } + + @Override + public TypeId getId() { + return TypeId.REGISTER_API_KEY_QUERY_ID; + } + + @Override + String getServer() { + return server; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(apiKeyString), + Encoded.of(serverSessionToken), + }; + } + + @Override + boolean isWebSocket() { + return false; + } + } + + public static class TransferSourceQuery extends Type { + public TransferSourceQuery() { + } + + public TransferSourceQuery(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public TypeId getId() { + return TypeId.TRANSFER_SOURCE_QUERY_ID; + } + + @Override + String getServer() { + return ""; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[0]; + } + + @Override + boolean isWebSocket() { + return true; + } + } + + public static class TransferTargetQuery extends Type { + public final long sessionNumber; + public final byte[] payload; + + public TransferTargetQuery(long sessionNumber, byte[] payload) { + this.sessionNumber = sessionNumber; + this.payload = payload; + } + + public TransferTargetQuery(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 2) { + throw new DecodingException(); + } + this.sessionNumber = encodedParts[0].decodeLong(); + this.payload = encodedParts[1].decodeBytes(); + } + + @Override + public TypeId getId() { + return TypeId.TRANSFER_TARGET_QUERY_ID; + } + + @Override + String getServer() { + return ""; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(sessionNumber), + Encoded.of(payload), + }; + } + + @Override + boolean isWebSocket() { + return true; + } + } + + public static class TransferRelayQuery extends Type { + public final String connectionIdentifier; + public final byte[] payload; + public final boolean noResponseExpected; + + public TransferRelayQuery(String connectionIdentifier, byte[] payload, boolean noResponseExpected) { + this.connectionIdentifier = connectionIdentifier; + this.payload = payload; + this.noResponseExpected = noResponseExpected; + } + + public TransferRelayQuery(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 3) { + throw new DecodingException(); + } + this.connectionIdentifier = encodedParts[0].decodeString(); + this.payload = encodedParts[1].decodeBytes(); + this.noResponseExpected = encodedParts[2].decodeBoolean(); + } + + @Override + public TypeId getId() { + return TypeId.TRANSFER_RELAY_QUERY_ID; + } + + @Override + String getServer() { + return ""; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[] { + Encoded.of(connectionIdentifier), + Encoded.of(payload), + Encoded.of(noResponseExpected), + }; + } + + @Override + boolean isWebSocket() { + return true; + } + } + + public static class TransferWaitQuery extends Type { + public TransferWaitQuery() { + } + + public TransferWaitQuery(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public TypeId getId() { + return TypeId.TRANSFER_WAIT_QUERY_ID; + } + + @Override + String getServer() { + return ""; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[0]; + } + + @Override + boolean isWebSocket() { + return true; + } + } + + public static class TransferCloseQuery extends Type { + public final boolean abort; + public TransferCloseQuery(boolean abort) { + this.abort = abort; + } + + public TransferCloseQuery(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.abort = encodedParts[0].decodeBoolean(); + } + + @Override + public TypeId getId() { + return TypeId.TRANSFER_CLOSE_QUERY_ID; + } + + @Override + String getServer() { + return ""; + } + + @Override + Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(abort), + }; + } + + @Override + boolean isWebSocket() { + return true; + } + }} diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/notifications/ProtocolNotifications.java b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/notifications/ProtocolNotifications.java index cb8944bc..31bf8f67 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/notifications/ProtocolNotifications.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/datatypes/notifications/ProtocolNotifications.java @@ -47,4 +47,6 @@ public abstract class ProtocolNotifications { public static final String NOTIFICATION_CONTACT_INTRODUCTION_INVITATION_RESPONSE_MEDIATOR_IDENTITY_KEY = "mediator_identity"; // Identity public static final String NOTIFICATION_CONTACT_INTRODUCTION_INVITATION_RESPONSE_CONTACT_SERIALIZED_DETAILS_KEY = "contact_serialized_details"; // String public static final String NOTIFICATION_CONTACT_INTRODUCTION_INVITATION_RESPONSE_ACCEPTED_KEY = "accepted"; // boolean + + public static final String NOTIFICATION_SNAPSHOT_RESTORATION_FINISHED = "protocol_manager_notification_snapshot_restoration_finished"; } 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 1dfda224..8dd0cd44 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 @@ -235,7 +235,7 @@ public Engine(File baseDirectory, ObvBackupAndSyncDelegate appBackupAndSyncDeleg } this.channelManager = new ChannelManager(metaManager); - this.identityManager = new IdentityManager(metaManager, baseDirectoryPath, jsonObjectMapper); + this.identityManager = new IdentityManager(metaManager, baseDirectoryPath, jsonObjectMapper, prng); this.fetchManager = new FetchManager(metaManager, sslSocketFactory, baseDirectoryPath, prng, jsonObjectMapper); this.sendManager = new SendManager(metaManager, sslSocketFactory, baseDirectoryPath, prng); this.notificationManager = new NotificationManager(metaManager); @@ -243,6 +243,7 @@ public Engine(File baseDirectory, ObvBackupAndSyncDelegate appBackupAndSyncDeleg this.backupManager = new BackupManager(metaManager, prng, jsonObjectMapper); registerToInternalNotifications(); + initializationComplete(); metaManager.initializationComplete(); } @@ -250,6 +251,29 @@ private static void upgradeTables(Session session, int oldVersion, int newVersio UserInterfaceDialog.upgradeTable(session, oldVersion, newVersion); } + private void initializationComplete() { + try { + // clear all transfer protocol UserInterfaceDialog + try (EngineSession engineSession = getSession()) { + for (UserInterfaceDialog userInterfaceDialog : UserInterfaceDialog.getAll(engineSession)) { + try { + if (userInterfaceDialog.getObvDialog().getCategory().getId() == ObvDialog.Category.TRANSFER_DIALOG_CATEGORY) { + userInterfaceDialog.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + try { + userInterfaceDialog.delete(); + } catch (Exception ignored) { } + } + } + engineSession.session.commit(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + private void deleteRecursive(File fileOrDirectory) { if (fileOrDirectory == null) { return; @@ -546,6 +570,10 @@ ObvDialog createDialog(ChannelDialogMessageToSend channelDialogMessageToSend) { category = ObvDialog.Category.createSyncItemToApply(dialogType.obvSyncAtom); break; } + case DialogType.TRANSFER_DIALOG_ID: { + category = ObvDialog.Category.createTransferDialog(dialogType.obvTransferStep); + break; + } default: Logger.w("Unknown DialogType " + dialogType.id); return null; @@ -646,7 +674,7 @@ public RegisterApiKeyResult registerOwnedIdentityApiKeyOnServer(byte[] bytesOwne return RegisterApiKeyResult.FAILED; } - StandaloneServerQueryOperation standaloneServerQueryOperation = new StandaloneServerQueryOperation(new ServerQuery(null, ownedIdentity, ServerQuery.Type.createRegisterApiKey(ownedIdentity, serverSessionToken, Logger.getUuidString(apiKey)))); + StandaloneServerQueryOperation standaloneServerQueryOperation = new StandaloneServerQueryOperation(new ServerQuery(null, ownedIdentity, new ServerQuery.RegisterApiKeyQuery(ownedIdentity, serverSessionToken, Logger.getUuidString(apiKey)))); OperationQueue queue = new OperationQueue(); queue.queue(standaloneServerQueryOperation); @@ -1052,7 +1080,7 @@ public ObvDeviceList queryRegisteredOwnedDevicesFromServer(byte[] bytesOwnedIden try (EngineSession engineSession = getSession()) { Identity ownedIdentity = Identity.of(bytesOwnedIdentity); - StandaloneServerQueryOperation standaloneServerQueryOperation = new StandaloneServerQueryOperation(new ServerQuery(null, ownedIdentity, ServerQuery.Type.createOwnedDeviceDiscoveryQuery(ownedIdentity))); + StandaloneServerQueryOperation standaloneServerQueryOperation = new StandaloneServerQueryOperation(new ServerQuery(null, ownedIdentity, new ServerQuery.OwnedDeviceDiscoveryQuery(ownedIdentity))); OperationQueue queue = new OperationQueue(); queue.queue(standaloneServerQueryOperation); @@ -1855,6 +1883,17 @@ public void addKeycloakContact(byte[] bytesOwnedIdentity, byte[] bytesContactIde protocolManager.addKeycloakContact(ownedIdentity, contactIdentity, signedContactDetails); } + @Override + public void initiateOwnedIdentityTransferProtocolOnSourceDevice(byte[] bytesOwnedIdentity) throws Exception { + Identity ownedIdentity = Identity.of(bytesOwnedIdentity); + protocolManager.initiateOwnedIdentityTransferProtocolOnSourceDevice(ownedIdentity); + } + + @Override + public void initiateOwnedIdentityTransferProtocolOnTargetDevice(String deviceName) throws Exception { + protocolManager.initiateOwnedIdentityTransferProtocolOnTargetDevice(deviceName); + } + // endregion diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/NotificationListenerChannelsAndProtocols.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/NotificationListenerChannelsAndProtocols.java index f38843a4..6c8d1734 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/NotificationListenerChannelsAndProtocols.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/NotificationListenerChannelsAndProtocols.java @@ -54,6 +54,7 @@ void registerToNotifications(NotificationManager notificationManager) { ProtocolNotifications.NOTIFICATION_KEYCLOAK_SYNCHRONIZATION_REQUIRED, ProtocolNotifications.NOTIFICATION_CONTACT_INTRODUCTION_INVITATION_SENT, ProtocolNotifications.NOTIFICATION_CONTACT_INTRODUCTION_INVITATION_RESPONSE, + ProtocolNotifications.NOTIFICATION_SNAPSHOT_RESTORATION_FINISHED, }) { notificationManager.addListener(notificationName, this); } @@ -216,6 +217,10 @@ public void callback(String notificationName, HashMap userInfo) engine.postEngineNotification(EngineNotifications.CONTACT_INTRODUCTION_INVITATION_RESPONSE, engineInfo); break; } + case ProtocolNotifications.NOTIFICATION_SNAPSHOT_RESTORATION_FINISHED: { + engine.postEngineNotification(EngineNotifications.ENGINE_SNAPSHOT_RESTORATION_FINISHED, new HashMap<>()); + 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/databases/UserInterfaceDialog.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/databases/UserInterfaceDialog.java index 5accbd54..1a001257 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/databases/UserInterfaceDialog.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/databases/UserInterfaceDialog.java @@ -216,6 +216,7 @@ public static UserInterfaceDialog[] getAll(EngineSession engineSession) { return list.toArray(new UserInterfaceDialog[0]); } } catch (SQLException e) { + e.printStackTrace(); return new UserInterfaceDialog[0]; } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineAPI.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineAPI.java index df999f24..e2bfb76c 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineAPI.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/EngineAPI.java @@ -181,6 +181,9 @@ enum ApiKeyStatus { void deleteOwnedIdentityAndNotifyContacts(byte[] bytesOwnedIdentity, boolean deleteEverywhere) throws Exception; void queryGroupOwnerForLatestGroupMembers(byte[] bytesGroupOwnerAndUid, byte[] bytesOwnedIdentity) throws Exception; void addKeycloakContact(byte[] bytesOwnedIdentity, byte[] bytesContactIdentity, String signedContactDetails) throws Exception; + void initiateOwnedIdentityTransferProtocolOnSourceDevice(byte[] bytesOwnedIdentity) throws Exception; + void initiateOwnedIdentityTransferProtocolOnTargetDevice(String deviceName) throws Exception; + // Post/receive messages byte[] getReturnReceiptNonce(); 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 119b43c2..69dbc9f6 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 @@ -297,6 +297,8 @@ public abstract class EngineNotifications { public static final String ENGINE_BACKUP_RESTORATION_FINISHED = "engine_notification_engine_backup_restoration_finished"; + public static final String ENGINE_SNAPSHOT_RESTORATION_FINISHED = "engine_notification_engine_snapshot_restoration_finished"; + public static final String PING_LOST = "engine_notification_ping_lost"; public static final String PING_RECEIVED = "engine_notification_ping_received"; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvBytesKey.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvBytesKey.java index 026ee63e..56ddd325 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvBytesKey.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvBytesKey.java @@ -65,7 +65,7 @@ public int compareTo(ObvBytesKey other) { } - public static class Serializer extends JsonSerializer { + public static class KeySerializer extends JsonSerializer { @Override public void serialize(ObvBytesKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeFieldName(serializers.getConfig().getBase64Variant().encode(value.bytes)); @@ -79,6 +79,13 @@ public Object deserializeKey(String key, DeserializationContext ctxt) throws IOE } } + public static class Serializer extends JsonSerializer { + @Override + public void serialize(ObvBytesKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(serializers.getConfig().getBase64Variant().encode(value.bytes)); + } + } + public static class Deserializer extends JsonDeserializer { @Override public ObvBytesKey deserialize(JsonParser p, DeserializationContext context) throws IOException { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvDialog.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvDialog.java index 2d602b97..e6b51805 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvDialog.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvDialog.java @@ -137,6 +137,40 @@ public void setResponseToAcceptOneToOneInvitation(boolean acceptInvitation) thro throw new Exception(); } } + + public void setAbortTransfer() throws Exception { + if (this.category.id == Category.TRANSFER_DIALOG_CATEGORY) { + encodedResponse = null; + } else { + throw new Exception(); + } + } + + public void setTransferSessionNumber(long sessionNumber) throws Exception { + if (this.category.id == Category.TRANSFER_DIALOG_CATEGORY && this.category.obvTransferStep.getStep() == ObvTransferStep.Step.TARGET_SESSION_NUMBER_INPUT) { + encodedResponse = Encoded.of(sessionNumber); + } else { + throw new Exception(); + } + } + + public void setTransferSasAndDeviceUid(String sas, byte[] deviceUidToKeepActive) throws Exception { + if (this.category.id == Category.TRANSFER_DIALOG_CATEGORY && this.category.obvTransferStep.getStep() == ObvTransferStep.Step.SOURCE_SAS_INPUT) { + if (deviceUidToKeepActive == null) { + encodedResponse = Encoded.of(new Encoded[]{ + Encoded.of(sas), + }); + } else { + encodedResponse = Encoded.of(new Encoded[]{ + Encoded.of(sas), + Encoded.of(deviceUidToKeepActive), + }); + } + } else { + throw new Exception(); + } + } + // endregion @@ -163,6 +197,8 @@ public static class Category { public static final int GROUP_V2_INVITATION_DIALOG_CATEGORY = 15; public static final int GROUP_V2_FROZEN_INVITATION_DIALOG_CATEGORY = 16; public static final int SYNC_ITEM_TO_APPLY_DIALOG_CATEGORY = 17; + public static final int TRANSFER_DIALOG_CATEGORY = 18; + private final int id; private final byte[] bytesContactIdentity; @@ -176,9 +212,10 @@ public static class Category { public final Long serverTimestamp; private final ObvGroupV2 obvGroupV2; private final ObvSyncAtom obvSyncAtom; + public final ObvTransferStep obvTransferStep; - public Category(int id, byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] sasToDisplay, byte[] sasEntered, byte[] bytesMediatorOrGroupOwnerIdentity, String serializedGroupDetails, byte[] bytesGroupUid, ObvIdentity[] pendingGroupMemberIdentities, Long serverTimestamp, ObvGroupV2 obvGroupV2, ObvSyncAtom obvSyncAtom) { + public Category(int id, byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] sasToDisplay, byte[] sasEntered, byte[] bytesMediatorOrGroupOwnerIdentity, String serializedGroupDetails, byte[] bytesGroupUid, ObvIdentity[] pendingGroupMemberIdentities, Long serverTimestamp, ObvGroupV2 obvGroupV2, ObvSyncAtom obvSyncAtom, ObvTransferStep obvTransferStep) { this.id = id; this.bytesContactIdentity = bytesContactIdentity; this.contactDisplayNameOrSerializedDetails = contactDisplayNameOrSerializedDetails; @@ -191,6 +228,7 @@ public Category(int id, byte[] bytesContactIdentity, String contactDisplayNameOr this.serverTimestamp = serverTimestamp; this.obvGroupV2 = obvGroupV2; this.obvSyncAtom = obvSyncAtom; + this.obvTransferStep = obvTransferStep; } public int getId() { @@ -241,6 +279,10 @@ public ObvSyncAtom getObvSyncItem() { return obvSyncAtom; } + public ObvTransferStep getObvTransferStep() { + return obvTransferStep; + } + private static Category of(Encoded encoded, ObjectMapper jsonObjectMapper) throws Exception { Encoded[] list = encoded.decodeList(); if (list.length != 2) { @@ -258,6 +300,7 @@ private static Category of(Encoded encoded, ObjectMapper jsonObjectMapper) throw Long serverTimestamp = null; ObvGroupV2 obvGroupV2 = null; ObvSyncAtom obvSyncAtom = null; + ObvTransferStep obvTransferStep = null; Encoded[] vars = list[1].decodeList(); switch (id) { @@ -358,6 +401,13 @@ private static Category of(Encoded encoded, ObjectMapper jsonObjectMapper) throw obvSyncAtom = ObvSyncAtom.of(vars[0]); break; } + case TRANSFER_DIALOG_CATEGORY: { + if (vars.length != 1) { + throw new DecodingException(); + } + obvTransferStep = ObvTransferStep.of(vars[0]); + break; + } default: if (vars.length != 2) { throw new DecodingException(); @@ -366,7 +416,7 @@ private static Category of(Encoded encoded, ObjectMapper jsonObjectMapper) throw contactDisplayNameOrSerializedDetails = vars[1].decodeString(); break; } - return new Category(id, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, sasEntered, bytesMediatorOrGroupOwnerIdentity, serializedGroupDetails, bytesGroupUid, pendingGroupMemberIdentities, serverTimestamp, obvGroupV2, obvSyncAtom); + return new Category(id, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, sasEntered, bytesMediatorOrGroupOwnerIdentity, serializedGroupDetails, bytesGroupUid, pendingGroupMemberIdentities, serverTimestamp, obvGroupV2, obvSyncAtom, obvTransferStep); } private Encoded encode(ObjectMapper jsonObjectMapper) { @@ -468,6 +518,11 @@ private Encoded encode(ObjectMapper jsonObjectMapper) { obvSyncAtom.encode(), }); break; + case TRANSFER_DIALOG_CATEGORY: + encodedVars = Encoded.of(new Encoded[]{ + obvTransferStep.encode(), + }); + break; } return Encoded.of(new Encoded[]{ Encoded.of(id), @@ -476,55 +531,59 @@ private Encoded encode(ObjectMapper jsonObjectMapper) { } public static Category createInviteSent(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails) { - return new Category(INVITE_SENT_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, null, null, null); + return new Category(INVITE_SENT_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, null, null, null, null); } public static Category createAcceptInvite(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, long serverTimestamp) { - return new Category(ACCEPT_INVITE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, serverTimestamp, null, null); + return new Category(ACCEPT_INVITE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, serverTimestamp, null, null, null); } public static Category createSasExchange(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] sasToDisplay, long serverTimestamp) { - return new Category(SAS_EXCHANGE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, null, null, null, null, null, serverTimestamp, null, null); + return new Category(SAS_EXCHANGE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, null, null, null, null, null, serverTimestamp, null, null, null); } public static Category createSasConfirmed(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] sasToDisplay, byte[] sasEntered) { - return new Category(SAS_CONFIRMED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, sasEntered, null, null, null, null, null, null, null); + return new Category(SAS_CONFIRMED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, sasToDisplay, sasEntered, null, null, null, null, null, null, null, null); } public static Category createInviteAccepted(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails) { - return new Category(INVITE_ACCEPTED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, null, null, null); + return new Category(INVITE_ACCEPTED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, null, null, null, null, null, null, null, null); } public static Category createAcceptMediatorInvite(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] bytesMediatorIdentity, long serverTimestamp) { - return new Category(ACCEPT_MEDIATOR_INVITE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, bytesMediatorIdentity, null, null, null, serverTimestamp, null, null); + return new Category(ACCEPT_MEDIATOR_INVITE_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, bytesMediatorIdentity, null, null, null, serverTimestamp, null, null, null); } public static Category createMediatorInviteAccepted(byte[] bytesContactIdentity, String contactDisplayNameOrSerializedDetails, byte[] bytesMediatorIdentity) { - return new Category(MEDIATOR_INVITE_ACCEPTED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, bytesMediatorIdentity, null, null, null, null, null, null); + return new Category(MEDIATOR_INVITE_ACCEPTED_DIALOG_CATEGORY, bytesContactIdentity, contactDisplayNameOrSerializedDetails, null, null, bytesMediatorIdentity, null, null, null, null, null, null, null); } public static Category createAcceptGroupInvite(String serializedGroupDetails, byte[] groupId, byte[] bytesGroupOwnerIdentity, ObvIdentity[] pendingGroupMemberIdentities, long serverTimestamp) { - return new Category(ACCEPT_GROUP_INVITE_DIALOG_CATEGORY, null, null, null, null, bytesGroupOwnerIdentity, serializedGroupDetails, groupId, pendingGroupMemberIdentities, serverTimestamp, null, null); + return new Category(ACCEPT_GROUP_INVITE_DIALOG_CATEGORY, null, null, null, null, bytesGroupOwnerIdentity, serializedGroupDetails, groupId, pendingGroupMemberIdentities, serverTimestamp, null, null, null); } public static Category createOneToOneInvitationSent(byte[] bytesContactIdentity) { - return new Category(ONE_TO_ONE_INVITATION_SENT_DIALOG_CATEGORY, bytesContactIdentity, null, null, null, null, null, null, null, null, null, null); + return new Category(ONE_TO_ONE_INVITATION_SENT_DIALOG_CATEGORY, bytesContactIdentity, null, null, null, null, null, null, null, null, null, null, null); } public static Category createAcceptOneToOneInvitation(byte[] bytesContactIdentity, Long serverTimestamp) { - return new Category(ACCEPT_ONE_TO_ONE_INVITATION_DIALOG_CATEGORY, bytesContactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null); + return new Category(ACCEPT_ONE_TO_ONE_INVITATION_DIALOG_CATEGORY, bytesContactIdentity, null, null, null, null, null, null, null, serverTimestamp, null, null, null); } public static Category createGroupV2Invitation(byte[] bytesInviterIdentity, ObvGroupV2 obvGroupV2) { - return new Category(GROUP_V2_INVITATION_DIALOG_CATEGORY, null, null, null, null, bytesInviterIdentity, null, null, null, null, obvGroupV2, null); + return new Category(GROUP_V2_INVITATION_DIALOG_CATEGORY, null, null, null, null, bytesInviterIdentity, null, null, null, null, obvGroupV2, null, null); } public static Category createGroupV2FrozenInvitation(byte[] bytesInviterIdentity, ObvGroupV2 obvGroupV2) { - return new Category(GROUP_V2_FROZEN_INVITATION_DIALOG_CATEGORY, null, null, null, null, bytesInviterIdentity, null, null, null, null, obvGroupV2, null); + return new Category(GROUP_V2_FROZEN_INVITATION_DIALOG_CATEGORY, null, null, null, null, bytesInviterIdentity, null, null, null, null, obvGroupV2, null, null); } public static Category createSyncItemToApply(ObvSyncAtom obvSyncAtom) { - return new Category(SYNC_ITEM_TO_APPLY_DIALOG_CATEGORY, null, null, null, null, null, null, null, null, null, null, obvSyncAtom); + return new Category(SYNC_ITEM_TO_APPLY_DIALOG_CATEGORY, null, null, null, null, null, null, null, null, null, null, obvSyncAtom, null); + } + + public static Category createTransferDialog(ObvTransferStep obvTransferStep) { + return new Category(TRANSFER_DIALOG_CATEGORY, null, null, null, null, null, null, null, null, null, null, null, obvTransferStep); } } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvTransferStep.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvTransferStep.java new file mode 100644 index 00000000..04372f62 --- /dev/null +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/ObvTransferStep.java @@ -0,0 +1,322 @@ +/* + * Olvid for Android + * Copyright © 2019-2023 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.engine.types; + +import java.util.HashMap; +import java.util.Map; + +import io.olvid.engine.encoder.DecodingException; +import io.olvid.engine.encoder.Encoded; + +public abstract class ObvTransferStep { + public abstract Step getStep(); + public abstract Encoded[] getEncodedParts(); + + + public static ObvTransferStep of(Encoded encoded) throws DecodingException { + Encoded[] list = encoded.decodeList(); + if (list.length != 2) { + throw new DecodingException(); + } + int id = (int) list[0].decodeLong(); + Encoded[] encodedParts = list[1].decodeList(); + Step step = Step.fromIntValue(id); + if (step == null) { + throw new DecodingException(); + } + switch (step) { + case FAIL: + return new Fail(encodedParts); + case SOURCE_WAIT_FOR_SESSION_NUMBER: + return new SourceWaitForSessionNumberStep(encodedParts); + case SOURCE_DISPLAY_SESSION_NUMBER: + return new SourceDisplaySessionNumber(encodedParts); + case TARGET_SESSION_NUMBER_INPUT: + return new TargetSessionNumberInput(encodedParts); + case ONGOING_PROTOCOL: + return new OngoingProtocol(encodedParts); + case SOURCE_SAS_INPUT: + return new SourceSasInput(encodedParts); + case TARGET_SHOW_SAS: + return new TargetShowSas(encodedParts); + case SOURCE_SNAPSHOT_SENT: + return new SourceSnapshotSent(encodedParts); + case TARGET_SNAPSHOT_RECEIVED: + return new TargetSnapshotReceived(encodedParts); + default: + throw new DecodingException(); + } + } + + Encoded encode() { + return Encoded.of(new Encoded[] { + Encoded.of(getStep().value), + Encoded.of(getEncodedParts()), + }); + } + + + public enum Step { + FAIL(1000), + SOURCE_WAIT_FOR_SESSION_NUMBER(0), + SOURCE_DISPLAY_SESSION_NUMBER(1), + TARGET_SESSION_NUMBER_INPUT(2), + ONGOING_PROTOCOL(3), + SOURCE_SAS_INPUT(4), + TARGET_SHOW_SAS(5), + SOURCE_SNAPSHOT_SENT(6), + TARGET_SNAPSHOT_RECEIVED(7); + + private static final Map valueMap = new HashMap<>(); + static { + for (Step step : values()) { + valueMap.put(step.value, step); + } + } + + final int value; + + Step(int value) { + this.value = value; + } + + static Step fromIntValue(int value) { + return valueMap.get(value); + } + } + + public static class SourceWaitForSessionNumberStep extends ObvTransferStep { + public SourceWaitForSessionNumberStep() { + } + + public SourceWaitForSessionNumberStep(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public Step getStep() { + return Step.SOURCE_WAIT_FOR_SESSION_NUMBER; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[0]; + } + } + + public static class SourceDisplaySessionNumber extends ObvTransferStep { + public final long sessionNumber; + public SourceDisplaySessionNumber(long sessionNumber) { + this.sessionNumber = sessionNumber; + } + + public SourceDisplaySessionNumber(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.sessionNumber = encodedParts[0].decodeLong(); + } + + @Override + public Step getStep() { + return Step.SOURCE_DISPLAY_SESSION_NUMBER; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[] { + Encoded.of(sessionNumber), + }; + } + } + + public static class Fail extends ObvTransferStep { + public static final int FAIL_REASON_NETWORK_ERROR = 1; + public static final int FAIL_REASON_TRANSFERRED_IDENTITY_ALREADY_EXISTS = 2; + public static final int FAIL_REASON_INVALID_RESPONSE = 3; + public final int failReason; + public Fail(int failReason) { + this.failReason = failReason; + } + + public Fail(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.failReason = (int) encodedParts[0].decodeLong(); + } + + @Override + public Step getStep() { + return Step.FAIL; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[] { + Encoded.of(failReason), + }; + } + } + + public static class TargetSessionNumberInput extends ObvTransferStep { + public TargetSessionNumberInput() { + } + + public TargetSessionNumberInput(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public Step getStep() { + return Step.TARGET_SESSION_NUMBER_INPUT; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[0]; + } + } + + public static class OngoingProtocol extends ObvTransferStep { + public OngoingProtocol() { + } + + public OngoingProtocol(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public Step getStep() { + return Step.ONGOING_PROTOCOL; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[0]; + } + } + + public static class SourceSasInput extends ObvTransferStep { + public final String correctSas; + public final String targetDeviceName; + + public SourceSasInput(String correctSas, String targetDeviceName) { + this.correctSas = correctSas; + this.targetDeviceName = targetDeviceName; + } + + public SourceSasInput(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 2) { + throw new DecodingException(); + } + this.correctSas = encodedParts[0].decodeString(); + this.targetDeviceName = encodedParts[1].decodeString(); + } + + @Override + public Step getStep() { + return Step.SOURCE_SAS_INPUT; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(correctSas), + Encoded.of(targetDeviceName), + }; + } + } + + public static class TargetShowSas extends ObvTransferStep { + public final String sas; + public TargetShowSas(String sas) { + this.sas = sas; + } + + public TargetShowSas(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 1) { + throw new DecodingException(); + } + this.sas = encodedParts[0].decodeString(); + } + + @Override + public Step getStep() { + return Step.TARGET_SHOW_SAS; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[]{ + Encoded.of(sas), + }; + } + } + + public static class SourceSnapshotSent extends ObvTransferStep { + public SourceSnapshotSent() { + } + + public SourceSnapshotSent(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public Step getStep() { + return Step.SOURCE_SNAPSHOT_SENT; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[0]; + } + } + + public static class TargetSnapshotReceived extends ObvTransferStep { + public TargetSnapshotReceived() { + } + + public TargetSnapshotReceived(Encoded[] encodedParts) throws DecodingException { + if (encodedParts.length != 0) { + throw new DecodingException(); + } + } + + @Override + public Step getStep() { + return Step.TARGET_SNAPSHOT_RECEIVED; + } + + @Override + public Encoded[] getEncodedParts() { + return new Encoded[0]; + } + } +} + diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvBackupAndSyncDelegate.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvBackupAndSyncDelegate.java index ced21ecf..f0df6172 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvBackupAndSyncDelegate.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvBackupAndSyncDelegate.java @@ -20,6 +20,7 @@ package io.olvid.engine.engine.types.sync; import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.engine.types.identities.ObvIdentity; public interface ObvBackupAndSyncDelegate { ////// @@ -28,8 +29,16 @@ public interface ObvBackupAndSyncDelegate { ////// // This method computes a snapshot of the data to sync - ObvSyncSnapshotNode getSyncSnapshot(Identity ownedIdentity); + ObvSyncSnapshotNode getSyncSnapshot(Identity ownedIdentity); // TODO: we probably need to add a context as we do not want to include the same elements for a backup or a sync + ////// + // This method allows each delegate to crate an owned identity base on the ObvIdentity the engine has restored + RestoreFinishedCallback restoreOwnedIdentity(ObvIdentity ownedIdentity, ObvSyncSnapshotNode node) throws Exception; + ////// + // This method restores a Snapshot, assuming the owned identity already exists in db. + // - it may return a callback that will only get called if the restore was successful for all delegates. + // - this callback can be used to commit a transaction on app side, only if the engine restore is successful, and roll it back otherwise + RestoreFinishedCallback restoreSyncSnapshot(ObvSyncSnapshotNode node) throws Exception; ////// // Method used to deserialize a node that was serialized with ObvSyncSnapshotNode.serialize(ObjectMapper jsonObjectMapper) @@ -38,4 +47,9 @@ public interface ObvBackupAndSyncDelegate { // Method used to deserialize a node that was serialized with ObvSyncSnapshotNode.serialize(ObjectMapper jsonObjectMapper) ObvSyncSnapshotNode deserialize(byte[] serializedSnapshotNode) throws Exception; + + interface RestoreFinishedCallback { + void onRestoreSuccess(); + void onRestoreFailure(); + } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvSyncSnapshot.java index 59f7fea3..714152f0 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvSyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/engine/types/sync/ObvSyncSnapshot.java @@ -28,11 +28,12 @@ import io.olvid.engine.datatypes.DictionaryKey; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.encoder.Encoded; +import io.olvid.engine.engine.types.identities.ObvIdentity; public class ObvSyncSnapshot { private final HashMap snapshotMap; - public ObvSyncSnapshot(HashMap snapshotMap) { + private ObvSyncSnapshot(HashMap snapshotMap) { this.snapshotMap = snapshotMap; } @@ -44,6 +45,57 @@ public static ObvSyncSnapshot get(Identity ownedIdentity, ObvBackupAndSyncDelega return new ObvSyncSnapshot(snapshotMap); } + + public List restoreOwnedIdentity(ObvIdentity obvOwnedIdentity, ObvBackupAndSyncDelegate... delegates) throws Exception { + List callbacks = new ArrayList<>(); + try { + for (ObvBackupAndSyncDelegate delegate : delegates) { + ObvSyncSnapshotNode node = snapshotMap.get(delegate.getTag()); + if (node == null) { + throw new Exception(); + } + ObvBackupAndSyncDelegate.RestoreFinishedCallback callback = delegate.restoreOwnedIdentity(obvOwnedIdentity, node); + if (callback != null) { + callbacks.add(callback); + } + } + return callbacks; + } catch (Exception e) { + // if an exception occurs, call the onRestoreFailure of all callbacks we already got (typically to rollback transactions) + for (ObvBackupAndSyncDelegate.RestoreFinishedCallback callback : callbacks) { + try { + callback.onRestoreFailure(); + } catch (Exception ignored) { } + } + throw e; + } + } + + public List restore(ObvBackupAndSyncDelegate... delegates) throws Exception { + List callbacks = new ArrayList<>(); + try { + for (ObvBackupAndSyncDelegate delegate : delegates) { + ObvSyncSnapshotNode node = snapshotMap.get(delegate.getTag()); + if (node == null) { + throw new Exception(); + } + ObvBackupAndSyncDelegate.RestoreFinishedCallback callback = delegate.restoreSyncSnapshot(node); + if (callback != null) { + callbacks.add(callback); + } + } + return callbacks; + } catch (Exception e) { + // if an exception occurs, call the onRestoreFailure of all callbacks we already got (typically to rollback transactions) + for (ObvBackupAndSyncDelegate.RestoreFinishedCallback callback : callbacks) { + try { + callback.onRestoreFailure(); + } catch (Exception ignored) { } + } + throw e; + } + } + public HashMap toEncodedDictionary(ObvBackupAndSyncDelegate... delegates) { try { HashMap map = new HashMap<>(); @@ -56,6 +108,7 @@ public HashMap toEncodedDictionary(ObvBackupAndSyncDeleg } return map; } catch (Exception e) { + e.printStackTrace(); return null; } } @@ -73,6 +126,7 @@ public static ObvSyncSnapshot fromEncodedDictionary(HashMap computeDiff(ObvSyncSnapshot otherSnapshot) throws Excep return diffs; } + + public ObvSyncSnapshotNode getSnapshotNode(String tag) { + return snapshotMap.get(tag); + } } 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 7c2a756e..711fbd2e 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 @@ -90,13 +90,13 @@ import io.olvid.engine.engine.types.JsonKeycloakRevocation; import io.olvid.engine.engine.types.JsonKeycloakUserDetails; import io.olvid.engine.engine.types.ObvCapability; -import io.olvid.engine.engine.types.sync.ObvBackupAndSyncDelegate; -import io.olvid.engine.engine.types.sync.ObvSyncAtom; import io.olvid.engine.engine.types.identities.ObvContactActiveOrInactiveReason; import io.olvid.engine.engine.types.identities.ObvGroupV2; import io.olvid.engine.engine.types.identities.ObvIdentity; import io.olvid.engine.engine.types.identities.ObvKeycloakState; import io.olvid.engine.engine.types.identities.ObvOwnedDevice; +import io.olvid.engine.engine.types.sync.ObvBackupAndSyncDelegate; +import io.olvid.engine.engine.types.sync.ObvSyncAtom; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.ContactDevice; import io.olvid.engine.identity.databases.ContactGroup; @@ -126,10 +126,10 @@ import io.olvid.engine.metamanager.BackupDelegate; import io.olvid.engine.metamanager.ChannelDelegate; import io.olvid.engine.metamanager.CreateSessionDelegate; -import io.olvid.engine.metamanager.IdentityDelegate; import io.olvid.engine.metamanager.EncryptionForIdentityDelegate; -import io.olvid.engine.metamanager.NotificationPostingDelegate; +import io.olvid.engine.metamanager.IdentityDelegate; import io.olvid.engine.metamanager.MetaManager; +import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.metamanager.ObvManager; import io.olvid.engine.metamanager.SolveChallengeDelegate; import io.olvid.engine.protocol.datatypes.ProtocolStarterDelegate; @@ -137,6 +137,7 @@ public class IdentityManager implements IdentityDelegate, SolveChallengeDelegate, EncryptionForIdentityDelegate, ObvBackupAndSyncDelegate, IdentityManagerSessionFactory, ObvManager { private final String engineBaseDirectory; private final ObjectMapper jsonObjectMapper; + private final PRNGService prng; private final SessionCommitListener backupNeededSessionCommitListener; private CreateSessionDelegate createSessionDelegate; @@ -144,9 +145,10 @@ public class IdentityManager implements IdentityDelegate, SolveChallengeDelegate private ProtocolStarterDelegate protocolStarterDelegate; private ChannelDelegate channelDelegate; - public IdentityManager(MetaManager metaManager, String engineBaseDirectory, ObjectMapper jsonObjectMapper) { + public IdentityManager(MetaManager metaManager, String engineBaseDirectory, ObjectMapper jsonObjectMapper, PRNGService prng) { this.engineBaseDirectory = engineBaseDirectory; this.jsonObjectMapper = jsonObjectMapper; + this.prng = prng; this.backupNeededSessionCommitListener = () -> { if (notificationPostingDelegate != null) { notificationPostingDelegate.postNotification(IdentityNotifications.NOTIFICATION_DATABASE_CONTENT_CHANGED, new HashMap<>()); @@ -412,18 +414,19 @@ public IdentityManagerSession getSession() throws SQLException { if (createSessionDelegate == null) { throw new SQLException("No CreateSessionDelegate was set in IdentityManager."); } - return new IdentityManagerSession(createSessionDelegate.getSession(), notificationPostingDelegate, this, engineBaseDirectory, jsonObjectMapper); + return new IdentityManagerSession(createSessionDelegate.getSession(), notificationPostingDelegate, this, engineBaseDirectory, jsonObjectMapper, prng); } private IdentityManagerSession wrapSession(Session session) { - return new IdentityManagerSession(session, notificationPostingDelegate, this, engineBaseDirectory, jsonObjectMapper); + return new IdentityManagerSession(session, notificationPostingDelegate, this, engineBaseDirectory, jsonObjectMapper, prng); } public ObjectMapper getJsonObjectMapper() { return jsonObjectMapper; } - public void downloadAllUserData(Session session) throws Exception { + @Override + public void downloadAllUserData(Session session) throws Exception { List ownedIdentityDetailsList = OwnedIdentityDetails.getAllWithMissingPhotoUrl(wrapSession(session)); for (OwnedIdentityDetails ownedIdentityDetails : ownedIdentityDetailsList) { protocolStarterDelegate.startDownloadIdentityPhotoProtocolWithinTransaction(session, ownedIdentityDetails.getOwnedIdentity(), ownedIdentityDetails.getOwnedIdentity(), ownedIdentityDetails.getJsonIdentityDetailsWithVersionAndPhoto()); @@ -3764,14 +3767,70 @@ public String getTag() { @Override public ObvSyncSnapshotNode getSyncSnapshot(Identity ownedIdentity) { try (IdentityManagerSession identityManagerSession = getSession()) { - // start a transaction to be sure the db is not modified while the snapshot is being computed! - identityManagerSession.session.startTransaction(); - return IdentityManagerSyncSnapshot.of(identityManagerSession, ownedIdentity); - } catch (Exception e) { + try { + // start a transaction to be sure the db is not modified while the snapshot is being computed! + identityManagerSession.session.startTransaction(); + return getSyncSnapshotWithinTransaction(identityManagerSession, ownedIdentity); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + // always rollback as the snapshot creation should never modify the DB. + identityManagerSession.session.rollback(); + } + } catch (SQLException e) { + e.printStackTrace(); return null; } } + + private ObvSyncSnapshotNode getSyncSnapshotWithinTransaction(IdentityManagerSession identityManagerSession, Identity ownedIdentity) throws Exception { + if (!identityManagerSession.session.isInTransaction()) { + Logger.e("ERROR: called IdentityManager.getSyncSnapshot outside a transaction!"); + throw new Exception(); + } + return IdentityManagerSyncSnapshot.of(identityManagerSession, ownedIdentity); + } + + + @Override + public RestoreFinishedCallback restoreOwnedIdentity(ObvIdentity ownedIdentity, ObvSyncSnapshotNode node) throws Exception { + // this method does not do anything for the IdentityManager: the ownedIdentity has already been restored before calling this + return null; + } + + @Override + public RestoreFinishedCallback restoreSyncSnapshot(ObvSyncSnapshotNode node) throws Exception { + try (IdentityManagerSession identityManagerSession = getSession()) { + boolean transactionSuccessful = false; + try { + // start a transaction to be sure the db is not modified while the snapshot is being computed! + identityManagerSession.session.startTransaction(); + RestoreFinishedCallback callback = restoreSyncSnapshotWithinTransaction(identityManagerSession, node); + transactionSuccessful = true; + return callback; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (transactionSuccessful) { + identityManagerSession.session.commit(); + } else { + identityManagerSession.session.rollback(); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + private RestoreFinishedCallback restoreSyncSnapshotWithinTransaction(IdentityManagerSession identityManagerSession, ObvSyncSnapshotNode node) throws Exception { + if (!(node instanceof IdentityManagerSyncSnapshot)) { + throw new Exception(); + } + ((IdentityManagerSyncSnapshot) node).restore(identityManagerSession, protocolStarterDelegate); + return null; + } @Override public byte[] serialize(ObvSyncSnapshotNode snapshotNode) throws Exception { @@ -3786,5 +3845,52 @@ public ObvSyncSnapshotNode deserialize(byte[] serializedSnapshotNode) throws Exc return jsonObjectMapper.readValue(serializedSnapshotNode, IdentityManagerSyncSnapshot.class); } + @Override + public ObvBackupAndSyncDelegate getSyncDelegateWithinTransaction(Session session) { + return new ObvBackupAndSyncDelegate() { + private final IdentityManagerSession identityManagerSession = wrapSession(session); + @Override + public String getTag() { + return IdentityManager.this.getTag(); + } + + @Override + public ObvSyncSnapshotNode getSyncSnapshot(Identity ownedIdentity) { + try { + return IdentityManager.this.getSyncSnapshotWithinTransaction(identityManagerSession, ownedIdentity); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public RestoreFinishedCallback restoreOwnedIdentity(ObvIdentity ownedIdentity, ObvSyncSnapshotNode node) throws Exception { + return IdentityManager.this.restoreOwnedIdentity(ownedIdentity, node); + } + + @Override + public RestoreFinishedCallback restoreSyncSnapshot(ObvSyncSnapshotNode node) throws Exception { + return IdentityManager.this.restoreSyncSnapshotWithinTransaction(identityManagerSession, node); + } + + @Override + public byte[] serialize(ObvSyncSnapshotNode snapshotNode) throws Exception { + return IdentityManager.this.serialize(snapshotNode); + } + + @Override + public ObvSyncSnapshotNode deserialize(byte[] serializedSnapshotNode) throws Exception { + return IdentityManager.this.deserialize(serializedSnapshotNode); + } + }; + } + + @Override + public ObvIdentity restoreTransferredOwnedIdentity(Session session, String deviceName, IdentityManagerSyncSnapshot node) throws Exception { + Identity ownedIdentity = Identity.of(node.owned_identity); + return node.owned_identity_node.restoreOwnedIdentity(wrapSession(session), deviceName, ownedIdentity); + } + // endregion } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroup.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroup.java index 6962f579..5d2cf499 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroup.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroup.java @@ -67,9 +67,9 @@ public class ContactGroup implements ObvDatabase { static final String GROUP_OWNER = "group_owner"; private int publishedDetailsVersion; static final String PUBLISHED_DETAILS_VERSION = "published_details_version"; - private int latestOrTrustedDetailsVersion; + public int latestOrTrustedDetailsVersion; static final String LATEST_OR_TRUSTED_DETAILS_VERSION = "latest_or_trusted_details_version"; - private long groupMembersVersion; + public long groupMembersVersion; static final String GROUP_MEMBERS_VERSION = "group_members_version"; public byte[] getGroupOwnerAndUid() { @@ -538,7 +538,7 @@ public static ContactGroup create(IdentityManagerSession identityManagerSession, } } - private ContactGroup(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, Identity groupOwner, int version) { + public ContactGroup(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, Identity groupOwner, int version) { this.identityManagerSession = identityManagerSession; this.groupOwnerAndUid = groupOwnerAndUid; this.ownedIdentity = ownedIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupDetails.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupDetails.java index d5703955..bbe6f8cc 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupDetails.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupDetails.java @@ -73,6 +73,10 @@ public int getVersion() { return version; } + public String getSerializedJsonDetails() { + return serializedJsonDetails; + } + public JsonGroupDetails getJsonGroupDetails() { try { return identityManagerSession.jsonObjectMapper.readValue(serializedJsonDetails, JsonGroupDetails.class); @@ -252,7 +256,7 @@ public static ContactGroupDetails copy(IdentityManagerSession identityManagerSes } - private ContactGroupDetails(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { + public ContactGroupDetails(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { this.identityManagerSession = identityManagerSession; this.groupOwnerAndUid = groupOwnerAndUid; this.ownedIdentity = ownedIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupMembersJoin.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupMembersJoin.java index 59ac8655..5c7a2127 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupMembersJoin.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupMembersJoin.java @@ -197,14 +197,14 @@ public static ContactGroupMembersJoin get(IdentityManagerSession identityManager } - public static Identity[] getContactIdentitiesInGroup(IdentityManagerSession identityManagerSession, byte[] groupUid, Identity ownedIdentity) { + public static Identity[] getContactIdentitiesInGroup(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity) { try (PreparedStatement statement = identityManagerSession.session.prepareStatement( "SELECT contact." + ContactIdentity.CONTACT_IDENTITY + " FROM " + TABLE_NAME + " AS joiin " + " INNER JOIN " + ContactIdentity.TABLE_NAME + " AS contact " + " ON contact." + ContactIdentity.CONTACT_IDENTITY + " = joiin." + CONTACT_IDENTITY + " AND contact." + ContactIdentity.OWNED_IDENTITY + " = joiin." + OWNED_IDENTITY + " WHERE joiin." + GROUP_OWNER_AND_UID + " = ? AND joiin." + OWNED_IDENTITY + " = ?;")) { - statement.setBytes(1, groupUid); + statement.setBytes(1, groupOwnerAndUid); statement.setBytes(2, ownedIdentity.getBytes()); try (ResultSet res = statement.executeQuery()) { List list = new ArrayList<>(); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2.java index 811e9272..c03cdd88 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2.java @@ -80,7 +80,7 @@ public class ContactGroupV2 implements ObvDatabase { static final String SERIALIZED_OWN_PERMISSIONS = "serialized_own_permissions"; private int version; // always 0 for a keycloak group static final String VERSION = "version"; - private int trustedDetailsVersion; // always 0 for a keycloak group + public int trustedDetailsVersion; // always 0 for a keycloak group static final String TRUSTED_DETAILS_VERSION = "trusted_details_version"; private byte[] verifiedAdministratorsChain; // null for a keycloak group @@ -254,7 +254,7 @@ public static ContactGroupV2 createKeycloak(IdentityManagerSession identityManag } } - private ContactGroupV2(IdentityManagerSession identityManagerSession, UID groupUid, String serverUrl, int category, Identity ownedIdentity, byte[] serializedOwnPermission, int version, byte[] verifiedAdministratorsChain, GroupV2.BlobKeys blobKeys, byte[] ownGroupInvitationNonce, boolean frozen, long lastModificationTimestamp, String pushTopic, String serializedSharedSettings, String serializedJsonGroupType) { + public ContactGroupV2(IdentityManagerSession identityManagerSession, UID groupUid, String serverUrl, int category, Identity ownedIdentity, byte[] serializedOwnPermission, int version, byte[] verifiedAdministratorsChain, GroupV2.BlobKeys blobKeys, byte[] ownGroupInvitationNonce, boolean frozen, long lastModificationTimestamp, String pushTopic, String serializedSharedSettings, String serializedJsonGroupType) { this.identityManagerSession = identityManagerSession; this.groupUid = groupUid; this.serverUrl = serverUrl; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Details.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Details.java index c567dd2e..be2ca0ea 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Details.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Details.java @@ -68,9 +68,9 @@ public class ContactGroupV2Details implements ObvDatabase { static final String SERIALIZED_JSON_DETAILS = "serialized_json_details"; private String photoUrl; static final String PHOTO_URL = "photo_url"; - private Identity photoServerIdentity; + private Identity photoServerIdentity; // this is null for Keycloak groups with a photo static final String PHOTO_SERVER_IDENTITY = "photo_server_identity"; - private UID photoServerLabel; // this is the only non-null field for Keycloak groups with a photo + private UID photoServerLabel; static final String PHOTO_SERVER_LABEL = "photo_server_label"; private AuthEncKey photoServerKey; static final String PHOTO_SERVER_KEY = "photo_server_key"; @@ -114,6 +114,10 @@ public UID getPhotoServerLabel() { return photoServerLabel; } + public AuthEncKey getPhotoServerKey() { + return photoServerKey; + } + // region Constructor public static ContactGroupV2Details createNew(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, String serializedGroupDetails, String absolutePhotoUrl, GroupV2.ServerPhotoInfo serverPhotoInfo) { @@ -264,7 +268,7 @@ public static ContactGroupV2Details createOrUpdateKeycloak(IdentityManagerSessio } } - private ContactGroupV2Details(IdentityManagerSession identityManagerSession, UID groupUid, String serverUrl, int category, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, Identity photoServerIdentity, UID photoServerLabel, AuthEncKey photoServerKey) { + public ContactGroupV2Details(IdentityManagerSession identityManagerSession, UID groupUid, String serverUrl, int category, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, Identity photoServerIdentity, UID photoServerLabel, AuthEncKey photoServerKey) { this.identityManagerSession = identityManagerSession; this.groupUid = groupUid; this.serverUrl = serverUrl; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Member.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Member.java index 1f36cb82..504222d3 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Member.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2Member.java @@ -27,6 +27,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import io.olvid.engine.Logger; @@ -73,7 +74,7 @@ public byte[] getGroupInvitationNonce() { // region constructor - public static ContactGroupV2Member create(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, Identity contactIdentity, List permissionStrings, byte[] groupInvitationNonce) { + public static ContactGroupV2Member create(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, Identity contactIdentity, Collection permissionStrings, byte[] groupInvitationNonce) { if ((identityManagerSession == null) || (ownedIdentity == null) || (groupIdentifier == null) || (contactIdentity == null) || (permissionStrings == null) || (groupInvitationNonce == null)) { return null; } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2PendingMember.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2PendingMember.java index 997c071d..0c92bba7 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2PendingMember.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactGroupV2PendingMember.java @@ -21,12 +21,16 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import net.iharder.Base64; + +import java.nio.charset.StandardCharsets; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import io.olvid.engine.Logger; @@ -36,6 +40,7 @@ import io.olvid.engine.datatypes.UID; import io.olvid.engine.datatypes.containers.GroupV2; import io.olvid.engine.encoder.DecodingException; +import io.olvid.engine.engine.types.JsonIdentityDetails; import io.olvid.engine.identity.datatypes.IdentityManagerSession; public class ContactGroupV2PendingMember implements ObvDatabase { @@ -79,7 +84,7 @@ public byte[] getGroupInvitationNonce() { // region constructor - public static ContactGroupV2PendingMember create(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, Identity contactIdentity, String serializedContactDetails, List permissionStrings, byte[] groupInvitationNonce) { + public static ContactGroupV2PendingMember create(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, Identity contactIdentity, String serializedContactDetails, Collection permissionStrings, byte[] groupInvitationNonce) { if ((identityManagerSession == null) || (ownedIdentity == null) || (groupIdentifier == null) || (contactIdentity == null) || (permissionStrings == null) || (serializedContactDetails == null) || (groupInvitationNonce == null)) { return null; } @@ -380,7 +385,24 @@ static void restoreAll(IdentityManagerSession identityManagerSession, Identity o } for (Pojo_0 pojo : pojos) { try { - create(identityManagerSession, ownedIdentity, groupIdentifier, Identity.of(pojo.contact_identity), pojo.serialized_details, Arrays.asList(pojo.permissions), pojo.invitation_nonce); + String sanitizedSerializedDetails = null; + try { + // check whether the input is base64 or plain json (there was a bug on iOS where the details were base64 encoded) + identityManagerSession.jsonObjectMapper.readValue(pojo.serialized_details, JsonIdentityDetails.class); + sanitizedSerializedDetails = pojo.serialized_details; + } catch (Exception ignored) { + try { + String serializedDetailsString = new String(Base64.decode(pojo.serialized_details), StandardCharsets.UTF_8); + identityManagerSession.jsonObjectMapper.readValue(serializedDetailsString, JsonIdentityDetails.class); + sanitizedSerializedDetails = serializedDetailsString; + } catch (Exception e) { + Logger.i("Could not determine serialized details of GroupV2 pending member."); + } + } + + if (sanitizedSerializedDetails != null) { + create(identityManagerSession, ownedIdentity, groupIdentifier, Identity.of(pojo.contact_identity), sanitizedSerializedDetails, Arrays.asList(pojo.permissions), pojo.invitation_nonce); + } } catch (DecodingException e) { e.printStackTrace(); } 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 fd61a678..03030220 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 @@ -69,15 +69,15 @@ public class ContactIdentity implements ObvDatabase { static final String OWNED_IDENTITY = "owned_identity"; private int trustedDetailsVersion; static final String TRUSTED_DETAILS_VERSION = "trusted_details_version"; - private int publishedDetailsVersion; + public int publishedDetailsVersion; static final String PUBLISHED_DETAILS_VERSION = "published_details_version"; private TrustLevel trustLevel; static final String TRUST_LEVEL = "trust_level"; private boolean certifiedByOwnKeycloak; static final String CERTIFIED_BY_OWN_KEYCLOAK = "keycloak_managed"; - private boolean revokedAsCompromised; + public boolean revokedAsCompromised; static final String REVOKED_AS_COMPROMISED = "revoked_as_compromised"; - private boolean forcefullyTrustedByUser; + public boolean forcefullyTrustedByUser; static final String FORCEFULLY_TRUSTED_BY_USER = "forcefully_trusted_by_user"; private boolean oneToOne; static final String ONE_TO_ONE = "one_to_one"; @@ -258,10 +258,9 @@ public void updatePublishedDetails(JsonIdentityDetailsWithVersionAndPhoto jsonId } try { // check if any detail actually changed - JsonIdentityDetailsWithVersionAndPhoto oldJson = publishedDetails.getJsonIdentityDetailsWithVersionAndPhoto(); - if (oldJson.getIdentityDetails().fieldsAreTheSame(jsonIdentityDetailsWithVersionAndPhoto.getIdentityDetails()) - && Arrays.equals(oldJson.getPhotoServerLabel(), jsonIdentityDetailsWithVersionAndPhoto.getPhotoServerLabel()) - && Arrays.equals(oldJson.getPhotoServerKey(), jsonIdentityDetailsWithVersionAndPhoto.getPhotoServerKey())) { + if (publishedDetails.getJsonIdentityDetails().fieldsAreTheSame(newPublishedDetails.getJsonIdentityDetails()) + && Objects.equals(publishedDetails.getPhotoServerKey(), newPublishedDetails.getPhotoServerKey()) + && Objects.equals(publishedDetails.getPhotoServerLabel(), newPublishedDetails.getPhotoServerLabel())) { // nothing user visible changed --> do not notify notifyNewDetails = false; } @@ -609,7 +608,7 @@ public static ContactIdentity create(IdentityManagerSession identityManagerSessi } } - private 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, boolean oneToOne) { this.identityManagerSession = identityManagerSession; this.contactIdentity = contactIdentity; this.ownedIdentity = ownedIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentityDetails.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentityDetails.java index 5ac206c1..29408ee6 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentityDetails.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactIdentityDetails.java @@ -153,7 +153,7 @@ public static ContactIdentityDetails copy(IdentityManagerSession identityManager } - private ContactIdentityDetails(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { + public ContactIdentityDetails(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { this.identityManagerSession = identityManagerSession; this.contactIdentity = contactIdentity; this.ownedIdentity = ownedIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactTrustOrigin.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactTrustOrigin.java index 480f0034..d52f7d9a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactTrustOrigin.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/ContactTrustOrigin.java @@ -65,11 +65,11 @@ public class ContactTrustOrigin implements ObvDatabase { private byte[] serializedGroupIdentifier; static final String SERIALIZED_GROUP_IDENTIFIER = "serialized_group_identifier"; - private static final int TRUST_TYPE_DIRECT = 1; - private static final int TRUST_TYPE_INTRODUCTION = 2; - private static final int TRUST_TYPE_GROUP = 3; - private static final int TRUST_TYPE_IDENTITY_SERVER = 4; - private static final int TRUST_TYPE_SERVER_GROUP_V2 = 5; + public static final int TRUST_TYPE_DIRECT = 1; + public static final int TRUST_TYPE_INTRODUCTION = 2; + public static final int TRUST_TYPE_GROUP = 3; + public static final int TRUST_TYPE_IDENTITY_SERVER = 4; + public static final int TRUST_TYPE_SERVER_GROUP_V2 = 5; // region computed properties @@ -263,7 +263,7 @@ public static ContactTrustOrigin create(IdentityManagerSession identityManagerSe } } - private ContactTrustOrigin(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, long timestamp, int trustType, Identity mediatorOrGroupOwnerIdentity, Integer mediatorOrGroupOwnerTrustLevelMajor, String identityServer, byte[] serializedGroupIdentifier) { + public ContactTrustOrigin(IdentityManagerSession identityManagerSession, Identity contactIdentity, Identity ownedIdentity, long timestamp, int trustType, Identity mediatorOrGroupOwnerIdentity, Integer mediatorOrGroupOwnerTrustLevelMajor, String identityServer, byte[] serializedGroupIdentifier) { this.identityManagerSession = identityManagerSession; this.contactIdentity = contactIdentity; this.ownedIdentity = ownedIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentity.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentity.java index 7ac91052..631d44cb 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentity.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentity.java @@ -523,7 +523,7 @@ public static OwnedIdentity create(IdentityManagerSession identityManagerSession } } - private OwnedIdentity(IdentityManagerSession identityManagerSession, PrivateIdentity privateIdentity, int detailsVersion) { + public OwnedIdentity(IdentityManagerSession identityManagerSession, PrivateIdentity privateIdentity, int detailsVersion) { this.identityManagerSession = identityManagerSession; this.ownedIdentity = privateIdentity.getPublicIdentity(); this.privateIdentity = privateIdentity; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentityDetails.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentityDetails.java index 6ee32c73..de13b807 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentityDetails.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/OwnedIdentityDetails.java @@ -181,7 +181,7 @@ public static OwnedIdentityDetails copy(IdentityManagerSession identityManagerSe - private OwnedIdentityDetails(IdentityManagerSession identityManagerSession, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { + public OwnedIdentityDetails(IdentityManagerSession identityManagerSession, Identity ownedIdentity, int version, String serializedJsonDetails, String photoUrl, UID photoServerLabel, AuthEncKey photoServerKey) { this.identityManagerSession = identityManagerSession; this.ownedIdentity = ownedIdentity; this.version = version; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/PendingGroupMember.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/PendingGroupMember.java index acc80bf1..be2de628 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/PendingGroupMember.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/PendingGroupMember.java @@ -51,7 +51,7 @@ public class PendingGroupMember implements ObvDatabase { static final String CONTACT_IDENTITY = "contact_identity"; private String contactSerializedDetails; static final String CONTACT_SERIALIZED_DETAILS = "contact_display_name"; - private boolean declined; + public boolean declined; static final String DECLINED = "declined"; public byte[] getGroupOwnerAndUid() { @@ -66,6 +66,13 @@ public Identity getContactIdentity() { return contactIdentity; } + public String getContactSerializedDetails() { + return contactSerializedDetails; + } + + public boolean isDeclined() { + return declined; + } // region constructors @@ -82,7 +89,7 @@ public static PendingGroupMember create(IdentityManagerSession identityManagerSe } } - private PendingGroupMember(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, Identity contactIdentity, String contactSerializedDetails) { + public PendingGroupMember(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity, Identity contactIdentity, String contactSerializedDetails) { this.identityManagerSession = identityManagerSession; this.groupOwnerAndUid = groupOwnerAndUid; this.ownedIdentity = ownedIdentity; @@ -237,7 +244,7 @@ public static PendingGroupMember get(IdentityManagerSession identityManagerSessi } } - private static PendingGroupMember[] getAllInGroup(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity) { + public static PendingGroupMember[] getAllInGroup(IdentityManagerSession identityManagerSession, byte[] groupOwnerAndUid, Identity ownedIdentity) { if ((groupOwnerAndUid == null) || (ownedIdentity == null)) { return new PendingGroupMember[0]; } 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 2883894d..4d10fcae 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 @@ -19,30 +19,231 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.TrustLevel; +import io.olvid.engine.engine.types.JsonKeycloakUserDetails; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.ContactIdentity; +import io.olvid.engine.identity.databases.ContactIdentityDetails; +import io.olvid.engine.identity.databases.ContactTrustOrigin; import io.olvid.engine.identity.datatypes.IdentityManagerSession; @JsonIgnoreProperties(ignoreUnknown = true) public class ContactSyncSnapshot implements ObvSyncSnapshotNode { - IdentityDetailsSyncSnapshot trustedDetails; + public static final String TRUSTED_DETAILS = "trusted_details"; + public static final String PUBLISHED_DETAILS= "published_details"; + public static final String ONE_TO_ONE= "one_to_one"; + public static final String REVOKED = "revoked"; + public static final String FORCEFULLY_TRUSTED = "forcefully_trusted"; + public static final String TRUST_LEVEL = "trust_level"; + public static final String TRUST_ORIGINS = "trust_origins"; + static HashSet DEFAULT_DOMAIN = new HashSet<>(Arrays.asList(TRUSTED_DETAILS, PUBLISHED_DETAILS, ONE_TO_ONE, REVOKED, FORCEFULLY_TRUSTED, TRUST_LEVEL, TRUST_ORIGINS)); + + public IdentityDetailsSyncSnapshot trusted_details; + public IdentityDetailsSyncSnapshot published_details; // null if equal to trusted details + public Boolean one_to_one; + public Boolean revoked; + public Boolean forcefully_trusted; + public String trust_level; // only used for backup/transfer, not taken into account when comparing for synchronization + public List trust_origins; // only used for backup/transfer, not taken into account when comparing for synchronization + public HashSet domain; + + + public static ContactSyncSnapshot of(IdentityManagerSession identityManagerSession, ContactIdentity contact) throws SQLException { - return null; + ContactSyncSnapshot contactSyncSnapshot = new ContactSyncSnapshot(); + + ContactIdentityDetails trustedDetails = contact.getTrustedDetails(); + contactSyncSnapshot.trusted_details = IdentityDetailsSyncSnapshot.of(identityManagerSession, trustedDetails); + + if (contact.getTrustedDetailsVersion() != contact.getPublishedDetailsVersion()) { + ContactIdentityDetails publishedDetails = contact.getPublishedDetails(); + contactSyncSnapshot.published_details = IdentityDetailsSyncSnapshot.of(identityManagerSession, publishedDetails); + } + + contactSyncSnapshot.one_to_one = contact.isOneToOne() ? true : null; + + contactSyncSnapshot.revoked = contact.isRevokedAsCompromised() ? true : null; + + contactSyncSnapshot.forcefully_trusted = contact.isForcefullyTrustedByUser() ? true : null; + + contactSyncSnapshot.trust_level = contact.getTrustLevel().toString(); + + contactSyncSnapshot.trust_origins = new ArrayList<>(); + for (ContactTrustOrigin contactTrustOrigin : ContactTrustOrigin.getAll(identityManagerSession, contact.getContactIdentity(), contact.getOwnedIdentity())) { + contactSyncSnapshot.trust_origins.add(TrustOrigin.of(contactTrustOrigin)); + } + + contactSyncSnapshot.domain = DEFAULT_DOMAIN; + return contactSyncSnapshot; + } + + @JsonIgnore + public ContactIdentity restore(IdentityManagerSession identityManagerSession, Identity ownedIdentity, Identity contactIdentity) throws Exception { + if (!domain.contains(TRUSTED_DETAILS)) { + Logger.e("Trying to restore an incomplete ContactSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + // restore the trusted details + ContactIdentityDetails trustedDetails = trusted_details.restoreContact(identityManagerSession, ownedIdentity, contactIdentity); + ContactIdentityDetails publishedDetails; + if (domain.contains(PUBLISHED_DETAILS) && published_details != null && !Objects.equals(trusted_details.version, published_details.version)) { + publishedDetails = published_details.restoreContact(identityManagerSession, ownedIdentity, contactIdentity); + } else { + publishedDetails = null; + } + + 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); + + ContactIdentity contactIdentityObject = new ContactIdentity(identityManagerSession, contactIdentity, ownedIdentity, trustedDetails.getVersion(), trustLevel, oneToOne); + if (publishedDetails != null) { + contactIdentityObject.publishedDetailsVersion = publishedDetails.getVersion(); + } + contactIdentityObject.revokedAsCompromised = domain.contains(REVOKED) && revoked != null && revoked; + contactIdentityObject.forcefullyTrustedByUser = domain.contains(FORCEFULLY_TRUSTED) && forcefully_trusted != null && forcefully_trusted; + contactIdentityObject.insert(); + + // check for keycloak badge + JsonKeycloakUserDetails jsonKeycloakUserDetails = identityManagerSession.identityDelegate.verifyKeycloakSignature(identityManagerSession.session, ownedIdentity, trustedDetails.getJsonIdentityDetailsWithVersionAndPhoto().getIdentityDetails().getSignedUserDetails()); + if (jsonKeycloakUserDetails != null) { + contactIdentityObject.setCertifiedByOwnKeycloak(true, trustedDetails.getSerializedJsonDetails()); + } + + // restore trust origin + if (domain.contains(TRUST_ORIGINS) && trust_origins != null) { + for (TrustOrigin trustOrigin : trust_origins) { + Identity mediatorOrGroupOwnerIdentity = null; + try { + if (trustOrigin.mediator_or_group_owner_identity != null) { + mediatorOrGroupOwnerIdentity = Identity.of(trustOrigin.mediator_or_group_owner_identity); + } + } catch (Exception e) { + e.printStackTrace(); + } + int trustType; + switch (TrustOrigin.TrustType.fromIntValue(trustOrigin.trust_type)) { + case TYPE_DIRECT: + trustType = ContactTrustOrigin.TRUST_TYPE_DIRECT; + break; + case TYPE_GROUP: + trustType = ContactTrustOrigin.TRUST_TYPE_GROUP; + break; + case TYPE_INTRODUCTION: + trustType = ContactTrustOrigin.TRUST_TYPE_INTRODUCTION; + break; + case TYPE_KEYCLOAK: + trustType = ContactTrustOrigin.TRUST_TYPE_IDENTITY_SERVER; + break; + case TYPE_SERVER_GROUP_V2: + trustType = ContactTrustOrigin.TRUST_TYPE_SERVER_GROUP_V2; + break; + default: + // ignore unknown trust types + continue; + } + ContactTrustOrigin contactTrustOrigin = new ContactTrustOrigin(identityManagerSession, contactIdentity, ownedIdentity, trustOrigin.timestamp, trustType, mediatorOrGroupOwnerIdentity, 0, trustOrigin.identity_server, trustOrigin.raw_obv_group_v2_identifier); + contactTrustOrigin.insert(); + } + } + + return contactIdentityObject; } @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { + // TODO areContentsTheSame return false; } @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { + // TODO computeDiff return null; } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class TrustOrigin { + enum TrustType { + TYPE_DIRECT(0), + TYPE_GROUP(1), + TYPE_INTRODUCTION(2), + TYPE_KEYCLOAK(3), + TYPE_SERVER_GROUP_V2(4); + private static final Map valueMap = new HashMap<>(); + + static { + for (TrustType trustType : values()) { + valueMap.put(trustType.value, trustType); + } + } + + public final int value; + + TrustType(int value) { + this.value = value; + } + + static TrustType fromIntValue(int value) { + return valueMap.get(value); + } + } + + + public long timestamp; + public int trust_type; + public byte[] mediator_or_group_owner_identity; + public String identity_server; + public byte[] raw_obv_group_v2_identifier; + + private static TrustOrigin of(ContactTrustOrigin contactTrustOrigin) { + io.olvid.engine.datatypes.containers.TrustOrigin trustOrigin = contactTrustOrigin.getTrustOrigin(); + + TrustOrigin to = new TrustOrigin(); + to.timestamp = trustOrigin.getTimestamp(); + switch (trustOrigin.getType()) { + case DIRECT: { + to.trust_type = TrustType.TYPE_DIRECT.value; + break; + } + case INTRODUCTION: { + to.trust_type = TrustType.TYPE_INTRODUCTION.value; + to.mediator_or_group_owner_identity = trustOrigin.getMediatorOrGroupOwnerIdentity().getBytes(); + break; + } + case GROUP: { + to.trust_type = TrustType.TYPE_GROUP.value; + to.mediator_or_group_owner_identity = trustOrigin.getMediatorOrGroupOwnerIdentity().getBytes(); + break; + } + case KEYCLOAK: { + to.trust_type = TrustType.TYPE_KEYCLOAK.value; + to.identity_server = trustOrigin.getKeycloakServer(); + break; + } + case SERVER_GROUP_V2: { + to.trust_type = TrustType.TYPE_SERVER_GROUP_V2.value; + to.raw_obv_group_v2_identifier = trustOrigin.getGroupIdentifier().getBytes(); + break; + } + } + + return to; + } + } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupDetailsSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupDetailsSyncSnapshot.java new file mode 100644 index 00000000..6ec8e78f --- /dev/null +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupDetailsSyncSnapshot.java @@ -0,0 +1,214 @@ +/* + * Olvid for Android + * Copyright © 2019-2023 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.identity.databases.sync; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.UID; +import io.olvid.engine.datatypes.containers.GroupV2; +import io.olvid.engine.datatypes.key.symmetric.AuthEncKey; +import io.olvid.engine.encoder.DecodingException; +import io.olvid.engine.encoder.Encoded; +import io.olvid.engine.engine.types.sync.ObvSyncDiff; +import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; +import io.olvid.engine.identity.databases.ContactGroupDetails; +import io.olvid.engine.identity.databases.ContactGroupV2Details; +import io.olvid.engine.identity.databases.ServerUserData; +import io.olvid.engine.identity.datatypes.IdentityManagerSession; + +// This class is used for both owned identity and contacts +@JsonIgnoreProperties(ignoreUnknown = true) +public class GroupDetailsSyncSnapshot implements ObvSyncSnapshotNode { + public static final String VERSION = "version"; + public static final String SERIALIZED_DETAILS = "serialized_details"; + public static final String PHOTO_SERVER_IDENTITY = "photo_server_identity"; + public static final String PHOTO_SERVER_LABEL = "photo_server_label"; + public static final String PHOTO_SERVER_KEY = "photo_server_key"; + static HashSet DEFAULT_V1_DOMAIN = new HashSet<>(Arrays.asList(VERSION, SERIALIZED_DETAILS, PHOTO_SERVER_LABEL, PHOTO_SERVER_KEY)); + static HashSet DEFAULT_V2_DOMAIN = new HashSet<>(Arrays.asList(SERIALIZED_DETAILS, PHOTO_SERVER_IDENTITY, PHOTO_SERVER_LABEL, PHOTO_SERVER_KEY)); + + + public Integer version; + public String serialized_details; + public byte[] photo_server_identity; // non-null only for group v2 (as we don't know which admin hosts the photo) + public byte[] photo_server_label; + public byte[] photo_server_key; + public HashSet domain; + + + public static GroupDetailsSyncSnapshot of(IdentityManagerSession identityManagerSession, ContactGroupDetails contactGroupDetails) { + GroupDetailsSyncSnapshot groupDetailsSyncSnapshot = new GroupDetailsSyncSnapshot(); + groupDetailsSyncSnapshot.version = contactGroupDetails.getVersion(); + groupDetailsSyncSnapshot.serialized_details = contactGroupDetails.getSerializedJsonDetails(); + if (contactGroupDetails.getPhotoServerLabel() != null && contactGroupDetails.getPhotoServerKey() != null) { + groupDetailsSyncSnapshot.photo_server_label = contactGroupDetails.getPhotoServerLabel().getBytes(); + groupDetailsSyncSnapshot.photo_server_key = Encoded.of(contactGroupDetails.getPhotoServerKey()).getBytes(); + } + groupDetailsSyncSnapshot.domain = DEFAULT_V1_DOMAIN; + return groupDetailsSyncSnapshot; + } + + public static GroupDetailsSyncSnapshot of(IdentityManagerSession identityManagerSession, ContactGroupV2Details contactGroupV2Details) { + GroupDetailsSyncSnapshot groupDetailsSyncSnapshot = new GroupDetailsSyncSnapshot(); + groupDetailsSyncSnapshot.serialized_details = contactGroupV2Details.getSerializedJsonDetails(); + if (contactGroupV2Details.getPhotoServerLabel() != null && contactGroupV2Details.getPhotoServerKey() != null) { + groupDetailsSyncSnapshot.photo_server_identity = contactGroupV2Details.getPhotoServerIdentity() == null ? null : contactGroupV2Details.getPhotoServerIdentity().getBytes(); + groupDetailsSyncSnapshot.photo_server_label = contactGroupV2Details.getPhotoServerLabel().getBytes(); + groupDetailsSyncSnapshot.photo_server_key = Encoded.of(contactGroupV2Details.getPhotoServerKey()).getBytes(); + } + groupDetailsSyncSnapshot.domain = DEFAULT_V2_DOMAIN; + return groupDetailsSyncSnapshot; + } + + + @JsonIgnore + public ContactGroupDetails restoreGroup(IdentityManagerSession identityManagerSession, Identity ownedIdentity, Identity groupOwnerIdentity, byte[] groupOwnerAndUid) throws Exception { + if (!domain.contains(VERSION) || !domain.contains(SERIALIZED_DETAILS)) { + Logger.e("Trying to restoreGroup an incomplete GroupDetailsSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + UID photoServerLabel; + AuthEncKey photoServerKey; + if (domain.contains(PHOTO_SERVER_LABEL) && domain.contains(PHOTO_SERVER_KEY) && photo_server_key != null && photo_server_label != null) { + try { + photoServerLabel = new UID(photo_server_label); + photoServerKey = (AuthEncKey) new Encoded(photo_server_key).decodeSymmetricKey(); + } catch (Exception e) { + e.printStackTrace(); + photoServerLabel = null; + photoServerKey = null; + } + } else { + photoServerLabel = null; + photoServerKey = null; + } + + ContactGroupDetails contactGroupDetails = new ContactGroupDetails(identityManagerSession, groupOwnerAndUid, ownedIdentity, version, serialized_details, null, photoServerLabel, photoServerKey); + contactGroupDetails.insert(); + if (groupOwnerIdentity.equals(ownedIdentity) && photoServerLabel != null && photoServerKey != null) { + ServerUserData.createForOwnedGroupDetails(identityManagerSession, ownedIdentity, photoServerLabel, groupOwnerAndUid); + } + return contactGroupDetails; + } + + @JsonIgnore + public ContactGroupV2Details restoreGroupV2(IdentityManagerSession identityManagerSession, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, int version) throws Exception { + if (!domain.contains(SERIALIZED_DETAILS)) { + Logger.e("Trying to restoreGroupV2 an incomplete GroupDetailsSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + Identity photoServerIdentity; + UID photoServerLabel; + AuthEncKey photoServerKey; + if (domain.contains(PHOTO_SERVER_IDENTITY) && domain.contains(PHOTO_SERVER_LABEL) && domain.contains(PHOTO_SERVER_KEY) && photo_server_key != null && photo_server_label != null) { + try { + photoServerIdentity = photo_server_identity == null ? null : Identity.of(photo_server_identity); + photoServerLabel = new UID(photo_server_label); + photoServerKey = (AuthEncKey) new Encoded(photo_server_key).decodeSymmetricKey(); + } catch (Exception e) { + e.printStackTrace(); + photoServerIdentity = null; + photoServerLabel = null; + photoServerKey = null; + } + } else { + photoServerIdentity = null; + photoServerLabel = null; + photoServerKey = null; + } + + ContactGroupV2Details contactGroupV2Details = new ContactGroupV2Details(identityManagerSession, groupIdentifier.groupUid, groupIdentifier.serverUrl, groupIdentifier.category, ownedIdentity, version, serialized_details, null, photoServerIdentity, photoServerLabel, photoServerKey); + contactGroupV2Details.insert(); + if (ownedIdentity.equals(photoServerIdentity)) { + ServerUserData.createForGroupV2(identityManagerSession, ownedIdentity, photoServerLabel, groupIdentifier.getBytes()); + } + return contactGroupV2Details; + } + + @Override + public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { + if (!(otherSnapshotNode instanceof GroupDetailsSyncSnapshot)) { + return false; + } + + GroupDetailsSyncSnapshot other = (GroupDetailsSyncSnapshot) otherSnapshotNode; + HashSet domainIntersection = new HashSet<>(domain); + domainIntersection.retainAll(other.domain); + + for (String item : domainIntersection) { + switch (item) { + case VERSION: { + if (!Objects.equals(version, other.version)) { + return false; + } + break; + } + case SERIALIZED_DETAILS: { + // TODO: we need to deserialize here for the comparison + if (!Objects.equals(serialized_details, other.serialized_details)) { + return false; + } + break; + } + case PHOTO_SERVER_IDENTITY: { + if (!Arrays.equals(photo_server_identity, other.photo_server_identity)) { + return false; + } + break; + } + case PHOTO_SERVER_LABEL: { + if (!Arrays.equals(photo_server_label, other.photo_server_label)) { + return false; + } + break; + } + case PHOTO_SERVER_KEY: { + try { + if ((photo_server_key == null && other.photo_server_key != null) + || (photo_server_key != null && other.photo_server_key == null) + || (photo_server_key != null && !Objects.equals(new Encoded(photo_server_key).decodeSymmetricKey(), new Encoded(other.photo_server_key).decodeSymmetricKey()))) { + return false; + } + } catch (DecodingException e) { + return false; + } + break; + } + } + } + return true; + } + + @Override + public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { + // TODO computeDiff + return null; + } +} diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV1SyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV1SyncSnapshot.java index a26d5d44..ceadf34a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV1SyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV1SyncSnapshot.java @@ -19,32 +19,150 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.engine.types.ObvBytesKey; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.ContactGroup; +import io.olvid.engine.identity.databases.ContactGroupDetails; +import io.olvid.engine.identity.databases.ContactGroupMembersJoin; +import io.olvid.engine.identity.databases.PendingGroupMember; import io.olvid.engine.identity.datatypes.IdentityManagerSession; @JsonIgnoreProperties(ignoreUnknown = true) public class GroupV1SyncSnapshot implements ObvSyncSnapshotNode { + public static final String PUBLISHED_DETAILS = "published_details"; + public static final String TRUSTED_DETAILS = "trusted_details"; + public static final String GROUP_MEMBERS_VERSION = "group_members_version"; + public static final String MEMBERS = "members"; + public static final String PENDING_MEMBERS = "pending_members"; + static HashSet DEFAULT_JOINED_DOMAIN = new HashSet<>(Arrays.asList(PUBLISHED_DETAILS, TRUSTED_DETAILS, GROUP_MEMBERS_VERSION, MEMBERS, PENDING_MEMBERS)); + static HashSet DEFAULT_OWNED_DOMAIN = new HashSet<>(Arrays.asList(PUBLISHED_DETAILS, GROUP_MEMBERS_VERSION, MEMBERS, PENDING_MEMBERS)); + + + public GroupDetailsSyncSnapshot published_details; + public GroupDetailsSyncSnapshot trusted_details; // only for groups you do not own, null if same as published + public Long group_members_version; + @JsonSerialize(contentUsing = ObvBytesKey.Serializer.class) + @JsonDeserialize(contentUsing = ObvBytesKey.Deserializer.class) + public HashSet members; + @JsonSerialize(keyUsing = ObvBytesKey.KeySerializer.class) + @JsonDeserialize(keyUsing = ObvBytesKey.KeyDeserializer.class) + public HashMap pending_members; + public HashSet domain; + public static GroupV1SyncSnapshot of(IdentityManagerSession identityManagerSession, ContactGroup group) throws SQLException { - // TODO - return null; + GroupV1SyncSnapshot groupV1SyncSnapshot = new GroupV1SyncSnapshot(); + + groupV1SyncSnapshot.published_details = GroupDetailsSyncSnapshot.of(identityManagerSession, group.getPublishedDetails()); + if (group.getGroupOwner() == null) { + groupV1SyncSnapshot.domain = DEFAULT_OWNED_DOMAIN; + } else { + if (group.getPublishedDetailsVersion() != group.getLatestOrTrustedDetailsVersion()) { + groupV1SyncSnapshot.trusted_details = GroupDetailsSyncSnapshot.of(identityManagerSession, group.getLatestOrTrustedDetails()); + } + groupV1SyncSnapshot.domain = DEFAULT_JOINED_DOMAIN; + } + + groupV1SyncSnapshot.group_members_version = group.getGroupMembersVersion(); + + groupV1SyncSnapshot.members = new HashSet<>(); + for (Identity memberIdentity : ContactGroupMembersJoin.getContactIdentitiesInGroup(identityManagerSession, group.getGroupOwnerAndUid(), group.getOwnedIdentity())) { + groupV1SyncSnapshot.members.add(new ObvBytesKey(memberIdentity.getBytes())); + } + + groupV1SyncSnapshot.pending_members = new HashMap<>(); + for (PendingGroupMember pendingGroupMember : PendingGroupMember.getAllInGroup(identityManagerSession, group.getGroupOwnerAndUid(), group.getOwnedIdentity())) { + groupV1SyncSnapshot.pending_members.put(new ObvBytesKey(pendingGroupMember.getContactIdentity().getBytes()), GroupV1PendingMember.of(pendingGroupMember)); + } + + return groupV1SyncSnapshot; + } + + @JsonIgnore + public ContactGroup restore(IdentityManagerSession identityManagerSession, Identity ownedIdentity, Identity groupOwnerIdentity, byte[] groupOwnerAndUid) throws Exception { + if (!domain.contains(GROUP_MEMBERS_VERSION) || !domain.contains(MEMBERS) || !domain.contains(PENDING_MEMBERS) || !domain.contains(PUBLISHED_DETAILS)) { + Logger.e("Trying to restore an incomplete GroupV1SyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + ContactGroup contactGroup; + if (groupOwnerIdentity.equals(ownedIdentity)) { + ContactGroupDetails publishedDetails = published_details.restoreGroup(identityManagerSession, ownedIdentity, groupOwnerIdentity, groupOwnerAndUid); + + contactGroup = new ContactGroup(identityManagerSession, groupOwnerAndUid, ownedIdentity, null, publishedDetails.getVersion()); + contactGroup.groupMembersVersion = group_members_version; + contactGroup.insert(); + } else { + ContactGroupDetails trustedDetails; + ContactGroupDetails publishedDetails = published_details.restoreGroup(identityManagerSession, ownedIdentity, groupOwnerIdentity, groupOwnerAndUid); + if (domain.contains(TRUSTED_DETAILS) && trusted_details != null && !Objects.equals(trusted_details.version, published_details.version)) { + trustedDetails = trusted_details.restoreGroup(identityManagerSession, ownedIdentity, groupOwnerIdentity, groupOwnerAndUid); + } else { + trustedDetails = null; + } + contactGroup = new ContactGroup(identityManagerSession, groupOwnerAndUid, ownedIdentity, groupOwnerIdentity, publishedDetails.getVersion()); + if (trustedDetails != null) { + contactGroup.latestOrTrustedDetailsVersion = trustedDetails.getVersion(); + } + contactGroup.groupMembersVersion = group_members_version; + contactGroup.insert(); + } + + // restore members + for (ObvBytesKey member : members) { + Identity memberIdentity = Identity.of(member.getBytes()); + ContactGroupMembersJoin.create(identityManagerSession, groupOwnerAndUid, ownedIdentity, memberIdentity); + } + + // restore pending members + for (Map.Entry pendingMemberEntry : pending_members.entrySet()) { + Identity pendingMemberIdentity = Identity.of(pendingMemberEntry.getKey().getBytes()); + PendingGroupMember pendingGroupMember = new PendingGroupMember(identityManagerSession, groupOwnerAndUid, ownedIdentity, pendingMemberIdentity, pendingMemberEntry.getValue().serialized_details); + pendingGroupMember.declined = pendingMemberEntry.getValue().declined != null && pendingMemberEntry.getValue().declined; + pendingGroupMember.insert(); + } + + return contactGroup; } @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { - // TODO + // TODO areContentsTheSame return false; } @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { - // TODO + // TODO computeDiff return null; } + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class GroupV1PendingMember { + public String serialized_details; + public Boolean declined; + + private static GroupV1PendingMember of(PendingGroupMember pendingGroupMember) { + GroupV1PendingMember groupV1PendingMember = new GroupV1PendingMember(); + groupV1PendingMember.serialized_details = pendingGroupMember.getContactSerializedDetails(); + groupV1PendingMember.declined = pendingGroupMember.isDeclined() ? true : null; + return groupV1PendingMember; + } + } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV2SyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV2SyncSnapshot.java index 83b8f839..50d14dd8 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV2SyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/GroupV2SyncSnapshot.java @@ -19,32 +19,242 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.Seed; +import io.olvid.engine.datatypes.containers.GroupV2; +import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationPrivateKey; +import io.olvid.engine.encoder.Encoded; +import io.olvid.engine.engine.types.ObvBytesKey; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.ContactGroupV2; +import io.olvid.engine.identity.databases.ContactGroupV2Details; +import io.olvid.engine.identity.databases.ContactGroupV2Member; +import io.olvid.engine.identity.databases.ContactGroupV2PendingMember; import io.olvid.engine.identity.datatypes.IdentityManagerSession; +import io.olvid.engine.protocol.datatypes.ProtocolStarterDelegate; @JsonIgnoreProperties(ignoreUnknown = true) public class GroupV2SyncSnapshot implements ObvSyncSnapshotNode { + public static final String PERMISSIONS = "permissions"; + public static final String VERSION = "version"; + public static final String DETAILS = "details"; + public static final String TRUSTED_DETAILS = "trusted_details"; + public static final String VERIFIED_ADMIN_CHAIN = "verified_admin_chain"; + public static final String MAIN_SEED = "main_seed"; + public static final String VERSION_SEED= "version_seed"; + public static final String ENCODED_ADMIN_KEY = "encoded_admin_key"; + public static final String INVITATION_NONCE = "invitation_nonce"; + public static final String LAST_MODIFICATION_TIMESTAMP = "last_modification_timestamp"; + public static final String PUSH_TOPIC = "push_topic"; + public static final String SERIALIZED_SHARED_SETTINGS = "serialized_shared_settings"; + public static final String SERIALIZED_GROUP_TYPE = "serialized_group_type"; + public static final String MEMBERS = "members"; + public static final String PENDING_MEMBERS = "pending_members"; + static HashSet DEFAULT_SERVER_DOMAIN = new HashSet<>(Arrays.asList(PERMISSIONS, VERSION, DETAILS, TRUSTED_DETAILS, VERIFIED_ADMIN_CHAIN, MAIN_SEED, VERSION_SEED, ENCODED_ADMIN_KEY, INVITATION_NONCE, SERIALIZED_GROUP_TYPE, MEMBERS, PENDING_MEMBERS)); + static HashSet DEFAULT_KEYCLOAK_DOMAIN = new HashSet<>(Arrays.asList(PERMISSIONS, DETAILS, INVITATION_NONCE, LAST_MODIFICATION_TIMESTAMP, PUSH_TOPIC, SERIALIZED_SHARED_SETTINGS, MEMBERS, PENDING_MEMBERS)); + + + + public HashSet permissions; + public Integer version; + public GroupDetailsSyncSnapshot details; + public GroupDetailsSyncSnapshot trusted_details; // on restore use version - 1 as a version number for this + public byte[] verified_admin_chain; + public byte[] main_seed; + public byte[] version_seed; + public byte[] encoded_admin_key; + public byte[] invitation_nonce; + public Long last_modification_timestamp; + public String push_topic; + public String serialized_shared_settings; + public String serialized_group_type; + + @JsonSerialize(keyUsing = ObvBytesKey.KeySerializer.class) + @JsonDeserialize(keyUsing = ObvBytesKey.KeyDeserializer.class) + public HashMap members; + + @JsonSerialize(keyUsing = ObvBytesKey.KeySerializer.class) + @JsonDeserialize(keyUsing = ObvBytesKey.KeyDeserializer.class) + public HashMap pending_members; + public HashSet domain; + + public static GroupV2SyncSnapshot of(IdentityManagerSession identityManagerSession, ContactGroupV2 group2) throws SQLException { - // TODO - return null; + GroupV2SyncSnapshot groupV2SyncSnapshot = new GroupV2SyncSnapshot(); + + groupV2SyncSnapshot.permissions = new HashSet<>(group2.getOwnPermissionStrings()); + + ContactGroupV2Details publishedDetails = ContactGroupV2Details.get(identityManagerSession, group2.getOwnedIdentity(), group2.getGroupIdentifier(), group2.getVersion()); + if (publishedDetails != null) { + groupV2SyncSnapshot.details = GroupDetailsSyncSnapshot.of(identityManagerSession, publishedDetails); + } + groupV2SyncSnapshot.invitation_nonce = group2.getOwnGroupInvitationNonce(); + + if (group2.getGroupIdentifier().category == GroupV2.Identifier.CATEGORY_SERVER) { + // normal group v2 + groupV2SyncSnapshot.version = group2.getVersion(); + if (group2.getTrustedDetailsVersion() != group2.getVersion()) { + ContactGroupV2Details trustedDetails = ContactGroupV2Details.get(identityManagerSession, group2.getOwnedIdentity(), group2.getGroupIdentifier(), group2.getTrustedDetailsVersion()); + if (trustedDetails != null) { + groupV2SyncSnapshot.trusted_details = GroupDetailsSyncSnapshot.of(identityManagerSession, trustedDetails); + } + } + groupV2SyncSnapshot.verified_admin_chain = group2.getVerifiedAdministratorsChain(); + groupV2SyncSnapshot.main_seed = group2.getBlobMainSeed().getBytes(); + groupV2SyncSnapshot.version_seed = group2.getBlobVersionSeed().getBytes(); + if (group2.getGroupAdminServerAuthenticationPrivateKey() != null) { + groupV2SyncSnapshot.encoded_admin_key = Encoded.of(group2.getGroupAdminServerAuthenticationPrivateKey()).getBytes(); + } + groupV2SyncSnapshot.serialized_group_type = group2.getSerializedJsonGroupType(); + groupV2SyncSnapshot.last_modification_timestamp = null; + groupV2SyncSnapshot.domain = DEFAULT_SERVER_DOMAIN; + } else { + // keycloak group v2 + groupV2SyncSnapshot.last_modification_timestamp = group2.getLastModificationTimestamp(); + groupV2SyncSnapshot.push_topic = group2.getPushTopic(); + groupV2SyncSnapshot.serialized_shared_settings = group2.getSerializedSharedSettings(); + groupV2SyncSnapshot.domain = DEFAULT_KEYCLOAK_DOMAIN; + } + + groupV2SyncSnapshot.members = new HashMap<>(); + for (ContactGroupV2Member groupV2Member : ContactGroupV2Member.getAll(identityManagerSession, group2.getOwnedIdentity(), group2.getGroupIdentifier())) { + groupV2SyncSnapshot.members.put(new ObvBytesKey(groupV2Member.getContactIdentity().getBytes()), GroupV2Member.of(groupV2Member)); + } + + groupV2SyncSnapshot.pending_members = new HashMap<>(); + for (ContactGroupV2PendingMember groupV2PendingMember : ContactGroupV2PendingMember.getAll(identityManagerSession, group2.getOwnedIdentity(), group2.getGroupIdentifier())) { + groupV2SyncSnapshot.pending_members.put(new ObvBytesKey(groupV2PendingMember.getContactIdentity().getBytes()), GroupV2PendingMember.of(groupV2PendingMember)); + } + + return groupV2SyncSnapshot; + } + + @JsonIgnore + public ContactGroupV2 restore(IdentityManagerSession identityManagerSession, ProtocolStarterDelegate protocolStarterDelegate, Identity ownedIdentity, GroupV2.Identifier groupIdentifier) throws Exception { + if (!domain.contains(PERMISSIONS) || !domain.contains(DETAILS) || !domain.contains(INVITATION_NONCE) || !domain.contains(MEMBERS) || !domain.contains(PENDING_MEMBERS) + || (groupIdentifier.category == GroupV2.Identifier.CATEGORY_SERVER && (!domain.contains(VERSION) || !domain.contains(VERIFIED_ADMIN_CHAIN) || !domain.contains(MAIN_SEED) || !domain.contains(VERSION_SEED) || !domain.contains(ENCODED_ADMIN_KEY)))) { + Logger.e("Trying to restore an incomplete GroupV2SyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + // restore the details + ContactGroupV2Details contactGroupV2Details = details.restoreGroupV2(identityManagerSession, ownedIdentity, groupIdentifier, version == null ? 0 : version); + ContactGroupV2Details trustedDetails; + if (domain.contains(TRUSTED_DETAILS) && trusted_details != null && groupIdentifier.category == GroupV2.Identifier.CATEGORY_SERVER) { + trustedDetails = trusted_details.restoreGroupV2(identityManagerSession, ownedIdentity, groupIdentifier, version == null ? -1 : version - 1); + } else { + trustedDetails = null; + } + + // restore the group + GroupV2.BlobKeys blobKeys; + if (groupIdentifier.category == GroupV2.Identifier.CATEGORY_SERVER) { + ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + if (encoded_admin_key != null) { + serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) new Encoded(encoded_admin_key).decodePrivateKey(); + } else { + serverAuthenticationPrivateKey = null; + } + blobKeys = ((main_seed == null) || (version_seed == null)) ? null : new GroupV2.BlobKeys(new Seed(main_seed), new Seed(version_seed), serverAuthenticationPrivateKey); + } else { + blobKeys = null; + } + + ContactGroupV2 groupV2 = new ContactGroupV2( + identityManagerSession, + groupIdentifier.groupUid, + groupIdentifier.serverUrl, + groupIdentifier.category, + ownedIdentity, + GroupV2.Permission.serializePermissionStrings(permissions), + contactGroupV2Details.getVersion(), + verified_admin_chain, + blobKeys, + invitation_nonce, + false, + (domain.contains(LAST_MODIFICATION_TIMESTAMP) && last_modification_timestamp != null) ? last_modification_timestamp : System.currentTimeMillis(), + domain.contains(PUSH_TOPIC) ? push_topic : null, + domain.contains(SERIALIZED_SHARED_SETTINGS) ? serialized_shared_settings : null, + serialized_group_type + ); + if (trustedDetails != null) { + groupV2.trustedDetailsVersion = trustedDetails.getVersion(); + } + groupV2.insert(); + + // restore members + for (Map.Entry memberEntry : members.entrySet()) { + Identity memberIdentity = Identity.of(memberEntry.getKey().getBytes()); + ContactGroupV2Member.create(identityManagerSession, ownedIdentity, groupIdentifier, memberIdentity, memberEntry.getValue().permissions, memberEntry.getValue().invitation_nonce); + } + + // restore pending members + for (Map.Entry pendingMemberEntry : pending_members.entrySet()) { + Identity pendingMemberIdentity = Identity.of(pendingMemberEntry.getKey().getBytes()); + ContactGroupV2PendingMember.create(identityManagerSession, ownedIdentity, groupIdentifier, pendingMemberIdentity, pendingMemberEntry.getValue().serialized_details, pendingMemberEntry.getValue().permissions, pendingMemberEntry.getValue().invitation_nonce); + } + + try { + protocolStarterDelegate.initiateGroupV2ReDownloadWithinTransaction(identityManagerSession.session, ownedIdentity, groupIdentifier); + } catch (Exception e) { + e.printStackTrace(); + } + + return groupV2; } @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { - // TODO + // TODO areContentsTheSame return false; } @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { - // TODO + // TODO computeDiff return null; } + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class GroupV2Member { + public HashSet permissions; + public byte[] invitation_nonce; + + private static GroupV2Member of(ContactGroupV2Member groupMember) { + GroupV2Member groupV2Member = new GroupV2Member(); + groupV2Member.permissions = new HashSet<>(GroupV2.Permission.deserializePermissions(groupMember.getSerializedPermissions())); + groupV2Member.invitation_nonce = groupMember.getGroupInvitationNonce(); + return groupV2Member; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class GroupV2PendingMember { + public String serialized_details; + public HashSet permissions; + public byte[] invitation_nonce; + + private static GroupV2PendingMember of(ContactGroupV2PendingMember pendingGroupMember) { + GroupV2PendingMember groupV2PendingMember = new GroupV2PendingMember(); + groupV2PendingMember.serialized_details = pendingGroupMember.getSerializedContactDetails(); + groupV2PendingMember.permissions = new HashSet<>(GroupV2.Permission.deserializePermissions(pendingGroupMember.getSerializedPermissions())); + groupV2PendingMember.invitation_nonce = pendingGroupMember.getGroupInvitationNonce(); + return groupV2PendingMember; + } + } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityDetailsSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityDetailsSyncSnapshot.java index a87f26ef..02e5e935 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityDetailsSyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityDetailsSyncSnapshot.java @@ -19,6 +19,7 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Arrays; @@ -26,11 +27,17 @@ import java.util.List; import java.util.Objects; +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.UID; +import io.olvid.engine.datatypes.key.symmetric.AuthEncKey; import io.olvid.engine.encoder.DecodingException; import io.olvid.engine.encoder.Encoded; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; +import io.olvid.engine.identity.databases.ContactIdentityDetails; import io.olvid.engine.identity.databases.OwnedIdentityDetails; +import io.olvid.engine.identity.databases.ServerUserData; import io.olvid.engine.identity.datatypes.IdentityManagerSession; // This class is used for both owned identity and contacts @@ -50,16 +57,87 @@ public class IdentityDetailsSyncSnapshot implements ObvSyncSnapshotNode { public HashSet domain; - public static IdentityDetailsSyncSnapshot of(IdentityManagerSession identityManagerSession, OwnedIdentityDetails publishedDetails) { + public static IdentityDetailsSyncSnapshot of(IdentityManagerSession identityManagerSession, OwnedIdentityDetails ownedIdentityDetails) { IdentityDetailsSyncSnapshot identityDetailsSyncSnapshot = new IdentityDetailsSyncSnapshot(); - identityDetailsSyncSnapshot.version = publishedDetails.getVersion(); - identityDetailsSyncSnapshot.serialized_details = publishedDetails.getSerializedJsonDetails(); - identityDetailsSyncSnapshot.photo_server_label = publishedDetails.getPhotoServerLabel().getBytes(); - identityDetailsSyncSnapshot.photo_server_key = Encoded.of(publishedDetails.getPhotoServerKey()).getBytes(); + identityDetailsSyncSnapshot.version = ownedIdentityDetails.getVersion(); + identityDetailsSyncSnapshot.serialized_details = ownedIdentityDetails.getSerializedJsonDetails(); + if (ownedIdentityDetails.getPhotoServerLabel() != null && ownedIdentityDetails.getPhotoServerKey() != null) { + identityDetailsSyncSnapshot.photo_server_label = ownedIdentityDetails.getPhotoServerLabel().getBytes(); + identityDetailsSyncSnapshot.photo_server_key = Encoded.of(ownedIdentityDetails.getPhotoServerKey()).getBytes(); + } + identityDetailsSyncSnapshot.domain = DEFAULT_DOMAIN; + return identityDetailsSyncSnapshot; + } + + public static IdentityDetailsSyncSnapshot of(IdentityManagerSession identityManagerSession, ContactIdentityDetails contactIdentityDetails) { + IdentityDetailsSyncSnapshot identityDetailsSyncSnapshot = new IdentityDetailsSyncSnapshot(); + identityDetailsSyncSnapshot.version = contactIdentityDetails.getVersion(); + identityDetailsSyncSnapshot.serialized_details = contactIdentityDetails.getSerializedJsonDetails(); + if (contactIdentityDetails.getPhotoServerLabel() != null && contactIdentityDetails.getPhotoServerKey() != null) { + identityDetailsSyncSnapshot.photo_server_label = contactIdentityDetails.getPhotoServerLabel().getBytes(); + identityDetailsSyncSnapshot.photo_server_key = Encoded.of(contactIdentityDetails.getPhotoServerKey()).getBytes(); + } identityDetailsSyncSnapshot.domain = DEFAULT_DOMAIN; return identityDetailsSyncSnapshot; } + @JsonIgnore + public OwnedIdentityDetails restoreOwned(IdentityManagerSession identityManagerSession, Identity ownedIdentity) throws Exception { + if (!domain.contains(VERSION) || !domain.contains(SERIALIZED_DETAILS)) { + Logger.e("Trying to restore an incomplete IdentityDetailsSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + UID photoServerLabel; + AuthEncKey photoServerKey; + if (domain.contains(PHOTO_SERVER_LABEL) && domain.contains(PHOTO_SERVER_KEY) && photo_server_key != null && photo_server_label != null) { + try { + photoServerLabel = new UID(photo_server_label); + photoServerKey = (AuthEncKey) new Encoded(photo_server_key).decodeSymmetricKey(); + } catch (Exception e) { + e.printStackTrace(); + photoServerLabel = null; + photoServerKey = null; + } + } else { + photoServerLabel = null; + photoServerKey = null; + } + OwnedIdentityDetails ownedIdentityDetails = new OwnedIdentityDetails(identityManagerSession, ownedIdentity, version, serialized_details, null, photoServerLabel, photoServerKey); + ownedIdentityDetails.insert(); + if (photoServerLabel != null && photoServerKey != null) { + ServerUserData.createForOwnedIdentityDetails(identityManagerSession, ownedIdentity, photoServerLabel); + } + return ownedIdentityDetails; + } + + @JsonIgnore + public ContactIdentityDetails restoreContact(IdentityManagerSession identityManagerSession, Identity ownedIdentity, Identity contactIdentity) throws Exception { + if (!domain.contains(VERSION) || !domain.contains(SERIALIZED_DETAILS)) { + Logger.e("Trying to restore an incomplete IdentityDetailsSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + UID photoServerLabel; + AuthEncKey photoServerKey; + if (domain.contains(PHOTO_SERVER_LABEL) && domain.contains(PHOTO_SERVER_KEY) && photo_server_key != null && photo_server_label != null) { + try { + photoServerLabel = new UID(photo_server_label); + photoServerKey = (AuthEncKey) new Encoded(photo_server_key).decodeSymmetricKey(); + } catch (Exception e) { + e.printStackTrace(); + photoServerLabel = null; + photoServerKey = null; + } + } else { + photoServerLabel = null; + photoServerKey = null; + } + ContactIdentityDetails contactIdentityDetails = new ContactIdentityDetails(identityManagerSession,contactIdentity, ownedIdentity, version, serialized_details, null, photoServerLabel, photoServerKey); + contactIdentityDetails.insert(); + return contactIdentityDetails; + } + @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { if (!(otherSnapshotNode instanceof IdentityDetailsSyncSnapshot)) { @@ -110,7 +188,7 @@ public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { - // TODO + // TODO computeDiff return null; } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityManagerSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityManagerSyncSnapshot.java index a08667e2..a3f37184 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityManagerSyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/IdentityManagerSyncSnapshot.java @@ -19,6 +19,7 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.sql.SQLException; @@ -27,11 +28,13 @@ import java.util.HashSet; import java.util.List; +import io.olvid.engine.Logger; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.OwnedIdentity; import io.olvid.engine.identity.datatypes.IdentityManagerSession; +import io.olvid.engine.protocol.datatypes.ProtocolStarterDelegate; @JsonIgnoreProperties(ignoreUnknown = true) public class IdentityManagerSyncSnapshot implements ObvSyncSnapshotNode { @@ -54,6 +57,21 @@ public static IdentityManagerSyncSnapshot of(IdentityManagerSession identityMana return identityManagerSyncSnapshot; } + @JsonIgnore + public void restore(IdentityManagerSession identityManagerSession, ProtocolStarterDelegate protocolStarterDelegate) throws Exception { + if (!domain.contains(OWNED_IDENTITY) || !domain.contains(OWNED_IDENTITY_NODE)) { + Logger.e("Trying to restore an incomplete IdentityManagerSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + Identity ownedIdentity = Identity.of(owned_identity); + if (!identityManagerSession.identityDelegate.isOwnedIdentity(identityManagerSession.session, ownedIdentity)) { + Logger.e("Trying to restore a snapshot of an unknown owned identity"); + throw new Exception(); + } + + owned_identity_node.restore(identityManagerSession, protocolStarterDelegate, ownedIdentity); + } + @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { if (!(otherSnapshotNode instanceof IdentityManagerSyncSnapshot)) { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/KeycloakSyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/KeycloakSyncSnapshot.java index 1c843c58..c3cc81e8 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/KeycloakSyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/KeycloakSyncSnapshot.java @@ -19,6 +19,7 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.sql.SQLException; @@ -27,6 +28,8 @@ import java.util.List; import java.util.Objects; +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.KeycloakServer; @@ -38,14 +41,18 @@ public class KeycloakSyncSnapshot implements ObvSyncSnapshotNode { public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; public static final String KEYCLOAK_USER_ID = "keycloak_user_id"; + public static final String JWKS = "jwks"; + public static final String SIGNATURE_KEY = "signature_key"; public static final String SELF_REVOCATION_TEST_NONCE = "self_revocation_test_nonce"; - static HashSet DEFAULT_DOMAIN = new HashSet<>(Arrays.asList(SERVER_URL, CLIENT_ID, CLIENT_SECRET, KEYCLOAK_USER_ID, SELF_REVOCATION_TEST_NONCE)); + static HashSet DEFAULT_DOMAIN = new HashSet<>(Arrays.asList(SERVER_URL, CLIENT_ID, CLIENT_SECRET, KEYCLOAK_USER_ID, JWKS, SIGNATURE_KEY, SELF_REVOCATION_TEST_NONCE)); public String server_url; public String client_id; public String client_secret; public String keycloak_user_id; + public String jwks; + public String signature_key; public String self_revocation_test_nonce; public HashSet domain; @@ -56,11 +63,36 @@ public static KeycloakSyncSnapshot of(IdentityManagerSession identityManagerSess keycloakSyncSnapshot.client_id = keycloakServer.getClientId(); keycloakSyncSnapshot.client_secret = keycloakServer.getClientSecret(); keycloakSyncSnapshot.keycloak_user_id = keycloakServer.getKeycloakUserId(); + keycloakSyncSnapshot.jwks = keycloakServer.getSerializedJwks(); + keycloakSyncSnapshot.signature_key = keycloakServer.getSerializedSignatureKey(); keycloakSyncSnapshot.self_revocation_test_nonce = keycloakServer.getSelfRevocationTestNonce(); keycloakSyncSnapshot.domain = DEFAULT_DOMAIN; return keycloakSyncSnapshot; } + @JsonIgnore + public KeycloakServer restore(IdentityManagerSession identityManagerSession, Identity ownedIdentity, KeycloakSyncSnapshot keycloak) throws Exception { + if (!domain.contains(SERVER_URL) || !domain.contains(CLIENT_ID) || !domain.contains(KEYCLOAK_USER_ID) || !domain.contains(JWKS)) { + Logger.e("Trying to restore an incomplete KeycloakSyncSnapshot. Domain: " + domain); + throw new Exception(); + } + if (keycloak.server_url == null || keycloak.client_id == null || keycloak.keycloak_user_id == null || keycloak.jwks == null) { + return null; + } + + try { + KeycloakServer keycloakServer = new KeycloakServer(identityManagerSession, server_url, ownedIdentity, jwks, domain.contains(SIGNATURE_KEY) ? signature_key : null, client_id, client_secret); + keycloakServer.insert(); + keycloakServer.setKeycloakUserId(keycloak_user_id); + keycloakServer.setSelfRevocationTestNonce(self_revocation_test_nonce); + + return keycloakServer; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { if (!(otherSnapshotNode instanceof KeycloakSyncSnapshot)) { @@ -97,6 +129,20 @@ public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { } break; } + case JWKS: { + // TODO: deserialize for comparison + if (!Objects.equals(jwks, other.jwks)) { + return false; + } + break; + } + case SIGNATURE_KEY: { + // TODO: deserialize for comparison + if (!Objects.equals(signature_key, other.signature_key)) { + return false; + } + break; + } case SELF_REVOCATION_TEST_NONCE: { if (!Objects.equals(self_revocation_test_nonce, other.self_revocation_test_nonce)) { return false; @@ -110,7 +156,7 @@ public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { - // TODO + // TODO computeDiff return null; } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/OwnedIdentitySyncSnapshot.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/OwnedIdentitySyncSnapshot.java index 775dd5fb..bc6332c1 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/OwnedIdentitySyncSnapshot.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/databases/sync/OwnedIdentitySyncSnapshot.java @@ -19,6 +19,7 @@ package io.olvid.engine.identity.databases.sync; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -28,32 +29,46 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; - +import java.util.Map; + +import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.containers.GroupV2; +import io.olvid.engine.datatypes.key.asymmetric.EncryptionPrivateKey; +import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationPrivateKey; +import io.olvid.engine.datatypes.key.symmetric.MACKey; +import io.olvid.engine.encoder.Encoded; import io.olvid.engine.engine.types.ObvBytesKey; +import io.olvid.engine.engine.types.ObvCapability; import io.olvid.engine.engine.types.ObvGroupOwnerAndUidKey; +import io.olvid.engine.engine.types.identities.ObvIdentity; import io.olvid.engine.engine.types.sync.ObvSyncDiff; import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; import io.olvid.engine.identity.databases.ContactGroup; import io.olvid.engine.identity.databases.ContactGroupV2; import io.olvid.engine.identity.databases.ContactIdentity; import io.olvid.engine.identity.databases.KeycloakServer; +import io.olvid.engine.identity.databases.OwnedDevice; import io.olvid.engine.identity.databases.OwnedIdentity; import io.olvid.engine.identity.databases.OwnedIdentityDetails; import io.olvid.engine.identity.datatypes.IdentityManagerSession; +import io.olvid.engine.protocol.datatypes.ProtocolStarterDelegate; @JsonIgnoreProperties(ignoreUnknown = true) public class OwnedIdentitySyncSnapshot implements ObvSyncSnapshotNode { + public static final String PRIVATE_IDENTITY = "private_identity"; public static final String PUBLISHED_DETAILS = "published_details"; public static final String KEYCLOAK = "keycloak"; public static final String CONTACTS = "contacts"; public static final String GROUPS = "groups"; public static final String GROUPS2 = "groups2"; - static HashSet DEFAULT_DOMAIN = new HashSet<>(Arrays.asList(PUBLISHED_DETAILS, KEYCLOAK, CONTACTS, GROUPS, GROUPS2)); + static HashSet DEFAULT_DOMAIN = new HashSet<>(Arrays.asList(PRIVATE_IDENTITY, PUBLISHED_DETAILS, KEYCLOAK, CONTACTS, GROUPS, GROUPS2)); + public PrivateIdentity private_identity; public IdentityDetailsSyncSnapshot published_details; public KeycloakSyncSnapshot keycloak; - @JsonSerialize(keyUsing = ObvBytesKey.Serializer.class) + @JsonSerialize(keyUsing = ObvBytesKey.KeySerializer.class) @JsonDeserialize(keyUsing = ObvBytesKey.KeyDeserializer.class) public HashMap contacts; @@ -61,15 +76,18 @@ public class OwnedIdentitySyncSnapshot implements ObvSyncSnapshotNode { @JsonDeserialize(keyUsing = ObvGroupOwnerAndUidKey.Deserializer.class) public HashMap groups; - @JsonSerialize(keyUsing = ObvBytesKey.Serializer.class) + @JsonSerialize(keyUsing = ObvBytesKey.KeySerializer.class) @JsonDeserialize(keyUsing = ObvBytesKey.KeyDeserializer.class) public HashMap groups2; public HashSet domain; public static OwnedIdentitySyncSnapshot of(IdentityManagerSession identityManagerSession, OwnedIdentity ownedIdentity) throws SQLException { + OwnedIdentitySyncSnapshot ownedIdentitySyncSnapshot = new OwnedIdentitySyncSnapshot(); + ownedIdentitySyncSnapshot.private_identity = PrivateIdentity.of(ownedIdentity.getPrivateIdentity()); + OwnedIdentityDetails publishedDetails = OwnedIdentityDetails.get(identityManagerSession, ownedIdentity.getOwnedIdentity(), ownedIdentity.getPublishedDetailsVersion()); if (publishedDetails != null) { ownedIdentitySyncSnapshot.published_details = IdentityDetailsSyncSnapshot.of(identityManagerSession, publishedDetails); @@ -101,15 +119,98 @@ public static OwnedIdentitySyncSnapshot of(IdentityManagerSession identityManage return ownedIdentitySyncSnapshot; } + + @JsonIgnore + public ObvIdentity restoreOwnedIdentity(IdentityManagerSession identityManagerSession, String deviceName, Identity ownedIdentity) throws Exception { + if (!domain.contains(PRIVATE_IDENTITY) || !domain.contains(PUBLISHED_DETAILS)) { + Logger.e("Trying to restore an incomplete OwnedIdentitySyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + // restore the private key + ServerAuthenticationPrivateKey serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) new Encoded(private_identity.server_authentication_private_key).decodePrivateKey(); + EncryptionPrivateKey encryptionPrivateKey = (EncryptionPrivateKey) new Encoded(private_identity.encryption_private_key).decodePrivateKey(); + MACKey macKey = (MACKey) new Encoded(private_identity.mac_key).decodeSymmetricKey(); + io.olvid.engine.datatypes.PrivateIdentity privateIdentity = new io.olvid.engine.datatypes.PrivateIdentity(ownedIdentity, serverAuthenticationPrivateKey, encryptionPrivateKey, macKey); + + // restore published details + OwnedIdentityDetails ownedIdentityDetails = published_details.restoreOwned(identityManagerSession, ownedIdentity); + + // create the owned identity in DB + OwnedIdentity ownedIdentityObject = new OwnedIdentity(identityManagerSession, privateIdentity, ownedIdentityDetails.getVersion()); + ownedIdentityObject.insert(); + + // restore keycloak data (if any) + if (domain.contains(KEYCLOAK) && keycloak != null) { + KeycloakServer keycloakServer = keycloak.restore(identityManagerSession, ownedIdentity, keycloak); + if (keycloakServer != null) { + ownedIdentityObject.setKeycloakServerUrl(keycloakServer.getServerUrl()); + } + } + + // create the current device with a random deviceUid + OwnedDevice currentOwnedDevice = OwnedDevice.createCurrentDevice(identityManagerSession, ownedIdentity, deviceName, identityManagerSession.prng); + currentOwnedDevice.setRawDeviceCapabilities(ObvCapability.capabilityListToStringArray(ObvCapability.currentCapabilities)); + + return new ObvIdentity(ownedIdentity, ownedIdentityDetails.getJsonIdentityDetails(), ownedIdentityObject.isKeycloakManaged(), true); + } + + @JsonIgnore + public void restore(IdentityManagerSession identityManagerSession, ProtocolStarterDelegate protocolStarterDelegate, Identity ownedIdentity) throws Exception { + if (!domain.contains(PRIVATE_IDENTITY) || !domain.contains(PUBLISHED_DETAILS)) { + Logger.e("Trying to restore an incomplete OwnedIdentitySyncSnapshot. Domain: " + domain); + throw new Exception(); + } + + // restore contacts + if (domain.contains(CONTACTS) && contacts != null) { + for (Map.Entry contactEntry : contacts.entrySet()) { + Identity contactIdentity = Identity.of(contactEntry.getKey().getBytes()); + contactEntry.getValue().restore(identityManagerSession, ownedIdentity, contactIdentity); + } + } + + // restore groups v1 + if (domain.contains(GROUPS) && groups != null) { + for (Map.Entry groupEntry : groups.entrySet()) { + Identity groupOwnerIdentity = Identity.of(groupEntry.getKey().groupOwner); + groupEntry.getValue().restore(identityManagerSession, ownedIdentity, groupOwnerIdentity, groupEntry.getKey().getGroupOwnerAndUid()); + } + } + + // restore groups v2 + if (domain.contains(GROUPS2) && groups2 != null) { + for (Map.Entry group2Entry : groups2.entrySet()) { + GroupV2.Identifier groupIdentifier = GroupV2.Identifier.of(new Encoded(group2Entry.getKey().getBytes())); + group2Entry.getValue().restore(identityManagerSession, protocolStarterDelegate, ownedIdentity, groupIdentifier); + } + } + } + @Override public boolean areContentsTheSame(ObvSyncSnapshotNode otherSnapshotNode) { - // TODO + // TODO areContentsTheSame return false; } @Override public List computeDiff(ObvSyncSnapshotNode otherSnapshotNode) throws Exception { - // TODO + // TODO computeDiff return null; } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PrivateIdentity { + public byte[] server_authentication_private_key; + public byte[] encryption_private_key; + public byte[] mac_key; + + private static PrivateIdentity of (io.olvid.engine.datatypes.PrivateIdentity privateIdentity) { + PrivateIdentity pi = new PrivateIdentity(); + pi.server_authentication_private_key = Encoded.of(privateIdentity.getServerAuthenticationPrivateKey()).getBytes(); + pi.encryption_private_key = Encoded.of(privateIdentity.getEncryptionPrivateKey()).getBytes(); + pi.mac_key = Encoded.of(privateIdentity.getMacKey()).getBytes(); + return pi; + } + } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/identity/datatypes/IdentityManagerSession.java b/obv_engine/engine/src/main/java/io/olvid/engine/identity/datatypes/IdentityManagerSession.java index bba82ff3..c9ca2e85 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/identity/datatypes/IdentityManagerSession.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/identity/datatypes/IdentityManagerSession.java @@ -23,6 +23,7 @@ import java.sql.SQLException; +import io.olvid.engine.crypto.PRNGService; import io.olvid.engine.datatypes.Session; import io.olvid.engine.metamanager.IdentityDelegate; import io.olvid.engine.metamanager.NotificationPostingDelegate; @@ -33,13 +34,15 @@ public class IdentityManagerSession implements AutoCloseable { public final IdentityDelegate identityDelegate; public final String engineBaseDirectory; public final ObjectMapper jsonObjectMapper; + public final PRNGService prng; - public IdentityManagerSession(Session session, NotificationPostingDelegate notificationPostingDelegate, IdentityDelegate identityDelegate, String engineBaseDirectory, ObjectMapper jsonObjectMapper) { + public IdentityManagerSession(Session session, NotificationPostingDelegate notificationPostingDelegate, IdentityDelegate identityDelegate, String engineBaseDirectory, ObjectMapper jsonObjectMapper, PRNGService prng) { this.session = session; this.notificationPostingDelegate = notificationPostingDelegate; this.identityDelegate = identityDelegate; this.engineBaseDirectory = engineBaseDirectory; this.jsonObjectMapper = jsonObjectMapper; + this.prng = prng; } @Override 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 92c4df6c..35d3645f 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 @@ -53,12 +53,14 @@ import io.olvid.engine.engine.types.JsonIdentityDetailsWithVersionAndPhoto; import io.olvid.engine.engine.types.JsonKeycloakUserDetails; import io.olvid.engine.engine.types.ObvCapability; +import io.olvid.engine.engine.types.sync.ObvBackupAndSyncDelegate; import io.olvid.engine.engine.types.sync.ObvSyncAtom; import io.olvid.engine.engine.types.identities.ObvContactActiveOrInactiveReason; import io.olvid.engine.engine.types.identities.ObvGroupV2; import io.olvid.engine.engine.types.identities.ObvIdentity; import io.olvid.engine.engine.types.identities.ObvKeycloakState; import io.olvid.engine.engine.types.identities.ObvOwnedDevice; +import io.olvid.engine.identity.databases.sync.IdentityManagerSyncSnapshot; import io.olvid.engine.identity.datatypes.KeycloakGroupBlob; public interface IdentityDelegate { @@ -249,4 +251,10 @@ public interface IdentityDelegate { // device sync void processSyncItem(Session session, Identity ownedIdentity, ObvSyncAtom obvSyncAtom) throws Exception; + + ObvBackupAndSyncDelegate getSyncDelegateWithinTransaction(Session session); + + ObvIdentity restoreTransferredOwnedIdentity(Session session, String deviceName, IdentityManagerSyncSnapshot node) throws Exception; + + void downloadAllUserData(Session session) throws Exception; } 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 b4d832e3..abd571e9 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 @@ -111,7 +111,7 @@ public FetchManager(MetaManager metaManager, SSLSocketFactory sslSocketFactory, this.serverPushNotificationsCoordinator = new RegisterServerPushNotificationsCoordinator(this, sslSocketFactory, createServerSessionCoordinator, downloadMessagesAndListAttachmentsCoordinator); this.downloadMessagesAndListAttachmentsCoordinator.setRegisterServerPushNotificationDelegate(this.serverPushNotificationsCoordinator); this.serverUserDataCoordinator = new ServerUserDataCoordinator(this, sslSocketFactory, createServerSessionCoordinator, jsonObjectMapper, prng); - this.serverQueryCoordinator = new ServerQueryCoordinator(this, sslSocketFactory, prng, createServerSessionCoordinator, serverUserDataCoordinator); + this.serverQueryCoordinator = new ServerQueryCoordinator(this, sslSocketFactory, prng, createServerSessionCoordinator, serverUserDataCoordinator, jsonObjectMapper); this.freeTrialCoordinator = new FreeTrialCoordinator(this, sslSocketFactory); this.verifyReceiptCoordinator = new VerifyReceiptCoordinator(this, sslSocketFactory, createServerSessionCoordinator); this.wellKnownCoordinator = new WellKnownCoordinator(this, sslSocketFactory, jsonObjectMapper); 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 8f05b0c0..99bc24ed 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 @@ -28,6 +28,7 @@ import javax.net.ssl.SSLSocketFactory; import io.olvid.engine.Logger; +import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.ExponentialBackoffRepeatingScheduler; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.datatypes.NoDuplicateOperationQueue; @@ -197,8 +198,15 @@ public void callback(String notificationName, HashMap userInfo) @Override public void onFinishCallback(Operation operation) { Identity identity = ((DownloadMessagesAndListAttachmentsOperation) operation).getOwnedIdentity(); + UID deviceUid = ((DownloadMessagesAndListAttachmentsOperation) operation).getDeviceUid(); + int newMessagesCount = ((DownloadMessagesAndListAttachmentsOperation) operation).getNewMessagesCount(); scheduler.clearFailedCount(identity); + if (newMessagesCount > Constants.RELIST_NEW_MESSAGE_COUNT_THRESHOLD) { + // if we listed more than 20 new messages, we might have missed some messages on the server --> trigger a new list in 30 seconds, once messages are processed and deleted from server + scheduler.schedule(identity, () -> queueNewDownloadMessagesAndListAttachmentsOperation(identity, deviceUid), "DownloadMessagesAndListAttachmentsOperation [relist]", Constants.RELIST_DELAY); + } + HashMap userInfo = new HashMap<>(); userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_OWNED_IDENTITY_KEY, identity); userInfo.put(DownloadNotifications.NOTIFICATION_SERVER_POLLED_SUCCESS_KEY, true); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinator.java index 78afff64..54edd3ad 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinator.java @@ -19,6 +19,8 @@ package io.olvid.engine.networkfetch.coordinators; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; @@ -55,6 +57,7 @@ public class ServerQueryCoordinator implements PendingServerQuery.PendingServerQ private final PRNGService prng; private final CreateServerSessionDelegate createServerSessionDelegate; + private final ServerQueryCoordinatorWebSocketModule webSocketModule; private final ExponentialBackoffRepeatingScheduler scheduler; private final NoDuplicateOperationQueue serverQueriesOperationQueue; @@ -73,13 +76,15 @@ public class ServerQueryCoordinator implements PendingServerQuery.PendingServerQ private ChannelDelegate channelDelegate; - public ServerQueryCoordinator(FetchManagerSessionFactory fetchManagerSessionFactory, SSLSocketFactory sslSocketFactory, PRNGService prng, CreateServerSessionDelegate createServerSessionDelegate, ServerUserDataCoordinator serverUserDataCoordinator) { + public ServerQueryCoordinator(FetchManagerSessionFactory fetchManagerSessionFactory, SSLSocketFactory sslSocketFactory, PRNGService prng, CreateServerSessionDelegate createServerSessionDelegate, ServerUserDataCoordinator serverUserDataCoordinator, ObjectMapper jsonObjectMapper) { this.fetchManagerSessionFactory = fetchManagerSessionFactory; this.sslSocketFactory = sslSocketFactory; this.prng = prng; this.createServerSessionDelegate = createServerSessionDelegate; this.serverUserDataCoordinator = serverUserDataCoordinator; + webSocketModule = new ServerQueryCoordinatorWebSocketModule(fetchManagerSessionFactory, sslSocketFactory, jsonObjectMapper, prng); + serverQueriesOperationQueue = new NoDuplicateOperationQueue(); scheduler = new ExponentialBackoffRepeatingScheduler<>(); @@ -101,8 +106,14 @@ public void initialQueueing() { try (FetchManagerSession fetchManagerSession = fetchManagerSessionFactory.getSession()) { PendingServerQuery[] pendingServerQueries = PendingServerQuery.getAll(fetchManagerSession); for (PendingServerQuery pendingServerQuery: pendingServerQueries) { - queueNewServerQueryOperation(pendingServerQuery.getUid()); + if (pendingServerQuery.isWebSocket()) { + pendingServerQuery.delete(); + } else { + queueNewServerQueryOperation(pendingServerQuery.getUid()); + } } + // commit, in case a WebSocket query was deleted + fetchManagerSession.session.commit(); initialQueueingPerformed = true; } catch (Exception e) { e.printStackTrace(); @@ -121,6 +132,7 @@ public void setNotificationListeningDelegate(NotificationListeningDelegate notif public void setChannelDelegate(ChannelDelegate channelDelegate) { this.channelDelegate = channelDelegate; + webSocketModule.setChannelDelegate(channelDelegate); } private void waitForServerSession(Identity identity, UID serverQueryUid) { @@ -290,8 +302,8 @@ public void onFinishCallback(Operation operation) { fetchManagerSession.session.commit(); } - if (serverQuery.getType().getId() == ServerQuery.Type.PUT_USER_DATA_QUERY_ID) { - serverUserDataCoordinator.newUserDataUploaded(serverQuery.getOwnedIdentity(), serverQuery.getType().getServerLabelOrDeviceUid()); + if (serverQuery.getType() instanceof ServerQuery.PutUserDataQuery) { + serverUserDataCoordinator.newUserDataUploaded(serverQuery.getOwnedIdentity(), ((ServerQuery.PutUserDataQuery) serverQuery.getType()).serverLabel); } } catch (Exception e) { e.printStackTrace(); @@ -302,7 +314,11 @@ public void onFinishCallback(Operation operation) { // Notifications received from PendingServerQuery database @Override - public void newPendingServerQuery(UID uid) { - queueNewServerQueryOperation(uid); + public void newPendingServerQuery(PendingServerQuery pendingServerQuery) { + if (pendingServerQuery.isWebSocket()) { + webSocketModule.handleServerQuery(pendingServerQuery, false); + } else { + queueNewServerQueryOperation(pendingServerQuery.getUid()); + } } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinatorWebSocketModule.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinatorWebSocketModule.java new file mode 100644 index 00000000..1aef62fa --- /dev/null +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerQueryCoordinatorWebSocketModule.java @@ -0,0 +1,502 @@ +/* + * Olvid for Android + * Copyright © 2019-2023 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.coordinators; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Objects; + +import javax.net.ssl.SSLSocketFactory; + +import io.olvid.engine.Logger; +import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.datatypes.Constants; +import io.olvid.engine.datatypes.NoExceptionSingleThreadExecutor; +import io.olvid.engine.datatypes.UID; +import io.olvid.engine.datatypes.containers.ChannelServerResponseMessageToSend; +import io.olvid.engine.datatypes.containers.ServerQuery; +import io.olvid.engine.encoder.DecodingException; +import io.olvid.engine.encoder.Encoded; +import io.olvid.engine.metamanager.ChannelDelegate; +import io.olvid.engine.networkfetch.databases.PendingServerQuery; +import io.olvid.engine.networkfetch.datatypes.FetchManagerSession; +import io.olvid.engine.networkfetch.datatypes.FetchManagerSessionFactory; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +public class ServerQueryCoordinatorWebSocketModule { + private static final int ERROR_CODE_GENERAL_ERROR = -1; + private static final int ERROR_CODE_UNKNOWN_SESSION_NUMBER = 1; + private static final int ERROR_CODE_OTHER_DISCONNECTED = 2; + private static final int ERROR_CODE_PAYLOAD_TOO_LARGE = 3; + + private final FetchManagerSessionFactory fetchManagerSessionFactory; + private final SSLSocketFactory sslSocketFactory; + private final ObjectMapper jsonObjectMapper; + private final PRNGService prng; + private final HashMap webSocketsMap; + private final NoExceptionSingleThreadExecutor executor; + private ChannelDelegate channelDelegate; + + + public ServerQueryCoordinatorWebSocketModule(FetchManagerSessionFactory fetchManagerSessionFactory, + SSLSocketFactory sslSocketFactory, + ObjectMapper jsonObjectMapper, + PRNGService prng) { + this.fetchManagerSessionFactory = fetchManagerSessionFactory; + this.sslSocketFactory = sslSocketFactory; + this.jsonObjectMapper = jsonObjectMapper; + this.prng = prng; + this.webSocketsMap = new HashMap<>(); + this.executor = new NoExceptionSingleThreadExecutor("ServerQueryCoordinatorWebSocketModule executor"); + } + + public void setChannelDelegate(ChannelDelegate channelDelegate) { + this.channelDelegate = channelDelegate; + } + + public void handleServerQuery(PendingServerQuery pendingServerQuery, boolean calledFromOnOpen) { + executor.execute(() -> { + ServerQuery serverQuery; + try { + serverQuery = ServerQuery.of(pendingServerQuery.getEncodedQuery()); + } catch (DecodingException e) { + e.printStackTrace(); + return; + } + + final UID protocolInstanceUid; + //noinspection CommentedOutCode + try { + Encoded[] listOfEncoded = serverQuery.getEncodedElements().decodeList(); + protocolInstanceUid = listOfEncoded[1].decodeUid(); +/* + int protocolId = (int) listOfEncoded[0].decodeLong(); + protocolInstanceUid = listOfEncoded[1].decodeUid(); + int protocolMessageId = (int) listOfEncoded[2].decodeLong(); + Encoded[] inputs = listOfEncoded[3].decodeList(); +*/ + } catch (ArrayIndexOutOfBoundsException | DecodingException e) { + // we cannot respond to the protocol for a proper fail, so we simply ignore the message + Logger.e("ServerQueryCoordinatorWebSocketModule.handlePendingServerQuery() failed to decode received serverQuery"); + return; + } + + try { + WebSocketClientAndServerQuery webSocketClientAndQuery = webSocketsMap.get(protocolInstanceUid); + if (webSocketClientAndQuery == null) { + // only the source and target message can trigger a websocket connection + if (serverQuery.getType().getId() != ServerQuery.TypeId.TRANSFER_SOURCE_QUERY_ID + && serverQuery.getType().getId() != ServerQuery.TypeId.TRANSFER_TARGET_QUERY_ID) { + sendFailedServerQueryResponse(pendingServerQuery); + return; + } + + // we do not handle the message yet, once the websocket is connected, it will be automatically handled + WebSocketClient webSocketClient = new WebSocketClient(protocolInstanceUid); + webSocketClientAndQuery = new WebSocketClientAndServerQuery(webSocketClient, pendingServerQuery); + webSocketsMap.put(protocolInstanceUid, webSocketClientAndQuery); + webSocketClientAndQuery.webSocketClient.connect(); + return; + } + + if (!calledFromOnOpen) { + if (webSocketClientAndQuery.pendingServerQuery != null) { + try { + // check if the existing pendingServerQuery is a noResponseExpected relay message + ServerQuery previousServerQuery = ServerQuery.of(webSocketClientAndQuery.pendingServerQuery.getEncodedQuery()); + if (previousServerQuery.getType().getId() == ServerQuery.TypeId.TRANSFER_RELAY_QUERY_ID + && ((ServerQuery.TransferRelayQuery) previousServerQuery.getType()).noResponseExpected) { + // in that case, delete the previous pendingServerQuery + try (FetchManagerSession fetchManagerSession = fetchManagerSessionFactory.getSession()) { + PendingServerQuery rePendingServerQuery = PendingServerQuery.get(fetchManagerSession, webSocketClientAndQuery.pendingServerQuery.getUid()); + if (rePendingServerQuery != null) { + rePendingServerQuery.delete(); + fetchManagerSession.session.commit(); + } + } + } else { + // we have a message to send but never responded to the previous message --> this should never happen! + failProtocol(protocolInstanceUid); + return; + } + } catch (Exception ignored) { } + } + + webSocketClientAndQuery.pendingServerQuery = pendingServerQuery; + } + + if (webSocketClientAndQuery.webSocketClient.connectionStatus != WebSocketClient.ConnectionStatus.CONNECTED) { + // we received a message to send before the websocket ever got a chance to connect --> this should never happen! + failProtocol(protocolInstanceUid); + return; + } + + switch (serverQuery.getType().getId()) { + case TRANSFER_SOURCE_QUERY_ID: { + JsonRequestSource request = new JsonRequestSource(); + request.action = "source"; + + webSocketClientAndQuery.webSocketClient.send(jsonObjectMapper.writeValueAsString(request)); + break; + } + case TRANSFER_TARGET_QUERY_ID: { + ServerQuery.TransferTargetQuery transferTargetQuery = (ServerQuery.TransferTargetQuery) serverQuery.getType(); + JsonRequestTarget request = new JsonRequestTarget(); + request.action = "target"; + request.sessionNumber = transferTargetQuery.sessionNumber; + request.payload = transferTargetQuery.payload; + + webSocketClientAndQuery.webSocketClient.send(jsonObjectMapper.writeValueAsString(request)); + break; + } + case TRANSFER_RELAY_QUERY_ID: { + ServerQuery.TransferRelayQuery transferRelayQuery = (ServerQuery.TransferRelayQuery) serverQuery.getType(); + JsonRequestRelay request = new JsonRequestRelay(); + request.action = "relay"; + request.relayConnectionId = transferRelayQuery.connectionIdentifier; + request.payload = transferRelayQuery.payload; + + if (request.payload.length > Constants.TRANSFER_MAX_PAYLOAD_SIZE) { + int totalFragments = ((transferRelayQuery.payload.length - 1) / Constants.TRANSFER_MAX_PAYLOAD_SIZE) + 1; + request.totalFragments = totalFragments; + for (int i=0; i { + // once connected, process the pending message + connectionStatus = ConnectionStatus.CONNECTED; + WebSocketClientAndServerQuery clientAndQuery = webSocketsMap.get(protocolInstanceUid); + if (clientAndQuery != null && clientAndQuery.pendingServerQuery != null) { + try { + handleServerQuery(clientAndQuery.pendingServerQuery, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + if (code != 1000) { + executor.execute(this::closeAndAbort); + } + } + + @Override + public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) { + t.printStackTrace(); + executor.execute(this::closeAndAbort); + } + + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { + onMessage(text); + } + + private void onMessage(String text) { + // ignore empty messages + if (text == null || text.trim().length() == 0) { + return; + } + executor.execute(() -> { + // first, try to parse error messages + try { + JsonResponseFail fail = jsonObjectMapper.readValue(text, JsonResponseFail.class); + switch (fail.errorCode) { + case ERROR_CODE_UNKNOWN_SESSION_NUMBER: + case ERROR_CODE_OTHER_DISCONNECTED: + case ERROR_CODE_PAYLOAD_TOO_LARGE: + case ERROR_CODE_GENERAL_ERROR: { + // for now we do not care what type of error we receive, but in the future we may want + // to inform the user about what went wrong and then need to differentiate based on error code + failProtocol(protocolInstanceUid); + return; + } + } + } catch (Exception ignored) { + } + + WebSocketClientAndServerQuery clientAndQuery = webSocketsMap.get(protocolInstanceUid); + + if (clientAndQuery == null) { + // protocol ended, we can ignore the message + return; + } + PendingServerQuery pendingServerQuery = clientAndQuery.pendingServerQuery; + if (pendingServerQuery == null) { + // we don't have a server query to respond to yet, put the message on hold + messageWaitingForWait = text; + return; + } + + // check if the message was truncated or not + try { + JsonResponse fragmentedResponse = jsonObjectMapper.readValue(text, JsonResponse.class); + if (fragmentedResponse.totalFragments != null + && fragmentedResponse.fragmentNumber != null + && fragmentedResponse.fragmentNumber >= 0 + && fragmentedResponse.fragmentNumber < fragmentedResponse.totalFragments) { + // we have a fragmented response --> store it in the clientAndQuery + if (clientAndQuery.fragmentedResponse == null) { + clientAndQuery.fragmentedResponse = new HashMap<>(); + } + clientAndQuery.fragmentedResponse.put(fragmentedResponse.fragmentNumber, fragmentedResponse); + if (clientAndQuery.fragmentedResponse.size() == fragmentedResponse.totalFragments) { + // we have all the fragments, reconstruct them + JsonResponse reconstructedResponse = new JsonResponse(); + int payloadSize = 0; + for (int i = 0; i < fragmentedResponse.totalFragments; i++) { + payloadSize += clientAndQuery.fragmentedResponse.get(i).payload.length; + } + reconstructedResponse.otherConnectionId = clientAndQuery.fragmentedResponse.get(0).otherConnectionId; + reconstructedResponse.payload = new byte[payloadSize]; + int offset = 0; + for (int i = 0; i < fragmentedResponse.totalFragments; i++) { + JsonResponse fragment = clientAndQuery.fragmentedResponse.get(i); + if (!Objects.equals(reconstructedResponse.otherConnectionId, fragment.otherConnectionId)) { + throw new Exception("otherConnectionId mismatch"); + } + System.arraycopy(fragment.payload, 0, reconstructedResponse.payload, offset, fragment.payload.length); + offset += fragment.payload.length; + } + clientAndQuery.fragmentedResponse = null; + clientAndQuery.pendingServerQuery = null; + sendServerQueryResponse(pendingServerQuery, jsonObjectMapper.writeValueAsString(reconstructedResponse)); + } + return; + } + } catch (Exception e) { + // in case of exception when parsing, ignore it and simply forward the response to the protocol + e.printStackTrace(); + } + + // remove the pendingServerQuery + clientAndQuery.pendingServerQuery = null; + + // We do not parse the response here, the protocol will take care of this + sendServerQueryResponse(pendingServerQuery, text); + }); + } + + private void closeAndAbort() { + connectionStatus = ConnectionStatus.DISCONNECTED; + failProtocol(protocolInstanceUid); + } + + public enum ConnectionStatus { + INITIALIZING, + CONNECTED, + DISCONNECTED, + } + } + + public static final class WebSocketClientAndServerQuery { + public final WebSocketClient webSocketClient; + public PendingServerQuery pendingServerQuery; // null after we have replied to a message + public HashMap fragmentedResponse; + + public WebSocketClientAndServerQuery(WebSocketClient webSocketClient, PendingServerQuery pendingServerQuery) { + this.webSocketClient = webSocketClient; + this.pendingServerQuery = pendingServerQuery; + this.fragmentedResponse = null; + } + } + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonResponseFail { + public int errorCode; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonRequestSource { + public String action; + } + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonRequestTarget { + public String action; + public long sessionNumber; + public byte[] payload; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonRequestRelay { + public String action; + public String relayConnectionId; + public byte[] payload; + public Integer fragmentNumber; + public Integer totalFragments; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonResponse { + public String otherConnectionId; + public byte[] payload; + public Integer fragmentNumber; + public Integer totalFragments; + } +} diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerUserDataCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerUserDataCoordinator.java index da95572d..2286615a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerUserDataCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/coordinators/ServerUserDataCoordinator.java @@ -274,7 +274,7 @@ public void onCancelCallback(Operation operation) { ServerQuery serverQuery = new ServerQuery( Encoded.of(new Encoded[0]), ownedIdentity, - ServerQuery.Type.createPutUserDataQuery(ownedIdentity, label, photoUrl, key) + new ServerQuery.PutUserDataQuery(ownedIdentity, label, photoUrl, key) ); PendingServerQuery.create(fetchManagerSession, serverQuery, prng); 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 81501acc..c1b311aa 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 @@ -159,7 +159,10 @@ protected long computeReschedulingDelay(int failedAttemptCount) { ownedIdentityServerSessionTokens = new HashMap<>(); existingWebsockets = new HashMap<>(); + okHttpClient = initializeOkHttpClientForWebSocket(sslSocketFactory); + } + public static OkHttpClient initializeOkHttpClientForWebSocket(SSLSocketFactory sslSocketFactory) { OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (sslSocketFactory != null) { try { @@ -203,9 +206,10 @@ protected long computeReschedulingDelay(int failedAttemptCount) { }); } builder.pingInterval(Constants.WEBSOCKET_PING_INTERVAL_MILLIS, TimeUnit.MILLISECONDS); - okHttpClient = builder.build(); + return builder.build(); } + public void setNotificationListeningDelegate(NotificationListeningDelegate notificationListeningDelegate) { this.notificationListeningDelegate = notificationListeningDelegate; // register to NotificationCenter for NOTIFICATION_SERVER_SESSION_CREATED diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingServerQuery.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingServerQuery.java index 87d69013..2d00e9d7 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingServerQuery.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/databases/PendingServerQuery.java @@ -46,6 +46,8 @@ public class PendingServerQuery implements ObvDatabase { private Encoded encodedQuery; static final String CREATION_TIMESTAMP = "creation_timestamp"; private long creationTimestamp; + static final String WEBSOCKET = "websocket"; + private boolean webSocket; public UID getUid() { return uid; @@ -59,6 +61,9 @@ public long getCreationTimestamp() { return creationTimestamp; } + public boolean isWebSocket() { + return webSocket; + } // region constructors @@ -69,7 +74,7 @@ public static PendingServerQuery create(FetchManagerSession fetchManagerSession, try { Encoded encodedQuery = serverQuery.encode(); UID uid = new UID(prng); - PendingServerQuery pendingServerQuery = new PendingServerQuery(fetchManagerSession, uid, encodedQuery); + PendingServerQuery pendingServerQuery = new PendingServerQuery(fetchManagerSession, uid, encodedQuery, serverQuery.isWebSocket()); pendingServerQuery.insert(); return pendingServerQuery; } catch (SQLException e) { @@ -78,12 +83,13 @@ public static PendingServerQuery create(FetchManagerSession fetchManagerSession, } } - private PendingServerQuery(FetchManagerSession fetchManagerSession, UID uid, Encoded encodedQuery) { + private PendingServerQuery(FetchManagerSession fetchManagerSession, UID uid, Encoded encodedQuery, boolean webSocket) { this.fetchManagerSession = fetchManagerSession; this.uid = uid; this.encodedQuery = encodedQuery; this.creationTimestamp = System.currentTimeMillis(); + this.webSocket = webSocket; } private PendingServerQuery(FetchManagerSession fetchManagerSession, ResultSet res) throws SQLException { @@ -92,6 +98,7 @@ private PendingServerQuery(FetchManagerSession fetchManagerSession, ResultSet re this.uid = new UID(res.getBytes(UID_)); this.encodedQuery = new Encoded(res.getBytes(ENCODED_QUERY)); this.creationTimestamp = res.getLong(CREATION_TIMESTAMP); + this.webSocket = res.getBoolean(WEBSOCKET); } // endregion @@ -103,7 +110,8 @@ public static void createTable(Session session) throws SQLException { statement.execute("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + UID_ + " BLOB PRIMARY KEY, " + ENCODED_QUERY + " BLOB NOT NULL, " + - CREATION_TIMESTAMP + " BIGINT NOT NULL " + + CREATION_TIMESTAMP + " BIGINT NOT NULL, " + + WEBSOCKET + " BIT NOT NULL " + " );"); } } @@ -116,14 +124,22 @@ public static void upgradeTable(Session session, int oldVersion, int newVersion) } oldVersion = 31; } + if (oldVersion < 37 && newVersion >= 37) { + Logger.d("MIGRATING `server_query` DATABASE FROM VERSION " + oldVersion + " TO 36"); + try (Statement statement = session.createStatement()) { + statement.execute("ALTER TABLE `server_query` ADD COLUMN `websocket` BIT NOT NULL DEFAULT 0"); + } + oldVersion = 37; + } } @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, uid.getBytes()); statement.setBytes(2, encodedQuery.getBytes()); statement.setLong(3, creationTimestamp); + statement.setBoolean(4, webSocket); statement.executeUpdate(); this.commitHookBits |= HOOK_BIT_INSERTED; fetchManagerSession.session.addSessionCommitListener(this); @@ -179,7 +195,7 @@ public static PendingServerQuery[] getAll(FetchManagerSession fetchManagerSessio // region hooks public interface PendingServerQueryListener { - void newPendingServerQuery(UID uid); + void newPendingServerQuery(PendingServerQuery pendingServerQuery); } @@ -190,7 +206,7 @@ public interface PendingServerQueryListener { public void wasCommitted() { if ((commitHookBits & HOOK_BIT_INSERTED) != 0) { if (fetchManagerSession.pendingServerQueryListener != null) { - fetchManagerSession.pendingServerQueryListener.newPendingServerQuery(uid); + fetchManagerSession.pendingServerQueryListener.newPendingServerQuery(this); } } commitHookBits = 0; 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 9b8bd6f0..8a6794d3 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 @@ -52,11 +52,16 @@ public class DownloadMessagesAndListAttachmentsOperation extends Operation { private final SSLSocketFactory sslSocketFactory; private final Identity ownedIdentity; private final UID deviceUid; + private int newMessagesCount = -1; public Identity getOwnedIdentity() { return ownedIdentity; } + public int getNewMessagesCount() { + return newMessagesCount; + } + public UID getDeviceUid() { return deviceUid; } @@ -144,6 +149,7 @@ public void doExecute() { } } Logger.d("DownloadMessagesAndListAttachmentsOperation found " + messageAndAttachmentLengthsArray.length + " messages (" + count + " new) on the server."); + this.newMessagesCount = count; finished = true; return; } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/ServerQueryOperation.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/ServerQueryOperation.java index cf094dfa..99100880 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/ServerQueryOperation.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/ServerQueryOperation.java @@ -107,19 +107,22 @@ public void doExecute() { Logger.d("?? Starting server query operation of type " + serverQuery.getType().getId()); ServerQueryServerMethod serverMethod; - switch (serverQuery.getType().getId()) { - case ServerQuery.Type.DEVICE_DISCOVERY_QUERY_ID: { - serverMethod = new DeviceDiscoveryServerMethod(serverQuery.getType().getIdentity()); + ServerQuery.Type queryType = serverQuery.getType(); + switch (queryType.getId()) { + case DEVICE_DISCOVERY_QUERY_ID: { + ServerQuery.DeviceDiscoveryQuery deviceDiscoveryQuery = (ServerQuery.DeviceDiscoveryQuery) queryType; + serverMethod = new DeviceDiscoveryServerMethod(deviceDiscoveryQuery.identity); break; } - case ServerQuery.Type.PUT_USER_DATA_QUERY_ID: { - byte[] serverSessionToken = ServerSession.getToken(fetchManagerSession, serverQuery.getType().getIdentity()); + case PUT_USER_DATA_QUERY_ID: { + ServerQuery.PutUserDataQuery putUserDataQuery = (ServerQuery.PutUserDataQuery) queryType; + byte[] serverSessionToken = ServerSession.getToken(fetchManagerSession, putUserDataQuery.ownedIdentity); if (serverSessionToken == null) { cancel(RFC_INVALID_SERVER_SESSION); return; } // encrypt the photo - String absoluteOrNotPhotoUrl = serverQuery.getType().getDataUrl(); + String absoluteOrNotPhotoUrl = putUserDataQuery.dataUrl; File photoFile; if (new File(absoluteOrNotPhotoUrl).isAbsolute()) { photoFile = new File(absoluteOrNotPhotoUrl); @@ -150,68 +153,83 @@ public void doExecute() { } } - AuthEnc authEnc = Suite.getAuthEnc(serverQuery.getType().getDataKey()); - EncryptedBytes encryptedPhoto = authEnc.encrypt(serverQuery.getType().getDataKey(), buffer, prng); + AuthEnc authEnc = Suite.getAuthEnc(putUserDataQuery.dataKey); + EncryptedBytes encryptedPhoto = authEnc.encrypt(putUserDataQuery.dataKey, buffer, prng); - serverMethod = new PutUserDataServerMethod(serverQuery.getType().getIdentity(), serverSessionToken, serverQuery.getType().getServerLabelOrDeviceUid(), encryptedPhoto); + serverMethod = new PutUserDataServerMethod(putUserDataQuery.ownedIdentity, serverSessionToken, putUserDataQuery.serverLabel, encryptedPhoto); break; } - case ServerQuery.Type.GET_USER_DATA_QUERY_ID: { - serverMethod = new GetUserDataServerMethod(serverQuery.getType().getIdentity(), serverQuery.getType().getServerLabelOrDeviceUid(), fetchManagerSession.engineBaseDirectory); + case GET_USER_DATA_QUERY_ID: { + ServerQuery.GetUserDataQuery getUserDataQuery = (ServerQuery.GetUserDataQuery) queryType; + serverMethod = new GetUserDataServerMethod(getUserDataQuery.identity, getUserDataQuery.serverLabel, fetchManagerSession.engineBaseDirectory); break; } - case ServerQuery.Type.CHECK_KEYCLOAK_REVOCATION_QUERY_ID: { - serverMethod = new CheckKeycloakRevocationServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getSignedContactDetails()); + case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: { + ServerQuery.CheckKeycloakRevocationQuery checkKeycloakRevocationQuery = (ServerQuery.CheckKeycloakRevocationQuery) queryType; + serverMethod = new CheckKeycloakRevocationServerMethod(checkKeycloakRevocationQuery.server, checkKeycloakRevocationQuery.signedContactDetails); break; } - case ServerQuery.Type.CREATE_GROUP_BLOB_QUERY_ID: { + case CREATE_GROUP_BLOB_QUERY_ID: { + ServerQuery.CreateGroupBlobQuery createGroupBlobQuery = (ServerQuery.CreateGroupBlobQuery) queryType; byte[] serverSessionToken = ServerSession.getToken(fetchManagerSession, serverQuery.getOwnedIdentity()); if (serverSessionToken == null) { cancel(RFC_INVALID_SERVER_SESSION); return; } - serverMethod = new CreateGroupBlobServerMethod(serverQuery.getOwnedIdentity(), serverSessionToken, serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getEncodedGroupAdminPublicKey(), serverQuery.getType().getEncryptedBlob()); + serverMethod = new CreateGroupBlobServerMethod(serverQuery.getOwnedIdentity(), serverSessionToken, createGroupBlobQuery.server, createGroupBlobQuery.groupUid, createGroupBlobQuery.encodedGroupAdminPublicKey, createGroupBlobQuery.encryptedBlob); break; } - case ServerQuery.Type.GET_GROUP_BLOB_QUERY_ID: { - serverMethod = new GetGroupBlobServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getNonce()); + case GET_GROUP_BLOB_QUERY_ID: { + ServerQuery.GetGroupBlobQuery groupBlobQuery = (ServerQuery.GetGroupBlobQuery) queryType; + serverMethod = new GetGroupBlobServerMethod(groupBlobQuery.server, groupBlobQuery.groupUid, groupBlobQuery.serverQueryNonce); break; } - case ServerQuery.Type.LOCK_GROUP_BLOB_QUERY_ID: { - serverMethod = new LockGroupBlobServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getNonce(), serverQuery.getType().getQuerySignature()); + case LOCK_GROUP_BLOB_QUERY_ID: { + ServerQuery.LockGroupBlobQuery lockGroupBlobQuery = (ServerQuery.LockGroupBlobQuery) queryType; + serverMethod = new LockGroupBlobServerMethod(lockGroupBlobQuery.server, lockGroupBlobQuery.groupUid, lockGroupBlobQuery.lockNonce, lockGroupBlobQuery.signature); break; } - case ServerQuery.Type.UPDATE_GROUP_BLOB_QUERY_ID: { - serverMethod = new UpdateGroupBlobServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getNonce(), serverQuery.getType().getEncryptedBlob(), serverQuery.getType().getEncodedGroupAdminPublicKey(), serverQuery.getType().getQuerySignature()); + case UPDATE_GROUP_BLOB_QUERY_ID: { + ServerQuery.UpdateGroupBlobQuery updateGroupBlobQuery = (ServerQuery.UpdateGroupBlobQuery) queryType; + serverMethod = new UpdateGroupBlobServerMethod(updateGroupBlobQuery.server, updateGroupBlobQuery.groupUid, updateGroupBlobQuery.lockNonce, updateGroupBlobQuery.encryptedBlob, updateGroupBlobQuery.encodedGroupAdminPublicKey, updateGroupBlobQuery.signature); break; } - case ServerQuery.Type.PUT_GROUP_LOG_QUERY_ID: { - serverMethod = new PutGroupLogServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getQuerySignature()); + case PUT_GROUP_LOG_QUERY_ID: { + ServerQuery.PutGroupLogQuery putGroupLogQuery = (ServerQuery.PutGroupLogQuery) queryType; + serverMethod = new PutGroupLogServerMethod(putGroupLogQuery.server, putGroupLogQuery.groupUid, putGroupLogQuery.signature); break; } - case ServerQuery.Type.DELETE_GROUP_BLOB_QUERY_ID: { - serverMethod = new DeleteGroupBlobServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getQuerySignature()); + case DELETE_GROUP_BLOB_QUERY_ID: { + ServerQuery.DeleteGroupBlobQuery deleteGroupBlobQuery = (ServerQuery.DeleteGroupBlobQuery) queryType; + serverMethod = new DeleteGroupBlobServerMethod(deleteGroupBlobQuery.server, deleteGroupBlobQuery.groupUid, deleteGroupBlobQuery.signature); break; } - case ServerQuery.Type.GET_KEYCLOAK_DATA_QUERY_ID: { - serverMethod = new GetKeycloakDataServerMethod(serverQuery.getType().getServer(), serverQuery.getType().getServerLabelOrDeviceUid(), fetchManagerSession.engineBaseDirectory); + case GET_KEYCLOAK_DATA_QUERY_ID: { + ServerQuery.GetKeycloakDataQuery getKeycloakDataQuery = (ServerQuery.GetKeycloakDataQuery) queryType; + serverMethod = new GetKeycloakDataServerMethod(getKeycloakDataQuery.server, getKeycloakDataQuery.serverLabel, fetchManagerSession.engineBaseDirectory); break; } - case ServerQuery.Type.OWNED_DEVICE_DISCOVERY_QUERY_ID: { - serverMethod = new OwnedDeviceDiscoveryServerMethod(serverQuery.getType().getIdentity()); + case OWNED_DEVICE_DISCOVERY_QUERY_ID: { + serverMethod = new OwnedDeviceDiscoveryServerMethod(serverQuery.getOwnedIdentity()); break; } - case ServerQuery.Type.DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: - case ServerQuery.Type.DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: - case ServerQuery.Type.DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: { + case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: + case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: + case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: { byte[] serverSessionToken = ServerSession.getToken(fetchManagerSession, serverQuery.getOwnedIdentity()); if (serverSessionToken == null) { cancel(RFC_INVALID_SERVER_SESSION); return; } - serverMethod = new DeviceManagementServerMethod(serverQuery.getOwnedIdentity(), serverSessionToken, serverQuery.getType().getId(), serverQuery.getType().getServerLabelOrDeviceUid(), serverQuery.getType().getEncryptedBlob()); + serverMethod = new DeviceManagementServerMethod(serverQuery.getOwnedIdentity(), serverSessionToken, serverQuery.getType()); break; } + case REGISTER_API_KEY_QUERY_ID: + case TRANSFER_SOURCE_QUERY_ID: + case TRANSFER_TARGET_QUERY_ID: + case TRANSFER_RELAY_QUERY_ID: + case TRANSFER_WAIT_QUERY_ID: + case TRANSFER_CLOSE_QUERY_ID: default: cancel(RFC_BAD_ENCODED_SERVER_QUERY); return; @@ -240,7 +258,7 @@ public void doExecute() { // if the device is not registered: // - cancel if this is a remote device // - retry later if this is our current device for a set nickname request - if (serverQuery.getType().getId() == ServerQuery.Type.DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID && serverQuery.getType().isCurrentDevice()) { + if (serverQuery.getType() instanceof ServerQuery.DeviceManagementSetNicknameQuery && ((ServerQuery.DeviceManagementSetNicknameQuery) serverQuery.getType()).isCurrentDevice) { cancel(RFC_DEVICE_NOT_YET_REGISTERED); } else { cancel(RFC_DEVICE_DOES_NOT_EXIST); @@ -254,38 +272,50 @@ public void doExecute() { // check if the serverQuery has expired if (System.currentTimeMillis() > pendingServerQuery.getCreationTimestamp() + Constants.SERVER_QUERY_EXPIRATION_DELAY) { switch (serverQuery.getType().getId()) { - case ServerQuery.Type.DEVICE_DISCOVERY_QUERY_ID: + case DEVICE_DISCOVERY_QUERY_ID: serverResponse = Encoded.of(new Encoded[]{ Encoded.of(Constants.BROADCAST_UID), }); // return the broadcast deviceUid so we know it's not a real output finished = true; return; - case ServerQuery.Type.OWNED_DEVICE_DISCOVERY_QUERY_ID: + case OWNED_DEVICE_DISCOVERY_QUERY_ID: serverResponse = Encoded.of(new byte[0]); // return an empty byte array finished = true; return; - case ServerQuery.Type.PUT_USER_DATA_QUERY_ID: - case ServerQuery.Type.GET_GROUP_BLOB_QUERY_ID: - case ServerQuery.Type.PUT_GROUP_LOG_QUERY_ID: - case ServerQuery.Type.LOCK_GROUP_BLOB_QUERY_ID: + case PUT_USER_DATA_QUERY_ID: + case GET_GROUP_BLOB_QUERY_ID: + case PUT_GROUP_LOG_QUERY_ID: + case LOCK_GROUP_BLOB_QUERY_ID: serverResponse = null; finished = true; return; - case ServerQuery.Type.GET_USER_DATA_QUERY_ID: - case ServerQuery.Type.GET_KEYCLOAK_DATA_QUERY_ID: + case GET_USER_DATA_QUERY_ID: + case GET_KEYCLOAK_DATA_QUERY_ID: serverResponse = Encoded.of(""); // as if it was deleted from the server finished = true; return; - case ServerQuery.Type.CHECK_KEYCLOAK_REVOCATION_QUERY_ID: + case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: serverResponse = Encoded.of(true); // consider the user is not revoked (rationale: another protocol has probably been run since then, we do not want to delete the user) finished = true; return; - case ServerQuery.Type.CREATE_GROUP_BLOB_QUERY_ID: - case ServerQuery.Type.UPDATE_GROUP_BLOB_QUERY_ID: - case ServerQuery.Type.DELETE_GROUP_BLOB_QUERY_ID: + case CREATE_GROUP_BLOB_QUERY_ID: + case UPDATE_GROUP_BLOB_QUERY_ID: + case DELETE_GROUP_BLOB_QUERY_ID: serverResponse = Encoded.of(false); // consider the query failed finished = true; return; + case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: + case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: + case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: + case REGISTER_API_KEY_QUERY_ID: + case TRANSFER_SOURCE_QUERY_ID: + case TRANSFER_TARGET_QUERY_ID: + case TRANSFER_RELAY_QUERY_ID: + case TRANSFER_WAIT_QUERY_ID: + case TRANSFER_CLOSE_QUERY_ID: + default: + // do nothing for these + break; } } cancel(RFC_NETWORK_ERROR); @@ -1046,23 +1076,14 @@ class DeviceManagementServerMethod extends ServerQueryServerMethod { private final String server; private final Identity identity; private final byte[] token; - private final int queryType; - private final UID deviceUid; - private final EncryptedBytes encryptedDeviceName; - + private final ServerQuery.Type queryType; private Encoded serverResponse; - public DeviceManagementServerMethod(Identity identity, byte[] token, int queryType, UID deviceUid, EncryptedBytes encryptedDeviceName) { + public DeviceManagementServerMethod(Identity identity, byte[] token, ServerQuery.Type queryType) { this.server = identity.getServer(); this.identity = identity; this.token = token; this.queryType = queryType; - this.deviceUid = deviceUid; - this.encryptedDeviceName = encryptedDeviceName; - } - - public UID getDeviceUid() { - return deviceUid; } @Override @@ -1077,36 +1098,31 @@ protected String getServerMethod() { @Override protected byte[] getDataToSend() { - switch (queryType) { - case ServerQuery.Type.DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: { - return Encoded.of(new Encoded[]{ - Encoded.of(identity), - Encoded.of(token), - Encoded.of(new byte[]{(byte) 0x00}), - Encoded.of(deviceUid), - Encoded.of(encryptedDeviceName), - }).getBytes(); - } - case ServerQuery.Type.DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: { - return Encoded.of(new Encoded[]{ - Encoded.of(identity), - Encoded.of(token), - Encoded.of(new byte[]{(byte) 0x01}), - Encoded.of(deviceUid), - }).getBytes(); - } - case ServerQuery.Type.DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: { - return Encoded.of(new Encoded[]{ - Encoded.of(identity), - Encoded.of(token), - Encoded.of(new byte[]{(byte) 0x02}), - Encoded.of(deviceUid), - }).getBytes(); - } - default: { - // invalid query type - return new byte[0]; - } + if (queryType instanceof ServerQuery.DeviceManagementSetNicknameQuery) { + return Encoded.of(new Encoded[]{ + Encoded.of(identity), + Encoded.of(token), + Encoded.of(new byte[]{(byte) 0x00}), + Encoded.of(((ServerQuery.DeviceManagementSetNicknameQuery) queryType).deviceUid), + Encoded.of(((ServerQuery.DeviceManagementSetNicknameQuery) queryType).encryptedDeviceName), + }).getBytes(); + } else if (queryType instanceof ServerQuery.DeviceManagementDeactivateDeviceQuery) { + return Encoded.of(new Encoded[]{ + Encoded.of(identity), + Encoded.of(token), + Encoded.of(new byte[]{(byte) 0x01}), + Encoded.of(((ServerQuery.DeviceManagementDeactivateDeviceQuery) queryType).deviceUid), + }).getBytes(); + } else if (queryType instanceof ServerQuery.DeviceManagementSetUnexpiringDeviceQuery) { + return Encoded.of(new Encoded[]{ + Encoded.of(identity), + Encoded.of(token), + Encoded.of(new byte[]{(byte) 0x02}), + Encoded.of(((ServerQuery.DeviceManagementSetUnexpiringDeviceQuery) queryType).deviceUid), + }).getBytes(); + } else { + // invalid query type + return new byte[0]; } } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/StandaloneServerQueryOperation.java b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/StandaloneServerQueryOperation.java index 71a8f5ab..e16256ca 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/StandaloneServerQueryOperation.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/networkfetch/operations/StandaloneServerQueryOperation.java @@ -48,14 +48,34 @@ public void doExecute() { try { ServerQueryServerMethod serverMethod; switch (serverQuery.getType().getId()) { - case ServerQuery.Type.OWNED_DEVICE_DISCOVERY_QUERY_ID: { + case OWNED_DEVICE_DISCOVERY_QUERY_ID: { serverMethod = new OwnedDeviceDiscoveryServerMethod(serverQuery.getOwnedIdentity()); break; } - case ServerQuery.Type.REGISTER_API_KEY_QUERY_ID: { - serverMethod = new RegisterApiKeyServerMethod(serverQuery.getOwnedIdentity(), serverQuery.getType().getNonce(), serverQuery.getType().getDataUrl()); + case REGISTER_API_KEY_QUERY_ID: { + ServerQuery.RegisterApiKeyQuery registerApiKeyQuery = (ServerQuery.RegisterApiKeyQuery) serverQuery.getType(); + serverMethod = new RegisterApiKeyServerMethod(serverQuery.getOwnedIdentity(), registerApiKeyQuery.serverSessionToken, registerApiKeyQuery.apiKeyString); break; } + case DEVICE_DISCOVERY_QUERY_ID: + case PUT_USER_DATA_QUERY_ID: + case GET_USER_DATA_QUERY_ID: + case CHECK_KEYCLOAK_REVOCATION_QUERY_ID: + case CREATE_GROUP_BLOB_QUERY_ID: + case GET_GROUP_BLOB_QUERY_ID: + case LOCK_GROUP_BLOB_QUERY_ID: + case UPDATE_GROUP_BLOB_QUERY_ID: + case PUT_GROUP_LOG_QUERY_ID: + case DELETE_GROUP_BLOB_QUERY_ID: + case GET_KEYCLOAK_DATA_QUERY_ID: + case DEVICE_MANAGEMENT_SET_NICKNAME_QUERY_ID: + case DEVICE_MANAGEMENT_DEACTIVATE_DEVICE_QUERY_ID: + case DEVICE_MANAGEMENT_SET_UNEXPIRING_DEVICE_QUERY_ID: + case TRANSFER_SOURCE_QUERY_ID: + case TRANSFER_TARGET_QUERY_ID: + case TRANSFER_RELAY_QUERY_ID: + case TRANSFER_WAIT_QUERY_ID: + case TRANSFER_CLOSE_QUERY_ID: default: { cancel(RFC_UNSUPPORTED_SERVER_QUERY_TYPE); return; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/ProtocolManager.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/ProtocolManager.java index f929d145..06d8c41e 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/ProtocolManager.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/ProtocolManager.java @@ -27,9 +27,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Objects; import io.olvid.engine.Logger; import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.crypto.Suite; +import io.olvid.engine.datatypes.Constants; import io.olvid.engine.datatypes.Identity; import io.olvid.engine.datatypes.NotificationListener; import io.olvid.engine.datatypes.Session; @@ -43,6 +46,12 @@ import io.olvid.engine.datatypes.containers.ProtocolReceivedMessage; import io.olvid.engine.datatypes.containers.ProtocolReceivedServerResponse; import io.olvid.engine.datatypes.containers.SendChannelInfo; +import io.olvid.engine.datatypes.key.asymmetric.EncryptionPrivateKey; +import io.olvid.engine.datatypes.key.asymmetric.EncryptionPublicKey; +import io.olvid.engine.datatypes.key.asymmetric.KeyPair; +import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationPrivateKey; +import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationPublicKey; +import io.olvid.engine.datatypes.key.symmetric.MACKey; import io.olvid.engine.datatypes.notifications.IdentityNotifications; import io.olvid.engine.engine.types.JsonGroupDetailsWithVersionAndPhoto; import io.olvid.engine.engine.types.JsonIdentityDetailsWithVersionAndPhoto; @@ -101,6 +110,7 @@ import io.olvid.engine.protocol.protocols.OwnedDeviceDiscoveryProtocol; import io.olvid.engine.protocol.protocols.OwnedDeviceManagementProtocol; import io.olvid.engine.protocol.protocols.OwnedIdentityDeletionProtocol; +import io.olvid.engine.protocol.protocols.OwnedIdentityTransferProtocol; import io.olvid.engine.protocol.protocols.SynchronizationProtocol; import io.olvid.engine.protocol.protocols.TrustEstablishmentWithMutualScanProtocol; import io.olvid.engine.protocol.protocols.TrustEstablishmentWithSasProtocol; @@ -113,6 +123,7 @@ public class ProtocolManager implements ProtocolDelegate, ProtocolStarterDelegat private ObvBackupAndSyncDelegate identityBackupAndSyncDelegate; private EncryptionForIdentityDelegate encryptionForIdentityDelegate; private NotificationPostingDelegate notificationPostingDelegate; + private NotificationListeningDelegate notificationListeningDelegate; private EngineOwnedIdentityCleanupDelegate engineOwnedIdentityCleanupDelegate; private PushNotificationDelegate pushNotificationDelegate; @@ -188,6 +199,9 @@ public void initialisationComplete() { // } // } + // delete all unfinished transfer instances + ProtocolInstance.deleteAllTransfer(protocolManagerSession); + protocolManagerSession.session.commit(); } catch (Exception e) { e.printStackTrace(); @@ -265,6 +279,7 @@ public void setDelegate(EncryptionForIdentityDelegate encryptionForIdentityDeleg } public void setDelegate(NotificationListeningDelegate notificationListeningDelegate) { + this.notificationListeningDelegate = notificationListeningDelegate; notificationListeningDelegate.addListener(IdentityNotifications.NOTIFICATION_NEW_CONTACT_DEVICE, newDeviceListener); notificationListeningDelegate.addListener(IdentityNotifications.NOTIFICATION_NEW_OWNED_DEVICE, newDeviceListener); notificationListeningDelegate.addListener(IdentityNotifications.NOTIFICATION_CONTACT_IDENTITY_DELETED, contactDeletedListener); @@ -441,7 +456,8 @@ public void abortProtocol(Session session, UID protocolInstanceUid, Identity own @Override public void process(Session session, ProtocolReceivedMessage message) throws Exception { - if (!identityDelegate.isOwnedIdentity(session, message.getOwnedIdentity())) { + if (!identityDelegate.isOwnedIdentity(session, message.getOwnedIdentity()) + && !Objects.equals(message.getOwnedIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { throw new Exception(); } GenericReceivedProtocolMessage genericReceivedProtocolMessage = GenericReceivedProtocolMessage.of(message); @@ -452,7 +468,8 @@ public void process(Session session, ProtocolReceivedMessage message) throws Exc @Override public void process(Session session, ProtocolReceivedDialogResponse message) throws Exception { - if (!identityDelegate.isOwnedIdentity(session, message.getToIdentity())) { + if (!identityDelegate.isOwnedIdentity(session, message.getToIdentity()) + && !Objects.equals(message.getToIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { throw new Exception(); } GenericReceivedProtocolMessage genericReceivedProtocolMessage = GenericReceivedProtocolMessage.of(message); @@ -463,7 +480,8 @@ public void process(Session session, ProtocolReceivedDialogResponse message) thr @Override public void process(Session session, ProtocolReceivedServerResponse message) throws Exception { - if (!identityDelegate.isOwnedIdentity(session, message.getToIdentity())) { + if (!identityDelegate.isOwnedIdentity(session, message.getToIdentity()) + && !Objects.equals(message.getToIdentity().getServer(), Constants.EPHEMERAL_IDENTITY_SERVER)) { throw new Exception(); } GenericReceivedProtocolMessage genericReceivedProtocolMessage = GenericReceivedProtocolMessage.of(message); @@ -487,11 +505,11 @@ public ProtocolManagerSession getSession() throws SQLException { if (createSessionDelegate == null) { throw new SQLException("No CreateSessionDelegate was set in ChannelManager."); } - return new ProtocolManagerSession(createSessionDelegate.getSession(), channelDelegate, identityDelegate, encryptionForIdentityDelegate, protocolStepCoordinator, this, this, notificationPostingDelegate, engineOwnedIdentityCleanupDelegate, pushNotificationDelegate, engineBaseDirectory, identityBackupAndSyncDelegate, appBackupAndSyncDelegate); + return new ProtocolManagerSession(createSessionDelegate.getSession(), channelDelegate, identityDelegate, encryptionForIdentityDelegate, protocolStepCoordinator, this, this, notificationPostingDelegate, notificationListeningDelegate, engineOwnedIdentityCleanupDelegate, pushNotificationDelegate, engineBaseDirectory, identityBackupAndSyncDelegate, appBackupAndSyncDelegate); } private ProtocolManagerSession wrapSession(Session session) { - return new ProtocolManagerSession(session, channelDelegate, identityDelegate, encryptionForIdentityDelegate, protocolStepCoordinator, this, this, notificationPostingDelegate, engineOwnedIdentityCleanupDelegate, pushNotificationDelegate, engineBaseDirectory, identityBackupAndSyncDelegate, appBackupAndSyncDelegate); + return new ProtocolManagerSession(session, channelDelegate, identityDelegate, encryptionForIdentityDelegate, protocolStepCoordinator, this, this, notificationPostingDelegate, notificationListeningDelegate, engineOwnedIdentityCleanupDelegate, pushNotificationDelegate, engineBaseDirectory, identityBackupAndSyncDelegate, appBackupAndSyncDelegate); } // endregion @@ -928,6 +946,14 @@ public void createOrUpdateKeycloakGroupV2(Session session, Identity ownedIdentit } + @Override + public void processDeviceManagementRequest(Identity ownedIdentity, ObvDeviceManagementRequest deviceManagementRequest) throws Exception { + try (ProtocolManagerSession protocolManagerSession = getSession()) { + processDeviceManagementRequest(protocolManagerSession.session, ownedIdentity, deviceManagementRequest); + protocolManagerSession.session.commit(); + } + } + @Override public void processDeviceManagementRequest(Session session, Identity ownedIdentity, ObvDeviceManagementRequest deviceManagementRequest) throws Exception { if (session == null || ownedIdentity == null || deviceManagementRequest == null) { @@ -1428,6 +1454,52 @@ public void initiateSingleItemSync(Session session, Identity ownedIdentity, ObvS // } // } + @Override + public void initiateOwnedIdentityTransferProtocolOnSourceDevice(Identity ownedIdentity) throws Exception { + if (ownedIdentity == null) { + throw new Exception(); + } + + try (ProtocolManagerSession protocolManagerSession = getSession()) { + UID protocolInstanceUid = new UID(prng); + + CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createLocalChannelInfo(ownedIdentity), + ConcreteProtocol.OWNED_IDENTITY_TRANSFER_PROTOCOL_ID, + protocolInstanceUid, + false); + + ChannelMessageToSend message = new OwnedIdentityTransferProtocol.InitiateTransferOnSourceDeviceMessage(coreProtocolMessage).generateChannelProtocolMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, message, prng); + protocolManagerSession.session.commit(); + } + } + + @Override + public void initiateOwnedIdentityTransferProtocolOnTargetDevice(String deviceName) throws Exception { + KeyPair serverAuthKeyPair = Suite.generateServerAuthenticationKeyPair(null, prng); + KeyPair encryptionKeyPair = Suite.generateEncryptionKeyPair(null, prng); + if (serverAuthKeyPair == null || encryptionKeyPair == null) { + throw new Exception(); + } + MACKey macKey = Suite.getDefaultMAC(0).generateKey(prng); + Identity ephemeralIdentity = new Identity(Constants.EPHEMERAL_IDENTITY_SERVER, (ServerAuthenticationPublicKey) serverAuthKeyPair.getPublicKey(), (EncryptionPublicKey) encryptionKeyPair.getPublicKey()); + + + try (ProtocolManagerSession protocolManagerSession = getSession()) { + UID protocolInstanceUid = new UID(prng); + + CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createLocalChannelInfo(ephemeralIdentity), + ConcreteProtocol.OWNED_IDENTITY_TRANSFER_PROTOCOL_ID, + protocolInstanceUid, + false); + + ChannelMessageToSend message = new OwnedIdentityTransferProtocol.InitiateTransferOnTargetDeviceMessage(coreProtocolMessage, deviceName, (ServerAuthenticationPrivateKey) serverAuthKeyPair.getPrivateKey(), (EncryptionPrivateKey) encryptionKeyPair.getPrivateKey(), macKey).generateChannelProtocolMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, message, prng); + protocolManagerSession.session.commit(); + } + } + + // endregion // region Implement FullRatchetProtocolStarterDelegate diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/coordinators/ProtocolStepCoordinator.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/coordinators/ProtocolStepCoordinator.java index 3acd1a37..984fe629 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/coordinators/ProtocolStepCoordinator.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/coordinators/ProtocolStepCoordinator.java @@ -70,6 +70,7 @@ public void initialQueueing() { try (ProtocolManagerSession protocolManagerSession = protocolManagerSessionFactory.getSession()) { // To improve: also cleanup protocol instances: implement a clean abort in each protocol, and call it when the protocol is stalled ReceivedMessage.deleteExpiredMessagesWithNoProtocol(protocolManagerSession); + ReceivedMessage.deleteAllTransfer(protocolManagerSession); ReceivedMessage[] receivedMessages = ReceivedMessage.getAll(protocolManagerSession); if (receivedMessages.length > 0) { @@ -78,6 +79,7 @@ public void initialQueueing() { queueNewProtocolOperation(receivedMessage.getUid()); } } + protocolManagerSession.session.commit(); } catch (Exception e) { e.printStackTrace(); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ProtocolInstance.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ProtocolInstance.java index 55285a26..c3d7dfb1 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ProtocolInstance.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ProtocolInstance.java @@ -36,6 +36,7 @@ import io.olvid.engine.encoder.DecodingException; import io.olvid.engine.encoder.Encoded; import io.olvid.engine.protocol.datatypes.ProtocolManagerSession; +import io.olvid.engine.protocol.protocol_engine.ConcreteProtocol; import io.olvid.engine.protocol.protocol_engine.ConcreteProtocolState; @@ -281,7 +282,12 @@ public static void deleteAllForOwnedIdentity(ProtocolManagerSession protocolMana } } - + public static void deleteAllTransfer(ProtocolManagerSession protocolManagerSession) throws SQLException { + try (PreparedStatement statement = protocolManagerSession.session.prepareStatement("DELETE FROM " + TABLE_NAME + " WHERE " + PROTOCOL_ID + " = ?;")) { + statement.setInt(1, ConcreteProtocol.OWNED_IDENTITY_TRANSFER_PROTOCOL_ID); + statement.executeUpdate(); + } + } @Override public void wasCommitted() { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ReceivedMessage.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ReceivedMessage.java index 6a48472b..bdd756bb 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ReceivedMessage.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/databases/ReceivedMessage.java @@ -40,6 +40,7 @@ import io.olvid.engine.encoder.Encoded; import io.olvid.engine.protocol.datatypes.GenericReceivedProtocolMessage; import io.olvid.engine.protocol.datatypes.ProtocolManagerSession; +import io.olvid.engine.protocol.protocol_engine.ConcreteProtocol; public class ReceivedMessage implements ObvDatabase { @@ -308,6 +309,13 @@ public static void deleteExpiredMessagesWithNoProtocol(ProtocolManagerSession pr } } + public static void deleteAllTransfer(ProtocolManagerSession protocolManagerSession) throws SQLException { + try (PreparedStatement statement = protocolManagerSession.session.prepareStatement("DELETE FROM " + TABLE_NAME + " WHERE " + PROTOCOL_ID + " = ?;")) { + statement.setInt(1, ConcreteProtocol.OWNED_IDENTITY_TRANSFER_PROTOCOL_ID); + statement.executeUpdate(); + } + } + // endregion // region getters diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolManagerSession.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolManagerSession.java index b2afec0d..1f765765 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolManagerSession.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolManagerSession.java @@ -28,6 +28,7 @@ import io.olvid.engine.metamanager.EncryptionForIdentityDelegate; import io.olvid.engine.metamanager.EngineOwnedIdentityCleanupDelegate; import io.olvid.engine.metamanager.IdentityDelegate; +import io.olvid.engine.metamanager.NotificationListeningDelegate; import io.olvid.engine.metamanager.NotificationPostingDelegate; import io.olvid.engine.metamanager.ProtocolDelegate; import io.olvid.engine.metamanager.PushNotificationDelegate; @@ -41,13 +42,14 @@ public class ProtocolManagerSession implements AutoCloseable { public final ProtocolStarterDelegate protocolStarterDelegate; public final ProtocolDelegate protocolDelegate; public final NotificationPostingDelegate notificationPostingDelegate; + public final NotificationListeningDelegate notificationListeningDelegate; public final EngineOwnedIdentityCleanupDelegate engineOwnedIdentityCleanupDelegate; public final PushNotificationDelegate pushNotificationDelegate; public final String engineBaseDirectory; public final ObvBackupAndSyncDelegate identityBackupAndSyncDelegate; public final ObvBackupAndSyncDelegate appBackupAndSyncDelegate; - public ProtocolManagerSession(Session session, ChannelDelegate channelDelegate, IdentityDelegate identityDelegate, EncryptionForIdentityDelegate encryptionForIdentityDelegate, ProtocolReceivedMessageProcessorDelegate protocolReceivedMessageProcessorDelegate, ProtocolStarterDelegate protocolStarterDelegate, ProtocolDelegate protocolDelegate, NotificationPostingDelegate notificationPostingDelegate, EngineOwnedIdentityCleanupDelegate engineOwnedIdentityCleanupDelegate, PushNotificationDelegate pushNotificationDelegate, String engineBaseDirectory, ObvBackupAndSyncDelegate identityBackupAndSyncDelegate, ObvBackupAndSyncDelegate appBackupAndSyncDelegate) { + public ProtocolManagerSession(Session session, ChannelDelegate channelDelegate, IdentityDelegate identityDelegate, EncryptionForIdentityDelegate encryptionForIdentityDelegate, ProtocolReceivedMessageProcessorDelegate protocolReceivedMessageProcessorDelegate, ProtocolStarterDelegate protocolStarterDelegate, ProtocolDelegate protocolDelegate, NotificationPostingDelegate notificationPostingDelegate, NotificationListeningDelegate notificationListeningDelegate, EngineOwnedIdentityCleanupDelegate engineOwnedIdentityCleanupDelegate, PushNotificationDelegate pushNotificationDelegate, String engineBaseDirectory, ObvBackupAndSyncDelegate identityBackupAndSyncDelegate, ObvBackupAndSyncDelegate appBackupAndSyncDelegate) { this.session = session; this.channelDelegate = channelDelegate; this.identityDelegate = identityDelegate; @@ -56,6 +58,7 @@ public ProtocolManagerSession(Session session, ChannelDelegate channelDelegate, this.protocolStarterDelegate = protocolStarterDelegate; this.protocolDelegate = protocolDelegate; this.notificationPostingDelegate = notificationPostingDelegate; + this.notificationListeningDelegate = notificationListeningDelegate; this.engineOwnedIdentityCleanupDelegate = engineOwnedIdentityCleanupDelegate; this.pushNotificationDelegate = pushNotificationDelegate; this.engineBaseDirectory = engineBaseDirectory; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolStarterDelegate.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolStarterDelegate.java index 4aaee452..05ba1abf 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolStarterDelegate.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/datatypes/ProtocolStarterDelegate.java @@ -53,6 +53,7 @@ public interface ProtocolStarterDelegate { void initiateGroupV2ReDownload(Identity ownedIdentity, GroupV2.Identifier groupIdentifier) throws Exception; void initiateGroupV2BatchKeysResend(Session session, Identity ownedIdentity, Identity contactIdentity, UID contactDeviceUid) throws Exception; void createOrUpdateKeycloakGroupV2(Session session, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, String serializedKeycloakGroupBlob) throws Exception; + void processDeviceManagementRequest(Identity ownedIdentity, ObvDeviceManagementRequest deviceManagementRequest) throws Exception; void processDeviceManagementRequest(Session session, Identity ownedIdentity, ObvDeviceManagementRequest deviceManagementRequest) throws Exception; void startIdentityDetailsPublicationProtocol(Session session, Identity ownedIdentity, int version) throws Exception; @@ -82,5 +83,8 @@ public interface ProtocolStarterDelegate { void initiateKeycloakGroupV2TargetedPing(Session session, Identity ownedIdentity, GroupV2.Identifier groupIdentifier, Identity contactIdentity) throws Exception; void initiateSingleItemSync(Session session, Identity ownedIdentity, ObvSyncAtom obvSyncAtom) throws Exception; -// void triggerOwnedDevicesSync(Session session, Identity ownedIdentity); + + // void triggerOwnedDevicesSync(Session session, Identity ownedIdentity); + void initiateOwnedIdentityTransferProtocolOnSourceDevice(Identity ownedIdentity) throws Exception; + void initiateOwnedIdentityTransferProtocolOnTargetDevice(String deviceName) throws Exception; } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ConcreteProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ConcreteProtocol.java index 24087d65..59e27da3 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ConcreteProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ConcreteProtocol.java @@ -53,6 +53,7 @@ import io.olvid.engine.protocol.protocols.OwnedDeviceDiscoveryProtocol; import io.olvid.engine.protocol.protocols.OwnedDeviceManagementProtocol; import io.olvid.engine.protocol.protocols.OwnedIdentityDeletionProtocol; +import io.olvid.engine.protocol.protocols.OwnedIdentityTransferProtocol; import io.olvid.engine.protocol.protocols.SynchronizationProtocol; import io.olvid.engine.protocol.protocols.TrustEstablishmentWithMutualScanProtocol; import io.olvid.engine.protocol.protocols.TrustEstablishmentWithSasProtocol; @@ -87,6 +88,7 @@ public abstract class ConcreteProtocol { public static final int KEYCLOAK_BINDING_AND_UNBINDING_PROTOCOL_ID = 23; public static final int OWNED_DEVICE_MANAGEMENT_PROTOCOL_ID = 24; public static final int SYNCHRONIZATION_PROTOCOL_ID = 25; + public static final int OWNED_IDENTITY_TRANSFER_PROTOCOL_ID = 26; // internal protocols, Android only public static final int LEGACY_KEYCLOAK_BINDING_AND_UNBINDING_PROTOCOL_ID = 1000; @@ -219,6 +221,8 @@ private static ConcreteProtocol getConcreteProtocol(ProtocolManagerSession proto return new OwnedDeviceManagementProtocol(protocolManagerSession, protocolInstanceUid, stateId, encodedState, ownedIdentity, prng, jsonObjectMapper); case SYNCHRONIZATION_PROTOCOL_ID: return new SynchronizationProtocol(protocolManagerSession, protocolInstanceUid, stateId, encodedState, ownedIdentity, prng, jsonObjectMapper); + case OWNED_IDENTITY_TRANSFER_PROTOCOL_ID: + return new OwnedIdentityTransferProtocol(protocolManagerSession, protocolInstanceUid, stateId, encodedState, ownedIdentity, prng, jsonObjectMapper); default: Logger.w("Unknown protocol id: " + protocolId); return null; diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ProtocolStep.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ProtocolStep.java index 9d5aa40e..1970fc35 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ProtocolStep.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocol_engine/ProtocolStep.java @@ -20,6 +20,8 @@ package io.olvid.engine.protocol.protocol_engine; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.Objects; import io.olvid.engine.Logger; @@ -78,6 +80,9 @@ public void doExecute() { public Identity getOwnedIdentity() { return protocol.getOwnedIdentity(); } + public ObjectMapper getJsonObjectMapper() { + return protocol.getJsonObjectMapper(); + } public ProtocolManagerSession getProtocolManagerSession() { return protocol.getProtocolManagerSession(); @@ -97,7 +102,7 @@ public int getProtocolId() { public abstract ConcreteProtocolState executeStep() throws Exception; - protected CoreProtocolMessage buildCoreProtocolMessage(SendChannelInfo sendChannelInfo) { + public CoreProtocolMessage buildCoreProtocolMessage(SendChannelInfo sendChannelInfo) { return new CoreProtocolMessage(sendChannelInfo, getProtocolId(), getProtocolInstanceUid(), false); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceCapabilitiesDiscoveryProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceCapabilitiesDiscoveryProtocol.java index d0a708c1..f9e0f257 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceCapabilitiesDiscoveryProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceCapabilitiesDiscoveryProtocol.java @@ -364,7 +364,7 @@ public AddOwnCapabilitiesAndSendThemToAllContactsAndOwnedDevicesStep(InitialProt public ConcreteProtocolState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); - boolean gainedOneToOneCapability = false; + boolean gainedOneToOneCapability; { // check whether the current device has different capabilities and update them diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceDiscoveryChildProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceDiscoveryChildProtocol.java index c6bfe202..dd07f334 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceDiscoveryChildProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DeviceDiscoveryChildProtocol.java @@ -260,7 +260,7 @@ public SendRequestStep(InitialProtocolState startState, InitialMessage receivedM public ServerRequestSentState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createDeviceDiscoveryQuery(receivedMessage.remoteIdentity))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.DeviceDiscoveryQuery(receivedMessage.remoteIdentity))); ChannelMessageToSend messageToSend = new ServerQueryMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); return new ServerRequestSentState(receivedMessage.remoteIdentity); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupPhotoChildProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupPhotoChildProtocol.java index 5e0b3a00..b7743825 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupPhotoChildProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupPhotoChildProtocol.java @@ -269,7 +269,7 @@ public DownloadingPhotoState executeStep() throws Exception { } UID photoServerLabel = new UID(jsonGroupDetailsWithVersionAndPhoto.getPhotoServerLabel()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetUserDataQuery(receivedMessage.groupInformation.groupOwnerIdentity, photoServerLabel))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetUserDataQuery(receivedMessage.groupInformation.groupOwnerIdentity, photoServerLabel))); ChannelMessageToSend messageToSend = new ServerGetPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); return new DownloadingPhotoState(receivedMessage.groupInformation); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupV2PhotoProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupV2PhotoProtocol.java index 2f55ebd3..12b108a1 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupV2PhotoProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadGroupV2PhotoProtocol.java @@ -264,13 +264,13 @@ public DownloadingPhotoState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); if (receivedMessage.groupIdentifier.category == GroupV2.Identifier.CATEGORY_KEYCLOAK) { - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetKeycloakDataQuery(receivedMessage.groupIdentifier.serverUrl, receivedMessage.serverPhotoInfo.serverPhotoLabel))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetKeycloakDataQuery(receivedMessage.groupIdentifier.serverUrl, receivedMessage.serverPhotoInfo.serverPhotoLabel))); ChannelMessageToSend messageToSend = new ServerGetPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); return new DownloadingPhotoState(receivedMessage.groupIdentifier, receivedMessage.serverPhotoInfo); } else { - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetUserDataQuery(receivedMessage.serverPhotoInfo.serverPhotoIdentity, receivedMessage.serverPhotoInfo.serverPhotoLabel))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetUserDataQuery(receivedMessage.serverPhotoInfo.serverPhotoIdentity, receivedMessage.serverPhotoInfo.serverPhotoLabel))); ChannelMessageToSend messageToSend = new ServerGetPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadIdentityPhotoChildProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadIdentityPhotoChildProtocol.java index 195447bf..77b8a51a 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadIdentityPhotoChildProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/DownloadIdentityPhotoChildProtocol.java @@ -276,7 +276,7 @@ public DownloadingPhotoState executeStep() throws Exception { } UID photoServerLabel = new UID(jsonIdentityDetailsWithVersionAndPhoto.getPhotoServerLabel()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetUserDataQuery(receivedMessage.contactIdentity, photoServerLabel))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetUserDataQuery(receivedMessage.contactIdentity, photoServerLabel))); ChannelMessageToSend messageToSend = new ServerGetPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); return new DownloadingPhotoState(receivedMessage.contactIdentity, receivedMessage.jsonIdentityDetailsWithVersionAndPhoto); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupManagementProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupManagementProtocol.java index 0acc3f8a..11cfb537 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupManagementProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupManagementProtocol.java @@ -838,7 +838,7 @@ public ConcreteProtocolState executeStep() throws Exception { // store the label and key in the details protocolManagerSession.identityDelegate.setOwnedGroupDetailsServerLabelAndKey(protocolManagerSession.session, getOwnedIdentity(), groupInformation.getGroupOwnerAndUid(), publishedDetails.getVersion(), photoServerLabel, photoServerKey); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutUserDataQuery(getOwnedIdentity(), photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutUserDataQuery(getOwnedIdentity(), photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); ChannelMessageToSend messageToSend = new UploadGroupPhotoMessage(coreProtocolMessage, groupInformation).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } catch (Exception e) { @@ -994,7 +994,7 @@ public ConcreteProtocolState executeStep() throws Exception { // store the label and key in the details protocolManagerSession.identityDelegate.setOwnedGroupDetailsServerLabelAndKey(protocolManagerSession.session, getOwnedIdentity(), groupInformation.getGroupOwnerAndUid(), publishedDetails.getVersion(), photoServerLabel, photoServerKey); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutUserDataQuery(getOwnedIdentity(), photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutUserDataQuery(getOwnedIdentity(), photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); ChannelMessageToSend messageToSend = new UploadGroupPhotoMessage(coreProtocolMessage, groupInformationWithKeyAndLabel).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupsV2Protocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupsV2Protocol.java index 9e44938c..b9eeac82 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupsV2Protocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/GroupsV2Protocol.java @@ -1865,7 +1865,7 @@ public ConcreteProtocolState executeStep() throws Exception { String photoUrl = protocolManagerSession.identityDelegate.getGroupV2PhotoUrl(protocolManagerSession.session, getOwnedIdentity(), groupIdentifier); if (photoUrl != null) { - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutUserDataQuery(getOwnedIdentity(), serverBlob.serverPhotoInfo.serverPhotoLabel, photoUrl, serverBlob.serverPhotoInfo.serverPhotoKey))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutUserDataQuery(getOwnedIdentity(), serverBlob.serverPhotoInfo.serverPhotoLabel, photoUrl, serverBlob.serverPhotoInfo.serverPhotoKey))); ChannelMessageToSend messageToSend = new UploadGroupPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -1875,7 +1875,7 @@ public ConcreteProtocolState executeStep() throws Exception { { // upload the encrypted blob - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createCreateGroupBlobQuery(groupIdentifier, Encoded.of(groupAdminServerAuthenticationKeyPair.getPublicKey()), encryptedBlob))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.CreateGroupBlobQuery(groupIdentifier, Encoded.of(groupAdminServerAuthenticationKeyPair.getPublicKey()), encryptedBlob))); ChannelMessageToSend messageToSend = new UploadGroupBlobMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -2011,7 +2011,7 @@ public ConcreteProtocolState executeStep() throws Exception { // delete the group from the server byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_DELETE_ON_SERVER, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createDeleteGroupBlobQuery(startState.groupIdentifier, signature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.DeleteGroupBlobQuery(startState.groupIdentifier, signature))); ChannelMessageToSend messageToSend = new DeleteGroupBlobFromServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); @@ -2437,7 +2437,7 @@ public ConcreteProtocolState executeStep() throws Exception { byte[] serverQueryNonce = getPrng().bytes(16); { // run the server query to download the server blob - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetGroupBlobQuery(groupIdentifier, serverQueryNonce))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetGroupBlobQuery(groupIdentifier, serverQueryNonce))); ChannelMessageToSend messageToSend = new DownloadGroupBlobMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -2802,7 +2802,10 @@ public ConcreteProtocolState executeStep() throws Exception { { // check the message is not a replay if (GroupV2SignatureReceived.exists(protocolManagerSession, getOwnedIdentity(), receivedMessage.signature)) { - Logger.w("Received a group join ping with a known signature"); + if (propagationNeeded) { + // do not log signature replays for propagated messages, they are normal + Logger.i("Received a group join ping with a known signature"); + } return new FinalState(); } } @@ -3099,7 +3102,7 @@ public ConcreteProtocolState executeStep() throws Exception { getOwnedIdentity(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutGroupLogQuery(groupIdentifier, leaveSignature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutGroupLogQuery(groupIdentifier, leaveSignature))); ChannelMessageToSend messageToSend = new PutGroupLogOnServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); @@ -3278,7 +3281,7 @@ public ConcreteProtocolState executeStep() throws Exception { byte[] serverQueryNonce = getPrng().bytes(16); { // run the server query to download the server blob - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetGroupBlobQuery(groupIdentifier, serverQueryNonce))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetGroupBlobQuery(groupIdentifier, serverQueryNonce))); ChannelMessageToSend messageToSend = new DownloadGroupBlobMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -3331,7 +3334,7 @@ public ConcreteProtocolState executeStep() throws Exception { byte[] serverQueryNonce = getPrng().bytes(16); { // run the server query to re-download the server blob - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createGetGroupBlobQuery(groupIdentifier, serverQueryNonce))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.GetGroupBlobQuery(groupIdentifier, serverQueryNonce))); ChannelMessageToSend messageToSend = new DownloadGroupBlobMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -3401,7 +3404,7 @@ public ConcreteProtocolState executeStep() throws Exception { { byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_LOCK_ON_SERVER, lockNonce, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createBlobLockQuery(receivedMessage.groupIdentifier, lockNonce, signature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.LockGroupBlobQuery(receivedMessage.groupIdentifier, lockNonce, signature))); ChannelMessageToSend messageToSend = new RequestLockMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -3807,7 +3810,7 @@ public ConcreteProtocolState executeStep() throws Exception { byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_UPDATE_ON_SERVER, dataToSign, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createUpdateGroupBlobQuery(startState.groupIdentifier, startState.lockNonce, encryptedBlob, encodedPublicKey, signature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.UpdateGroupBlobQuery(startState.groupIdentifier, startState.lockNonce, encryptedBlob, encodedPublicKey, signature))); ChannelMessageToSend messageToSend = new UploadGroupBlobMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -3872,7 +3875,7 @@ public ConcreteProtocolState executeStep() throws Exception { { byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_LOCK_ON_SERVER, lockNonce, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createBlobLockQuery(startState.groupIdentifier, lockNonce, signature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.LockGroupBlobQuery(startState.groupIdentifier, lockNonce, signature))); ChannelMessageToSend messageToSend = new RequestLockMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -3889,7 +3892,7 @@ public ConcreteProtocolState executeStep() throws Exception { protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } else { // upload the group photo if needed - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutUserDataQuery(getOwnedIdentity(), startState.updatedBlob.serverPhotoInfo.serverPhotoLabel, startState.absolutePhotoUrlToUpload, startState.updatedBlob.serverPhotoInfo.serverPhotoKey))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutUserDataQuery(getOwnedIdentity(), startState.updatedBlob.serverPhotoInfo.serverPhotoLabel, startState.absolutePhotoUrlToUpload, startState.updatedBlob.serverPhotoInfo.serverPhotoKey))); ChannelMessageToSend messageToSend = new UploadGroupPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -4348,7 +4351,7 @@ public ConcreteProtocolState executeStep() throws Exception { getOwnedIdentity(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutGroupLogQuery(groupIdentifier, leaveSignature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutGroupLogQuery(groupIdentifier, leaveSignature))); ChannelMessageToSend messageToSend = new PutGroupLogOnServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); @@ -4484,7 +4487,7 @@ public ConcreteProtocolState executeStep() throws Exception { if (!propagated) { // delete the group from the server byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_DELETE_ON_SERVER, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createDeleteGroupBlobQuery(this.groupIdentifier, signature))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.DeleteGroupBlobQuery(this.groupIdentifier, signature))); ChannelMessageToSend messageToSend = new DeleteGroupBlobFromServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/IdentityDetailsPublicationProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/IdentityDetailsPublicationProtocol.java index 90435266..21e42f2f 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/IdentityDetailsPublicationProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/IdentityDetailsPublicationProtocol.java @@ -356,7 +356,7 @@ public ConcreteProtocolState executeStep() throws Exception { // store the label and key in the details protocolManagerSession.identityDelegate.setOwnedIdentityDetailsServerLabelAndKey(protocolManagerSession.session, ownedIdentity, publishedDetails.getVersion(), photoServerLabel, photoServerKey); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(ownedIdentity, ServerQuery.Type.createPutUserDataQuery(ownedIdentity, photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(ownedIdentity, new ServerQuery.PutUserDataQuery(ownedIdentity, photoServerLabel, publishedDetails.getPhotoUrl(), photoServerKey))); ChannelMessageToSend messageToSend = new ServerPutPhotoMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/KeycloakContactAdditionProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/KeycloakContactAdditionProtocol.java index 76cfdf42..bf5ab4c0 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/KeycloakContactAdditionProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/KeycloakContactAdditionProtocol.java @@ -729,7 +729,7 @@ public ConcreteProtocolState executeStep() throws Exception { // perform the server query to check for revoked identity /////// - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createCheckKeycloakRevocationServerQuery(receivedMessage.keycloakServerUrl, receivedMessage.signedContactDetails))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.CheckKeycloakRevocationQuery(receivedMessage.keycloakServerUrl, receivedMessage.signedContactDetails))); ChannelMessageToSend messageToSend = new CheckForRevocationServerQueryMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceDiscoveryProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceDiscoveryProtocol.java index ae64470e..109fea44 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceDiscoveryProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceDiscoveryProtocol.java @@ -273,7 +273,7 @@ public SendRequestStep(InitialProtocolState startState, TriggerOwnedDeviceDiscov public ConcreteProtocolState executeStep() throws Exception { ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createOwnedDeviceDiscoveryQuery(getOwnedIdentity()))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.OwnedDeviceDiscoveryQuery(getOwnedIdentity()))); ChannelMessageToSend messageToSend = new ServerQueryMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceManagementProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceManagementProtocol.java index 6bfd098c..f4d47114 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceManagementProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedDeviceManagementProtocol.java @@ -106,6 +106,7 @@ public Encoded encode() { } public static class ResponseProcessedState extends ConcreteProtocolState { + @SuppressWarnings("unused") public ResponseProcessedState(Encoded encodedState) throws Exception { super(RESPONSE_PROCESSED_STATE_ID); Encoded[] list = encodedState.decodeList(); @@ -252,15 +253,15 @@ public ConcreteProtocolState executeStep() throws Exception { Suite.getDefaultPRNGService(0)); UID currentDeviceUid = protocolManagerSession.identityDelegate.getCurrentDeviceUidOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity()); - serverQueryType = ServerQuery.Type.createDeviceManagementSetNicknameQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid(), encryptedDeviceName, currentDeviceUid.equals(receivedMessage.deviceManagementRequest.getDeviceUid())); + serverQueryType = new ServerQuery.DeviceManagementSetNicknameQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid(), encryptedDeviceName, currentDeviceUid.equals(receivedMessage.deviceManagementRequest.getDeviceUid())); break; } case ObvDeviceManagementRequest.ACTION_DEACTIVATE_DEVICE: { - serverQueryType = ServerQuery.Type.createDeviceManagementDeactivateDeviceQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid()); + serverQueryType = new ServerQuery.DeviceManagementDeactivateDeviceQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid()); break; } case ObvDeviceManagementRequest.ACTION_SET_UNEXPIRING_DEVICE: { - serverQueryType = ServerQuery.Type.createDeviceManagementSetUnexpiringDeviceQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid()); + serverQueryType = new ServerQuery.DeviceManagementSetUnexpiringDeviceQuery(getOwnedIdentity(), receivedMessage.deviceManagementRequest.getDeviceUid()); break; } default: { @@ -280,6 +281,7 @@ public ConcreteProtocolState executeStep() throws Exception { public static class ProcessResponseStateStep extends ProtocolStep { @SuppressWarnings({"unused", "FieldCanBeLocal"}) private final RequestSentState startState; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) private final ServerQueryMessage receivedMessage; public ProcessResponseStateStep(RequestSentState startState, ServerQueryMessage receivedMessage, OwnedDeviceManagementProtocol protocol) throws Exception { diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityDeletionProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityDeletionProtocol.java index 49c221e5..34899d8c 100644 --- a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityDeletionProtocol.java +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityDeletionProtocol.java @@ -365,7 +365,7 @@ public ConcreteProtocolState executeStep() throws Exception { { UID currentDeviceUid = protocolManagerSession.identityDelegate.getCurrentDeviceUidOfOwnedIdentity(protocolManagerSession.session, getOwnedIdentity()); - CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createDeviceManagementDeactivateDeviceQuery(getOwnedIdentity(), currentDeviceUid))); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.DeviceManagementDeactivateDeviceQuery(getOwnedIdentity(), currentDeviceUid))); ChannelMessageToSend messageToSend = new DeactivateCurrentDeviceServerQueryMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -572,7 +572,7 @@ public ConcreteProtocolState executeStep() throws Exception { GroupV2.BlobKeys blobKeys = protocolManagerSession.identityDelegate.getGroupV2BlobKeys(protocolManagerSession.session, getOwnedIdentity(), groupV2.groupIdentifier); { byte[] signature = Signature.sign(Constants.SignatureContext.GROUP_DELETE_ON_SERVER, blobKeys.groupAdminServerAuthenticationPrivateKey.getSignaturePrivateKey(), getPrng()); - CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createDeleteGroupBlobQuery(groupV2.groupIdentifier, signature)), ConcreteProtocol.GROUPS_V2_PROTOCOL_ID, new UID(getPrng()), false); + CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.DeleteGroupBlobQuery(groupV2.groupIdentifier, signature)), ConcreteProtocol.GROUPS_V2_PROTOCOL_ID, new UID(getPrng()), false); ChannelMessageToSend messageToSend = new GroupsV2Protocol.DeleteGroupBlobFromServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } @@ -613,7 +613,7 @@ public ConcreteProtocolState executeStep() throws Exception { getOwnedIdentity(), getPrng()); - CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), ServerQuery.Type.createPutGroupLogQuery(groupV2.groupIdentifier, leaveSignature)), ConcreteProtocol.GROUPS_V2_PROTOCOL_ID, new UID(getPrng()), false); + CoreProtocolMessage coreProtocolMessage = new CoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.PutGroupLogQuery(groupV2.groupIdentifier, leaveSignature)), ConcreteProtocol.GROUPS_V2_PROTOCOL_ID, new UID(getPrng()), false); ChannelMessageToSend messageToSend = new GroupsV2Protocol.PutGroupLogOnServerMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); } diff --git a/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityTransferProtocol.java b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityTransferProtocol.java new file mode 100644 index 00000000..f9077213 --- /dev/null +++ b/obv_engine/engine/src/main/java/io/olvid/engine/protocol/protocols/OwnedIdentityTransferProtocol.java @@ -0,0 +1,1746 @@ +/* + * Olvid for Android + * Copyright © 2019-2023 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.protocol.protocols; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import io.olvid.engine.Logger; +import io.olvid.engine.crypto.Commitment; +import io.olvid.engine.crypto.Hash; +import io.olvid.engine.crypto.MAC; +import io.olvid.engine.crypto.PRNGService; +import io.olvid.engine.crypto.SAS; +import io.olvid.engine.crypto.Suite; +import io.olvid.engine.datatypes.Constants; +import io.olvid.engine.datatypes.EncryptedBytes; +import io.olvid.engine.datatypes.Identity; +import io.olvid.engine.datatypes.NotificationListener; +import io.olvid.engine.datatypes.Seed; +import io.olvid.engine.datatypes.UID; +import io.olvid.engine.datatypes.containers.ChannelMessageToSend; +import io.olvid.engine.datatypes.containers.DialogType; +import io.olvid.engine.datatypes.containers.ReceptionChannelInfo; +import io.olvid.engine.datatypes.containers.SendChannelInfo; +import io.olvid.engine.datatypes.containers.ServerQuery; +import io.olvid.engine.datatypes.key.asymmetric.EncryptionPrivateKey; +import io.olvid.engine.datatypes.key.asymmetric.ServerAuthenticationPrivateKey; +import io.olvid.engine.datatypes.key.symmetric.MACKey; +import io.olvid.engine.datatypes.notifications.DownloadNotifications; +import io.olvid.engine.datatypes.notifications.ProtocolNotifications; +import io.olvid.engine.encoder.DecodingException; +import io.olvid.engine.encoder.Encoded; +import io.olvid.engine.engine.types.ObvDeviceManagementRequest; +import io.olvid.engine.engine.types.ObvTransferStep; +import io.olvid.engine.engine.types.identities.ObvIdentity; +import io.olvid.engine.engine.types.sync.ObvBackupAndSyncDelegate; +import io.olvid.engine.engine.types.sync.ObvSyncSnapshot; +import io.olvid.engine.engine.types.sync.ObvSyncSnapshotNode; +import io.olvid.engine.identity.databases.sync.IdentityManagerSyncSnapshot; +import io.olvid.engine.protocol.databases.ReceivedMessage; +import io.olvid.engine.protocol.datatypes.CoreProtocolMessage; +import io.olvid.engine.protocol.datatypes.ProtocolManagerSession; +import io.olvid.engine.protocol.protocol_engine.ConcreteProtocol; +import io.olvid.engine.protocol.protocol_engine.ConcreteProtocolMessage; +import io.olvid.engine.protocol.protocol_engine.ConcreteProtocolState; +import io.olvid.engine.protocol.protocol_engine.EmptyProtocolMessage; +import io.olvid.engine.protocol.protocol_engine.InitialProtocolState; +import io.olvid.engine.protocol.protocol_engine.OneWayDialogProtocolMessage; +import io.olvid.engine.protocol.protocol_engine.ProtocolStep; + +public class OwnedIdentityTransferProtocol extends ConcreteProtocol { + public OwnedIdentityTransferProtocol(ProtocolManagerSession protocolManagerSession, UID protocolInstanceUid, int currentStateId, Encoded encodedCurrentState, Identity ownedIdentity, PRNGService prng, ObjectMapper jsonObjectMapper) throws Exception { + super(protocolManagerSession, protocolInstanceUid, currentStateId, encodedCurrentState, ownedIdentity, prng, jsonObjectMapper); + } + + @Override + public int getProtocolId() { + return OWNED_IDENTITY_TRANSFER_PROTOCOL_ID; + } + + // region States + + public static final int SOURCE_WAITING_FOR_SESSION_NUMBER_STATE_ID = 1; + public static final int SOURCE_WAITING_FOR_TARGET_CONNECTION_STATE_ID = 2; + public static final int TARGET_WAITING_FOR_SESSION_NUMBER_STATE_ID = 3; + public static final int TARGET_WAITING_FOR_TRANSFERRED_IDENTITY_STATE_ID = 4; + public static final int SOURCE_WAITING_FOR_TARGET_SEED_STATE_ID = 5; + public static final int TARGET_WAITING_FOR_DECOMMITMENT_STATE_ID = 6; + public static final int SOURCE_WAITING_FOR_SAS_INPUT_STATE_ID = 7; + public static final int TARGET_WAITING_FOR_SNAPSHOT_STATE_ID = 8; + public static final int FINAL_STATE_ID = 99; + + @Override + public int[] getFinalStateIds() { + return new int[]{FINAL_STATE_ID}; + } + + @Override + protected Class getStateClass(int stateId) { + switch (stateId) { + case INITIAL_STATE_ID: + return InitialProtocolState.class; + case SOURCE_WAITING_FOR_SESSION_NUMBER_STATE_ID: + return SourceWaitingForSessionNumberState.class; + case SOURCE_WAITING_FOR_TARGET_CONNECTION_STATE_ID: + return SourceWaitingForTargetConnectionState.class; + case TARGET_WAITING_FOR_SESSION_NUMBER_STATE_ID: + return TargetWaitingForSessionNumberState.class; + case TARGET_WAITING_FOR_TRANSFERRED_IDENTITY_STATE_ID: + return TargetWaitingForTransferredIdentityState.class; + case SOURCE_WAITING_FOR_TARGET_SEED_STATE_ID: + return SourceWaitingForTargetSeedState.class; + case TARGET_WAITING_FOR_DECOMMITMENT_STATE_ID: + return TargetWaitingForDecommitmentState.class; + case SOURCE_WAITING_FOR_SAS_INPUT_STATE_ID: + return SourceWaitingForSasInputState.class; + case TARGET_WAITING_FOR_SNAPSHOT_STATE_ID: + return TargetWaitingForSnapshotState.class; + case FINAL_STATE_ID: + return FinalState.class; + default: + return null; + } + } + + public static class SourceWaitingForSessionNumberState extends ConcreteProtocolState { + private final UUID dialogUuid; + protected SourceWaitingForSessionNumberState(UUID dialogUuid) { + super(SOURCE_WAITING_FOR_SESSION_NUMBER_STATE_ID); + this.dialogUuid = dialogUuid; + } + + @SuppressWarnings("unused") + public SourceWaitingForSessionNumberState(Encoded encodedState) throws Exception { + super(SOURCE_WAITING_FOR_SESSION_NUMBER_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 1) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + }); + } + } + + + public static class SourceWaitingForTargetConnectionState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String ownConnectionIdentifier; + protected SourceWaitingForTargetConnectionState(UUID dialogUuid, String ownConnectionIdentifier) { + super(SOURCE_WAITING_FOR_TARGET_CONNECTION_STATE_ID); + this.dialogUuid = dialogUuid; + this.ownConnectionIdentifier = ownConnectionIdentifier; + } + + @SuppressWarnings("unused") + public SourceWaitingForTargetConnectionState(Encoded encodedState) throws Exception { + super(SOURCE_WAITING_FOR_TARGET_CONNECTION_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 2) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.ownConnectionIdentifier = list[1].decodeString(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(ownConnectionIdentifier), + }); + } + } + + public static class TargetWaitingForSessionNumberState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String deviceName; + private final ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + private final EncryptionPrivateKey encryptionPrivateKey; + private final MACKey macKey; + + protected TargetWaitingForSessionNumberState(UUID dialogUuid, String deviceName, ServerAuthenticationPrivateKey serverAuthenticationPrivateKey, EncryptionPrivateKey encryptionPrivateKey, MACKey macKey) { + super(TARGET_WAITING_FOR_SESSION_NUMBER_STATE_ID); + this.dialogUuid = dialogUuid; + this.deviceName = deviceName; + this.serverAuthenticationPrivateKey = serverAuthenticationPrivateKey; + this.encryptionPrivateKey = encryptionPrivateKey; + this.macKey = macKey; + } + + @SuppressWarnings("unused") + public TargetWaitingForSessionNumberState(Encoded encodedState) throws Exception { + super(TARGET_WAITING_FOR_SESSION_NUMBER_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 5) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.deviceName = list[1].decodeString(); + this.serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) list[2].decodePrivateKey(); + this.encryptionPrivateKey = (EncryptionPrivateKey) list[3].decodePrivateKey(); + this.macKey = (MACKey) list[4].decodeSymmetricKey(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(deviceName), + Encoded.of(serverAuthenticationPrivateKey), + Encoded.of(encryptionPrivateKey), + Encoded.of(macKey), + }); + } + } + + + + public static class TargetWaitingForTransferredIdentityState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String deviceName; + private final ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + private final EncryptionPrivateKey encryptionPrivateKey; + private final MACKey macKey; + + protected TargetWaitingForTransferredIdentityState(UUID dialogUuid, String deviceName, ServerAuthenticationPrivateKey serverAuthenticationPrivateKey, EncryptionPrivateKey encryptionPrivateKey, MACKey macKey) { + super(TARGET_WAITING_FOR_TRANSFERRED_IDENTITY_STATE_ID); + this.dialogUuid = dialogUuid; + this.deviceName = deviceName; + this.serverAuthenticationPrivateKey = serverAuthenticationPrivateKey; + this.encryptionPrivateKey = encryptionPrivateKey; + this.macKey = macKey; + } + + @SuppressWarnings("unused") + public TargetWaitingForTransferredIdentityState(Encoded encodedState) throws Exception { + super(TARGET_WAITING_FOR_TRANSFERRED_IDENTITY_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 5) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.deviceName = list[1].decodeString(); + this.serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) list[2].decodePrivateKey(); + this.encryptionPrivateKey = (EncryptionPrivateKey) list[3].decodePrivateKey(); + this.macKey = (MACKey) list[4].decodeSymmetricKey(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(deviceName), + Encoded.of(serverAuthenticationPrivateKey), + Encoded.of(encryptionPrivateKey), + Encoded.of(macKey), + }); + } + } + + + public static class SourceWaitingForTargetSeedState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String otherConnectionIdentifier; + private final Identity ephemeralIdentity; + private final Seed seedSourceForSas; + private final byte[] decommitment; + + public SourceWaitingForTargetSeedState(UUID dialogUuid, String otherConnectionIdentifier, Identity ephemeralIdentity, Seed seedSourceForSas, byte[] decommitment) { + super(SOURCE_WAITING_FOR_TARGET_SEED_STATE_ID); + this.dialogUuid = dialogUuid; + this.otherConnectionIdentifier = otherConnectionIdentifier; + this.ephemeralIdentity = ephemeralIdentity; + this.seedSourceForSas = seedSourceForSas; + this.decommitment = decommitment; + } + + @SuppressWarnings("unused") + public SourceWaitingForTargetSeedState(Encoded encodedState) throws Exception { + super(SOURCE_WAITING_FOR_TARGET_SEED_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 5) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.otherConnectionIdentifier = list[1].decodeString(); + this.ephemeralIdentity = list[2].decodeIdentity(); + this.seedSourceForSas = list[3].decodeSeed(); + this.decommitment = list[4].decodeBytes(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(otherConnectionIdentifier), + Encoded.of(ephemeralIdentity), + Encoded.of(seedSourceForSas), + Encoded.of(decommitment), + }); + } + } + + + public static class TargetWaitingForDecommitmentState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String deviceName; + private final String otherConnectionIdentifier; + private final Identity transferredIdentity; + private final byte[] commitment; + private final Seed seedTargetForSas; + private final ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + private final EncryptionPrivateKey encryptionPrivateKey; + private final MACKey macKey; + + public TargetWaitingForDecommitmentState(UUID dialogUuid, String deviceName, String otherConnectionIdentifier, Identity transferredIdentity, byte[] commitment, Seed seedTargetForSas, ServerAuthenticationPrivateKey serverAuthenticationPrivateKey, EncryptionPrivateKey encryptionPrivateKey, MACKey macKey) { + super(TARGET_WAITING_FOR_DECOMMITMENT_STATE_ID); + this.dialogUuid = dialogUuid; + this.deviceName = deviceName; + this.otherConnectionIdentifier = otherConnectionIdentifier; + this.transferredIdentity = transferredIdentity; + this.commitment = commitment; + this.seedTargetForSas = seedTargetForSas; + this.serverAuthenticationPrivateKey = serverAuthenticationPrivateKey; + this.encryptionPrivateKey = encryptionPrivateKey; + this.macKey = macKey; + } + + @SuppressWarnings("unused") + public TargetWaitingForDecommitmentState(Encoded encodedState) throws Exception { + super(TARGET_WAITING_FOR_DECOMMITMENT_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 9) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.deviceName = list[1].decodeString(); + this.otherConnectionIdentifier = list[2].decodeString(); + this.transferredIdentity = list[3].decodeIdentity(); + this.commitment = list[4].decodeBytes(); + this.seedTargetForSas = list[5].decodeSeed(); + this.serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) list[6].decodePrivateKey(); + this.encryptionPrivateKey = (EncryptionPrivateKey) list[7].decodePrivateKey(); + this.macKey = (MACKey) list[8].decodeSymmetricKey(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(deviceName), + Encoded.of(otherConnectionIdentifier), + Encoded.of(transferredIdentity), + Encoded.of(commitment), + Encoded.of(seedTargetForSas), + Encoded.of(serverAuthenticationPrivateKey), + Encoded.of(encryptionPrivateKey), + Encoded.of(macKey), + }); + } + } + + + public static class SourceWaitingForSasInputState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String otherConnectionIdentifier; + private final String targetDeviceName; + private final Identity ephemeralIdentity; + private final String fullSas; + + public SourceWaitingForSasInputState(UUID dialogUuid, String otherConnectionIdentifier, String targetDeviceName, Identity ephemeralIdentity, String fullSas) { + super(SOURCE_WAITING_FOR_SAS_INPUT_STATE_ID); + this.dialogUuid = dialogUuid; + this.otherConnectionIdentifier = otherConnectionIdentifier; + this.targetDeviceName = targetDeviceName; + this.ephemeralIdentity = ephemeralIdentity; + this.fullSas = fullSas; + } + + @SuppressWarnings("unused") + public SourceWaitingForSasInputState(Encoded encodedState) throws Exception { + super(SOURCE_WAITING_FOR_SAS_INPUT_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 5) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.otherConnectionIdentifier = list[1].decodeString(); + this.targetDeviceName = list[2].decodeString(); + this.ephemeralIdentity = list[3].decodeIdentity(); + this.fullSas = list[4].decodeString(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(otherConnectionIdentifier), + Encoded.of(targetDeviceName), + Encoded.of(ephemeralIdentity), + Encoded.of(fullSas), + }); + } + } + + + public static class TargetWaitingForSnapshotState extends ConcreteProtocolState { + private final UUID dialogUuid; + private final String deviceName; + private final String otherConnectionIdentifier; + private final Identity transferredIdentity; + private final ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + private final EncryptionPrivateKey encryptionPrivateKey; + private final MACKey macKey; + + public TargetWaitingForSnapshotState(UUID dialogUuid, String deviceName, String otherConnectionIdentifier, Identity transferredIdentity, ServerAuthenticationPrivateKey serverAuthenticationPrivateKey, EncryptionPrivateKey encryptionPrivateKey, MACKey macKey) { + super(TARGET_WAITING_FOR_SNAPSHOT_STATE_ID); + this.dialogUuid = dialogUuid; + this.deviceName = deviceName; + this.otherConnectionIdentifier = otherConnectionIdentifier; + this.transferredIdentity = transferredIdentity; + this.serverAuthenticationPrivateKey = serverAuthenticationPrivateKey; + this.encryptionPrivateKey = encryptionPrivateKey; + this.macKey = macKey; + } + + @SuppressWarnings("unused") + public TargetWaitingForSnapshotState(Encoded encodedState) throws Exception { + super(TARGET_WAITING_FOR_SNAPSHOT_STATE_ID); + Encoded[] list = encodedState.decodeList(); + if (list.length != 7) { + throw new Exception(); + } + this.dialogUuid = list[0].decodeUuid(); + this.deviceName = list[1].decodeString(); + this.otherConnectionIdentifier = list[2].decodeString(); + this.transferredIdentity = list[3].decodeIdentity(); + this.serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) list[4].decodePrivateKey(); + this.encryptionPrivateKey = (EncryptionPrivateKey) list[5].decodePrivateKey(); + this.macKey = (MACKey) list[6].decodeSymmetricKey(); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[]{ + Encoded.of(dialogUuid), + Encoded.of(deviceName), + Encoded.of(otherConnectionIdentifier), + Encoded.of(transferredIdentity), + Encoded.of(serverAuthenticationPrivateKey), + Encoded.of(encryptionPrivateKey), + Encoded.of(macKey), + }); + } + } + + + public static class FinalState extends ConcreteProtocolState { + protected FinalState() { + super(FINAL_STATE_ID); + } + + @Override + public Encoded encode() { + return Encoded.of(new Encoded[0]); + } + } + + + // endregion + + + + // region Messages + + public static final int INITIATE_TRANSFER_ON_SOURCE_DEVICE_MESSAGE_ID = 0; + public static final int INITIATE_TRANSFER_ON_TARGET_DEVICE_MESSAGE_ID = 1; + public static final int SOURCE_GET_SESSION_NUMBER_MESSAGE_ID = 2; + public static final int ABORTABLE_ONE_WAY_DIALOG_MESSAGE_ID = 3; + public static final int SOURCE_WAIT_FOR_TARGET_CONNECTION_MESSAGE_ID = 4; + public static final int TARGET_GET_SESSION_NUMBER_MESSAGE_ID = 5; + public static final int TARGET_SEND_EPHEMERAL_IDENTITY_MESSAGE_ID = 6; + public static final int SOURCE_SEND_COMMITMENT_MESSAGE_ID = 7; + public static final int TARGET_SEED_MESSAGE_ID = 8; + public static final int SOURCE_SAS_INPUT_MESSAGE_ID = 9; + public static final int SOURCE_DECOMMITMENT_MESSAGE_ID = 10; + public static final int TARGET_WAIT_FOR_SNAPSHOT_MESSAGE_ID = 11; + public static final int SOURCE_SNAPSHOT_MESSAGE_ID = 12; + + + + + @Override + protected Class getMessageClass(int protocolMessageId) { + switch (protocolMessageId) { + case INITIATE_TRANSFER_ON_SOURCE_DEVICE_MESSAGE_ID: + return InitiateTransferOnSourceDeviceMessage.class; + case INITIATE_TRANSFER_ON_TARGET_DEVICE_MESSAGE_ID: + return InitiateTransferOnTargetDeviceMessage.class; + case SOURCE_GET_SESSION_NUMBER_MESSAGE_ID: + return SourceGetSessionNumberMessage.class; + case ABORTABLE_ONE_WAY_DIALOG_MESSAGE_ID: + return AbortableOneWayDialogMessage.class; + case SOURCE_WAIT_FOR_TARGET_CONNECTION_MESSAGE_ID: + return SourceWaitForTargetConnectionMessage.class; + case TARGET_GET_SESSION_NUMBER_MESSAGE_ID: + return TargetGetSessionNumberMessage.class; + case TARGET_SEND_EPHEMERAL_IDENTITY_MESSAGE_ID: + return TargetSendEphemeralIdentityMessage.class; + case SOURCE_SEND_COMMITMENT_MESSAGE_ID: + return SourceSendCommitmentMessage.class; + case TARGET_SEED_MESSAGE_ID: + return TargetSeedMessage.class; + case SOURCE_SAS_INPUT_MESSAGE_ID: + return SourceSasInputMessage.class; + case SOURCE_DECOMMITMENT_MESSAGE_ID: + return SourceDecommitmentMessage.class; + case TARGET_WAIT_FOR_SNAPSHOT_MESSAGE_ID: + return TargetWaitForSnapshotMessage.class; + case SOURCE_SNAPSHOT_MESSAGE_ID: + return SourceSnapshotMessage.class; + default: + return null; + } + } + + public static class InitiateTransferOnSourceDeviceMessage extends ConcreteProtocolMessage { + public InitiateTransferOnSourceDeviceMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public InitiateTransferOnSourceDeviceMessage(ReceivedMessage receivedMessage) throws Exception { + super(new CoreProtocolMessage(receivedMessage)); + Encoded[] list = receivedMessage.getInputs(); + if (list.length != 0) { + throw new Exception(); + } + } + + @Override + public int getProtocolMessageId() { + return INITIATE_TRANSFER_ON_SOURCE_DEVICE_MESSAGE_ID; + } + + @Override + public Encoded[] getInputs() { + return new Encoded[0]; + } + } + + public static class InitiateTransferOnTargetDeviceMessage extends ConcreteProtocolMessage { + private final String deviceName; + private final ServerAuthenticationPrivateKey serverAuthenticationPrivateKey; + private final EncryptionPrivateKey encryptionPrivateKey; + private final MACKey macKey; + + public InitiateTransferOnTargetDeviceMessage(CoreProtocolMessage coreProtocolMessage, String deviceName, ServerAuthenticationPrivateKey serverAuthenticationPrivateKey, EncryptionPrivateKey encryptionPrivateKey, MACKey macKey) { + super(coreProtocolMessage); + this.deviceName = deviceName; + this.serverAuthenticationPrivateKey = serverAuthenticationPrivateKey; + this.encryptionPrivateKey = encryptionPrivateKey; + this.macKey = macKey; + } + + @SuppressWarnings("unused") + public InitiateTransferOnTargetDeviceMessage(ReceivedMessage receivedMessage) throws Exception { + super(new CoreProtocolMessage(receivedMessage)); + Encoded[] list = receivedMessage.getInputs(); + if (list.length != 4) { + throw new Exception(); + } + this.deviceName = list[0].decodeString(); + this.serverAuthenticationPrivateKey = (ServerAuthenticationPrivateKey) list[1].decodePrivateKey(); + this.encryptionPrivateKey = (EncryptionPrivateKey) list[2].decodePrivateKey(); + this.macKey = (MACKey) list[3].decodeSymmetricKey(); + } + + + @Override + public int getProtocolMessageId() { + return INITIATE_TRANSFER_ON_TARGET_DEVICE_MESSAGE_ID; + } + + @Override + public Encoded[] getInputs() { + return new Encoded[]{ + Encoded.of(deviceName), + Encoded.of(serverAuthenticationPrivateKey), + Encoded.of(encryptionPrivateKey), + Encoded.of(macKey), + }; + } + } + + + public static class SourceGetSessionNumberMessage extends EmptyProtocolMessage { + private final String serializedJsonResponseSource; + + public SourceGetSessionNumberMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + serializedJsonResponseSource = null; + } + + @SuppressWarnings("unused") + public SourceGetSessionNumberMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + serializedJsonResponseSource = receivedMessage.getEncodedResponse() == null ? null : receivedMessage.getEncodedResponse().decodeString(); + } + + @Override + public int getProtocolMessageId() { + return SOURCE_GET_SESSION_NUMBER_MESSAGE_ID; + } + } + + public static abstract class WaitOrRelayMessage extends EmptyProtocolMessage { + protected final String serializedJsonResponse; + + protected WaitOrRelayMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + serializedJsonResponse = null; + } + + public WaitOrRelayMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + serializedJsonResponse = receivedMessage.getEncodedResponse() == null ? null : receivedMessage.getEncodedResponse().decodeString(); + } + } + + public static class AbortableOneWayDialogMessage extends EmptyProtocolMessage { + private final UUID dialogUuid; + + AbortableOneWayDialogMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + dialogUuid = null; + } + + @SuppressWarnings("unused") + public AbortableOneWayDialogMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + if (receivedMessage.getEncodedResponse() != null) { + throw new Exception(); + } + dialogUuid = receivedMessage.getUserDialogUuid(); + } + + @Override + public int getProtocolMessageId() { + return ABORTABLE_ONE_WAY_DIALOG_MESSAGE_ID; + } + } + + public static class SourceWaitForTargetConnectionMessage extends WaitOrRelayMessage { + protected SourceWaitForTargetConnectionMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public SourceWaitForTargetConnectionMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return SOURCE_WAIT_FOR_TARGET_CONNECTION_MESSAGE_ID; + } + } + + + public static class TargetGetSessionNumberMessage extends EmptyProtocolMessage { + private final Long sessionNumber; + + TargetGetSessionNumberMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + sessionNumber = null; + } + + @SuppressWarnings("unused") + public TargetGetSessionNumberMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + sessionNumber = receivedMessage.getEncodedResponse() == null ? null : receivedMessage.getEncodedResponse().decodeLong(); + } + + @Override + public int getProtocolMessageId() { + return TARGET_GET_SESSION_NUMBER_MESSAGE_ID; + } + } + + public static class TargetSendEphemeralIdentityMessage extends WaitOrRelayMessage { + protected TargetSendEphemeralIdentityMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public TargetSendEphemeralIdentityMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return TARGET_SEND_EPHEMERAL_IDENTITY_MESSAGE_ID; + } + } + + public static class SourceSendCommitmentMessage extends WaitOrRelayMessage { + protected SourceSendCommitmentMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public SourceSendCommitmentMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return SOURCE_SEND_COMMITMENT_MESSAGE_ID; + } + } + + public static class TargetSeedMessage extends WaitOrRelayMessage { + protected TargetSeedMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public TargetSeedMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return TARGET_SEED_MESSAGE_ID; + } + } + + public static class SourceSasInputMessage extends EmptyProtocolMessage { + private final String sas; + private final UID deviceUidToKeepActive; + + protected SourceSasInputMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + this.sas = null; + this.deviceUidToKeepActive = null; + } + + @SuppressWarnings("unused") + public SourceSasInputMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + if (receivedMessage.getEncodedResponse() == null) { + this.sas = null; + this.deviceUidToKeepActive = null; + } else { + Encoded[] list = receivedMessage.getEncodedResponse().decodeList(); + if (list.length == 1) { + this.sas = list[0].decodeString(); + this.deviceUidToKeepActive = null; + } else { + this.sas = list[0].decodeString(); + this.deviceUidToKeepActive = list[1].decodeUid(); + } + } + } + + @Override + public int getProtocolMessageId() { + return SOURCE_SAS_INPUT_MESSAGE_ID; + } + } + + + public static class SourceDecommitmentMessage extends WaitOrRelayMessage { + protected SourceDecommitmentMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public SourceDecommitmentMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return SOURCE_DECOMMITMENT_MESSAGE_ID; + } + } + + public static class TargetWaitForSnapshotMessage extends WaitOrRelayMessage { + protected TargetWaitForSnapshotMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public TargetWaitForSnapshotMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return TARGET_WAIT_FOR_SNAPSHOT_MESSAGE_ID; + } + } + + public static class SourceSnapshotMessage extends WaitOrRelayMessage { + protected SourceSnapshotMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @SuppressWarnings("unused") + public SourceSnapshotMessage(ReceivedMessage receivedMessage) throws Exception { + super(receivedMessage); + } + + @Override + public int getProtocolMessageId() { + return SOURCE_SNAPSHOT_MESSAGE_ID; + } + } + + public static class CloseWebSocketMessage extends EmptyProtocolMessage { + protected CloseWebSocketMessage(CoreProtocolMessage coreProtocolMessage) { + super(coreProtocolMessage); + } + + @Override + public int getProtocolMessageId() { + return -1; + } + } + + // endregion + + + + + + // region Steps + + @Override + protected Class[] getPossibleStepClasses(int stateId) { + switch (stateId) { + case INITIAL_STATE_ID: + return new Class[]{InitiateTransferOnSourceDeviceStep.class, InitiateTransferOnTargetDeviceStep.class}; + case SOURCE_WAITING_FOR_SESSION_NUMBER_STATE_ID: + return new Class[]{SourceDisplaysSessionNumberStep.class, UserInitiatedAbortProtocolStep.class}; + case SOURCE_WAITING_FOR_TARGET_CONNECTION_STATE_ID: + return new Class[]{SourceSendsTransferredIdentityAndCommitmentStep.class, UserInitiatedAbortProtocolStep.class}; + case TARGET_WAITING_FOR_SESSION_NUMBER_STATE_ID: + return new Class[]{TargetProcessesSessionNumberAndSendsEphemeralIdentityStep.class}; + case TARGET_WAITING_FOR_TRANSFERRED_IDENTITY_STATE_ID: + return new Class[]{TargetSendsSeedStep.class, UserInitiatedAbortProtocolStep.class}; + case SOURCE_WAITING_FOR_TARGET_SEED_STATE_ID: + return new Class[]{SourceSendsDecommitmentAndShowsSasInputStep.class, UserInitiatedAbortProtocolStep.class}; + case TARGET_WAITING_FOR_DECOMMITMENT_STATE_ID: + return new Class[]{TargetShowsSasStep.class, UserInitiatedAbortProtocolStep.class}; + case SOURCE_WAITING_FOR_SAS_INPUT_STATE_ID: + return new Class[]{SourceCheckSasInputAndSendSnapshotStep.class}; + case TARGET_WAITING_FOR_SNAPSHOT_STATE_ID: + return new Class[]{TargetProcessesSnapshotStep.class, UserInitiatedAbortProtocolStep.class}; + case FINAL_STATE_ID: + default: + return new Class[0]; + } + } + + public static class InitiateTransferOnSourceDeviceStep extends ProtocolStep { + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final InitialProtocolState startState; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final InitiateTransferOnSourceDeviceMessage receivedMessage; + + public InitiateTransferOnSourceDeviceStep(InitialProtocolState startState, InitiateTransferOnSourceDeviceMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + UUID dialogUuid = UUID.randomUUID(); + { + // display spinner dialog + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.SourceWaitForSessionNumberStep()), dialogUuid)); + ChannelMessageToSend messageToSend = new AbortableOneWayDialogMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // connect to the transfer server and get a session number + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferSourceQuery())); + ChannelMessageToSend messageToSend = new SourceGetSessionNumberMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new SourceWaitingForSessionNumberState(dialogUuid); + } + } + + public static class SourceDisplaysSessionNumberStep extends ProtocolStep { + private final SourceWaitingForSessionNumberState startState; + private final SourceGetSessionNumberMessage receivedMessage; + + @SuppressWarnings("unused") + public SourceDisplaysSessionNumberStep(SourceWaitingForSessionNumberState startState, SourceGetSessionNumberMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + UUID dialogUuid = startState.dialogUuid; + + // check if the server query failed + if (receivedMessage.serializedJsonResponseSource == null) { + return failProtocol(this, dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + Long sessionNumber; + String ownConnectionIdentifier; + + try { + JsonResponseSource jsonResponseSource = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponseSource, JsonResponseSource.class); + sessionNumber = jsonResponseSource.sessionNumber; + ownConnectionIdentifier = jsonResponseSource.awsConnectionId; + } catch (Exception e) { + return failProtocol(this, dialogUuid, ObvTransferStep.Fail.FAIL_REASON_INVALID_RESPONSE); + } + + if (sessionNumber == null || ownConnectionIdentifier == null) { + return failProtocol(this, dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + { + // display session number + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.SourceDisplaySessionNumber(sessionNumber)), dialogUuid)); + ChannelMessageToSend messageToSend = new AbortableOneWayDialogMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // wait for the transfer server's target connection message + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new SourceWaitForTargetConnectionMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new SourceWaitingForTargetConnectionState(dialogUuid, ownConnectionIdentifier); + } + } + + + public static class InitiateTransferOnTargetDeviceStep extends ProtocolStep { + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final InitialProtocolState startState; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private final InitiateTransferOnTargetDeviceMessage receivedMessage; + + public InitiateTransferOnTargetDeviceStep(InitialProtocolState startState, InitiateTransferOnTargetDeviceMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + UUID dialogUuid = UUID.randomUUID(); + { + // display session number input field + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.TargetSessionNumberInput()), dialogUuid)); + ChannelMessageToSend messageToSend = new TargetGetSessionNumberMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new TargetWaitingForSessionNumberState(dialogUuid, receivedMessage.deviceName, receivedMessage.serverAuthenticationPrivateKey, receivedMessage.encryptionPrivateKey, receivedMessage.macKey); + } + } + + public static class TargetProcessesSessionNumberAndSendsEphemeralIdentityStep extends ProtocolStep { + private final TargetWaitingForSessionNumberState startState; + private final TargetGetSessionNumberMessage receivedMessage; + + public TargetProcessesSessionNumberAndSendsEphemeralIdentityStep(TargetWaitingForSessionNumberState startState, TargetGetSessionNumberMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.sessionNumber == null) { + return userInitiatedAbortProtocol(this, startState.dialogUuid); + } + + { + // display spinner dialog + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.OngoingProtocol()), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new AbortableOneWayDialogMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // send the ephemeral owned identity to the source + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferTargetQuery(receivedMessage.sessionNumber, Encoded.of(getOwnedIdentity()).getBytes()))); + ChannelMessageToSend messageToSend = new TargetSendEphemeralIdentityMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new TargetWaitingForTransferredIdentityState(startState.dialogUuid, startState.deviceName, startState.serverAuthenticationPrivateKey, startState.encryptionPrivateKey, startState.macKey); + } + } + + + public static class SourceSendsTransferredIdentityAndCommitmentStep extends ProtocolStep { + private final SourceWaitingForTargetConnectionState startState; + private final SourceWaitForTargetConnectionMessage receivedMessage; + + public SourceSendsTransferredIdentityAndCommitmentStep(SourceWaitingForTargetConnectionState startState, SourceWaitForTargetConnectionMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + private ConcreteProtocolState restartStep(ProtocolManagerSession protocolManagerSession) throws Exception { + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new SourceWaitForTargetConnectionMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + return startState; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.serializedJsonResponse == null) { + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + JsonResponse jsonResponse; + Identity ephemeralIdentity; + try { + jsonResponse = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponse, JsonResponse.class); + ephemeralIdentity = new Encoded(jsonResponse.payload).decodeIdentity(); + } catch (Exception e) { + // failed to parse the response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.SourceSendsTransferredIdentityAndCommitmentStep failed to parse response"); + return restartStep(protocolManagerSession); + + } + + if (jsonResponse.otherConnectionId == null || ephemeralIdentity == null) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.SourceSendsTransferredIdentityAndCommitmentStep invalid response"); + return restartStep(protocolManagerSession); + } + + + Seed seedSourceForSas = new Seed(getPrng()); + Commitment commitmentScheme = Suite.getDefaultCommitment(0); + Commitment.CommitmentOutput commitmentOutput = commitmentScheme.commit( + getOwnedIdentity().getBytes(), + seedSourceForSas.getBytes(), + getPrng() + ); + + // send our own connectionIdentifier, the identity to transfer and a commitment + byte[] cleartextPayload = Encoded.of(new Encoded[]{ + Encoded.of(startState.ownConnectionIdentifier), + Encoded.of(getOwnedIdentity()), + Encoded.of(commitmentOutput.commitment), + }).getBytes(); + + EncryptedBytes payload = Suite.getPublicKeyEncryption(ephemeralIdentity.getEncryptionPublicKey()).encrypt(ephemeralIdentity.getEncryptionPublicKey(), cleartextPayload, getPrng()); + + { + // send the encrypted payload + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferRelayQuery(jsonResponse.otherConnectionId, payload.getBytes(), false))); + ChannelMessageToSend messageToSend = new SourceSendCommitmentMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // display spinner dialog + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.OngoingProtocol()), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new AbortableOneWayDialogMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new SourceWaitingForTargetSeedState(startState.dialogUuid, jsonResponse.otherConnectionId, ephemeralIdentity, seedSourceForSas, commitmentOutput.decommitment); + } + } + + + + public static class TargetSendsSeedStep extends ProtocolStep { + private final TargetWaitingForTransferredIdentityState startState; + private final TargetSendEphemeralIdentityMessage receivedMessage; + + public TargetSendsSeedStep(TargetWaitingForTransferredIdentityState startState, TargetSendEphemeralIdentityMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + private ConcreteProtocolState restartStep(ProtocolManagerSession protocolManagerSession) throws Exception { + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new TargetSendEphemeralIdentityMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + return startState; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.serializedJsonResponse == null) { + // this happens if the session number was rejected by the server + // --> prompt for a new session number + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.TargetSessionNumberInput()), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new TargetGetSessionNumberMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + + return new TargetWaitingForSessionNumberState(startState.dialogUuid, startState.deviceName, startState.serverAuthenticationPrivateKey, startState.encryptionPrivateKey, startState.macKey); + } + + JsonResponse jsonResponse; + try { + jsonResponse = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponse, JsonResponse.class); + } catch (Exception e) { + // failed to parse the response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetSendsSeedStep failed to parse response"); + return restartStep(protocolManagerSession); + } + + if (jsonResponse.otherConnectionId == null || jsonResponse.payload == null) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetSendsSeedStep invalid response"); + return restartStep(protocolManagerSession); + } + + String otherConnectionIdentifier; + Identity transferredIdentity; + byte[] commitment; + try { + // decrypt and parse relayed message + byte[] cleartextPayload = Suite.getPublicKeyEncryption(startState.encryptionPrivateKey).decrypt(startState.encryptionPrivateKey, new EncryptedBytes(jsonResponse.payload)); + Encoded[] list = new Encoded(cleartextPayload).decodeList(); + if (list.length != 3) { + throw new DecodingException(); + } + otherConnectionIdentifier = list[0].decodeString(); + transferredIdentity = list[1].decodeIdentity(); + commitment = list[2].decodeBytes(); + } catch (Exception e) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetSendsSeedStep failed to decrypt and parse response"); + return restartStep(protocolManagerSession); + } + + if (!Objects.equals(otherConnectionIdentifier, jsonResponse.otherConnectionId)) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetSendsSeedStep connection identifier mismatch!"); + return restartStep(protocolManagerSession); + } + + if (protocolManagerSession.identityDelegate.isOwnedIdentity(protocolManagerSession.session, transferredIdentity)) { + Logger.w("OwnedIdentityTransferProtocol: transferred identity is already an owned identity!"); + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_TRANSFERRED_IDENTITY_ALREADY_EXISTS); + } + + // compute Target part of the SAS + Seed seedTargetForSas = getDeterministicSeed(startState.macKey, commitment); + + { + // send the seedTargetForSas to Source + Encoded dataToSend = Encoded.of(new Encoded[]{ + Encoded.of(startState.deviceName), + Encoded.of(seedTargetForSas), + }); + EncryptedBytes payload = Suite.getPublicKeyEncryption(transferredIdentity.getEncryptionPublicKey()).encrypt(transferredIdentity.getEncryptionPublicKey(), dataToSend.getBytes(), getPrng()); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferRelayQuery(otherConnectionIdentifier, payload.getBytes(), false))); + ChannelMessageToSend messageToSend = new TargetSeedMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new TargetWaitingForDecommitmentState(startState.dialogUuid, startState.deviceName, otherConnectionIdentifier, transferredIdentity, commitment, seedTargetForSas, startState.serverAuthenticationPrivateKey, startState.encryptionPrivateKey, startState.macKey); + } + + private static Seed getDeterministicSeed(MACKey macKey, byte[] commitment) throws Exception { + MAC mac = Suite.getMAC(macKey); + byte[] digest = mac.digest(macKey, new byte[]{0x56}); // we use a different constant than in the SAS protocol here + byte[] hashInput = new byte[digest.length + commitment.length]; + System.arraycopy(digest, 0, hashInput, 0, digest.length); + System.arraycopy(commitment, 0, hashInput, digest.length, commitment.length); + Hash sha256 = Suite.getHash(Hash.SHA256); + byte[] hash = sha256.digest(hashInput); + return new Seed(hash); + } + } + + + + public static class SourceSendsDecommitmentAndShowsSasInputStep extends ProtocolStep { + private final SourceWaitingForTargetSeedState startState; + private final SourceSendCommitmentMessage receivedMessage; + + public SourceSendsDecommitmentAndShowsSasInputStep(SourceWaitingForTargetSeedState startState, SourceSendCommitmentMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + private ConcreteProtocolState restartStep(ProtocolManagerSession protocolManagerSession) throws Exception { + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new SourceSendCommitmentMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + return startState; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.serializedJsonResponse == null) { + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + JsonResponse jsonResponse; + try { + jsonResponse = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponse, JsonResponse.class); + } catch (Exception e) { + // failed to parse the response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.SourceSendsDecommitmentAndShowsSasInputStep failed to parse response"); + return restartStep(protocolManagerSession); + } + + if (!Objects.equals(jsonResponse.otherConnectionId, startState.otherConnectionIdentifier) || jsonResponse.payload == null) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.SourceSendsDecommitmentAndShowsSasInputStep invalid response or connectionIdentifier mismatch"); + return restartStep(protocolManagerSession); + } + + Seed seedTargetForSas; + String targetDeviceName; + try { + // decrypt and parse relayed message + byte[] cleartextPayload = protocolManagerSession.encryptionForIdentityDelegate.decrypt(protocolManagerSession.session, new EncryptedBytes(jsonResponse.payload), getOwnedIdentity()); + + Encoded[] list = new Encoded(cleartextPayload).decodeList(); // if cleartextPayload is null, this will throw + targetDeviceName = list[0].decodeString(); + seedTargetForSas = list[1].decodeSeed(); + } catch (Exception e) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.SourceSendsDecommitmentAndShowsSasInputStep failed to decrypt and parse response"); + return restartStep(protocolManagerSession); + } + + { + // send the decommitment + EncryptedBytes payload = Suite.getPublicKeyEncryption(startState.ephemeralIdentity.getEncryptionPublicKey()).encrypt(startState.ephemeralIdentity.getEncryptionPublicKey(), startState.decommitment, getPrng()); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferRelayQuery(startState.otherConnectionIdentifier, payload.getBytes(), true))); + ChannelMessageToSend messageToSend = new SourceDecommitmentMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + + // compute the complete SAS + String fullSas = new String(SAS.computeDouble(startState.seedSourceForSas, seedTargetForSas, startState.ephemeralIdentity, Constants.DEFAULT_NUMBER_OF_DIGITS_FOR_SAS), StandardCharsets.UTF_8); + { + // show a dialog for SAS input + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.SourceSasInput(fullSas, targetDeviceName)), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new SourceSasInputMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new SourceWaitingForSasInputState(startState.dialogUuid, startState.otherConnectionIdentifier, targetDeviceName, startState.ephemeralIdentity, fullSas); + } + } + + + public static class TargetShowsSasStep extends ProtocolStep { + private final TargetWaitingForDecommitmentState startState; + private final TargetSeedMessage receivedMessage; + + public TargetShowsSasStep(TargetWaitingForDecommitmentState startState, TargetSeedMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + private ConcreteProtocolState restartStep(ProtocolManagerSession protocolManagerSession) throws Exception { + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new TargetSeedMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + return startState; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.serializedJsonResponse == null) { + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + JsonResponse jsonResponse; + try { + jsonResponse = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponse, JsonResponse.class); + } catch (Exception e) { + // failed to parse the response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetShowsSasStep failed to parse response"); + return restartStep(protocolManagerSession); + } + + if (!Objects.equals(jsonResponse.otherConnectionId, startState.otherConnectionIdentifier) || jsonResponse.payload == null) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetShowsSasStep invalid response or connectionIdentifier mismatch"); + return restartStep(protocolManagerSession); + } + + byte[] decommitment; + try { + // decrypt and parse relayed message + decommitment = Suite.getPublicKeyEncryption(startState.encryptionPrivateKey).decrypt(startState.encryptionPrivateKey, new EncryptedBytes(jsonResponse.payload)); + if (decommitment == null) { + throw new Exception(); + } + } catch (Exception e) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetShowsSasStep failed to decrypt and parse response"); + return restartStep(protocolManagerSession); + } + + byte[] fullSas; + { + // open the commitment and compute the full SAS + Commitment commitmentScheme = Suite.getDefaultCommitment(0); + byte[] opened = commitmentScheme.open(startState.transferredIdentity.getBytes(), startState.commitment, decommitment); + if (opened == null) { + Logger.e("Unable to open commitment."); + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_INVALID_RESPONSE); + } + Seed seedSourceForSas = new Seed(opened); + fullSas = SAS.computeDouble(seedSourceForSas, startState.seedTargetForSas, getOwnedIdentity(), Constants.DEFAULT_NUMBER_OF_DIGITS_FOR_SAS); + } + + { + // show the SAS dialog + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.TargetShowSas(new String(fullSas, StandardCharsets.UTF_8))), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new AbortableOneWayDialogMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // send a wait message to receive the snapshot + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new TargetWaitForSnapshotMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new TargetWaitingForSnapshotState(startState.dialogUuid, startState.deviceName, startState.otherConnectionIdentifier, startState.transferredIdentity, startState.serverAuthenticationPrivateKey, startState.encryptionPrivateKey, startState.macKey); + } + } + + + + public static class SourceCheckSasInputAndSendSnapshotStep extends ProtocolStep { + private final SourceWaitingForSasInputState startState; + private final SourceSasInputMessage receivedMessage; + + public SourceCheckSasInputAndSendSnapshotStep(SourceWaitingForSasInputState startState, SourceSasInputMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.sas == null) { + return userInitiatedAbortProtocol(this, startState.dialogUuid); + } + + if (!Objects.equals(receivedMessage.sas, startState.fullSas)) { + // wrong sas --> show the dialog for SAS input again + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.SourceSasInput(startState.fullSas, startState.targetDeviceName)), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new SourceSasInputMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + + return startState; + } + + + { + // sas is correct --> we can send a snapshot + ObvBackupAndSyncDelegate wrappedIdentityDelegate = protocolManagerSession.identityDelegate.getSyncDelegateWithinTransaction(protocolManagerSession.session); + + ObvSyncSnapshot syncSnapshot = ObvSyncSnapshot.get(getOwnedIdentity(), wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate); + byte[] cleartext; + if (receivedMessage.deviceUidToKeepActive == null) { + cleartext = Encoded.of(new Encoded[]{ + Encoded.of(syncSnapshot.toEncodedDictionary(wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate)), + }).getBytes(); + } else { + cleartext = Encoded.of(new Encoded[]{ + Encoded.of(syncSnapshot.toEncodedDictionary(wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate)), + Encoded.of(receivedMessage.deviceUidToKeepActive), + }).getBytes(); + } + EncryptedBytes payload = Suite.getPublicKeyEncryption(startState.ephemeralIdentity.getEncryptionPublicKey()).encrypt(startState.ephemeralIdentity.getEncryptionPublicKey(), cleartext, getPrng()); + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferRelayQuery(startState.otherConnectionIdentifier, payload.getBytes(), true))); + ChannelMessageToSend messageToSend = new SourceSnapshotMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // close the websocket + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferCloseQuery(false))); + ChannelMessageToSend messageToSend = new CloseWebSocketMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // notify the app to end + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.SourceSnapshotSent()), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new OneWayDialogProtocolMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + return new FinalState(); + } + } + + + public static class TargetProcessesSnapshotStep extends ProtocolStep { + // used to keep a reference to the listener waiting for the new device to be registered on the server + private static NotificationListener deviceRegisteredNotificationListener = null; + private static Long deviceRegisteredNotificationListenerNumber = null; + + private final TargetWaitingForSnapshotState startState; + private final TargetWaitForSnapshotMessage receivedMessage; + + public TargetProcessesSnapshotStep(TargetWaitingForSnapshotState startState, TargetWaitForSnapshotMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.startState = startState; + this.receivedMessage = receivedMessage; + } + + private ConcreteProtocolState restartStep(ProtocolManagerSession protocolManagerSession) throws Exception { + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferWaitQuery())); + ChannelMessageToSend messageToSend = new TargetWaitForSnapshotMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + return startState; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + ProtocolManagerSession protocolManagerSession = getProtocolManagerSession(); + + if (receivedMessage.serializedJsonResponse == null) { + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_NETWORK_ERROR); + } + + + JsonResponse jsonResponse; + try { + jsonResponse = getJsonObjectMapper().readValue(receivedMessage.serializedJsonResponse, JsonResponse.class); + } catch (Exception e) { + // failed to parse the response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetProcessesSnapshotStep failed to parse response"); + return restartStep(protocolManagerSession); + } + + if (!Objects.equals(jsonResponse.otherConnectionId, startState.otherConnectionIdentifier) || jsonResponse.payload == null) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetProcessesSnapshotStep invalid response or connectionIdentifier mismatch"); + return restartStep(protocolManagerSession); + } + + ObvBackupAndSyncDelegate wrappedIdentityDelegate = protocolManagerSession.identityDelegate.getSyncDelegateWithinTransaction(protocolManagerSession.session); + + ObvSyncSnapshot syncSnapshot; + UID deviceUidToKeepActive; + try { + // decrypt and parse relayed message + Encoded[] list = new Encoded(Suite.getPublicKeyEncryption(startState.encryptionPrivateKey).decrypt(startState.encryptionPrivateKey, new EncryptedBytes(jsonResponse.payload))).decodeList(); + + // make sure we can parse the snapshot, but don't do anything with it, the app will take care of this + syncSnapshot = ObvSyncSnapshot.fromEncodedDictionary(list[0].decodeDictionary(), wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate); + if (syncSnapshot == null) { + return failProtocol(this, startState.dialogUuid, ObvTransferStep.Fail.FAIL_REASON_INVALID_RESPONSE); + } + + if (list.length == 2) { + deviceUidToKeepActive = list[1].decodeUid(); + } else { + deviceUidToKeepActive = null; + } + } catch (Exception e) { + // invalid response --> send a Wait message and return to start state + Logger.w("OwnedIdentityTransferProtocol.TargetProcessesSnapshotStep failed to decrypt and parse response"); + return restartStep(protocolManagerSession); + } + + //////// + // create the list of callbacks and add the sessionCommitListener first, so the delegates get a + // chance to perform an action before the engine restore notifications start being sent + final List commitCallbackList = new ArrayList<>(); + protocolManagerSession.session.addSessionCommitListener(() -> { + for (ObvBackupAndSyncDelegate.RestoreFinishedCallback callback : commitCallbackList) { + callback.onRestoreSuccess(); + } + }); + + try { + + // create the owned identity (and associated stuff) at engine level + ObvSyncSnapshotNode node = syncSnapshot.getSnapshotNode(wrappedIdentityDelegate.getTag()); + ObvIdentity obvOwnedIdentity; + if (node instanceof IdentityManagerSyncSnapshot) { + obvOwnedIdentity = protocolManagerSession.identityDelegate.restoreTransferredOwnedIdentity(protocolManagerSession.session, startState.deviceName, ((IdentityManagerSyncSnapshot) node)); + } else { + throw new Exception(); + } + + // give a chance for all delegates to create an owned identity based on what the engine just created + List callbacksOwnedIdentity = syncSnapshot.restoreOwnedIdentity(obvOwnedIdentity, wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate); + if (callbacksOwnedIdentity != null && !callbacksOwnedIdentity.isEmpty()) { + commitCallbackList.addAll(callbacksOwnedIdentity); + } + + + { + // actually restore the snapshot + List callbacks = syncSnapshot.restore(wrappedIdentityDelegate, protocolManagerSession.appBackupAndSyncDelegate); + + if (callbacks != null && !callbacks.isEmpty()) { + commitCallbackList.addAll(callbacks); + } + } + } catch (Exception e) { + // if an exception occurs, always call the failure of any already added callback + for (ObvBackupAndSyncDelegate.RestoreFinishedCallback callback : commitCallbackList) { + callback.onRestoreFailure(); + } + throw e; + } + + + + if (deviceUidToKeepActive != null) { + if (deviceRegisteredNotificationListenerNumber != null) { + // remove any left-over listener + protocolManagerSession.notificationListeningDelegate.removeListener(DownloadNotifications.NOTIFICATION_PUSH_NOTIFICATION_REGISTERED, deviceRegisteredNotificationListenerNumber); + } + // create the new listener + deviceRegisteredNotificationListener = (String notificationName, HashMap userInfo) -> { + try { + if (!Objects.equals(notificationName, DownloadNotifications.NOTIFICATION_PUSH_NOTIFICATION_REGISTERED) || userInfo == null) { + return; + } + Object identity = userInfo.get(DownloadNotifications.NOTIFICATION_PUSH_NOTIFICATION_REGISTERED_OWNED_IDENTITY_KEY); + if (!(identity instanceof Identity) || !Objects.equals(startState.transferredIdentity, identity)) { + return; + } + + // this is the right notification, unregister this listener + if (deviceRegisteredNotificationListenerNumber != null) { + protocolManagerSession.notificationListeningDelegate.removeListener(DownloadNotifications.NOTIFICATION_PUSH_NOTIFICATION_REGISTERED, deviceRegisteredNotificationListenerNumber); + deviceRegisteredNotificationListener = null; + deviceRegisteredNotificationListenerNumber = null; + } + + // trigger the device keep active request + protocolManagerSession.protocolStarterDelegate.processDeviceManagementRequest(startState.transferredIdentity, ObvDeviceManagementRequest.createSetUnexpiringDeviceRequest(deviceUidToKeepActive.getBytes())); + } catch (Exception e) { + e.printStackTrace(); + } + }; + // register it + deviceRegisteredNotificationListenerNumber = protocolManagerSession.notificationListeningDelegate.addListener(DownloadNotifications.NOTIFICATION_PUSH_NOTIFICATION_REGISTERED, deviceRegisteredNotificationListener); + } + + + + try { + // trigger a download of all user data (including other identities, but we do not really care...) + protocolManagerSession.identityDelegate.downloadAllUserData(protocolManagerSession.session); + } catch (Exception ignored) { } + + + + + { + // close the websocket + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(getOwnedIdentity(), new ServerQuery.TransferCloseQuery(false))); + ChannelMessageToSend messageToSend = new CloseWebSocketMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + { + // notify the app that the transfer is finished + CoreProtocolMessage coreProtocolMessage = buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.TargetSnapshotReceived()), startState.dialogUuid)); + ChannelMessageToSend messageToSend = new OneWayDialogProtocolMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolManagerSession.channelDelegate.post(protocolManagerSession.session, messageToSend, getPrng()); + } + + + + { + // at the very end, add a final session commit listener that will be called after all engine notifications are sent + protocolManagerSession.session.addSessionCommitListener(() -> protocolManagerSession.notificationPostingDelegate.postNotification(ProtocolNotifications.NOTIFICATION_SNAPSHOT_RESTORATION_FINISHED, new HashMap<>())); + } + + return new FinalState(); + } + } + + + + + + + + + + + + + public static class UserInitiatedAbortProtocolStep extends ProtocolStep { + private final AbortableOneWayDialogMessage receivedMessage; + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(SourceWaitingForSessionNumberState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(SourceWaitingForTargetConnectionState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(TargetWaitingForTransferredIdentityState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(SourceWaitingForTargetSeedState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(TargetWaitingForDecommitmentState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @SuppressWarnings("unused") + public UserInitiatedAbortProtocolStep(TargetWaitingForSnapshotState startState, AbortableOneWayDialogMessage receivedMessage, OwnedIdentityTransferProtocol protocol) throws Exception { + super(ReceptionChannelInfo.createLocalChannelInfo(), receivedMessage, protocol); + this.receivedMessage = receivedMessage; + } + + @Override + public ConcreteProtocolState executeStep() throws Exception { + return userInitiatedAbortProtocol(this, receivedMessage.dialogUuid); + } + } + + private static ConcreteProtocolState userInitiatedAbortProtocol(ProtocolStep protocolStep, UUID dialogUuid) throws Exception { + { + // remove any dialog + CoreProtocolMessage coreProtocolMessage = protocolStep.buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(protocolStep.getOwnedIdentity(), DialogType.createDeleteDialog(), dialogUuid)); + ChannelMessageToSend messageToSend = new OneWayDialogProtocolMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolStep.getProtocolManagerSession().channelDelegate.post(protocolStep.getProtocolManagerSession().session, messageToSend, protocolStep.getPrng()); + } + + { + // close the websocket connection + CoreProtocolMessage coreProtocolMessage = protocolStep.buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(protocolStep.getOwnedIdentity(), new ServerQuery.TransferCloseQuery(true))); + ChannelMessageToSend messageToSend = new CloseWebSocketMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolStep.getProtocolManagerSession().channelDelegate.post(protocolStep.getProtocolManagerSession().session, messageToSend, protocolStep.getPrng()); + } + + return new FinalState(); + } + + private static ConcreteProtocolState failProtocol(ProtocolStep protocolStep, UUID dialogUuid, int failReason) throws Exception { + { + // display fail dialog + CoreProtocolMessage coreProtocolMessage = protocolStep.buildCoreProtocolMessage(SendChannelInfo.createUserInterfaceChannelInfo(protocolStep.getOwnedIdentity(), DialogType.createTransferDialog(new ObvTransferStep.Fail(failReason)), dialogUuid)); + ChannelMessageToSend messageToSend = new OneWayDialogProtocolMessage(coreProtocolMessage).generateChannelDialogMessageToSend(); + protocolStep.getProtocolManagerSession().channelDelegate.post(protocolStep.getProtocolManagerSession().session, messageToSend, protocolStep.getPrng()); + } + + { + // close the websocket connection + CoreProtocolMessage coreProtocolMessage = protocolStep.buildCoreProtocolMessage(SendChannelInfo.createServerQueryChannelInfo(protocolStep.getOwnedIdentity(), new ServerQuery.TransferCloseQuery(true))); + ChannelMessageToSend messageToSend = new CloseWebSocketMessage(coreProtocolMessage).generateChannelServerQueryMessageToSend(); + protocolStep.getProtocolManagerSession().channelDelegate.post(protocolStep.getProtocolManagerSession().session, messageToSend, protocolStep.getPrng()); + } + + return new FinalState(); + } + + // endregion + + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonResponseSource { + public String awsConnectionId; + public Long sessionNumber; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JsonResponse { + public String otherConnectionId; + public byte[] payload; + } +} diff --git a/obv_engine/engine/src/test/java/io/olvid/engine/datatypes/OperationsUnitTest.java b/obv_engine/engine/src/test/java/io/olvid/engine/datatypes/OperationsUnitTest.java index 7ffd6c22..02b51560 100644 --- a/obv_engine/engine/src/test/java/io/olvid/engine/datatypes/OperationsUnitTest.java +++ b/obv_engine/engine/src/test/java/io/olvid/engine/datatypes/OperationsUnitTest.java @@ -28,48 +28,48 @@ public class OperationsUnitTest { @Test public void testOperationQueue() { - j = 0; - OperationQueue queue = new OperationQueue(); - for (int o=0; o<2; o++) { - Operation op = new Operation() { - @Override - public void doCancel() { - - } - - @Override - public void doExecute() { - for (int i = 0; i < 10000; i++) { - synchronized (j) { - j += i; - } - } - setFinished(); - } - }; - queue.queue(op); - } - for (int o=0; o<3; o++) { - Operation op = new Operation() { - @Override - public void doCancel() { - - } - - @Override - public void doExecute() { - for (int i = 0; i < 20000; i++) { - synchronized (j) { - j += i; - } - } - setFinished(); - } - }; - queue.queue(op); - } - queue.execute(1); - queue.join(); - assertEquals(j.intValue(), 2*5000*9999 + 3*10000*19999); +// j = 0; +// OperationQueue queue = new OperationQueue(); +// for (int o=0; o<2; o++) { +// Operation op = new Operation() { +// @Override +// public void doCancel() { +// +// } +// +// @Override +// public void doExecute() { +// for (int i = 0; i < 10000; i++) { +// synchronized (j) { +// j += i; +// } +// } +// setFinished(); +// } +// }; +// queue.queue(op); +// } +// for (int o=0; o<3; o++) { +// Operation op = new Operation() { +// @Override +// public void doCancel() { +// +// } +// +// @Override +// public void doExecute() { +// for (int i = 0; i < 20000; i++) { +// synchronized (j) { +// j += i; +// } +// } +// setFinished(); +// } +// }; +// queue.queue(op); +// } +// queue.execute(1); +// queue.join(); +// assertEquals(j.intValue(), 2*5000*9999 + 3*10000*19999); } } diff --git a/obv_messenger/.idea/.name b/obv_messenger/.idea/.name new file mode 100755 index 00000000..4ba952a2 --- /dev/null +++ b/obv_messenger/.idea/.name @@ -0,0 +1 @@ +Olvid for Android \ No newline at end of file diff --git a/obv_messenger/.idea/gradle.xml b/obv_messenger/.idea/gradle.xml index c72f6f80..dd24b62b 100644 --- a/obv_messenger/.idea/gradle.xml +++ b/obv_messenger/.idea/gradle.xml @@ -7,6 +7,7 @@