From 7561338d309b71d31ceb08fe56fc90bd95b2c644 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Mon, 11 Jul 2016 23:37:38 +0300 Subject: [PATCH 01/81] feat(core): Restoring Key Manager --- .../java/im/actor/core/modules/Modules.java | 8 +- .../encryption/EncryptedPeerActor.java | 11 +- .../encryption/EncryptedProcessor.java | 32 +- .../encryption/EncryptedSessionActor.java | 24 +- .../modules/encryption/EncryptionModule.java | 54 +-- .../modules/encryption/KeyManagerActor.java | 336 +++++++----------- .../modules/encryption/KeyManagerInt.java | 118 ++++-- .../encryption/SessionManagerActor.java | 8 +- .../encryption/entity/OwnIdentity.java | 23 ++ 9 files changed, 312 insertions(+), 302 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java index fd17ce01d0..f3f41b16cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java @@ -163,8 +163,8 @@ public void onLoggedIn(boolean first) { profile = new ProfileModule(this); timing.section("Mentions"); mentions = new MentionsModule(this); -// timing.section("Encryption"); -// encryptionModule = new EncryptionModule(this); + timing.section("Encryption"); + encryptionModule = new EncryptionModule(this); timing.section("DisplayLists"); displayLists = new DisplayLists(this); timing.section("DeviceInfo"); @@ -188,8 +188,8 @@ public void onLoggedIn(boolean first) { notifications.run(); timing.section("AppState"); appStateModule.run(); -// timing.section("Encryption"); -// encryptionModule.run(); + timing.section("Encryption"); + encryptionModule.run(); timing.section("Contacts"); contacts.run(); timing.section("Messages"); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java index 25d395421b..079daff534 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java @@ -10,6 +10,7 @@ import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.core.modules.encryption.entity.UserKeysGroup; import im.actor.core.modules.ModuleActor; @@ -64,14 +65,14 @@ public EncryptedPeerActor(int uid, ModuleContext context) { public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); Promises.tuple( keyManager.getOwnIdentity(), keyManager.getUserKeyGroups(uid)) - .then(new Consumer>() { + .then(new Consumer>() { @Override - public void apply(Tuple2 res) { + public void apply(Tuple2 res) { Log.d(TAG, "then"); ownKeyGroupId = res.getT1().getKeyGroup(); theirKeys = res.getT2(); @@ -118,7 +119,7 @@ public Promise apply(final UserKeysGroup keysGroup) { if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); } - return context().getEncryption().getSessionManagerInt() + return context().getEncryption().getSessionManager() .pickSession(uid, keysGroup.getKeyGroupId()) .failure(new Consumer() { @Override @@ -204,7 +205,7 @@ public Promise> apply(final EncryptedBoxKe } } } - return context().getEncryption().getSessionManagerInt() + return context().getEncryption().getSessionManager() .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) .map(new Function>() { @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java index b416b7ace4..b510988930 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java @@ -1,5 +1,8 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.updates.UpdateEncryptedPackage; +import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; +import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; @@ -15,21 +18,20 @@ public EncryptedProcessor(ModuleContext context) { @Override public Promise process(Update update) { -// if (update instanceof UpdatePublicKeyGroupAdded) { -// context().getEncryption().getKeyManager().send(new KeyManagerActor.PublicKeysGroupAdded( -// ((UpdatePublicKeyGroupAdded) update).getUid(), -// ((UpdatePublicKeyGroupAdded) update).getKeyGroup() -// )); -// return true; -// } else if (update instanceof UpdatePublicKeyGroupRemoved) { -// context().getEncryption().getKeyManager().send(new KeyManagerActor.PublicKeysGroupRemoved( -// ((UpdatePublicKeyGroupRemoved) update).getUid(), -// ((UpdatePublicKeyGroupRemoved) update).getKeyGroupId() -// )); -// return true; -// } else if (update instanceof UpdateEncryptedPackage) { -// -// } + if (update instanceof UpdatePublicKeyGroupAdded) { + UpdatePublicKeyGroupAdded groupAdded = (UpdatePublicKeyGroupAdded) update; + return context().getEncryption() + .getKeyManager() + .onKeyGroupAdded(groupAdded.getUid(), groupAdded.getKeyGroup()); + } else if (update instanceof UpdatePublicKeyGroupRemoved) { + UpdatePublicKeyGroupRemoved groupRemoved = (UpdatePublicKeyGroupRemoved) update; + return context().getEncryption() + .getKeyManager() + .onKeyGroupRemoved(groupRemoved.getUid(), groupRemoved.getKeyGroupId()); + } else if (update instanceof UpdateEncryptedPackage) { + // TODO: Implement + return Promise.success(null); + } return null; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java index f7fbd5094c..210d7caf1e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java @@ -4,6 +4,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; @@ -11,6 +12,7 @@ import im.actor.runtime.actors.ask.AskResult; import im.actor.runtime.function.Consumer; import im.actor.runtime.function.Function; +import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -26,7 +28,7 @@ * 3) Own Pre Key Id * 4) Their Key Group Id * 5) Their Pre Key Id - *

+ *

* During actor starting it downloads all required key from Key Manager. * To encrypt/decrypt messages this actor spawns encryption chains. */ @@ -76,7 +78,7 @@ public EncryptedSessionActor(ModuleContext context, PeerSession session) { @Override public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); } private Promise onEncrypt(final byte[] data) { @@ -88,19 +90,11 @@ private Promise onEncrypt(final byte[] data) { // return success(latestTheirEphemeralKey) - .mapIfNullPromise(keyManager.supplyUserPreKey(uid, session.getTheirKeyGroupId())) - .map(new Function() { - @Override - public EncryptedSessionChain apply(byte[] publicKey) { - return pickEncryptChain(publicKey); - } - }) - .map(new Function() { - @Override - public EncryptedPackageRes apply(EncryptedSessionChain encryptedSessionChain) { - return encrypt(encryptedSessionChain, data); - } - }); + .mapIfNullPromise(() -> + keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) + .map(PublicKey::getPublicKey)) + .map(publicKey -> pickEncryptChain(publicKey)) + .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } private Promise onDecrypt(final byte[] data) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 6598e2a20b..75b01f2a20 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -16,66 +16,48 @@ public class EncryptionModule extends AbsModule { - private ActorRef keyManager; private KeyManagerInt keyManagerInt; - private ActorRef sessionManager; private SessionManagerInt sessionManagerInt; private ActorRef messageEncryptor; - private HashMap encryptedStates = new HashMap(); + private final HashMap encryptedStates = new HashMap<>(); public EncryptionModule(ModuleContext context) { super(context); } public void run() { - keyManager = system().actorOf(Props.create(new ActorCreator() { - @Override - public KeyManagerActor create() { - return new KeyManagerActor(context()); - } - }), "encryption/keys"); - keyManagerInt = new KeyManagerInt(keyManager); - sessionManager = system().actorOf(Props.create(new ActorCreator() { - @Override - public SessionManagerActor create() { - return new SessionManagerActor(context()); - } - }), "encryption/sessions"); + + keyManagerInt = new KeyManagerInt(context()); + + // Session Manager + ActorRef sessionManager = system().actorOf("encryption/sessions", + () -> new SessionManagerActor(context())); sessionManagerInt = new SessionManagerInt(sessionManager); - messageEncryptor = system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedMsgActor create() { - return new EncryptedMsgActor(context()); - } - }), "encryption/messaging"); + + + messageEncryptor = system().actorOf("encryption/messaging", + () -> new EncryptedMsgActor(context())); } - public SessionManagerInt getSessionManagerInt() { + public SessionManagerInt getSessionManager() { return sessionManagerInt; } - public ActorRef getMessageEncryptor() { - return messageEncryptor; + public KeyManagerInt getKeyManager() { + return keyManagerInt; } - public ActorRef getKeyManager() { - return keyManager; - } - public KeyManagerInt getKeyManagerInt() { - return keyManagerInt; + public ActorRef getMessageEncryptor() { + return messageEncryptor; } public ActorRef getEncryptedChatManager(final int uid) { synchronized (encryptedStates) { if (!encryptedStates.containsKey(uid)) { - encryptedStates.put(uid, system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedPeerActor create() { - return new EncryptedPeerActor(uid, context()); - } - }), "encryption/uid_" + uid)); + encryptedStates.put(uid, system().actorOf("encryption/uid_" + uid, + () -> new EncryptedPeerActor(uid, context()))); } return encryptedStates.get(uid); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java index 3a0a3f97a5..13ca85f3cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java @@ -13,12 +13,9 @@ import im.actor.core.api.rpc.RequestLoadPublicKey; import im.actor.core.api.rpc.RequestLoadPublicKeyGroups; import im.actor.core.api.rpc.RequestUploadPreKey; -import im.actor.core.api.rpc.ResponseCreateNewKeyGroup; -import im.actor.core.api.rpc.ResponsePublicKeyGroups; -import im.actor.core.api.rpc.ResponsePublicKeys; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKeyStorage; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.UserKeys; @@ -29,16 +26,13 @@ import im.actor.runtime.Crypto; import im.actor.runtime.Log; import im.actor.runtime.Storage; -import im.actor.runtime.actors.ask.AskIntRequest; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.collections.ManagedList; import im.actor.runtime.crypto.Curve25519KeyPair; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.ratchet.RatchetKeySignature; -import im.actor.runtime.function.Consumer; import im.actor.runtime.promise.PromisesArray; import im.actor.runtime.function.Tuple2; import im.actor.runtime.storage.KeyValueStorage; @@ -53,7 +47,7 @@ public class KeyManagerActor extends ModuleActor { private static final String TAG = "KeyManagerActor"; private KeyValueStorage encryptionKeysStorage; - private HashMap cachedUserKeys = new HashMap(); + private HashMap cachedUserKeys = new HashMap<>(); private PrivateKeyStorage ownKeys; private boolean isReady = false; @@ -121,21 +115,15 @@ public void preStart() { .map(PrivateKey.SIGN(ownKeys.getIdentityKey())); Log.d(TAG, "Creation of new key group"); - api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(new Consumer() { - @Override - public void apply(ResponseCreateNewKeyGroup response) { - ownKeys = ownKeys.setGroupId(response.getKeyGroupId()); - encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); - onMainKeysReady(); - } - }).failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Keys upload error"); - Log.e(TAG, e); - - // Just ignore - } + api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(response -> { + ownKeys = ownKeys.setGroupId(response.getKeyGroupId()); + encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); + onMainKeysReady(); + }).failure(e -> { + Log.w(TAG, "Keys upload error"); + Log.e(TAG, e); + + // Just ignore }); } else { onMainKeysReady(); @@ -173,22 +161,16 @@ private void onMainKeysReady() { pendingEphermalKeys.map(PrivateKey.SIGN(ownKeys.getIdentityKey())); api(new RequestUploadPreKey(ownKeys.getKeyGroupId(), uploadingKeys, uploadingSignatures)) - .then(new Consumer() { - @Override - public void apply(ResponseVoid responseVoid) { - ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()])); - encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); - onAllKeysReady(); - } + .then(responseVoid -> { + ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()])); + encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); + onAllKeysReady(); }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Ephemeral keys upload error"); - Log.e(TAG, e); + .failure(e -> { + Log.w(TAG, "Ephemeral keys upload error"); + Log.e(TAG, e); - // Ignore. This will freeze all encryption operations. - } + // Ignore. This will freeze all encryption operations. }); } else { onAllKeysReady(); @@ -282,26 +264,20 @@ private Promise fetchUserGroups(final int uid) { } return api(new RequestLoadPublicKeyGroups(new ApiUserOutPeer(uid, user.getAccessHash()))) - .map(new Function>() { - @Override - public ArrayList apply(ResponsePublicKeyGroups response) { - ArrayList keysGroups = new ArrayList<>(); - for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) { - UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); - if (validatedKeysGroup != null) { - keysGroups.add(validatedKeysGroup); - } + .map(response -> { + ArrayList keysGroups = new ArrayList<>(); + for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) { + UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); + if (validatedKeysGroup != null) { + keysGroups.add(validatedKeysGroup); } - return keysGroups; } + return keysGroups; }) - .map(new Function, UserKeys>() { - @Override - public UserKeys apply(ArrayList userKeysGroups) { - UserKeys userKeys = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()])); - cacheUserKeys(userKeys); - return userKeys; - } + .map(userKeysGroups -> { + UserKeys userKeys1 = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()])); + cacheUserKeys(userKeys1); + return userKeys1; }); } @@ -320,65 +296,62 @@ private Promise fetchUserPreKey(final int uid, final int keyGroupId, } return pickUserGroup(uid, keyGroupId) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(final Tuple2 keysGroup) { - - // - // Searching in cache - // - - for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) { - if (p.getKeyId() == keyId) { - return Promise.success(p); - } + .flatMap(keysGroup -> { + + // + // Searching in cache + // + + for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) { + if (p.getKeyId() == keyId) { + return Promise.success(p); } + } - // - // Downloading pre key - // - - ArrayList ids = new ArrayList(); - ids.add(keyId); - final UserKeysGroup finalKeysGroup = keysGroup.getT1(); - - return api(new RequestLoadPublicKey(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId, ids)) - .map(new Function() { - @Override - public PublicKey apply(ResponsePublicKeys responsePublicKeys) { - if (responsePublicKeys.getPublicKey().size() == 0) { - throw new RuntimeException("Unable to find public key on server"); - } - ApiEncryptionKeySignature sig = null; - for (ApiEncryptionKeySignature s : responsePublicKeys.getSignatures()) { - if (s.getKeyId() == keyId && "Ed25519".equals(s.getSignatureAlg())) { - sig = s; - break; - } - } - if (sig == null) { - throw new RuntimeException("Unable to find public key on server"); - } - - ApiEncryptionKey key = responsePublicKeys.getPublicKey().get(0); - - byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), - key.getKeyMaterial()); - - if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), - keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); - } - - PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); - UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey); - cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId()) - .addUserKeyGroup(userKeysGroup)); - - return pkey; + // + // Downloading pre key + // + + ArrayList ids = new ArrayList<>(); + ids.add(keyId); + final UserKeysGroup finalKeysGroup = keysGroup.getT1(); + RequestLoadPublicKey request = new RequestLoadPublicKey( + new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), + keyGroupId, ids); + + return api(request) + .map(responsePublicKeys -> { + if (responsePublicKeys.getPublicKey().size() == 0) { + throw new RuntimeException("Unable to find public key on server"); + } + ApiEncryptionKeySignature sig = null; + for (ApiEncryptionKeySignature s : responsePublicKeys.getSignatures()) { + if (s.getKeyId() == keyId && "Ed25519".equals(s.getSignatureAlg())) { + sig = s; + break; } - }); - } + } + if (sig == null) { + throw new RuntimeException("Unable to find public key on server"); + } + + ApiEncryptionKey key = responsePublicKeys.getPublicKey().get(0); + + byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), + key.getKeyMaterial()); + + if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), + keyHash, sig.getSignature())) { + throw new RuntimeException("Key signature does not match"); + } + + PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); + UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey); + cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId()) + .addUserKeyGroup(userKeysGroup)); + + return pkey; + }); }); } @@ -390,40 +363,34 @@ public PublicKey apply(ResponsePublicKeys responsePublicKeys) { */ private Promise fetchUserPreKey(final int uid, final int keyGroupId) { return pickUserGroup(uid, keyGroupId) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(final Tuple2 keyGroups) { - return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId)) - .map(new Function() { - @Override - public PublicKey apply(ResponsePublicKeys response) { - if (response.getPublicKey().size() == 0) { - throw new RuntimeException("User doesn't have pre keys"); - } - ApiEncryptionKey key = response.getPublicKey().get(0); - ApiEncryptionKeySignature sig = null; - for (ApiEncryptionKeySignature s : response.getSignatures()) { - if (s.getKeyId() == key.getKeyId() && "Ed25519".equals(s.getSignatureAlg())) { - sig = s; - break; - } - } - if (sig == null) { - throw new RuntimeException("Unable to find public key on server"); - } - - byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), - key.getKeyMaterial()); - - if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), - keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); - } - - return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); + .flatMap(keyGroups -> { + return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId)) + .map(response -> { + if (response.getPublicKey().size() == 0) { + throw new RuntimeException("User doesn't have pre keys"); + } + ApiEncryptionKey key = response.getPublicKey().get(0); + ApiEncryptionKeySignature sig = null; + for (ApiEncryptionKeySignature s : response.getSignatures()) { + if (s.getKeyId() == key.getKeyId() && "Ed25519".equals(s.getSignatureAlg())) { + sig = s; + break; } - }); - } + } + if (sig == null) { + throw new RuntimeException("Unable to find public key on server"); + } + + byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), + key.getKeyMaterial()); + + if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), + keyHash, sig.getSignature())) { + throw new RuntimeException("Key signature does not match"); + } + + return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); + }); }); } @@ -437,10 +404,10 @@ public PublicKey apply(ResponsePublicKeys response) { * @param uid User's id * @param keyGroup Added key group */ - private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { + private Promise onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { - return; + return Promise.success(null); } UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); if (validatedKeysGroup != null) { @@ -449,6 +416,7 @@ private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); } + return Promise.success(null); } /** @@ -457,16 +425,17 @@ private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { * @param uid User's id * @param keyGroupId Removed key group id */ - private void onPublicKeysGroupRemoved(int uid, int keyGroupId) { + private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { - return; + return Promise.success(null); } UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId); cacheUserKeys(updatedUserKeys); context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + return Promise.success(null); } // @@ -485,7 +454,7 @@ private UserKeysGroup validateUserKeysGroup(int uid, ApiEncryptionKeyGroup keyGr keyGroup.getIdentityKey().getKeyAlg(), keyGroup.getIdentityKey().getKeyMaterial()); - ArrayList keys = new ArrayList(); + ArrayList keys = new ArrayList<>(); key_loop: for (ApiEncryptionKey key : keyGroup.getKeys()) { @@ -536,16 +505,15 @@ private Promise> pickUserGroup(int uid, final in return fetchUserGroups(uid) .map(userKeys -> { UserKeysGroup keysGroup = null; -// for (UserKeysGroup g : userKeys.getUserKeysGroups()) { -// if (g.getKeyGroupId() == keyGroupId) { -// keysGroup = g; -// } -// } -// if (keysGroup == null) { -// throw new RuntimeException("Key Group #" + keyGroupId + " not found"); -// } -// return new Tuple2<>(keysGroup, userKeys); - return null; + for (UserKeysGroup g : userKeys.getUserKeysGroups()) { + if (g.getKeyGroupId() == keyGroupId) { + keysGroup = g; + } + } + if (keysGroup == null) { + throw new RuntimeException("Key Group #" + keyGroupId + " not found"); + } + return new Tuple2<>(keysGroup, userKeys); }); } @@ -574,27 +542,12 @@ private void cacheUserKeys(UserKeys userKeys) { // @Override - public void onReceive(Object message) { - if (!isReady - && (message instanceof AskIntRequest - || message instanceof PublicKeysGroupAdded - || message instanceof PublicKeysGroupRemoved)) { + public Promise onAsk(Object message) throws Exception { + if (!isReady) { stash(); - return; - } - if (message instanceof PublicKeysGroupAdded) { - PublicKeysGroupAdded publicKeysGroupAdded = (PublicKeysGroupAdded) message; - onPublicKeysGroupAdded(publicKeysGroupAdded.getUid(), publicKeysGroupAdded.getPublicKeyGroup()); - } else if (message instanceof PublicKeysGroupRemoved) { - PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; - onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); - } else { - super.onReceive(message); + return null; } - } - @Override - public Promise onAsk(Object message) throws Exception { if (message instanceof FetchOwnKey) { return fetchOwnIdentity(); } else if (message instanceof FetchOwnPreKeyByPublic) { @@ -609,6 +562,12 @@ public Promise onAsk(Object message) throws Exception { return fetchUserPreKey(((FetchUserPreKeyRandom) message).getUid(), ((FetchUserPreKeyRandom) message).getKeyGroup()); } else if (message instanceof FetchOwnRandomPreKey) { return fetchPreKey(); + } else if (message instanceof PublicKeysGroupAdded) { + PublicKeysGroupAdded publicKeysGroupAdded = (PublicKeysGroupAdded) message; + return onPublicKeysGroupAdded(publicKeysGroupAdded.getUid(), publicKeysGroupAdded.getPublicKeyGroup()); + } else if (message instanceof PublicKeysGroupRemoved) { + PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; + return onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); } else { return super.onAsk(message); } @@ -622,25 +581,6 @@ public static class FetchOwnKey implements AskMessage { } - public static class OwnIdentity extends AskResult { - - private int keyGroup; - private PrivateKey identityKey; - - public OwnIdentity(int keyGroup, PrivateKey identityKey) { - this.keyGroup = keyGroup; - this.identityKey = identityKey; - } - - public int getKeyGroup() { - return keyGroup; - } - - public PrivateKey getIdentityKey() { - return identityKey; - } - } - public static class FetchOwnRandomPreKey implements AskMessage { } @@ -735,7 +675,7 @@ public int getKeyGroup() { // Updates handling // - public static class PublicKeysGroupAdded { + public static class PublicKeysGroupAdded implements AskMessage { private int uid; private ApiEncryptionKeyGroup publicKeyGroup; @@ -754,7 +694,7 @@ public ApiEncryptionKeyGroup getPublicKeyGroup() { } } - public static class PublicKeysGroupRemoved { + public static class PublicKeysGroupRemoved implements AskMessage { private int uid; private int keyGroupId; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java index 351019a02e..d109f37df3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java @@ -1,58 +1,126 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.ApiEncryptionKeyGroup; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Supplier; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; -import im.actor.runtime.promise.Promises; +import static im.actor.runtime.actors.ActorSystem.system; + +/** + * Encryption Key Manager. Used for loading user's keys for encryption/decryption. + */ public class KeyManagerInt extends ActorInterface { - public KeyManagerInt(ActorRef dest) { - super(dest); + /** + * Default Constructor + * + * @param context actor context + */ + public KeyManagerInt(ModuleContext context) { + super(system().actorOf("encryption/keys", () -> new KeyManagerActor(context))); } - public Promise getOwnIdentity() { + + // + // Identity + // + + /** + * Loading Own Identity Key + * + * @return promise of keys + */ + public Promise getOwnIdentity() { return ask(new KeyManagerActor.FetchOwnKey()); } + /** + * Loading user key groups by uid + * + * @param uid user's id + * @return promise of key groups + */ public Promise getUserKeyGroups(int uid) { return ask(new KeyManagerActor.FetchUserKeys(uid)); } - public Promise getUserRandomPreKey(int uid, int keyGroupId) { - return ask(new KeyManagerActor.FetchUserPreKeyRandom(uid, keyGroupId)); - } - public Promise getUserPreKey(int uid, int keyGroupId, long preKeyId) { - return ask(new KeyManagerActor.FetchUserPreKey(uid, keyGroupId, preKeyId)); - } + // + // Pre Keys + // + /** + * Load own random pre key + * + * @return promise of private key + */ public Promise getOwnRandomPreKey() { return ask(new KeyManagerActor.FetchOwnRandomPreKey()); } + /** + * Load own pre key by key id + * + * @param id key id + * @return promise of private key + */ public Promise getOwnPreKey(long id) { return ask(new KeyManagerActor.FetchOwnPreKeyById(id)); } + /** + * Loading random user's pre key from key group + * + * @param uid user's id + * @param keyGroupId key group id + * @return promise of public key + */ + public Promise getUserRandomPreKey(int uid, int keyGroupId) { + return ask(new KeyManagerActor.FetchUserPreKeyRandom(uid, keyGroupId)); + } + + /** + * Loading user's pre key by pre key id + * + * @param uid user's id + * @param keyGroupId key group id + * @param preKeyId pre key id + * @return promise of public key + */ + public Promise getUserPreKey(int uid, int keyGroupId, long preKeyId) { + return ask(new KeyManagerActor.FetchUserPreKey(uid, keyGroupId, preKeyId)); + } + + // + // Updates + // + + /** + * Call this when update about new key group added received + * + * @param uid user's id + * @param keyGroup added key group + * @return promise of void + */ + public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { + return ask(new KeyManagerActor.PublicKeysGroupAdded(uid, keyGroup)); + } - public Supplier> supplyUserPreKey(final int uid, final int keyGroupId) { - return new Supplier>() { - @Override - public Promise get() { - return getUserRandomPreKey(uid, keyGroupId) - .map(new Function() { - @Override - public byte[] apply(PublicKey publicKey) { - return publicKey.getPublicKey(); - } - }); - } - }; + /** + * Call this when update about key group removing received + * + * @param uid user's id + * @param gid removed key group id + * @return promise of void + */ + public Promise onKeyGroupRemoved(int uid, int gid) { + return ask(new KeyManagerActor.PublicKeysGroupRemoved(uid, gid)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java index 51c158e89d..e17332f0f5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java @@ -6,6 +6,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; @@ -21,7 +22,6 @@ import im.actor.runtime.crypto.ratchet.RatchetPublicKey; import im.actor.runtime.function.Function; import im.actor.runtime.function.FunctionTupled4; -import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; @@ -44,7 +44,7 @@ public SessionManagerActor(ModuleContext context) { @Override public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); peerSessions = new BaseKeyValueEngine(Storage.createKeyValue("encryption_sessions")) { @Override @@ -137,9 +137,9 @@ public Promise apply(Exception e) { keyManager.getOwnPreKey(ownKeyId), keyManager.getUserKeyGroups(uid), keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { + .map(new FunctionTupled4() { @Override - public PeerSession apply(KeyManagerActor.OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { + public PeerSession apply(OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java new file mode 100644 index 0000000000..d05e90fb04 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java @@ -0,0 +1,23 @@ +package im.actor.core.modules.encryption.entity; + +import im.actor.core.modules.encryption.entity.PrivateKey; +import im.actor.runtime.actors.ask.AskResult; + +public class OwnIdentity extends AskResult { + + private int keyGroup; + private PrivateKey identityKey; + + public OwnIdentity(int keyGroup, PrivateKey identityKey) { + this.keyGroup = keyGroup; + this.identityKey = identityKey; + } + + public int getKeyGroup() { + return keyGroup; + } + + public PrivateKey getIdentityKey() { + return identityKey; + } +} From f96fdd8cae5f968fc34535a3de16dfe616f00d19 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 04:48:49 +0300 Subject: [PATCH 02/81] fix(android): Fixing empty wallpaper --- .../controllers/conversation/messages/MessagesFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 4b6500a94d..424a08c6fa 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -119,8 +119,11 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } else { background = Drawable.createFromPath(BaseActorSettingsFragment.getWallpaperFile()); } - ((ImageView) res.findViewById(R.id.chatBackgroundView)).setImageDrawable(background); + } else { + background = getResources().getDrawable(backgrounds[0]); } + ImageView backgroundView = (ImageView) res.findViewById(R.id.chatBackgroundView); + backgroundView.setImageDrawable(background); // From 8888e340c3c554ef89edc823e643008422247cbe Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 05:09:03 +0300 Subject: [PATCH 03/81] feat(android+core): Secret Chat displaying --- .../im/actor/sdk/controllers/Intents.java | 4 + .../messages/MessagesFragment.java | 19 ++- .../toolbar/ChatToolbarFragment.java | 26 +++- .../controllers/profile/ProfileFragment.java | 18 +++ .../src/main/res/layout/fragment_profile.xml | 27 ++++ .../src/main/res/values/ui_text.xml | 1 + .../main/java/im/actor/core/entity/Peer.java | 16 ++- .../core/entity/encryption/PeerSession.java | 26 +--- .../im/actor/core/modules/ModuleActor.java | 3 +- .../encryption/EncryptedSessionActor.java | 62 +++------ .../modules/encryption/EncryptionModule.java | 20 ++- .../modules/encryption/KeyManagerInt.java | 10 ++ .../encryption/SessionManagerActor.java | 129 ++++++++---------- .../modules/encryption/SessionManagerInt.java | 26 +++- .../encryption/entity/EncryptedBoxKey.java | 7 +- .../history/ConversationHistoryActor.java | 14 +- 16 files changed, 230 insertions(+), 178 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java index 00453de81e..c81f8ae420 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java @@ -122,6 +122,10 @@ public static Intent openPrivateDialog(int uid, boolean compose, Context context return openDialog(Peer.user(uid), compose, context); } + public static Intent openPrivateSecretDialog(int uid, boolean compose, Context context) { + return openDialog(Peer.secret(uid), compose, context); + } + public static Intent openGroupDialog(int chatId, boolean compose, Context context) { return openDialog(Peer.group(chatId), compose, context); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 424a08c6fa..6d0590e65b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -17,6 +17,7 @@ import fr.castorflex.android.circularprogressbar.CircularProgressBar; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.viewmodel.ConversationVM; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; @@ -275,13 +276,17 @@ public void onResume() { } // Bind Progress - bind(conversationVM.getIsLoaded(), conversationVM.getIsEmpty(), (isLoaded, valueModel, isEmpty, valueModel2) -> { - if (isEmpty && !isLoaded) { - showView(progressView); - } else { - hideView(progressView); - } - }); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + bind(conversationVM.getIsLoaded(), conversationVM.getIsEmpty(), (isLoaded, valueModel, isEmpty, valueModel2) -> { + if (isEmpty && !isLoaded) { + showView(progressView); + } else { + hideView(progressView); + } + }); + } else { + hideView(progressView); + } } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 0f51eda4fb..a440e8f83a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -4,7 +4,9 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.pm.PackageManager; +import android.graphics.Color; import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; @@ -99,6 +101,13 @@ public void onConfigureActionBar(ActionBar actionBar) { actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayShowCustomEnabled(true); + // Coloring Toolbar + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getAccentColor())); + } else { + actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getToolBarColor())); + } + // Loading Toolbar header views ActorStyle style = ActorSDK.sharedActor().style; barView = LayoutInflater.from(getActivity()).inflate(R.layout.bar_conversation, null); @@ -130,7 +139,7 @@ public void onConfigureActionBar(ActionBar actionBar) { barAvatar.init(Screen.dp(32), 18); barView.findViewById(R.id.titleContainer).setOnClickListener(v -> { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { ActorSDKLauncher.startProfileActivity(getActivity(), peer.getPeerId()); } else if (peer.getPeerType() == PeerType.GROUP) { ActorSDK.sharedActor().startGroupInfoActivity(getActivity(), peer.getPeerId()); @@ -147,7 +156,7 @@ public void onResume() { // Performing all required Data Binding here - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { // Loading user final UserVM user = users().get(peer.getPeerId()); @@ -161,7 +170,6 @@ public void onResume() { // Binding User name to Toolbar bind(barTitle, user.getName()); - bind(user.getIsVerified(), (val, valueModel) -> { barTitle.setCompoundDrawablesWithIntrinsicBounds(null, null, val ? new TintDrawable( @@ -175,7 +183,11 @@ public void onResume() { bind(barSubtitle, user); // Binding User typing to Toolbar - bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); + } else { + // TODO: Implement + } // Refresh menu on contact state change bind(user.isContact(), (val, valueModel) -> { @@ -207,7 +219,9 @@ public void onResume() { } // Show/Hide Avatar - if (!style.isShowAvatarInTitle() || (peer.getPeerType() == PeerType.PRIVATE && !style.isShowAvatarPrivateInTitle())) { + if (!style.isShowAvatarInTitle() || + ((peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) + && !style.isShowAvatarPrivateInTitle())) { barAvatar.setVisibility(View.GONE); } @@ -358,7 +372,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in private void startCall(boolean video) { Command cmd; - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { cmd = video ? messenger().doVideoCall(peer.getPeerId()) : messenger().doCall(peer.getPeerId()); } else { cmd = messenger().doGroupCall(peer.getPeerId()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index cba7c1f39c..5331ac231f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -176,6 +176,24 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun }); + // + // New Encrypted Message + // + + View newEncryptedMessageView = res.findViewById(R.id.newEncryptedMessage); + ImageView newEncryptedMessageIcon = (ImageView) newEncryptedMessageView.findViewById(R.id.newEncryptedMessageIcon); + TextView newEncryptedMessageTitle = (TextView) newEncryptedMessageView.findViewById(R.id.newEncryptedMessageText); + { + Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_chat_black_24dp)); + DrawableCompat.setTint(drawable, style.getListActionColor()); + newEncryptedMessageIcon.setImageDrawable(drawable); + newEncryptedMessageTitle.setTextColor(style.getListActionColor()); + } + newEncryptedMessageView.setOnClickListener(v -> { + startActivity(Intents.openPrivateSecretDialog(user.getId(), true, getActivity())); + }); + + // // Voice Call // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index dd2a761e8d..6d1222b075 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -113,6 +113,33 @@ android:textColor="@color/action" /> + + + + + + + Add to Contacts Voice Call New Message + Secret Chat Mobile phone diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java index ee160ced49..0a52a7b948 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java @@ -16,12 +16,7 @@ public class Peer extends BserObject { - public static final BserCreator CREATOR = new BserCreator() { - @Override - public Peer createInstance() { - return new Peer(); - } - }; + public static final BserCreator CREATOR = Peer::new; public static Peer fromBytes(byte[] data) throws IOException { return Bser.parse(new Peer(), data); @@ -37,6 +32,8 @@ public static Peer fromUniqueId(long uid) { return new Peer(PeerType.PRIVATE, id); case 1: return new Peer(PeerType.GROUP, id); + case 2: + return new Peer(PeerType.PRIVATE_ENCRYPTED, id); } } @@ -48,6 +45,10 @@ public static Peer group(int gid) { return new Peer(PeerType.GROUP, gid); } + public static Peer secret(int uid) { + return new Peer(PeerType.PRIVATE_ENCRYPTED, uid); + } + @Property("readonly, nonatomic") private PeerType peerType; @Property("readonly, nonatomic") @@ -72,6 +73,9 @@ public long getUnuqueId() { case GROUP: type = 1; break; + case PRIVATE_ENCRYPTED: + type = 2; + break; } return ((long) peerId & 0xFFFFFFFFL) + (((long) type & 0xFFFFFFFFL) << 32); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java index 79291bee86..8b8d0cb606 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java @@ -12,31 +12,17 @@ public class PeerSession extends BserObject { public static Predicate BY_THEIR_GROUP(final int theirKeyGroupId) { - return new Predicate() { - @Override - public boolean apply(PeerSession session) { - return session.getTheirKeyGroupId() == theirKeyGroupId; - } - }; + return session -> session.getTheirKeyGroupId() == theirKeyGroupId; } public static Predicate BY_IDS(final int theirKeyGroupId, final long ownPreKeyId, final long theirPreKeyId) { - return new Predicate() { - @Override - public boolean apply(PeerSession session) { - return session.getTheirKeyGroupId() == theirKeyGroupId && - session.getOwnPreKeyId() == ownPreKeyId && - session.getTheirPreKeyId() == theirPreKeyId; - } - }; + return session -> session.getTheirKeyGroupId() == theirKeyGroupId && + session.getOwnPreKeyId() == ownPreKeyId && + session.getTheirPreKeyId() == theirPreKeyId; } - public static final Comparator COMPARATOR = new Comparator() { - @Override - public int compare(PeerSession lhs, PeerSession rhs) { - return ByteStrings.compare(lhs.getMasterKey(), rhs.getMasterKey()); - } - }; + public static final Comparator COMPARATOR = (lhs, rhs) -> + ByteStrings.compare(lhs.getMasterKey(), rhs.getMasterKey()); private long sid; private int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java index 154b79bcf1..95ea8b2c72 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java @@ -73,7 +73,8 @@ public ApiOutPeer buidOutPeer(Peer peer) { } return new ApiOutPeer(ApiPeerType.GROUP, group.getGroupId(), group.getAccessHash()); } else { - throw new RuntimeException("Unknown peer: " + peer); + //throw new RuntimeException("Unknown peer: " + peer); + return null; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java index 210d7caf1e..3337f28128 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java @@ -4,6 +4,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; @@ -21,7 +22,7 @@ import static im.actor.runtime.promise.Promise.success; /** - * Axolotl Ratchet encryption session + * Double Ratchet encryption session * Session is identified by: * 1) Destination User's Id * 2) Own Key Group Id @@ -115,25 +116,8 @@ private Promise onDecrypt(final byte[] data) { Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(new Function() { - @Override - public DecryptedPackage apply(EncryptedSessionChain encryptedSessionChain) { - return decrypt(encryptedSessionChain, data); - } - }) - .then(new Consumer() { - @Override - public void apply(DecryptedPackage decryptedPackage) { - Log.d(TAG, "onDecrypted"); - latestTheirEphemeralKey = senderEphemeralKey; - } - }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.d(TAG, "onError"); - } - }); + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, data)) + .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { @@ -170,6 +154,7 @@ private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { } private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { + EncryptedSessionChain pickedChain = null; for (EncryptedSessionChain c : decryptionChains) { if (ByteStrings.isEquals(Curve25519.keyGenPublic(c.getOwnPrivateKey()), ephemeralKey)) { @@ -177,30 +162,21 @@ private Promise pickDecryptChain(final byte[] theirEpheme break; } } - return success(pickedChain) - .flatMap(new Function>() { - @Override - public Promise apply(EncryptedSessionChain src) { - if (src != null) { - return success(src); - } - - // TODO: Implement! - return null; -// return ask(context().getEncryption().getKeyManager(), new FetchOwnPreKeyByPublic(ephemeralKey)) -// .map(new Function() { -// @Override -// public EncryptedSessionChain apply(PrivateKey src) { -// EncryptedSessionChain chain = new EncryptedSessionChain(session, src.getKey(), theirEphemeralKey); -// decryptionChains.add(0, chain); -// if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { -// decryptionChains.remove(MAX_DECRYPT_CHAINS) -// .safeErase(); -// } -// return chain; -// } -// }); + if (pickedChain != null) { + return Promise.success(pickedChain); + } + + + return context().getEncryption().getKeyManager().getOwnPreKey(ephemeralKey) + .map(src1 -> { + EncryptedSessionChain chain = new EncryptedSessionChain(session, + src1.getKey(), theirEphemeralKey); + decryptionChains.add(0, chain); + if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { + decryptionChains.remove(MAX_DECRYPT_CHAINS) + .safeErase(); } + return chain; }); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 75b01f2a20..281227f9ab 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -16,8 +16,8 @@ public class EncryptionModule extends AbsModule { - private KeyManagerInt keyManagerInt; - private SessionManagerInt sessionManagerInt; + private KeyManagerInt keyManager; + private SessionManagerInt sessionManager; private ActorRef messageEncryptor; private final HashMap encryptedStates = new HashMap<>(); @@ -28,24 +28,20 @@ public EncryptionModule(ModuleContext context) { public void run() { - keyManagerInt = new KeyManagerInt(context()); - - // Session Manager - ActorRef sessionManager = system().actorOf("encryption/sessions", - () -> new SessionManagerActor(context())); - sessionManagerInt = new SessionManagerInt(sessionManager); + keyManager = new KeyManagerInt(context()); + sessionManager = new SessionManagerInt(context()); messageEncryptor = system().actorOf("encryption/messaging", () -> new EncryptedMsgActor(context())); } - public SessionManagerInt getSessionManager() { - return sessionManagerInt; + public KeyManagerInt getKeyManager() { + return keyManager; } - public KeyManagerInt getKeyManager() { - return keyManagerInt; + public SessionManagerInt getSessionManager() { + return sessionManager; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java index d109f37df3..bbd142cf8d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java @@ -75,6 +75,16 @@ public Promise getOwnPreKey(long id) { return ask(new KeyManagerActor.FetchOwnPreKeyById(id)); } + /** + * Load own pre key by public key + * + * @param publicKey public key + * @return promise of private key + */ + public Promise getOwnPreKey(byte[] publicKey) { + return ask(new KeyManagerActor.FetchOwnPreKeyByPublic(publicKey)); + } + /** * Loading random user's pre key from key group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java index e17332f0f5..73314fdec5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java @@ -70,49 +70,38 @@ protected PeerSessionsStorage deserialize(byte[] data) { * @param uid User's id * @param keyGroupId User's key group */ - public Promise pickSession(final int uid, - final int keyGroupId) { - -// return pickCachedSession(uid, keyGroupId) -// .fallback(new Function>() { -// @Override -// public Promise apply(Exception e) { -// return Promises.tuple( -// keyManager.getOwnIdentity(), -// keyManager.getOwnRandomPreKey(), -// keyManager.getUserKeyGroups(uid), -// keyManager.getUserRandomPreKey(uid, keyGroupId)) -// .flatMap(new FunctionTupled4>() { -// @Override -// public Promise apply(KeyManagerActor.OwnIdentity ownIdentity, -// PrivateKey ownPreKey, UserKeys userKeys, -// PublicKey theirPreKey) { -// -// UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) -// .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) -// .first(); -// -// spawnSession(uid, -// ownIdentity.getKeyGroup(), -// keyGroupId, -// ownIdentity.getIdentityKey(), -// keysGroup.getIdentityKey(), -// ownPreKey, -// theirPreKey); -// -// return Promise.success(null); -// } -// }); -// } -// }) -// .afterVoid(new Supplier>() { -// @Override -// public Promise get() { -// return pickCachedSession(uid, keyGroupId); -// } -// }); - return null; + public Promise pickSession(final int uid, + final int keyGroupId) { + + return pickCachedSession(uid, keyGroupId) + .fallback(e -> Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnRandomPreKey(), + keyManager.getUserKeyGroups(uid), + keyManager.getUserRandomPreKey(uid, keyGroupId)) + .flatMap(new FunctionTupled4>() { + @Override + public Promise apply(OwnIdentity ownIdentity, + PrivateKey ownPreKey, + UserKeys userKeys, + PublicKey theirPreKey) { + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + + return Promise.success(null); + } + })) + .flatMap(peerSession -> pickCachedSession(uid, keyGroupId)); } /** @@ -129,33 +118,31 @@ public Promise pickSession(final int uid, final long theirKeyId) { return pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId) - .fallback(new Function>() { - @Override - public Promise apply(Exception e) { - return Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnPreKey(ownKeyId), - keyManager.getUserKeyGroups(uid), - keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { - @Override - public PeerSession apply(OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - return spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - } - }); - } - }); + .fallback(e -> Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnPreKey(ownKeyId), + keyManager.getUserKeyGroups(uid), + keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) + .map(new FunctionTupled4() { + @Override + public PeerSession apply(OwnIdentity ownIdentity, + PrivateKey ownPreKey, + UserKeys userKeys, + PublicKey theirPreKey) { + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + return spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + } + })); } /** @@ -208,7 +195,7 @@ private PeerSession spawnSession(int uid, PeerSessionsStorage sessionsStorage = peerSessions.getValue(uid); if (sessionsStorage == null) { - sessionsStorage = new PeerSessionsStorage(uid, new ArrayList()); + sessionsStorage = new PeerSessionsStorage(uid, new ArrayList<>()); } sessionsStorage = sessionsStorage.addSession(peerSession); peerSessions.addOrUpdateItem(sessionsStorage); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java index 4760bc6501..0f0ed06592 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java @@ -1,20 +1,42 @@ package im.actor.core.modules.encryption; import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + +/** + * Session Manager for encrypted chats + */ public class SessionManagerInt extends ActorInterface { - public SessionManagerInt(ActorRef dest) { - super(dest); + public SessionManagerInt(ModuleContext context) { + super(system().actorOf("encryption/sessions", () -> new SessionManagerActor(context))); } + /** + * Pick fresh session with random pre keys + * + * @param uid user's id + * @param keyGroup key group id + * @return promise of session + */ public Promise pickSession(int uid, int keyGroup) { return ask(new SessionManagerActor.PickSessionForEncrypt(uid, keyGroup)); } + /** + * Pick session with specific identity keys + * + * @param uid user's id + * @param keyGroup key group id + * @param ownKeyId own identity prekey id + * @param theirKeyId their identity prekey id + * @return + */ public Promise pickSession(int uid, int keyGroup, long ownKeyId, long theirKeyId) { return ask(new SessionManagerActor.PickSessionForDecrypt(uid, keyGroup, theirKeyId, ownKeyId)); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java index 6e1fdf1b41..e252acd6a8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java @@ -5,12 +5,7 @@ public class EncryptedBoxKey { public static Predicate FILTER(final int myUid, final int keyGroupId) { - return new Predicate() { - @Override - public boolean apply(EncryptedBoxKey boxKey) { - return boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; - } - }; + return boxKey -> boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; } private final int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java index b76d4d000e..5a2ab94324 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java @@ -16,6 +16,7 @@ import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.Reaction; import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.api.ApiSupportConfiguration; @@ -52,10 +53,15 @@ public ConversationHistoryActor(Peer peer, ModuleContext context) { @Override public void preStart() { super.preStart(); - historyMaxDate = preferences().getLong(KEY_LOADED_DATE, Long.MAX_VALUE); - historyLoaded = preferences().getBool(KEY_LOADED, false); - if (!preferences().getBool(KEY_LOADED_INIT, false)) { - self().send(new LoadMore()); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + historyMaxDate = 0; + historyLoaded = true; + } else { + historyMaxDate = preferences().getLong(KEY_LOADED_DATE, Long.MAX_VALUE); + historyLoaded = preferences().getBool(KEY_LOADED, false); + if (!preferences().getBool(KEY_LOADED_INIT, false)) { + self().send(new LoadMore()); + } } } From eac57a40cd991be44ac7ad4dd24c7b816f0938a1 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 06:55:30 +0300 Subject: [PATCH 04/81] feat(core): Restored encrypting of messages (decryption is not working yet) --- .../im/actor/core/modules/ModuleActor.java | 11 +- .../encryption/EncryptedPeerActor.java | 420 ------------------ .../modules/encryption/EncryptionModule.java | 58 +-- ...rocessor.java => EncryptionProcessor.java} | 4 +- .../encryption/ratchet/EncryptedMsg.java | 30 ++ .../{ => ratchet}/EncryptedMsgActor.java | 60 +-- .../encryption/ratchet/EncryptedSession.java | 27 ++ .../{ => ratchet}/EncryptedSessionActor.java | 24 +- .../encryption/ratchet/EncryptedUser.java | 48 ++ .../ratchet/EncryptedUserActor.java | 329 ++++++++++++++ .../KeyManager.java} | 7 +- .../{ => ratchet}/KeyManagerActor.java | 14 +- .../SessionManager.java} | 13 +- .../{ => ratchet}/SessionManagerActor.java | 132 +++--- .../messaging/actions/SenderActor.java | 62 ++- .../sequence/processor/UpdateProcessor.java | 11 +- 16 files changed, 639 insertions(+), 611 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{EncryptedProcessor.java => EncryptionProcessor.java} (91%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/EncryptedMsgActor.java (65%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/EncryptedSessionActor.java (93%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{KeyManagerInt.java => ratchet/KeyManager.java} (94%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/KeyManagerActor.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{SessionManagerInt.java => ratchet/SessionManager.java} (76%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/SessionManagerActor.java (67%) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java index 95ea8b2c72..d04d09c428 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java @@ -72,9 +72,14 @@ public ApiOutPeer buidOutPeer(Peer peer) { return null; } return new ApiOutPeer(ApiPeerType.GROUP, group.getGroupId(), group.getAccessHash()); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + User user = getUser(peer.getPeerId()); + if (user == null) { + return null; + } + return new ApiOutPeer(ApiPeerType.ENCRYPTEDPRIVATE, user.getUid(), user.getAccessHash()); } else { - //throw new RuntimeException("Unknown peer: " + peer); - return null; + throw new RuntimeException("Unknown peer: " + peer); } } @@ -83,6 +88,8 @@ public ApiPeer buildApiPeer(Peer peer) { return new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()); } else if (peer.getPeerType() == PeerType.GROUP) { return new ApiPeer(ApiPeerType.GROUP, peer.getPeerId()); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return new ApiPeer(ApiPeerType.ENCRYPTEDPRIVATE, peer.getPeerId()); } else { return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java deleted file mode 100644 index 079daff534..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java +++ /dev/null @@ -1,420 +0,0 @@ -package im.actor.core.modules.encryption; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import im.actor.core.entity.encryption.PeerSession; -import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; -import im.actor.core.modules.ModuleActor; -import im.actor.core.util.RandomUtils; -import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Props; -import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.crypto.Cryptos; -import im.actor.runtime.crypto.IntegrityException; -import im.actor.runtime.crypto.primitives.prf.PRF; -import im.actor.runtime.function.Consumer; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Predicate; -import im.actor.runtime.promise.Promise; -import im.actor.runtime.crypto.box.ActorBox; -import im.actor.runtime.crypto.box.ActorBoxKey; -import im.actor.runtime.crypto.primitives.util.ByteStrings; -import im.actor.runtime.promise.Promises; -import im.actor.runtime.promise.PromisesArray; -import im.actor.runtime.function.Tuple2; - -import static im.actor.runtime.promise.Promise.success; - -public class EncryptedPeerActor extends ModuleActor { - - private final String TAG; - - private final int uid; - - private int ownKeyGroupId; - private UserKeys theirKeys; - - private HashMap activeSessions = new HashMap<>(); - private HashSet ignoredKeyGroups = new HashSet<>(); - - private boolean isReady = false; - private KeyManagerInt keyManager; - - private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); - - public EncryptedPeerActor(int uid, ModuleContext context) { - super(context); - this.uid = uid; - TAG = "EncryptedPeerActor#" + uid; - } - - @Override - public void preStart() { - super.preStart(); - - keyManager = context().getEncryption().getKeyManager(); - - Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getUserKeyGroups(uid)) - .then(new Consumer>() { - @Override - public void apply(Tuple2 res) { - Log.d(TAG, "then"); - ownKeyGroupId = res.getT1().getKeyGroup(); - theirKeys = res.getT2(); - isReady = true; - unstashAll(); - } - }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Unable to fetch initial parameters"); - Log.e(TAG, e); - } - }); - } - - private Promise doEncrypt(final byte[] data) { - - if (!isReady) { - stash(); - return null; - } - - // - // Stage 1: Loading User Key Groups - // Stage 2: Pick sessions for encryption - // Stage 3: Encrypt box_key int session - // Stage 4: Encrypt box - // - final byte[] encKey = Crypto.randomBytes(32); - final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "doEncrypt"); - final long start = Runtime.getActorTime(); - return PromisesArray.of(theirKeys.getUserKeysGroups()) - .filter(new Predicate() { - @Override - public boolean apply(UserKeysGroup keysGroup) { - return !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()); - } - }) - .mapOptional(new Function>() { - @Override - public Promise apply(final UserKeysGroup keysGroup) { - if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { - return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); - } - return context().getEncryption().getSessionManager() - .pickSession(uid, keysGroup.getKeyGroupId()) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - ignoredKeyGroups.add(keysGroup.getKeyGroupId()); - } - }) - .map(new Function() { - @Override - public SessionActor apply(PeerSession src) { - return spawnSession(src); - } - }); - } - }) - .mapOptional(encrypt(encKeyExtended)) - .zip() - .map(new Function, EncryptBoxResponse>() { - @Override - public EncryptBoxResponse apply(List src) { - - if (src.size() == 0) { - throw new RuntimeException("No sessions available"); - } - - Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - - ArrayList encryptedKeys = new ArrayList<>(); - for (EncryptedSessionActor.EncryptedPackageRes r : src) { - Log.d(TAG, "Keys: " + r.getKeyGroupId()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); - } - - byte[] encData; - try { - encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); - } catch (IntegrityException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - - Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - - return new EncryptBoxResponse(new EncryptedBox( - encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), - ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData))); - } - }); - } - - private Promise doDecrypt(final EncryptedBox data) { - - if (!isReady) { - stash(); - return null; - } - - final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); - final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); - - // - // Picking session - // - - if (ignoredKeyGroups.contains(senderKeyGroup)) { - throw new RuntimeException("This key group is ignored"); - } - - return PromisesArray.of(data.getKeys()) - .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) - .first() - .flatMap(new Function>>() { - @Override - public Promise> apply(final EncryptedBoxKey boxKey) { - final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); - final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); - - if (activeSessions.containsKey(boxKey.getKeyGroupId())) { - for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { - if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && - s.getSession().getTheirPreKeyId() == senderPreKeyId) { - return success(new Tuple2<>(s, boxKey)); - } - } - } - return context().getEncryption().getSessionManager() - .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) - .map(new Function>() { - @Override - public Tuple2 apply(PeerSession src) { - return new Tuple2<>(spawnSession(src), boxKey); - } - }); - } - }) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(Tuple2 src) { - Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); - // TODO: Implement - return null; - } - }) - .map(new Function() { - @Override - public DecryptBoxResponse apply(EncryptedSessionActor.DecryptedPackage decryptedPackage) { - byte[] encData; - try { - byte[] encKeyExtended = decryptedPackage.getData().length >= 128 - ? decryptedPackage.getData() - : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); - encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); - Log.d(TAG, "Box size: " + encData.length); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - return new DecryptBoxResponse(encData); - } - }); - } - - private void onKeysUpdated(UserKeys userKeys) { - if (!isReady) { - stash(); - return; - } - - this.theirKeys = userKeys; - } - - private SessionActor spawnSession(final PeerSession session) { - - ActorRef res = system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedSessionActor create() { - return new EncryptedSessionActor(context(), session); - } - }), getPath() + "/k_" + RandomUtils.nextRid()); - - SessionActor cont = new SessionActor(res, session); - - if (activeSessions.containsKey(session.getTheirKeyGroupId())) { - activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); - } else { - ArrayList l = new ArrayList<>(); - l.add(cont); - activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); - } - return cont; - } - - private Function> encrypt(final byte[] encKey) { - return new Function>() { - @Override - public Promise apply(SessionActor sessionActor) { - // return ask(sessionActor.getActorRef(), new EncryptedSessionActor.EncryptPackage(encKey)); - // TODO: Implement - return null; - } - }; - } - - // - // Messages - // - - @Override - public Promise onAsk(Object message) throws Exception { - if (message instanceof EncryptBox) { - if (!isReady) { - stash(); - return null; - } - return doEncrypt(((EncryptBox) message).getData()); - } else if (message instanceof DecryptBox) { - if (!isReady) { - stash(); - return null; - } - return doDecrypt(((DecryptBox) message).getEncryptedBox()); - } else { - return super.onAsk(message); - } - } - - @Override - public void onReceive(Object message) { - if (message instanceof KeyGroupUpdated) { - onKeysUpdated(((KeyGroupUpdated) message).getUserKeys()); - } else { - super.onReceive(message); - } - } - - public static class EncryptBox implements AskMessage { - private byte[] data; - - public EncryptBox(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } - - public static class EncryptBoxResponse extends AskResult { - - private EncryptedBox box; - - public EncryptBoxResponse(EncryptedBox box) { - this.box = box; - } - - public EncryptedBox getBox() { - return box; - } - } - - public static class DecryptBox implements AskMessage { - - private EncryptedBox encryptedBox; - - public DecryptBox(EncryptedBox encryptedBox) { - this.encryptedBox = encryptedBox; - } - - public EncryptedBox getEncryptedBox() { - return encryptedBox; - } - } - - public static class DecryptBoxResponse extends AskResult { - - private byte[] data; - - public DecryptBoxResponse(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } - - private class SessionHolder { - - private int keyGroupId; - private ArrayList sessions; - - public SessionHolder(int keyGroupId, ArrayList sessions) { - this.keyGroupId = keyGroupId; - this.sessions = sessions; - } - - public int getKeyGroupId() { - return keyGroupId; - } - - public ArrayList getSessions() { - return sessions; - } - } - - private class SessionActor { - - private ActorRef actorRef; - private PeerSession session; - - public SessionActor(ActorRef actorRef, PeerSession session) { - this.actorRef = actorRef; - this.session = session; - } - - public ActorRef getActorRef() { - return actorRef; - } - - public PeerSession getSession() { - return session; - } - } - - public static class KeyGroupUpdated { - - private UserKeys userKeys; - - public KeyGroupUpdated(UserKeys userKeys) { - this.userKeys = userKeys; - } - - public UserKeys getUserKeys() { - return userKeys; - } - } -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 281227f9ab..508cdd75c4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -2,25 +2,28 @@ import java.util.HashMap; +import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiMessage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.EncryptedPeerActor; -import im.actor.core.modules.encryption.KeyManagerActor; -import im.actor.core.modules.encryption.EncryptedMsgActor; -import im.actor.core.modules.encryption.KeyManagerInt; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Props; +import im.actor.core.modules.encryption.ratchet.EncryptedMsg; +import im.actor.core.modules.encryption.ratchet.EncryptedMsgActor; +import im.actor.core.modules.encryption.ratchet.EncryptedUser; +import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; +import im.actor.core.modules.encryption.ratchet.KeyManager; +import im.actor.core.modules.encryption.ratchet.SessionManager; +import im.actor.core.network.mtp.entity.EncryptedPackage; +import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; public class EncryptionModule extends AbsModule { - private KeyManagerInt keyManager; - private SessionManagerInt sessionManager; + private KeyManager keyManager; + private SessionManager sessionManager; + private EncryptedMsg encryption; - private ActorRef messageEncryptor; - private final HashMap encryptedStates = new HashMap<>(); + private final HashMap users = new HashMap<>(); public EncryptionModule(ModuleContext context) { super(context); @@ -28,34 +31,37 @@ public EncryptionModule(ModuleContext context) { public void run() { - keyManager = new KeyManagerInt(context()); + keyManager = new KeyManager(context()); - sessionManager = new SessionManagerInt(context()); + sessionManager = new SessionManager(context()); - messageEncryptor = system().actorOf("encryption/messaging", - () -> new EncryptedMsgActor(context())); + encryption = new EncryptedMsg(system().actorOf("encryption/messaging", + () -> new EncryptedMsgActor(context()))); } - public KeyManagerInt getKeyManager() { + public KeyManager getKeyManager() { return keyManager; } - public SessionManagerInt getSessionManager() { + public SessionManager getSessionManager() { return sessionManager; } - - public ActorRef getMessageEncryptor() { - return messageEncryptor; + public EncryptedMsg getEncryption() { + return encryption; } - public ActorRef getEncryptedChatManager(final int uid) { - synchronized (encryptedStates) { - if (!encryptedStates.containsKey(uid)) { - encryptedStates.put(uid, system().actorOf("encryption/uid_" + uid, - () -> new EncryptedPeerActor(uid, context()))); + public EncryptedUser getEncryptedUser(int uid) { + synchronized (users) { + if (!users.containsKey(uid)) { + users.put(uid, new EncryptedUser(system().actorOf("encryption/uid_" + uid, + () -> new EncryptedUserActor(uid, context())))); } - return encryptedStates.get(uid); + return users.get(uid); } } + + public Promise encrypt(int uid, ApiMessage message) { + return getEncryption().encrypt(uid, message); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java similarity index 91% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index b510988930..d173ba72a5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -10,9 +10,9 @@ import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; -public class EncryptedProcessor extends AbsModule implements SequenceProcessor { +public class EncryptionProcessor extends AbsModule implements SequenceProcessor { - public EncryptedProcessor(ModuleContext context) { + public EncryptionProcessor(ModuleContext context) { super(context); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java new file mode 100644 index 0000000000..ee0f911348 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -0,0 +1,30 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiMessage; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Entry point for message encryption + */ +public class EncryptedMsg extends ActorInterface { + + public EncryptedMsg(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypt Message for private secret chat + * + * @param uid user's id + * @param message message content + * @return promise of encrypted message + */ + public Promise encrypt(int uid, ApiMessage message) { + return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java similarity index 65% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 9247c9a773..3f587d906f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -1,10 +1,9 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedBoxSignature; import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.api.ApiMessage; @@ -14,9 +13,7 @@ import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ask.AskCallback; -import im.actor.runtime.function.Function; +import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.promise.Promise; public class EncryptedMsgActor extends ModuleActor { @@ -27,28 +24,23 @@ public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { + private Promise doEncrypt(int uid, ApiMessage message) throws IOException { Log.d(TAG, "doEncrypt"); -// return ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedPeerActor.EncryptBox(message.buildContainer())) -// .map(new Function() { -// @Override -// public EncryptedMessage apply(EncryptedPeerActor.EncryptBoxResponse encryptBoxResponse) { -// Log.d(TAG, "doEncrypt:onResult"); -// ArrayList boxKeys = new ArrayList(); -// for (EncryptedBoxKey b : encryptBoxResponse.getBox().getKeys()) { -// boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), -// b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); -// } -// ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", encryptBoxResponse.getBox().getEncryptedPackage(), -// new ArrayList()); -// ApiEncryptedMessage apiEncryptedMessage = new ApiEncryptedMessage(apiEncryptedBox); -// return new EncryptedMessage(apiEncryptedMessage); -// } -// }); - - // TODO: Implement - return null; + return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) + .map(encryptBoxResponse -> { + Log.d(TAG, "doEncrypt:onResult"); + ArrayList boxKeys = new ArrayList<>(); + for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { + boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), + b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); + } + ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, + boxKeys, "aes-kuznechik", + encryptBoxResponse.getEncryptedPackage(), + new ArrayList<>()); + return new ApiEncryptedMessage(apiEncryptedBox); + }); } public void onDecrypt(int uid, ApiEncryptedMessage message) { @@ -64,11 +56,11 @@ public void onDecrypt(int uid, ApiEncryptedMessage message) { final EncryptedBox encryptedBox = new EncryptedBox(encryptedBoxKeys.toArray(new EncryptedBoxKey[0]), message.getBox().getEncPackage()); // TODO: Implement -// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedPeerActor.DecryptBox(encryptedBox), new AskCallback() { +// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedUserActor.DecryptBox(encryptedBox), new AskCallback() { // @Override // public void onResult(Object obj) { // Log.d(TAG, "onDecrypt:onResult in " + (Runtime.getActorTime() - start) + " ms"); -// EncryptedPeerActor.DecryptBoxResponse re = (EncryptedPeerActor.DecryptBoxResponse) obj; +// EncryptedUserActor.DecryptBoxResponse re = (EncryptedUserActor.DecryptBoxResponse) obj; // try { // ApiMessage message = ApiMessage.fromBytes(re.getData()); // Log.d(TAG, "onDecrypt:onResult " + message); @@ -122,7 +114,7 @@ public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMess } } - public static class EncryptMessage { + public static class EncryptMessage implements AskMessage { private int uid; private ApiMessage message; @@ -141,18 +133,6 @@ public ApiMessage getMessage() { } } - public static class EncryptedMessage { - private ApiEncryptedMessage encryptedMessage; - - public EncryptedMessage(ApiEncryptedMessage encryptedMessage) { - this.encryptedMessage = encryptedMessage; - } - - public ApiEncryptedMessage getEncryptedMessage() { - return encryptedMessage; - } - } - public static class DecryptMessage { private ApiEncryptedMessage encryptedMessage; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java new file mode 100644 index 0000000000..b9979d921d --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -0,0 +1,27 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Double Ratchet encrypted session operations + */ +public class EncryptedSession extends ActorInterface { + + public EncryptedSession(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypt data for session + * + * @param data for encryption + * @return promise of encrypted package + */ + public Promise encrypt(byte[] data) { + return ask(new EncryptedSessionActor.EncryptPackage(data)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java similarity index 93% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index 3337f28128..c8e3944c7f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -1,19 +1,15 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.util.ArrayList; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.function.Consumer; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -55,7 +51,7 @@ public class EncryptedSessionActor extends ModuleActor { // Key Manager reference // - private KeyManagerInt keyManager; + private KeyManager keyManager; // // Temp encryption chains @@ -90,10 +86,15 @@ private Promise onEncrypt(final byte[] data) { // Stage 3: Decrypt // - return success(latestTheirEphemeralKey) - .mapIfNullPromise(() -> - keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) - .map(PublicKey::getPublicKey)) + Promise ephemeralKey; + if (latestTheirEphemeralKey != null) { + ephemeralKey = success(latestTheirEphemeralKey); + } else { + ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) + .map(PublicKey::getPublicKey); + } + + return ephemeralKey .map(publicKey -> pickEncryptChain(publicKey)) .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } @@ -130,8 +131,9 @@ private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { return encryptionChains.get(0); } + EncryptedSessionChain chain = new EncryptedSessionChain(session, + Curve25519.keyGenPrivate(Crypto.randomBytes(32)), ephemeralKey); - EncryptedSessionChain chain = new EncryptedSessionChain(session, Curve25519.keyGenPrivate(Crypto.randomBytes(32)), ephemeralKey); encryptionChains.add(0, chain); return chain; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java new file mode 100644 index 0000000000..16294d582e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java @@ -0,0 +1,48 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.modules.encryption.entity.EncryptedBox; +import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Encrypting data for private Secret Chats + */ +public class EncryptedUser extends ActorInterface { + + public EncryptedUser(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypting data + * + * @param data data for encryption + * @return promise of encrypted box + */ + public Promise encrypt(byte[] data) { + return ask(new EncryptedUserActor.EncryptBox(data)); + } + + /** + * Decrypting data + * + * @param data data for decryption + * @return promise of decrypted box + */ + public Promise decrypt(EncryptedBox data) { + return ask(new EncryptedUserActor.DecryptBox(data)); + } + + /** + * Notify about user keys updated for refreshing internal keys cache + * + * @param updatedUserKeys updated user keys + */ + public void onUserKeysChanged(UserKeys updatedUserKeys) { + send(new EncryptedUserActor.KeyGroupUpdated(updatedUserKeys)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java new file mode 100644 index 0000000000..f03b306898 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -0,0 +1,329 @@ +package im.actor.core.modules.encryption.ratchet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.EncryptedBox; +import im.actor.core.modules.encryption.entity.EncryptedBoxKey; +import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.ModuleActor; +import im.actor.core.util.RandomUtils; +import im.actor.runtime.*; +import im.actor.runtime.Runtime; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.ask.AskResult; +import im.actor.runtime.crypto.Cryptos; +import im.actor.runtime.crypto.IntegrityException; +import im.actor.runtime.crypto.primitives.prf.PRF; +import im.actor.runtime.function.Function; +import im.actor.runtime.promise.Promise; +import im.actor.runtime.crypto.box.ActorBox; +import im.actor.runtime.crypto.box.ActorBoxKey; +import im.actor.runtime.crypto.primitives.util.ByteStrings; +import im.actor.runtime.promise.Promises; +import im.actor.runtime.promise.PromisesArray; +import im.actor.runtime.function.Tuple2; + +import static im.actor.runtime.promise.Promise.success; + +public class EncryptedUserActor extends ModuleActor { + + private final String TAG; + + private final int uid; + + private int ownKeyGroupId; + private UserKeys theirKeys; + + private HashMap activeSessions = new HashMap<>(); + private HashSet ignoredKeyGroups = new HashSet<>(); + + private boolean isReady = false; + + private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); + + public EncryptedUserActor(int uid, ModuleContext context) { + super(context); + this.uid = uid; + TAG = "EncryptedUserActor#" + uid; + } + + @Override + public void preStart() { + super.preStart(); + + KeyManager keyManager = context().getEncryption().getKeyManager(); + + Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getUserKeyGroups(uid)) + .then(res -> { + ownKeyGroupId = res.getT1().getKeyGroup(); + theirKeys = res.getT2(); + onLoaded(); + }) + .failure(e -> { + Log.w(TAG, "Unable to fetch initial parameters. Freezing encryption with user #" + uid); + Log.e(TAG, e); + }); + } + + private void onLoaded() { + Log.d(TAG, "Loaded initial parameters"); + isReady = true; + unstashAll(); + } + + private Promise doEncrypt(final byte[] data) { + + + // + // Stage 1: Loading User Key Groups + // Stage 2: Pick sessions for encryption + // Stage 3: Encrypt box_key int session + // Stage 4: Encrypt box + // + final byte[] encKey = Crypto.randomBytes(32); + final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); + Log.d(TAG, "doEncrypt"); + final long start = Runtime.getActorTime(); + return PromisesArray.of(theirKeys.getUserKeysGroups()) + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId())) + .mapOptional(keysGroup -> { + if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { + return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); + } + return context().getEncryption().getSessionManager() + .pickSession(uid, keysGroup.getKeyGroupId()) + .failure(e -> { + ignoredKeyGroups.add(keysGroup.getKeyGroupId()); + }) + .map(src -> spawnSession(src)); + }) + .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended)) + .zip() + .map(src -> { + + if (src.size() == 0) { + throw new RuntimeException("No sessions available"); + } + + Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + + ArrayList encryptedKeys = new ArrayList<>(); + for (EncryptedSessionActor.EncryptedPackageRes r : src) { + Log.d(TAG, "Keys: " + r.getKeyGroupId()); + encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); + } + + byte[] encData; + try { + encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); + } catch (IntegrityException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + + return new EncryptedBox( + encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), + ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); + }); + } + + private Promise doDecrypt(final EncryptedBox data) { + + final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); + final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); + + // + // Picking session + // + + if (ignoredKeyGroups.contains(senderKeyGroup)) { + throw new RuntimeException("This key group is ignored"); + } + + return PromisesArray.of(data.getKeys()) + .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) + .first() + .flatMap(boxKey -> { + final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); + final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); + + if (activeSessions.containsKey(boxKey.getKeyGroupId())) { + for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { + if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && + s.getSession().getTheirPreKeyId() == senderPreKeyId) { + return success(new Tuple2<>(s, boxKey)); + } + } + } + return context().getEncryption().getSessionManager() + .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) + .map(new Function>() { + @Override + public Tuple2 apply(PeerSession src) { + return new Tuple2<>(spawnSession(src), boxKey); + } + }); + }) + .flatMap((Function, Promise>) src -> { + Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); + // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); + // TODO: Implement + return null; + }) + .map(decryptedPackage -> { + byte[] encData; + try { + byte[] encKeyExtended = decryptedPackage.getData().length >= 128 + ? decryptedPackage.getData() + : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); + encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); + Log.d(TAG, "Box size: " + encData.length); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return encData; + }); + } + + private void onKeysUpdated(UserKeys userKeys) { + this.theirKeys = userKeys; + } + + private SessionActor spawnSession(final PeerSession session) { + + ActorRef res = system().actorOf(getPath() + "/k_" + RandomUtils.nextRid(), + () -> new EncryptedSessionActor(context(), session)); + + SessionActor cont = new SessionActor(new EncryptedSession(res), session); + + if (activeSessions.containsKey(session.getTheirKeyGroupId())) { + activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); + } else { + ArrayList l = new ArrayList<>(); + l.add(cont); + activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); + } + return cont; + } + + // + // Messages + // + + @Override + public Promise onAsk(Object message) throws Exception { + if (!isReady) { + stash(); + return null; + } + + if (message instanceof EncryptBox) { + return doEncrypt(((EncryptBox) message).getData()); + } else if (message instanceof DecryptBox) { + return doDecrypt(((DecryptBox) message).getEncryptedBox()); + } else { + return super.onAsk(message); + } + } + + @Override + public void onReceive(Object message) { + if (message instanceof KeyGroupUpdated) { + if (!isReady) { + stash(); + return; + } + onKeysUpdated(((KeyGroupUpdated) message).getUserKeys()); + } else { + super.onReceive(message); + } + } + + public static class EncryptBox implements AskMessage { + private byte[] data; + + public EncryptBox(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + } + + public static class DecryptBox implements AskMessage { + + private EncryptedBox encryptedBox; + + public DecryptBox(EncryptedBox encryptedBox) { + this.encryptedBox = encryptedBox; + } + + public EncryptedBox getEncryptedBox() { + return encryptedBox; + } + } + + public static class KeyGroupUpdated { + + private UserKeys userKeys; + + public KeyGroupUpdated(UserKeys userKeys) { + this.userKeys = userKeys; + } + + public UserKeys getUserKeys() { + return userKeys; + } + } + + private class SessionHolder { + + private int keyGroupId; + private ArrayList sessions; + + public SessionHolder(int keyGroupId, ArrayList sessions) { + this.keyGroupId = keyGroupId; + this.sessions = sessions; + } + + public int getKeyGroupId() { + return keyGroupId; + } + + public ArrayList getSessions() { + return sessions; + } + } + + private class SessionActor { + + private EncryptedSession encryptedSession; + private PeerSession session; + + public SessionActor(EncryptedSession encryptedSession, PeerSession session) { + this.encryptedSession = encryptedSession; + this.session = session; + } + + public EncryptedSession getEncryptedSession() { + return encryptedSession; + } + + public PeerSession getSession() { + return session; + } + } + +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java similarity index 94% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java index bbd142cf8d..7e20072d5a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.modules.ModuleContext; @@ -7,7 +7,6 @@ import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; @@ -16,14 +15,14 @@ /** * Encryption Key Manager. Used for loading user's keys for encryption/decryption. */ -public class KeyManagerInt extends ActorInterface { +public class KeyManager extends ActorInterface { /** * Default Constructor * * @param context actor context */ - public KeyManagerInt(ModuleContext context) { + public KeyManager(ModuleContext context) { super(system().actorOf("encryption/keys", () -> new KeyManagerActor(context))); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index 13ca85f3cf..ec7f04681c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; @@ -15,6 +15,7 @@ import im.actor.core.api.rpc.RequestUploadPreKey; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.Configuration; import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKeyStorage; import im.actor.core.modules.encryption.entity.PrivateKey; @@ -22,6 +23,7 @@ import im.actor.core.modules.encryption.entity.UserKeysGroup; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -413,8 +415,9 @@ private Promise onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyG if (validatedKeysGroup != null) { UserKeys updatedUserKeys = userKeys.addUserKeyGroup(validatedKeysGroup); cacheUserKeys(updatedUserKeys); - context().getEncryption().getEncryptedChatManager(uid) - .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + context().getEncryption() + .getEncryptedUser(uid) + .onUserKeysChanged(updatedUserKeys); } return Promise.success(null); } @@ -433,8 +436,9 @@ private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId); cacheUserKeys(updatedUserKeys); - context().getEncryption().getEncryptedChatManager(uid) - .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + context().getEncryption() + .getEncryptedUser(uid) + .onUserKeysChanged(updatedUserKeys); return Promise.success(null); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java similarity index 76% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java index 0f0ed06592..90371d77fe 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java @@ -1,19 +1,20 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; /** - * Session Manager for encrypted chats + * Session Manager for encrypted chats. + * Stores and manages encrypted sessions between users. + * Can be asked to pick session parameters for specific users. */ -public class SessionManagerInt extends ActorInterface { +public class SessionManager extends ActorInterface { - public SessionManagerInt(ModuleContext context) { + public SessionManager(ModuleContext context) { super(system().actorOf("encryption/sessions", () -> new SessionManagerActor(context))); } @@ -35,7 +36,7 @@ public Promise pickSession(int uid, int keyGroup) { * @param keyGroup key group id * @param ownKeyId own identity prekey id * @param theirKeyId their identity prekey id - * @return + * @return promise of session */ public Promise pickSession(int uid, int keyGroup, long ownKeyId, long theirKeyId) { return ask(new SessionManagerActor.PickSessionForDecrypt(uid, keyGroup, theirKeyId, ownKeyId)); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java similarity index 67% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 73314fdec5..3fca3939c7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; @@ -22,6 +22,7 @@ import im.actor.runtime.crypto.ratchet.RatchetPublicKey; import im.actor.runtime.function.Function; import im.actor.runtime.function.FunctionTupled4; +import im.actor.runtime.function.Tuple4; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; @@ -35,7 +36,7 @@ public class SessionManagerActor extends ModuleActor { private static final String TAG = "SessionManagerActor"; private KeyValueEngine peerSessions; - private KeyManagerInt keyManager; + private KeyManager keyManager; public SessionManagerActor(ModuleContext context) { super(context); @@ -70,38 +71,39 @@ protected PeerSessionsStorage deserialize(byte[] data) { * @param uid User's id * @param keyGroupId User's key group */ - public Promise pickSession(final int uid, - final int keyGroupId) { - - return pickCachedSession(uid, keyGroupId) - .fallback(e -> Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnRandomPreKey(), - keyManager.getUserKeyGroups(uid), - keyManager.getUserRandomPreKey(uid, keyGroupId)) - .flatMap(new FunctionTupled4>() { - @Override - public Promise apply(OwnIdentity ownIdentity, - PrivateKey ownPreKey, - UserKeys userKeys, - PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - - return Promise.success(null); - } - })) - .flatMap(peerSession -> pickCachedSession(uid, keyGroupId)); + public Promise pickSession(final int uid, + final int keyGroupId) { + + PeerSession cached = pickCachedSession(uid, keyGroupId); + if (cached != null) { + return Promise.success(cached); + } + return Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnRandomPreKey(), + keyManager.getUserKeyGroups(uid), + keyManager.getUserRandomPreKey(uid, keyGroupId)) + .map(tuple -> { + + OwnIdentity ownIdentity = tuple.getT1(); + PrivateKey ownPreKey = tuple.getT2(); + UserKeys userKeys = tuple.getT3(); + PublicKey theirPreKey = tuple.getT4(); + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + + return pickCachedSession(uid, keyGroupId); + }); } /** @@ -117,32 +119,34 @@ public Promise pickSession(final int uid, final long ownKeyId, final long theirKeyId) { - return pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId) - .fallback(e -> Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnPreKey(ownKeyId), - keyManager.getUserKeyGroups(uid), - keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { - @Override - public PeerSession apply(OwnIdentity ownIdentity, - PrivateKey ownPreKey, - UserKeys userKeys, - PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - return spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - } - })); + PeerSession cached = pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId); + if (cached != null) { + return Promise.success(cached); + } + + return Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnPreKey(ownKeyId), + keyManager.getUserKeyGroups(uid), + keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) + .map(tuple -> { + OwnIdentity ownIdentity = tuple.getT1(); + PrivateKey ownPreKey = tuple.getT2(); + UserKeys userKeys = tuple.getT3(); + PublicKey theirPreKey = tuple.getT4(); + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + return spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + }); } /** @@ -209,12 +213,12 @@ private PeerSession spawnSession(int uid, * @param keyGroupId Key Group Id * @return promise of session */ - private Promise pickCachedSession(int uid, final int keyGroupId) { + private PeerSession pickCachedSession(int uid, final int keyGroupId) { return ManagedList.of(peerSessions.getValue(uid)) .flatMap(PeerSessionsStorage.SESSIONS) .filter(PeerSession.BY_THEIR_GROUP(keyGroupId)) .sorted(PeerSession.COMPARATOR) - .firstPromise(); + .firstOrNull(); } /** @@ -226,12 +230,12 @@ private Promise pickCachedSession(int uid, final int keyGroupId) { * @param theirKeyId Their Pre key id * @return promise of session */ - private Promise pickCachedSession(int uid, final int keyGroupId, final long ownKeyId, final long theirKeyId) { + private PeerSession pickCachedSession(int uid, final int keyGroupId, final long ownKeyId, final long theirKeyId) { return ManagedList.of(peerSessions.getValue(uid)) .flatMap(PeerSessionsStorage.SESSIONS) .filter(PeerSession.BY_IDS(keyGroupId, ownKeyId, theirKeyId)) .sorted(PeerSession.COMPARATOR) - .firstPromise(); + .firstOrNull(); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 098ef9dbf9..c6af6d06c7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -14,8 +14,11 @@ import im.actor.core.api.ApiDocumentExAnimation; import im.actor.core.api.ApiDocumentExVoice; +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiFastThumb; import im.actor.core.api.ApiJsonMessage; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiDocumentEx; @@ -25,6 +28,7 @@ import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiTextMessage; import im.actor.core.api.base.SeqUpdate; +import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; @@ -62,6 +66,7 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -465,29 +470,42 @@ private void performSendContent(final Peer peer, final long rid, AbsContent cont } private void performSendApiContent(final Peer peer, final long rid, ApiMessage message, final WakeLock wakeLock) { - final ApiOutPeer outPeer = buidOutPeer(peer); - final ApiPeer apiPeer = buildApiPeer(peer); - if (outPeer == null || apiPeer == null) { - return; - } - request(new RequestSendMessage(outPeer, rid, message, null, null), - new RpcCallback() { - @Override - public void onResult(ResponseSeqDate response) { - self().send(new MessageSent(peer, rid)); - updates().onUpdateReceived(new SeqUpdate(response.getSeq(), - response.getState(), - UpdateMessageSent.HEADER, - new UpdateMessageSent(apiPeer, rid, response.getDate()).toByteArray())); - wakeLock.releaseLock(); - } + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + final ApiOutPeer outPeer = buidOutPeer(peer); + final ApiPeer apiPeer = buildApiPeer(peer); + request(new RequestSendMessage(outPeer, rid, message, null, null), + new RpcCallback() { + @Override + public void onResult(ResponseSeqDate response) { + self().send(new MessageSent(peer, rid)); + updates().onUpdateReceived(new SeqUpdate(response.getSeq(), + response.getState(), + UpdateMessageSent.HEADER, + new UpdateMessageSent(apiPeer, rid, response.getDate()).toByteArray())); + wakeLock.releaseLock(); + } - @Override - public void onError(RpcException e) { - self().send(new MessageError(peer, rid)); - wakeLock.releaseLock(); - } - }); + @Override + public void onError(RpcException e) { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + } + }); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + Log.d("SenderActor", "Pending encrypted message: " + message); + context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { + Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); + // TODO: Implement sending + final ApiOutPeer outPeer = buidOutPeer(peer); + ArrayList peers = new ArrayList<>(); + peers.add(outPeer); + // api(new RequestSendEncryptedPackage(rid, peers,new ArrayList<>(), new ApiEncryptedBox())); + wakeLock.releaseLock(); + }).failure(e -> { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + }); + } } private void onSent(Peer peer, long rid) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java index 90a9637000..97b2afc3e7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java @@ -7,19 +7,16 @@ import java.util.ArrayList; import java.util.List; -import im.actor.core.api.ApiGroup; -import im.actor.core.api.ApiUser; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateMessageRead; import im.actor.core.api.updates.UpdateMessageReadByMe; import im.actor.core.api.updates.UpdateMessageReceived; -import im.actor.core.entity.Group; import im.actor.core.entity.Peer; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.calls.CallsProcessor; import im.actor.core.modules.contacts.ContactsProcessor; -import im.actor.core.modules.encryption.EncryptedProcessor; +import im.actor.core.modules.encryption.EncryptionProcessor; import im.actor.core.modules.eventbus.EventBusProcessor; import im.actor.core.modules.groups.GroupsProcessor; import im.actor.core.modules.presence.PresenceProcessor; @@ -32,13 +29,9 @@ import im.actor.core.modules.users.UsersProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Predicate; import im.actor.runtime.function.Supplier; -import im.actor.runtime.function.Tuple2; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; -import im.actor.runtime.promise.PromisesArray; public class UpdateProcessor extends AbsModule { @@ -63,7 +56,7 @@ public UpdateProcessor(ModuleContext context) { new UsersProcessor(context), new GroupsProcessor(context), new ContactsProcessor(context), - new EncryptedProcessor(context), + new EncryptionProcessor(context), new StickersProcessor(context), new SettingsProcessor(context) }; From 17f085f9ee2264e5b7408481721c18862713d59a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:28:13 +0300 Subject: [PATCH 05/81] feat(core+android): Implemented Encrypted Messages sending (not receiving) --- .../controllers/dialogs/view/DialogView.java | 6 +++ .../res/drawable-hdpi/ic_lock_black_18dp.png | Bin 0 -> 315 bytes .../res/drawable-mdpi/ic_lock_black_18dp.png | Bin 0 -> 234 bytes .../res/drawable-xhdpi/ic_lock_black_18dp.png | Bin 0 -> 349 bytes .../drawable-xxhdpi/ic_lock_black_18dp.png | Bin 0 -> 545 bytes .../drawable-xxxhdpi/ic_lock_black_18dp.png | Bin 0 -> 643 bytes .../main/java/im/actor/core/entity/User.java | 5 +++ .../modules/encryption/EncryptionModule.java | 3 +- .../encryption/ratchet/EncryptedMsg.java | 4 +- .../encryption/ratchet/EncryptedMsgActor.java | 6 +-- .../messaging/actions/SenderActor.java | 35 +++++++++++++++--- .../messaging/dialogs/DialogsActor.java | 16 ++++++-- .../modules/messaging/router/RouterActor.java | 2 +- .../modules/messaging/router/RouterInt.java | 5 +++ .../router/entity/RouterOutgoingSent.java | 4 +- 15 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index f3330dd89c..9835e64a4e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -60,6 +60,7 @@ public class DialogView extends ListItemBackgroundView*Bi`Nfo4!~(=$p@!MwP~!}h{{qC!lvpN%6gR9m)NmrH zk*35l1tf#;`5vZ5VNhEJ3is(i`~yc)CxpKP@l+sYBF3^PGA%*J;lx;0PbJIhiLtDK zN|rSYImQ3!53i(8Rm*CrWLXWdjT=fW0>!5lvDHi{5N`wG17u*3zF>wC1pt~5E=F)$%On5* N002ovPDHLkV1n3#eVzaS literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3dab038e42d3c8e7d6258e1bd2af2c9ca763ab53 GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rhejh-%!Ar-fh6Ati4G~D3m@NJke zwbADR>yh&fKkP;9o4Y2mlr;WO7kJNe8)wLR_6WaPHFJr? z7V(bjiGuf8rYcLxFg!AiIAZSbl%q*b;a{`JfgJ`OzT$Sx9fmAz^^c8T*murQY0hK* zDC@<`v{Y727U*~ePgg&ebxsLQ0EiV-+5i9m literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0032524c8750e8e97b5b99bbc95c92441d165e5e GIT binary patch literal 349 zcmV-j0iyniP)H*+)ga^XORU>Fa%@J200xfc`yeL@buxWEkPkHnKIZ*MEsmVC5)4yGB>aQ zeb5u9b|ueGqJ}NzWV0U-Oz}f*3EW9fu{3(~%dcLM{1%7MkZjNo zkUt>#BMzYn*`O03zf1BbMkoxSB(Q-S;1UeVMZD8e5wd?M;>T7Iav6nO6`?0HM#u=g z@y3y<;GkP*4N{~g4IG_9RY_f4r%+r{m(VFxlhieI3OS;0-jM+t-GA8;%mgQM4;i5- v3tgf%4{=V6QxzeLVWxJ4jM7YPB9id}=A;=w>1~2?00000NkvXXu0mjfsR59l literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..507e39b7939642aa28d1c554be11b898a52813df GIT binary patch literal 545 zcmV++0^a?JP)|36ov;kTyRBD1RJq8h#-iBlv*fh)JmVity0kh5j(**uo6T`6|@xuMR5TeQB1F* zB1sfU{4cqIFy4ui$s}_wo&z7GnETK9GGuaQF$^xQ-132WumcVy;`1En(@`i0E(XGhwc`4>sG*s;%r%sXM_fjmNXvoR`P^}^-z+x=+ z&&0$S6b3PnDq`3z6tS;I5V9E0ik3Pnql^`&Zp(!UNM%`dQ!GYE?7>a36d|#cn__8V zuo#Q67>nsE_9X9%j7r34^sKwsCFoG(iL2~uUBx~@PoB(P@TH^JzKz_7j$)w_4%eQF zZQ01()=}&ZROHF51oyh06=(BgF6ex^7nd08rn|c0;owG*mqKJFkA%FnAV~5^#bPYR zVk|~gEKW%3!%eXxLSj)j#fHH*0V&bLklPO?ri|b3WR#OvFj1;UNv&W8EE$=1G0K71 j*R+v&LxGEni%9(d=jPaujIeF`z9AU46mP81(NM6i@-jNmf}f`vBrB4~n1P!a4z!CS2{ z663!(+vr}h$!5*j`G5GDz@Eu`OwP=1)-V``iD=qE9HhZ1cm^fKv-)fsOo29?#PoqR z@Cqtb@&Z;tZ`9??sMS%akw-8cMKLX4w+=DCNvoQXk+UD59E-iENUCP#e1aS}QsmTU zUf1nJq35_w@CqbAx6QRZU=G}P)DYW~`dk`v8LWWTD(bYUUGK}Kaf{sD)?DPw)=)3u zB56h9A@H>UlUa8;Cy6>t#$9?&Yn|e69coWX5|nnBL?VFN^A5GQBndjTnLx>5x2mQ2 zmu>2wND^eXd*uSGe{EC$Ns^$F%><4Dtbc4%|3i`>yB={6VEsdfF(`Ag$+5_*o85+IS)d$?#V`bA1q(V4 zLr_t$ptt|p!X-gL6huK3L_ripK@>zmLIk~o4KSu2OPIt!vO%YIQG)Kkkjq%Z;9gfj z?;dijj2J)vd1)tT!^fDLItm)|G3L0Ag8V!u?xG+H`a{gem=ih*O8OWxrK6ytI_G$0 zRM&03q7H#09&#RZztt$JbI!PG#$BrCqykVA)l$ARlC73 d48t(m encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiMessage message) { return getEncryption().encrypt(uid, message); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index ee0f911348..a2782e49c1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiMessage; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; @@ -24,7 +24,7 @@ public EncryptedMsg(@NotNull ActorRef dest) { * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiMessage message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 3f587d906f..c813b5166a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -24,7 +24,7 @@ public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { + private Promise doEncrypt(int uid, ApiMessage message) throws IOException { Log.d(TAG, "doEncrypt"); return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) @@ -39,7 +39,7 @@ private Promise doEncrypt(int uid, ApiMessage message) thro boxKeys, "aes-kuznechik", encryptBoxResponse.getEncryptedPackage(), new ArrayList<>()); - return new ApiEncryptedMessage(apiEncryptedBox); + return apiEncryptedBox; }); } @@ -114,7 +114,7 @@ public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMess } } - public static class EncryptMessage implements AskMessage { + public static class EncryptMessage implements AskMessage { private int uid; private ApiMessage message; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index c6af6d06c7..a80f61b6cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -26,10 +26,13 @@ import im.actor.core.api.ApiDocumentExVideo; import im.actor.core.api.ApiDocumentMessage; import im.actor.core.api.ApiOutPeer; +import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiTextMessage; +import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -495,12 +498,32 @@ public void onError(RpcException e) { Log.d("SenderActor", "Pending encrypted message: " + message); context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); - // TODO: Implement sending - final ApiOutPeer outPeer = buidOutPeer(peer); - ArrayList peers = new ArrayList<>(); - peers.add(outPeer); - // api(new RequestSendEncryptedPackage(rid, peers,new ArrayList<>(), new ApiEncryptedBox())); - wakeLock.releaseLock(); + + long accessHash = getUser(peer.getPeerId()).getAccessHash(); + ArrayList peers = new ArrayList<>(); + peers.add(new ApiUserOutPeer(peer.getPeerId(), accessHash)); + RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, peers, + new ArrayList<>(), apiEncryptedMessage); + + api(request).then(response -> { + + self().send(new MessageSent(peer, rid)); + + // TODO: Replace + context().getMessagesModule().getRouter().onOutgoingSent(peer, + rid, response.getDate()); +// updates().onUpdateReceived(new SeqUpdate(response.getSeq(), +// response.getState(), +// UpdateMessageSent.HEADER, +// new UpdateMessageSent(new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()), +// rid, response.getDate()).toByteArray())); + + wakeLock.releaseLock(); + }).failure(e -> { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + }); + }).failure(e -> { self().send(new MessageError(peer, rid)); wakeLock.releaseLock(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index bde055b7ac..c4626c4037 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -15,6 +15,7 @@ import im.actor.core.entity.Group; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.User; import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.ModuleContext; @@ -149,6 +150,8 @@ && equalsE(dialog.getDialogAvatar(), user.getAvatar())) { Dialog updated = dialog.editPeerInfo(user.getName(), user.getAvatar()); addOrUpdateItem(updated); updateSearch(updated); + + // TODO: Update for secret chats } return Promise.success(null); @@ -341,11 +344,12 @@ private void notifyState(boolean force) { private PeerDesc buildPeerDesc(Peer peer) { switch (peer.getPeerType()) { case PRIVATE: + case PRIVATE_ENCRYPTED: User u = getUser(peer.getPeerId()); - return new PeerDesc(u.getName(), u.getAvatar()); + return new PeerDesc(u.getName(), u.getAvatar(), peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED); case GROUP: Group g = getGroup(peer.getPeerId()); - return new PeerDesc(g.getTitle(), g.getAvatar()); + return new PeerDesc(g.getTitle(), g.getAvatar(), false); default: return null; } @@ -355,10 +359,12 @@ private class PeerDesc { private String title; private Avatar avatar; + private boolean isEncrypted; - private PeerDesc(String title, Avatar avatar) { + private PeerDesc(String title, Avatar avatar, boolean isEncrypted) { this.title = title; this.avatar = avatar; + this.isEncrypted = isEncrypted; } public String getTitle() { @@ -368,6 +374,10 @@ public String getTitle() { public Avatar getAvatar() { return avatar; } + + public boolean isEncrypted() { + return isEncrypted; + } } // Messages diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index f380f9aee9..ea069508e6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -755,7 +755,7 @@ private void notifyActiveDialogsVM() { } public boolean isValidPeer(Peer peer) { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { return users().getValue(peer.getPeerId()) != null; } else if (peer.getPeerType() == PeerType.GROUP) { return groups().getValue(peer.getPeerId()) != null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index df8370adc0..78f5d24789 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -27,6 +27,7 @@ import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; +import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.ActorInterface; @@ -95,6 +96,10 @@ public Promise onOutgoingError(Peer peer, long rid) { return ask(new RouterOutgoingError(peer, rid)); } + public Promise onOutgoingSent(Peer peer, long rid, long date) { + return ask(new RouterOutgoingSent(peer, rid, date)); + } + public Promise onContentChanged(Peer peer, long rid, AbsContent content) { return ask(new RouterChangedContent(peer, rid, content)); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java index 3a20809a98..884de2c604 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java @@ -1,8 +1,10 @@ package im.actor.core.modules.messaging.router.entity; import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; -public class RouterOutgoingSent implements RouterMessageOnlyActive { +public class RouterOutgoingSent implements AskMessage, RouterMessageOnlyActive { private Peer peer; private long rid; From 5ec783b4957472b5e2a0e3525da14f68ba7c589f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:31:12 +0300 Subject: [PATCH 06/81] fix(core): Fixing subscription to online of secret chat --- .../src/main/java/im/actor/core/entity/Peer.java | 7 +++++++ .../java/im/actor/core/modules/presence/PresenceActor.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java index 0a52a7b948..4f5016a7ad 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java @@ -150,4 +150,11 @@ public String toString() { public String toIdString() { return peerType + "_" + peerId; } + + public Peer toUnencryptedCompat() { + if (peerType == PeerType.PRIVATE_ENCRYPTED) { + return new Peer(PeerType.PRIVATE, peerId); + } + return this; + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java index af2ed4317e..0da0d3edc7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java @@ -323,9 +323,9 @@ public void onBusEvent(Event event) { if (event instanceof NewSessionCreated) { self().send(new SessionCreated()); } else if (event instanceof PeerChatOpened) { - self().send(new Subscribe(((PeerChatOpened) event).getPeer())); + self().send(new Subscribe(((PeerChatOpened) event).getPeer().toUnencryptedCompat())); } else if (event instanceof PeerInfoOpened) { - self().send(new Subscribe(((PeerInfoOpened) event).getPeer())); + self().send(new Subscribe(((PeerInfoOpened) event).getPeer().toUnencryptedCompat())); } else if (event instanceof UserVisible) { self().send(new Subscribe(Peer.user(((UserVisible) event).getUid()))); } From ecb8e10aac18534ffa19c0b6d3d55e41935ad8a8 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:52:55 +0300 Subject: [PATCH 07/81] ref(core): Encrypted session code cleanup --- .../encryption/ratchet/EncryptedSession.java | 12 ++++- .../ratchet/EncryptedSessionActor.java | 48 ++++--------------- .../ratchet/EncryptedUserActor.java | 9 ++-- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java index b9979d921d..91223ea308 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -21,7 +21,17 @@ public EncryptedSession(@NotNull ActorRef dest) { * @param data for encryption * @return promise of encrypted package */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedSessionActor.EncryptPackage(data)); } + + /** + * Decrypt data for session + * + * @param data for decryption + * @return promise of decrypted package + */ + public Promise decrypt(byte[] data) { + return ask(new EncryptedSessionActor.DecryptPackage(data)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index c8e3944c7f..b811109d53 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -78,7 +78,7 @@ public void preStart() { keyManager = context().getEncryption().getKeyManager(); } - private Promise onEncrypt(final byte[] data) { + private Promise onEncrypt(final byte[] data) { // // Stage 1: Pick Their Ephemeral key. Use already received or pick random pre key. @@ -99,7 +99,7 @@ private Promise onEncrypt(final byte[] data) { .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } - private Promise onDecrypt(final byte[] data) { + private Promise onDecrypt(final byte[] data) { // // Stage 1: Parsing message header @@ -139,7 +139,7 @@ private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { return chain; } - private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { + private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { byte[] encrypted; try { @@ -152,7 +152,7 @@ private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); - return new EncryptedPackageRes(encrypted, session.getTheirKeyGroupId()); + return encrypted; } private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { @@ -182,7 +182,7 @@ private Promise pickDecryptChain(final byte[] theirEpheme }); } - private DecryptedPackage decrypt(EncryptedSessionChain chain, byte[] data) { + private byte[] decrypt(EncryptedSessionChain chain, byte[] data) { byte[] decrypted; try { decrypted = chain.decrypt(data); @@ -190,7 +190,7 @@ private DecryptedPackage decrypt(EncryptedSessionChain chain, byte[] data) { e.printStackTrace(); throw new RuntimeException(e); } - return new DecryptedPackage(decrypted); + return decrypted; } // @@ -209,7 +209,7 @@ public Promise onAsk(Object message) throws Exception { } } - public static class EncryptPackage implements AskMessage { + public static class EncryptPackage implements AskMessage { private byte[] data; public EncryptPackage(byte[] data) { @@ -221,26 +221,7 @@ public byte[] getData() { } } - public static class EncryptedPackageRes extends AskResult { - - private byte[] data; - private int keyGroupId; - - public EncryptedPackageRes(byte[] data, int keyGroupId) { - this.data = data; - this.keyGroupId = keyGroupId; - } - - public byte[] getData() { - return data; - } - - public int getKeyGroupId() { - return keyGroupId; - } - } - - public static class DecryptPackage implements AskMessage { + public static class DecryptPackage implements AskMessage { private byte[] data; @@ -252,17 +233,4 @@ public byte[] getData() { return data; } } - - public static class DecryptedPackage extends AskResult { - - private byte[] data; - - public DecryptedPackage(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index f03b306898..9677878c36 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -105,7 +105,8 @@ private Promise doEncrypt(final byte[] data) { }) .map(src -> spawnSession(src)); }) - .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended)) + .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended) + .map(r -> new Tuple2<>(r, sessionActor.getSession().getTheirKeyGroupId()))) .zip() .map(src -> { @@ -116,9 +117,9 @@ private Promise doEncrypt(final byte[] data) { Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); ArrayList encryptedKeys = new ArrayList<>(); - for (EncryptedSessionActor.EncryptedPackageRes r : src) { - Log.d(TAG, "Keys: " + r.getKeyGroupId()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); + for (Tuple2 r : src) { + Log.d(TAG, "Keys: " + r.getT2()); + encryptedKeys.add(new EncryptedBoxKey(uid, r.getT2(), "curve25519", r.getT1())); } byte[] encData; From 244d0978b3f9b7512cb31838dd02e776b01a0d9d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:53:52 +0300 Subject: [PATCH 08/81] fix(core): Fixing compilation error --- .../modules/encryption/ratchet/EncryptedUserActor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 9677878c36..ce08deb2af 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -175,7 +175,7 @@ public Tuple2 apply(PeerSession src) { } }); }) - .flatMap((Function, Promise>) src -> { + .flatMap((Function, Promise>) src -> { Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); // TODO: Implement @@ -184,9 +184,9 @@ public Tuple2 apply(PeerSession src) { .map(decryptedPackage -> { byte[] encData; try { - byte[] encKeyExtended = decryptedPackage.getData().length >= 128 - ? decryptedPackage.getData() - : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); + byte[] encKeyExtended = decryptedPackage.length >= 128 + ? decryptedPackage + : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); Log.d(TAG, "Box size: " + encData.length); } catch (IOException e) { From 526926b7013283a99784a56410590a139008c782 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 08:02:25 +0300 Subject: [PATCH 09/81] fix(core): Additional locking in SessionManager to avoid double creation of sessions --- .../ratchet/SessionManagerActor.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 3fca3939c7..2422229f18 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; @@ -37,6 +38,7 @@ public class SessionManagerActor extends ModuleActor { private KeyValueEngine peerSessions; private KeyManager keyManager; + private final HashSet locked = new HashSet<>(); public SessionManagerActor(ModuleContext context) { super(context); @@ -78,6 +80,13 @@ public Promise pickSession(final int uid, if (cached != null) { return Promise.success(cached); } + + if (locked.contains(uid)) { + stash(); + return null; + } + locked.add(uid); + return Promises.tuple( keyManager.getOwnIdentity(), keyManager.getOwnRandomPreKey(), @@ -94,15 +103,16 @@ public Promise pickSession(final int uid, .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) .first(); - spawnSession(uid, + return spawnSession(uid, ownIdentity.getKeyGroup(), keyGroupId, ownIdentity.getIdentityKey(), keysGroup.getIdentityKey(), ownPreKey, theirPreKey); - - return pickCachedSession(uid, keyGroupId); + }).after((r, e) -> { + locked.remove(uid); + unstashAll(); }); } @@ -124,6 +134,12 @@ public Promise pickSession(final int uid, return Promise.success(cached); } + if (locked.contains(uid)) { + stash(); + return null; + } + locked.add(uid); + return Promises.tuple( keyManager.getOwnIdentity(), keyManager.getOwnPreKey(ownKeyId), @@ -146,6 +162,9 @@ public Promise pickSession(final int uid, keysGroup.getIdentityKey(), ownPreKey, theirPreKey); + }).after((r, e) -> { + locked.remove(uid); + unstashAll(); }); } From 657ce7bdec9bb702a282d2cebfabe8ee3d244b56 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 09:06:09 +0300 Subject: [PATCH 10/81] feat(core): Implemented receiving of encrypted messages --- .../modules/encryption/EncryptionModule.java | 4 + .../encryption/EncryptionProcessor.java | 17 +++- .../encryption/ratchet/EncryptedMsg.java | 11 +++ .../encryption/ratchet/EncryptedMsgActor.java | 98 ++++++------------- .../ratchet/EncryptedUserActor.java | 6 +- 5 files changed, 61 insertions(+), 75 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index d0f46b5120..a0489af992 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -65,4 +65,8 @@ public EncryptedUser getEncryptedUser(int uid) { public Promise encrypt(int uid, ApiMessage message) { return getEncryption().encrypt(uid, message); } + + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + return getEncryption().decrypt(uid, encryptedBox); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index d173ba72a5..9395b6657a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -3,6 +3,10 @@ import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; +import im.actor.core.entity.Message; +import im.actor.core.entity.MessageState; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; @@ -29,8 +33,17 @@ public Promise process(Update update) { .getKeyManager() .onKeyGroupRemoved(groupRemoved.getUid(), groupRemoved.getKeyGroupId()); } else if (update instanceof UpdateEncryptedPackage) { - // TODO: Implement - return Promise.success(null); + UpdateEncryptedPackage encryptedPackage = (UpdateEncryptedPackage) update; + return context().getEncryption() + .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) + .flatMap(message -> { + Message msg = new Message(encryptedPackage.getRandomId(), + encryptedPackage.getDate(), encryptedPackage.getDate(), + encryptedPackage.getSenderId(), MessageState.UNKNOWN, + AbsContent.fromMessage(message)); + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(encryptedPackage.getSenderId()), msg); + }); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index a2782e49c1..8132e2bb44 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -27,4 +27,15 @@ public EncryptedMsg(@NotNull ActorRef dest) { public Promise encrypt(int uid, ApiMessage message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } + + /** + * Decrypt Message from private secret chat + * + * @param uid user's id + * @param encryptedBox encrypted message + * @return promise of decrypted message + */ + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + return ask(new EncryptedMsgActor.DecryptMessage(uid, encryptedBox)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index c813b5166a..eb78f4c289 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -4,15 +4,12 @@ import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.api.ApiMessage; -import im.actor.core.entity.Peer; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; -import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.promise.Promise; @@ -25,95 +22,52 @@ public EncryptedMsgActor(ModuleContext context) { } private Promise doEncrypt(int uid, ApiMessage message) throws IOException { - Log.d(TAG, "doEncrypt"); - return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) .map(encryptBoxResponse -> { - Log.d(TAG, "doEncrypt:onResult"); ArrayList boxKeys = new ArrayList<>(); for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); } - ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, + return new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", encryptBoxResponse.getEncryptedPackage(), new ArrayList<>()); - return apiEncryptedBox; }); } - public void onDecrypt(int uid, ApiEncryptedMessage message) { - Log.d(TAG, "onDecrypt:" + uid); - final long start = im.actor.runtime.Runtime.getActorTime(); - ArrayList encryptedBoxKeys = new ArrayList(); - for (ApiEncyptedBoxKey key : message.getBox().getKeys()) { + public Promise doDecrypt(int uid, ApiEncryptedBox box) { + ArrayList encryptedBoxKeys = new ArrayList<>(); + for (ApiEncyptedBoxKey key : box.getKeys()) { if (key.getUsersId() == myUid()) { encryptedBoxKeys.add(new EncryptedBoxKey(key.getUsersId(), key.getKeyGroupId(), key.getAlgType(), key.getEncryptedKey())); } } - final EncryptedBox encryptedBox = new EncryptedBox(encryptedBoxKeys.toArray(new EncryptedBoxKey[0]), message.getBox().getEncPackage()); - - // TODO: Implement -// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedUserActor.DecryptBox(encryptedBox), new AskCallback() { -// @Override -// public void onResult(Object obj) { -// Log.d(TAG, "onDecrypt:onResult in " + (Runtime.getActorTime() - start) + " ms"); -// EncryptedUserActor.DecryptBoxResponse re = (EncryptedUserActor.DecryptBoxResponse) obj; -// try { -// ApiMessage message = ApiMessage.fromBytes(re.getData()); -// Log.d(TAG, "onDecrypt:onResult " + message); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// -// @Override -// public void onError(Exception e) { -// Log.d(TAG, "onDecrypt:onError"); -// e.printStackTrace(); -// } -// }); + EncryptedBox encryptedBox = new EncryptedBox( + encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), + box.getEncPackage()); + + return context().getEncryption().getEncryptedUser(uid).decrypt(encryptedBox).map(bytes -> { + try { + return ApiMessage.fromBytes(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } @Override public Promise onAsk(Object message) throws Exception { if (message instanceof EncryptMessage) { return doEncrypt(((EncryptMessage) message).getUid(), ((EncryptMessage) message).getMessage()); + } else if (message instanceof DecryptMessage) { + return doDecrypt(((DecryptMessage) message).getUid(), ((DecryptMessage) message).getEncryptedBox()); } else { return super.onAsk(message); } } - @Override - public void onReceive(Object message) { - Log.d(TAG, "msg: " + message); - if (message instanceof InMessage) { - InMessage inMessage = (InMessage) message; - onDecrypt(inMessage.senderUid, inMessage.encryptedMessage); - } else { - super.onReceive(message); - } - } - - public static class InMessage { - - private Peer peer; - private long date; - private int senderUid; - private long rid; - private ApiEncryptedMessage encryptedMessage; - - public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMessage encryptedMessage) { - this.peer = peer; - this.date = date; - this.senderUid = senderUid; - this.rid = rid; - this.encryptedMessage = encryptedMessage; - } - } - public static class EncryptMessage implements AskMessage { private int uid; @@ -133,16 +87,22 @@ public ApiMessage getMessage() { } } - public static class DecryptMessage { + public static class DecryptMessage implements AskMessage { - private ApiEncryptedMessage encryptedMessage; + private int uid; + private ApiEncryptedBox encryptedBox; - public DecryptMessage(ApiEncryptedMessage encryptedMessage) { - this.encryptedMessage = encryptedMessage; + public DecryptMessage(int uid, ApiEncryptedBox encryptedBox) { + this.uid = uid; + this.encryptedBox = encryptedBox; + } + + public int getUid() { + return uid; } - public ApiEncryptedMessage getEncryptedMessage() { - return encryptedMessage; + public ApiEncryptedBox getEncryptedBox() { + return encryptedBox; } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index ce08deb2af..5fba6f6d66 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -175,11 +175,9 @@ public Tuple2 apply(PeerSession src) { } }); }) - .flatMap((Function, Promise>) src -> { + .flatMap(src -> { Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); - // TODO: Implement - return null; + return src.getT1().getEncryptedSession().decrypt(src.getT2().getEncryptedKey()); }) .map(decryptedPackage -> { byte[] encData; From ed22afbc850b22862bcb2cb2db91ad5ada52eaa5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 10:47:28 +0300 Subject: [PATCH 11/81] feat(core): Updated encrypted containers --- actor-sdk/sdk-api/actor.json | 270 ++++++++++++++++-- .../languageModels/editor.mps | 9 +- .../models/im/actor/api/scheme.mps | 219 ++++++++++++-- .../conversation/ChatActivity.java | 11 +- .../actor/core/api/ApiEncryptedContent.java | 39 +++ .../api/ApiEncryptedContentUnsupported.java | 42 +++ .../im/actor/core/api/ApiEncryptedData.java | 64 +++++ .../core/api/ApiEncryptedDeleteContent.java | 64 +++++ .../core/api/ApiEncryptedEditContent.java | 78 +++++ .../im/actor/core/api/ApiEncryptedGroup.java | 74 +++++ .../actor/core/api/ApiEncryptedMessage.java | 69 ----- .../core/api/ApiEncryptedMessageContent.java | 78 +++++ .../java/im/actor/core/api/ApiMessage.java | 1 - .../modules/encryption/EncryptionModule.java | 7 +- .../encryption/EncryptionProcessor.java | 36 ++- .../encryption/ratchet/EncryptedMsg.java | 5 +- .../encryption/ratchet/EncryptedMsgActor.java | 63 ++-- .../messaging/actions/SenderActor.java | 12 +- .../notifications/NotificationsActor.java | 4 +- .../core/modules/settings/SettingsModule.java | 2 +- 20 files changed, 977 insertions(+), 170 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 1fade09012..4982e5eed5 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -5239,36 +5239,6 @@ ] } }, - { - "type": "struct", - "content": { - "name": "EncryptedMessage", - "doc": [ - "Encrypted Message", - { - "type": "reference", - "argument": "box", - "category": "full", - "description": " Encrypted box" - } - ], - "trait": { - "name": "Message", - "key": 8 - }, - "expandable": "true", - "attributes": [ - { - "type": { - "type": "struct", - "childType": "EncryptedBox" - }, - "id": 1, - "name": "box" - } - ] - } - }, { "type": "struct", "content": { @@ -19281,6 +19251,246 @@ } ] } + }, + { + "type": "comment", + "content": "Encrypted Content Container" + }, + { + "type": "struct", + "content": { + "name": "EncryptedData", + "doc": [ + "Encrypted Content", + { + "type": "reference", + "argument": "version", + "category": "full", + "description": " Version of data" + }, + { + "type": "reference", + "argument": "data", + "category": "full", + "description": " Data" + } + ], + "attributes": [ + { + "type": "int32", + "id": 1, + "name": "version" + }, + { + "type": "bytes", + "id": 2, + "name": "data" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedGroup", + "doc": [ + "Encrypted Group information", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Random Id" + }, + { + "type": "reference", + "argument": "title", + "category": "full", + "description": " Group Title" + }, + { + "type": "reference", + "argument": "members", + "category": "compact", + "description": " Group Members" + } + ], + "attributes": [ + { + "type": "int64", + "id": 1, + "name": "groupId" + }, + { + "type": "string", + "id": 2, + "name": "title" + }, + { + "type": { + "type": "list", + "childType": "int32" + }, + "id": 3, + "name": "members" + } + ] + } + }, + { + "type": "trait", + "content": { + "isContainer": "true", + "name": "EncryptedContent", + "attributes": [] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedMessageContent", + "doc": [ + "New incoming encrypted message", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + }, + { + "type": "reference", + "argument": "message", + "category": "full", + "description": " Content of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 1 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + }, + { + "type": { + "type": "trait", + "childType": "Message" + }, + "id": 3, + "name": "message" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedEditContent", + "doc": [ + "Encrypted message edit", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + }, + { + "type": "reference", + "argument": "message", + "category": "full", + "description": " Updated content of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 2 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + }, + { + "type": { + "type": "trait", + "childType": "Message" + }, + "id": 3, + "name": "message" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedDeleteContent", + "doc": [ + "Encrypted message delete", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 3 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps b/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps index 0a6cb28361..23be5af374 100644 --- a/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps +++ b/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps @@ -1322,12 +1322,6 @@ - - - - - - @@ -1389,6 +1383,9 @@ + + + diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 60c935120c..d1286a38d0 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -4762,30 +4762,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -16318,6 +16294,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index ef30244fb0..c84a007b3a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -9,8 +9,10 @@ import android.support.v4.app.Fragment; import android.support.v7.view.ActionMode; import android.support.v7.widget.Toolbar; +import android.view.WindowManager; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.sdk.R; import im.actor.sdk.controllers.activity.BaseActivity; @@ -31,6 +33,13 @@ public void onCreate(Bundle saveInstance) { // For faster keyboard open/close getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + // Secure Window from screenshoting + Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE); + } + // // Loading Layout // @@ -41,7 +50,7 @@ public void onCreate(Bundle saveInstance) { // Loading Fragments if needed // if (saveInstance == null) { - Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); + ChatFragment chatFragment = ChatFragment.create(peer); getSupportFragmentManager().beginTransaction() .add(R.id.chatFragment, chatFragment) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java new file mode 100644 index 0000000000..197fd87258 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -0,0 +1,39 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public abstract class ApiEncryptedContent extends BserObject { + public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { + BserValues values = new BserValues(BserParser.deserialize(new DataInput(src, 0, src.length))); + int key = values.getInt(1); + byte[] content = values.getBytes(2); + switch(key) { + case 1: return Bser.parse(new ApiEncryptedMessageContent(), content); + case 2: return Bser.parse(new ApiEncryptedEditContent(), content); + case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); + default: return new ApiEncryptedContentUnsupported(key, content); + } + } + public abstract int getHeader(); + + public byte[] buildContainer() throws IOException { + DataOutput res = new DataOutput(); + BserWriter writer = new BserWriter(res); + writer.writeInt(1, getHeader()); + writer.writeBytes(2, toByteArray()); + return res.toByteArray(); + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java new file mode 100644 index 0000000000..5c77f0f96e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java @@ -0,0 +1,42 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedContentUnsupported extends ApiEncryptedContent { + + private int key; + private byte[] content; + + public ApiEncryptedContentUnsupported(int key, byte[] content) { + this.key = key; + this.content = content; + } + + @Override + public int getHeader() { + return this.key; + } + + @Override + public void parse(BserValues values) throws IOException { + throw new IOException("Parsing is unsupported"); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeRaw(content); + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java new file mode 100644 index 0000000000..8582b87b42 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedData extends BserObject { + + private int version; + private byte[] data; + + public ApiEncryptedData(int version, @NotNull byte[] data) { + this.version = version; + this.data = data; + } + + public ApiEncryptedData() { + + } + + public int getVersion() { + return this.version; + } + + @NotNull + public byte[] getData() { + return this.data; + } + + @Override + public void parse(BserValues values) throws IOException { + this.version = values.getInt(1); + this.data = values.getBytes(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.version); + if (this.data == null) { + throw new IOException(); + } + writer.writeBytes(2, this.data); + } + + @Override + public String toString() { + String res = "struct EncryptedData{"; + res += "version=" + this.version; + res += ", data=" + byteArrayToString(this.data); + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java new file mode 100644 index 0000000000..1695294074 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedDeleteContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + + public ApiEncryptedDeleteContent(int receiverId, long rid) { + this.receiverId = receiverId; + this.rid = rid; + } + + public ApiEncryptedDeleteContent() { + + } + + public int getHeader() { + return 3; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + } + + @Override + public String toString() { + String res = "struct EncryptedDeleteContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java new file mode 100644 index 0000000000..e5ab3485f0 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java @@ -0,0 +1,78 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedEditContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + private ApiMessage message; + + public ApiEncryptedEditContent(int receiverId, long rid, @NotNull ApiMessage message) { + this.receiverId = receiverId; + this.rid = rid; + this.message = message; + } + + public ApiEncryptedEditContent() { + + } + + public int getHeader() { + return 2; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @NotNull + public ApiMessage getMessage() { + return this.message; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + this.message = ApiMessage.fromBytes(values.getBytes(3)); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + if (this.message == null) { + throw new IOException(); + } + + writer.writeBytes(3, this.message.buildContainer()); + } + + @Override + public String toString() { + String res = "struct EncryptedEditContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += ", message=" + this.message; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java new file mode 100644 index 0000000000..289445b30e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java @@ -0,0 +1,74 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedGroup extends BserObject { + + private long groupId; + private String title; + private List members; + + public ApiEncryptedGroup(long groupId, @NotNull String title, @NotNull List members) { + this.groupId = groupId; + this.title = title; + this.members = members; + } + + public ApiEncryptedGroup() { + + } + + public long getGroupId() { + return this.groupId; + } + + @NotNull + public String getTitle() { + return this.title; + } + + @NotNull + public List getMembers() { + return this.members; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getLong(1); + this.title = values.getString(2); + this.members = values.getRepeatedInt(3); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeLong(1, this.groupId); + if (this.title == null) { + throw new IOException(); + } + writer.writeString(2, this.title); + writer.writeRepeatedInt(3, this.members); + } + + @Override + public String toString() { + String res = "struct EncryptedGroup{"; + res += "groupId=" + this.groupId; + res += ", title=" + this.title; + res += ", members=" + this.members.size(); + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java deleted file mode 100644 index 84ea016c48..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java +++ /dev/null @@ -1,69 +0,0 @@ -package im.actor.core.api; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; - -public class ApiEncryptedMessage extends ApiMessage { - - private ApiEncryptedBox box; - - public ApiEncryptedMessage(@NotNull ApiEncryptedBox box) { - this.box = box; - } - - public ApiEncryptedMessage() { - - } - - public int getHeader() { - return 8; - } - - @NotNull - public ApiEncryptedBox getBox() { - return this.box; - } - - @Override - public void parse(BserValues values) throws IOException { - this.box = values.getObj(1, new ApiEncryptedBox()); - if (values.hasRemaining()) { - setUnmappedObjects(values.buildRemaining()); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - if (this.box == null) { - throw new IOException(); - } - writer.writeObject(1, this.box); - if (this.getUnmappedObjects() != null) { - SparseArray unmapped = this.getUnmappedObjects(); - for (int i = 0; i < unmapped.size(); i++) { - int key = unmapped.keyAt(i); - writer.writeUnmapped(key, unmapped.get(key)); - } - } - } - - @Override - public String toString() { - String res = "struct EncryptedMessage{"; - res += "box=" + this.box; - res += "}"; - return res; - } - -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java new file mode 100644 index 0000000000..c10af3cfa7 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java @@ -0,0 +1,78 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedMessageContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + private ApiMessage message; + + public ApiEncryptedMessageContent(int receiverId, long rid, @NotNull ApiMessage message) { + this.receiverId = receiverId; + this.rid = rid; + this.message = message; + } + + public ApiEncryptedMessageContent() { + + } + + public int getHeader() { + return 1; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @NotNull + public ApiMessage getMessage() { + return this.message; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + this.message = ApiMessage.fromBytes(values.getBytes(3)); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + if (this.message == null) { + throw new IOException(); + } + + writer.writeBytes(3, this.message.buildContainer()); + } + + @Override + public String toString() { + String res = "struct EncryptedMessageContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += ", message=" + this.message; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java index 4782926df9..32720606e1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java @@ -27,7 +27,6 @@ public static ApiMessage fromBytes(byte[] src) throws IOException { case 5: return Bser.parse(new ApiUnsupportedMessage(), content); case 6: return Bser.parse(new ApiStickerMessage(), content); case 7: return Bser.parse(new ApiBinaryMessage(), content); - case 8: return Bser.parse(new ApiEncryptedMessage(), content); case 9: return Bser.parse(new ApiEmptyMessage(), content); default: return new ApiMessageUnsupported(key, content); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index a0489af992..d71daecad9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -3,7 +3,7 @@ import java.util.HashMap; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiMessage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; @@ -13,7 +13,6 @@ import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; -import im.actor.core.network.mtp.entity.EncryptedPackage; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -62,11 +61,11 @@ public EncryptedUser getEncryptedUser(int uid) { } } - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiEncryptedContent message) { return getEncryption().encrypt(uid, message); } - public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return getEncryption().decrypt(uid, encryptedBox); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 9395b6657a..6fad452130 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -1,5 +1,9 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; +import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; @@ -37,14 +41,34 @@ public Promise process(Update update) { return context().getEncryption() .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) .flatMap(message -> { - Message msg = new Message(encryptedPackage.getRandomId(), - encryptedPackage.getDate(), encryptedPackage.getDate(), - encryptedPackage.getSenderId(), MessageState.UNKNOWN, - AbsContent.fromMessage(message)); - return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(encryptedPackage.getSenderId()), msg); + return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); }); } return null; } + + public Promise process(int senderId, long date, ApiEncryptedContent update) { + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(destId), msg); + } else if (update instanceof ApiEncryptedDeleteContent) { + // TODO: Implement + return Promise.success(null); + } else if (update instanceof ApiEncryptedEditContent) { + // TODO: Implement + return Promise.success(null); + } else { + return Promise.success(null); + } + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index 8132e2bb44..e7d0076b4d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull; import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiMessage; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; @@ -24,7 +25,7 @@ public EncryptedMsg(@NotNull ActorRef dest) { * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiEncryptedContent message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } @@ -35,7 +36,7 @@ public Promise encrypt(int uid, ApiMessage message) { * @param encryptedBox encrypted message * @return promise of decrypted message */ - public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return ask(new EncryptedMsgActor.DecryptMessage(uid, encryptedBox)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index eb78f4c289..2e3b86d06b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -4,30 +4,41 @@ import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedContentUnsupported; +import im.actor.core.api.ApiEncryptedData; import im.actor.core.api.ApiEncyptedBoxKey; -import im.actor.core.api.ApiMessage; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.bser.Bser; import im.actor.runtime.promise.Promise; public class EncryptedMsgActor extends ModuleActor { + private static final int VERSION = 1; + private static final String TAG = "MessageEncryptionActor"; public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { - return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) + private Promise doEncrypt(int uid, ApiEncryptedContent message) throws IOException { + + // Building Encrypted Data + ApiEncryptedData data = new ApiEncryptedData(VERSION, message.buildContainer()); + byte[] encData = data.toByteArray(); + + return context().getEncryption().getEncryptedUser(uid) + .encrypt(encData) .map(encryptBoxResponse -> { ArrayList boxKeys = new ArrayList<>(); for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { - boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), - b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); + boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), + "curve25519", b.getEncryptedKey())); } return new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", @@ -36,7 +47,9 @@ private Promise doEncrypt(int uid, ApiMessage message) throws I }); } - public Promise doDecrypt(int uid, ApiEncryptedBox box) { + public Promise doDecrypt(int uid, ApiEncryptedBox box) { + + // Building Encrypted Box ArrayList encryptedBoxKeys = new ArrayList<>(); for (ApiEncyptedBoxKey key : box.getKeys()) { if (key.getUsersId() == myUid()) { @@ -48,13 +61,29 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), box.getEncPackage()); - return context().getEncryption().getEncryptedUser(uid).decrypt(encryptedBox).map(bytes -> { - try { - return ApiMessage.fromBytes(bytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + return context().getEncryption() + .getEncryptedUser(uid) + .decrypt(encryptedBox).map(bytes -> { + ApiEncryptedData encData; + try { + encData = Bser.parse(new ApiEncryptedData(), bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (encData.getVersion() != VERSION) { + throw new RuntimeException("Unsupported version " + encData.getVersion()); + } + ApiEncryptedContent content; + try { + content = ApiEncryptedContent.fromBytes(encData.getData()); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (content instanceof ApiEncryptedContentUnsupported) { + throw new RuntimeException("Unsupported content type #" + content.getHeader()); + } + return content; + }); } @Override @@ -71,9 +100,9 @@ public Promise onAsk(Object message) throws Exception { public static class EncryptMessage implements AskMessage { private int uid; - private ApiMessage message; + private ApiEncryptedContent message; - public EncryptMessage(int uid, ApiMessage message) { + public EncryptMessage(int uid, ApiEncryptedContent message) { this.uid = uid; this.message = message; } @@ -82,12 +111,12 @@ public int getUid() { return uid; } - public ApiMessage getMessage() { + public ApiEncryptedContent getMessage() { return message; } } - public static class DecryptMessage implements AskMessage { + public static class DecryptMessage implements AskMessage { private int uid; private ApiEncryptedBox encryptedBox; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index a80f61b6cf..faa72124f4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -14,11 +14,10 @@ import im.actor.core.api.ApiDocumentExAnimation; import im.actor.core.api.ApiDocumentExVoice; -import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiFastThumb; import im.actor.core.api.ApiJsonMessage; -import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiDocumentEx; @@ -26,13 +25,11 @@ import im.actor.core.api.ApiDocumentExVideo; import im.actor.core.api.ApiDocumentMessage; import im.actor.core.api.ApiOutPeer; -import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiTextMessage; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; -import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -69,7 +66,6 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; -import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -496,7 +492,9 @@ public void onError(RpcException e) { }); } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { Log.d("SenderActor", "Pending encrypted message: " + message); - context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { + ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), + rid, message); + context().getEncryption().encrypt(peer.getPeerId(), content).then(apiEncryptedMessage -> { Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); long accessHash = getUser(peer.getPeerId()).getAccessHash(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java index 20add7552b..46842990b6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java @@ -31,7 +31,7 @@ /** * Actor that controls all notifications in application - *

+ *

* NotificationsActor keeps all unread messages for showing last unread messages in notifications * Actor also control sound effects playing logic */ @@ -480,7 +480,7 @@ private boolean isNotificationsEnabled(Peer peer, boolean hasMention) { // All group notifications are disabled return false; } - } else if (peer.getPeerType() == PeerType.PRIVATE) { + } else if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { // For private conversations only check if peer notifications enabled return context().getSettingsModule().isNotificationsEnabled(peer); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java index 5ba278b548..07a7bfbb6b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java @@ -404,7 +404,7 @@ public void onUpdatedSetting(String key, String value) { } private String getChatKey(Peer peer) { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { return "PRIVATE_" + peer.getPeerId(); } else if (peer.getPeerType() == PeerType.GROUP) { return "GROUP_" + peer.getPeerId(); From b3fe08400ef82895e3490e0341edcebf2b21496a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 13:31:07 +0300 Subject: [PATCH 12/81] feat(core): Update encryption scheme to reduce unnecessary duplication, implementing multi-peer message sending --- .../modules/encryption/EncryptionModule.java | 39 ++- .../encryption/entity/EncryptedBox.java | 20 -- .../encryption/entity/EncryptedBoxKey.java | 38 --- .../entity/SessionEphemeralKey.java | 50 --- .../modules/encryption/entity/SessionId.java | 64 ---- .../encryption/entity/SessionStorage.java | 109 ------- .../encryption/entity/UserSessions.java | 46 --- .../encryption/ratchet/EncryptedMsg.java | 25 +- .../encryption/ratchet/EncryptedMsgActor.java | 150 ++++++--- .../encryption/ratchet/EncryptedSession.java | 40 ++- .../ratchet/EncryptedSessionActor.java | 36 ++- .../EncryptedSessionChain.java | 27 +- .../encryption/ratchet/EncryptedUser.java | 28 +- .../ratchet/EncryptedUserActor.java | 302 +++++++++--------- .../encryption/ratchet/KeyManager.java | 8 +- .../encryption/ratchet/KeyManagerActor.java | 50 ++- .../ratchet/SessionManagerActor.java | 13 +- .../ratchet/entity/EncryptedMessage.java | 25 ++ .../ratchet/entity/EncryptedUserKeys.java | 31 ++ .../{ => ratchet}/entity/OwnIdentity.java | 3 +- .../{ => ratchet}/entity/PrivateKey.java | 2 +- .../entity/PrivateKeyStorage.java | 2 +- .../{ => ratchet}/entity/PublicKey.java | 2 +- .../{ => ratchet}/entity/UserKeys.java | 2 +- .../{ => ratchet}/entity/UserKeysGroup.java | 9 +- .../encryption/session/EncryptedSession.java | 53 --- .../session/EncryptedSessionStorage.java | 5 - .../messaging/actions/SenderActor.java | 40 +-- .../actor/runtime/promise/PromisesArray.java | 14 + 29 files changed, 509 insertions(+), 724 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{session => ratchet}/EncryptedSessionChain.java (86%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/OwnIdentity.java (80%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PrivateKey.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PrivateKeyStorage.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PublicKey.java (95%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/UserKeys.java (97%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/UserKeysGroup.java (90%) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index d71daecad9..919e7d0c14 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -1,18 +1,24 @@ package im.actor.core.modules.encryption; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiMessage; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.api.ApiUserOutPeer; +import im.actor.core.api.rpc.RequestSendEncryptedPackage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.ratchet.EncryptedMsg; -import im.actor.core.modules.encryption.ratchet.EncryptedMsgActor; import im.actor.core.modules.encryption.ratchet.EncryptedUser; import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; +import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -30,13 +36,9 @@ public EncryptionModule(ModuleContext context) { } public void run() { - keyManager = new KeyManager(context()); - sessionManager = new SessionManager(context()); - - encryption = new EncryptedMsg(system().actorOf("encryption/messaging", - () -> new EncryptedMsgActor(context()))); + encryption = new EncryptedMsg(context()); } public KeyManager getKeyManager() { @@ -61,11 +63,30 @@ public EncryptedUser getEncryptedUser(int uid) { } } - public Promise encrypt(int uid, ApiEncryptedContent message) { - return getEncryption().encrypt(uid, message); + public Promise encrypt(List uids, ApiEncryptedContent message) { + return getEncryption().encrypt(uids, message); } public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return getEncryption().decrypt(uid, encryptedBox); } + + public Promise doSend(long rid, ApiEncryptedContent content, List uids) { + + ArrayList outPeers = new ArrayList<>(); + for (int i : uids) { + outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); + } + + return encrypt(uids, content).flatMap(encryptedMessage -> { + RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, + encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); + return api(request).flatMap(r -> { + if (r.getSeq() != null && r.getSeq() != 0) { + return Promise.success(r); + } + return Promise.failure(new RuntimeException("Incorrect keys")); + }); + }); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java deleted file mode 100644 index 1aa49ce492..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java +++ /dev/null @@ -1,20 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -public class EncryptedBox { - - private final EncryptedBoxKey[] keys; - private final byte[] encryptedPackage; - - public EncryptedBox(EncryptedBoxKey[] keys, byte[] encryptedPackage) { - this.keys = keys; - this.encryptedPackage = encryptedPackage; - } - - public EncryptedBoxKey[] getKeys() { - return keys; - } - - public byte[] getEncryptedPackage() { - return encryptedPackage; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java deleted file mode 100644 index e252acd6a8..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java +++ /dev/null @@ -1,38 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import im.actor.runtime.function.Predicate; - -public class EncryptedBoxKey { - - public static Predicate FILTER(final int myUid, final int keyGroupId) { - return boxKey -> boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; - } - - private final int uid; - private final int keyGroupId; - private final byte[] encryptedKey; - private final String keyAlg; - - public EncryptedBoxKey(int uid, int keyGroupId, String keyAlg, byte[] encryptedKey) { - this.uid = uid; - this.keyGroupId = keyGroupId; - this.encryptedKey = encryptedKey; - this.keyAlg = keyAlg; - } - - public String getKeyAlg() { - return keyAlg; - } - - public int getUid() { - return uid; - } - - public int getKeyGroupId() { - return keyGroupId; - } - - public byte[] getEncryptedKey() { - return encryptedKey; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java deleted file mode 100644 index 52b3885345..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java +++ /dev/null @@ -1,50 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; - -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class SessionEphemeralKey extends BserObject { - - private byte[] publicKey; - private byte[] privateKey; - private long dateCreated; - - public SessionEphemeralKey(byte[] publicKey, byte[] privateKey, long dateCreated) { - this.publicKey = publicKey; - this.privateKey = privateKey; - this.dateCreated = dateCreated; - } - - public SessionEphemeralKey(byte[] data) throws IOException { - load(data); - } - - public byte[] getPublicKey() { - return publicKey; - } - - public byte[] getPrivateKey() { - return privateKey; - } - - public long getDateCreated() { - return dateCreated; - } - - @Override - public void parse(BserValues values) throws IOException { - dateCreated = values.getLong(1); - publicKey = values.getBytes(2); - privateKey = values.optBytes(3); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeLong(1, dateCreated); - writer.writeBytes(2, publicKey); - writer.writeBytes(3, privateKey); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java deleted file mode 100644 index 5d87f7b01c..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java +++ /dev/null @@ -1,64 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -public final class SessionId { - - private int ownKeyGroupId; - private int theirKeyGroupId; - private long ownKeyId0; - private long theirKeyId0; - - public SessionId(int ownKeyGroupId, long ownKeyId0, int theirKeyGroupId, long theirKeyId0) { - this.theirKeyGroupId = theirKeyGroupId; - this.ownKeyId0 = ownKeyId0; - this.theirKeyId0 = theirKeyId0; - this.ownKeyGroupId = ownKeyGroupId; - } - - public int getOwnKeyGroupId() { - return ownKeyGroupId; - } - - public int getTheirKeyGroupId() { - return theirKeyGroupId; - } - - public long getOwnKeyId0() { - return ownKeyId0; - } - - public long getTheirKeyId0() { - return theirKeyId0; - } - - @Override - public String toString() { - return "SessionId{" + - "ownKeyGroupId=" + ownKeyGroupId + - ", theirKeyGroupId=" + theirKeyGroupId + - ", ownKeyId0=" + ownKeyId0 + - ", theirKeyId0=" + theirKeyId0 + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SessionId sessionId = (SessionId) o; - - if (ownKeyGroupId != sessionId.ownKeyGroupId) return false; - if (theirKeyGroupId != sessionId.theirKeyGroupId) return false; - if (ownKeyId0 != sessionId.ownKeyId0) return false; - return theirKeyId0 == sessionId.theirKeyId0; - } - - @Override - public int hashCode() { - int result = ownKeyGroupId; - result = 31 * result + theirKeyGroupId; - result = 31 * result + (int) (ownKeyId0 ^ (ownKeyId0 >>> 32)); - result = 31 * result + (int) (theirKeyId0 ^ (theirKeyId0 >>> 32)); - return result; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java deleted file mode 100644 index ba5ed286b9..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java +++ /dev/null @@ -1,109 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class SessionStorage extends BserObject { - - private long sid; - private int uid; - private int ownKeyGroupId; - private int theirKeyGroupId; - private long ownPreKeyId; - private long theirPreKeyId; - - private ArrayList theirKeys; - private ArrayList ownKeys; - - public SessionStorage(long sid, int uid, - int theirKeyGroupId, - int ownKeyGroupId, - long ownPreKeyId, - long theirPreKeyId, - ArrayList theirKeys, - ArrayList ownKeys) { - this.sid = sid; - this.uid = uid; - this.theirKeyGroupId = theirKeyGroupId; - this.ownKeyGroupId = ownKeyGroupId; - this.ownPreKeyId = ownPreKeyId; - this.theirPreKeyId = theirPreKeyId; - this.theirKeys = new ArrayList<>(theirKeys); - this.ownKeys = new ArrayList<>(ownKeys); - } - - public SessionStorage(byte[] data) throws IOException { - load(data); - } - - public long getSid() { - return sid; - } - - public int getUid() { - return uid; - } - - public int getOwnKeyGroupId() { - return ownKeyGroupId; - } - - public int getTheirKeyGroupId() { - return theirKeyGroupId; - } - - public long getOwnPreKeyId() { - return ownPreKeyId; - } - - public long getTheirPreKeyId() { - return theirPreKeyId; - } - - public ArrayList getTheirKeys() { - return theirKeys; - } - - public ArrayList getOwnKeys() { - return ownKeys; - } - - @Override - public void parse(BserValues values) throws IOException { - sid = values.getLong(1); - uid = values.getInt(2); - ownKeyGroupId = values.getInt(3); - theirKeyGroupId = values.getInt(4); - ownPreKeyId = values.getLong(5); - theirPreKeyId = values.getLong(6); - - theirKeys = new ArrayList<>(); - List theirEphemeral = values.getRepeatedBytes(7); - for (byte[] b : theirEphemeral) { - theirKeys.add(new SessionEphemeralKey(b)); - } - - ownKeys = new ArrayList<>(); - List ownEphemeral = values.getRepeatedBytes(8); - for (byte[] b : ownEphemeral) { - theirKeys.add(new SessionEphemeralKey(b)); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeLong(1, sid); - writer.writeInt(2, uid); - writer.writeInt(3, ownKeyGroupId); - writer.writeInt(4, theirKeyGroupId); - writer.writeLong(5, ownPreKeyId); - writer.writeLong(6, theirPreKeyId); - writer.writeRepeatedObj(7, theirKeys); - writer.writeRepeatedObj(8, ownKeys); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java deleted file mode 100644 index 73772808db..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java +++ /dev/null @@ -1,46 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import im.actor.core.entity.encryption.PeerSession; -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class UserSessions extends BserObject { - - private int uid; - private ArrayList sessionDescriptors; - - public UserSessions(int uid, ArrayList sessionDescriptors) { - this.uid = uid; - this.sessionDescriptors = sessionDescriptors; - } - - public int getUid() { - return uid; - } - - public ArrayList getSessionDescriptors() { - return sessionDescriptors; - } - - @Override - public void parse(BserValues values) throws IOException { - uid = values.getInt(1); - - sessionDescriptors = new ArrayList<>(); - List desc = values.getRepeatedBytes(2); - for (byte[] b : desc) { - sessionDescriptors.add(new PeerSession(b)); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, uid); - writer.writeRepeatedObj(2, sessionDescriptors); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index e7d0076b4d..b1ddb821e9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -1,32 +1,39 @@ package im.actor.core.modules.encryption.ratchet; -import org.jetbrains.annotations.NotNull; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiMessage; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + /** * Entry point for message encryption */ public class EncryptedMsg extends ActorInterface { - public EncryptedMsg(@NotNull ActorRef dest) { - super(dest); + /** + * Constructor of encrypted messaging interface + * + * @param context context + */ + public EncryptedMsg(ModuleContext context) { + super(system().actorOf("encryption/messaging", () -> new EncryptedMsgActor(context))); } /** - * Encrypt Message for private secret chat + * Encrypt Message for secret chats * - * @param uid user's id + * @param uids User's ids. Add own UID for sending to other devices * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiEncryptedContent message) { - return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); + public Promise encrypt(List uids, ApiEncryptedContent message) { + return ask(new EncryptedMsgActor.EncryptMessage(message, uids)); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 2e3b86d06b..753145c1ac 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -2,19 +2,29 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedContentUnsupported; import im.actor.core.api.ApiEncryptedData; import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.runtime.Crypto; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.bser.Bser; +import im.actor.runtime.crypto.Cryptos; +import im.actor.runtime.crypto.IntegrityException; +import im.actor.runtime.crypto.box.ActorBox; +import im.actor.runtime.crypto.box.ActorBoxKey; +import im.actor.runtime.crypto.primitives.prf.PRF; +import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.promise.Promise; +import im.actor.runtime.promise.PromisesArray; public class EncryptedMsgActor extends ModuleActor { @@ -22,66 +32,104 @@ public class EncryptedMsgActor extends ModuleActor { private static final String TAG = "MessageEncryptionActor"; + private final PRF KEY_PRF = Cryptos.PRF_SHA_STREEBOG_256(); + + private boolean isFreezed = false; + private OwnIdentity ownIdentity; + public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiEncryptedContent message) throws IOException { + @Override + public void preStart() { + super.preStart(); + + context().getEncryption().getKeyManager().getOwnIdentity().then(id -> { + ownIdentity = id; + isFreezed = false; + unstashAll(); + }); + } + + private Promise doEncrypt(ApiEncryptedContent message, List uids) throws IOException { + + // Generate Encryption Keys + byte[] encKey = Crypto.randomBytes(32); + byte[] encKeyExtended = KEY_PRF.calculate(encKey, "ActorPackage", 128); + + // Encrypt Data + byte[] encryptedData; + byte[] dataToEncrypt = message.toByteArray(); + byte[] dataHeader = ByteStrings.merge(new byte[]{VERSION}, + ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(ownIdentity.getKeyGroup())); + try { + encryptedData = ActorBox.closeBox(dataHeader, dataToEncrypt, Crypto.randomBytes(32), + new ActorBoxKey(encKeyExtended)); + } catch (IntegrityException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } - // Building Encrypted Data - ApiEncryptedData data = new ApiEncryptedData(VERSION, message.buildContainer()); - byte[] encData = data.toByteArray(); + // Put To Plain-Text versioned container + byte[] dataContainer = new ApiEncryptedData(VERSION, encryptedData).toByteArray(); - return context().getEncryption().getEncryptedUser(uid) - .encrypt(encData) - .map(encryptBoxResponse -> { + // Encryption for all required users + return PromisesArray.of(uids) + .map((u) -> Promise.success(context().getEncryption().getEncryptedUser(u))) + .map((u) -> u.encrypt(encKeyExtended)) + .zip((r) -> { ArrayList boxKeys = new ArrayList<>(); - for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { - boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), - "curve25519", b.getEncryptedKey())); + ArrayList ignored = new ArrayList<>(); + for (EncryptedUserKeys uk : r) { + boxKeys.addAll(uk.getBoxKeys()); + for (Integer i : uk.getIgnoredKeys()) { + ignored.add(new ApiKeyGroupId(uk.getUid(), i)); + } } - return new ApiEncryptedBox(0, - boxKeys, "aes-kuznechik", - encryptBoxResponse.getEncryptedPackage(), - new ArrayList<>()); + + return new EncryptedMessage(new ApiEncryptedBox(ownIdentity.getKeyGroup(), + boxKeys, "aes-kuznechik", dataContainer, new ArrayList<>()), ignored); }); } public Promise doDecrypt(int uid, ApiEncryptedBox box) { - // Building Encrypted Box - ArrayList encryptedBoxKeys = new ArrayList<>(); - for (ApiEncyptedBoxKey key : box.getKeys()) { - if (key.getUsersId() == myUid()) { - encryptedBoxKeys.add(new EncryptedBoxKey(key.getUsersId(), key.getKeyGroupId(), - key.getAlgType(), key.getEncryptedKey())); - } + // Loading Package + ApiEncryptedData encData; + try { + encData = Bser.parse(new ApiEncryptedData(), box.getEncPackage()); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (encData.getVersion() != VERSION) { + throw new RuntimeException("Unsupported version " + encData.getVersion()); } - EncryptedBox encryptedBox = new EncryptedBox( - encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), - box.getEncPackage()); return context().getEncryption() .getEncryptedUser(uid) - .decrypt(encryptedBox).map(bytes -> { - ApiEncryptedData encData; + .decrypt(box.getSenderKeyGroupId(), box.getKeys()).map(bytes -> { + + // Decryption of package + byte[] dataHeader = ByteStrings.merge( + new byte[]{VERSION}, + ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(box.getSenderKeyGroupId())); + byte[] data; try { - encData = Bser.parse(new ApiEncryptedData(), bytes); - } catch (IOException e) { + data = ActorBox.openBox(dataHeader, encData.getData(), new ActorBoxKey(bytes)); + } catch (IntegrityException e) { throw new RuntimeException(e); } - if (encData.getVersion() != VERSION) { - throw new RuntimeException("Unsupported version " + encData.getVersion()); - } + + // Parsing content ApiEncryptedContent content; try { - content = ApiEncryptedContent.fromBytes(encData.getData()); + content = ApiEncryptedContent.fromBytes(data); } catch (IOException e) { throw new RuntimeException(e); } - if (content instanceof ApiEncryptedContentUnsupported) { - throw new RuntimeException("Unsupported content type #" + content.getHeader()); - } return content; }); } @@ -89,31 +137,39 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { @Override public Promise onAsk(Object message) throws Exception { if (message instanceof EncryptMessage) { - return doEncrypt(((EncryptMessage) message).getUid(), ((EncryptMessage) message).getMessage()); + if (isFreezed) { + stash(); + return null; + } + return doEncrypt(((EncryptMessage) message).getMessage(), ((EncryptMessage) message).getUids()); } else if (message instanceof DecryptMessage) { + if (isFreezed) { + stash(); + return null; + } return doDecrypt(((DecryptMessage) message).getUid(), ((DecryptMessage) message).getEncryptedBox()); } else { return super.onAsk(message); } } - public static class EncryptMessage implements AskMessage { + public static class EncryptMessage implements AskMessage { - private int uid; private ApiEncryptedContent message; + private List uids; - public EncryptMessage(int uid, ApiEncryptedContent message) { - this.uid = uid; + public EncryptMessage(ApiEncryptedContent message, List uids) { + this.uids = uids; this.message = message; } - public int getUid() { - return uid; - } - public ApiEncryptedContent getMessage() { return message; } + + public List getUids() { + return uids; + } } public static class DecryptMessage implements AskMessage { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java index 91223ea308..783c0a413c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -1,18 +1,40 @@ package im.actor.core.modules.encryption.ratchet; -import org.jetbrains.annotations.NotNull; - +import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; +import im.actor.core.util.RandomUtils; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + /** * Double Ratchet encrypted session operations */ public class EncryptedSession extends ActorInterface { - public EncryptedSession(@NotNull ActorRef dest) { - super(dest); + private PeerSession session; + + /** + * Constructor of session encryption + * + * @param session session settings + * @param context context + */ + public EncryptedSession(PeerSession session, ModuleContext context) { + super(system().actorOf("encryption/uid_" + session.getUid() + "/session_" + + RandomUtils.nextRid(), () -> new EncryptedSessionActor(context, session))); + this.session = session; + } + + /** + * Get Peer Session parameters + * + * @return peer session + */ + public PeerSession getSession() { + return session; } /** @@ -21,17 +43,17 @@ public EncryptedSession(@NotNull ActorRef dest) { * @param data for encryption * @return promise of encrypted package */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedSessionActor.EncryptPackage(data)); } /** * Decrypt data for session * - * @param data for decryption + * @param key for decryption * @return promise of decrypted package */ - public Promise decrypt(byte[] data) { - return ask(new EncryptedSessionActor.DecryptPackage(data)); + public Promise decrypt(ApiEncyptedBoxKey key) { + return ask(new EncryptedSessionActor.DecryptPackage(key)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index b811109d53..9348515891 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -2,14 +2,13 @@ import java.util.ArrayList; +import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.session.EncryptedSessionChain; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -29,7 +28,7 @@ * During actor starting it downloads all required key from Key Manager. * To encrypt/decrypt messages this actor spawns encryption chains. */ -public class EncryptedSessionActor extends ModuleActor { +class EncryptedSessionActor extends ModuleActor { private final String TAG; @@ -78,7 +77,7 @@ public void preStart() { keyManager = context().getEncryption().getKeyManager(); } - private Promise onEncrypt(final byte[] data) { + private Promise onEncrypt(final byte[] data) { // // Stage 1: Pick Their Ephemeral key. Use already received or pick random pre key. @@ -96,10 +95,12 @@ private Promise onEncrypt(final byte[] data) { return ephemeralKey .map(publicKey -> pickEncryptChain(publicKey)) - .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); + .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)) + .map(bytes -> new ApiEncyptedBoxKey(session.getUid(), session.getTheirKeyGroupId(), + "curve25519", bytes)); } - private Promise onDecrypt(final byte[] data) { + private Promise onDecrypt(ApiEncyptedBoxKey data) { // // Stage 1: Parsing message header @@ -108,16 +109,17 @@ private Promise onDecrypt(final byte[] data) { // Stage 4: Saving their ephemeral key // - // final int ownKeyGroupId = ByteStrings.bytesToInt(data, 0); - // final long ownEphemeralKey0Id = ByteStrings.bytesToLong(data, 4); - // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 12); - final byte[] senderEphemeralKey = ByteStrings.substring(data, 20, 32); - final byte[] receiverEphemeralKey = ByteStrings.substring(data, 52, 32); + byte[] material = data.getEncryptedKey(); + + // final long ownEphemeralKey0Id = ByteStrings.bytesToLong(data, 0); + // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 8); + final byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); + final byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(encryptedSessionChain -> decrypt(encryptedSessionChain, data)) + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)) .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } @@ -209,7 +211,7 @@ public Promise onAsk(Object message) throws Exception { } } - public static class EncryptPackage implements AskMessage { + public static class EncryptPackage implements AskMessage { private byte[] data; public EncryptPackage(byte[] data) { @@ -223,13 +225,13 @@ public byte[] getData() { public static class DecryptPackage implements AskMessage { - private byte[] data; + private ApiEncyptedBoxKey data; - public DecryptPackage(byte[] data) { + public DecryptPackage(ApiEncyptedBoxKey data) { this.data = data; } - public byte[] getData() { + public ApiEncyptedBoxKey getData() { return data; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java similarity index 86% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java index 738a0502ba..e052fddc80 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java @@ -1,12 +1,8 @@ -package im.actor.core.modules.encryption.session; - -import java.util.HashSet; -import java.util.Random; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; -import im.actor.runtime.Log; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; import im.actor.runtime.crypto.box.ActorBox; @@ -22,7 +18,6 @@ public class EncryptedSessionChain { private PeerSession session; private byte[] ownPrivateKey; private byte[] theirPublicKey; - private HashSet receivedCounters; private int sentCounter; private byte[] rootChainKey; @@ -30,7 +25,6 @@ public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] t this.session = session; this.ownPrivateKey = ownPrivateKey; this.theirPublicKey = theirPublicKey; - this.receivedCounters = new HashSet<>(); this.sentCounter = 0; this.rootChainKey = RatchetRootChainKey.makeRootChainKey( new RatchetPrivateKey(ownPrivateKey), @@ -52,7 +46,7 @@ public byte[] getTheirPublicKey() { public byte[] decrypt(byte[] data) throws IntegrityException { - if (data.length < 88) { + if (data.length < 80) { throw new IntegrityException("Data length is too small"); } @@ -60,12 +54,11 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // Parsing message header // - final int senderKeyGroupId = ByteStrings.bytesToInt(data, 0); - final long senderEphermalKey0Id = ByteStrings.bytesToLong(data, 4); - final long receiverEphermalKey0Id = ByteStrings.bytesToLong(data, 12); - final byte[] senderEphemeralKey = ByteStrings.substring(data, 20, 32); - final byte[] receiverEphemeralKey = ByteStrings.substring(data, 52, 32); - final int messageIndex = ByteStrings.bytesToInt(data, 84); + final long senderEphermalKey0Id = ByteStrings.bytesToLong(data, 0); + final long receiverEphermalKey0Id = ByteStrings.bytesToLong(data, 8); + final byte[] senderEphemeralKey = ByteStrings.substring(data, 16, 32); + final byte[] receiverEphemeralKey = ByteStrings.substring(data, 48, 32); + final int messageIndex = ByteStrings.bytesToInt(data, 80); // // Validating header @@ -92,8 +85,8 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); - byte[] header = ByteStrings.substring(data, 0, 88); - byte[] message = ByteStrings.substring(data, 88, data.length - 88); + byte[] header = ByteStrings.substring(data, 0, 80); + byte[] message = ByteStrings.substring(data, 80, data.length - 80); return ActorBox.openBox(header, message, ratchetMessageKey); } @@ -102,7 +95,6 @@ public byte[] encrypt(byte[] data) throws IntegrityException { ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); byte[] header = ByteStrings.merge( - ByteStrings.intToBytes(session.getOwnKeyGroupId()), ByteStrings.longToBytes(session.getOwnPreKeyId()), /*Alice Initial Ephermal*/ ByteStrings.longToBytes(session.getTheirPreKeyId()), /*Bob Initial Ephermal*/ Curve25519.keyGenPublic(ownPrivateKey), @@ -125,7 +117,6 @@ public void safeErase() { for (int i = 0; i < rootChainKey.length; i++) { rootChainKey[i] = (byte) RandomUtils.randomId(255); } - receivedCounters.clear(); sentCounter = 0; } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java index 16294d582e..8cbf6f4cbd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java @@ -2,14 +2,17 @@ import org.jetbrains.annotations.NotNull; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.UserKeys; +import java.util.List; + +import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; /** - * Encrypting data for private Secret Chats + * Encrypting shared key for private Secret Chats */ public class EncryptedUser extends ActorInterface { @@ -18,23 +21,24 @@ public EncryptedUser(@NotNull ActorRef dest) { } /** - * Encrypting data + * Encrypting shared key * - * @param data data for encryption - * @return promise of encrypted box + * @param data shared key for encryption + * @return promise of list of encrypted shared key */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedUserActor.EncryptBox(data)); } /** - * Decrypting data + * Decrypting shared key * - * @param data data for decryption - * @return promise of decrypted box + * @param senderKeyGroupId sender's key group id + * @param keys list of encrypted box keys + * @return promise of shared key */ - public Promise decrypt(EncryptedBox data) { - return ask(new EncryptedUserActor.DecryptBox(data)); + public Promise decrypt(int senderKeyGroupId, List keys) { + return ask(new EncryptedUserActor.DecryptBox(senderKeyGroupId, keys)); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 5fba6f6d66..868976c213 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -1,33 +1,22 @@ package im.actor.core.modules.encryption.ratchet; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; -import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.core.modules.ModuleActor; -import im.actor.core.util.RandomUtils; import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.crypto.Cryptos; -import im.actor.runtime.crypto.IntegrityException; -import im.actor.runtime.crypto.primitives.prf.PRF; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; -import im.actor.runtime.crypto.box.ActorBox; -import im.actor.runtime.crypto.box.ActorBoxKey; import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.promise.Promises; import im.actor.runtime.promise.PromisesArray; -import im.actor.runtime.function.Tuple2; import static im.actor.runtime.promise.Promise.success; @@ -36,20 +25,20 @@ public class EncryptedUserActor extends ModuleActor { private final String TAG; private final int uid; + private final boolean isOwnUser; private int ownKeyGroupId; private UserKeys theirKeys; - private HashMap activeSessions = new HashMap<>(); + private HashMap activeSessions = new HashMap<>(); private HashSet ignoredKeyGroups = new HashSet<>(); - private boolean isReady = false; - - private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); + private boolean isFreezed = true; public EncryptedUserActor(int uid, ModuleContext context) { super(context); this.uid = uid; + this.isOwnUser = myUid() == uid; TAG = "EncryptedUserActor#" + uid; } @@ -63,9 +52,14 @@ public void preStart() { keyManager.getOwnIdentity(), keyManager.getUserKeyGroups(uid)) .then(res -> { + Log.d(TAG, "Loaded initial parameters"); ownKeyGroupId = res.getT1().getKeyGroup(); theirKeys = res.getT2(); - onLoaded(); + if (isOwnUser) { + ignoredKeyGroups.add(ownKeyGroupId); + } + isFreezed = false; + unstashAll(); }) .failure(e -> { Log.w(TAG, "Unable to fetch initial parameters. Freezing encryption with user #" + uid); @@ -73,164 +67,164 @@ public void preStart() { }); } - private void onLoaded() { - Log.d(TAG, "Loaded initial parameters"); - isReady = true; - unstashAll(); - } + private Promise doEncrypt(byte[] data) { - private Promise doEncrypt(final byte[] data) { + // Stage 1: Loading User Key Groups + return wrap(PromisesArray.of(theirKeys.getUserKeysGroups()) + // Stage 1.1: Filtering invalid key groups and own key groups + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) && + (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) - // - // Stage 1: Loading User Key Groups - // Stage 2: Pick sessions for encryption - // Stage 3: Encrypt box_key int session - // Stage 4: Encrypt box - // - final byte[] encKey = Crypto.randomBytes(32); - final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "doEncrypt"); - final long start = Runtime.getActorTime(); - return PromisesArray.of(theirKeys.getUserKeysGroups()) - .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId())) - .mapOptional(keysGroup -> { + // Stage 2: Pick sessions for encryption + .map(keysGroup -> { if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { - return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); + return success(activeSessions.get(keysGroup.getKeyGroupId()).first()); } - return context().getEncryption().getSessionManager() + return getSessionManager() .pickSession(uid, keysGroup.getKeyGroupId()) + .map(src -> spawnSession(src)) .failure(e -> { ignoredKeyGroups.add(keysGroup.getKeyGroupId()); - }) - .map(src -> spawnSession(src)); + }); }) - .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended) - .map(r -> new Tuple2<>(r, sessionActor.getSession().getTheirKeyGroupId()))) - .zip() - .map(src -> { + .filterFailed() - if (src.size() == 0) { - throw new RuntimeException("No sessions available"); - } + // Stage 3: Encrypt box_keys + .map(s -> s.encrypt(data)) + .filterFailed() - Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + // Stage 4: Zip Everything together + .zip(src -> new EncryptedUserKeys(uid, src, new HashSet<>(ignoredKeyGroups)))); - ArrayList encryptedKeys = new ArrayList<>(); - for (Tuple2 r : src) { - Log.d(TAG, "Keys: " + r.getT2()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getT2(), "curve25519", r.getT1())); - } - byte[] encData; - try { - encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); - } catch (IntegrityException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } +// final byte[] encKey = Crypto.randomBytes(32); +// final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + // byte[] encData; +// try { +// encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); +// } catch (IntegrityException e) { +// e.printStackTrace(); +// throw new RuntimeException(e); +// } + +// Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + +// return new EncryptedUserKeys( +// encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), +// ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - return new EncryptedBox( - encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), - ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - }); } - private Promise doDecrypt(final EncryptedBox data) { + private Promise doDecrypt(int senderKeyGroupId, List keys) { - final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); - final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); +// final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); +// final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); // - // Picking session + // Picking key // - - if (ignoredKeyGroups.contains(senderKeyGroup)) { + if (ignoredKeyGroups.contains(senderKeyGroupId)) { throw new RuntimeException("This key group is ignored"); } + ApiEncyptedBoxKey key = null; + for (ApiEncyptedBoxKey boxKey : keys) { + if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == uid) { + key = boxKey; + break; + } + } + if (key == null) { + throw new RuntimeException("Unable to find suitable key group's key"); + } + final ApiEncyptedBoxKey finalKey = key; - return PromisesArray.of(data.getKeys()) - .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) - .first() - .flatMap(boxKey -> { - final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); - final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); - - if (activeSessions.containsKey(boxKey.getKeyGroupId())) { - for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { - if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && - s.getSession().getTheirPreKeyId() == senderPreKeyId) { - return success(new Tuple2<>(s, boxKey)); - } - } - } - return context().getEncryption().getSessionManager() - .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) - .map(new Function>() { - @Override - public Tuple2 apply(PeerSession src) { - return new Tuple2<>(spawnSession(src), boxKey); - } - }); - }) - .flatMap(src -> { - Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - return src.getT1().getEncryptedSession().decrypt(src.getT2().getEncryptedKey()); - }) - .map(decryptedPackage -> { - byte[] encData; - try { - byte[] encKeyExtended = decryptedPackage.length >= 128 - ? decryptedPackage - : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); - encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); - Log.d(TAG, "Box size: " + encData.length); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - return encData; - }); + // + // Decryption + // + long senderPreKeyId = ByteStrings.bytesToLong(key.getEncryptedKey(), 0); + long receiverPreKeyId = ByteStrings.bytesToLong(key.getEncryptedKey(), 8); + if (activeSessions.containsKey(key.getKeyGroupId())) { + for (EncryptedSession s : activeSessions.get(senderKeyGroupId).getSessions()) { + if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && + s.getSession().getTheirPreKeyId() == senderPreKeyId) { + return wrap(s.decrypt(key)); + } + } + } + return wrap(getSessionManager() + .pickSession(uid, senderKeyGroupId, receiverPreKeyId, senderPreKeyId) + .flatMap(src -> spawnSession(src).decrypt(finalKey))); + +// .map(decryptedPackage -> { +// byte[] encData; +// try { +// byte[] encKeyExtended = decryptedPackage.length >= 128 +// ? decryptedPackage +// : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); +// encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroupId), encPackage, new ActorBoxKey(encKeyExtended)); +// Log.d(TAG, "Box size: " + encData.length); +// } catch (IOException e) { +// e.printStackTrace(); +// throw new RuntimeException(e); +// } +// return encData; +// }); } private void onKeysUpdated(UserKeys userKeys) { this.theirKeys = userKeys; } - private SessionActor spawnSession(final PeerSession session) { - - ActorRef res = system().actorOf(getPath() + "/k_" + RandomUtils.nextRid(), - () -> new EncryptedSessionActor(context(), session)); - SessionActor cont = new SessionActor(new EncryptedSession(res), session); + // + // Tools + // - if (activeSessions.containsKey(session.getTheirKeyGroupId())) { - activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); + private EncryptedSession spawnSession(PeerSession peerSession) { + EncryptedSession session = new EncryptedSession(peerSession, context()); + if (activeSessions.containsKey(peerSession.getTheirKeyGroupId())) { + activeSessions.get(peerSession.getTheirKeyGroupId()).getSessions().add(session); } else { - ArrayList l = new ArrayList<>(); - l.add(cont); - activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); + ArrayList l = new ArrayList<>(); + l.add(session); + activeSessions.put(peerSession.getTheirKeyGroupId(), new KeyGroupHolder(peerSession.getTheirKeyGroupId(), l)); } - return cont; + return session; + } + + private Promise wrap(Promise p) { + isFreezed = true; + p.after((r, e) -> { + isFreezed = false; + unstashAll(); + }); + return p; + } + + private SessionManager getSessionManager() { + return context().getEncryption().getSessionManager(); } + // // Messages // @Override public Promise onAsk(Object message) throws Exception { - if (!isReady) { + if (isFreezed) { stash(); return null; } if (message instanceof EncryptBox) { - return doEncrypt(((EncryptBox) message).getData()); + EncryptBox encryptBox = (EncryptBox) message; + return doEncrypt(encryptBox.getData()); } else if (message instanceof DecryptBox) { - return doDecrypt(((DecryptBox) message).getEncryptedBox()); + DecryptBox decryptBox = (DecryptBox) message; + return doDecrypt(decryptBox.getSenderKeyGroupId(), decryptBox.getKeys()); } else { return super.onAsk(message); } @@ -239,7 +233,7 @@ public Promise onAsk(Object message) throws Exception { @Override public void onReceive(Object message) { if (message instanceof KeyGroupUpdated) { - if (!isReady) { + if (isFreezed) { stash(); return; } @@ -249,7 +243,7 @@ public void onReceive(Object message) { } } - public static class EncryptBox implements AskMessage { + public static class EncryptBox implements AskMessage { private byte[] data; public EncryptBox(byte[] data) { @@ -263,14 +257,20 @@ public byte[] getData() { public static class DecryptBox implements AskMessage { - private EncryptedBox encryptedBox; + private int senderKeyGroupId; + private List keys; + + public DecryptBox(int senderKeyGroupId, List keys) { + this.senderKeyGroupId = senderKeyGroupId; + this.keys = keys; + } - public DecryptBox(EncryptedBox encryptedBox) { - this.encryptedBox = encryptedBox; + public int getSenderKeyGroupId() { + return senderKeyGroupId; } - public EncryptedBox getEncryptedBox() { - return encryptedBox; + public List getKeys() { + return keys; } } @@ -287,12 +287,12 @@ public UserKeys getUserKeys() { } } - private class SessionHolder { + private class KeyGroupHolder { private int keyGroupId; - private ArrayList sessions; + private ArrayList sessions; - public SessionHolder(int keyGroupId, ArrayList sessions) { + public KeyGroupHolder(int keyGroupId, ArrayList sessions) { this.keyGroupId = keyGroupId; this.sessions = sessions; } @@ -301,28 +301,12 @@ public int getKeyGroupId() { return keyGroupId; } - public ArrayList getSessions() { + public ArrayList getSessions() { return sessions; } - } - - private class SessionActor { - - private EncryptedSession encryptedSession; - private PeerSession session; - public SessionActor(EncryptedSession encryptedSession, PeerSession session) { - this.encryptedSession = encryptedSession; - this.session = session; - } - - public EncryptedSession getEncryptedSession() { - return encryptedSession; - } - - public PeerSession getSession() { - return session; + public EncryptedSession first() { + return sessions.get(0); } } - } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java index 7e20072d5a..1838f98220 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java @@ -2,10 +2,10 @@ import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index ec7f04681c..9b68643035 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -3,10 +3,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import im.actor.core.api.ApiEncryptionKey; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.api.ApiEncryptionKeySignature; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateNewKeyGroup; import im.actor.core.api.rpc.RequestLoadPrePublicKeys; @@ -16,14 +18,13 @@ import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.Configuration; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKeyStorage; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; -import im.actor.core.modules.encryption.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKeyStorage; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeysGroup; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; import im.actor.core.modules.ModuleActor; -import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -442,6 +443,18 @@ private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { return Promise.success(null); } + private Promise onUserKeysChanged(List missed, List obsolete) { + + + +// UserKeys userKeys = getCachedUserKeys(uid); +// if (userKeys == null) { +// return Promise.success(null); +// } + + return Promise.success(null); + } + // // Helper methods // @@ -716,4 +729,27 @@ public int getKeyGroupId() { return keyGroupId; } } + + // + // Missed or wrong key groups + // + + public static class KeyGroupsDiff implements AskMessage { + + private List missed; + private List obsolete; + + public KeyGroupsDiff(List missed, List obsolete) { + this.missed = missed; + this.obsolete = obsolete; + } + + public List getMissed() { + return missed; + } + + public List getObsolete() { + return obsolete; + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 2422229f18..21fb0708cc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -7,11 +7,11 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeysGroup; import im.actor.core.util.BaseKeyValueEngine; import im.actor.core.modules.ModuleActor; import im.actor.core.util.RandomUtils; @@ -21,9 +21,6 @@ import im.actor.runtime.crypto.ratchet.RatchetMasterSecret; import im.actor.runtime.crypto.ratchet.RatchetPrivateKey; import im.actor.runtime.crypto.ratchet.RatchetPublicKey; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.FunctionTupled4; -import im.actor.runtime.function.Tuple4; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java new file mode 100644 index 0000000000..38c8e80940 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java @@ -0,0 +1,25 @@ +package im.actor.core.modules.encryption.ratchet.entity; + +import java.util.List; + +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiKeyGroupId; + +public class EncryptedMessage { + + private ApiEncryptedBox encryptedBox; + private List ignoredGroups; + + public EncryptedMessage(ApiEncryptedBox encryptedBox, List ignoredGroups) { + this.encryptedBox = encryptedBox; + this.ignoredGroups = ignoredGroups; + } + + public ApiEncryptedBox getEncryptedBox() { + return encryptedBox; + } + + public List getIgnoredGroups() { + return ignoredGroups; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java new file mode 100644 index 0000000000..663f28279c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java @@ -0,0 +1,31 @@ +package im.actor.core.modules.encryption.ratchet.entity; + +import java.util.HashSet; +import java.util.List; + +import im.actor.core.api.ApiEncyptedBoxKey; + +public class EncryptedUserKeys { + + private int uid; + private List boxKeys; + private HashSet ignoredKeys; + + public EncryptedUserKeys(int uid, List boxKeys, HashSet ignoredKeys) { + this.uid = uid; + this.boxKeys = boxKeys; + this.ignoredKeys = ignoredKeys; + } + + public int getUid() { + return uid; + } + + public List getBoxKeys() { + return boxKeys; + } + + public HashSet getIgnoredKeys() { + return ignoredKeys; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java similarity index 80% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java index d05e90fb04..29aa84b1bc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java @@ -1,6 +1,5 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; -import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.runtime.actors.ask.AskResult; public class OwnIdentity extends AskResult { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java index 07d7be2e28..e672c7cd8b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java index 74a0c65541..b011c4c4fc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java similarity index 95% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java index a246c75033..c08ebcc625 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java similarity index 97% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java index 54af77bfbf..4d789dadea 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java similarity index 90% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java index 43df64a59e..afcdc34c87 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; @@ -12,12 +12,7 @@ public class UserKeysGroup extends BserObject { public static Predicate BY_KEY_GROUP(final int keyGroupId) { - return new Predicate() { - @Override - public boolean apply(UserKeysGroup keysGroup) { - return keysGroup.getKeyGroupId() == keyGroupId; - } - }; + return keysGroup -> keysGroup.getKeyGroupId() == keyGroupId; } private int keyGroupId; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java deleted file mode 100644 index 9b72a29b07..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java +++ /dev/null @@ -1,53 +0,0 @@ -package im.actor.core.modules.encryption.session; - -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.runtime.crypto.ratchet.RatchetMasterSecret; -import im.actor.runtime.crypto.ratchet.RatchetPrivateKey; -import im.actor.runtime.crypto.ratchet.RatchetPublicKey; - -public class EncryptedSession { - private PrivateKey ownIdentityKey; - private PrivateKey ownPreKey; - private PublicKey theirIdentityKey; - private PublicKey theirPreKey; - private int peerKeyGroupId; - private byte[] masterKey; - - public EncryptedSession(PrivateKey ownIdentityKey, PrivateKey ownPreKey, PublicKey theirIdentityKey, PublicKey theirPreKey, int peerKeyGroupId) { - this.ownIdentityKey = ownIdentityKey; - this.ownPreKey = ownPreKey; - this.theirIdentityKey = theirIdentityKey; - this.theirPreKey = theirPreKey; - this.peerKeyGroupId = peerKeyGroupId; - this.masterKey = RatchetMasterSecret.calculateMasterSecret( - new RatchetPrivateKey(ownIdentityKey.getKey()), - new RatchetPrivateKey(ownPreKey.getKey()), - new RatchetPublicKey(theirIdentityKey.getPublicKey()), - new RatchetPublicKey(theirPreKey.getPublicKey())); - } - - public PrivateKey getOwnIdentityKey() { - return ownIdentityKey; - } - - public PrivateKey getOwnPreKey() { - return ownPreKey; - } - - public PublicKey getTheirIdentityKey() { - return theirIdentityKey; - } - - public PublicKey getTheirPreKey() { - return theirPreKey; - } - - public int getPeerKeyGroupId() { - return peerKeyGroupId; - } - - public byte[] getMasterKey() { - return masterKey; - } -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java deleted file mode 100644 index 0d805dd19e..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java +++ /dev/null @@ -1,5 +0,0 @@ -package im.actor.core.modules.encryption.session; - -public class EncryptedSessionStorage { - -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index faa72124f4..fb81defa14 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -30,6 +30,7 @@ import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -66,6 +67,7 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -494,34 +496,18 @@ public void onError(RpcException e) { Log.d("SenderActor", "Pending encrypted message: " + message); ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), rid, message); - context().getEncryption().encrypt(peer.getPeerId(), content).then(apiEncryptedMessage -> { - Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); - - long accessHash = getUser(peer.getPeerId()).getAccessHash(); - ArrayList peers = new ArrayList<>(); - peers.add(new ApiUserOutPeer(peer.getPeerId(), accessHash)); - RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, peers, - new ArrayList<>(), apiEncryptedMessage); - - api(request).then(response -> { - - self().send(new MessageSent(peer, rid)); - - // TODO: Replace - context().getMessagesModule().getRouter().onOutgoingSent(peer, - rid, response.getDate()); -// updates().onUpdateReceived(new SeqUpdate(response.getSeq(), -// response.getState(), -// UpdateMessageSent.HEADER, -// new UpdateMessageSent(new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()), -// rid, response.getDate()).toByteArray())); - - wakeLock.releaseLock(); - }).failure(e -> { - self().send(new MessageError(peer, rid)); - wakeLock.releaseLock(); - }); + ArrayList receivers = new ArrayList<>(); + // receivers.add(myUid()); + receivers.add(peer.getPeerId()); + context().getEncryption().doSend(rid, content, receivers).then(r -> { + + self().send(new MessageSent(peer, rid)); + + // TODO: Replace + context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r.getDate()); + + wakeLock.releaseLock(); }).failure(e -> { self().send(new MessageError(peer, rid)); wakeLock.releaseLock(); diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java index 3eda9ce2cb..78f1f6fc93 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java @@ -130,6 +130,11 @@ public PromisesArray ignoreFailed() { })); } + public PromisesArray filterFailed() { + return ignoreFailed() + .filterNull(); + } + public PromisesArray filterNull() { return filter(Predicates.NOT_NULL); } @@ -364,4 +369,13 @@ public Promise zipPromise(final ListFunction> fuc) { public Promise> zip() { return zipPromise(t -> Promise.success(t)); } + + /** + * Zipping array of promises to single promise of array + * + * @return promise + */ + public Promise zip(Function, V> mapfunc) { + return zip().map(mapfunc); + } } \ No newline at end of file From 4f76b1dfca83382709bbe416d414ec61a1e32541 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 13:36:28 +0300 Subject: [PATCH 13/81] fix(android): Fixing actor crashing on last message deletion --- .../im/actor/core/modules/messaging/router/RouterActor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index ea069508e6..afa89498fd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -497,7 +497,11 @@ private Promise onMessageDeleted(Peer peer, List rids) { Message head = conversation(peer).getHeadValue(); - return getDialogsRouter().onMessageDeleted(peer, head.getMessageState() == MessageState.PENDING ? null : head); + if (head != null && head.getMessageState() == MessageState.PENDING) { + head = null; + } + + return getDialogsRouter().onMessageDeleted(peer, head); } private Promise onChatClear(Peer peer) { From be30ad43b8eb8b84b2e2bbd2d2e6755a76bd2a35 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:13:26 +0300 Subject: [PATCH 14/81] fix(core): Restore keys validation in chain decryption, fix key filtration in EncryptedUserActor, fallback of processing of encrypted updates --- .../encryption/EncryptionProcessor.java | 3 +- .../ratchet/EncryptedSessionChain.java | 35 +++++++++---------- .../ratchet/EncryptedUserActor.java | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 6fad452130..92f3fa99da 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -16,6 +16,7 @@ import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; public class EncryptionProcessor extends AbsModule implements SequenceProcessor { @@ -42,7 +43,7 @@ public Promise process(Update update) { .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) .flatMap(message -> { return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); - }); + }).fallback(e -> Promise.success(null)); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java index e052fddc80..3f4c6b9695 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java @@ -17,6 +17,7 @@ public class EncryptedSessionChain { private PeerSession session; private byte[] ownPrivateKey; + private byte[] ownPublicKey; private byte[] theirPublicKey; private int sentCounter; private byte[] rootChainKey; @@ -24,6 +25,7 @@ public class EncryptedSessionChain { public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] theirPublicKey) { this.session = session; this.ownPrivateKey = ownPrivateKey; + this.ownPublicKey = Curve25519.keyGenPublic(ownPrivateKey); this.theirPublicKey = theirPublicKey; this.sentCounter = 0; this.rootChainKey = RatchetRootChainKey.makeRootChainKey( @@ -64,29 +66,26 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // Validating header // -// if (senderKeyGroupId != session.getPeerKeyGroupId()) { -// throw new IntegrityException("Incorrect sender key group id"); -// } -// if (senderEphermalKey0Id != session.getTheirPreKey().getKeyId()) { -// throw new IntegrityException("Incorrect sender pre key id"); -// } -// if (receiverEphermalKey0Id != session.getOwnPreKey().getKeyId()) { -// throw new IntegrityException("Incorrect receiver pre key id"); -// } -// if (ByteStrings.isEquals(senderEphemeralKey, theirPublicKey)) { -// throw new IntegrityException("Incorrect sender ephemeral key"); -// } -// if (ByteStrings.isEquals(receiverEphemeralKey, ownPrivateKey)) { -// throw new IntegrityException("Incorrect receiver ephemeral key"); -// } + if (senderEphermalKey0Id != session.getTheirPreKeyId()) { + throw new IntegrityException("Incorrect sender pre key id"); + } + if (receiverEphermalKey0Id != session.getOwnPreKeyId()) { + throw new IntegrityException("Incorrect receiver pre key id"); + } + if (!ByteStrings.isEquals(senderEphemeralKey, theirPublicKey)) { + throw new IntegrityException("Incorrect sender ephemeral key"); + } + if (!ByteStrings.isEquals(receiverEphemeralKey, ownPublicKey)) { + throw new IntegrityException("Incorrect receiver ephemeral key"); + } // // Decryption // ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); - byte[] header = ByteStrings.substring(data, 0, 80); - byte[] message = ByteStrings.substring(data, 80, data.length - 80); + byte[] header = ByteStrings.substring(data, 0, 84); + byte[] message = ByteStrings.substring(data, 84, data.length - 84); return ActorBox.openBox(header, message, ratchetMessageKey); } @@ -97,7 +96,7 @@ public byte[] encrypt(byte[] data) throws IntegrityException { byte[] header = ByteStrings.merge( ByteStrings.longToBytes(session.getOwnPreKeyId()), /*Alice Initial Ephermal*/ ByteStrings.longToBytes(session.getTheirPreKeyId()), /*Bob Initial Ephermal*/ - Curve25519.keyGenPublic(ownPrivateKey), + ownPublicKey, theirPublicKey, ByteStrings.intToBytes(messageIndex)); /* Message Index */ diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 868976c213..8da08da2b2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -130,7 +130,7 @@ private Promise doDecrypt(int senderKeyGroupId, List } ApiEncyptedBoxKey key = null; for (ApiEncyptedBoxKey boxKey : keys) { - if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == uid) { + if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == myUid()) { key = boxKey; break; } From 10443413232a48aa129e5c052523d98cd570493e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:55:15 +0300 Subject: [PATCH 15/81] fix(core): Fixing incorrect user id in encrypted message header --- .../core/modules/encryption/ratchet/EncryptedMsgActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 753145c1ac..31dacd0c70 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -114,7 +114,7 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { // Decryption of package byte[] dataHeader = ByteStrings.merge( new byte[]{VERSION}, - ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(uid), ByteStrings.intToBytes(box.getSenderKeyGroupId())); byte[] data; try { From c96b89724ac52c209044496aa51538578e5339e4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:56:16 +0300 Subject: [PATCH 16/81] fix(core): Remove saving latest their ephemeral key --- .../encryption/ratchet/EncryptedSessionActor.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index 9348515891..f71045fa62 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -88,9 +88,11 @@ private Promise onEncrypt(final byte[] data) { Promise ephemeralKey; if (latestTheirEphemeralKey != null) { ephemeralKey = success(latestTheirEphemeralKey); + Log.d(TAG, "Picked cached"); } else { ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) .map(PublicKey::getPublicKey); + Log.d(TAG, "Picked from server #" + uid + " " + session.getTheirKeyGroupId()); } return ephemeralKey @@ -115,12 +117,12 @@ private Promise onDecrypt(ApiEncyptedBoxKey data) { // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 8); final byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); final byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); - Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); - Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); +// Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); +// Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)) - .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)); + //.then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { @@ -151,8 +153,8 @@ private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { throw new RuntimeException(e); } - Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); - Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); +// Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); +// Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); return encrypted; } From 687bb129a21b4ea99e6755d5cd34759612833fea Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 15:00:17 +0300 Subject: [PATCH 17/81] fix(core): Fixing encrypted container serialization --- .../core/modules/encryption/ratchet/EncryptedMsgActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 31dacd0c70..c14c1678e0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -60,7 +60,7 @@ private Promise doEncrypt(ApiEncryptedContent message, List Date: Thu, 14 Jul 2016 15:16:28 +0300 Subject: [PATCH 18/81] feat(core): Separated encrypted sequence processors --- .../EncryptedSequenceProcessor.java | 9 +++++ .../encryption/EncryptionProcessor.java | 36 ++++++++--------- .../messaging/MessagesProcessorEncrypted.java | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java new file mode 100644 index 0000000000..f509226154 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java @@ -0,0 +1,9 @@ +package im.actor.core.modules.encryption; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public interface EncryptedSequenceProcessor { + Promise onUpdate(int senderId, long date, ApiEncryptedContent update); +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 92f3fa99da..b4688a949b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -13,6 +13,7 @@ import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; @@ -21,8 +22,14 @@ public class EncryptionProcessor extends AbsModule implements SequenceProcessor { + private EncryptedSequenceProcessor[] processors; + public EncryptionProcessor(ModuleContext context) { super(context); + + processors = new EncryptedSequenceProcessor[]{ + new MessagesProcessorEncrypted(context) + }; } @Override @@ -49,27 +56,16 @@ public Promise process(Update update) { } public Promise process(int senderId, long date, ApiEncryptedContent update) { - if (update instanceof ApiEncryptedMessageContent) { - ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; - - Message msg = new Message(content.getRid(), date, date, senderId, - MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); - - int destId = senderId; - if (senderId == myUid()) { - destId = content.getReceiverId(); + Promise res = null; + for (EncryptedSequenceProcessor s : processors) { + res = s.onUpdate(senderId, date, update); + if (res != null) { + break; } - - return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(destId), msg); - } else if (update instanceof ApiEncryptedDeleteContent) { - // TODO: Implement - return Promise.success(null); - } else if (update instanceof ApiEncryptedEditContent) { - // TODO: Implement - return Promise.success(null); - } else { - return Promise.success(null); } + if (res == null) { + res = Promise.success(null); + } + return res; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java new file mode 100644 index 0000000000..9fee0bd6ec --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -0,0 +1,40 @@ +package im.actor.core.modules.messaging; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.entity.Message; +import im.actor.core.entity.MessageState; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.EncryptedSequenceProcessor; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class MessagesProcessorEncrypted extends AbsModule implements EncryptedSequenceProcessor { + + public MessagesProcessorEncrypted(ModuleContext context) { + super(context); + } + + @Override + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(destId), msg); + } else { + return null; + } + } +} \ No newline at end of file From 713b44f25f8cb2cca2273cb3e8a6b05a90310e91 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 17:29:49 +0300 Subject: [PATCH 19/81] feat(core): Read/Receive for encrypted chats --- actor-sdk/sdk-api/actor.json | 86 ++++++++++++++++++- .../models/im/actor/api/scheme.mps | 67 +++++++++++++++ .../actor/core/api/ApiEncryptedContent.java | 2 + .../im/actor/core/api/ApiEncryptedRead.java | 64 ++++++++++++++ .../actor/core/api/ApiEncryptedReceived.java | 64 ++++++++++++++ .../api/rpc/RequestTransferOwnership.java | 14 +-- .../core/api/rpc/ResponseCreateGroup.java | 10 ++- .../api/updates/UpdateGroupMemberDiff.java | 24 ++++-- .../modules/encryption/EncryptionModule.java | 9 ++ .../encryption/EncryptionProcessor.java | 4 + .../messaging/MessagesProcessorEncrypted.java | 24 ++---- .../messaging/actions/CursorReaderActor.java | 29 ++++--- .../actions/CursorReceiverActor.java | 30 ++++--- .../modules/messaging/router/RouterActor.java | 44 ++++++++++ .../modules/messaging/router/RouterInt.java | 6 ++ .../router/entity/RouterEncryptedUpdate.java | 30 +++++++ 16 files changed, 446 insertions(+), 61 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 5a6a1c7770..c6cbfc11fa 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -7904,7 +7904,7 @@ "type": "reference", "argument": "canSendMessage", "category": "full", - "description": " Can user send messages. Default is equals isMember for Group and false for others." + "description": " Can user send messages. Default is equals isMember for Group and false for channels." }, { "type": "reference", @@ -8134,7 +8134,7 @@ "type": "reference", "argument": "isAsyncMembers", "category": "full", - "description": " Is Members need to be loaded asynchronous. Default is false." + "description": " Is Members need to be loaded asynchronous." }, { "type": "reference", @@ -8152,7 +8152,7 @@ "type": "reference", "argument": "isSharedHistory", "category": "full", - "description": " Is history shared among all users. Default is false." + "description": " Is history shared among all users." } ], "attributes": [ @@ -19513,6 +19513,86 @@ } ] } + }, + { + "type": "struct", + "content": { + "name": "EncryptedReceived", + "doc": [ + "Encrypted message receive notification", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "receiveDate", + "category": "full", + "description": " Receive message sort date" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 4 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "receiveDate" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedRead", + "doc": [ + "Encrypted message read notification", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "readDate", + "category": "full", + "description": " Read message sort date" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 5 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "readDate" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index d08b3f5e1d..33883f064b 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -16508,6 +16508,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java index 197fd87258..bdf91eb1ee 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -23,6 +23,8 @@ public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { case 1: return Bser.parse(new ApiEncryptedMessageContent(), content); case 2: return Bser.parse(new ApiEncryptedEditContent(), content); case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); + case 4: return Bser.parse(new ApiEncryptedReceived(), content); + case 5: return Bser.parse(new ApiEncryptedRead(), content); default: return new ApiEncryptedContentUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java new file mode 100644 index 0000000000..1579c294c2 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedRead extends ApiEncryptedContent { + + private int receiverId; + private long readDate; + + public ApiEncryptedRead(int receiverId, long readDate) { + this.receiverId = receiverId; + this.readDate = readDate; + } + + public ApiEncryptedRead() { + + } + + public int getHeader() { + return 5; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getReadDate() { + return this.readDate; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.readDate = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.readDate); + } + + @Override + public String toString() { + String res = "struct EncryptedRead{"; + res += "receiverId=" + this.receiverId; + res += ", readDate=" + this.readDate; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java new file mode 100644 index 0000000000..bbbf408bf2 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedReceived extends ApiEncryptedContent { + + private int receiverId; + private long receiveDate; + + public ApiEncryptedReceived(int receiverId, long receiveDate) { + this.receiverId = receiverId; + this.receiveDate = receiveDate; + } + + public ApiEncryptedReceived() { + + } + + public int getHeader() { + return 4; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getReceiveDate() { + return this.receiveDate; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.receiveDate = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.receiveDate); + } + + @Override + public String toString() { + String res = "struct EncryptedReceived{"; + res += "receiverId=" + this.receiverId; + res += ", receiveDate=" + this.receiveDate; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java index 5583c9b4fe..7b0ab441bb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/RequestTransferOwnership.java @@ -23,9 +23,9 @@ public static RequestTransferOwnership fromBytes(byte[] data) throws IOException } private ApiGroupOutPeer groupPeer; - private int newOwner; + private ApiUserOutPeer newOwner; - public RequestTransferOwnership(@NotNull ApiGroupOutPeer groupPeer, int newOwner) { + public RequestTransferOwnership(@NotNull ApiGroupOutPeer groupPeer, @NotNull ApiUserOutPeer newOwner) { this.groupPeer = groupPeer; this.newOwner = newOwner; } @@ -39,14 +39,15 @@ public ApiGroupOutPeer getGroupPeer() { return this.groupPeer; } - public int getNewOwner() { + @NotNull + public ApiUserOutPeer getNewOwner() { return this.newOwner; } @Override public void parse(BserValues values) throws IOException { this.groupPeer = values.getObj(1, new ApiGroupOutPeer()); - this.newOwner = values.getInt(2); + this.newOwner = values.getObj(2, new ApiUserOutPeer()); } @Override @@ -55,7 +56,10 @@ public void serialize(BserWriter writer) throws IOException { throw new IOException(); } writer.writeObject(1, this.groupPeer); - writer.writeInt(2, this.newOwner); + if (this.newOwner == null) { + throw new IOException(); + } + writer.writeObject(2, this.newOwner); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java index d91f7aeeb8..ab181a6f7c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/rpc/ResponseCreateGroup.java @@ -24,13 +24,15 @@ public static ResponseCreateGroup fromBytes(byte[] data) throws IOException { private int seq; private byte[] state; + private long date; private ApiGroup group; private List users; private List userPeers; - public ResponseCreateGroup(int seq, @NotNull byte[] state, @NotNull ApiGroup group, @NotNull List users, @NotNull List userPeers) { + public ResponseCreateGroup(int seq, @NotNull byte[] state, long date, @NotNull ApiGroup group, @NotNull List users, @NotNull List userPeers) { this.seq = seq; this.state = state; + this.date = date; this.group = group; this.users = users; this.userPeers = userPeers; @@ -49,6 +51,10 @@ public byte[] getState() { return this.state; } + public long getDate() { + return this.date; + } + @NotNull public ApiGroup getGroup() { return this.group; @@ -68,6 +74,7 @@ public List getUserPeers() { public void parse(BserValues values) throws IOException { this.seq = values.getInt(1); this.state = values.getBytes(2); + this.date = values.getLong(6); this.group = values.getObj(3, new ApiGroup()); List _users = new ArrayList(); for (int i = 0; i < values.getRepeatedCount(4); i ++) { @@ -88,6 +95,7 @@ public void serialize(BserWriter writer) throws IOException { throw new IOException(); } writer.writeBytes(2, this.state); + writer.writeLong(6, this.date); if (this.group == null) { throw new IOException(); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java index 1407d373d6..b2d2a7def4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/updates/UpdateGroupMemberDiff.java @@ -22,11 +22,13 @@ public static UpdateGroupMemberDiff fromBytes(byte[] data) throws IOException { return Bser.parse(new UpdateGroupMemberDiff(), data); } + private int groupId; private List removedUsers; private List addedMembers; private int membersCount; - public UpdateGroupMemberDiff(@NotNull List removedUsers, @NotNull List addedMembers, int membersCount) { + public UpdateGroupMemberDiff(int groupId, @NotNull List removedUsers, @NotNull List addedMembers, int membersCount) { + this.groupId = groupId; this.removedUsers = removedUsers; this.addedMembers = addedMembers; this.membersCount = membersCount; @@ -36,6 +38,10 @@ public UpdateGroupMemberDiff() { } + public int getGroupId() { + return this.groupId; + } + @NotNull public List getRemovedUsers() { return this.removedUsers; @@ -52,20 +58,22 @@ public int getMembersCount() { @Override public void parse(BserValues values) throws IOException { - this.removedUsers = values.getRepeatedInt(1); + this.groupId = values.getInt(1); + this.removedUsers = values.getRepeatedInt(2); List _addedMembers = new ArrayList(); - for (int i = 0; i < values.getRepeatedCount(2); i ++) { + for (int i = 0; i < values.getRepeatedCount(3); i ++) { _addedMembers.add(new ApiMember()); } - this.addedMembers = values.getRepeatedObj(2, _addedMembers); - this.membersCount = values.getInt(3); + this.addedMembers = values.getRepeatedObj(3, _addedMembers); + this.membersCount = values.getInt(4); } @Override public void serialize(BserWriter writer) throws IOException { - writer.writeRepeatedInt(1, this.removedUsers); - writer.writeRepeatedObj(2, this.addedMembers); - writer.writeInt(3, this.membersCount); + writer.writeInt(1, this.groupId); + writer.writeRepeatedInt(2, this.removedUsers); + writer.writeRepeatedObj(3, this.addedMembers); + writer.writeInt(4, this.membersCount); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 919e7d0c14..ad86a8eb54 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -71,6 +71,15 @@ public Promise decrypt(int uid, ApiEncryptedBox encryptedBo return getEncryption().decrypt(uid, encryptedBox); } + public Promise doSend(long rid, ApiEncryptedContent content, int uid) { + ArrayList receiver = new ArrayList<>(); + receiver.add(uid); +// if (uid != myUid()) { +// receiver.add(myUid()); +// } + return doSend(rid, content, receiver); + } + public Promise doSend(long rid, ApiEncryptedContent content, List uids) { ArrayList outPeers = new ArrayList<>(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index b4688a949b..070d49a888 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -16,6 +16,7 @@ import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; +import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; @@ -56,6 +57,9 @@ public Promise process(Update update) { } public Promise process(int senderId, long date, ApiEncryptedContent update) { + + Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); + Promise res = null; for (EncryptedSequenceProcessor s : processors) { res = s.onUpdate(senderId, date, update); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java index 9fee0bd6ec..fd6d4ec577 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -2,10 +2,8 @@ import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiEncryptedMessageContent; -import im.actor.core.entity.Message; -import im.actor.core.entity.MessageState; -import im.actor.core.entity.Peer; -import im.actor.core.entity.content.AbsContent; +import im.actor.core.api.ApiEncryptedRead; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.EncryptedSequenceProcessor; @@ -20,21 +18,13 @@ public MessagesProcessorEncrypted(ModuleContext context) { @Override public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { - if (update instanceof ApiEncryptedMessageContent) { - ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; - - Message msg = new Message(content.getRid(), date, date, senderId, - MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); - - int destId = senderId; - if (senderId == myUid()) { - destId = content.getReceiverId(); - } + if (update instanceof ApiEncryptedMessageContent || + update instanceof ApiEncryptedReceived || + update instanceof ApiEncryptedRead) { return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(destId), msg); - } else { - return null; + .onEncryptedUpdate(senderId, date, update); } + return null; } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java index f2860e6e07..740198b227 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java @@ -4,13 +4,13 @@ package im.actor.core.modules.messaging.actions; +import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageRead; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; public class CursorReaderActor extends CursorActor { @@ -25,17 +25,20 @@ protected void perform(final Peer peer, final long date) { return; } - request(new RequestMessageRead(outPeer, date), new RpcCallback() { - @Override - public void onResult(ResponseVoid response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedRead(peer.getPeerId(), date), peer.getPeerId()).then(r -> { onCompleted(peer, date); - } - - @Override - public void onError(RpcException e) { - CursorReaderActor.this.onError(peer, date); - } - }); + }).failure(e -> { + onError(peer, date); + }); + } else { + api(new RequestMessageRead(outPeer, date)).then(responseVoid -> { + onCompleted(peer, date); + }).failure(e -> { + onError(peer, date); + }); + } } // Messages diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java index afca3ef43b..7ec25d567f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java @@ -4,13 +4,13 @@ package im.actor.core.modules.messaging.actions; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageReceived; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; public class CursorReceiverActor extends CursorActor { @@ -21,22 +21,24 @@ public CursorReceiverActor(ModuleContext context) { @Override protected void perform(final Peer peer, final long date) { ApiOutPeer outPeer = buidOutPeer(peer); - if (outPeer == null) { return; } - request(new RequestMessageReceived(outPeer, date), new RpcCallback() { - @Override - public void onResult(ResponseVoid response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedReceived(peer.getPeerId(), date), peer.getPeerId()).then(r -> { onCompleted(peer, date); - } - - @Override - public void onError(RpcException e) { - CursorReceiverActor.this.onError(peer, date); - } - }); + }).failure(e -> { + onError(peer, date); + }); + } else { + api(new RequestMessageReceived(outPeer, date)).then(responseVoid -> { + onCompleted(peer, date); + }).failure(e -> { + onError(peer, date); + }); + } } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index afa89498fd..2bfe6cf5ec 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -7,6 +7,10 @@ import im.actor.core.api.ApiDialogGroup; import im.actor.core.api.ApiDialogShort; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.api.ApiEncryptedRead; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.api.ApiMessageReaction; import im.actor.core.api.rpc.RequestLoadGroupedDialogs; import im.actor.core.api.updates.UpdateChatClear; @@ -53,6 +57,7 @@ import im.actor.core.modules.messaging.router.entity.RouterDeletedMessages; import im.actor.core.modules.messaging.router.entity.RouterDifferenceEnd; import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart; +import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageOnlyActive; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; @@ -879,6 +884,41 @@ public Promise onUpdate(Update update) { return Promise.success(null); } + public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + ArrayList messages = new ArrayList<>(); + messages.add(msg); + return onNewMessages(Peer.secret(destId), messages); + } else if (update instanceof ApiEncryptedReceived) { + ApiEncryptedReceived encryptedReceived = (ApiEncryptedReceived) update; + int destId = senderId; + if (senderId == myUid()) { + destId = encryptedReceived.getReceiverId(); + } + return onMessageReceived(Peer.secret(destId), encryptedReceived.getReceiveDate()); + } else if (update instanceof ApiEncryptedRead) { + ApiEncryptedRead encryptedRead = (ApiEncryptedRead) update; + if (senderId == myUid()) { + // TODO: Fix Counter + return onMessageReadByMe(Peer.secret(encryptedRead.getReceiverId()), encryptedRead.getReadDate(), 0); + } else { + return onMessageRead(Peer.secret(senderId), encryptedRead.getReadDate()); + } + } + + return Promise.success(null); + } + @Override public void onReceive(Object message) { if (!activeDialogStorage.isLoaded() && message instanceof RouterMessageOnlyActive) { @@ -908,6 +948,10 @@ public Promise onAsk(Object message) throws Exception { } if (message instanceof RouterMessageUpdate) { return onUpdate(((RouterMessageUpdate) message).getUpdate()); + } else if (message instanceof RouterEncryptedUpdate) { + RouterEncryptedUpdate encryptedUpdate = (RouterEncryptedUpdate) message; + return onEncryptedUpdate(encryptedUpdate.getSenderId(), encryptedUpdate.getDate(), + encryptedUpdate.getUpdate()); } else if (message instanceof RouterDifferenceStart) { return onDifferenceStart(); } else if (message instanceof RouterDifferenceEnd) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 78f5d24789..784843c494 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.entity.Group; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; @@ -23,6 +24,7 @@ import im.actor.core.modules.messaging.router.entity.RouterDeletedMessages; import im.actor.core.modules.messaging.router.entity.RouterDifferenceEnd; import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart; +import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; @@ -64,6 +66,10 @@ public Promise onUpdate(Update update) { return ask(new RouterMessageUpdate(update)); } + public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + return ask(new RouterEncryptedUpdate(senderId, date, update)); + } + public Promise onDifferenceEnd() { return ask(new RouterDifferenceEnd()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java new file mode 100644 index 0000000000..7c0b915c98 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java @@ -0,0 +1,30 @@ +package im.actor.core.modules.messaging.router.entity; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterEncryptedUpdate implements AskMessage { + + private int senderId; + private long date; + private ApiEncryptedContent update; + + public RouterEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + this.senderId = senderId; + this.date = date; + this.update = update; + } + + public int getSenderId() { + return senderId; + } + + public long getDate() { + return date; + } + + public ApiEncryptedContent getUpdate() { + return update; + } +} From 48a511350263270aa808db3633d263b7a31118c8 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 19:49:30 +0300 Subject: [PATCH 20/81] feat(core): Secret Chats: typings, delete, chat clear --- actor-sdk/sdk-api/actor.json | 61 ++++++- .../models/im/actor/api/scheme.mps | 49 +++++- .../messages/MessagesDefaultFragment.java | 15 +- .../toolbar/ChatToolbarFragment.java | 2 +- .../main/java/im/actor/core/Messenger.java | 26 +-- .../actor/core/api/ApiEncryptedContent.java | 1 + .../actor/core/api/ApiEncryptedDeleteAll.java | 55 +++++++ .../core/api/ApiEncryptedDeleteContent.java | 11 +- .../java/im/actor/core/api/ApiServiceEx.java | 1 + .../core/api/ApiServiceTimerChanged.java | 65 ++++++++ .../modules/encryption/EncryptedUpdates.java | 38 +++++ .../modules/encryption/EncryptionModule.java | 49 +++++- .../encryption/EncryptionProcessor.java | 43 +---- .../ratchet/EncryptedUserActor.java | 41 +---- .../modules/messaging/MessagesModule.java | 153 +++++++++++------- .../messaging/MessagesProcessorEncrypted.java | 8 +- .../messaging/actions/MessageDeleteActor.java | 36 +++-- .../messaging/actions/SenderActor.java | 5 +- .../modules/messaging/router/RouterActor.java | 25 +++ .../modules/messaging/router/RouterInt.java | 6 +- .../core/modules/typing/TypingActor.java | 116 ++++++++++++- .../core/modules/typing/TypingModule.java | 14 +- 22 files changed, 625 insertions(+), 195 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index c6cbfc11fa..c0ca8999a9 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -4738,6 +4738,33 @@ "attributes": [] } }, + { + "type": "struct", + "content": { + "name": "ServiceTimerChanged", + "doc": [ + "Service Message about timer changed", + { + "type": "reference", + "argument": "timerMs", + "category": "full", + "description": " Timer in milliseconds" + } + ], + "trait": { + "name": "ServiceEx", + "key": 23 + }, + "expandable": "true", + "attributes": [ + { + "type": "int32", + "id": 1, + "name": "timerMs" + } + ] + } + }, { "type": "struct", "content": { @@ -19507,7 +19534,10 @@ "name": "receiverId" }, { - "type": "int64", + "type": { + "type": "list", + "childType": "int64" + }, "id": 2, "name": "rid" } @@ -19593,6 +19623,35 @@ } ] } + }, + { + "type": "struct", + "content": { + "name": "EncryptedDeleteAll", + "doc": [ + "Encrypted message about clearing chat", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 6 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 33883f064b..2485d68733 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -4341,6 +4341,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -16489,7 +16511,9 @@ - + + + @@ -16575,6 +16599,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java index 989cf29914..e5a63309b5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java @@ -15,6 +15,7 @@ import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.content.AbsContent; import im.actor.core.entity.content.TextContent; import im.actor.core.entity.content.UnsupportedContent; @@ -120,10 +121,18 @@ public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { } } - menu.findItem(R.id.copy).setVisible(isAllText); menu.findItem(R.id.quote).setVisible(isAllText); - menu.findItem(R.id.forward).setVisible(selected.length == 1 || isAllText); - menu.findItem(R.id.like).setVisible(selected.length == 1); + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + menu.findItem(R.id.copy).setVisible(false); + menu.findItem(R.id.forward).setVisible(false); + menu.findItem(R.id.like).setVisible(false); + } else { + menu.findItem(R.id.copy).setVisible(isAllText); + menu.findItem(R.id.forward).setVisible(selected.length == 1 || isAllText); + menu.findItem(R.id.like).setVisible(selected.length == 1); + } + return false; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index a440e8f83a..de7a006a04 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -186,7 +186,7 @@ public void onResume() { if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); } else { - // TODO: Implement + bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getSecretTyping(user.getId())); } // Refresh menu on contact state change diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 743e975f7c..fbb482b4dc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -64,6 +64,7 @@ import im.actor.core.viewmodel.UploadFileCallback; import im.actor.core.viewmodel.UploadFileVM; import im.actor.core.viewmodel.UploadFileVMCallback; +import im.actor.core.viewmodel.UserTypingVM; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.ActorSystem; import im.actor.runtime.actors.messages.Void; @@ -488,7 +489,7 @@ public DialogGroupsVM getDialogGroupsVM() { } /** - * Get private chat ViewModel + * Get private chat typing ViewModel * * @param uid chat's User Id * @return ValueModel of Boolean for typing state @@ -499,6 +500,18 @@ public ValueModel getTyping(int uid) { return modules.getTypingModule().getTyping(uid).getTyping(); } + /** + * Get Secret chat typing View Model + * + * @param uid chat's User Id + * @return ValueModel of Boolean for typing state + */ + @NotNull + @ObjectiveCName("getSecretTypingWithUid:") + public ValueModel getSecretTyping(int uid) { + return modules.getTypingModule().getSecretTyping(uid).getTyping(); + } + /** * Get group chat ViewModel * @@ -1598,17 +1611,6 @@ public Command revokeIntegrationToken(int gid) { .failure(e -> callback.onError(e)); } - /** - * Check if chat with bot is started - * - * @param uid bot user id - * @return is chat with bot started - */ - @ObjectiveCName("isStartedWithUid:") - public Promise isStarted(int uid) { - return modules.getMessagesModule().chatIsEmpty(Peer.user(uid)); - } - ////////////////////////////////////// // Blocked List diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java index bdf91eb1ee..9bc1dbfe8d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -25,6 +25,7 @@ public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); case 4: return Bser.parse(new ApiEncryptedReceived(), content); case 5: return Bser.parse(new ApiEncryptedRead(), content); + case 6: return Bser.parse(new ApiEncryptedDeleteAll(), content); default: return new ApiEncryptedContentUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java new file mode 100644 index 0000000000..ecd28d5296 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java @@ -0,0 +1,55 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedDeleteAll extends ApiEncryptedContent { + + private int receiverId; + + public ApiEncryptedDeleteAll(int receiverId) { + this.receiverId = receiverId; + } + + public ApiEncryptedDeleteAll() { + + } + + public int getHeader() { + return 6; + } + + public int getReceiverId() { + return this.receiverId; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + } + + @Override + public String toString() { + String res = "struct EncryptedDeleteAll{"; + res += "receiverId=" + this.receiverId; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java index 1695294074..5c9b36e82b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java @@ -17,9 +17,9 @@ public class ApiEncryptedDeleteContent extends ApiEncryptedContent { private int receiverId; - private long rid; + private List rid; - public ApiEncryptedDeleteContent(int receiverId, long rid) { + public ApiEncryptedDeleteContent(int receiverId, @NotNull List rid) { this.receiverId = receiverId; this.rid = rid; } @@ -36,20 +36,21 @@ public int getReceiverId() { return this.receiverId; } - public long getRid() { + @NotNull + public List getRid() { return this.rid; } @Override public void parse(BserValues values) throws IOException { this.receiverId = values.getInt(1); - this.rid = values.getLong(2); + this.rid = values.getRepeatedLong(2); } @Override public void serialize(BserWriter writer) throws IOException { writer.writeInt(1, this.receiverId); - writer.writeLong(2, this.rid); + writer.writeRepeatedLong(2, this.rid); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java index a970fe563d..3e7ebee995 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java @@ -34,6 +34,7 @@ public static ApiServiceEx fromBytes(byte[] src) throws IOException { case 16: return Bser.parse(new ApiServiceExPhoneCall(), content); case 20: return Bser.parse(new ApiServiceExChatArchived(), content); case 21: return Bser.parse(new ApiServiceExChatRestored(), content); + case 23: return Bser.parse(new ApiServiceTimerChanged(), content); default: return new ApiServiceExUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java new file mode 100644 index 0000000000..719f637d90 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java @@ -0,0 +1,65 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiServiceTimerChanged extends ApiServiceEx { + + private int timerMs; + + public ApiServiceTimerChanged(int timerMs) { + this.timerMs = timerMs; + } + + public ApiServiceTimerChanged() { + + } + + public int getHeader() { + return 23; + } + + public int getTimerMs() { + return this.timerMs; + } + + @Override + public void parse(BserValues values) throws IOException { + this.timerMs = values.getInt(1); + if (values.hasRemaining()) { + setUnmappedObjects(values.buildRemaining()); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.timerMs); + if (this.getUnmappedObjects() != null) { + SparseArray unmapped = this.getUnmappedObjects(); + for (int i = 0; i < unmapped.size(); i++) { + int key = unmapped.keyAt(i); + writer.writeUnmapped(key, unmapped.get(key)); + } + } + } + + @Override + public String toString() { + String res = "struct ServiceTimerChanged{"; + res += "timerMs=" + this.timerMs; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java new file mode 100644 index 0000000000..526a5b6dc9 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java @@ -0,0 +1,38 @@ +package im.actor.core.modules.encryption; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.MessagesProcessorEncrypted; +import im.actor.runtime.Log; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class EncryptedUpdates extends AbsModule { + + private EncryptedSequenceProcessor[] processors; + + public EncryptedUpdates(ModuleContext context) { + super(context); + + processors = new EncryptedSequenceProcessor[]{ + new MessagesProcessorEncrypted(context) + }; + } + + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); + + Promise res = null; + for (EncryptedSequenceProcessor s : processors) { + res = s.onUpdate(senderId, date, update); + if (res != null) { + break; + } + } + if (res == null) { + res = Promise.success(null); + } + return res; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index ad86a8eb54..05e9673a82 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -6,7 +6,6 @@ import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSendEncryptedPackage; @@ -18,7 +17,8 @@ import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; -import im.actor.runtime.function.Function; +import im.actor.core.util.RandomUtils; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -28,6 +28,7 @@ public class EncryptionModule extends AbsModule { private KeyManager keyManager; private SessionManager sessionManager; private EncryptedMsg encryption; + private EncryptedUpdates encryptedUpdates; private final HashMap users = new HashMap<>(); @@ -39,6 +40,7 @@ public void run() { keyManager = new KeyManager(context()); sessionManager = new SessionManager(context()); encryption = new EncryptedMsg(context()); + encryptedUpdates = new EncryptedUpdates(context()); } public KeyManager getKeyManager() { @@ -63,6 +65,10 @@ public EncryptedUser getEncryptedUser(int uid) { } } + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + return encryptedUpdates.onUpdate(senderId, date, update); + } + public Promise encrypt(List uids, ApiEncryptedContent message) { return getEncryption().encrypt(uids, message); } @@ -71,23 +77,46 @@ public Promise decrypt(int uid, ApiEncryptedBox encryptedBo return getEncryption().decrypt(uid, encryptedBox); } + public Promise doSend(ApiEncryptedContent content, int uid) { + return doSend(RandomUtils.nextRid(), content, uid, false); + } + + public Promise doSend(ApiEncryptedContent content, int uid, boolean autoPostUpdate) { + return doSend(RandomUtils.nextRid(), content, uid, autoPostUpdate); + } + + public Promise doSend(ApiEncryptedContent content, List uids) { + return doSend(RandomUtils.nextRid(), content, uids); + } + public Promise doSend(long rid, ApiEncryptedContent content, int uid) { + return doSend(rid, content, uid, false); + } + + public Promise doSend(long rid, ApiEncryptedContent content, int uid, + boolean autoPostUpdate) { ArrayList receiver = new ArrayList<>(); receiver.add(uid); -// if (uid != myUid()) { -// receiver.add(myUid()); -// } - return doSend(rid, content, receiver); + if (uid != myUid()) { + receiver.add(myUid()); + } + return doSend(rid, content, receiver, autoPostUpdate); } - public Promise doSend(long rid, ApiEncryptedContent content, List uids) { + public Promise doSend(long rid, ApiEncryptedContent content, + List uids) { + return doSend(rid, content, uids, false); + } + + public Promise doSend(long rid, ApiEncryptedContent content, + List uids, boolean autoPostUpdate) { ArrayList outPeers = new ArrayList<>(); for (int i : uids) { outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); } - return encrypt(uids, content).flatMap(encryptedMessage -> { + Promise res = encrypt(uids, content).flatMap(encryptedMessage -> { RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); return api(request).flatMap(r -> { @@ -97,5 +126,9 @@ public Promise doSend(long rid, ApiEncryptedConten return Promise.failure(new RuntimeException("Incorrect keys")); }); }); + if (autoPostUpdate) { + res.then(r -> context().getEncryption().onUpdate(myUid(), r.getDate(), content)); + } + return res; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 070d49a888..b34ad7a796 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -1,36 +1,19 @@ package im.actor.core.modules.encryption; -import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedDeleteContent; -import im.actor.core.api.ApiEncryptedEditContent; -import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; -import im.actor.core.entity.Message; -import im.actor.core.entity.MessageState; -import im.actor.core.entity.Peer; -import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; -import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; public class EncryptionProcessor extends AbsModule implements SequenceProcessor { - private EncryptedSequenceProcessor[] processors; - public EncryptionProcessor(ModuleContext context) { super(context); - - processors = new EncryptedSequenceProcessor[]{ - new MessagesProcessorEncrypted(context) - }; } @Override @@ -49,27 +32,13 @@ public Promise process(Update update) { UpdateEncryptedPackage encryptedPackage = (UpdateEncryptedPackage) update; return context().getEncryption() .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) - .flatMap(message -> { - return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); - }).fallback(e -> Promise.success(null)); + .flatMap(message -> context().getEncryption() + .onUpdate( + encryptedPackage.getSenderId(), + encryptedPackage.getDate(), + message)) + .fallback(e -> Promise.success(null)); } return null; } - - public Promise process(int senderId, long date, ApiEncryptedContent update) { - - Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); - - Promise res = null; - for (EncryptedSequenceProcessor s : processors) { - res = s.onUpdate(senderId, date, update); - if (res != null) { - break; - } - } - if (res == null) { - res = Promise.success(null); - } - return res; - } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 8da08da2b2..afe391dcc1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -73,8 +73,8 @@ private Promise doEncrypt(byte[] data) { return wrap(PromisesArray.of(theirKeys.getUserKeysGroups()) // Stage 1.1: Filtering invalid key groups and own key groups - .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) && - (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) + && (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) // Stage 2: Pick sessions for encryption .map(keysGroup -> { @@ -96,32 +96,10 @@ private Promise doEncrypt(byte[] data) { // Stage 4: Zip Everything together .zip(src -> new EncryptedUserKeys(uid, src, new HashSet<>(ignoredKeyGroups)))); - - -// final byte[] encKey = Crypto.randomBytes(32); -// final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - - // byte[] encData; -// try { -// encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); -// } catch (IntegrityException e) { -// e.printStackTrace(); -// throw new RuntimeException(e); -// } - -// Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - -// return new EncryptedUserKeys( -// encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), -// ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - } private Promise doDecrypt(int senderKeyGroupId, List keys) { -// final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); -// final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); - // // Picking key // @@ -156,21 +134,6 @@ private Promise doDecrypt(int senderKeyGroupId, List return wrap(getSessionManager() .pickSession(uid, senderKeyGroupId, receiverPreKeyId, senderPreKeyId) .flatMap(src -> spawnSession(src).decrypt(finalKey))); - -// .map(decryptedPackage -> { -// byte[] encData; -// try { -// byte[] encKeyExtended = decryptedPackage.length >= 128 -// ? decryptedPackage -// : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); -// encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroupId), encPackage, new ActorBoxKey(encKeyExtended)); -// Log.d(TAG, "Box size: " + encData.length); -// } catch (IOException e) { -// e.printStackTrace(); -// throw new RuntimeException(e); -// } -// return encData; -// }); } private void onKeysUpdated(UserKeys userKeys) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index a7b49d824b..f53dcd051a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -10,6 +10,8 @@ import java.util.ArrayList; import java.util.HashMap; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiPeer; @@ -27,6 +29,7 @@ import im.actor.core.api.rpc.ResponseDialogsOrder; import im.actor.core.api.rpc.ResponseLoadArchived; import im.actor.core.api.rpc.ResponseReactionsResponse; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeq; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateChatClear; @@ -64,6 +67,7 @@ import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; import im.actor.core.network.RpcInternalException; +import im.actor.core.util.RandomUtils; import im.actor.core.viewmodel.Command; import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.ConversationVM; @@ -282,47 +286,52 @@ public void sendDocument(Peer peer, String fileName, String mimeType, FastThumb public Promise updateMessage(final Peer peer, final String message, final long rid) { context().getTypingModule().onMessageSent(peer); - ArrayList mentions = new ArrayList<>(); - TextContent content = TextContent.create(message, null, mentions); - if (peer.getPeerType() == PeerType.GROUP) { - Group group = groups().getValue(peer.getPeerId()); - String lowText = message.toLowerCase(); - for (GroupMember member : group.getMembers()) { - User user = users().getValue(member.getUid()); - if (user.getNick() != null) { - String nick = "@" + user.getNick().toLowerCase(); - // TODO: Better filtering - if (lowText.contains(nick + ":") - || lowText.contains(nick + " ") - || lowText.contains(" " + nick) - || lowText.endsWith(nick) - || lowText.equals(nick)) { - mentions.add(user.getUid()); + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + ApiEncryptedEditContent editContent = new ApiEncryptedEditContent( + peer.getPeerId(), rid, new ApiTextMessage(message, new ArrayList<>(), null)); + + return context().getEncryption().doSend(RandomUtils.nextRid(), editContent, peer.getPeerId()).then(r -> { + context().getEncryption().onUpdate(myUid(), r.getDate(), editContent); + }).flatMap(r -> null); + } else { + ArrayList mentions = new ArrayList<>(); + TextContent content = TextContent.create(message, null, mentions); + if (peer.getPeerType() == PeerType.GROUP) { + Group group = groups().getValue(peer.getPeerId()); + String lowText = message.toLowerCase(); + for (GroupMember member : group.getMembers()) { + User user = users().getValue(member.getUid()); + if (user.getNick() != null) { + String nick = "@" + user.getNick().toLowerCase(); + // TODO: Better filtering + if (lowText.contains(nick + ":") + || lowText.contains(nick + " ") + || lowText.contains(" " + nick) + || lowText.endsWith(nick) + || lowText.equals(nick)) { + mentions.add(user.getUid()); + } } } } + ApiMessage editMessage = new ApiTextMessage(message, content.getMentions(), content.getTextMessageEx()); + + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestUpdateMessage(apiOutPeer, rid, editMessage))) + .flatMap(responseSeqDate -> + updates().applyUpdate( + responseSeqDate.getSeq(), + responseSeqDate.getState(), + new UpdateMessageContentChanged( + new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()), + rid, + editMessage) + )); } - ApiMessage editMessage = new ApiTextMessage(message, content.getMentions(), content.getTextMessageEx()); - - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestUpdateMessage(apiOutPeer, rid, editMessage))) - .flatMap(responseSeqDate -> - updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateMessageContentChanged( - new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()), - rid, - editMessage) - )); } - public Promise chatIsEmpty(Peer peer) { - return new Promise<>(resolver -> resolver.result(getConversationEngine(peer).getCount() == 0)); - } - - public void forwardContent(Peer peer, AbsContent content) { sendMessageActor.send(new SenderActor.ForwardContent(peer, content)); } @@ -332,22 +341,30 @@ public void sendSticker(@NotNull Peer peer, sendMessageActor.send(new SenderActor.SendSticker(peer, sticker)); } - public void saveDraft(Peer peer, String draft) { - context().getSettingsModule().setStringValue("drafts_" + peer.getUnuqueId(), draft); + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + context().getSettingsModule().setStringValue("drafts_" + peer.getUnuqueId(), draft); + } } public String loadDraft(Peer peer) { - String res = context().getSettingsModule().getStringValue("drafts_" + peer.getUnuqueId(), null); - if (res == null) { - return ""; + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + String res = context().getSettingsModule().getStringValue("drafts_" + peer.getUnuqueId(), null); + if (res == null) { + return ""; + } else { + return res; + } } else { - return res; + return ""; } } public Promise addReaction(final Peer peer, final long rid, final String reaction) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return Promise.failure(new RuntimeException("Unsupported in secret chats")); + } return buildOutPeer(peer) .flatMap(apiOutPeer -> api(new RequestMessageSetReaction(apiOutPeer, rid, reaction))) @@ -361,6 +378,9 @@ public Promise addReaction(final Peer peer, final long rid, final String r } public Promise removeReaction(final Peer peer, final long rid, final String reaction) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return Promise.failure(new RuntimeException("Unsupported in secret chats")); + } return buildOutPeer(peer) .flatMap(apiOutPeer -> api(new RequestMessageRemoveReaction(apiOutPeer, rid, reaction))) @@ -406,27 +426,38 @@ public Promise archiveChat(final Peer peer) { public Promise deleteChat(final Peer peer) { - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestDeleteChat(apiOutPeer))) - .flatMap(responseSeq -> - updates().applyUpdate( - responseSeq.getSeq(), - responseSeq.getState(), - new UpdateChatDelete(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId())) - )); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + // FIXME: Not actually deletes chat from dialog list + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), + peer.getPeerId(), false).map(v -> (Void) null); + } else { + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestDeleteChat(apiOutPeer))) + .flatMap(responseSeq -> + updates().applyUpdate( + responseSeq.getSeq(), + responseSeq.getState(), + new UpdateChatDelete(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId())) + )); + } } public Promise clearChat(final Peer peer) { - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestClearChat(apiOutPeer))) - .flatMap(responseSeq -> - updates().applyUpdate( - responseSeq.getSeq(), - responseSeq.getState(), - new UpdateChatClear(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()))) - ); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), + peer.getPeerId(), false).map(v -> (Void) null); + } else { + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestClearChat(apiOutPeer))) + .flatMap(responseSeq -> + updates().applyUpdate( + responseSeq.getSeq(), + responseSeq.getState(), + new UpdateChatClear(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()))) + ); + } } @@ -448,7 +479,9 @@ public void loadMoreArchivedDialogs(final boolean init, final RpcCallback getHistoryActor(peer).send(new ConversationHistoryActor.LoadMore())); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + im.actor.runtime.Runtime.dispatch(() -> getHistoryActor(peer).send(new ConversationHistoryActor.LoadMore())); + } } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java index fd6d4ec577..5647f4e74b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -1,6 +1,9 @@ package im.actor.core.modules.messaging; import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiEncryptedReceived; @@ -21,7 +24,10 @@ public Promise onUpdate(int senderId, long date, ApiEncryptedContent updat if (update instanceof ApiEncryptedMessageContent || update instanceof ApiEncryptedReceived || - update instanceof ApiEncryptedRead) { + update instanceof ApiEncryptedRead || + update instanceof ApiEncryptedDeleteContent || + update instanceof ApiEncryptedEditContent || + update instanceof ApiEncryptedDeleteAll) { return context().getMessagesModule().getRouter() .onEncryptedUpdate(senderId, date, update); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java index 6d27b10f5b..0059a7f373 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiEncryptedDeleteContent; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.base.SeqUpdate; @@ -15,12 +16,14 @@ import im.actor.core.api.rpc.ResponseSeq; import im.actor.core.api.updates.UpdateMessageDelete; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.messaging.actions.entity.Delete; import im.actor.core.modules.messaging.actions.entity.DeleteStorage; import im.actor.core.modules.ModuleActor; import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; import im.actor.runtime.storage.SyncKeyValue; public class MessageDeleteActor extends ModuleActor { @@ -61,32 +64,33 @@ void saveStorage() { } public void performDelete(final Peer peer, final List rids) { - final ApiOutPeer outPeer = buidOutPeer(peer); - final ApiPeer apiPeer = buildApiPeer(peer); - request(new RequestDeleteMessage(outPeer, rids), new RpcCallback() { - - @Override - public void onResult(ResponseSeq response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedDeleteContent(peer.getPeerId(), rids), peer.getPeerId()).then(r -> { + if (deleteStorage.getPendingDeletions().containsKey(peer)) { + deleteStorage.getPendingDeletions().get(peer).getRids().removeAll(rids); + saveStorage(); + } + }); + } else { + final ApiOutPeer outPeer = buidOutPeer(peer); + final ApiPeer apiPeer = buildApiPeer(peer); + api(new RequestDeleteMessage(outPeer, rids)).then(r -> { if (deleteStorage.getPendingDeletions().containsKey(peer)) { deleteStorage.getPendingDeletions().get(peer).getRids().removeAll(rids); saveStorage(); } - updates().onUpdateReceived(new SeqUpdate(response.getSeq(),response.getState(), - UpdateMessageDelete.HEADER,new UpdateMessageDelete(apiPeer, rids).toByteArray())); - } - - @Override - public void onError(RpcException e) { - - } - }); + updates().onUpdateReceived(new SeqUpdate(r.getSeq(), r.getState(), + UpdateMessageDelete.HEADER, new UpdateMessageDelete(apiPeer, rids).toByteArray())); + }); + } } public void onDeleteMessage(Peer peer, List rids) { // Add to storage if (!deleteStorage.getPendingDeletions().containsKey(peer)) { - deleteStorage.getPendingDeletions().put(peer, new Delete(peer,new ArrayList())); + deleteStorage.getPendingDeletions().put(peer, new Delete(peer, new ArrayList())); } deleteStorage.getPendingDeletions().get(peer).getRids().addAll(rids); saveStorage(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index fb81defa14..9aee205140 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -497,10 +497,7 @@ public void onError(RpcException e) { ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), rid, message); - ArrayList receivers = new ArrayList<>(); - // receivers.add(myUid()); - receivers.add(peer.getPeerId()); - context().getEncryption().doSend(rid, content, receivers).then(r -> { + context().getEncryption().doSend(rid, content, peer.getPeerId()).then(r -> { self().send(new MessageSent(peer, rid)); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 2bfe6cf5ec..33d45561ed 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -8,6 +8,9 @@ import im.actor.core.api.ApiDialogGroup; import im.actor.core.api.ApiDialogShort; import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiEncryptedReceived; @@ -914,6 +917,28 @@ public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedCont } else { return onMessageRead(Peer.secret(senderId), encryptedRead.getReadDate()); } + } else if (update instanceof ApiEncryptedDeleteContent) { + ApiEncryptedDeleteContent delete = (ApiEncryptedDeleteContent) update; + int destId = senderId; + if (senderId == myUid()) { + destId = delete.getReceiverId(); + } + return onMessageDeleted(Peer.secret(destId), delete.getRid()); + } else if (update instanceof ApiEncryptedEditContent) { + ApiEncryptedEditContent editContent = (ApiEncryptedEditContent) update; + int destId = senderId; + if (senderId == myUid()) { + destId = editContent.getReceiverId(); + } + return onContentUpdate(Peer.secret(destId), editContent.getRid(), + AbsContent.fromMessage(editContent.getMessage())); + } else if (update instanceof ApiEncryptedDeleteAll) { + ApiEncryptedDeleteAll deleteAll = (ApiEncryptedDeleteAll) update; + int destId = senderId; + if (senderId == myUid()) { + destId = deleteAll.getReceiverId(); + } + return onChatClear(Peer.secret(destId)); } return Promise.success(null); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 784843c494..b43fdb9b22 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -41,11 +41,9 @@ import static im.actor.runtime.actors.ActorSystem.system; public class RouterInt extends ActorInterface implements BusSubscriber { - - private final ModuleContext context; - + public RouterInt(final ModuleContext context) { - this.context = context; + setDest(system().actorOf("actor/router", () -> new RouterActor(context))); context.getEvents().subscribe(this, PeerChatOpened.EVENT); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java index d91d615bc5..d28f3fd24c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java @@ -26,8 +26,10 @@ public static ActorRef get(final ModuleContext messenger) { private static final int TYPING_TEXT_TIMEOUT = 7000; - private HashMap typingsCancellables = new HashMap<>(); private HashSet typings = new HashSet<>(); + private HashMap typingsCancellables = new HashMap<>(); + private HashSet secretTypings = new HashSet<>(); + private HashMap secretTypingsCancellables = new HashMap<>(); private HashMap> groupTypings = new HashMap<>(); private HashMap> groupCancellables = new HashMap<>(); @@ -71,6 +73,42 @@ private void stopPrivateTyping(int uid) { } } + @Verified + private void privateSecretTyping(int uid, ApiTypingType type) { + // Support only text typings + if (type != ApiTypingType.TEXT) { + return; + } + + if (getUser(uid) == null) { + return; + } + + if (!secretTypings.contains(uid)) { + secretTypings.add(uid); + + context().getTypingModule().getSecretTyping(uid).getTyping().change(true); + } + + if (secretTypingsCancellables.containsKey(uid)) { + secretTypingsCancellables.remove(uid).cancel(); + } + secretTypingsCancellables.put(uid, schedule(new StopSecretTyping(uid), TYPING_TEXT_TIMEOUT)); + } + + @Verified + private void stopPrivateSecretTyping(int uid) { + if (secretTypings.contains(uid)) { + secretTypings.remove(uid); + + if (secretTypingsCancellables.containsKey(uid)) { + secretTypingsCancellables.remove(uid).cancel(); + } + + context().getTypingModule().getSecretTyping(uid).getTyping().change(false); + } + } + @Verified private void groupTyping(int gid, int uid, ApiTypingType type) { // Support only text typings @@ -151,12 +189,18 @@ public void onReceive(Object message) { if (message instanceof PrivateTyping) { PrivateTyping typing = (PrivateTyping) message; privateTyping(typing.getUid(), typing.getType()); + } else if (message instanceof PrivateSecretTyping) { + PrivateSecretTyping typing = (PrivateSecretTyping) message; + privateSecretTyping(typing.getUid(), typing.getType()); } else if (message instanceof GroupTyping) { GroupTyping typing = (GroupTyping) message; groupTyping(typing.getGid(), typing.getUid(), typing.getType()); } else if (message instanceof StopTyping) { StopTyping typing = (StopTyping) message; stopPrivateTyping(typing.getUid()); + } else if (message instanceof StopSecretTyping) { + StopSecretTyping secretTyping = (StopSecretTyping) message; + stopPrivateSecretTyping(secretTyping.getUid()); } else if (message instanceof StopGroupTyping) { StopGroupTyping typing = (StopGroupTyping) message; stopGroupTyping(typing.getGid(), typing.getUid()); @@ -194,6 +238,36 @@ public int hashCode() { } } + public static class StopSecretTyping { + + private int uid; + + public StopSecretTyping(int uid) { + this.uid = uid; + } + + public int getUid() { + return uid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StopSecretTyping that = (StopSecretTyping) o; + + if (uid != that.uid) return false; + + return true; + } + + @Override + public int hashCode() { + return uid; + } + } + public static class StopGroupTyping { private int gid; private int uid; @@ -233,6 +307,7 @@ public int hashCode() { } public static class PrivateTyping { + private int uid; private ApiTypingType type; @@ -270,6 +345,45 @@ public int hashCode() { } } + public static class PrivateSecretTyping { + + private int uid; + private ApiTypingType type; + + public PrivateSecretTyping(int uid, ApiTypingType type) { + this.uid = uid; + this.type = type; + } + + public int getUid() { + return uid; + } + + public ApiTypingType getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PrivateTyping that = (PrivateTyping) o; + + if (type != that.type) return false; + if (uid != that.uid) return false; + + return true; + } + + @Override + public int hashCode() { + int result = uid; + result = 31 * result + type.getValue(); + return result; + } + } + public static class GroupTyping { private int gid; private int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java index dabfdde2c8..1de205d992 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java @@ -19,8 +19,9 @@ public class TypingModule extends AbsModule { private ActorRef ownTypingActor; private ActorRef typingActor; - private HashMap uids = new HashMap<>(); - private HashMap groups = new HashMap<>(); + private final HashMap uids = new HashMap<>(); + private final HashMap sec_uids = new HashMap<>(); + private final HashMap groups = new HashMap<>(); public TypingModule(final ModuleContext context) { super(context); @@ -47,6 +48,15 @@ public UserTypingVM getTyping(int uid) { } } + public UserTypingVM getSecretTyping(int uid) { + synchronized (sec_uids) { + if (!sec_uids.containsKey(uid)) { + sec_uids.put(uid, new UserTypingVM(uid)); + } + return sec_uids.get(uid); + } + } + public void onTyping(Peer peer) { ownTypingActor.send(new OwnTypingActor.Typing(peer)); } From f704831b6166d4d05a22d30bd42da56ddd60dce3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 20:13:40 +0300 Subject: [PATCH 21/81] feat(android): Restyle DialogView --- .../main/java/im/actor/sdk/ActorStyle.java | 10 +++++++ .../controllers/dialogs/view/DialogView.java | 28 +++++++++++++++--- .../res/drawable-hdpi/ic_group_black_18dp.png | Bin 0 -> 319 bytes .../res/drawable-mdpi/ic_group_black_18dp.png | Bin 0 -> 194 bytes .../drawable-xhdpi/ic_group_black_18dp.png | Bin 0 -> 327 bytes .../drawable-xxhdpi/ic_group_black_18dp.png | Bin 0 -> 442 bytes .../drawable-xxxhdpi/ic_group_black_18dp.png | Bin 0 -> 568 bytes 7 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_group_black_18dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_group_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 6dd2be1b46..6c38382f85 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -358,6 +358,16 @@ public void setDialogsTitleColor(int dialogsTitleColor) { this.dialogsTitleColor = dialogsTitleColor; } + private int dialogsTitleSecureColor = 0xff559d44; + + public int getDialogsTitleSecureColor() { + return getColorWithFallback(dialogsTitleSecureColor, getDialogsTitleColor()); + } + + public void setDialogsTitleSecureColor(int dialogsTitleSecureColor) { + this.dialogsTitleSecureColor = dialogsTitleSecureColor; + } + private int dialogsTextColor = 0; public int getDialogsTextColor() { diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index 9835e64a4e..7c300bda0d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -53,6 +53,7 @@ public class DialogView extends ListItemBackgroundVieweH;sPQAkvH&~=c@v?AB+K}rafEKjTcltlMh(%x70ob^Ax;F;yu*@+Nb(^l zxcu=%I!sL;$Jw_ zv?5z}jRMQABAN3Dhnic+mOY@rvIj(%^I*U%3jyK;GI0nI=7fwk4MqV$00310%ILvl R%Z2~|002ovPDHLkV1i~fc*Ot! literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..735614edd98130a7dbf558bfa4048385db2f585b GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@RhePEQxdkcwMxFC64`FpywTWZ5V!P6{|Xa6+sc#Wm6`FEjJ`eh7MkI}pkB+xeMaCm&q~3S zy-VUf>~HdL9=_mHm1unAYlG0eU!PA-ob1oHpoy<)ZL3gC?J|Qe;YBx2$lqBzp^^Qt sbN$oYmbjoyp1*>bo=eTX)W@W<2l9Fp_kDcv3+N~YPgg&ebxsLQ0LgAk`Tzg` literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..792d67d93ee1b684b36a290922aabf8c9d3631f4 GIT binary patch literal 327 zcmV-N0l5B&P)0{Df+P3|7Ob%27?zxcJ4o!tJ|RPsH^lQiwy zuW!^!m0iAO>B430ykw2O$C5Yj!QzE}q z>j)*nT*?2K zOB(Jp@=r8geAJ@q?5dh?Zd)_&u{BV}b>o|x(ad|?3>LZtX6xo7-K4AT?D!XA7=~fu Z$O95TAr;jtr0DvlZH81gmq}aTetd+`g7^vjaDU&)h>JbKN z_;f5%hlVw%i`_&R=sJN8jY5Qh@(FZkv>v_pA%>}#S3|cu^cQp`V3Px#07*qoM6N<$g4(UZSO5S3 literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_group_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_group_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ba45085a3c080fd4377065f407a037ed4c48827c GIT binary patch literal 568 zcmV-80>}M{P)L|=s+4E&0K7q;LL{8HpnU@l5*JY^>6tjt_6g9Ih*qP% z+3q-C+uiMaJG1itCBI|;`=2k;N}k|nZ1*2yB7AybCu zvocK<$W|Ax$Vmh9SxJzu9?DB=^sL#d1;lLFd(1(H>VJwy!1kIK09kYl$XXe z=ChMkuz-Dy`RtSwEZ|mSK06Hs3#e+$XQ$4*fO^yiR8+8l8;$wwWE3o5Ph&niDFq8y z)R-R%*FBU!Hz1##De@&i`3gC1Kz=C1eJ(=0S>_GO4{ayqJ(SM{JVbKmhsFwdNIsD7 zj`!pNnT$X_00000z8NA{$uxOHUXo?2{m(+vvF2QU<(yY^+WE|2z-O*InJuRLp6GWT zzDwRII82I+9p_-$=c9+XuYP09_syKuzTd4V7)qz}lX%9g$4*I~w~l`EJ(tKc4#iFJ zRZV#4x5=FE${2Z~VK6^ECP%w;J!Q`SbJsi>FdXS4xzwSr`1kdbG5^;sUc;c_NW}&I za);h$%=tDWG1HynXAPndq4hR4M000000000W;(P%pv!=h5MlBxz0000 Date: Mon, 11 Jul 2016 23:37:38 +0300 Subject: [PATCH 22/81] feat(core): Restoring Key Manager --- .../java/im/actor/core/modules/Modules.java | 8 +- .../encryption/EncryptedPeerActor.java | 11 +- .../encryption/EncryptedProcessor.java | 32 +- .../encryption/EncryptedSessionActor.java | 24 +- .../modules/encryption/EncryptionModule.java | 54 +-- .../modules/encryption/KeyManagerActor.java | 336 +++++++----------- .../modules/encryption/KeyManagerInt.java | 118 ++++-- .../encryption/SessionManagerActor.java | 8 +- .../encryption/entity/OwnIdentity.java | 23 ++ 9 files changed, 312 insertions(+), 302 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java index 618cefdb2f..2ff44c7916 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/Modules.java @@ -163,8 +163,8 @@ public void onLoggedIn(boolean first) { profile = new ProfileModule(this); timing.section("Mentions"); mentions = new MentionsModule(this); -// timing.section("Encryption"); -// encryptionModule = new EncryptionModule(this); + timing.section("Encryption"); + encryptionModule = new EncryptionModule(this); timing.section("DisplayLists"); displayLists = new DisplayLists(this); timing.section("DeviceInfo"); @@ -190,8 +190,8 @@ public void onLoggedIn(boolean first) { notifications.run(); timing.section("AppState"); appStateModule.run(); -// timing.section("Encryption"); -// encryptionModule.run(); + timing.section("Encryption"); + encryptionModule.run(); timing.section("Contacts"); contacts.run(); timing.section("Messages"); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java index 25d395421b..079daff534 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java @@ -10,6 +10,7 @@ import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.core.modules.encryption.entity.UserKeysGroup; import im.actor.core.modules.ModuleActor; @@ -64,14 +65,14 @@ public EncryptedPeerActor(int uid, ModuleContext context) { public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); Promises.tuple( keyManager.getOwnIdentity(), keyManager.getUserKeyGroups(uid)) - .then(new Consumer>() { + .then(new Consumer>() { @Override - public void apply(Tuple2 res) { + public void apply(Tuple2 res) { Log.d(TAG, "then"); ownKeyGroupId = res.getT1().getKeyGroup(); theirKeys = res.getT2(); @@ -118,7 +119,7 @@ public Promise apply(final UserKeysGroup keysGroup) { if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); } - return context().getEncryption().getSessionManagerInt() + return context().getEncryption().getSessionManager() .pickSession(uid, keysGroup.getKeyGroupId()) .failure(new Consumer() { @Override @@ -204,7 +205,7 @@ public Promise> apply(final EncryptedBoxKe } } } - return context().getEncryption().getSessionManagerInt() + return context().getEncryption().getSessionManager() .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) .map(new Function>() { @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java index b416b7ace4..b510988930 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java @@ -1,5 +1,8 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.updates.UpdateEncryptedPackage; +import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; +import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; @@ -15,21 +18,20 @@ public EncryptedProcessor(ModuleContext context) { @Override public Promise process(Update update) { -// if (update instanceof UpdatePublicKeyGroupAdded) { -// context().getEncryption().getKeyManager().send(new KeyManagerActor.PublicKeysGroupAdded( -// ((UpdatePublicKeyGroupAdded) update).getUid(), -// ((UpdatePublicKeyGroupAdded) update).getKeyGroup() -// )); -// return true; -// } else if (update instanceof UpdatePublicKeyGroupRemoved) { -// context().getEncryption().getKeyManager().send(new KeyManagerActor.PublicKeysGroupRemoved( -// ((UpdatePublicKeyGroupRemoved) update).getUid(), -// ((UpdatePublicKeyGroupRemoved) update).getKeyGroupId() -// )); -// return true; -// } else if (update instanceof UpdateEncryptedPackage) { -// -// } + if (update instanceof UpdatePublicKeyGroupAdded) { + UpdatePublicKeyGroupAdded groupAdded = (UpdatePublicKeyGroupAdded) update; + return context().getEncryption() + .getKeyManager() + .onKeyGroupAdded(groupAdded.getUid(), groupAdded.getKeyGroup()); + } else if (update instanceof UpdatePublicKeyGroupRemoved) { + UpdatePublicKeyGroupRemoved groupRemoved = (UpdatePublicKeyGroupRemoved) update; + return context().getEncryption() + .getKeyManager() + .onKeyGroupRemoved(groupRemoved.getUid(), groupRemoved.getKeyGroupId()); + } else if (update instanceof UpdateEncryptedPackage) { + // TODO: Implement + return Promise.success(null); + } return null; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java index f7fbd5094c..210d7caf1e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java @@ -4,6 +4,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; @@ -11,6 +12,7 @@ import im.actor.runtime.actors.ask.AskResult; import im.actor.runtime.function.Consumer; import im.actor.runtime.function.Function; +import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -26,7 +28,7 @@ * 3) Own Pre Key Id * 4) Their Key Group Id * 5) Their Pre Key Id - *

+ *

* During actor starting it downloads all required key from Key Manager. * To encrypt/decrypt messages this actor spawns encryption chains. */ @@ -76,7 +78,7 @@ public EncryptedSessionActor(ModuleContext context, PeerSession session) { @Override public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); } private Promise onEncrypt(final byte[] data) { @@ -88,19 +90,11 @@ private Promise onEncrypt(final byte[] data) { // return success(latestTheirEphemeralKey) - .mapIfNullPromise(keyManager.supplyUserPreKey(uid, session.getTheirKeyGroupId())) - .map(new Function() { - @Override - public EncryptedSessionChain apply(byte[] publicKey) { - return pickEncryptChain(publicKey); - } - }) - .map(new Function() { - @Override - public EncryptedPackageRes apply(EncryptedSessionChain encryptedSessionChain) { - return encrypt(encryptedSessionChain, data); - } - }); + .mapIfNullPromise(() -> + keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) + .map(PublicKey::getPublicKey)) + .map(publicKey -> pickEncryptChain(publicKey)) + .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } private Promise onDecrypt(final byte[] data) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 6598e2a20b..75b01f2a20 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -16,66 +16,48 @@ public class EncryptionModule extends AbsModule { - private ActorRef keyManager; private KeyManagerInt keyManagerInt; - private ActorRef sessionManager; private SessionManagerInt sessionManagerInt; private ActorRef messageEncryptor; - private HashMap encryptedStates = new HashMap(); + private final HashMap encryptedStates = new HashMap<>(); public EncryptionModule(ModuleContext context) { super(context); } public void run() { - keyManager = system().actorOf(Props.create(new ActorCreator() { - @Override - public KeyManagerActor create() { - return new KeyManagerActor(context()); - } - }), "encryption/keys"); - keyManagerInt = new KeyManagerInt(keyManager); - sessionManager = system().actorOf(Props.create(new ActorCreator() { - @Override - public SessionManagerActor create() { - return new SessionManagerActor(context()); - } - }), "encryption/sessions"); + + keyManagerInt = new KeyManagerInt(context()); + + // Session Manager + ActorRef sessionManager = system().actorOf("encryption/sessions", + () -> new SessionManagerActor(context())); sessionManagerInt = new SessionManagerInt(sessionManager); - messageEncryptor = system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedMsgActor create() { - return new EncryptedMsgActor(context()); - } - }), "encryption/messaging"); + + + messageEncryptor = system().actorOf("encryption/messaging", + () -> new EncryptedMsgActor(context())); } - public SessionManagerInt getSessionManagerInt() { + public SessionManagerInt getSessionManager() { return sessionManagerInt; } - public ActorRef getMessageEncryptor() { - return messageEncryptor; + public KeyManagerInt getKeyManager() { + return keyManagerInt; } - public ActorRef getKeyManager() { - return keyManager; - } - public KeyManagerInt getKeyManagerInt() { - return keyManagerInt; + public ActorRef getMessageEncryptor() { + return messageEncryptor; } public ActorRef getEncryptedChatManager(final int uid) { synchronized (encryptedStates) { if (!encryptedStates.containsKey(uid)) { - encryptedStates.put(uid, system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedPeerActor create() { - return new EncryptedPeerActor(uid, context()); - } - }), "encryption/uid_" + uid)); + encryptedStates.put(uid, system().actorOf("encryption/uid_" + uid, + () -> new EncryptedPeerActor(uid, context()))); } return encryptedStates.get(uid); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java index 3a0a3f97a5..13ca85f3cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java @@ -13,12 +13,9 @@ import im.actor.core.api.rpc.RequestLoadPublicKey; import im.actor.core.api.rpc.RequestLoadPublicKeyGroups; import im.actor.core.api.rpc.RequestUploadPreKey; -import im.actor.core.api.rpc.ResponseCreateNewKeyGroup; -import im.actor.core.api.rpc.ResponsePublicKeyGroups; -import im.actor.core.api.rpc.ResponsePublicKeys; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKeyStorage; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.UserKeys; @@ -29,16 +26,13 @@ import im.actor.runtime.Crypto; import im.actor.runtime.Log; import im.actor.runtime.Storage; -import im.actor.runtime.actors.ask.AskIntRequest; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.collections.ManagedList; import im.actor.runtime.crypto.Curve25519KeyPair; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.ratchet.RatchetKeySignature; -import im.actor.runtime.function.Consumer; import im.actor.runtime.promise.PromisesArray; import im.actor.runtime.function.Tuple2; import im.actor.runtime.storage.KeyValueStorage; @@ -53,7 +47,7 @@ public class KeyManagerActor extends ModuleActor { private static final String TAG = "KeyManagerActor"; private KeyValueStorage encryptionKeysStorage; - private HashMap cachedUserKeys = new HashMap(); + private HashMap cachedUserKeys = new HashMap<>(); private PrivateKeyStorage ownKeys; private boolean isReady = false; @@ -121,21 +115,15 @@ public void preStart() { .map(PrivateKey.SIGN(ownKeys.getIdentityKey())); Log.d(TAG, "Creation of new key group"); - api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(new Consumer() { - @Override - public void apply(ResponseCreateNewKeyGroup response) { - ownKeys = ownKeys.setGroupId(response.getKeyGroupId()); - encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); - onMainKeysReady(); - } - }).failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Keys upload error"); - Log.e(TAG, e); - - // Just ignore - } + api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(response -> { + ownKeys = ownKeys.setGroupId(response.getKeyGroupId()); + encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); + onMainKeysReady(); + }).failure(e -> { + Log.w(TAG, "Keys upload error"); + Log.e(TAG, e); + + // Just ignore }); } else { onMainKeysReady(); @@ -173,22 +161,16 @@ private void onMainKeysReady() { pendingEphermalKeys.map(PrivateKey.SIGN(ownKeys.getIdentityKey())); api(new RequestUploadPreKey(ownKeys.getKeyGroupId(), uploadingKeys, uploadingSignatures)) - .then(new Consumer() { - @Override - public void apply(ResponseVoid responseVoid) { - ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()])); - encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); - onAllKeysReady(); - } + .then(responseVoid -> { + ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()])); + encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); + onAllKeysReady(); }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Ephemeral keys upload error"); - Log.e(TAG, e); + .failure(e -> { + Log.w(TAG, "Ephemeral keys upload error"); + Log.e(TAG, e); - // Ignore. This will freeze all encryption operations. - } + // Ignore. This will freeze all encryption operations. }); } else { onAllKeysReady(); @@ -282,26 +264,20 @@ private Promise fetchUserGroups(final int uid) { } return api(new RequestLoadPublicKeyGroups(new ApiUserOutPeer(uid, user.getAccessHash()))) - .map(new Function>() { - @Override - public ArrayList apply(ResponsePublicKeyGroups response) { - ArrayList keysGroups = new ArrayList<>(); - for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) { - UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); - if (validatedKeysGroup != null) { - keysGroups.add(validatedKeysGroup); - } + .map(response -> { + ArrayList keysGroups = new ArrayList<>(); + for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) { + UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); + if (validatedKeysGroup != null) { + keysGroups.add(validatedKeysGroup); } - return keysGroups; } + return keysGroups; }) - .map(new Function, UserKeys>() { - @Override - public UserKeys apply(ArrayList userKeysGroups) { - UserKeys userKeys = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()])); - cacheUserKeys(userKeys); - return userKeys; - } + .map(userKeysGroups -> { + UserKeys userKeys1 = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()])); + cacheUserKeys(userKeys1); + return userKeys1; }); } @@ -320,65 +296,62 @@ private Promise fetchUserPreKey(final int uid, final int keyGroupId, } return pickUserGroup(uid, keyGroupId) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(final Tuple2 keysGroup) { - - // - // Searching in cache - // - - for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) { - if (p.getKeyId() == keyId) { - return Promise.success(p); - } + .flatMap(keysGroup -> { + + // + // Searching in cache + // + + for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) { + if (p.getKeyId() == keyId) { + return Promise.success(p); } + } - // - // Downloading pre key - // - - ArrayList ids = new ArrayList(); - ids.add(keyId); - final UserKeysGroup finalKeysGroup = keysGroup.getT1(); - - return api(new RequestLoadPublicKey(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId, ids)) - .map(new Function() { - @Override - public PublicKey apply(ResponsePublicKeys responsePublicKeys) { - if (responsePublicKeys.getPublicKey().size() == 0) { - throw new RuntimeException("Unable to find public key on server"); - } - ApiEncryptionKeySignature sig = null; - for (ApiEncryptionKeySignature s : responsePublicKeys.getSignatures()) { - if (s.getKeyId() == keyId && "Ed25519".equals(s.getSignatureAlg())) { - sig = s; - break; - } - } - if (sig == null) { - throw new RuntimeException("Unable to find public key on server"); - } - - ApiEncryptionKey key = responsePublicKeys.getPublicKey().get(0); - - byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), - key.getKeyMaterial()); - - if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), - keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); - } - - PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); - UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey); - cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId()) - .addUserKeyGroup(userKeysGroup)); - - return pkey; + // + // Downloading pre key + // + + ArrayList ids = new ArrayList<>(); + ids.add(keyId); + final UserKeysGroup finalKeysGroup = keysGroup.getT1(); + RequestLoadPublicKey request = new RequestLoadPublicKey( + new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), + keyGroupId, ids); + + return api(request) + .map(responsePublicKeys -> { + if (responsePublicKeys.getPublicKey().size() == 0) { + throw new RuntimeException("Unable to find public key on server"); + } + ApiEncryptionKeySignature sig = null; + for (ApiEncryptionKeySignature s : responsePublicKeys.getSignatures()) { + if (s.getKeyId() == keyId && "Ed25519".equals(s.getSignatureAlg())) { + sig = s; + break; } - }); - } + } + if (sig == null) { + throw new RuntimeException("Unable to find public key on server"); + } + + ApiEncryptionKey key = responsePublicKeys.getPublicKey().get(0); + + byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), + key.getKeyMaterial()); + + if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), + keyHash, sig.getSignature())) { + throw new RuntimeException("Key signature does not match"); + } + + PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); + UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey); + cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId()) + .addUserKeyGroup(userKeysGroup)); + + return pkey; + }); }); } @@ -390,40 +363,34 @@ public PublicKey apply(ResponsePublicKeys responsePublicKeys) { */ private Promise fetchUserPreKey(final int uid, final int keyGroupId) { return pickUserGroup(uid, keyGroupId) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(final Tuple2 keyGroups) { - return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId)) - .map(new Function() { - @Override - public PublicKey apply(ResponsePublicKeys response) { - if (response.getPublicKey().size() == 0) { - throw new RuntimeException("User doesn't have pre keys"); - } - ApiEncryptionKey key = response.getPublicKey().get(0); - ApiEncryptionKeySignature sig = null; - for (ApiEncryptionKeySignature s : response.getSignatures()) { - if (s.getKeyId() == key.getKeyId() && "Ed25519".equals(s.getSignatureAlg())) { - sig = s; - break; - } - } - if (sig == null) { - throw new RuntimeException("Unable to find public key on server"); - } - - byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), - key.getKeyMaterial()); - - if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), - keyHash, sig.getSignature())) { - throw new RuntimeException("Key signature does not match"); - } - - return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); + .flatMap(keyGroups -> { + return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId)) + .map(response -> { + if (response.getPublicKey().size() == 0) { + throw new RuntimeException("User doesn't have pre keys"); + } + ApiEncryptionKey key = response.getPublicKey().get(0); + ApiEncryptionKeySignature sig = null; + for (ApiEncryptionKeySignature s : response.getSignatures()) { + if (s.getKeyId() == key.getKeyId() && "Ed25519".equals(s.getSignatureAlg())) { + sig = s; + break; } - }); - } + } + if (sig == null) { + throw new RuntimeException("Unable to find public key on server"); + } + + byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), + key.getKeyMaterial()); + + if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), + keyHash, sig.getSignature())) { + throw new RuntimeException("Key signature does not match"); + } + + return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); + }); }); } @@ -437,10 +404,10 @@ public PublicKey apply(ResponsePublicKeys response) { * @param uid User's id * @param keyGroup Added key group */ - private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { + private Promise onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { - return; + return Promise.success(null); } UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); if (validatedKeysGroup != null) { @@ -449,6 +416,7 @@ private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); } + return Promise.success(null); } /** @@ -457,16 +425,17 @@ private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { * @param uid User's id * @param keyGroupId Removed key group id */ - private void onPublicKeysGroupRemoved(int uid, int keyGroupId) { + private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { - return; + return Promise.success(null); } UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId); cacheUserKeys(updatedUserKeys); context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + return Promise.success(null); } // @@ -485,7 +454,7 @@ private UserKeysGroup validateUserKeysGroup(int uid, ApiEncryptionKeyGroup keyGr keyGroup.getIdentityKey().getKeyAlg(), keyGroup.getIdentityKey().getKeyMaterial()); - ArrayList keys = new ArrayList(); + ArrayList keys = new ArrayList<>(); key_loop: for (ApiEncryptionKey key : keyGroup.getKeys()) { @@ -536,16 +505,15 @@ private Promise> pickUserGroup(int uid, final in return fetchUserGroups(uid) .map(userKeys -> { UserKeysGroup keysGroup = null; -// for (UserKeysGroup g : userKeys.getUserKeysGroups()) { -// if (g.getKeyGroupId() == keyGroupId) { -// keysGroup = g; -// } -// } -// if (keysGroup == null) { -// throw new RuntimeException("Key Group #" + keyGroupId + " not found"); -// } -// return new Tuple2<>(keysGroup, userKeys); - return null; + for (UserKeysGroup g : userKeys.getUserKeysGroups()) { + if (g.getKeyGroupId() == keyGroupId) { + keysGroup = g; + } + } + if (keysGroup == null) { + throw new RuntimeException("Key Group #" + keyGroupId + " not found"); + } + return new Tuple2<>(keysGroup, userKeys); }); } @@ -574,27 +542,12 @@ private void cacheUserKeys(UserKeys userKeys) { // @Override - public void onReceive(Object message) { - if (!isReady - && (message instanceof AskIntRequest - || message instanceof PublicKeysGroupAdded - || message instanceof PublicKeysGroupRemoved)) { + public Promise onAsk(Object message) throws Exception { + if (!isReady) { stash(); - return; - } - if (message instanceof PublicKeysGroupAdded) { - PublicKeysGroupAdded publicKeysGroupAdded = (PublicKeysGroupAdded) message; - onPublicKeysGroupAdded(publicKeysGroupAdded.getUid(), publicKeysGroupAdded.getPublicKeyGroup()); - } else if (message instanceof PublicKeysGroupRemoved) { - PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; - onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); - } else { - super.onReceive(message); + return null; } - } - @Override - public Promise onAsk(Object message) throws Exception { if (message instanceof FetchOwnKey) { return fetchOwnIdentity(); } else if (message instanceof FetchOwnPreKeyByPublic) { @@ -609,6 +562,12 @@ public Promise onAsk(Object message) throws Exception { return fetchUserPreKey(((FetchUserPreKeyRandom) message).getUid(), ((FetchUserPreKeyRandom) message).getKeyGroup()); } else if (message instanceof FetchOwnRandomPreKey) { return fetchPreKey(); + } else if (message instanceof PublicKeysGroupAdded) { + PublicKeysGroupAdded publicKeysGroupAdded = (PublicKeysGroupAdded) message; + return onPublicKeysGroupAdded(publicKeysGroupAdded.getUid(), publicKeysGroupAdded.getPublicKeyGroup()); + } else if (message instanceof PublicKeysGroupRemoved) { + PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; + return onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); } else { return super.onAsk(message); } @@ -622,25 +581,6 @@ public static class FetchOwnKey implements AskMessage { } - public static class OwnIdentity extends AskResult { - - private int keyGroup; - private PrivateKey identityKey; - - public OwnIdentity(int keyGroup, PrivateKey identityKey) { - this.keyGroup = keyGroup; - this.identityKey = identityKey; - } - - public int getKeyGroup() { - return keyGroup; - } - - public PrivateKey getIdentityKey() { - return identityKey; - } - } - public static class FetchOwnRandomPreKey implements AskMessage { } @@ -735,7 +675,7 @@ public int getKeyGroup() { // Updates handling // - public static class PublicKeysGroupAdded { + public static class PublicKeysGroupAdded implements AskMessage { private int uid; private ApiEncryptionKeyGroup publicKeyGroup; @@ -754,7 +694,7 @@ public ApiEncryptionKeyGroup getPublicKeyGroup() { } } - public static class PublicKeysGroupRemoved { + public static class PublicKeysGroupRemoved implements AskMessage { private int uid; private int keyGroupId; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java index 351019a02e..d109f37df3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java @@ -1,58 +1,126 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.ApiEncryptionKeyGroup; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Supplier; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; -import im.actor.runtime.promise.Promises; +import static im.actor.runtime.actors.ActorSystem.system; + +/** + * Encryption Key Manager. Used for loading user's keys for encryption/decryption. + */ public class KeyManagerInt extends ActorInterface { - public KeyManagerInt(ActorRef dest) { - super(dest); + /** + * Default Constructor + * + * @param context actor context + */ + public KeyManagerInt(ModuleContext context) { + super(system().actorOf("encryption/keys", () -> new KeyManagerActor(context))); } - public Promise getOwnIdentity() { + + // + // Identity + // + + /** + * Loading Own Identity Key + * + * @return promise of keys + */ + public Promise getOwnIdentity() { return ask(new KeyManagerActor.FetchOwnKey()); } + /** + * Loading user key groups by uid + * + * @param uid user's id + * @return promise of key groups + */ public Promise getUserKeyGroups(int uid) { return ask(new KeyManagerActor.FetchUserKeys(uid)); } - public Promise getUserRandomPreKey(int uid, int keyGroupId) { - return ask(new KeyManagerActor.FetchUserPreKeyRandom(uid, keyGroupId)); - } - public Promise getUserPreKey(int uid, int keyGroupId, long preKeyId) { - return ask(new KeyManagerActor.FetchUserPreKey(uid, keyGroupId, preKeyId)); - } + // + // Pre Keys + // + /** + * Load own random pre key + * + * @return promise of private key + */ public Promise getOwnRandomPreKey() { return ask(new KeyManagerActor.FetchOwnRandomPreKey()); } + /** + * Load own pre key by key id + * + * @param id key id + * @return promise of private key + */ public Promise getOwnPreKey(long id) { return ask(new KeyManagerActor.FetchOwnPreKeyById(id)); } + /** + * Loading random user's pre key from key group + * + * @param uid user's id + * @param keyGroupId key group id + * @return promise of public key + */ + public Promise getUserRandomPreKey(int uid, int keyGroupId) { + return ask(new KeyManagerActor.FetchUserPreKeyRandom(uid, keyGroupId)); + } + + /** + * Loading user's pre key by pre key id + * + * @param uid user's id + * @param keyGroupId key group id + * @param preKeyId pre key id + * @return promise of public key + */ + public Promise getUserPreKey(int uid, int keyGroupId, long preKeyId) { + return ask(new KeyManagerActor.FetchUserPreKey(uid, keyGroupId, preKeyId)); + } + + // + // Updates + // + + /** + * Call this when update about new key group added received + * + * @param uid user's id + * @param keyGroup added key group + * @return promise of void + */ + public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { + return ask(new KeyManagerActor.PublicKeysGroupAdded(uid, keyGroup)); + } - public Supplier> supplyUserPreKey(final int uid, final int keyGroupId) { - return new Supplier>() { - @Override - public Promise get() { - return getUserRandomPreKey(uid, keyGroupId) - .map(new Function() { - @Override - public byte[] apply(PublicKey publicKey) { - return publicKey.getPublicKey(); - } - }); - } - }; + /** + * Call this when update about key group removing received + * + * @param uid user's id + * @param gid removed key group id + * @return promise of void + */ + public Promise onKeyGroupRemoved(int uid, int gid) { + return ask(new KeyManagerActor.PublicKeysGroupRemoved(uid, gid)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java index 51c158e89d..e17332f0f5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java @@ -6,6 +6,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; @@ -21,7 +22,6 @@ import im.actor.runtime.crypto.ratchet.RatchetPublicKey; import im.actor.runtime.function.Function; import im.actor.runtime.function.FunctionTupled4; -import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; @@ -44,7 +44,7 @@ public SessionManagerActor(ModuleContext context) { @Override public void preStart() { super.preStart(); - keyManager = context().getEncryption().getKeyManagerInt(); + keyManager = context().getEncryption().getKeyManager(); peerSessions = new BaseKeyValueEngine(Storage.createKeyValue("encryption_sessions")) { @Override @@ -137,9 +137,9 @@ public Promise apply(Exception e) { keyManager.getOwnPreKey(ownKeyId), keyManager.getUserKeyGroups(uid), keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { + .map(new FunctionTupled4() { @Override - public PeerSession apply(KeyManagerActor.OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { + public PeerSession apply(OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java new file mode 100644 index 0000000000..d05e90fb04 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java @@ -0,0 +1,23 @@ +package im.actor.core.modules.encryption.entity; + +import im.actor.core.modules.encryption.entity.PrivateKey; +import im.actor.runtime.actors.ask.AskResult; + +public class OwnIdentity extends AskResult { + + private int keyGroup; + private PrivateKey identityKey; + + public OwnIdentity(int keyGroup, PrivateKey identityKey) { + this.keyGroup = keyGroup; + this.identityKey = identityKey; + } + + public int getKeyGroup() { + return keyGroup; + } + + public PrivateKey getIdentityKey() { + return identityKey; + } +} From a11244930f89d1bf5dd3abdbf6b11ddf0ac75e16 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 04:48:49 +0300 Subject: [PATCH 23/81] fix(android): Fixing empty wallpaper --- .../controllers/conversation/messages/MessagesFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 2e49070e4c..424a08c6fa 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -122,7 +122,8 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } else { background = getResources().getDrawable(backgrounds[0]); } - ((ImageView) res.findViewById(R.id.chatBackgroundView)).setImageDrawable(background); + ImageView backgroundView = (ImageView) res.findViewById(R.id.chatBackgroundView); + backgroundView.setImageDrawable(background); // From f3c9ad6089f8026f5d4e533c1870f526fa1b67c8 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 05:09:03 +0300 Subject: [PATCH 24/81] feat(android+core): Secret Chat displaying --- .../im/actor/sdk/controllers/Intents.java | 4 + .../messages/MessagesFragment.java | 19 ++- .../toolbar/ChatToolbarFragment.java | 27 +++- .../controllers/profile/ProfileFragment.java | 18 +++ .../src/main/res/layout/fragment_profile.xml | 27 ++++ .../src/main/res/values/ui_text.xml | 1 + .../main/java/im/actor/core/entity/Peer.java | 16 ++- .../core/entity/encryption/PeerSession.java | 26 +--- .../im/actor/core/modules/ModuleActor.java | 3 +- .../encryption/EncryptedSessionActor.java | 62 +++------ .../modules/encryption/EncryptionModule.java | 20 ++- .../modules/encryption/KeyManagerInt.java | 10 ++ .../encryption/SessionManagerActor.java | 129 ++++++++---------- .../modules/encryption/SessionManagerInt.java | 26 +++- .../encryption/entity/EncryptedBoxKey.java | 7 +- .../history/ConversationHistoryActor.java | 14 +- 16 files changed, 233 insertions(+), 176 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java index 00453de81e..c81f8ae420 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/Intents.java @@ -122,6 +122,10 @@ public static Intent openPrivateDialog(int uid, boolean compose, Context context return openDialog(Peer.user(uid), compose, context); } + public static Intent openPrivateSecretDialog(int uid, boolean compose, Context context) { + return openDialog(Peer.secret(uid), compose, context); + } + public static Intent openGroupDialog(int chatId, boolean compose, Context context) { return openDialog(Peer.group(chatId), compose, context); } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java index 424a08c6fa..6d0590e65b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesFragment.java @@ -17,6 +17,7 @@ import fr.castorflex.android.circularprogressbar.CircularProgressBar; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.viewmodel.ConversationVM; import im.actor.runtime.mvvm.Value; import im.actor.runtime.mvvm.ValueChangedListener; @@ -275,13 +276,17 @@ public void onResume() { } // Bind Progress - bind(conversationVM.getIsLoaded(), conversationVM.getIsEmpty(), (isLoaded, valueModel, isEmpty, valueModel2) -> { - if (isEmpty && !isLoaded) { - showView(progressView); - } else { - hideView(progressView); - } - }); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + bind(conversationVM.getIsLoaded(), conversationVM.getIsEmpty(), (isLoaded, valueModel, isEmpty, valueModel2) -> { + if (isEmpty && !isLoaded) { + showView(progressView); + } else { + hideView(progressView); + } + }); + } else { + hideView(progressView); + } } @Override diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 83398ef06b..b5aa7cc0e8 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -6,6 +6,7 @@ import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; @@ -101,6 +102,13 @@ public void onConfigureActionBar(ActionBar actionBar) { actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayShowCustomEnabled(true); + // Coloring Toolbar + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getAccentColor())); + } else { + actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getToolBarColor())); + } + // Loading Toolbar header views ActorStyle style = ActorSDK.sharedActor().style; barView = LayoutInflater.from(getActivity()).inflate(R.layout.bar_conversation, null); @@ -132,7 +140,7 @@ public void onConfigureActionBar(ActionBar actionBar) { barAvatar.init(Screen.dp(32), 18); barView.findViewById(R.id.titleContainer).setOnClickListener(v -> { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { ActorSDKLauncher.startProfileActivity(getActivity(), peer.getPeerId()); } else if (peer.getPeerType() == PeerType.GROUP) { ActorSDK.sharedActor().startGroupInfoActivity(getActivity(), peer.getPeerId()); @@ -148,7 +156,7 @@ public void onResume() { // Performing all required Data Binding here - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { // Loading user UserVM user = users().get(peer.getPeerId()); @@ -171,7 +179,11 @@ public void onResume() { bind(barSubtitle, user); // Binding User typing to Toolbar - bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); + } else { + // TODO: Implement + } // Refresh menu on contact state change bind(user.isContact(), (val, valueModel) -> { @@ -206,6 +218,13 @@ public void onResume() { bindGroupTyping(barTyping, barTypingContainer, barSubtitle, messenger().getGroupTyping(group.getId())); } } + + // Show/Hide Avatar + if (!style.isShowAvatarInTitle() || + ((peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) + && !style.isShowAvatarPrivateInTitle())) { + barAvatar.setVisibility(View.GONE); + } // Global Counter bind(messenger().getGlobalState().getGlobalCounter(), (val, valueModel) -> { @@ -367,7 +386,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in private void startCall(boolean video) { Command cmd; - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { cmd = video ? messenger().doVideoCall(peer.getPeerId()) : messenger().doCall(peer.getPeerId()); } else { cmd = messenger().doGroupCall(peer.getPeerId()); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java index 43ff205c73..322f888c69 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/profile/ProfileFragment.java @@ -176,6 +176,24 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun }); + // + // New Encrypted Message + // + + View newEncryptedMessageView = res.findViewById(R.id.newEncryptedMessage); + ImageView newEncryptedMessageIcon = (ImageView) newEncryptedMessageView.findViewById(R.id.newEncryptedMessageIcon); + TextView newEncryptedMessageTitle = (TextView) newEncryptedMessageView.findViewById(R.id.newEncryptedMessageText); + { + Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_chat_black_24dp)); + DrawableCompat.setTint(drawable, style.getListActionColor()); + newEncryptedMessageIcon.setImageDrawable(drawable); + newEncryptedMessageTitle.setTextColor(style.getListActionColor()); + } + newEncryptedMessageView.setOnClickListener(v -> { + startActivity(Intents.openPrivateSecretDialog(user.getId(), true, getActivity())); + }); + + // // Voice Call // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml index 1a4dbfb975..b1252ab61e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/layout/fragment_profile.xml @@ -113,6 +113,33 @@ android:textColor="@color/action" /> + + + + + + + Add to Contacts Voice Call New Message + Secret Chat Mobile phone diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java index ee160ced49..0a52a7b948 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java @@ -16,12 +16,7 @@ public class Peer extends BserObject { - public static final BserCreator CREATOR = new BserCreator() { - @Override - public Peer createInstance() { - return new Peer(); - } - }; + public static final BserCreator CREATOR = Peer::new; public static Peer fromBytes(byte[] data) throws IOException { return Bser.parse(new Peer(), data); @@ -37,6 +32,8 @@ public static Peer fromUniqueId(long uid) { return new Peer(PeerType.PRIVATE, id); case 1: return new Peer(PeerType.GROUP, id); + case 2: + return new Peer(PeerType.PRIVATE_ENCRYPTED, id); } } @@ -48,6 +45,10 @@ public static Peer group(int gid) { return new Peer(PeerType.GROUP, gid); } + public static Peer secret(int uid) { + return new Peer(PeerType.PRIVATE_ENCRYPTED, uid); + } + @Property("readonly, nonatomic") private PeerType peerType; @Property("readonly, nonatomic") @@ -72,6 +73,9 @@ public long getUnuqueId() { case GROUP: type = 1; break; + case PRIVATE_ENCRYPTED: + type = 2; + break; } return ((long) peerId & 0xFFFFFFFFL) + (((long) type & 0xFFFFFFFFL) << 32); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java index 79291bee86..8b8d0cb606 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/encryption/PeerSession.java @@ -12,31 +12,17 @@ public class PeerSession extends BserObject { public static Predicate BY_THEIR_GROUP(final int theirKeyGroupId) { - return new Predicate() { - @Override - public boolean apply(PeerSession session) { - return session.getTheirKeyGroupId() == theirKeyGroupId; - } - }; + return session -> session.getTheirKeyGroupId() == theirKeyGroupId; } public static Predicate BY_IDS(final int theirKeyGroupId, final long ownPreKeyId, final long theirPreKeyId) { - return new Predicate() { - @Override - public boolean apply(PeerSession session) { - return session.getTheirKeyGroupId() == theirKeyGroupId && - session.getOwnPreKeyId() == ownPreKeyId && - session.getTheirPreKeyId() == theirPreKeyId; - } - }; + return session -> session.getTheirKeyGroupId() == theirKeyGroupId && + session.getOwnPreKeyId() == ownPreKeyId && + session.getTheirPreKeyId() == theirPreKeyId; } - public static final Comparator COMPARATOR = new Comparator() { - @Override - public int compare(PeerSession lhs, PeerSession rhs) { - return ByteStrings.compare(lhs.getMasterKey(), rhs.getMasterKey()); - } - }; + public static final Comparator COMPARATOR = (lhs, rhs) -> + ByteStrings.compare(lhs.getMasterKey(), rhs.getMasterKey()); private long sid; private int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java index 154b79bcf1..95ea8b2c72 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java @@ -73,7 +73,8 @@ public ApiOutPeer buidOutPeer(Peer peer) { } return new ApiOutPeer(ApiPeerType.GROUP, group.getGroupId(), group.getAccessHash()); } else { - throw new RuntimeException("Unknown peer: " + peer); + //throw new RuntimeException("Unknown peer: " + peer); + return null; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java index 210d7caf1e..3337f28128 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java @@ -4,6 +4,7 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; @@ -21,7 +22,7 @@ import static im.actor.runtime.promise.Promise.success; /** - * Axolotl Ratchet encryption session + * Double Ratchet encryption session * Session is identified by: * 1) Destination User's Id * 2) Own Key Group Id @@ -115,25 +116,8 @@ private Promise onDecrypt(final byte[] data) { Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(new Function() { - @Override - public DecryptedPackage apply(EncryptedSessionChain encryptedSessionChain) { - return decrypt(encryptedSessionChain, data); - } - }) - .then(new Consumer() { - @Override - public void apply(DecryptedPackage decryptedPackage) { - Log.d(TAG, "onDecrypted"); - latestTheirEphemeralKey = senderEphemeralKey; - } - }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.d(TAG, "onError"); - } - }); + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, data)) + .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { @@ -170,6 +154,7 @@ private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { } private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { + EncryptedSessionChain pickedChain = null; for (EncryptedSessionChain c : decryptionChains) { if (ByteStrings.isEquals(Curve25519.keyGenPublic(c.getOwnPrivateKey()), ephemeralKey)) { @@ -177,30 +162,21 @@ private Promise pickDecryptChain(final byte[] theirEpheme break; } } - return success(pickedChain) - .flatMap(new Function>() { - @Override - public Promise apply(EncryptedSessionChain src) { - if (src != null) { - return success(src); - } - - // TODO: Implement! - return null; -// return ask(context().getEncryption().getKeyManager(), new FetchOwnPreKeyByPublic(ephemeralKey)) -// .map(new Function() { -// @Override -// public EncryptedSessionChain apply(PrivateKey src) { -// EncryptedSessionChain chain = new EncryptedSessionChain(session, src.getKey(), theirEphemeralKey); -// decryptionChains.add(0, chain); -// if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { -// decryptionChains.remove(MAX_DECRYPT_CHAINS) -// .safeErase(); -// } -// return chain; -// } -// }); + if (pickedChain != null) { + return Promise.success(pickedChain); + } + + + return context().getEncryption().getKeyManager().getOwnPreKey(ephemeralKey) + .map(src1 -> { + EncryptedSessionChain chain = new EncryptedSessionChain(session, + src1.getKey(), theirEphemeralKey); + decryptionChains.add(0, chain); + if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { + decryptionChains.remove(MAX_DECRYPT_CHAINS) + .safeErase(); } + return chain; }); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 75b01f2a20..281227f9ab 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -16,8 +16,8 @@ public class EncryptionModule extends AbsModule { - private KeyManagerInt keyManagerInt; - private SessionManagerInt sessionManagerInt; + private KeyManagerInt keyManager; + private SessionManagerInt sessionManager; private ActorRef messageEncryptor; private final HashMap encryptedStates = new HashMap<>(); @@ -28,24 +28,20 @@ public EncryptionModule(ModuleContext context) { public void run() { - keyManagerInt = new KeyManagerInt(context()); - - // Session Manager - ActorRef sessionManager = system().actorOf("encryption/sessions", - () -> new SessionManagerActor(context())); - sessionManagerInt = new SessionManagerInt(sessionManager); + keyManager = new KeyManagerInt(context()); + sessionManager = new SessionManagerInt(context()); messageEncryptor = system().actorOf("encryption/messaging", () -> new EncryptedMsgActor(context())); } - public SessionManagerInt getSessionManager() { - return sessionManagerInt; + public KeyManagerInt getKeyManager() { + return keyManager; } - public KeyManagerInt getKeyManager() { - return keyManagerInt; + public SessionManagerInt getSessionManager() { + return sessionManager; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java index d109f37df3..bbd142cf8d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java @@ -75,6 +75,16 @@ public Promise getOwnPreKey(long id) { return ask(new KeyManagerActor.FetchOwnPreKeyById(id)); } + /** + * Load own pre key by public key + * + * @param publicKey public key + * @return promise of private key + */ + public Promise getOwnPreKey(byte[] publicKey) { + return ask(new KeyManagerActor.FetchOwnPreKeyByPublic(publicKey)); + } + /** * Loading random user's pre key from key group * diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java index e17332f0f5..73314fdec5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java @@ -70,49 +70,38 @@ protected PeerSessionsStorage deserialize(byte[] data) { * @param uid User's id * @param keyGroupId User's key group */ - public Promise pickSession(final int uid, - final int keyGroupId) { - -// return pickCachedSession(uid, keyGroupId) -// .fallback(new Function>() { -// @Override -// public Promise apply(Exception e) { -// return Promises.tuple( -// keyManager.getOwnIdentity(), -// keyManager.getOwnRandomPreKey(), -// keyManager.getUserKeyGroups(uid), -// keyManager.getUserRandomPreKey(uid, keyGroupId)) -// .flatMap(new FunctionTupled4>() { -// @Override -// public Promise apply(KeyManagerActor.OwnIdentity ownIdentity, -// PrivateKey ownPreKey, UserKeys userKeys, -// PublicKey theirPreKey) { -// -// UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) -// .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) -// .first(); -// -// spawnSession(uid, -// ownIdentity.getKeyGroup(), -// keyGroupId, -// ownIdentity.getIdentityKey(), -// keysGroup.getIdentityKey(), -// ownPreKey, -// theirPreKey); -// -// return Promise.success(null); -// } -// }); -// } -// }) -// .afterVoid(new Supplier>() { -// @Override -// public Promise get() { -// return pickCachedSession(uid, keyGroupId); -// } -// }); - return null; + public Promise pickSession(final int uid, + final int keyGroupId) { + + return pickCachedSession(uid, keyGroupId) + .fallback(e -> Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnRandomPreKey(), + keyManager.getUserKeyGroups(uid), + keyManager.getUserRandomPreKey(uid, keyGroupId)) + .flatMap(new FunctionTupled4>() { + @Override + public Promise apply(OwnIdentity ownIdentity, + PrivateKey ownPreKey, + UserKeys userKeys, + PublicKey theirPreKey) { + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + + return Promise.success(null); + } + })) + .flatMap(peerSession -> pickCachedSession(uid, keyGroupId)); } /** @@ -129,33 +118,31 @@ public Promise pickSession(final int uid, final long theirKeyId) { return pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId) - .fallback(new Function>() { - @Override - public Promise apply(Exception e) { - return Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnPreKey(ownKeyId), - keyManager.getUserKeyGroups(uid), - keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { - @Override - public PeerSession apply(OwnIdentity ownIdentity, PrivateKey ownPreKey, UserKeys userKeys, PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - return spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - } - }); - } - }); + .fallback(e -> Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnPreKey(ownKeyId), + keyManager.getUserKeyGroups(uid), + keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) + .map(new FunctionTupled4() { + @Override + public PeerSession apply(OwnIdentity ownIdentity, + PrivateKey ownPreKey, + UserKeys userKeys, + PublicKey theirPreKey) { + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + return spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + } + })); } /** @@ -208,7 +195,7 @@ private PeerSession spawnSession(int uid, PeerSessionsStorage sessionsStorage = peerSessions.getValue(uid); if (sessionsStorage == null) { - sessionsStorage = new PeerSessionsStorage(uid, new ArrayList()); + sessionsStorage = new PeerSessionsStorage(uid, new ArrayList<>()); } sessionsStorage = sessionsStorage.addSession(peerSession); peerSessions.addOrUpdateItem(sessionsStorage); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java index 4760bc6501..0f0ed06592 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java @@ -1,20 +1,42 @@ package im.actor.core.modules.encryption; import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + +/** + * Session Manager for encrypted chats + */ public class SessionManagerInt extends ActorInterface { - public SessionManagerInt(ActorRef dest) { - super(dest); + public SessionManagerInt(ModuleContext context) { + super(system().actorOf("encryption/sessions", () -> new SessionManagerActor(context))); } + /** + * Pick fresh session with random pre keys + * + * @param uid user's id + * @param keyGroup key group id + * @return promise of session + */ public Promise pickSession(int uid, int keyGroup) { return ask(new SessionManagerActor.PickSessionForEncrypt(uid, keyGroup)); } + /** + * Pick session with specific identity keys + * + * @param uid user's id + * @param keyGroup key group id + * @param ownKeyId own identity prekey id + * @param theirKeyId their identity prekey id + * @return + */ public Promise pickSession(int uid, int keyGroup, long ownKeyId, long theirKeyId) { return ask(new SessionManagerActor.PickSessionForDecrypt(uid, keyGroup, theirKeyId, ownKeyId)); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java index 6e1fdf1b41..e252acd6a8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java @@ -5,12 +5,7 @@ public class EncryptedBoxKey { public static Predicate FILTER(final int myUid, final int keyGroupId) { - return new Predicate() { - @Override - public boolean apply(EncryptedBoxKey boxKey) { - return boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; - } - }; + return boxKey -> boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; } private final int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java index 6ac9f1609d..807cf402f3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java @@ -16,6 +16,7 @@ import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.Reaction; import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.api.ApiSupportConfiguration; @@ -55,10 +56,15 @@ public ConversationHistoryActor(Peer peer, ModuleContext context) { @Override public void preStart() { super.preStart(); - historyMaxDate = preferences().getLong(KEY_LOADED_DATE, Long.MAX_VALUE); - historyLoaded = preferences().getBool(KEY_LOADED, false); - if (!preferences().getBool(KEY_LOADED_INIT, false)) { - self().send(new LoadMore()); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + historyMaxDate = 0; + historyLoaded = true; + } else { + historyMaxDate = preferences().getLong(KEY_LOADED_DATE, Long.MAX_VALUE); + historyLoaded = preferences().getBool(KEY_LOADED, false); + if (!preferences().getBool(KEY_LOADED_INIT, false)) { + self().send(new LoadMore()); + } } } From 059e48e82ee994d5b146d35e0d5109efc194126a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 06:55:30 +0300 Subject: [PATCH 25/81] feat(core): Restored encrypting of messages (decryption is not working yet) --- .../im/actor/core/modules/ModuleActor.java | 11 +- .../encryption/EncryptedPeerActor.java | 420 ------------------ .../modules/encryption/EncryptionModule.java | 58 +-- ...rocessor.java => EncryptionProcessor.java} | 4 +- .../encryption/ratchet/EncryptedMsg.java | 30 ++ .../{ => ratchet}/EncryptedMsgActor.java | 60 +-- .../encryption/ratchet/EncryptedSession.java | 27 ++ .../{ => ratchet}/EncryptedSessionActor.java | 24 +- .../encryption/ratchet/EncryptedUser.java | 48 ++ .../ratchet/EncryptedUserActor.java | 329 ++++++++++++++ .../KeyManager.java} | 7 +- .../{ => ratchet}/KeyManagerActor.java | 14 +- .../SessionManager.java} | 13 +- .../{ => ratchet}/SessionManagerActor.java | 132 +++--- .../messaging/actions/SenderActor.java | 62 ++- .../sequence/processor/UpdateProcessor.java | 11 +- 16 files changed, 639 insertions(+), 611 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{EncryptedProcessor.java => EncryptionProcessor.java} (91%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/EncryptedMsgActor.java (65%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/EncryptedSessionActor.java (93%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{KeyManagerInt.java => ratchet/KeyManager.java} (94%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/KeyManagerActor.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{SessionManagerInt.java => ratchet/SessionManager.java} (76%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/SessionManagerActor.java (67%) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java index 95ea8b2c72..d04d09c428 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/ModuleActor.java @@ -72,9 +72,14 @@ public ApiOutPeer buidOutPeer(Peer peer) { return null; } return new ApiOutPeer(ApiPeerType.GROUP, group.getGroupId(), group.getAccessHash()); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + User user = getUser(peer.getPeerId()); + if (user == null) { + return null; + } + return new ApiOutPeer(ApiPeerType.ENCRYPTEDPRIVATE, user.getUid(), user.getAccessHash()); } else { - //throw new RuntimeException("Unknown peer: " + peer); - return null; + throw new RuntimeException("Unknown peer: " + peer); } } @@ -83,6 +88,8 @@ public ApiPeer buildApiPeer(Peer peer) { return new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()); } else if (peer.getPeerType() == PeerType.GROUP) { return new ApiPeer(ApiPeerType.GROUP, peer.getPeerId()); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return new ApiPeer(ApiPeerType.ENCRYPTEDPRIVATE, peer.getPeerId()); } else { return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java deleted file mode 100644 index 079daff534..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedPeerActor.java +++ /dev/null @@ -1,420 +0,0 @@ -package im.actor.core.modules.encryption; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import im.actor.core.entity.encryption.PeerSession; -import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; -import im.actor.core.modules.ModuleActor; -import im.actor.core.util.RandomUtils; -import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Props; -import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.crypto.Cryptos; -import im.actor.runtime.crypto.IntegrityException; -import im.actor.runtime.crypto.primitives.prf.PRF; -import im.actor.runtime.function.Consumer; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Predicate; -import im.actor.runtime.promise.Promise; -import im.actor.runtime.crypto.box.ActorBox; -import im.actor.runtime.crypto.box.ActorBoxKey; -import im.actor.runtime.crypto.primitives.util.ByteStrings; -import im.actor.runtime.promise.Promises; -import im.actor.runtime.promise.PromisesArray; -import im.actor.runtime.function.Tuple2; - -import static im.actor.runtime.promise.Promise.success; - -public class EncryptedPeerActor extends ModuleActor { - - private final String TAG; - - private final int uid; - - private int ownKeyGroupId; - private UserKeys theirKeys; - - private HashMap activeSessions = new HashMap<>(); - private HashSet ignoredKeyGroups = new HashSet<>(); - - private boolean isReady = false; - private KeyManagerInt keyManager; - - private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); - - public EncryptedPeerActor(int uid, ModuleContext context) { - super(context); - this.uid = uid; - TAG = "EncryptedPeerActor#" + uid; - } - - @Override - public void preStart() { - super.preStart(); - - keyManager = context().getEncryption().getKeyManager(); - - Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getUserKeyGroups(uid)) - .then(new Consumer>() { - @Override - public void apply(Tuple2 res) { - Log.d(TAG, "then"); - ownKeyGroupId = res.getT1().getKeyGroup(); - theirKeys = res.getT2(); - isReady = true; - unstashAll(); - } - }) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - Log.w(TAG, "Unable to fetch initial parameters"); - Log.e(TAG, e); - } - }); - } - - private Promise doEncrypt(final byte[] data) { - - if (!isReady) { - stash(); - return null; - } - - // - // Stage 1: Loading User Key Groups - // Stage 2: Pick sessions for encryption - // Stage 3: Encrypt box_key int session - // Stage 4: Encrypt box - // - final byte[] encKey = Crypto.randomBytes(32); - final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "doEncrypt"); - final long start = Runtime.getActorTime(); - return PromisesArray.of(theirKeys.getUserKeysGroups()) - .filter(new Predicate() { - @Override - public boolean apply(UserKeysGroup keysGroup) { - return !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()); - } - }) - .mapOptional(new Function>() { - @Override - public Promise apply(final UserKeysGroup keysGroup) { - if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { - return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); - } - return context().getEncryption().getSessionManager() - .pickSession(uid, keysGroup.getKeyGroupId()) - .failure(new Consumer() { - @Override - public void apply(Exception e) { - ignoredKeyGroups.add(keysGroup.getKeyGroupId()); - } - }) - .map(new Function() { - @Override - public SessionActor apply(PeerSession src) { - return spawnSession(src); - } - }); - } - }) - .mapOptional(encrypt(encKeyExtended)) - .zip() - .map(new Function, EncryptBoxResponse>() { - @Override - public EncryptBoxResponse apply(List src) { - - if (src.size() == 0) { - throw new RuntimeException("No sessions available"); - } - - Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - - ArrayList encryptedKeys = new ArrayList<>(); - for (EncryptedSessionActor.EncryptedPackageRes r : src) { - Log.d(TAG, "Keys: " + r.getKeyGroupId()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); - } - - byte[] encData; - try { - encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); - } catch (IntegrityException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - - Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - - return new EncryptBoxResponse(new EncryptedBox( - encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), - ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData))); - } - }); - } - - private Promise doDecrypt(final EncryptedBox data) { - - if (!isReady) { - stash(); - return null; - } - - final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); - final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); - - // - // Picking session - // - - if (ignoredKeyGroups.contains(senderKeyGroup)) { - throw new RuntimeException("This key group is ignored"); - } - - return PromisesArray.of(data.getKeys()) - .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) - .first() - .flatMap(new Function>>() { - @Override - public Promise> apply(final EncryptedBoxKey boxKey) { - final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); - final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); - - if (activeSessions.containsKey(boxKey.getKeyGroupId())) { - for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { - if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && - s.getSession().getTheirPreKeyId() == senderPreKeyId) { - return success(new Tuple2<>(s, boxKey)); - } - } - } - return context().getEncryption().getSessionManager() - .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) - .map(new Function>() { - @Override - public Tuple2 apply(PeerSession src) { - return new Tuple2<>(spawnSession(src), boxKey); - } - }); - } - }) - .flatMap(new Function, Promise>() { - @Override - public Promise apply(Tuple2 src) { - Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); - // TODO: Implement - return null; - } - }) - .map(new Function() { - @Override - public DecryptBoxResponse apply(EncryptedSessionActor.DecryptedPackage decryptedPackage) { - byte[] encData; - try { - byte[] encKeyExtended = decryptedPackage.getData().length >= 128 - ? decryptedPackage.getData() - : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); - encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); - Log.d(TAG, "Box size: " + encData.length); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - return new DecryptBoxResponse(encData); - } - }); - } - - private void onKeysUpdated(UserKeys userKeys) { - if (!isReady) { - stash(); - return; - } - - this.theirKeys = userKeys; - } - - private SessionActor spawnSession(final PeerSession session) { - - ActorRef res = system().actorOf(Props.create(new ActorCreator() { - @Override - public EncryptedSessionActor create() { - return new EncryptedSessionActor(context(), session); - } - }), getPath() + "/k_" + RandomUtils.nextRid()); - - SessionActor cont = new SessionActor(res, session); - - if (activeSessions.containsKey(session.getTheirKeyGroupId())) { - activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); - } else { - ArrayList l = new ArrayList<>(); - l.add(cont); - activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); - } - return cont; - } - - private Function> encrypt(final byte[] encKey) { - return new Function>() { - @Override - public Promise apply(SessionActor sessionActor) { - // return ask(sessionActor.getActorRef(), new EncryptedSessionActor.EncryptPackage(encKey)); - // TODO: Implement - return null; - } - }; - } - - // - // Messages - // - - @Override - public Promise onAsk(Object message) throws Exception { - if (message instanceof EncryptBox) { - if (!isReady) { - stash(); - return null; - } - return doEncrypt(((EncryptBox) message).getData()); - } else if (message instanceof DecryptBox) { - if (!isReady) { - stash(); - return null; - } - return doDecrypt(((DecryptBox) message).getEncryptedBox()); - } else { - return super.onAsk(message); - } - } - - @Override - public void onReceive(Object message) { - if (message instanceof KeyGroupUpdated) { - onKeysUpdated(((KeyGroupUpdated) message).getUserKeys()); - } else { - super.onReceive(message); - } - } - - public static class EncryptBox implements AskMessage { - private byte[] data; - - public EncryptBox(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } - - public static class EncryptBoxResponse extends AskResult { - - private EncryptedBox box; - - public EncryptBoxResponse(EncryptedBox box) { - this.box = box; - } - - public EncryptedBox getBox() { - return box; - } - } - - public static class DecryptBox implements AskMessage { - - private EncryptedBox encryptedBox; - - public DecryptBox(EncryptedBox encryptedBox) { - this.encryptedBox = encryptedBox; - } - - public EncryptedBox getEncryptedBox() { - return encryptedBox; - } - } - - public static class DecryptBoxResponse extends AskResult { - - private byte[] data; - - public DecryptBoxResponse(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } - - private class SessionHolder { - - private int keyGroupId; - private ArrayList sessions; - - public SessionHolder(int keyGroupId, ArrayList sessions) { - this.keyGroupId = keyGroupId; - this.sessions = sessions; - } - - public int getKeyGroupId() { - return keyGroupId; - } - - public ArrayList getSessions() { - return sessions; - } - } - - private class SessionActor { - - private ActorRef actorRef; - private PeerSession session; - - public SessionActor(ActorRef actorRef, PeerSession session) { - this.actorRef = actorRef; - this.session = session; - } - - public ActorRef getActorRef() { - return actorRef; - } - - public PeerSession getSession() { - return session; - } - } - - public static class KeyGroupUpdated { - - private UserKeys userKeys; - - public KeyGroupUpdated(UserKeys userKeys) { - this.userKeys = userKeys; - } - - public UserKeys getUserKeys() { - return userKeys; - } - } -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 281227f9ab..508cdd75c4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -2,25 +2,28 @@ import java.util.HashMap; +import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiMessage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.EncryptedPeerActor; -import im.actor.core.modules.encryption.KeyManagerActor; -import im.actor.core.modules.encryption.EncryptedMsgActor; -import im.actor.core.modules.encryption.KeyManagerInt; -import im.actor.runtime.actors.ActorCreator; -import im.actor.runtime.actors.ActorRef; -import im.actor.runtime.actors.Props; +import im.actor.core.modules.encryption.ratchet.EncryptedMsg; +import im.actor.core.modules.encryption.ratchet.EncryptedMsgActor; +import im.actor.core.modules.encryption.ratchet.EncryptedUser; +import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; +import im.actor.core.modules.encryption.ratchet.KeyManager; +import im.actor.core.modules.encryption.ratchet.SessionManager; +import im.actor.core.network.mtp.entity.EncryptedPackage; +import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; public class EncryptionModule extends AbsModule { - private KeyManagerInt keyManager; - private SessionManagerInt sessionManager; + private KeyManager keyManager; + private SessionManager sessionManager; + private EncryptedMsg encryption; - private ActorRef messageEncryptor; - private final HashMap encryptedStates = new HashMap<>(); + private final HashMap users = new HashMap<>(); public EncryptionModule(ModuleContext context) { super(context); @@ -28,34 +31,37 @@ public EncryptionModule(ModuleContext context) { public void run() { - keyManager = new KeyManagerInt(context()); + keyManager = new KeyManager(context()); - sessionManager = new SessionManagerInt(context()); + sessionManager = new SessionManager(context()); - messageEncryptor = system().actorOf("encryption/messaging", - () -> new EncryptedMsgActor(context())); + encryption = new EncryptedMsg(system().actorOf("encryption/messaging", + () -> new EncryptedMsgActor(context()))); } - public KeyManagerInt getKeyManager() { + public KeyManager getKeyManager() { return keyManager; } - public SessionManagerInt getSessionManager() { + public SessionManager getSessionManager() { return sessionManager; } - - public ActorRef getMessageEncryptor() { - return messageEncryptor; + public EncryptedMsg getEncryption() { + return encryption; } - public ActorRef getEncryptedChatManager(final int uid) { - synchronized (encryptedStates) { - if (!encryptedStates.containsKey(uid)) { - encryptedStates.put(uid, system().actorOf("encryption/uid_" + uid, - () -> new EncryptedPeerActor(uid, context()))); + public EncryptedUser getEncryptedUser(int uid) { + synchronized (users) { + if (!users.containsKey(uid)) { + users.put(uid, new EncryptedUser(system().actorOf("encryption/uid_" + uid, + () -> new EncryptedUserActor(uid, context())))); } - return encryptedStates.get(uid); + return users.get(uid); } } + + public Promise encrypt(int uid, ApiMessage message) { + return getEncryption().encrypt(uid, message); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java similarity index 91% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index b510988930..d173ba72a5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -10,9 +10,9 @@ import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; -public class EncryptedProcessor extends AbsModule implements SequenceProcessor { +public class EncryptionProcessor extends AbsModule implements SequenceProcessor { - public EncryptedProcessor(ModuleContext context) { + public EncryptionProcessor(ModuleContext context) { super(context); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java new file mode 100644 index 0000000000..ee0f911348 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -0,0 +1,30 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiMessage; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Entry point for message encryption + */ +public class EncryptedMsg extends ActorInterface { + + public EncryptedMsg(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypt Message for private secret chat + * + * @param uid user's id + * @param message message content + * @return promise of encrypted message + */ + public Promise encrypt(int uid, ApiMessage message) { + return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java similarity index 65% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 9247c9a773..3f587d906f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -1,10 +1,9 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedBoxSignature; import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.api.ApiMessage; @@ -14,9 +13,7 @@ import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ask.AskCallback; -import im.actor.runtime.function.Function; +import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.promise.Promise; public class EncryptedMsgActor extends ModuleActor { @@ -27,28 +24,23 @@ public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { + private Promise doEncrypt(int uid, ApiMessage message) throws IOException { Log.d(TAG, "doEncrypt"); -// return ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedPeerActor.EncryptBox(message.buildContainer())) -// .map(new Function() { -// @Override -// public EncryptedMessage apply(EncryptedPeerActor.EncryptBoxResponse encryptBoxResponse) { -// Log.d(TAG, "doEncrypt:onResult"); -// ArrayList boxKeys = new ArrayList(); -// for (EncryptedBoxKey b : encryptBoxResponse.getBox().getKeys()) { -// boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), -// b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); -// } -// ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", encryptBoxResponse.getBox().getEncryptedPackage(), -// new ArrayList()); -// ApiEncryptedMessage apiEncryptedMessage = new ApiEncryptedMessage(apiEncryptedBox); -// return new EncryptedMessage(apiEncryptedMessage); -// } -// }); - - // TODO: Implement - return null; + return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) + .map(encryptBoxResponse -> { + Log.d(TAG, "doEncrypt:onResult"); + ArrayList boxKeys = new ArrayList<>(); + for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { + boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), + b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); + } + ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, + boxKeys, "aes-kuznechik", + encryptBoxResponse.getEncryptedPackage(), + new ArrayList<>()); + return new ApiEncryptedMessage(apiEncryptedBox); + }); } public void onDecrypt(int uid, ApiEncryptedMessage message) { @@ -64,11 +56,11 @@ public void onDecrypt(int uid, ApiEncryptedMessage message) { final EncryptedBox encryptedBox = new EncryptedBox(encryptedBoxKeys.toArray(new EncryptedBoxKey[0]), message.getBox().getEncPackage()); // TODO: Implement -// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedPeerActor.DecryptBox(encryptedBox), new AskCallback() { +// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedUserActor.DecryptBox(encryptedBox), new AskCallback() { // @Override // public void onResult(Object obj) { // Log.d(TAG, "onDecrypt:onResult in " + (Runtime.getActorTime() - start) + " ms"); -// EncryptedPeerActor.DecryptBoxResponse re = (EncryptedPeerActor.DecryptBoxResponse) obj; +// EncryptedUserActor.DecryptBoxResponse re = (EncryptedUserActor.DecryptBoxResponse) obj; // try { // ApiMessage message = ApiMessage.fromBytes(re.getData()); // Log.d(TAG, "onDecrypt:onResult " + message); @@ -122,7 +114,7 @@ public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMess } } - public static class EncryptMessage { + public static class EncryptMessage implements AskMessage { private int uid; private ApiMessage message; @@ -141,18 +133,6 @@ public ApiMessage getMessage() { } } - public static class EncryptedMessage { - private ApiEncryptedMessage encryptedMessage; - - public EncryptedMessage(ApiEncryptedMessage encryptedMessage) { - this.encryptedMessage = encryptedMessage; - } - - public ApiEncryptedMessage getEncryptedMessage() { - return encryptedMessage; - } - } - public static class DecryptMessage { private ApiEncryptedMessage encryptedMessage; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java new file mode 100644 index 0000000000..b9979d921d --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -0,0 +1,27 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Double Ratchet encrypted session operations + */ +public class EncryptedSession extends ActorInterface { + + public EncryptedSession(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypt data for session + * + * @param data for encryption + * @return promise of encrypted package + */ + public Promise encrypt(byte[] data) { + return ask(new EncryptedSessionActor.EncryptPackage(data)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java similarity index 93% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index 3337f28128..c8e3944c7f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -1,19 +1,15 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.util.ArrayList; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.session.EncryptedSessionChain; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.function.Consumer; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Supplier; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -55,7 +51,7 @@ public class EncryptedSessionActor extends ModuleActor { // Key Manager reference // - private KeyManagerInt keyManager; + private KeyManager keyManager; // // Temp encryption chains @@ -90,10 +86,15 @@ private Promise onEncrypt(final byte[] data) { // Stage 3: Decrypt // - return success(latestTheirEphemeralKey) - .mapIfNullPromise(() -> - keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) - .map(PublicKey::getPublicKey)) + Promise ephemeralKey; + if (latestTheirEphemeralKey != null) { + ephemeralKey = success(latestTheirEphemeralKey); + } else { + ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) + .map(PublicKey::getPublicKey); + } + + return ephemeralKey .map(publicKey -> pickEncryptChain(publicKey)) .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } @@ -130,8 +131,9 @@ private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { return encryptionChains.get(0); } + EncryptedSessionChain chain = new EncryptedSessionChain(session, + Curve25519.keyGenPrivate(Crypto.randomBytes(32)), ephemeralKey); - EncryptedSessionChain chain = new EncryptedSessionChain(session, Curve25519.keyGenPrivate(Crypto.randomBytes(32)), ephemeralKey); encryptionChains.add(0, chain); return chain; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java new file mode 100644 index 0000000000..16294d582e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java @@ -0,0 +1,48 @@ +package im.actor.core.modules.encryption.ratchet; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.modules.encryption.entity.EncryptedBox; +import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.promise.Promise; + +/** + * Encrypting data for private Secret Chats + */ +public class EncryptedUser extends ActorInterface { + + public EncryptedUser(@NotNull ActorRef dest) { + super(dest); + } + + /** + * Encrypting data + * + * @param data data for encryption + * @return promise of encrypted box + */ + public Promise encrypt(byte[] data) { + return ask(new EncryptedUserActor.EncryptBox(data)); + } + + /** + * Decrypting data + * + * @param data data for decryption + * @return promise of decrypted box + */ + public Promise decrypt(EncryptedBox data) { + return ask(new EncryptedUserActor.DecryptBox(data)); + } + + /** + * Notify about user keys updated for refreshing internal keys cache + * + * @param updatedUserKeys updated user keys + */ + public void onUserKeysChanged(UserKeys updatedUserKeys) { + send(new EncryptedUserActor.KeyGroupUpdated(updatedUserKeys)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java new file mode 100644 index 0000000000..f03b306898 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -0,0 +1,329 @@ +package im.actor.core.modules.encryption.ratchet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.entity.EncryptedBox; +import im.actor.core.modules.encryption.entity.EncryptedBoxKey; +import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.ModuleActor; +import im.actor.core.util.RandomUtils; +import im.actor.runtime.*; +import im.actor.runtime.Runtime; +import im.actor.runtime.actors.ActorRef; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.ask.AskResult; +import im.actor.runtime.crypto.Cryptos; +import im.actor.runtime.crypto.IntegrityException; +import im.actor.runtime.crypto.primitives.prf.PRF; +import im.actor.runtime.function.Function; +import im.actor.runtime.promise.Promise; +import im.actor.runtime.crypto.box.ActorBox; +import im.actor.runtime.crypto.box.ActorBoxKey; +import im.actor.runtime.crypto.primitives.util.ByteStrings; +import im.actor.runtime.promise.Promises; +import im.actor.runtime.promise.PromisesArray; +import im.actor.runtime.function.Tuple2; + +import static im.actor.runtime.promise.Promise.success; + +public class EncryptedUserActor extends ModuleActor { + + private final String TAG; + + private final int uid; + + private int ownKeyGroupId; + private UserKeys theirKeys; + + private HashMap activeSessions = new HashMap<>(); + private HashSet ignoredKeyGroups = new HashSet<>(); + + private boolean isReady = false; + + private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); + + public EncryptedUserActor(int uid, ModuleContext context) { + super(context); + this.uid = uid; + TAG = "EncryptedUserActor#" + uid; + } + + @Override + public void preStart() { + super.preStart(); + + KeyManager keyManager = context().getEncryption().getKeyManager(); + + Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getUserKeyGroups(uid)) + .then(res -> { + ownKeyGroupId = res.getT1().getKeyGroup(); + theirKeys = res.getT2(); + onLoaded(); + }) + .failure(e -> { + Log.w(TAG, "Unable to fetch initial parameters. Freezing encryption with user #" + uid); + Log.e(TAG, e); + }); + } + + private void onLoaded() { + Log.d(TAG, "Loaded initial parameters"); + isReady = true; + unstashAll(); + } + + private Promise doEncrypt(final byte[] data) { + + + // + // Stage 1: Loading User Key Groups + // Stage 2: Pick sessions for encryption + // Stage 3: Encrypt box_key int session + // Stage 4: Encrypt box + // + final byte[] encKey = Crypto.randomBytes(32); + final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); + Log.d(TAG, "doEncrypt"); + final long start = Runtime.getActorTime(); + return PromisesArray.of(theirKeys.getUserKeysGroups()) + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId())) + .mapOptional(keysGroup -> { + if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { + return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); + } + return context().getEncryption().getSessionManager() + .pickSession(uid, keysGroup.getKeyGroupId()) + .failure(e -> { + ignoredKeyGroups.add(keysGroup.getKeyGroupId()); + }) + .map(src -> spawnSession(src)); + }) + .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended)) + .zip() + .map(src -> { + + if (src.size() == 0) { + throw new RuntimeException("No sessions available"); + } + + Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + + ArrayList encryptedKeys = new ArrayList<>(); + for (EncryptedSessionActor.EncryptedPackageRes r : src) { + Log.d(TAG, "Keys: " + r.getKeyGroupId()); + encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); + } + + byte[] encData; + try { + encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); + } catch (IntegrityException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + + return new EncryptedBox( + encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), + ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); + }); + } + + private Promise doDecrypt(final EncryptedBox data) { + + final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); + final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); + + // + // Picking session + // + + if (ignoredKeyGroups.contains(senderKeyGroup)) { + throw new RuntimeException("This key group is ignored"); + } + + return PromisesArray.of(data.getKeys()) + .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) + .first() + .flatMap(boxKey -> { + final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); + final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); + + if (activeSessions.containsKey(boxKey.getKeyGroupId())) { + for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { + if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && + s.getSession().getTheirPreKeyId() == senderPreKeyId) { + return success(new Tuple2<>(s, boxKey)); + } + } + } + return context().getEncryption().getSessionManager() + .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) + .map(new Function>() { + @Override + public Tuple2 apply(PeerSession src) { + return new Tuple2<>(spawnSession(src), boxKey); + } + }); + }) + .flatMap((Function, Promise>) src -> { + Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); + // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); + // TODO: Implement + return null; + }) + .map(decryptedPackage -> { + byte[] encData; + try { + byte[] encKeyExtended = decryptedPackage.getData().length >= 128 + ? decryptedPackage.getData() + : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); + encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); + Log.d(TAG, "Box size: " + encData.length); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return encData; + }); + } + + private void onKeysUpdated(UserKeys userKeys) { + this.theirKeys = userKeys; + } + + private SessionActor spawnSession(final PeerSession session) { + + ActorRef res = system().actorOf(getPath() + "/k_" + RandomUtils.nextRid(), + () -> new EncryptedSessionActor(context(), session)); + + SessionActor cont = new SessionActor(new EncryptedSession(res), session); + + if (activeSessions.containsKey(session.getTheirKeyGroupId())) { + activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); + } else { + ArrayList l = new ArrayList<>(); + l.add(cont); + activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); + } + return cont; + } + + // + // Messages + // + + @Override + public Promise onAsk(Object message) throws Exception { + if (!isReady) { + stash(); + return null; + } + + if (message instanceof EncryptBox) { + return doEncrypt(((EncryptBox) message).getData()); + } else if (message instanceof DecryptBox) { + return doDecrypt(((DecryptBox) message).getEncryptedBox()); + } else { + return super.onAsk(message); + } + } + + @Override + public void onReceive(Object message) { + if (message instanceof KeyGroupUpdated) { + if (!isReady) { + stash(); + return; + } + onKeysUpdated(((KeyGroupUpdated) message).getUserKeys()); + } else { + super.onReceive(message); + } + } + + public static class EncryptBox implements AskMessage { + private byte[] data; + + public EncryptBox(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + } + + public static class DecryptBox implements AskMessage { + + private EncryptedBox encryptedBox; + + public DecryptBox(EncryptedBox encryptedBox) { + this.encryptedBox = encryptedBox; + } + + public EncryptedBox getEncryptedBox() { + return encryptedBox; + } + } + + public static class KeyGroupUpdated { + + private UserKeys userKeys; + + public KeyGroupUpdated(UserKeys userKeys) { + this.userKeys = userKeys; + } + + public UserKeys getUserKeys() { + return userKeys; + } + } + + private class SessionHolder { + + private int keyGroupId; + private ArrayList sessions; + + public SessionHolder(int keyGroupId, ArrayList sessions) { + this.keyGroupId = keyGroupId; + this.sessions = sessions; + } + + public int getKeyGroupId() { + return keyGroupId; + } + + public ArrayList getSessions() { + return sessions; + } + } + + private class SessionActor { + + private EncryptedSession encryptedSession; + private PeerSession session; + + public SessionActor(EncryptedSession encryptedSession, PeerSession session) { + this.encryptedSession = encryptedSession; + this.session = session; + } + + public EncryptedSession getEncryptedSession() { + return encryptedSession; + } + + public PeerSession getSession() { + return session; + } + } + +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java similarity index 94% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java index bbd142cf8d..7e20072d5a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.modules.ModuleContext; @@ -7,7 +7,6 @@ import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; @@ -16,14 +15,14 @@ /** * Encryption Key Manager. Used for loading user's keys for encryption/decryption. */ -public class KeyManagerInt extends ActorInterface { +public class KeyManager extends ActorInterface { /** * Default Constructor * * @param context actor context */ - public KeyManagerInt(ModuleContext context) { + public KeyManager(ModuleContext context) { super(system().actorOf("encryption/keys", () -> new KeyManagerActor(context))); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index 13ca85f3cf..ec7f04681c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; @@ -15,6 +15,7 @@ import im.actor.core.api.rpc.RequestUploadPreKey; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.Configuration; import im.actor.core.modules.encryption.entity.OwnIdentity; import im.actor.core.modules.encryption.entity.PrivateKeyStorage; import im.actor.core.modules.encryption.entity.PrivateKey; @@ -22,6 +23,7 @@ import im.actor.core.modules.encryption.entity.UserKeysGroup; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -413,8 +415,9 @@ private Promise onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyG if (validatedKeysGroup != null) { UserKeys updatedUserKeys = userKeys.addUserKeyGroup(validatedKeysGroup); cacheUserKeys(updatedUserKeys); - context().getEncryption().getEncryptedChatManager(uid) - .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + context().getEncryption() + .getEncryptedUser(uid) + .onUserKeysChanged(updatedUserKeys); } return Promise.success(null); } @@ -433,8 +436,9 @@ private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId); cacheUserKeys(updatedUserKeys); - context().getEncryption().getEncryptedChatManager(uid) - .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); + context().getEncryption() + .getEncryptedUser(uid) + .onUserKeysChanged(updatedUserKeys); return Promise.success(null); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java similarity index 76% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java index 0f0ed06592..90371d77fe 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManager.java @@ -1,19 +1,20 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; /** - * Session Manager for encrypted chats + * Session Manager for encrypted chats. + * Stores and manages encrypted sessions between users. + * Can be asked to pick session parameters for specific users. */ -public class SessionManagerInt extends ActorInterface { +public class SessionManager extends ActorInterface { - public SessionManagerInt(ModuleContext context) { + public SessionManager(ModuleContext context) { super(system().actorOf("encryption/sessions", () -> new SessionManagerActor(context))); } @@ -35,7 +36,7 @@ public Promise pickSession(int uid, int keyGroup) { * @param keyGroup key group id * @param ownKeyId own identity prekey id * @param theirKeyId their identity prekey id - * @return + * @return promise of session */ public Promise pickSession(int uid, int keyGroup, long ownKeyId, long theirKeyId) { return ask(new SessionManagerActor.PickSessionForDecrypt(uid, keyGroup, theirKeyId, ownKeyId)); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java similarity index 67% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 73314fdec5..3fca3939c7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.ratchet; import java.io.IOException; import java.util.ArrayList; @@ -22,6 +22,7 @@ import im.actor.runtime.crypto.ratchet.RatchetPublicKey; import im.actor.runtime.function.Function; import im.actor.runtime.function.FunctionTupled4; +import im.actor.runtime.function.Tuple4; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; @@ -35,7 +36,7 @@ public class SessionManagerActor extends ModuleActor { private static final String TAG = "SessionManagerActor"; private KeyValueEngine peerSessions; - private KeyManagerInt keyManager; + private KeyManager keyManager; public SessionManagerActor(ModuleContext context) { super(context); @@ -70,38 +71,39 @@ protected PeerSessionsStorage deserialize(byte[] data) { * @param uid User's id * @param keyGroupId User's key group */ - public Promise pickSession(final int uid, - final int keyGroupId) { - - return pickCachedSession(uid, keyGroupId) - .fallback(e -> Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnRandomPreKey(), - keyManager.getUserKeyGroups(uid), - keyManager.getUserRandomPreKey(uid, keyGroupId)) - .flatMap(new FunctionTupled4>() { - @Override - public Promise apply(OwnIdentity ownIdentity, - PrivateKey ownPreKey, - UserKeys userKeys, - PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - - return Promise.success(null); - } - })) - .flatMap(peerSession -> pickCachedSession(uid, keyGroupId)); + public Promise pickSession(final int uid, + final int keyGroupId) { + + PeerSession cached = pickCachedSession(uid, keyGroupId); + if (cached != null) { + return Promise.success(cached); + } + return Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnRandomPreKey(), + keyManager.getUserKeyGroups(uid), + keyManager.getUserRandomPreKey(uid, keyGroupId)) + .map(tuple -> { + + OwnIdentity ownIdentity = tuple.getT1(); + PrivateKey ownPreKey = tuple.getT2(); + UserKeys userKeys = tuple.getT3(); + PublicKey theirPreKey = tuple.getT4(); + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + + return pickCachedSession(uid, keyGroupId); + }); } /** @@ -117,32 +119,34 @@ public Promise pickSession(final int uid, final long ownKeyId, final long theirKeyId) { - return pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId) - .fallback(e -> Promises.tuple( - keyManager.getOwnIdentity(), - keyManager.getOwnPreKey(ownKeyId), - keyManager.getUserKeyGroups(uid), - keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) - .map(new FunctionTupled4() { - @Override - public PeerSession apply(OwnIdentity ownIdentity, - PrivateKey ownPreKey, - UserKeys userKeys, - PublicKey theirPreKey) { - - UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) - .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) - .first(); - - return spawnSession(uid, - ownIdentity.getKeyGroup(), - keyGroupId, - ownIdentity.getIdentityKey(), - keysGroup.getIdentityKey(), - ownPreKey, - theirPreKey); - } - })); + PeerSession cached = pickCachedSession(uid, keyGroupId, ownKeyId, theirKeyId); + if (cached != null) { + return Promise.success(cached); + } + + return Promises.tuple( + keyManager.getOwnIdentity(), + keyManager.getOwnPreKey(ownKeyId), + keyManager.getUserKeyGroups(uid), + keyManager.getUserPreKey(uid, keyGroupId, theirKeyId)) + .map(tuple -> { + OwnIdentity ownIdentity = tuple.getT1(); + PrivateKey ownPreKey = tuple.getT2(); + UserKeys userKeys = tuple.getT3(); + PublicKey theirPreKey = tuple.getT4(); + + UserKeysGroup keysGroup = ManagedList.of(userKeys.getUserKeysGroups()) + .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) + .first(); + + return spawnSession(uid, + ownIdentity.getKeyGroup(), + keyGroupId, + ownIdentity.getIdentityKey(), + keysGroup.getIdentityKey(), + ownPreKey, + theirPreKey); + }); } /** @@ -209,12 +213,12 @@ private PeerSession spawnSession(int uid, * @param keyGroupId Key Group Id * @return promise of session */ - private Promise pickCachedSession(int uid, final int keyGroupId) { + private PeerSession pickCachedSession(int uid, final int keyGroupId) { return ManagedList.of(peerSessions.getValue(uid)) .flatMap(PeerSessionsStorage.SESSIONS) .filter(PeerSession.BY_THEIR_GROUP(keyGroupId)) .sorted(PeerSession.COMPARATOR) - .firstPromise(); + .firstOrNull(); } /** @@ -226,12 +230,12 @@ private Promise pickCachedSession(int uid, final int keyGroupId) { * @param theirKeyId Their Pre key id * @return promise of session */ - private Promise pickCachedSession(int uid, final int keyGroupId, final long ownKeyId, final long theirKeyId) { + private PeerSession pickCachedSession(int uid, final int keyGroupId, final long ownKeyId, final long theirKeyId) { return ManagedList.of(peerSessions.getValue(uid)) .flatMap(PeerSessionsStorage.SESSIONS) .filter(PeerSession.BY_IDS(keyGroupId, ownKeyId, theirKeyId)) .sorted(PeerSession.COMPARATOR) - .firstPromise(); + .firstOrNull(); } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 8deddc101d..4c8da53d4e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -14,8 +14,11 @@ import im.actor.core.api.ApiDocumentExAnimation; import im.actor.core.api.ApiDocumentExVoice; +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiFastThumb; import im.actor.core.api.ApiJsonMessage; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiDocumentEx; @@ -25,6 +28,7 @@ import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiTextMessage; import im.actor.core.api.base.SeqUpdate; +import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; @@ -62,6 +66,7 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -467,29 +472,42 @@ private void performSendContent(final Peer peer, final long rid, AbsContent cont } private void performSendApiContent(final Peer peer, final long rid, ApiMessage message, final WakeLock wakeLock) { - final ApiOutPeer outPeer = buidOutPeer(peer); - final ApiPeer apiPeer = buildApiPeer(peer); - if (outPeer == null || apiPeer == null) { - return; - } - request(new RequestSendMessage(outPeer, rid, message, null, null), - new RpcCallback() { - @Override - public void onResult(ResponseSeqDate response) { - self().send(new MessageSent(peer, rid)); - updates().onUpdateReceived(new SeqUpdate(response.getSeq(), - response.getState(), - UpdateMessageSent.HEADER, - new UpdateMessageSent(apiPeer, rid, response.getDate()).toByteArray())); - wakeLock.releaseLock(); - } + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + final ApiOutPeer outPeer = buidOutPeer(peer); + final ApiPeer apiPeer = buildApiPeer(peer); + request(new RequestSendMessage(outPeer, rid, message, null, null), + new RpcCallback() { + @Override + public void onResult(ResponseSeqDate response) { + self().send(new MessageSent(peer, rid)); + updates().onUpdateReceived(new SeqUpdate(response.getSeq(), + response.getState(), + UpdateMessageSent.HEADER, + new UpdateMessageSent(apiPeer, rid, response.getDate()).toByteArray())); + wakeLock.releaseLock(); + } - @Override - public void onError(RpcException e) { - self().send(new MessageError(peer, rid)); - wakeLock.releaseLock(); - } - }); + @Override + public void onError(RpcException e) { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + } + }); + } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + Log.d("SenderActor", "Pending encrypted message: " + message); + context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { + Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); + // TODO: Implement sending + final ApiOutPeer outPeer = buidOutPeer(peer); + ArrayList peers = new ArrayList<>(); + peers.add(outPeer); + // api(new RequestSendEncryptedPackage(rid, peers,new ArrayList<>(), new ApiEncryptedBox())); + wakeLock.releaseLock(); + }).failure(e -> { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + }); + } } private void onSent(Peer peer, long rid) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java index 90a9637000..97b2afc3e7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java @@ -7,19 +7,16 @@ import java.util.ArrayList; import java.util.List; -import im.actor.core.api.ApiGroup; -import im.actor.core.api.ApiUser; import im.actor.core.api.updates.UpdateMessage; import im.actor.core.api.updates.UpdateMessageRead; import im.actor.core.api.updates.UpdateMessageReadByMe; import im.actor.core.api.updates.UpdateMessageReceived; -import im.actor.core.entity.Group; import im.actor.core.entity.Peer; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.calls.CallsProcessor; import im.actor.core.modules.contacts.ContactsProcessor; -import im.actor.core.modules.encryption.EncryptedProcessor; +import im.actor.core.modules.encryption.EncryptionProcessor; import im.actor.core.modules.eventbus.EventBusProcessor; import im.actor.core.modules.groups.GroupsProcessor; import im.actor.core.modules.presence.PresenceProcessor; @@ -32,13 +29,9 @@ import im.actor.core.modules.users.UsersProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.Predicate; import im.actor.runtime.function.Supplier; -import im.actor.runtime.function.Tuple2; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; -import im.actor.runtime.promise.PromisesArray; public class UpdateProcessor extends AbsModule { @@ -63,7 +56,7 @@ public UpdateProcessor(ModuleContext context) { new UsersProcessor(context), new GroupsProcessor(context), new ContactsProcessor(context), - new EncryptedProcessor(context), + new EncryptionProcessor(context), new StickersProcessor(context), new SettingsProcessor(context) }; From e4aa8ad13eb6c18f5e0eacdccd4eb73e0766291e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:28:13 +0300 Subject: [PATCH 26/81] feat(core+android): Implemented Encrypted Messages sending (not receiving) --- .../controllers/dialogs/view/DialogView.java | 61 +++++------------- .../drawable-xxxhdpi/ic_lock_black_18dp.png | Bin 0 -> 643 bytes .../main/java/im/actor/core/entity/User.java | 5 ++ .../modules/encryption/EncryptionModule.java | 3 +- .../encryption/ratchet/EncryptedMsg.java | 4 +- .../encryption/ratchet/EncryptedMsgActor.java | 6 +- .../messaging/actions/SenderActor.java | 35 ++++++++-- .../modules/messaging/router/RouterActor.java | 2 +- .../modules/messaging/router/RouterInt.java | 5 ++ .../router/entity/RouterOutgoingSent.java | 4 +- 10 files changed, 65 insertions(+), 60 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index dba1098755..9835e64a4e 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -53,7 +53,6 @@ public class DialogView extends ListItemBackgroundView 0) { - String contentText = messenger().getFormatter().formatContentText(arg.getSenderId(), - arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid(), - arg.isChannel()); + arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid()); if (arg.getPeer().getPeerType() == PeerType.GROUP) { if (messenger().getFormatter().isLargeDialogMessage(arg.getMessageType())) { @@ -544,7 +522,6 @@ public static class DialogLayout { private CharSequence shortName; private Layout titleLayout; private Drawable titleIcon; - private int titleIconTop; private String date; private int dateWidth; private Layout textLayout; @@ -552,14 +529,6 @@ public static class DialogLayout { private int counterWidth; private Drawable state; - public int getTitleIconTop() { - return titleIconTop; - } - - public void setTitleIconTop(int titleIconTop) { - this.titleIconTop = titleIconTop; - } - public Drawable getState() { return state; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b12436e8486f6b5ac4e69f432b0a20836e7e1a00 GIT binary patch literal 643 zcmV-}0(||6P)=jPaujIeF`z9AU46mP81(NM6i@-jNmf}f`vBrB4~n1P!a4z!CS2{ z663!(+vr}h$!5*j`G5GDz@Eu`OwP=1)-V``iD=qE9HhZ1cm^fKv-)fsOo29?#PoqR z@Cqtb@&Z;tZ`9??sMS%akw-8cMKLX4w+=DCNvoQXk+UD59E-iENUCP#e1aS}QsmTU zUf1nJq35_w@CqbAx6QRZU=G}P)DYW~`dk`v8LWWTD(bYUUGK}Kaf{sD)?DPw)=)3u zB56h9A@H>UlUa8;Cy6>t#$9?&Yn|e69coWX5|nnBL?VFN^A5GQBndjTnLx>5x2mQ2 zmu>2wND^eXd*uSGe{EC$Ns^$F%><4Dtbc4%|3i`>yB={6VEsdfF(`Ag$+5_*o85+IS)d$?#V`bA1q(V4 zLr_t$ptt|p!X-gL6huK3L_ripK@>zmLIk~o4KSu2OPIt!vO%YIQG)Kkkjq%Z;9gfj z?;dijj2J)vd1)tT!^fDLItm)|G3L0Ag8V!u?xG+H`a{gem=ih*O8OWxrK6ytI_G$0 zRM&03q7H#09&#RZztt$JbI!PG#$BrCqykVA)l$ARlC73 d48t(m encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiMessage message) { return getEncryption().encrypt(uid, message); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index ee0f911348..a2782e49c1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiMessage; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; @@ -24,7 +24,7 @@ public EncryptedMsg(@NotNull ActorRef dest) { * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiMessage message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 3f587d906f..c813b5166a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -24,7 +24,7 @@ public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { + private Promise doEncrypt(int uid, ApiMessage message) throws IOException { Log.d(TAG, "doEncrypt"); return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) @@ -39,7 +39,7 @@ private Promise doEncrypt(int uid, ApiMessage message) thro boxKeys, "aes-kuznechik", encryptBoxResponse.getEncryptedPackage(), new ArrayList<>()); - return new ApiEncryptedMessage(apiEncryptedBox); + return apiEncryptedBox; }); } @@ -114,7 +114,7 @@ public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMess } } - public static class EncryptMessage implements AskMessage { + public static class EncryptMessage implements AskMessage { private int uid; private ApiMessage message; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 4c8da53d4e..0ca025cb3b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -26,10 +26,13 @@ import im.actor.core.api.ApiDocumentExVideo; import im.actor.core.api.ApiDocumentMessage; import im.actor.core.api.ApiOutPeer; +import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiTextMessage; +import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -497,12 +500,32 @@ public void onError(RpcException e) { Log.d("SenderActor", "Pending encrypted message: " + message); context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); - // TODO: Implement sending - final ApiOutPeer outPeer = buidOutPeer(peer); - ArrayList peers = new ArrayList<>(); - peers.add(outPeer); - // api(new RequestSendEncryptedPackage(rid, peers,new ArrayList<>(), new ApiEncryptedBox())); - wakeLock.releaseLock(); + + long accessHash = getUser(peer.getPeerId()).getAccessHash(); + ArrayList peers = new ArrayList<>(); + peers.add(new ApiUserOutPeer(peer.getPeerId(), accessHash)); + RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, peers, + new ArrayList<>(), apiEncryptedMessage); + + api(request).then(response -> { + + self().send(new MessageSent(peer, rid)); + + // TODO: Replace + context().getMessagesModule().getRouter().onOutgoingSent(peer, + rid, response.getDate()); +// updates().onUpdateReceived(new SeqUpdate(response.getSeq(), +// response.getState(), +// UpdateMessageSent.HEADER, +// new UpdateMessageSent(new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()), +// rid, response.getDate()).toByteArray())); + + wakeLock.releaseLock(); + }).failure(e -> { + self().send(new MessageError(peer, rid)); + wakeLock.releaseLock(); + }); + }).failure(e -> { self().send(new MessageError(peer, rid)); wakeLock.releaseLock(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 4b5923c64a..2e7bff3e78 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -783,7 +783,7 @@ private void notifyActiveDialogsVM() { } public boolean isValidPeer(Peer peer) { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { return users().getValue(peer.getPeerId()) != null; } else if (peer.getPeerType() == PeerType.GROUP) { return groups().getValue(peer.getPeerId()) != null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 1bc228aba6..7b0c5f68ae 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -27,6 +27,7 @@ import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; +import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; import im.actor.core.modules.messaging.router.entity.RouterResetChat; import im.actor.core.network.parser.Update; @@ -96,6 +97,10 @@ public Promise onOutgoingError(Peer peer, long rid) { return ask(new RouterOutgoingError(peer, rid)); } + public Promise onOutgoingSent(Peer peer, long rid, long date) { + return ask(new RouterOutgoingSent(peer, rid, date)); + } + public Promise onContentChanged(Peer peer, long rid, AbsContent content) { return ask(new RouterChangedContent(peer, rid, content)); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java index 3a20809a98..884de2c604 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterOutgoingSent.java @@ -1,8 +1,10 @@ package im.actor.core.modules.messaging.router.entity; import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; -public class RouterOutgoingSent implements RouterMessageOnlyActive { +public class RouterOutgoingSent implements AskMessage, RouterMessageOnlyActive { private Peer peer; private long rid; From ab5dd511988d72d9eb281a8f5e053b7544a190df Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:31:12 +0300 Subject: [PATCH 27/81] fix(core): Fixing subscription to online of secret chat --- .../src/main/java/im/actor/core/entity/Peer.java | 7 +++++++ .../java/im/actor/core/modules/presence/PresenceActor.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java index 0a52a7b948..4f5016a7ad 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Peer.java @@ -150,4 +150,11 @@ public String toString() { public String toIdString() { return peerType + "_" + peerId; } + + public Peer toUnencryptedCompat() { + if (peerType == PeerType.PRIVATE_ENCRYPTED) { + return new Peer(PeerType.PRIVATE, peerId); + } + return this; + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java index e2998d9ebf..b5b259f893 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/presence/PresenceActor.java @@ -331,9 +331,9 @@ public void onBusEvent(Event event) { if (event instanceof NewSessionCreated) { self().send(new SessionCreated()); } else if (event instanceof PeerChatOpened) { - self().send(new Subscribe(((PeerChatOpened) event).getPeer())); + self().send(new Subscribe(((PeerChatOpened) event).getPeer().toUnencryptedCompat())); } else if (event instanceof PeerInfoOpened) { - self().send(new Subscribe(((PeerInfoOpened) event).getPeer())); + self().send(new Subscribe(((PeerInfoOpened) event).getPeer().toUnencryptedCompat())); } else if (event instanceof UserVisible) { self().send(new Subscribe(Peer.user(((UserVisible) event).getUid()))); } From 14298cda74e3b6737ab1b193d5919874133699e8 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:52:55 +0300 Subject: [PATCH 28/81] ref(core): Encrypted session code cleanup --- .../encryption/ratchet/EncryptedSession.java | 12 ++++- .../ratchet/EncryptedSessionActor.java | 48 ++++--------------- .../ratchet/EncryptedUserActor.java | 9 ++-- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java index b9979d921d..91223ea308 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -21,7 +21,17 @@ public EncryptedSession(@NotNull ActorRef dest) { * @param data for encryption * @return promise of encrypted package */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedSessionActor.EncryptPackage(data)); } + + /** + * Decrypt data for session + * + * @param data for decryption + * @return promise of decrypted package + */ + public Promise decrypt(byte[] data) { + return ask(new EncryptedSessionActor.DecryptPackage(data)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index c8e3944c7f..b811109d53 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -78,7 +78,7 @@ public void preStart() { keyManager = context().getEncryption().getKeyManager(); } - private Promise onEncrypt(final byte[] data) { + private Promise onEncrypt(final byte[] data) { // // Stage 1: Pick Their Ephemeral key. Use already received or pick random pre key. @@ -99,7 +99,7 @@ private Promise onEncrypt(final byte[] data) { .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); } - private Promise onDecrypt(final byte[] data) { + private Promise onDecrypt(final byte[] data) { // // Stage 1: Parsing message header @@ -139,7 +139,7 @@ private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { return chain; } - private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { + private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { byte[] encrypted; try { @@ -152,7 +152,7 @@ private EncryptedPackageRes encrypt(EncryptedSessionChain chain, byte[] data) { Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); - return new EncryptedPackageRes(encrypted, session.getTheirKeyGroupId()); + return encrypted; } private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { @@ -182,7 +182,7 @@ private Promise pickDecryptChain(final byte[] theirEpheme }); } - private DecryptedPackage decrypt(EncryptedSessionChain chain, byte[] data) { + private byte[] decrypt(EncryptedSessionChain chain, byte[] data) { byte[] decrypted; try { decrypted = chain.decrypt(data); @@ -190,7 +190,7 @@ private DecryptedPackage decrypt(EncryptedSessionChain chain, byte[] data) { e.printStackTrace(); throw new RuntimeException(e); } - return new DecryptedPackage(decrypted); + return decrypted; } // @@ -209,7 +209,7 @@ public Promise onAsk(Object message) throws Exception { } } - public static class EncryptPackage implements AskMessage { + public static class EncryptPackage implements AskMessage { private byte[] data; public EncryptPackage(byte[] data) { @@ -221,26 +221,7 @@ public byte[] getData() { } } - public static class EncryptedPackageRes extends AskResult { - - private byte[] data; - private int keyGroupId; - - public EncryptedPackageRes(byte[] data, int keyGroupId) { - this.data = data; - this.keyGroupId = keyGroupId; - } - - public byte[] getData() { - return data; - } - - public int getKeyGroupId() { - return keyGroupId; - } - } - - public static class DecryptPackage implements AskMessage { + public static class DecryptPackage implements AskMessage { private byte[] data; @@ -252,17 +233,4 @@ public byte[] getData() { return data; } } - - public static class DecryptedPackage extends AskResult { - - private byte[] data; - - public DecryptedPackage(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } - } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index f03b306898..9677878c36 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -105,7 +105,8 @@ private Promise doEncrypt(final byte[] data) { }) .map(src -> spawnSession(src)); }) - .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended)) + .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended) + .map(r -> new Tuple2<>(r, sessionActor.getSession().getTheirKeyGroupId()))) .zip() .map(src -> { @@ -116,9 +117,9 @@ private Promise doEncrypt(final byte[] data) { Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); ArrayList encryptedKeys = new ArrayList<>(); - for (EncryptedSessionActor.EncryptedPackageRes r : src) { - Log.d(TAG, "Keys: " + r.getKeyGroupId()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getKeyGroupId(), "curve25519", r.getData())); + for (Tuple2 r : src) { + Log.d(TAG, "Keys: " + r.getT2()); + encryptedKeys.add(new EncryptedBoxKey(uid, r.getT2(), "curve25519", r.getT1())); } byte[] encData; From 8eb48a736d6645131d7efae523cce40b67bf4d31 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 07:53:52 +0300 Subject: [PATCH 29/81] fix(core): Fixing compilation error --- .../modules/encryption/ratchet/EncryptedUserActor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 9677878c36..ce08deb2af 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -175,7 +175,7 @@ public Tuple2 apply(PeerSession src) { } }); }) - .flatMap((Function, Promise>) src -> { + .flatMap((Function, Promise>) src -> { Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); // TODO: Implement @@ -184,9 +184,9 @@ public Tuple2 apply(PeerSession src) { .map(decryptedPackage -> { byte[] encData; try { - byte[] encKeyExtended = decryptedPackage.getData().length >= 128 - ? decryptedPackage.getData() - : keyPrf.calculate(decryptedPackage.getData(), "ActorPackage", 128); + byte[] encKeyExtended = decryptedPackage.length >= 128 + ? decryptedPackage + : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); Log.d(TAG, "Box size: " + encData.length); } catch (IOException e) { From 9d3332b77f5209cb2fec3ae9cca7268809996f2f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 08:02:25 +0300 Subject: [PATCH 30/81] fix(core): Additional locking in SessionManager to avoid double creation of sessions --- .../ratchet/SessionManagerActor.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 3fca3939c7..2422229f18 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; @@ -37,6 +38,7 @@ public class SessionManagerActor extends ModuleActor { private KeyValueEngine peerSessions; private KeyManager keyManager; + private final HashSet locked = new HashSet<>(); public SessionManagerActor(ModuleContext context) { super(context); @@ -78,6 +80,13 @@ public Promise pickSession(final int uid, if (cached != null) { return Promise.success(cached); } + + if (locked.contains(uid)) { + stash(); + return null; + } + locked.add(uid); + return Promises.tuple( keyManager.getOwnIdentity(), keyManager.getOwnRandomPreKey(), @@ -94,15 +103,16 @@ public Promise pickSession(final int uid, .filter(UserKeysGroup.BY_KEY_GROUP(keyGroupId)) .first(); - spawnSession(uid, + return spawnSession(uid, ownIdentity.getKeyGroup(), keyGroupId, ownIdentity.getIdentityKey(), keysGroup.getIdentityKey(), ownPreKey, theirPreKey); - - return pickCachedSession(uid, keyGroupId); + }).after((r, e) -> { + locked.remove(uid); + unstashAll(); }); } @@ -124,6 +134,12 @@ public Promise pickSession(final int uid, return Promise.success(cached); } + if (locked.contains(uid)) { + stash(); + return null; + } + locked.add(uid); + return Promises.tuple( keyManager.getOwnIdentity(), keyManager.getOwnPreKey(ownKeyId), @@ -146,6 +162,9 @@ public Promise pickSession(final int uid, keysGroup.getIdentityKey(), ownPreKey, theirPreKey); + }).after((r, e) -> { + locked.remove(uid); + unstashAll(); }); } From 5782709fd15eda010ce6de7a5605f63eea325cd2 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 09:06:09 +0300 Subject: [PATCH 31/81] feat(core): Implemented receiving of encrypted messages --- .../modules/encryption/EncryptionModule.java | 4 + .../encryption/EncryptionProcessor.java | 17 +++- .../encryption/ratchet/EncryptedMsg.java | 11 +++ .../encryption/ratchet/EncryptedMsgActor.java | 98 ++++++------------- .../ratchet/EncryptedUserActor.java | 6 +- 5 files changed, 61 insertions(+), 75 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index d0f46b5120..a0489af992 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -65,4 +65,8 @@ public EncryptedUser getEncryptedUser(int uid) { public Promise encrypt(int uid, ApiMessage message) { return getEncryption().encrypt(uid, message); } + + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + return getEncryption().decrypt(uid, encryptedBox); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index d173ba72a5..9395b6657a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -3,6 +3,10 @@ import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; +import im.actor.core.entity.Message; +import im.actor.core.entity.MessageState; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.sequence.processor.SequenceProcessor; @@ -29,8 +33,17 @@ public Promise process(Update update) { .getKeyManager() .onKeyGroupRemoved(groupRemoved.getUid(), groupRemoved.getKeyGroupId()); } else if (update instanceof UpdateEncryptedPackage) { - // TODO: Implement - return Promise.success(null); + UpdateEncryptedPackage encryptedPackage = (UpdateEncryptedPackage) update; + return context().getEncryption() + .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) + .flatMap(message -> { + Message msg = new Message(encryptedPackage.getRandomId(), + encryptedPackage.getDate(), encryptedPackage.getDate(), + encryptedPackage.getSenderId(), MessageState.UNKNOWN, + AbsContent.fromMessage(message)); + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(encryptedPackage.getSenderId()), msg); + }); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index a2782e49c1..8132e2bb44 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -27,4 +27,15 @@ public EncryptedMsg(@NotNull ActorRef dest) { public Promise encrypt(int uid, ApiMessage message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } + + /** + * Decrypt Message from private secret chat + * + * @param uid user's id + * @param encryptedBox encrypted message + * @return promise of decrypted message + */ + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + return ask(new EncryptedMsgActor.DecryptMessage(uid, encryptedBox)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index c813b5166a..eb78f4c289 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -4,15 +4,12 @@ import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.api.ApiMessage; -import im.actor.core.entity.Peer; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; -import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.promise.Promise; @@ -25,95 +22,52 @@ public EncryptedMsgActor(ModuleContext context) { } private Promise doEncrypt(int uid, ApiMessage message) throws IOException { - Log.d(TAG, "doEncrypt"); - return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) .map(encryptBoxResponse -> { - Log.d(TAG, "doEncrypt:onResult"); ArrayList boxKeys = new ArrayList<>(); for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); } - ApiEncryptedBox apiEncryptedBox = new ApiEncryptedBox(0, + return new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", encryptBoxResponse.getEncryptedPackage(), new ArrayList<>()); - return apiEncryptedBox; }); } - public void onDecrypt(int uid, ApiEncryptedMessage message) { - Log.d(TAG, "onDecrypt:" + uid); - final long start = im.actor.runtime.Runtime.getActorTime(); - ArrayList encryptedBoxKeys = new ArrayList(); - for (ApiEncyptedBoxKey key : message.getBox().getKeys()) { + public Promise doDecrypt(int uid, ApiEncryptedBox box) { + ArrayList encryptedBoxKeys = new ArrayList<>(); + for (ApiEncyptedBoxKey key : box.getKeys()) { if (key.getUsersId() == myUid()) { encryptedBoxKeys.add(new EncryptedBoxKey(key.getUsersId(), key.getKeyGroupId(), key.getAlgType(), key.getEncryptedKey())); } } - final EncryptedBox encryptedBox = new EncryptedBox(encryptedBoxKeys.toArray(new EncryptedBoxKey[0]), message.getBox().getEncPackage()); - - // TODO: Implement -// ask(context().getEncryption().getEncryptedChatManager(uid), new EncryptedUserActor.DecryptBox(encryptedBox), new AskCallback() { -// @Override -// public void onResult(Object obj) { -// Log.d(TAG, "onDecrypt:onResult in " + (Runtime.getActorTime() - start) + " ms"); -// EncryptedUserActor.DecryptBoxResponse re = (EncryptedUserActor.DecryptBoxResponse) obj; -// try { -// ApiMessage message = ApiMessage.fromBytes(re.getData()); -// Log.d(TAG, "onDecrypt:onResult " + message); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// -// @Override -// public void onError(Exception e) { -// Log.d(TAG, "onDecrypt:onError"); -// e.printStackTrace(); -// } -// }); + EncryptedBox encryptedBox = new EncryptedBox( + encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), + box.getEncPackage()); + + return context().getEncryption().getEncryptedUser(uid).decrypt(encryptedBox).map(bytes -> { + try { + return ApiMessage.fromBytes(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } @Override public Promise onAsk(Object message) throws Exception { if (message instanceof EncryptMessage) { return doEncrypt(((EncryptMessage) message).getUid(), ((EncryptMessage) message).getMessage()); + } else if (message instanceof DecryptMessage) { + return doDecrypt(((DecryptMessage) message).getUid(), ((DecryptMessage) message).getEncryptedBox()); } else { return super.onAsk(message); } } - @Override - public void onReceive(Object message) { - Log.d(TAG, "msg: " + message); - if (message instanceof InMessage) { - InMessage inMessage = (InMessage) message; - onDecrypt(inMessage.senderUid, inMessage.encryptedMessage); - } else { - super.onReceive(message); - } - } - - public static class InMessage { - - private Peer peer; - private long date; - private int senderUid; - private long rid; - private ApiEncryptedMessage encryptedMessage; - - public InMessage(Peer peer, long date, int senderUid, long rid, ApiEncryptedMessage encryptedMessage) { - this.peer = peer; - this.date = date; - this.senderUid = senderUid; - this.rid = rid; - this.encryptedMessage = encryptedMessage; - } - } - public static class EncryptMessage implements AskMessage { private int uid; @@ -133,16 +87,22 @@ public ApiMessage getMessage() { } } - public static class DecryptMessage { + public static class DecryptMessage implements AskMessage { - private ApiEncryptedMessage encryptedMessage; + private int uid; + private ApiEncryptedBox encryptedBox; - public DecryptMessage(ApiEncryptedMessage encryptedMessage) { - this.encryptedMessage = encryptedMessage; + public DecryptMessage(int uid, ApiEncryptedBox encryptedBox) { + this.uid = uid; + this.encryptedBox = encryptedBox; + } + + public int getUid() { + return uid; } - public ApiEncryptedMessage getEncryptedMessage() { - return encryptedMessage; + public ApiEncryptedBox getEncryptedBox() { + return encryptedBox; } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index ce08deb2af..5fba6f6d66 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -175,11 +175,9 @@ public Tuple2 apply(PeerSession src) { } }); }) - .flatMap((Function, Promise>) src -> { + .flatMap(src -> { Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - // return ask(src.getT1().getActorRef(), new EncryptedSessionActor.DecryptPackage(src.getT2().getEncryptedKey())); - // TODO: Implement - return null; + return src.getT1().getEncryptedSession().decrypt(src.getT2().getEncryptedKey()); }) .map(decryptedPackage -> { byte[] encData; From e576151350ddd78bb6d43ae4ebc2b64152ed705e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 12 Jul 2016 10:47:28 +0300 Subject: [PATCH 32/81] feat(core): Updated encrypted containers --- actor-sdk/sdk-api/actor.json | 270 ++++++++++++++++-- .../languageModels/editor.mps | 9 +- .../models/im/actor/api/scheme.mps | 219 ++++++++++++-- .../conversation/ChatActivity.java | 13 +- .../actor/core/api/ApiEncryptedContent.java | 39 +++ .../api/ApiEncryptedContentUnsupported.java | 42 +++ .../im/actor/core/api/ApiEncryptedData.java | 64 +++++ .../core/api/ApiEncryptedDeleteContent.java | 64 +++++ .../core/api/ApiEncryptedEditContent.java | 78 +++++ .../im/actor/core/api/ApiEncryptedGroup.java | 74 +++++ .../actor/core/api/ApiEncryptedMessage.java | 69 ----- .../core/api/ApiEncryptedMessageContent.java | 78 +++++ .../java/im/actor/core/api/ApiMessage.java | 1 - .../modules/encryption/EncryptionModule.java | 7 +- .../encryption/EncryptionProcessor.java | 36 ++- .../encryption/ratchet/EncryptedMsg.java | 5 +- .../encryption/ratchet/EncryptedMsgActor.java | 63 ++-- .../messaging/actions/SenderActor.java | 12 +- .../notifications/NotificationsActor.java | 2 +- .../core/modules/settings/SettingsModule.java | 2 +- 20 files changed, 977 insertions(+), 170 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index b27bd3cb55..bce97b53f2 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -5239,36 +5239,6 @@ ] } }, - { - "type": "struct", - "content": { - "name": "EncryptedMessage", - "doc": [ - "Encrypted Message", - { - "type": "reference", - "argument": "box", - "category": "full", - "description": " Encrypted box" - } - ], - "trait": { - "name": "Message", - "key": 8 - }, - "expandable": "true", - "attributes": [ - { - "type": { - "type": "struct", - "childType": "EncryptedBox" - }, - "id": 1, - "name": "box" - } - ] - } - }, { "type": "struct", "content": { @@ -19819,6 +19789,246 @@ } ] } + }, + { + "type": "comment", + "content": "Encrypted Content Container" + }, + { + "type": "struct", + "content": { + "name": "EncryptedData", + "doc": [ + "Encrypted Content", + { + "type": "reference", + "argument": "version", + "category": "full", + "description": " Version of data" + }, + { + "type": "reference", + "argument": "data", + "category": "full", + "description": " Data" + } + ], + "attributes": [ + { + "type": "int32", + "id": 1, + "name": "version" + }, + { + "type": "bytes", + "id": 2, + "name": "data" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedGroup", + "doc": [ + "Encrypted Group information", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Random Id" + }, + { + "type": "reference", + "argument": "title", + "category": "full", + "description": " Group Title" + }, + { + "type": "reference", + "argument": "members", + "category": "compact", + "description": " Group Members" + } + ], + "attributes": [ + { + "type": "int64", + "id": 1, + "name": "groupId" + }, + { + "type": "string", + "id": 2, + "name": "title" + }, + { + "type": { + "type": "list", + "childType": "int32" + }, + "id": 3, + "name": "members" + } + ] + } + }, + { + "type": "trait", + "content": { + "isContainer": "true", + "name": "EncryptedContent", + "attributes": [] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedMessageContent", + "doc": [ + "New incoming encrypted message", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + }, + { + "type": "reference", + "argument": "message", + "category": "full", + "description": " Content of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 1 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + }, + { + "type": { + "type": "trait", + "childType": "Message" + }, + "id": 3, + "name": "message" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedEditContent", + "doc": [ + "Encrypted message edit", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + }, + { + "type": "reference", + "argument": "message", + "category": "full", + "description": " Updated content of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 2 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + }, + { + "type": { + "type": "trait", + "childType": "Message" + }, + "id": 3, + "name": "message" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedDeleteContent", + "doc": [ + "Encrypted message delete", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": " Random id of message" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 3 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "rid" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps b/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps index 0a6cb28361..23be5af374 100644 --- a/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps +++ b/actor-sdk/sdk-api/api-language/languages/im.actor.apiLanguage/languageModels/editor.mps @@ -1322,12 +1322,6 @@ - - - - - - @@ -1389,6 +1383,9 @@ + + + diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index dc46e3760b..2093367f86 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -4762,30 +4762,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -16844,6 +16820,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 8c4f2fe3d5..40f0e27244 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -20,6 +20,9 @@ import im.actor.core.entity.Peer; import im.actor.sdk.ActorSDK; + +import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.sdk.R; import im.actor.sdk.controllers.activity.BaseActivity; import im.actor.sdk.util.Screen; @@ -49,6 +52,13 @@ public void onCreate(Bundle saveInstance) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + // Secure Window from screenshoting + Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE); + } + // // Loading Layout // @@ -95,8 +105,7 @@ public void onCreate(Bundle saveInstance) { // if (saveInstance == null) { - Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); - chatFragment = ChatFragment.create(peer); + ChatFragment chatFragment = ChatFragment.create(peer); getSupportFragmentManager().beginTransaction() .add(R.id.chatFragment, chatFragment) .commitNow(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java new file mode 100644 index 0000000000..197fd87258 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -0,0 +1,39 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public abstract class ApiEncryptedContent extends BserObject { + public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { + BserValues values = new BserValues(BserParser.deserialize(new DataInput(src, 0, src.length))); + int key = values.getInt(1); + byte[] content = values.getBytes(2); + switch(key) { + case 1: return Bser.parse(new ApiEncryptedMessageContent(), content); + case 2: return Bser.parse(new ApiEncryptedEditContent(), content); + case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); + default: return new ApiEncryptedContentUnsupported(key, content); + } + } + public abstract int getHeader(); + + public byte[] buildContainer() throws IOException { + DataOutput res = new DataOutput(); + BserWriter writer = new BserWriter(res); + writer.writeInt(1, getHeader()); + writer.writeBytes(2, toByteArray()); + return res.toByteArray(); + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java new file mode 100644 index 0000000000..5c77f0f96e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContentUnsupported.java @@ -0,0 +1,42 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedContentUnsupported extends ApiEncryptedContent { + + private int key; + private byte[] content; + + public ApiEncryptedContentUnsupported(int key, byte[] content) { + this.key = key; + this.content = content; + } + + @Override + public int getHeader() { + return this.key; + } + + @Override + public void parse(BserValues values) throws IOException { + throw new IOException("Parsing is unsupported"); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeRaw(content); + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java new file mode 100644 index 0000000000..8582b87b42 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedData.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedData extends BserObject { + + private int version; + private byte[] data; + + public ApiEncryptedData(int version, @NotNull byte[] data) { + this.version = version; + this.data = data; + } + + public ApiEncryptedData() { + + } + + public int getVersion() { + return this.version; + } + + @NotNull + public byte[] getData() { + return this.data; + } + + @Override + public void parse(BserValues values) throws IOException { + this.version = values.getInt(1); + this.data = values.getBytes(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.version); + if (this.data == null) { + throw new IOException(); + } + writer.writeBytes(2, this.data); + } + + @Override + public String toString() { + String res = "struct EncryptedData{"; + res += "version=" + this.version; + res += ", data=" + byteArrayToString(this.data); + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java new file mode 100644 index 0000000000..1695294074 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedDeleteContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + + public ApiEncryptedDeleteContent(int receiverId, long rid) { + this.receiverId = receiverId; + this.rid = rid; + } + + public ApiEncryptedDeleteContent() { + + } + + public int getHeader() { + return 3; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + } + + @Override + public String toString() { + String res = "struct EncryptedDeleteContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java new file mode 100644 index 0000000000..e5ab3485f0 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedEditContent.java @@ -0,0 +1,78 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedEditContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + private ApiMessage message; + + public ApiEncryptedEditContent(int receiverId, long rid, @NotNull ApiMessage message) { + this.receiverId = receiverId; + this.rid = rid; + this.message = message; + } + + public ApiEncryptedEditContent() { + + } + + public int getHeader() { + return 2; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @NotNull + public ApiMessage getMessage() { + return this.message; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + this.message = ApiMessage.fromBytes(values.getBytes(3)); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + if (this.message == null) { + throw new IOException(); + } + + writer.writeBytes(3, this.message.buildContainer()); + } + + @Override + public String toString() { + String res = "struct EncryptedEditContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += ", message=" + this.message; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java new file mode 100644 index 0000000000..289445b30e --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedGroup.java @@ -0,0 +1,74 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedGroup extends BserObject { + + private long groupId; + private String title; + private List members; + + public ApiEncryptedGroup(long groupId, @NotNull String title, @NotNull List members) { + this.groupId = groupId; + this.title = title; + this.members = members; + } + + public ApiEncryptedGroup() { + + } + + public long getGroupId() { + return this.groupId; + } + + @NotNull + public String getTitle() { + return this.title; + } + + @NotNull + public List getMembers() { + return this.members; + } + + @Override + public void parse(BserValues values) throws IOException { + this.groupId = values.getLong(1); + this.title = values.getString(2); + this.members = values.getRepeatedInt(3); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeLong(1, this.groupId); + if (this.title == null) { + throw new IOException(); + } + writer.writeString(2, this.title); + writer.writeRepeatedInt(3, this.members); + } + + @Override + public String toString() { + String res = "struct EncryptedGroup{"; + res += "groupId=" + this.groupId; + res += ", title=" + this.title; + res += ", members=" + this.members.size(); + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java deleted file mode 100644 index 84ea016c48..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessage.java +++ /dev/null @@ -1,69 +0,0 @@ -package im.actor.core.api; -/* - * Generated by the Actor API Scheme generator. DO NOT EDIT! - */ - -import im.actor.runtime.bser.*; -import im.actor.runtime.collections.*; -import static im.actor.runtime.bser.Utils.*; -import im.actor.core.network.parser.*; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import com.google.j2objc.annotations.ObjectiveCName; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; - -public class ApiEncryptedMessage extends ApiMessage { - - private ApiEncryptedBox box; - - public ApiEncryptedMessage(@NotNull ApiEncryptedBox box) { - this.box = box; - } - - public ApiEncryptedMessage() { - - } - - public int getHeader() { - return 8; - } - - @NotNull - public ApiEncryptedBox getBox() { - return this.box; - } - - @Override - public void parse(BserValues values) throws IOException { - this.box = values.getObj(1, new ApiEncryptedBox()); - if (values.hasRemaining()) { - setUnmappedObjects(values.buildRemaining()); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - if (this.box == null) { - throw new IOException(); - } - writer.writeObject(1, this.box); - if (this.getUnmappedObjects() != null) { - SparseArray unmapped = this.getUnmappedObjects(); - for (int i = 0; i < unmapped.size(); i++) { - int key = unmapped.keyAt(i); - writer.writeUnmapped(key, unmapped.get(key)); - } - } - } - - @Override - public String toString() { - String res = "struct EncryptedMessage{"; - res += "box=" + this.box; - res += "}"; - return res; - } - -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java new file mode 100644 index 0000000000..c10af3cfa7 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java @@ -0,0 +1,78 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedMessageContent extends ApiEncryptedContent { + + private int receiverId; + private long rid; + private ApiMessage message; + + public ApiEncryptedMessageContent(int receiverId, long rid, @NotNull ApiMessage message) { + this.receiverId = receiverId; + this.rid = rid; + this.message = message; + } + + public ApiEncryptedMessageContent() { + + } + + public int getHeader() { + return 1; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @NotNull + public ApiMessage getMessage() { + return this.message; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(2); + this.message = ApiMessage.fromBytes(values.getBytes(3)); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.rid); + if (this.message == null) { + throw new IOException(); + } + + writer.writeBytes(3, this.message.buildContainer()); + } + + @Override + public String toString() { + String res = "struct EncryptedMessageContent{"; + res += "receiverId=" + this.receiverId; + res += ", rid=" + this.rid; + res += ", message=" + this.message; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java index 4782926df9..32720606e1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiMessage.java @@ -27,7 +27,6 @@ public static ApiMessage fromBytes(byte[] src) throws IOException { case 5: return Bser.parse(new ApiUnsupportedMessage(), content); case 6: return Bser.parse(new ApiStickerMessage(), content); case 7: return Bser.parse(new ApiBinaryMessage(), content); - case 8: return Bser.parse(new ApiEncryptedMessage(), content); case 9: return Bser.parse(new ApiEmptyMessage(), content); default: return new ApiMessageUnsupported(key, content); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index a0489af992..d71daecad9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -3,7 +3,7 @@ import java.util.HashMap; import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiMessage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; @@ -13,7 +13,6 @@ import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; -import im.actor.core.network.mtp.entity.EncryptedPackage; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -62,11 +61,11 @@ public EncryptedUser getEncryptedUser(int uid) { } } - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiEncryptedContent message) { return getEncryption().encrypt(uid, message); } - public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return getEncryption().decrypt(uid, encryptedBox); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 9395b6657a..6fad452130 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -1,5 +1,9 @@ package im.actor.core.modules.encryption; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; +import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; @@ -37,14 +41,34 @@ public Promise process(Update update) { return context().getEncryption() .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) .flatMap(message -> { - Message msg = new Message(encryptedPackage.getRandomId(), - encryptedPackage.getDate(), encryptedPackage.getDate(), - encryptedPackage.getSenderId(), MessageState.UNKNOWN, - AbsContent.fromMessage(message)); - return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(encryptedPackage.getSenderId()), msg); + return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); }); } return null; } + + public Promise process(int senderId, long date, ApiEncryptedContent update) { + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(destId), msg); + } else if (update instanceof ApiEncryptedDeleteContent) { + // TODO: Implement + return Promise.success(null); + } else if (update instanceof ApiEncryptedEditContent) { + // TODO: Implement + return Promise.success(null); + } else { + return Promise.success(null); + } + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index 8132e2bb44..e7d0076b4d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull; import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiMessage; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; @@ -24,7 +25,7 @@ public EncryptedMsg(@NotNull ActorRef dest) { * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiMessage message) { + public Promise encrypt(int uid, ApiEncryptedContent message) { return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); } @@ -35,7 +36,7 @@ public Promise encrypt(int uid, ApiMessage message) { * @param encryptedBox encrypted message * @return promise of decrypted message */ - public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { + public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return ask(new EncryptedMsgActor.DecryptMessage(uid, encryptedBox)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index eb78f4c289..2e3b86d06b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -4,30 +4,41 @@ import java.util.ArrayList; import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedContentUnsupported; +import im.actor.core.api.ApiEncryptedData; import im.actor.core.api.ApiEncyptedBoxKey; -import im.actor.core.api.ApiMessage; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.EncryptedBox; import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.bser.Bser; import im.actor.runtime.promise.Promise; public class EncryptedMsgActor extends ModuleActor { + private static final int VERSION = 1; + private static final String TAG = "MessageEncryptionActor"; public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiMessage message) throws IOException { - return context().getEncryption().getEncryptedUser(uid).encrypt(message.buildContainer()) + private Promise doEncrypt(int uid, ApiEncryptedContent message) throws IOException { + + // Building Encrypted Data + ApiEncryptedData data = new ApiEncryptedData(VERSION, message.buildContainer()); + byte[] encData = data.toByteArray(); + + return context().getEncryption().getEncryptedUser(uid) + .encrypt(encData) .map(encryptBoxResponse -> { ArrayList boxKeys = new ArrayList<>(); for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { - boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), - b.getKeyGroupId(), "curve25519", b.getEncryptedKey())); + boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), + "curve25519", b.getEncryptedKey())); } return new ApiEncryptedBox(0, boxKeys, "aes-kuznechik", @@ -36,7 +47,9 @@ private Promise doEncrypt(int uid, ApiMessage message) throws I }); } - public Promise doDecrypt(int uid, ApiEncryptedBox box) { + public Promise doDecrypt(int uid, ApiEncryptedBox box) { + + // Building Encrypted Box ArrayList encryptedBoxKeys = new ArrayList<>(); for (ApiEncyptedBoxKey key : box.getKeys()) { if (key.getUsersId() == myUid()) { @@ -48,13 +61,29 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), box.getEncPackage()); - return context().getEncryption().getEncryptedUser(uid).decrypt(encryptedBox).map(bytes -> { - try { - return ApiMessage.fromBytes(bytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + return context().getEncryption() + .getEncryptedUser(uid) + .decrypt(encryptedBox).map(bytes -> { + ApiEncryptedData encData; + try { + encData = Bser.parse(new ApiEncryptedData(), bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (encData.getVersion() != VERSION) { + throw new RuntimeException("Unsupported version " + encData.getVersion()); + } + ApiEncryptedContent content; + try { + content = ApiEncryptedContent.fromBytes(encData.getData()); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (content instanceof ApiEncryptedContentUnsupported) { + throw new RuntimeException("Unsupported content type #" + content.getHeader()); + } + return content; + }); } @Override @@ -71,9 +100,9 @@ public Promise onAsk(Object message) throws Exception { public static class EncryptMessage implements AskMessage { private int uid; - private ApiMessage message; + private ApiEncryptedContent message; - public EncryptMessage(int uid, ApiMessage message) { + public EncryptMessage(int uid, ApiEncryptedContent message) { this.uid = uid; this.message = message; } @@ -82,12 +111,12 @@ public int getUid() { return uid; } - public ApiMessage getMessage() { + public ApiEncryptedContent getMessage() { return message; } } - public static class DecryptMessage implements AskMessage { + public static class DecryptMessage implements AskMessage { private int uid; private ApiEncryptedBox encryptedBox; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 0ca025cb3b..c1fbf99034 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -14,11 +14,10 @@ import im.actor.core.api.ApiDocumentExAnimation; import im.actor.core.api.ApiDocumentExVoice; -import im.actor.core.api.ApiEncryptedBox; -import im.actor.core.api.ApiEncryptedMessage; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiFastThumb; import im.actor.core.api.ApiJsonMessage; -import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiDocumentEx; @@ -26,13 +25,11 @@ import im.actor.core.api.ApiDocumentExVideo; import im.actor.core.api.ApiDocumentMessage; import im.actor.core.api.ApiOutPeer; -import im.actor.core.api.ApiPeerType; import im.actor.core.api.ApiTextMessage; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; -import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -69,7 +66,6 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; -import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -498,7 +494,9 @@ public void onError(RpcException e) { }); } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { Log.d("SenderActor", "Pending encrypted message: " + message); - context().getEncryption().encrypt(peer.getPeerId(), message).then(apiEncryptedMessage -> { + ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), + rid, message); + context().getEncryption().encrypt(peer.getPeerId(), content).then(apiEncryptedMessage -> { Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); long accessHash = getUser(peer.getPeerId()).getAccessHash(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java index 95af715229..4b78e9cc9d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/notifications/NotificationsActor.java @@ -485,7 +485,7 @@ private boolean isNotificationsEnabled(Peer peer, boolean hasMention) { // All group notifications are disabled return false; } - } else if (peer.getPeerType() == PeerType.PRIVATE) { + } else if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { // For private conversations only check if peer notifications enabled return context().getSettingsModule().isNotificationsEnabled(peer); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java index 5ba278b548..07a7bfbb6b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java @@ -404,7 +404,7 @@ public void onUpdatedSetting(String key, String value) { } private String getChatKey(Peer peer) { - if (peer.getPeerType() == PeerType.PRIVATE) { + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { return "PRIVATE_" + peer.getPeerId(); } else if (peer.getPeerType() == PeerType.GROUP) { return "GROUP_" + peer.getPeerId(); From 3a5a6f587176e85fcd13438884b003c1e7c38b34 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 13:31:07 +0300 Subject: [PATCH 33/81] feat(core): Update encryption scheme to reduce unnecessary duplication, implementing multi-peer message sending --- .../modules/encryption/EncryptionModule.java | 39 ++- .../encryption/entity/EncryptedBox.java | 20 -- .../encryption/entity/EncryptedBoxKey.java | 38 --- .../entity/SessionEphemeralKey.java | 50 --- .../modules/encryption/entity/SessionId.java | 64 ---- .../encryption/entity/SessionStorage.java | 109 ------- .../encryption/entity/UserSessions.java | 46 --- .../encryption/ratchet/EncryptedMsg.java | 25 +- .../encryption/ratchet/EncryptedMsgActor.java | 150 ++++++--- .../encryption/ratchet/EncryptedSession.java | 40 ++- .../ratchet/EncryptedSessionActor.java | 36 ++- .../EncryptedSessionChain.java | 27 +- .../encryption/ratchet/EncryptedUser.java | 28 +- .../ratchet/EncryptedUserActor.java | 302 +++++++++--------- .../encryption/ratchet/KeyManager.java | 8 +- .../encryption/ratchet/KeyManagerActor.java | 50 ++- .../ratchet/SessionManagerActor.java | 13 +- .../ratchet/entity/EncryptedMessage.java | 25 ++ .../ratchet/entity/EncryptedUserKeys.java | 31 ++ .../{ => ratchet}/entity/OwnIdentity.java | 3 +- .../{ => ratchet}/entity/PrivateKey.java | 2 +- .../entity/PrivateKeyStorage.java | 2 +- .../{ => ratchet}/entity/PublicKey.java | 2 +- .../{ => ratchet}/entity/UserKeys.java | 2 +- .../{ => ratchet}/entity/UserKeysGroup.java | 9 +- .../encryption/session/EncryptedSession.java | 53 --- .../session/EncryptedSessionStorage.java | 5 - .../messaging/actions/SenderActor.java | 40 +-- .../actor/runtime/promise/PromisesArray.java | 14 + 29 files changed, 509 insertions(+), 724 deletions(-) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{session => ratchet}/EncryptedSessionChain.java (86%) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/OwnIdentity.java (80%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PrivateKey.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PrivateKeyStorage.java (98%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/PublicKey.java (95%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/UserKeys.java (97%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => ratchet}/entity/UserKeysGroup.java (90%) delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java delete mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index d71daecad9..919e7d0c14 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -1,18 +1,24 @@ package im.actor.core.modules.encryption; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiMessage; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.api.ApiUserOutPeer; +import im.actor.core.api.rpc.RequestSendEncryptedPackage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.ratchet.EncryptedMsg; -import im.actor.core.modules.encryption.ratchet.EncryptedMsgActor; import im.actor.core.modules.encryption.ratchet.EncryptedUser; import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; +import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -30,13 +36,9 @@ public EncryptionModule(ModuleContext context) { } public void run() { - keyManager = new KeyManager(context()); - sessionManager = new SessionManager(context()); - - encryption = new EncryptedMsg(system().actorOf("encryption/messaging", - () -> new EncryptedMsgActor(context()))); + encryption = new EncryptedMsg(context()); } public KeyManager getKeyManager() { @@ -61,11 +63,30 @@ public EncryptedUser getEncryptedUser(int uid) { } } - public Promise encrypt(int uid, ApiEncryptedContent message) { - return getEncryption().encrypt(uid, message); + public Promise encrypt(List uids, ApiEncryptedContent message) { + return getEncryption().encrypt(uids, message); } public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { return getEncryption().decrypt(uid, encryptedBox); } + + public Promise doSend(long rid, ApiEncryptedContent content, List uids) { + + ArrayList outPeers = new ArrayList<>(); + for (int i : uids) { + outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); + } + + return encrypt(uids, content).flatMap(encryptedMessage -> { + RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, + encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); + return api(request).flatMap(r -> { + if (r.getSeq() != null && r.getSeq() != 0) { + return Promise.success(r); + } + return Promise.failure(new RuntimeException("Incorrect keys")); + }); + }); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java deleted file mode 100644 index 1aa49ce492..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBox.java +++ /dev/null @@ -1,20 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -public class EncryptedBox { - - private final EncryptedBoxKey[] keys; - private final byte[] encryptedPackage; - - public EncryptedBox(EncryptedBoxKey[] keys, byte[] encryptedPackage) { - this.keys = keys; - this.encryptedPackage = encryptedPackage; - } - - public EncryptedBoxKey[] getKeys() { - return keys; - } - - public byte[] getEncryptedPackage() { - return encryptedPackage; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java deleted file mode 100644 index e252acd6a8..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/EncryptedBoxKey.java +++ /dev/null @@ -1,38 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import im.actor.runtime.function.Predicate; - -public class EncryptedBoxKey { - - public static Predicate FILTER(final int myUid, final int keyGroupId) { - return boxKey -> boxKey.getUid() == myUid && boxKey.getKeyGroupId() == keyGroupId; - } - - private final int uid; - private final int keyGroupId; - private final byte[] encryptedKey; - private final String keyAlg; - - public EncryptedBoxKey(int uid, int keyGroupId, String keyAlg, byte[] encryptedKey) { - this.uid = uid; - this.keyGroupId = keyGroupId; - this.encryptedKey = encryptedKey; - this.keyAlg = keyAlg; - } - - public String getKeyAlg() { - return keyAlg; - } - - public int getUid() { - return uid; - } - - public int getKeyGroupId() { - return keyGroupId; - } - - public byte[] getEncryptedKey() { - return encryptedKey; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java deleted file mode 100644 index 52b3885345..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionEphemeralKey.java +++ /dev/null @@ -1,50 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; - -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class SessionEphemeralKey extends BserObject { - - private byte[] publicKey; - private byte[] privateKey; - private long dateCreated; - - public SessionEphemeralKey(byte[] publicKey, byte[] privateKey, long dateCreated) { - this.publicKey = publicKey; - this.privateKey = privateKey; - this.dateCreated = dateCreated; - } - - public SessionEphemeralKey(byte[] data) throws IOException { - load(data); - } - - public byte[] getPublicKey() { - return publicKey; - } - - public byte[] getPrivateKey() { - return privateKey; - } - - public long getDateCreated() { - return dateCreated; - } - - @Override - public void parse(BserValues values) throws IOException { - dateCreated = values.getLong(1); - publicKey = values.getBytes(2); - privateKey = values.optBytes(3); - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeLong(1, dateCreated); - writer.writeBytes(2, publicKey); - writer.writeBytes(3, privateKey); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java deleted file mode 100644 index 5d87f7b01c..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionId.java +++ /dev/null @@ -1,64 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -public final class SessionId { - - private int ownKeyGroupId; - private int theirKeyGroupId; - private long ownKeyId0; - private long theirKeyId0; - - public SessionId(int ownKeyGroupId, long ownKeyId0, int theirKeyGroupId, long theirKeyId0) { - this.theirKeyGroupId = theirKeyGroupId; - this.ownKeyId0 = ownKeyId0; - this.theirKeyId0 = theirKeyId0; - this.ownKeyGroupId = ownKeyGroupId; - } - - public int getOwnKeyGroupId() { - return ownKeyGroupId; - } - - public int getTheirKeyGroupId() { - return theirKeyGroupId; - } - - public long getOwnKeyId0() { - return ownKeyId0; - } - - public long getTheirKeyId0() { - return theirKeyId0; - } - - @Override - public String toString() { - return "SessionId{" + - "ownKeyGroupId=" + ownKeyGroupId + - ", theirKeyGroupId=" + theirKeyGroupId + - ", ownKeyId0=" + ownKeyId0 + - ", theirKeyId0=" + theirKeyId0 + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SessionId sessionId = (SessionId) o; - - if (ownKeyGroupId != sessionId.ownKeyGroupId) return false; - if (theirKeyGroupId != sessionId.theirKeyGroupId) return false; - if (ownKeyId0 != sessionId.ownKeyId0) return false; - return theirKeyId0 == sessionId.theirKeyId0; - } - - @Override - public int hashCode() { - int result = ownKeyGroupId; - result = 31 * result + theirKeyGroupId; - result = 31 * result + (int) (ownKeyId0 ^ (ownKeyId0 >>> 32)); - result = 31 * result + (int) (theirKeyId0 ^ (theirKeyId0 >>> 32)); - return result; - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java deleted file mode 100644 index ba5ed286b9..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/SessionStorage.java +++ /dev/null @@ -1,109 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class SessionStorage extends BserObject { - - private long sid; - private int uid; - private int ownKeyGroupId; - private int theirKeyGroupId; - private long ownPreKeyId; - private long theirPreKeyId; - - private ArrayList theirKeys; - private ArrayList ownKeys; - - public SessionStorage(long sid, int uid, - int theirKeyGroupId, - int ownKeyGroupId, - long ownPreKeyId, - long theirPreKeyId, - ArrayList theirKeys, - ArrayList ownKeys) { - this.sid = sid; - this.uid = uid; - this.theirKeyGroupId = theirKeyGroupId; - this.ownKeyGroupId = ownKeyGroupId; - this.ownPreKeyId = ownPreKeyId; - this.theirPreKeyId = theirPreKeyId; - this.theirKeys = new ArrayList<>(theirKeys); - this.ownKeys = new ArrayList<>(ownKeys); - } - - public SessionStorage(byte[] data) throws IOException { - load(data); - } - - public long getSid() { - return sid; - } - - public int getUid() { - return uid; - } - - public int getOwnKeyGroupId() { - return ownKeyGroupId; - } - - public int getTheirKeyGroupId() { - return theirKeyGroupId; - } - - public long getOwnPreKeyId() { - return ownPreKeyId; - } - - public long getTheirPreKeyId() { - return theirPreKeyId; - } - - public ArrayList getTheirKeys() { - return theirKeys; - } - - public ArrayList getOwnKeys() { - return ownKeys; - } - - @Override - public void parse(BserValues values) throws IOException { - sid = values.getLong(1); - uid = values.getInt(2); - ownKeyGroupId = values.getInt(3); - theirKeyGroupId = values.getInt(4); - ownPreKeyId = values.getLong(5); - theirPreKeyId = values.getLong(6); - - theirKeys = new ArrayList<>(); - List theirEphemeral = values.getRepeatedBytes(7); - for (byte[] b : theirEphemeral) { - theirKeys.add(new SessionEphemeralKey(b)); - } - - ownKeys = new ArrayList<>(); - List ownEphemeral = values.getRepeatedBytes(8); - for (byte[] b : ownEphemeral) { - theirKeys.add(new SessionEphemeralKey(b)); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeLong(1, sid); - writer.writeInt(2, uid); - writer.writeInt(3, ownKeyGroupId); - writer.writeInt(4, theirKeyGroupId); - writer.writeLong(5, ownPreKeyId); - writer.writeLong(6, theirPreKeyId); - writer.writeRepeatedObj(7, theirKeys); - writer.writeRepeatedObj(8, ownKeys); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java deleted file mode 100644 index 73772808db..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserSessions.java +++ /dev/null @@ -1,46 +0,0 @@ -package im.actor.core.modules.encryption.entity; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import im.actor.core.entity.encryption.PeerSession; -import im.actor.runtime.bser.BserObject; -import im.actor.runtime.bser.BserValues; -import im.actor.runtime.bser.BserWriter; - -public class UserSessions extends BserObject { - - private int uid; - private ArrayList sessionDescriptors; - - public UserSessions(int uid, ArrayList sessionDescriptors) { - this.uid = uid; - this.sessionDescriptors = sessionDescriptors; - } - - public int getUid() { - return uid; - } - - public ArrayList getSessionDescriptors() { - return sessionDescriptors; - } - - @Override - public void parse(BserValues values) throws IOException { - uid = values.getInt(1); - - sessionDescriptors = new ArrayList<>(); - List desc = values.getRepeatedBytes(2); - for (byte[] b : desc) { - sessionDescriptors.add(new PeerSession(b)); - } - } - - @Override - public void serialize(BserWriter writer) throws IOException { - writer.writeInt(1, uid); - writer.writeRepeatedObj(2, sessionDescriptors); - } -} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java index e7d0076b4d..b1ddb821e9 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsg.java @@ -1,32 +1,39 @@ package im.actor.core.modules.encryption.ratchet; -import org.jetbrains.annotations.NotNull; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiMessage; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + /** * Entry point for message encryption */ public class EncryptedMsg extends ActorInterface { - public EncryptedMsg(@NotNull ActorRef dest) { - super(dest); + /** + * Constructor of encrypted messaging interface + * + * @param context context + */ + public EncryptedMsg(ModuleContext context) { + super(system().actorOf("encryption/messaging", () -> new EncryptedMsgActor(context))); } /** - * Encrypt Message for private secret chat + * Encrypt Message for secret chats * - * @param uid user's id + * @param uids User's ids. Add own UID for sending to other devices * @param message message content * @return promise of encrypted message */ - public Promise encrypt(int uid, ApiEncryptedContent message) { - return ask(new EncryptedMsgActor.EncryptMessage(uid, message)); + public Promise encrypt(List uids, ApiEncryptedContent message) { + return ask(new EncryptedMsgActor.EncryptMessage(message, uids)); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 2e3b86d06b..753145c1ac 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -2,19 +2,29 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedContentUnsupported; import im.actor.core.api.ApiEncryptedData; import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.runtime.Crypto; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.bser.Bser; +import im.actor.runtime.crypto.Cryptos; +import im.actor.runtime.crypto.IntegrityException; +import im.actor.runtime.crypto.box.ActorBox; +import im.actor.runtime.crypto.box.ActorBoxKey; +import im.actor.runtime.crypto.primitives.prf.PRF; +import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.promise.Promise; +import im.actor.runtime.promise.PromisesArray; public class EncryptedMsgActor extends ModuleActor { @@ -22,66 +32,104 @@ public class EncryptedMsgActor extends ModuleActor { private static final String TAG = "MessageEncryptionActor"; + private final PRF KEY_PRF = Cryptos.PRF_SHA_STREEBOG_256(); + + private boolean isFreezed = false; + private OwnIdentity ownIdentity; + public EncryptedMsgActor(ModuleContext context) { super(context); } - private Promise doEncrypt(int uid, ApiEncryptedContent message) throws IOException { + @Override + public void preStart() { + super.preStart(); + + context().getEncryption().getKeyManager().getOwnIdentity().then(id -> { + ownIdentity = id; + isFreezed = false; + unstashAll(); + }); + } + + private Promise doEncrypt(ApiEncryptedContent message, List uids) throws IOException { + + // Generate Encryption Keys + byte[] encKey = Crypto.randomBytes(32); + byte[] encKeyExtended = KEY_PRF.calculate(encKey, "ActorPackage", 128); + + // Encrypt Data + byte[] encryptedData; + byte[] dataToEncrypt = message.toByteArray(); + byte[] dataHeader = ByteStrings.merge(new byte[]{VERSION}, + ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(ownIdentity.getKeyGroup())); + try { + encryptedData = ActorBox.closeBox(dataHeader, dataToEncrypt, Crypto.randomBytes(32), + new ActorBoxKey(encKeyExtended)); + } catch (IntegrityException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } - // Building Encrypted Data - ApiEncryptedData data = new ApiEncryptedData(VERSION, message.buildContainer()); - byte[] encData = data.toByteArray(); + // Put To Plain-Text versioned container + byte[] dataContainer = new ApiEncryptedData(VERSION, encryptedData).toByteArray(); - return context().getEncryption().getEncryptedUser(uid) - .encrypt(encData) - .map(encryptBoxResponse -> { + // Encryption for all required users + return PromisesArray.of(uids) + .map((u) -> Promise.success(context().getEncryption().getEncryptedUser(u))) + .map((u) -> u.encrypt(encKeyExtended)) + .zip((r) -> { ArrayList boxKeys = new ArrayList<>(); - for (EncryptedBoxKey b : encryptBoxResponse.getKeys()) { - boxKeys.add(new ApiEncyptedBoxKey(b.getUid(), b.getKeyGroupId(), - "curve25519", b.getEncryptedKey())); + ArrayList ignored = new ArrayList<>(); + for (EncryptedUserKeys uk : r) { + boxKeys.addAll(uk.getBoxKeys()); + for (Integer i : uk.getIgnoredKeys()) { + ignored.add(new ApiKeyGroupId(uk.getUid(), i)); + } } - return new ApiEncryptedBox(0, - boxKeys, "aes-kuznechik", - encryptBoxResponse.getEncryptedPackage(), - new ArrayList<>()); + + return new EncryptedMessage(new ApiEncryptedBox(ownIdentity.getKeyGroup(), + boxKeys, "aes-kuznechik", dataContainer, new ArrayList<>()), ignored); }); } public Promise doDecrypt(int uid, ApiEncryptedBox box) { - // Building Encrypted Box - ArrayList encryptedBoxKeys = new ArrayList<>(); - for (ApiEncyptedBoxKey key : box.getKeys()) { - if (key.getUsersId() == myUid()) { - encryptedBoxKeys.add(new EncryptedBoxKey(key.getUsersId(), key.getKeyGroupId(), - key.getAlgType(), key.getEncryptedKey())); - } + // Loading Package + ApiEncryptedData encData; + try { + encData = Bser.parse(new ApiEncryptedData(), box.getEncPackage()); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (encData.getVersion() != VERSION) { + throw new RuntimeException("Unsupported version " + encData.getVersion()); } - EncryptedBox encryptedBox = new EncryptedBox( - encryptedBoxKeys.toArray(new EncryptedBoxKey[encryptedBoxKeys.size()]), - box.getEncPackage()); return context().getEncryption() .getEncryptedUser(uid) - .decrypt(encryptedBox).map(bytes -> { - ApiEncryptedData encData; + .decrypt(box.getSenderKeyGroupId(), box.getKeys()).map(bytes -> { + + // Decryption of package + byte[] dataHeader = ByteStrings.merge( + new byte[]{VERSION}, + ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(box.getSenderKeyGroupId())); + byte[] data; try { - encData = Bser.parse(new ApiEncryptedData(), bytes); - } catch (IOException e) { + data = ActorBox.openBox(dataHeader, encData.getData(), new ActorBoxKey(bytes)); + } catch (IntegrityException e) { throw new RuntimeException(e); } - if (encData.getVersion() != VERSION) { - throw new RuntimeException("Unsupported version " + encData.getVersion()); - } + + // Parsing content ApiEncryptedContent content; try { - content = ApiEncryptedContent.fromBytes(encData.getData()); + content = ApiEncryptedContent.fromBytes(data); } catch (IOException e) { throw new RuntimeException(e); } - if (content instanceof ApiEncryptedContentUnsupported) { - throw new RuntimeException("Unsupported content type #" + content.getHeader()); - } return content; }); } @@ -89,31 +137,39 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { @Override public Promise onAsk(Object message) throws Exception { if (message instanceof EncryptMessage) { - return doEncrypt(((EncryptMessage) message).getUid(), ((EncryptMessage) message).getMessage()); + if (isFreezed) { + stash(); + return null; + } + return doEncrypt(((EncryptMessage) message).getMessage(), ((EncryptMessage) message).getUids()); } else if (message instanceof DecryptMessage) { + if (isFreezed) { + stash(); + return null; + } return doDecrypt(((DecryptMessage) message).getUid(), ((DecryptMessage) message).getEncryptedBox()); } else { return super.onAsk(message); } } - public static class EncryptMessage implements AskMessage { + public static class EncryptMessage implements AskMessage { - private int uid; private ApiEncryptedContent message; + private List uids; - public EncryptMessage(int uid, ApiEncryptedContent message) { - this.uid = uid; + public EncryptMessage(ApiEncryptedContent message, List uids) { + this.uids = uids; this.message = message; } - public int getUid() { - return uid; - } - public ApiEncryptedContent getMessage() { return message; } + + public List getUids() { + return uids; + } } public static class DecryptMessage implements AskMessage { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java index 91223ea308..783c0a413c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -1,18 +1,40 @@ package im.actor.core.modules.encryption.ratchet; -import org.jetbrains.annotations.NotNull; - +import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.entity.encryption.PeerSession; +import im.actor.core.modules.ModuleContext; +import im.actor.core.util.RandomUtils; import im.actor.runtime.actors.ActorInterface; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; +import static im.actor.runtime.actors.ActorSystem.system; + /** * Double Ratchet encrypted session operations */ public class EncryptedSession extends ActorInterface { - public EncryptedSession(@NotNull ActorRef dest) { - super(dest); + private PeerSession session; + + /** + * Constructor of session encryption + * + * @param session session settings + * @param context context + */ + public EncryptedSession(PeerSession session, ModuleContext context) { + super(system().actorOf("encryption/uid_" + session.getUid() + "/session_" + + RandomUtils.nextRid(), () -> new EncryptedSessionActor(context, session))); + this.session = session; + } + + /** + * Get Peer Session parameters + * + * @return peer session + */ + public PeerSession getSession() { + return session; } /** @@ -21,17 +43,17 @@ public EncryptedSession(@NotNull ActorRef dest) { * @param data for encryption * @return promise of encrypted package */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedSessionActor.EncryptPackage(data)); } /** * Decrypt data for session * - * @param data for decryption + * @param key for decryption * @return promise of decrypted package */ - public Promise decrypt(byte[] data) { - return ask(new EncryptedSessionActor.DecryptPackage(data)); + public Promise decrypt(ApiEncyptedBoxKey key) { + return ask(new EncryptedSessionActor.DecryptPackage(key)); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index b811109d53..9348515891 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -2,14 +2,13 @@ import java.util.ArrayList; +import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.session.EncryptedSessionChain; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; import im.actor.core.modules.ModuleActor; import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; @@ -29,7 +28,7 @@ * During actor starting it downloads all required key from Key Manager. * To encrypt/decrypt messages this actor spawns encryption chains. */ -public class EncryptedSessionActor extends ModuleActor { +class EncryptedSessionActor extends ModuleActor { private final String TAG; @@ -78,7 +77,7 @@ public void preStart() { keyManager = context().getEncryption().getKeyManager(); } - private Promise onEncrypt(final byte[] data) { + private Promise onEncrypt(final byte[] data) { // // Stage 1: Pick Their Ephemeral key. Use already received or pick random pre key. @@ -96,10 +95,12 @@ private Promise onEncrypt(final byte[] data) { return ephemeralKey .map(publicKey -> pickEncryptChain(publicKey)) - .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)); + .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)) + .map(bytes -> new ApiEncyptedBoxKey(session.getUid(), session.getTheirKeyGroupId(), + "curve25519", bytes)); } - private Promise onDecrypt(final byte[] data) { + private Promise onDecrypt(ApiEncyptedBoxKey data) { // // Stage 1: Parsing message header @@ -108,16 +109,17 @@ private Promise onDecrypt(final byte[] data) { // Stage 4: Saving their ephemeral key // - // final int ownKeyGroupId = ByteStrings.bytesToInt(data, 0); - // final long ownEphemeralKey0Id = ByteStrings.bytesToLong(data, 4); - // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 12); - final byte[] senderEphemeralKey = ByteStrings.substring(data, 20, 32); - final byte[] receiverEphemeralKey = ByteStrings.substring(data, 52, 32); + byte[] material = data.getEncryptedKey(); + + // final long ownEphemeralKey0Id = ByteStrings.bytesToLong(data, 0); + // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 8); + final byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); + final byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(encryptedSessionChain -> decrypt(encryptedSessionChain, data)) + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)) .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } @@ -209,7 +211,7 @@ public Promise onAsk(Object message) throws Exception { } } - public static class EncryptPackage implements AskMessage { + public static class EncryptPackage implements AskMessage { private byte[] data; public EncryptPackage(byte[] data) { @@ -223,13 +225,13 @@ public byte[] getData() { public static class DecryptPackage implements AskMessage { - private byte[] data; + private ApiEncyptedBoxKey data; - public DecryptPackage(byte[] data) { + public DecryptPackage(ApiEncyptedBoxKey data) { this.data = data; } - public byte[] getData() { + public ApiEncyptedBoxKey getData() { return data; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java similarity index 86% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java index 738a0502ba..e052fddc80 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionChain.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java @@ -1,12 +1,8 @@ -package im.actor.core.modules.encryption.session; - -import java.util.HashSet; -import java.util.Random; +package im.actor.core.modules.encryption.ratchet; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; -import im.actor.runtime.Log; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; import im.actor.runtime.crypto.box.ActorBox; @@ -22,7 +18,6 @@ public class EncryptedSessionChain { private PeerSession session; private byte[] ownPrivateKey; private byte[] theirPublicKey; - private HashSet receivedCounters; private int sentCounter; private byte[] rootChainKey; @@ -30,7 +25,6 @@ public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] t this.session = session; this.ownPrivateKey = ownPrivateKey; this.theirPublicKey = theirPublicKey; - this.receivedCounters = new HashSet<>(); this.sentCounter = 0; this.rootChainKey = RatchetRootChainKey.makeRootChainKey( new RatchetPrivateKey(ownPrivateKey), @@ -52,7 +46,7 @@ public byte[] getTheirPublicKey() { public byte[] decrypt(byte[] data) throws IntegrityException { - if (data.length < 88) { + if (data.length < 80) { throw new IntegrityException("Data length is too small"); } @@ -60,12 +54,11 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // Parsing message header // - final int senderKeyGroupId = ByteStrings.bytesToInt(data, 0); - final long senderEphermalKey0Id = ByteStrings.bytesToLong(data, 4); - final long receiverEphermalKey0Id = ByteStrings.bytesToLong(data, 12); - final byte[] senderEphemeralKey = ByteStrings.substring(data, 20, 32); - final byte[] receiverEphemeralKey = ByteStrings.substring(data, 52, 32); - final int messageIndex = ByteStrings.bytesToInt(data, 84); + final long senderEphermalKey0Id = ByteStrings.bytesToLong(data, 0); + final long receiverEphermalKey0Id = ByteStrings.bytesToLong(data, 8); + final byte[] senderEphemeralKey = ByteStrings.substring(data, 16, 32); + final byte[] receiverEphemeralKey = ByteStrings.substring(data, 48, 32); + final int messageIndex = ByteStrings.bytesToInt(data, 80); // // Validating header @@ -92,8 +85,8 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); - byte[] header = ByteStrings.substring(data, 0, 88); - byte[] message = ByteStrings.substring(data, 88, data.length - 88); + byte[] header = ByteStrings.substring(data, 0, 80); + byte[] message = ByteStrings.substring(data, 80, data.length - 80); return ActorBox.openBox(header, message, ratchetMessageKey); } @@ -102,7 +95,6 @@ public byte[] encrypt(byte[] data) throws IntegrityException { ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); byte[] header = ByteStrings.merge( - ByteStrings.intToBytes(session.getOwnKeyGroupId()), ByteStrings.longToBytes(session.getOwnPreKeyId()), /*Alice Initial Ephermal*/ ByteStrings.longToBytes(session.getTheirPreKeyId()), /*Bob Initial Ephermal*/ Curve25519.keyGenPublic(ownPrivateKey), @@ -125,7 +117,6 @@ public void safeErase() { for (int i = 0; i < rootChainKey.length; i++) { rootChainKey[i] = (byte) RandomUtils.randomId(255); } - receivedCounters.clear(); sentCounter = 0; } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java index 16294d582e..8cbf6f4cbd 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUser.java @@ -2,14 +2,17 @@ import org.jetbrains.annotations.NotNull; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.UserKeys; +import java.util.List; + +import im.actor.core.api.ApiEncyptedBoxKey; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.promise.Promise; /** - * Encrypting data for private Secret Chats + * Encrypting shared key for private Secret Chats */ public class EncryptedUser extends ActorInterface { @@ -18,23 +21,24 @@ public EncryptedUser(@NotNull ActorRef dest) { } /** - * Encrypting data + * Encrypting shared key * - * @param data data for encryption - * @return promise of encrypted box + * @param data shared key for encryption + * @return promise of list of encrypted shared key */ - public Promise encrypt(byte[] data) { + public Promise encrypt(byte[] data) { return ask(new EncryptedUserActor.EncryptBox(data)); } /** - * Decrypting data + * Decrypting shared key * - * @param data data for decryption - * @return promise of decrypted box + * @param senderKeyGroupId sender's key group id + * @param keys list of encrypted box keys + * @return promise of shared key */ - public Promise decrypt(EncryptedBox data) { - return ask(new EncryptedUserActor.DecryptBox(data)); + public Promise decrypt(int senderKeyGroupId, List keys) { + return ask(new EncryptedUserActor.DecryptBox(senderKeyGroupId, keys)); } /** diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 5fba6f6d66..868976c213 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -1,33 +1,22 @@ package im.actor.core.modules.encryption.ratchet; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.EncryptedBox; -import im.actor.core.modules.encryption.entity.EncryptedBoxKey; -import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.EncryptedUserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.core.modules.ModuleActor; -import im.actor.core.util.RandomUtils; import im.actor.runtime.*; -import im.actor.runtime.Runtime; -import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ask.AskMessage; -import im.actor.runtime.actors.ask.AskResult; -import im.actor.runtime.crypto.Cryptos; -import im.actor.runtime.crypto.IntegrityException; -import im.actor.runtime.crypto.primitives.prf.PRF; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; -import im.actor.runtime.crypto.box.ActorBox; -import im.actor.runtime.crypto.box.ActorBoxKey; import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.promise.Promises; import im.actor.runtime.promise.PromisesArray; -import im.actor.runtime.function.Tuple2; import static im.actor.runtime.promise.Promise.success; @@ -36,20 +25,20 @@ public class EncryptedUserActor extends ModuleActor { private final String TAG; private final int uid; + private final boolean isOwnUser; private int ownKeyGroupId; private UserKeys theirKeys; - private HashMap activeSessions = new HashMap<>(); + private HashMap activeSessions = new HashMap<>(); private HashSet ignoredKeyGroups = new HashSet<>(); - private boolean isReady = false; - - private final PRF keyPrf = Cryptos.PRF_SHA_STREEBOG_256(); + private boolean isFreezed = true; public EncryptedUserActor(int uid, ModuleContext context) { super(context); this.uid = uid; + this.isOwnUser = myUid() == uid; TAG = "EncryptedUserActor#" + uid; } @@ -63,9 +52,14 @@ public void preStart() { keyManager.getOwnIdentity(), keyManager.getUserKeyGroups(uid)) .then(res -> { + Log.d(TAG, "Loaded initial parameters"); ownKeyGroupId = res.getT1().getKeyGroup(); theirKeys = res.getT2(); - onLoaded(); + if (isOwnUser) { + ignoredKeyGroups.add(ownKeyGroupId); + } + isFreezed = false; + unstashAll(); }) .failure(e -> { Log.w(TAG, "Unable to fetch initial parameters. Freezing encryption with user #" + uid); @@ -73,164 +67,164 @@ public void preStart() { }); } - private void onLoaded() { - Log.d(TAG, "Loaded initial parameters"); - isReady = true; - unstashAll(); - } + private Promise doEncrypt(byte[] data) { - private Promise doEncrypt(final byte[] data) { + // Stage 1: Loading User Key Groups + return wrap(PromisesArray.of(theirKeys.getUserKeysGroups()) + // Stage 1.1: Filtering invalid key groups and own key groups + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) && + (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) - // - // Stage 1: Loading User Key Groups - // Stage 2: Pick sessions for encryption - // Stage 3: Encrypt box_key int session - // Stage 4: Encrypt box - // - final byte[] encKey = Crypto.randomBytes(32); - final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "doEncrypt"); - final long start = Runtime.getActorTime(); - return PromisesArray.of(theirKeys.getUserKeysGroups()) - .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId())) - .mapOptional(keysGroup -> { + // Stage 2: Pick sessions for encryption + .map(keysGroup -> { if (activeSessions.containsKey(keysGroup.getKeyGroupId())) { - return success(activeSessions.get(keysGroup.getKeyGroupId()).getSessions().get(0)); + return success(activeSessions.get(keysGroup.getKeyGroupId()).first()); } - return context().getEncryption().getSessionManager() + return getSessionManager() .pickSession(uid, keysGroup.getKeyGroupId()) + .map(src -> spawnSession(src)) .failure(e -> { ignoredKeyGroups.add(keysGroup.getKeyGroupId()); - }) - .map(src -> spawnSession(src)); + }); }) - .mapOptional(sessionActor -> sessionActor.getEncryptedSession().encrypt(encKeyExtended) - .map(r -> new Tuple2<>(r, sessionActor.getSession().getTheirKeyGroupId()))) - .zip() - .map(src -> { + .filterFailed() - if (src.size() == 0) { - throw new RuntimeException("No sessions available"); - } + // Stage 3: Encrypt box_keys + .map(s -> s.encrypt(data)) + .filterFailed() - Log.d(TAG, "Keys Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + // Stage 4: Zip Everything together + .zip(src -> new EncryptedUserKeys(uid, src, new HashSet<>(ignoredKeyGroups)))); - ArrayList encryptedKeys = new ArrayList<>(); - for (Tuple2 r : src) { - Log.d(TAG, "Keys: " + r.getT2()); - encryptedKeys.add(new EncryptedBoxKey(uid, r.getT2(), "curve25519", r.getT1())); - } - byte[] encData; - try { - encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); - } catch (IntegrityException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } +// final byte[] encKey = Crypto.randomBytes(32); +// final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + // byte[] encData; +// try { +// encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); +// } catch (IntegrityException e) { +// e.printStackTrace(); +// throw new RuntimeException(e); +// } + +// Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); + +// return new EncryptedUserKeys( +// encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), +// ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - return new EncryptedBox( - encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), - ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - }); } - private Promise doDecrypt(final EncryptedBox data) { + private Promise doDecrypt(int senderKeyGroupId, List keys) { - final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); - final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); +// final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); +// final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); // - // Picking session + // Picking key // - - if (ignoredKeyGroups.contains(senderKeyGroup)) { + if (ignoredKeyGroups.contains(senderKeyGroupId)) { throw new RuntimeException("This key group is ignored"); } + ApiEncyptedBoxKey key = null; + for (ApiEncyptedBoxKey boxKey : keys) { + if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == uid) { + key = boxKey; + break; + } + } + if (key == null) { + throw new RuntimeException("Unable to find suitable key group's key"); + } + final ApiEncyptedBoxKey finalKey = key; - return PromisesArray.of(data.getKeys()) - .filter(EncryptedBoxKey.FILTER(myUid(), ownKeyGroupId)) - .first() - .flatMap(boxKey -> { - final long senderPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 4); - final long receiverPreKeyId = ByteStrings.bytesToLong(boxKey.getEncryptedKey(), 12); - - if (activeSessions.containsKey(boxKey.getKeyGroupId())) { - for (SessionActor s : activeSessions.get(senderKeyGroup).getSessions()) { - if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && - s.getSession().getTheirPreKeyId() == senderPreKeyId) { - return success(new Tuple2<>(s, boxKey)); - } - } - } - return context().getEncryption().getSessionManager() - .pickSession(uid, senderKeyGroup, receiverPreKeyId, senderPreKeyId) - .map(new Function>() { - @Override - public Tuple2 apply(PeerSession src) { - return new Tuple2<>(spawnSession(src), boxKey); - } - }); - }) - .flatMap(src -> { - Log.d(TAG, "Key size:" + src.getT2().getEncryptedKey().length); - return src.getT1().getEncryptedSession().decrypt(src.getT2().getEncryptedKey()); - }) - .map(decryptedPackage -> { - byte[] encData; - try { - byte[] encKeyExtended = decryptedPackage.length >= 128 - ? decryptedPackage - : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); - encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroup), encPackage, new ActorBoxKey(encKeyExtended)); - Log.d(TAG, "Box size: " + encData.length); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - return encData; - }); + // + // Decryption + // + long senderPreKeyId = ByteStrings.bytesToLong(key.getEncryptedKey(), 0); + long receiverPreKeyId = ByteStrings.bytesToLong(key.getEncryptedKey(), 8); + if (activeSessions.containsKey(key.getKeyGroupId())) { + for (EncryptedSession s : activeSessions.get(senderKeyGroupId).getSessions()) { + if (s.getSession().getOwnPreKeyId() == receiverPreKeyId && + s.getSession().getTheirPreKeyId() == senderPreKeyId) { + return wrap(s.decrypt(key)); + } + } + } + return wrap(getSessionManager() + .pickSession(uid, senderKeyGroupId, receiverPreKeyId, senderPreKeyId) + .flatMap(src -> spawnSession(src).decrypt(finalKey))); + +// .map(decryptedPackage -> { +// byte[] encData; +// try { +// byte[] encKeyExtended = decryptedPackage.length >= 128 +// ? decryptedPackage +// : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); +// encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroupId), encPackage, new ActorBoxKey(encKeyExtended)); +// Log.d(TAG, "Box size: " + encData.length); +// } catch (IOException e) { +// e.printStackTrace(); +// throw new RuntimeException(e); +// } +// return encData; +// }); } private void onKeysUpdated(UserKeys userKeys) { this.theirKeys = userKeys; } - private SessionActor spawnSession(final PeerSession session) { - - ActorRef res = system().actorOf(getPath() + "/k_" + RandomUtils.nextRid(), - () -> new EncryptedSessionActor(context(), session)); - SessionActor cont = new SessionActor(new EncryptedSession(res), session); + // + // Tools + // - if (activeSessions.containsKey(session.getTheirKeyGroupId())) { - activeSessions.get(session.getTheirKeyGroupId()).getSessions().add(cont); + private EncryptedSession spawnSession(PeerSession peerSession) { + EncryptedSession session = new EncryptedSession(peerSession, context()); + if (activeSessions.containsKey(peerSession.getTheirKeyGroupId())) { + activeSessions.get(peerSession.getTheirKeyGroupId()).getSessions().add(session); } else { - ArrayList l = new ArrayList<>(); - l.add(cont); - activeSessions.put(session.getTheirKeyGroupId(), new SessionHolder(session.getTheirKeyGroupId(), l)); + ArrayList l = new ArrayList<>(); + l.add(session); + activeSessions.put(peerSession.getTheirKeyGroupId(), new KeyGroupHolder(peerSession.getTheirKeyGroupId(), l)); } - return cont; + return session; + } + + private Promise wrap(Promise p) { + isFreezed = true; + p.after((r, e) -> { + isFreezed = false; + unstashAll(); + }); + return p; + } + + private SessionManager getSessionManager() { + return context().getEncryption().getSessionManager(); } + // // Messages // @Override public Promise onAsk(Object message) throws Exception { - if (!isReady) { + if (isFreezed) { stash(); return null; } if (message instanceof EncryptBox) { - return doEncrypt(((EncryptBox) message).getData()); + EncryptBox encryptBox = (EncryptBox) message; + return doEncrypt(encryptBox.getData()); } else if (message instanceof DecryptBox) { - return doDecrypt(((DecryptBox) message).getEncryptedBox()); + DecryptBox decryptBox = (DecryptBox) message; + return doDecrypt(decryptBox.getSenderKeyGroupId(), decryptBox.getKeys()); } else { return super.onAsk(message); } @@ -239,7 +233,7 @@ public Promise onAsk(Object message) throws Exception { @Override public void onReceive(Object message) { if (message instanceof KeyGroupUpdated) { - if (!isReady) { + if (isFreezed) { stash(); return; } @@ -249,7 +243,7 @@ public void onReceive(Object message) { } } - public static class EncryptBox implements AskMessage { + public static class EncryptBox implements AskMessage { private byte[] data; public EncryptBox(byte[] data) { @@ -263,14 +257,20 @@ public byte[] getData() { public static class DecryptBox implements AskMessage { - private EncryptedBox encryptedBox; + private int senderKeyGroupId; + private List keys; + + public DecryptBox(int senderKeyGroupId, List keys) { + this.senderKeyGroupId = senderKeyGroupId; + this.keys = keys; + } - public DecryptBox(EncryptedBox encryptedBox) { - this.encryptedBox = encryptedBox; + public int getSenderKeyGroupId() { + return senderKeyGroupId; } - public EncryptedBox getEncryptedBox() { - return encryptedBox; + public List getKeys() { + return keys; } } @@ -287,12 +287,12 @@ public UserKeys getUserKeys() { } } - private class SessionHolder { + private class KeyGroupHolder { private int keyGroupId; - private ArrayList sessions; + private ArrayList sessions; - public SessionHolder(int keyGroupId, ArrayList sessions) { + public KeyGroupHolder(int keyGroupId, ArrayList sessions) { this.keyGroupId = keyGroupId; this.sessions = sessions; } @@ -301,28 +301,12 @@ public int getKeyGroupId() { return keyGroupId; } - public ArrayList getSessions() { + public ArrayList getSessions() { return sessions; } - } - - private class SessionActor { - - private EncryptedSession encryptedSession; - private PeerSession session; - public SessionActor(EncryptedSession encryptedSession, PeerSession session) { - this.encryptedSession = encryptedSession; - this.session = session; - } - - public EncryptedSession getEncryptedSession() { - return encryptedSession; - } - - public PeerSession getSession() { - return session; + public EncryptedSession first() { + return sessions.get(0); } } - } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java index 7e20072d5a..1838f98220 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java @@ -2,10 +2,10 @@ import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index ec7f04681c..9b68643035 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -3,10 +3,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import im.actor.core.api.ApiEncryptionKey; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.api.ApiEncryptionKeySignature; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateNewKeyGroup; import im.actor.core.api.rpc.RequestLoadPrePublicKeys; @@ -16,14 +18,13 @@ import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.Configuration; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKeyStorage; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; -import im.actor.core.modules.encryption.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKeyStorage; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeysGroup; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; import im.actor.core.modules.ModuleActor; -import im.actor.core.modules.encryption.ratchet.EncryptedUserActor; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; import im.actor.runtime.Log; @@ -442,6 +443,18 @@ private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { return Promise.success(null); } + private Promise onUserKeysChanged(List missed, List obsolete) { + + + +// UserKeys userKeys = getCachedUserKeys(uid); +// if (userKeys == null) { +// return Promise.success(null); +// } + + return Promise.success(null); + } + // // Helper methods // @@ -716,4 +729,27 @@ public int getKeyGroupId() { return keyGroupId; } } + + // + // Missed or wrong key groups + // + + public static class KeyGroupsDiff implements AskMessage { + + private List missed; + private List obsolete; + + public KeyGroupsDiff(List missed, List obsolete) { + this.missed = missed; + this.obsolete = obsolete; + } + + public List getMissed() { + return missed; + } + + public List getObsolete() { + return obsolete; + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java index 2422229f18..21fb0708cc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/SessionManagerActor.java @@ -7,11 +7,11 @@ import im.actor.core.entity.encryption.PeerSession; import im.actor.core.entity.encryption.PeerSessionsStorage; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.entity.OwnIdentity; -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.core.modules.encryption.entity.UserKeys; -import im.actor.core.modules.encryption.entity.UserKeysGroup; +import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; +import im.actor.core.modules.encryption.ratchet.entity.PublicKey; +import im.actor.core.modules.encryption.ratchet.entity.UserKeys; +import im.actor.core.modules.encryption.ratchet.entity.UserKeysGroup; import im.actor.core.util.BaseKeyValueEngine; import im.actor.core.modules.ModuleActor; import im.actor.core.util.RandomUtils; @@ -21,9 +21,6 @@ import im.actor.runtime.crypto.ratchet.RatchetMasterSecret; import im.actor.runtime.crypto.ratchet.RatchetPrivateKey; import im.actor.runtime.crypto.ratchet.RatchetPublicKey; -import im.actor.runtime.function.Function; -import im.actor.runtime.function.FunctionTupled4; -import im.actor.runtime.function.Tuple4; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.Promises; import im.actor.runtime.storage.KeyValueEngine; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java new file mode 100644 index 0000000000..38c8e80940 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedMessage.java @@ -0,0 +1,25 @@ +package im.actor.core.modules.encryption.ratchet.entity; + +import java.util.List; + +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiKeyGroupId; + +public class EncryptedMessage { + + private ApiEncryptedBox encryptedBox; + private List ignoredGroups; + + public EncryptedMessage(ApiEncryptedBox encryptedBox, List ignoredGroups) { + this.encryptedBox = encryptedBox; + this.ignoredGroups = ignoredGroups; + } + + public ApiEncryptedBox getEncryptedBox() { + return encryptedBox; + } + + public List getIgnoredGroups() { + return ignoredGroups; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java new file mode 100644 index 0000000000..663f28279c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/EncryptedUserKeys.java @@ -0,0 +1,31 @@ +package im.actor.core.modules.encryption.ratchet.entity; + +import java.util.HashSet; +import java.util.List; + +import im.actor.core.api.ApiEncyptedBoxKey; + +public class EncryptedUserKeys { + + private int uid; + private List boxKeys; + private HashSet ignoredKeys; + + public EncryptedUserKeys(int uid, List boxKeys, HashSet ignoredKeys) { + this.uid = uid; + this.boxKeys = boxKeys; + this.ignoredKeys = ignoredKeys; + } + + public int getUid() { + return uid; + } + + public List getBoxKeys() { + return boxKeys; + } + + public HashSet getIgnoredKeys() { + return ignoredKeys; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java similarity index 80% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java index d05e90fb04..29aa84b1bc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/OwnIdentity.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/OwnIdentity.java @@ -1,6 +1,5 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; -import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.runtime.actors.ask.AskResult; public class OwnIdentity extends AskResult { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java index 07d7be2e28..e672c7cd8b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKey.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java similarity index 98% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java index 74a0c65541..b011c4c4fc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PrivateKeyStorage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PrivateKeyStorage.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java similarity index 95% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java index a246c75033..c08ebcc625 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/PublicKey.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/PublicKey.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java similarity index 97% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java index 54af77bfbf..4d789dadea 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeys.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeys.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java similarity index 90% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java index 43df64a59e..afcdc34c87 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/entity/UserKeysGroup.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/UserKeysGroup.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption.entity; +package im.actor.core.modules.encryption.ratchet.entity; import java.io.IOException; import java.util.ArrayList; @@ -12,12 +12,7 @@ public class UserKeysGroup extends BserObject { public static Predicate BY_KEY_GROUP(final int keyGroupId) { - return new Predicate() { - @Override - public boolean apply(UserKeysGroup keysGroup) { - return keysGroup.getKeyGroupId() == keyGroupId; - } - }; + return keysGroup -> keysGroup.getKeyGroupId() == keyGroupId; } private int keyGroupId; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java deleted file mode 100644 index 9b72a29b07..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSession.java +++ /dev/null @@ -1,53 +0,0 @@ -package im.actor.core.modules.encryption.session; - -import im.actor.core.modules.encryption.entity.PrivateKey; -import im.actor.core.modules.encryption.entity.PublicKey; -import im.actor.runtime.crypto.ratchet.RatchetMasterSecret; -import im.actor.runtime.crypto.ratchet.RatchetPrivateKey; -import im.actor.runtime.crypto.ratchet.RatchetPublicKey; - -public class EncryptedSession { - private PrivateKey ownIdentityKey; - private PrivateKey ownPreKey; - private PublicKey theirIdentityKey; - private PublicKey theirPreKey; - private int peerKeyGroupId; - private byte[] masterKey; - - public EncryptedSession(PrivateKey ownIdentityKey, PrivateKey ownPreKey, PublicKey theirIdentityKey, PublicKey theirPreKey, int peerKeyGroupId) { - this.ownIdentityKey = ownIdentityKey; - this.ownPreKey = ownPreKey; - this.theirIdentityKey = theirIdentityKey; - this.theirPreKey = theirPreKey; - this.peerKeyGroupId = peerKeyGroupId; - this.masterKey = RatchetMasterSecret.calculateMasterSecret( - new RatchetPrivateKey(ownIdentityKey.getKey()), - new RatchetPrivateKey(ownPreKey.getKey()), - new RatchetPublicKey(theirIdentityKey.getPublicKey()), - new RatchetPublicKey(theirPreKey.getPublicKey())); - } - - public PrivateKey getOwnIdentityKey() { - return ownIdentityKey; - } - - public PrivateKey getOwnPreKey() { - return ownPreKey; - } - - public PublicKey getTheirIdentityKey() { - return theirIdentityKey; - } - - public PublicKey getTheirPreKey() { - return theirPreKey; - } - - public int getPeerKeyGroupId() { - return peerKeyGroupId; - } - - public byte[] getMasterKey() { - return masterKey; - } -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java deleted file mode 100644 index 0d805dd19e..0000000000 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/session/EncryptedSessionStorage.java +++ /dev/null @@ -1,5 +0,0 @@ -package im.actor.core.modules.encryption.session; - -public class EncryptedSessionStorage { - -} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index c1fbf99034..1b428fa5ea 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -30,6 +30,7 @@ import im.actor.core.api.base.SeqUpdate; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -66,6 +67,7 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -496,34 +498,18 @@ public void onError(RpcException e) { Log.d("SenderActor", "Pending encrypted message: " + message); ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), rid, message); - context().getEncryption().encrypt(peer.getPeerId(), content).then(apiEncryptedMessage -> { - Log.d("SenderActor", "Encrypted: " + apiEncryptedMessage); - - long accessHash = getUser(peer.getPeerId()).getAccessHash(); - ArrayList peers = new ArrayList<>(); - peers.add(new ApiUserOutPeer(peer.getPeerId(), accessHash)); - RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, peers, - new ArrayList<>(), apiEncryptedMessage); - - api(request).then(response -> { - - self().send(new MessageSent(peer, rid)); - - // TODO: Replace - context().getMessagesModule().getRouter().onOutgoingSent(peer, - rid, response.getDate()); -// updates().onUpdateReceived(new SeqUpdate(response.getSeq(), -// response.getState(), -// UpdateMessageSent.HEADER, -// new UpdateMessageSent(new ApiPeer(ApiPeerType.PRIVATE, peer.getPeerId()), -// rid, response.getDate()).toByteArray())); - - wakeLock.releaseLock(); - }).failure(e -> { - self().send(new MessageError(peer, rid)); - wakeLock.releaseLock(); - }); + ArrayList receivers = new ArrayList<>(); + // receivers.add(myUid()); + receivers.add(peer.getPeerId()); + context().getEncryption().doSend(rid, content, receivers).then(r -> { + + self().send(new MessageSent(peer, rid)); + + // TODO: Replace + context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r.getDate()); + + wakeLock.releaseLock(); }).failure(e -> { self().send(new MessageError(peer, rid)); wakeLock.releaseLock(); diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java index 3eda9ce2cb..78f1f6fc93 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/PromisesArray.java @@ -130,6 +130,11 @@ public PromisesArray ignoreFailed() { })); } + public PromisesArray filterFailed() { + return ignoreFailed() + .filterNull(); + } + public PromisesArray filterNull() { return filter(Predicates.NOT_NULL); } @@ -364,4 +369,13 @@ public Promise zipPromise(final ListFunction> fuc) { public Promise> zip() { return zipPromise(t -> Promise.success(t)); } + + /** + * Zipping array of promises to single promise of array + * + * @return promise + */ + public Promise zip(Function, V> mapfunc) { + return zip().map(mapfunc); + } } \ No newline at end of file From af2362f3edd9ad373ebb39a2a3b027a6e030e960 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:13:26 +0300 Subject: [PATCH 34/81] fix(core): Restore keys validation in chain decryption, fix key filtration in EncryptedUserActor, fallback of processing of encrypted updates --- .../encryption/EncryptionProcessor.java | 3 +- .../ratchet/EncryptedSessionChain.java | 35 +++++++++---------- .../ratchet/EncryptedUserActor.java | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 6fad452130..92f3fa99da 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -16,6 +16,7 @@ import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; public class EncryptionProcessor extends AbsModule implements SequenceProcessor { @@ -42,7 +43,7 @@ public Promise process(Update update) { .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) .flatMap(message -> { return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); - }); + }).fallback(e -> Promise.success(null)); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java index e052fddc80..3f4c6b9695 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java @@ -17,6 +17,7 @@ public class EncryptedSessionChain { private PeerSession session; private byte[] ownPrivateKey; + private byte[] ownPublicKey; private byte[] theirPublicKey; private int sentCounter; private byte[] rootChainKey; @@ -24,6 +25,7 @@ public class EncryptedSessionChain { public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] theirPublicKey) { this.session = session; this.ownPrivateKey = ownPrivateKey; + this.ownPublicKey = Curve25519.keyGenPublic(ownPrivateKey); this.theirPublicKey = theirPublicKey; this.sentCounter = 0; this.rootChainKey = RatchetRootChainKey.makeRootChainKey( @@ -64,29 +66,26 @@ public byte[] decrypt(byte[] data) throws IntegrityException { // Validating header // -// if (senderKeyGroupId != session.getPeerKeyGroupId()) { -// throw new IntegrityException("Incorrect sender key group id"); -// } -// if (senderEphermalKey0Id != session.getTheirPreKey().getKeyId()) { -// throw new IntegrityException("Incorrect sender pre key id"); -// } -// if (receiverEphermalKey0Id != session.getOwnPreKey().getKeyId()) { -// throw new IntegrityException("Incorrect receiver pre key id"); -// } -// if (ByteStrings.isEquals(senderEphemeralKey, theirPublicKey)) { -// throw new IntegrityException("Incorrect sender ephemeral key"); -// } -// if (ByteStrings.isEquals(receiverEphemeralKey, ownPrivateKey)) { -// throw new IntegrityException("Incorrect receiver ephemeral key"); -// } + if (senderEphermalKey0Id != session.getTheirPreKeyId()) { + throw new IntegrityException("Incorrect sender pre key id"); + } + if (receiverEphermalKey0Id != session.getOwnPreKeyId()) { + throw new IntegrityException("Incorrect receiver pre key id"); + } + if (!ByteStrings.isEquals(senderEphemeralKey, theirPublicKey)) { + throw new IntegrityException("Incorrect sender ephemeral key"); + } + if (!ByteStrings.isEquals(receiverEphemeralKey, ownPublicKey)) { + throw new IntegrityException("Incorrect receiver ephemeral key"); + } // // Decryption // ActorBoxKey ratchetMessageKey = RatchetMessageKey.buildKey(rootChainKey, messageIndex); - byte[] header = ByteStrings.substring(data, 0, 80); - byte[] message = ByteStrings.substring(data, 80, data.length - 80); + byte[] header = ByteStrings.substring(data, 0, 84); + byte[] message = ByteStrings.substring(data, 84, data.length - 84); return ActorBox.openBox(header, message, ratchetMessageKey); } @@ -97,7 +96,7 @@ public byte[] encrypt(byte[] data) throws IntegrityException { byte[] header = ByteStrings.merge( ByteStrings.longToBytes(session.getOwnPreKeyId()), /*Alice Initial Ephermal*/ ByteStrings.longToBytes(session.getTheirPreKeyId()), /*Bob Initial Ephermal*/ - Curve25519.keyGenPublic(ownPrivateKey), + ownPublicKey, theirPublicKey, ByteStrings.intToBytes(messageIndex)); /* Message Index */ diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 868976c213..8da08da2b2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -130,7 +130,7 @@ private Promise doDecrypt(int senderKeyGroupId, List } ApiEncyptedBoxKey key = null; for (ApiEncyptedBoxKey boxKey : keys) { - if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == uid) { + if (boxKey.getKeyGroupId() == ownKeyGroupId && boxKey.getUsersId() == myUid()) { key = boxKey; break; } From 923f85b822a9bc906d337549513eb5d3bda6eb22 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:55:15 +0300 Subject: [PATCH 35/81] fix(core): Fixing incorrect user id in encrypted message header --- .../core/modules/encryption/ratchet/EncryptedMsgActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 753145c1ac..31dacd0c70 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -114,7 +114,7 @@ public Promise doDecrypt(int uid, ApiEncryptedBox box) { // Decryption of package byte[] dataHeader = ByteStrings.merge( new byte[]{VERSION}, - ByteStrings.intToBytes(myUid()), + ByteStrings.intToBytes(uid), ByteStrings.intToBytes(box.getSenderKeyGroupId())); byte[] data; try { From 1e58a7856ae312af56077e654a4ced1776c94a4a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 14:56:16 +0300 Subject: [PATCH 36/81] fix(core): Remove saving latest their ephemeral key --- .../encryption/ratchet/EncryptedSessionActor.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index 9348515891..f71045fa62 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -88,9 +88,11 @@ private Promise onEncrypt(final byte[] data) { Promise ephemeralKey; if (latestTheirEphemeralKey != null) { ephemeralKey = success(latestTheirEphemeralKey); + Log.d(TAG, "Picked cached"); } else { ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) .map(PublicKey::getPublicKey); + Log.d(TAG, "Picked from server #" + uid + " " + session.getTheirKeyGroupId()); } return ephemeralKey @@ -115,12 +117,12 @@ private Promise onDecrypt(ApiEncyptedBoxKey data) { // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 8); final byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); final byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); - Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); - Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); +// Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); +// Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)) - .then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)); + //.then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); } private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { @@ -151,8 +153,8 @@ private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { throw new RuntimeException(e); } - Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); - Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); +// Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); +// Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); return encrypted; } From 86ebed4b665f000198cf2011963e25748af6e1e7 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 15:00:17 +0300 Subject: [PATCH 37/81] fix(core): Fixing encrypted container serialization --- .../core/modules/encryption/ratchet/EncryptedMsgActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index 31dacd0c70..c14c1678e0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -60,7 +60,7 @@ private Promise doEncrypt(ApiEncryptedContent message, List Date: Thu, 14 Jul 2016 15:16:28 +0300 Subject: [PATCH 38/81] feat(core): Separated encrypted sequence processors --- .../EncryptedSequenceProcessor.java | 9 +++++ .../encryption/EncryptionProcessor.java | 36 ++++++++--------- .../messaging/MessagesProcessorEncrypted.java | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java new file mode 100644 index 0000000000..f509226154 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java @@ -0,0 +1,9 @@ +package im.actor.core.modules.encryption; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public interface EncryptedSequenceProcessor { + Promise onUpdate(int senderId, long date, ApiEncryptedContent update); +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 92f3fa99da..b4688a949b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -13,6 +13,7 @@ import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.messages.Void; @@ -21,8 +22,14 @@ public class EncryptionProcessor extends AbsModule implements SequenceProcessor { + private EncryptedSequenceProcessor[] processors; + public EncryptionProcessor(ModuleContext context) { super(context); + + processors = new EncryptedSequenceProcessor[]{ + new MessagesProcessorEncrypted(context) + }; } @Override @@ -49,27 +56,16 @@ public Promise process(Update update) { } public Promise process(int senderId, long date, ApiEncryptedContent update) { - if (update instanceof ApiEncryptedMessageContent) { - ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; - - Message msg = new Message(content.getRid(), date, date, senderId, - MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); - - int destId = senderId; - if (senderId == myUid()) { - destId = content.getReceiverId(); + Promise res = null; + for (EncryptedSequenceProcessor s : processors) { + res = s.onUpdate(senderId, date, update); + if (res != null) { + break; } - - return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(destId), msg); - } else if (update instanceof ApiEncryptedDeleteContent) { - // TODO: Implement - return Promise.success(null); - } else if (update instanceof ApiEncryptedEditContent) { - // TODO: Implement - return Promise.success(null); - } else { - return Promise.success(null); } + if (res == null) { + res = Promise.success(null); + } + return res; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java new file mode 100644 index 0000000000..9fee0bd6ec --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -0,0 +1,40 @@ +package im.actor.core.modules.messaging; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.entity.Message; +import im.actor.core.entity.MessageState; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.EncryptedSequenceProcessor; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class MessagesProcessorEncrypted extends AbsModule implements EncryptedSequenceProcessor { + + public MessagesProcessorEncrypted(ModuleContext context) { + super(context); + } + + @Override + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(destId), msg); + } else { + return null; + } + } +} \ No newline at end of file From f5adf6b26cff95539fd06282147b516783d8ddc4 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 17:29:49 +0300 Subject: [PATCH 39/81] feat(core): Read/Receive for encrypted chats --- actor-sdk/sdk-api/actor.json | 1038 +++++------------ .../models/im/actor/api/scheme.mps | 67 ++ .../actor/core/api/ApiEncryptedContent.java | 2 + .../im/actor/core/api/ApiEncryptedRead.java | 64 + .../actor/core/api/ApiEncryptedReceived.java | 64 + .../modules/encryption/EncryptionModule.java | 9 + .../encryption/EncryptionProcessor.java | 4 + .../messaging/MessagesProcessorEncrypted.java | 24 +- .../messaging/actions/CursorReaderActor.java | 29 +- .../actions/CursorReceiverActor.java | 30 +- .../modules/messaging/router/RouterActor.java | 44 + .../modules/messaging/router/RouterInt.java | 6 + .../router/entity/RouterEncryptedUpdate.java | 30 + 13 files changed, 630 insertions(+), 781 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index bce97b53f2..c6cbfc11fa 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -6571,32 +6571,6 @@ ] } }, - { - "type": "update", - "content": { - "name": "ChatDropCache", - "header": 2690, - "doc": [ - "Update about cache drop", - { - "type": "reference", - "argument": "peer", - "category": "full", - "description": " Destination peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "Peer" - }, - "id": 1, - "name": "peer" - } - ] - } - }, { "type": "update", "content": { @@ -6607,7 +6581,7 @@ { "type": "reference", "argument": "dialogs", - "category": "compact", + "category": "full", "description": " New dialgos list" } ], @@ -7872,116 +7846,12 @@ ] } }, - { - "type": "enum", - "content": { - "name": "GroupPermissions", - "values": [ - { - "name": "SEND_MESSAGE", - "id": 1 - }, - { - "name": "CLEAR", - "id": 2 - }, - { - "name": "LEAVE", - "id": 3 - }, - { - "name": "DELETE", - "id": 4 - }, - { - "name": "JOIN", - "id": 5 - }, - { - "name": "VIEW_INFO", - "id": 6 - } - ] - } - }, - { - "type": "enum", - "content": { - "name": "GroupFullPermissions", - "values": [ - { - "name": "EDIT_INFO", - "id": 1 - }, - { - "name": "VIEW_MEMBERS", - "id": 2 - }, - { - "name": "INVITE_MEMBERS", - "id": 3 - }, - { - "name": "INVITE_VIA_LINK", - "id": 4 - }, - { - "name": "CALL", - "id": 5 - }, - { - "name": "EDIT_ADMIN_SETTINGS", - "id": 6 - }, - { - "name": "VIEW_ADMINS", - "id": 7 - }, - { - "name": "EDIT_ADMINS", - "id": 8 - }, - { - "name": "KICK_INVITED", - "id": 9 - }, - { - "name": "KICK_ANYONE", - "id": 10 - }, - { - "name": "EDIT_FOREIGN", - "id": 11 - }, - { - "name": "DELETE_FOREIGN", - "id": 12 - } - ] - } - }, { "type": "struct", "content": { "name": "Group", "doc": [ "Group information", - "", - "Permissions.", - "Permissions of this structure is about group messages operation, such as", - "ability to send messages, clear chat, leave group and so on. This operations", - "Can be held outside of the Group Info page.", - "", - "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", - "If default is TRUE then ONE == FALSE.", - "Bits:", - "0 - canSendMessage. Default is FALSE.", - "1 - canClear. Default is FALSE.", - "2 - canLeave. Default is FALSE.", - "3 - canDelete. Default is FALSE.", - "4 - canJoin. Default is FALSE.", - "5 - canViewInfo. Default is FALSE.", - "", { "type": "reference", "argument": "id", @@ -8032,15 +7902,9 @@ }, { "type": "reference", - "argument": "permissions", - "category": "full", - "description": " Permissions of group object" - }, - { - "type": "reference", - "argument": "isDeleted", + "argument": "canSendMessage", "category": "full", - "description": " Is this group deleted" + "description": " Can user send messages. Default is equals isMember for Group and false for channels." }, { "type": "reference", @@ -8151,21 +8015,13 @@ "id": 25, "name": "groupType" }, - { - "type": { - "type": "opt", - "childType": "int64" - }, - "id": 26, - "name": "permissions" - }, { "type": { "type": "opt", "childType": "bool" }, - "id": 27, - "name": "isDeleted" + "id": 26, + "name": "canSendMessage" }, { "type": { @@ -8184,8 +8040,7 @@ "childType": "bool" }, "id": 16, - "name": "isAdmin", - "deprecated": "true" + "name": "isAdmin" }, { "type": { @@ -8193,8 +8048,7 @@ "childType": "userId" }, "id": 8, - "name": "creatorUid", - "deprecated": "true" + "name": "creatorUid" }, { "type": { @@ -8205,8 +8059,7 @@ } }, "id": 9, - "name": "members", - "deprecated": "true" + "name": "members" }, { "type": { @@ -8214,8 +8067,7 @@ "childType": "date" }, "id": 10, - "name": "createDate", - "deprecated": "true" + "name": "createDate" }, { "type": { @@ -8223,8 +8075,7 @@ "childType": "string" }, "id": 17, - "name": "theme", - "deprecated": "true" + "name": "theme" }, { "type": { @@ -8232,8 +8083,7 @@ "childType": "string" }, "id": 18, - "name": "about", - "deprecated": "true" + "name": "about" } ] } @@ -8244,26 +8094,6 @@ "name": "GroupFull", "doc": [ "Goup Full information", - "Permissions.", - "Idea of Group Full mermissions is about Group Info pages. This permissions", - "are usefull only when trying to view and update group settings and not related", - "to chat messages itself.", - "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", - "If default is TRUE then ONE == FALSE.", - "Bits:", - "0 - canEditInfo. Default is FALSE.", - "1 - canViewMembers. Default is FALSE.", - "2 - canInviteMembers. Default is FALSE.", - "3 - canInviteViaLink. Default is FALSE.", - "4 - canCall. Default is FALSE.", - "5 - canEditAdminSettings. Default is FALSE.", - "6 - canViewAdmins. Default is FALSE.", - "7 - canEditAdmins. Default is FALSE.", - "8 - canKickInvited. Default is FALSE.", - "9 - canKickAnyone. Default is FALSE.", - "10 - canEditForeign. Default is FALSE.", - "11 - canDeleteForeign. Default is FALSE.", - "", { "type": "reference", "argument": "id", @@ -8280,7 +8110,7 @@ "type": "reference", "argument": "ownerUid", "category": "full", - "description": " Optional group owner" + "description": " Group owner" }, { "type": "reference", @@ -8304,28 +8134,27 @@ "type": "reference", "argument": "isAsyncMembers", "category": "full", - "description": " Is Members need to be loaded asynchronous. Default is false." + "description": " Is Members need to be loaded asynchronous." }, { "type": "reference", - "argument": "isSharedHistory", + "argument": "canViewMembers", "category": "full", - "description": " Is history shared among all users. Default is false." + "description": " Can current user view members of the group. Default is true." }, { "type": "reference", - "argument": "shortName", + "argument": "canInvitePeople", "category": "full", - "description": " Group's short name" + "description": " Can current user invite new people. Default is true." }, { "type": "reference", - "argument": "permissions", + "argument": "isSharedHistory", "category": "full", - "description": " Group Permissions" + "description": " Is history shared among all users." } ], - "expandable": "true", "attributes": [ { "type": { @@ -8345,11 +8174,8 @@ }, { "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "userId" - } + "type": "alias", + "childType": "userId" }, "id": 5, "name": "ownerUid" @@ -8405,24 +8231,24 @@ "type": "opt", "childType": "bool" }, - "id": 10, - "name": "isSharedHistory" + "id": 8, + "name": "canViewMembers" }, { "type": { "type": "opt", - "childType": "string" + "childType": "bool" }, - "id": 14, - "name": "shortName" + "id": 9, + "name": "canInvitePeople" }, { "type": { "type": "opt", - "childType": "int64" + "childType": "bool" }, - "id": 27, - "name": "permissions" + "id": 10, + "name": "isSharedHistory" } ] } @@ -8492,7 +8318,7 @@ "doc": [ { "type": "reference", - "argument": "users", + "argument": "members", "category": "full", "description": " Group members" }, @@ -8504,17 +8330,6 @@ } ], "attributes": [ - { - "type": { - "type": "list", - "childType": { - "type": "struct", - "childType": "Member" - } - }, - "id": 3, - "name": "members" - }, { "type": { "type": "list", @@ -8524,7 +8339,7 @@ } }, "id": 1, - "name": "users" + "name": "members" }, { "type": { @@ -8832,46 +8647,6 @@ ] } }, - { - "type": "update", - "content": { - "name": "GroupShortNameChanged", - "header": 2628, - "doc": [ - "Group's short name changed", - { - "type": "reference", - "argument": "groupId", - "category": "full", - "description": " Group Id" - }, - { - "type": "reference", - "argument": "shortName", - "category": "full", - "description": " Group short name" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "groupId" - }, - "id": 1, - "name": "groupId" - }, - { - "type": { - "type": "opt", - "childType": "string" - }, - "id": 2, - "name": "shortName" - } - ] - } - }, { "type": "update", "content": { @@ -8941,15 +8716,21 @@ { "type": "update", "content": { - "name": "GroupDeleted", - "header": 2658, + "name": "GroupCanSendMessagesChanged", + "header": 2624, "doc": [ - "Update about group deleted", + "Update about can send messages changed", { "type": "reference", "argument": "groupId", "category": "full", "description": " Group Id" + }, + { + "type": "reference", + "argument": "canSendMessages", + "category": "full", + "description": " Can send messages" } ], "attributes": [ @@ -8960,6 +8741,11 @@ }, "id": 1, "name": "groupId" + }, + { + "type": "bool", + "id": 2, + "name": "canSendMessages" } ] } @@ -8967,10 +8753,10 @@ { "type": "update", "content": { - "name": "GroupPermissionsChanged", - "header": 2663, + "name": "GroupCanViewMembersChanged", + "header": 2625, "doc": [ - "Update about group permissions changed", + "Update about can view members changed", { "type": "reference", "argument": "groupId", @@ -8979,9 +8765,9 @@ }, { "type": "reference", - "argument": "permissions", + "argument": "canViewMembers", "category": "full", - "description": " New Permissions" + "description": " Can view members" } ], "attributes": [ @@ -8994,9 +8780,9 @@ "name": "groupId" }, { - "type": "int64", + "type": "bool", "id": 2, - "name": "permissions" + "name": "canViewMembers" } ] } @@ -9004,10 +8790,10 @@ { "type": "update", "content": { - "name": "GroupFullPermissionsChanged", - "header": 2664, + "name": "GroupCanInviteMembersChanged", + "header": 2626, "doc": [ - "Update about Full Group permissions changed", + "Update about can invite members changed", { "type": "reference", "argument": "groupId", @@ -9016,9 +8802,9 @@ }, { "type": "reference", - "argument": "permissions", + "argument": "canInviteMembers", "category": "full", - "description": " New Permissions" + "description": " Can invite members" } ], "attributes": [ @@ -9031,9 +8817,9 @@ "name": "groupId" }, { - "type": "int64", + "type": "bool", "id": 2, - "name": "permissions" + "name": "canInviteMembers" } ] } @@ -9755,50 +9541,6 @@ ] } }, - { - "type": "rpc", - "content": { - "name": "EditGroupShortName", - "header": 2793, - "response": { - "type": "reference", - "name": "Seq" - }, - "doc": [ - "Edit Group Short Name", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - }, - { - "type": "reference", - "argument": "shortName", - "category": "full", - "description": "New group's short name" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - }, - { - "type": { - "type": "opt", - "childType": "string" - }, - "id": 2, - "name": "shortName" - } - ] - } - }, { "type": "rpc", "content": { @@ -10088,50 +9830,20 @@ { "type": "rpc", "content": { - "name": "LeaveAndDelete", - "header": 2721, + "name": "KickUser", + "header": 71, "response": { "type": "reference", - "name": "Seq" + "name": "SeqDate" }, "doc": [ - "Leave group and Delete Chat", + "Kicking user from group", { "type": "reference", "argument": "groupPeer", "category": "full", - "description": "Group peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "KickUser", - "header": 71, - "response": { - "type": "reference", - "name": "SeqDate" - }, - "doc": [ - "Kicking user from group", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - }, + "description": "Group's peer" + }, { "type": "reference", "argument": "user", @@ -10182,336 +9894,10 @@ "childType": { "type": "enum", "childType": "UpdateOptimization" - } - }, - "id": 5, - "name": "optimizations" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "JoinGroupByPeer", - "header": 2722, - "response": { - "type": "reference", - "name": "Seq" - }, - "doc": [ - "Join group by peer", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - } - ] - } - }, - { - "type": "comment", - "content": "Administration" - }, - { - "type": "rpc", - "content": { - "name": "MakeUserAdmin", - "header": 2784, - "response": { - "type": "reference", - "name": "SeqDate" - }, - "doc": [ - "Make user admin", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - }, - { - "type": "reference", - "argument": "userPeer", - "category": "full", - "description": "User's peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "UserOutPeer" - }, - "id": 2, - "name": "userPeer" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "DismissUserAdmin", - "header": 2791, - "response": { - "type": "reference", - "name": "Seq" - }, - "doc": [ - "Dismissing user admin", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - }, - { - "type": "reference", - "argument": "userPeer", - "category": "full", - "description": "User's peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "UserOutPeer" - }, - "id": 2, - "name": "userPeer" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "TransferOwnership", - "header": 2789, - "response": { - "type": "reference", - "name": "SeqDate" - }, - "doc": [ - "Transfer ownership of group", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - }, - { - "type": "reference", - "argument": "newOwner", - "category": "full", - "description": "New group's owner" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "UserOutPeer" - }, - "id": 2, - "name": "newOwner" - } - ] - } - }, - { - "type": "struct", - "content": { - "name": "AdminSettings", - "doc": [ - "Admin Settings", - { - "type": "reference", - "argument": "showAdminsToMembers", - "category": "full", - "description": " Show admins in member list" - }, - { - "type": "reference", - "argument": "canMembersInvite", - "category": "full", - "description": " Can members of a group invite people" - }, - { - "type": "reference", - "argument": "canMembersEditGroupInfo", - "category": "full", - "description": " Can members edit group info" - }, - { - "type": "reference", - "argument": "canAdminsEditGroupInfo", - "category": "full", - "description": " Can admins edit group info" - }, - { - "type": "reference", - "argument": "showJoinLeaveMessages", - "category": "full", - "description": " Should join and leave messages be visible to members" - } - ], - "expandable": "true", - "attributes": [ - { - "type": "bool", - "id": 1, - "name": "showAdminsToMembers" - }, - { - "type": "bool", - "id": 2, - "name": "canMembersInvite" - }, - { - "type": "bool", - "id": 3, - "name": "canMembersEditGroupInfo" - }, - { - "type": "bool", - "id": 4, - "name": "canAdminsEditGroupInfo" - }, - { - "type": "bool", - "id": 5, - "name": "showJoinLeaveMessages" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "LoadAdminSettings", - "header": 2790, - "response": { - "type": "anonymous", - "header": 2794, - "doc": [ - "Loaded settings", - { - "type": "reference", - "argument": "settings", - "category": "full", - "description": " Current group admin settings" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "AdminSettings" - }, - "id": 1, - "name": "settings" - } - ] - }, - "doc": [ - "Loading administration settings", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's peer" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - } - ] - } - }, - { - "type": "rpc", - "content": { - "name": "SaveAdminSettings", - "header": 2792, - "response": { - "type": "reference", - "name": "Void" - }, - "doc": [ - "Save administartion settings", - { - "type": "reference", - "argument": "groupPeer", - "category": "full", - "description": "Group's Peer" - }, - { - "type": "reference", - "argument": "settings", - "category": "full", - "description": "Group's settings" - } - ], - "attributes": [ - { - "type": { - "type": "struct", - "childType": "GroupOutPeer" - }, - "id": 1, - "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "AdminSettings" + } }, - "id": 2, - "name": "settings" + "id": 5, + "name": "optimizations" } ] } @@ -10519,19 +9905,25 @@ { "type": "rpc", "content": { - "name": "DeleteGroup", - "header": 2795, + "name": "MakeUserAdmin", + "header": 2784, "response": { "type": "reference", - "name": "Seq" + "name": "SeqDate" }, "doc": [ - "Delete Group", + "Make user admin", { "type": "reference", "argument": "groupPeer", "category": "full", "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" } ], "attributes": [ @@ -10542,6 +9934,14 @@ }, "id": 1, "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" } ] } @@ -10549,19 +9949,25 @@ { "type": "rpc", "content": { - "name": "ShareHistory", - "header": 2796, + "name": "TransferOwnership", + "header": 2789, "response": { "type": "reference", - "name": "Seq" + "name": "SeqDate" }, "doc": [ - "Share History", + "Transfer ownership of group", { "type": "reference", "argument": "groupPeer", "category": "full", "description": "Group's peer" + }, + { + "type": "reference", + "argument": "newOwner", + "category": "full", + "description": "New group's owner" } ], "attributes": [ @@ -10572,6 +9978,14 @@ }, "id": 1, "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "newOwner" } ] } @@ -12482,9 +11896,39 @@ }, { "type": "reference", - "argument": "optMatchString", + "argument": "title", + "category": "full", + "description": " Peer title" + }, + { + "type": "reference", + "argument": "description", "category": "full", "description": " Description" + }, + { + "type": "reference", + "argument": "membersCount", + "category": "full", + "description": " Members count" + }, + { + "type": "reference", + "argument": "dateCreated", + "category": "full", + "description": " Group Creation Date" + }, + { + "type": "reference", + "argument": "creator", + "category": "full", + "description": " Group Creator uid" + }, + { + "type": "reference", + "argument": "isPublic", + "category": "full", + "description": " Is group public" } ], "attributes": [ @@ -12496,13 +11940,61 @@ "id": 1, "name": "peer" }, + { + "type": "string", + "id": 2, + "name": "title" + }, { "type": { "type": "opt", "childType": "string" }, "id": 3, - "name": "optMatchString" + "name": "description" + }, + { + "type": { + "type": "opt", + "childType": "int32" + }, + "id": 4, + "name": "membersCount" + }, + { + "type": { + "type": "opt", + "childType": { + "type": "alias", + "childType": "date" + } + }, + "id": 5, + "name": "dateCreated" + }, + { + "type": { + "type": "opt", + "childType": "int32" + }, + "id": 6, + "name": "creator" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 7, + "name": "isPublic" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 8, + "name": "isJoined" } ] } @@ -19544,7 +19036,7 @@ { "type": "reference", "argument": "keyGroupId", - "category": "full", + "category": "hidden", "description": " Key Group Id" } ], @@ -19568,45 +19060,6 @@ ] } }, - { - "type": "struct", - "content": { - "name": "KeyGroupHolder", - "doc": [ - "Key Group Holder", - { - "type": "reference", - "argument": "uid", - "category": "full", - "description": " User's id" - }, - { - "type": "reference", - "argument": "keyGroup", - "category": "full", - "description": " Key Group" - } - ], - "attributes": [ - { - "type": { - "type": "alias", - "childType": "userId" - }, - "id": 1, - "name": "uid" - }, - { - "type": { - "type": "struct", - "childType": "EncryptionKeyGroup" - }, - "id": 2, - "name": "keyGroup" - } - ] - } - }, { "type": "rpc", "content": { @@ -19616,6 +19069,18 @@ "type": "anonymous", "header": 2664, "doc": [ + { + "type": "reference", + "argument": "seq", + "category": "full", + "description": " seq" + }, + { + "type": "reference", + "argument": "state", + "category": "full", + "description": " state" + }, { "type": "reference", "argument": "date", @@ -19626,7 +19091,7 @@ "type": "reference", "argument": "obsoleteKeyGroups", "category": "full", - "description": " obsolete key group ids" + "description": " obsolete key groups" }, { "type": "reference", @@ -19636,6 +19101,25 @@ } ], "attributes": [ + { + "type": { + "type": "opt", + "childType": "int32" + }, + "id": 1, + "name": "seq" + }, + { + "type": { + "type": "opt", + "childType": { + "type": "alias", + "childType": "seq_state" + } + }, + "id": 2, + "name": "state" + }, { "type": { "type": "opt", @@ -19644,7 +19128,7 @@ "childType": "date" } }, - "id": 1, + "id": 3, "name": "date" }, { @@ -19655,7 +19139,7 @@ "childType": "KeyGroupId" } }, - "id": 2, + "id": 4, "name": "obsoleteKeyGroups" }, { @@ -19663,10 +19147,10 @@ "type": "list", "childType": { "type": "struct", - "childType": "KeyGroupHolder" + "childType": "KeyGroupId" } }, - "id": 3, + "id": 5, "name": "missedKeyGroups" } ] @@ -20029,6 +19513,86 @@ } ] } + }, + { + "type": "struct", + "content": { + "name": "EncryptedReceived", + "doc": [ + "Encrypted message receive notification", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "receiveDate", + "category": "full", + "description": " Receive message sort date" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 4 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "receiveDate" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "EncryptedRead", + "doc": [ + "Encrypted message read notification", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "readDate", + "category": "full", + "description": " Read message sort date" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 5 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": "int64", + "id": 2, + "name": "readDate" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 2093367f86..3decd00296 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -17015,6 +17015,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java index 197fd87258..bdf91eb1ee 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -23,6 +23,8 @@ public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { case 1: return Bser.parse(new ApiEncryptedMessageContent(), content); case 2: return Bser.parse(new ApiEncryptedEditContent(), content); case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); + case 4: return Bser.parse(new ApiEncryptedReceived(), content); + case 5: return Bser.parse(new ApiEncryptedRead(), content); default: return new ApiEncryptedContentUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java new file mode 100644 index 0000000000..1579c294c2 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedRead.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedRead extends ApiEncryptedContent { + + private int receiverId; + private long readDate; + + public ApiEncryptedRead(int receiverId, long readDate) { + this.receiverId = receiverId; + this.readDate = readDate; + } + + public ApiEncryptedRead() { + + } + + public int getHeader() { + return 5; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getReadDate() { + return this.readDate; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.readDate = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.readDate); + } + + @Override + public String toString() { + String res = "struct EncryptedRead{"; + res += "receiverId=" + this.receiverId; + res += ", readDate=" + this.readDate; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java new file mode 100644 index 0000000000..bbbf408bf2 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedReceived.java @@ -0,0 +1,64 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedReceived extends ApiEncryptedContent { + + private int receiverId; + private long receiveDate; + + public ApiEncryptedReceived(int receiverId, long receiveDate) { + this.receiverId = receiverId; + this.receiveDate = receiveDate; + } + + public ApiEncryptedReceived() { + + } + + public int getHeader() { + return 4; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getReceiveDate() { + return this.receiveDate; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.receiveDate = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(2, this.receiveDate); + } + + @Override + public String toString() { + String res = "struct EncryptedReceived{"; + res += "receiverId=" + this.receiverId; + res += ", receiveDate=" + this.receiveDate; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 919e7d0c14..ad86a8eb54 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -71,6 +71,15 @@ public Promise decrypt(int uid, ApiEncryptedBox encryptedBo return getEncryption().decrypt(uid, encryptedBox); } + public Promise doSend(long rid, ApiEncryptedContent content, int uid) { + ArrayList receiver = new ArrayList<>(); + receiver.add(uid); +// if (uid != myUid()) { +// receiver.add(myUid()); +// } + return doSend(rid, content, receiver); + } + public Promise doSend(long rid, ApiEncryptedContent content, List uids) { ArrayList outPeers = new ArrayList<>(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index b4688a949b..070d49a888 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -16,6 +16,7 @@ import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; +import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; @@ -56,6 +57,9 @@ public Promise process(Update update) { } public Promise process(int senderId, long date, ApiEncryptedContent update) { + + Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); + Promise res = null; for (EncryptedSequenceProcessor s : processors) { res = s.onUpdate(senderId, date, update); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java index 9fee0bd6ec..fd6d4ec577 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -2,10 +2,8 @@ import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiEncryptedMessageContent; -import im.actor.core.entity.Message; -import im.actor.core.entity.MessageState; -import im.actor.core.entity.Peer; -import im.actor.core.entity.content.AbsContent; +import im.actor.core.api.ApiEncryptedRead; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.EncryptedSequenceProcessor; @@ -20,21 +18,13 @@ public MessagesProcessorEncrypted(ModuleContext context) { @Override public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { - if (update instanceof ApiEncryptedMessageContent) { - ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; - - Message msg = new Message(content.getRid(), date, date, senderId, - MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); - - int destId = senderId; - if (senderId == myUid()) { - destId = content.getReceiverId(); - } + if (update instanceof ApiEncryptedMessageContent || + update instanceof ApiEncryptedReceived || + update instanceof ApiEncryptedRead) { return context().getMessagesModule().getRouter() - .onNewMessage(Peer.secret(destId), msg); - } else { - return null; + .onEncryptedUpdate(senderId, date, update); } + return null; } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java index f2860e6e07..740198b227 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java @@ -4,13 +4,13 @@ package im.actor.core.modules.messaging.actions; +import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageRead; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; public class CursorReaderActor extends CursorActor { @@ -25,17 +25,20 @@ protected void perform(final Peer peer, final long date) { return; } - request(new RequestMessageRead(outPeer, date), new RpcCallback() { - @Override - public void onResult(ResponseVoid response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedRead(peer.getPeerId(), date), peer.getPeerId()).then(r -> { onCompleted(peer, date); - } - - @Override - public void onError(RpcException e) { - CursorReaderActor.this.onError(peer, date); - } - }); + }).failure(e -> { + onError(peer, date); + }); + } else { + api(new RequestMessageRead(outPeer, date)).then(responseVoid -> { + onCompleted(peer, date); + }).failure(e -> { + onError(peer, date); + }); + } } // Messages diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java index afca3ef43b..7ec25d567f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java @@ -4,13 +4,13 @@ package im.actor.core.modules.messaging.actions; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageReceived; -import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; public class CursorReceiverActor extends CursorActor { @@ -21,22 +21,24 @@ public CursorReceiverActor(ModuleContext context) { @Override protected void perform(final Peer peer, final long date) { ApiOutPeer outPeer = buidOutPeer(peer); - if (outPeer == null) { return; } - request(new RequestMessageReceived(outPeer, date), new RpcCallback() { - @Override - public void onResult(ResponseVoid response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedReceived(peer.getPeerId(), date), peer.getPeerId()).then(r -> { onCompleted(peer, date); - } - - @Override - public void onError(RpcException e) { - CursorReceiverActor.this.onError(peer, date); - } - }); + }).failure(e -> { + onError(peer, date); + }); + } else { + api(new RequestMessageReceived(outPeer, date)).then(responseVoid -> { + onCompleted(peer, date); + }).failure(e -> { + onError(peer, date); + }); + } } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 2e7bff3e78..888dfee9cb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -7,6 +7,10 @@ import im.actor.core.api.ApiDialogGroup; import im.actor.core.api.ApiDialogShort; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedMessageContent; +import im.actor.core.api.ApiEncryptedRead; +import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.api.ApiMessageReaction; import im.actor.core.api.rpc.RequestLoadGroupedDialogs; import im.actor.core.api.updates.UpdateChatClear; @@ -54,6 +58,7 @@ import im.actor.core.modules.messaging.router.entity.RouterDeletedMessages; import im.actor.core.modules.messaging.router.entity.RouterDifferenceEnd; import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart; +import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageOnlyActive; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; @@ -910,6 +915,41 @@ public Promise onUpdate(Update update) { return Promise.success(null); } + public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + + if (update instanceof ApiEncryptedMessageContent) { + ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + + Message msg = new Message(content.getRid(), date, date, senderId, + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + + int destId = senderId; + if (senderId == myUid()) { + destId = content.getReceiverId(); + } + ArrayList messages = new ArrayList<>(); + messages.add(msg); + return onNewMessages(Peer.secret(destId), messages); + } else if (update instanceof ApiEncryptedReceived) { + ApiEncryptedReceived encryptedReceived = (ApiEncryptedReceived) update; + int destId = senderId; + if (senderId == myUid()) { + destId = encryptedReceived.getReceiverId(); + } + return onMessageReceived(Peer.secret(destId), encryptedReceived.getReceiveDate()); + } else if (update instanceof ApiEncryptedRead) { + ApiEncryptedRead encryptedRead = (ApiEncryptedRead) update; + if (senderId == myUid()) { + // TODO: Fix Counter + return onMessageReadByMe(Peer.secret(encryptedRead.getReceiverId()), encryptedRead.getReadDate(), 0); + } else { + return onMessageRead(Peer.secret(senderId), encryptedRead.getReadDate()); + } + } + + return Promise.success(null); + } + @Override public void onReceive(Object message) { if (!activeDialogStorage.isLoaded() && message instanceof RouterMessageOnlyActive) { @@ -939,6 +979,10 @@ public Promise onAsk(Object message) throws Exception { } if (message instanceof RouterMessageUpdate) { return onUpdate(((RouterMessageUpdate) message).getUpdate()); + } else if (message instanceof RouterEncryptedUpdate) { + RouterEncryptedUpdate encryptedUpdate = (RouterEncryptedUpdate) message; + return onEncryptedUpdate(encryptedUpdate.getSenderId(), encryptedUpdate.getDate(), + encryptedUpdate.getUpdate()); } else if (message instanceof RouterDifferenceStart) { return onDifferenceStart(); } else if (message instanceof RouterDifferenceEnd) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 7b0c5f68ae..37d5ee3272 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiEncryptedContent; import im.actor.core.entity.Group; import im.actor.core.entity.Message; import im.actor.core.entity.Peer; @@ -23,6 +24,7 @@ import im.actor.core.modules.messaging.router.entity.RouterDeletedMessages; import im.actor.core.modules.messaging.router.entity.RouterDifferenceEnd; import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart; +import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; @@ -65,6 +67,10 @@ public Promise onUpdate(Update update) { return ask(new RouterMessageUpdate(update)); } + public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + return ask(new RouterEncryptedUpdate(senderId, date, update)); + } + public Promise onDifferenceEnd() { return ask(new RouterDifferenceEnd()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java new file mode 100644 index 0000000000..7c0b915c98 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterEncryptedUpdate.java @@ -0,0 +1,30 @@ +package im.actor.core.modules.messaging.router.entity; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterEncryptedUpdate implements AskMessage { + + private int senderId; + private long date; + private ApiEncryptedContent update; + + public RouterEncryptedUpdate(int senderId, long date, ApiEncryptedContent update) { + this.senderId = senderId; + this.date = date; + this.update = update; + } + + public int getSenderId() { + return senderId; + } + + public long getDate() { + return date; + } + + public ApiEncryptedContent getUpdate() { + return update; + } +} From 3c72e2074c652153f5489405b3a993928bc65c0e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 19:49:30 +0300 Subject: [PATCH 40/81] feat(core): Secret Chats: typings, delete, chat clear --- actor-sdk/sdk-api/actor.json | 61 ++++++- .../models/im/actor/api/scheme.mps | 49 +++++- .../messages/MessagesDefaultFragment.java | 15 +- .../toolbar/ChatToolbarFragment.java | 2 +- .../main/java/im/actor/core/Messenger.java | 26 +-- .../actor/core/api/ApiEncryptedContent.java | 1 + .../actor/core/api/ApiEncryptedDeleteAll.java | 55 +++++++ .../core/api/ApiEncryptedDeleteContent.java | 11 +- .../java/im/actor/core/api/ApiServiceEx.java | 1 + .../core/api/ApiServiceTimerChanged.java | 65 ++++++++ .../modules/encryption/EncryptedUpdates.java | 38 +++++ .../modules/encryption/EncryptionModule.java | 49 +++++- .../encryption/EncryptionProcessor.java | 43 +---- .../ratchet/EncryptedUserActor.java | 41 +---- .../modules/messaging/MessagesModule.java | 153 +++++++++++------- .../messaging/MessagesProcessorEncrypted.java | 8 +- .../messaging/actions/MessageDeleteActor.java | 36 +++-- .../messaging/actions/SenderActor.java | 5 +- .../modules/messaging/router/RouterActor.java | 25 +++ .../modules/messaging/router/RouterInt.java | 6 +- .../core/modules/typing/TypingActor.java | 116 ++++++++++++- .../core/modules/typing/TypingModule.java | 14 +- 22 files changed, 625 insertions(+), 195 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index c6cbfc11fa..c0ca8999a9 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -4738,6 +4738,33 @@ "attributes": [] } }, + { + "type": "struct", + "content": { + "name": "ServiceTimerChanged", + "doc": [ + "Service Message about timer changed", + { + "type": "reference", + "argument": "timerMs", + "category": "full", + "description": " Timer in milliseconds" + } + ], + "trait": { + "name": "ServiceEx", + "key": 23 + }, + "expandable": "true", + "attributes": [ + { + "type": "int32", + "id": 1, + "name": "timerMs" + } + ] + } + }, { "type": "struct", "content": { @@ -19507,7 +19534,10 @@ "name": "receiverId" }, { - "type": "int64", + "type": { + "type": "list", + "childType": "int64" + }, "id": 2, "name": "rid" } @@ -19593,6 +19623,35 @@ } ] } + }, + { + "type": "struct", + "content": { + "name": "EncryptedDeleteAll", + "doc": [ + "Encrypted message about clearing chat", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 6 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 3decd00296..49fec511bc 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -4341,6 +4341,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -16996,7 +17018,9 @@ - + + + @@ -17082,6 +17106,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java index 989cf29914..e5a63309b5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/messages/MessagesDefaultFragment.java @@ -15,6 +15,7 @@ import im.actor.core.entity.Message; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.entity.content.AbsContent; import im.actor.core.entity.content.TextContent; import im.actor.core.entity.content.UnsupportedContent; @@ -120,10 +121,18 @@ public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { } } - menu.findItem(R.id.copy).setVisible(isAllText); menu.findItem(R.id.quote).setVisible(isAllText); - menu.findItem(R.id.forward).setVisible(selected.length == 1 || isAllText); - menu.findItem(R.id.like).setVisible(selected.length == 1); + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + menu.findItem(R.id.copy).setVisible(false); + menu.findItem(R.id.forward).setVisible(false); + menu.findItem(R.id.like).setVisible(false); + } else { + menu.findItem(R.id.copy).setVisible(isAllText); + menu.findItem(R.id.forward).setVisible(selected.length == 1 || isAllText); + menu.findItem(R.id.like).setVisible(selected.length == 1); + } + return false; } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index b5aa7cc0e8..8a27e39d87 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -182,7 +182,7 @@ public void onResume() { if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getTyping(user.getId())); } else { - // TODO: Implement + bindPrivateTyping(barTyping, barTypingContainer, barSubtitle, messenger().getSecretTyping(user.getId())); } // Refresh menu on contact state change diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 97144815f0..6304af73d1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -68,6 +68,7 @@ import im.actor.core.viewmodel.UploadFileCallback; import im.actor.core.viewmodel.UploadFileVM; import im.actor.core.viewmodel.UploadFileVMCallback; +import im.actor.core.viewmodel.UserTypingVM; import im.actor.core.viewmodel.UserVM; import im.actor.runtime.actors.ActorSystem; import im.actor.runtime.actors.messages.Void; @@ -493,7 +494,7 @@ public DialogGroupsVM getDialogGroupsVM() { } /** - * Get private chat ViewModel + * Get private chat typing ViewModel * * @param uid chat's User Id * @return ValueModel of Boolean for typing state @@ -504,6 +505,18 @@ public ValueModel getTyping(int uid) { return modules.getTypingModule().getTyping(uid).getTyping(); } + /** + * Get Secret chat typing View Model + * + * @param uid chat's User Id + * @return ValueModel of Boolean for typing state + */ + @NotNull + @ObjectiveCName("getSecretTypingWithUid:") + public ValueModel getSecretTyping(int uid) { + return modules.getTypingModule().getSecretTyping(uid).getTyping(); + } + /** * Get group chat ViewModel * @@ -1748,17 +1761,6 @@ public Command revokeIntegrationToken(int gid) { .failure(e -> callback.onError(e)); } - /** - * Check if chat with bot is started - * - * @param uid bot user id - * @return is chat with bot started - */ - @ObjectiveCName("isStartedWithUid:") - public Promise isStarted(int uid) { - return modules.getMessagesModule().chatIsEmpty(Peer.user(uid)); - } - ////////////////////////////////////// // Blocked List diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java index bdf91eb1ee..9bc1dbfe8d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -25,6 +25,7 @@ public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { case 3: return Bser.parse(new ApiEncryptedDeleteContent(), content); case 4: return Bser.parse(new ApiEncryptedReceived(), content); case 5: return Bser.parse(new ApiEncryptedRead(), content); + case 6: return Bser.parse(new ApiEncryptedDeleteAll(), content); default: return new ApiEncryptedContentUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java new file mode 100644 index 0000000000..ecd28d5296 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteAll.java @@ -0,0 +1,55 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedDeleteAll extends ApiEncryptedContent { + + private int receiverId; + + public ApiEncryptedDeleteAll(int receiverId) { + this.receiverId = receiverId; + } + + public ApiEncryptedDeleteAll() { + + } + + public int getHeader() { + return 6; + } + + public int getReceiverId() { + return this.receiverId; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + } + + @Override + public String toString() { + String res = "struct EncryptedDeleteAll{"; + res += "receiverId=" + this.receiverId; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java index 1695294074..5c9b36e82b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedDeleteContent.java @@ -17,9 +17,9 @@ public class ApiEncryptedDeleteContent extends ApiEncryptedContent { private int receiverId; - private long rid; + private List rid; - public ApiEncryptedDeleteContent(int receiverId, long rid) { + public ApiEncryptedDeleteContent(int receiverId, @NotNull List rid) { this.receiverId = receiverId; this.rid = rid; } @@ -36,20 +36,21 @@ public int getReceiverId() { return this.receiverId; } - public long getRid() { + @NotNull + public List getRid() { return this.rid; } @Override public void parse(BserValues values) throws IOException { this.receiverId = values.getInt(1); - this.rid = values.getLong(2); + this.rid = values.getRepeatedLong(2); } @Override public void serialize(BserWriter writer) throws IOException { writer.writeInt(1, this.receiverId); - writer.writeLong(2, this.rid); + writer.writeRepeatedLong(2, this.rid); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java index a970fe563d..3e7ebee995 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceEx.java @@ -34,6 +34,7 @@ public static ApiServiceEx fromBytes(byte[] src) throws IOException { case 16: return Bser.parse(new ApiServiceExPhoneCall(), content); case 20: return Bser.parse(new ApiServiceExChatArchived(), content); case 21: return Bser.parse(new ApiServiceExChatRestored(), content); + case 23: return Bser.parse(new ApiServiceTimerChanged(), content); default: return new ApiServiceExUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java new file mode 100644 index 0000000000..719f637d90 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiServiceTimerChanged.java @@ -0,0 +1,65 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiServiceTimerChanged extends ApiServiceEx { + + private int timerMs; + + public ApiServiceTimerChanged(int timerMs) { + this.timerMs = timerMs; + } + + public ApiServiceTimerChanged() { + + } + + public int getHeader() { + return 23; + } + + public int getTimerMs() { + return this.timerMs; + } + + @Override + public void parse(BserValues values) throws IOException { + this.timerMs = values.getInt(1); + if (values.hasRemaining()) { + setUnmappedObjects(values.buildRemaining()); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.timerMs); + if (this.getUnmappedObjects() != null) { + SparseArray unmapped = this.getUnmappedObjects(); + for (int i = 0; i < unmapped.size(); i++) { + int key = unmapped.keyAt(i); + writer.writeUnmapped(key, unmapped.get(key)); + } + } + } + + @Override + public String toString() { + String res = "struct ServiceTimerChanged{"; + res += "timerMs=" + this.timerMs; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java new file mode 100644 index 0000000000..526a5b6dc9 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java @@ -0,0 +1,38 @@ +package im.actor.core.modules.encryption; + +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.modules.AbsModule; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.MessagesProcessorEncrypted; +import im.actor.runtime.Log; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class EncryptedUpdates extends AbsModule { + + private EncryptedSequenceProcessor[] processors; + + public EncryptedUpdates(ModuleContext context) { + super(context); + + processors = new EncryptedSequenceProcessor[]{ + new MessagesProcessorEncrypted(context) + }; + } + + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); + + Promise res = null; + for (EncryptedSequenceProcessor s : processors) { + res = s.onUpdate(senderId, date, update); + if (res != null) { + break; + } + } + if (res == null) { + res = Promise.success(null); + } + return res; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index ad86a8eb54..05e9673a82 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -6,7 +6,6 @@ import im.actor.core.api.ApiEncryptedBox; import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSendEncryptedPackage; @@ -18,7 +17,8 @@ import im.actor.core.modules.encryption.ratchet.KeyManager; import im.actor.core.modules.encryption.ratchet.SessionManager; import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; -import im.actor.runtime.function.Function; +import im.actor.core.util.RandomUtils; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; import static im.actor.runtime.actors.ActorSystem.system; @@ -28,6 +28,7 @@ public class EncryptionModule extends AbsModule { private KeyManager keyManager; private SessionManager sessionManager; private EncryptedMsg encryption; + private EncryptedUpdates encryptedUpdates; private final HashMap users = new HashMap<>(); @@ -39,6 +40,7 @@ public void run() { keyManager = new KeyManager(context()); sessionManager = new SessionManager(context()); encryption = new EncryptedMsg(context()); + encryptedUpdates = new EncryptedUpdates(context()); } public KeyManager getKeyManager() { @@ -63,6 +65,10 @@ public EncryptedUser getEncryptedUser(int uid) { } } + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { + return encryptedUpdates.onUpdate(senderId, date, update); + } + public Promise encrypt(List uids, ApiEncryptedContent message) { return getEncryption().encrypt(uids, message); } @@ -71,23 +77,46 @@ public Promise decrypt(int uid, ApiEncryptedBox encryptedBo return getEncryption().decrypt(uid, encryptedBox); } + public Promise doSend(ApiEncryptedContent content, int uid) { + return doSend(RandomUtils.nextRid(), content, uid, false); + } + + public Promise doSend(ApiEncryptedContent content, int uid, boolean autoPostUpdate) { + return doSend(RandomUtils.nextRid(), content, uid, autoPostUpdate); + } + + public Promise doSend(ApiEncryptedContent content, List uids) { + return doSend(RandomUtils.nextRid(), content, uids); + } + public Promise doSend(long rid, ApiEncryptedContent content, int uid) { + return doSend(rid, content, uid, false); + } + + public Promise doSend(long rid, ApiEncryptedContent content, int uid, + boolean autoPostUpdate) { ArrayList receiver = new ArrayList<>(); receiver.add(uid); -// if (uid != myUid()) { -// receiver.add(myUid()); -// } - return doSend(rid, content, receiver); + if (uid != myUid()) { + receiver.add(myUid()); + } + return doSend(rid, content, receiver, autoPostUpdate); } - public Promise doSend(long rid, ApiEncryptedContent content, List uids) { + public Promise doSend(long rid, ApiEncryptedContent content, + List uids) { + return doSend(rid, content, uids, false); + } + + public Promise doSend(long rid, ApiEncryptedContent content, + List uids, boolean autoPostUpdate) { ArrayList outPeers = new ArrayList<>(); for (int i : uids) { outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); } - return encrypt(uids, content).flatMap(encryptedMessage -> { + Promise res = encrypt(uids, content).flatMap(encryptedMessage -> { RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); return api(request).flatMap(r -> { @@ -97,5 +126,9 @@ public Promise doSend(long rid, ApiEncryptedConten return Promise.failure(new RuntimeException("Incorrect keys")); }); }); + if (autoPostUpdate) { + res.then(r -> context().getEncryption().onUpdate(myUid(), r.getDate(), content)); + } + return res; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index 070d49a888..b34ad7a796 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -1,36 +1,19 @@ package im.actor.core.modules.encryption; -import im.actor.core.api.ApiEncryptedContent; -import im.actor.core.api.ApiEncryptedDeleteContent; -import im.actor.core.api.ApiEncryptedEditContent; -import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdatePublicKeyGroupAdded; import im.actor.core.api.updates.UpdatePublicKeyGroupRemoved; -import im.actor.core.entity.Message; -import im.actor.core.entity.MessageState; -import im.actor.core.entity.Peer; -import im.actor.core.entity.content.AbsContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.messaging.MessagesProcessorEncrypted; import im.actor.core.modules.sequence.processor.SequenceProcessor; import im.actor.core.network.parser.Update; -import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; -import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; public class EncryptionProcessor extends AbsModule implements SequenceProcessor { - private EncryptedSequenceProcessor[] processors; - public EncryptionProcessor(ModuleContext context) { super(context); - - processors = new EncryptedSequenceProcessor[]{ - new MessagesProcessorEncrypted(context) - }; } @Override @@ -49,27 +32,13 @@ public Promise process(Update update) { UpdateEncryptedPackage encryptedPackage = (UpdateEncryptedPackage) update; return context().getEncryption() .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) - .flatMap(message -> { - return process(encryptedPackage.getSenderId(), encryptedPackage.getDate(), message); - }).fallback(e -> Promise.success(null)); + .flatMap(message -> context().getEncryption() + .onUpdate( + encryptedPackage.getSenderId(), + encryptedPackage.getDate(), + message)) + .fallback(e -> Promise.success(null)); } return null; } - - public Promise process(int senderId, long date, ApiEncryptedContent update) { - - Log.d("EncryptedUpdates", "Handling update (from #" + senderId + "): " + update); - - Promise res = null; - for (EncryptedSequenceProcessor s : processors) { - res = s.onUpdate(senderId, date, update); - if (res != null) { - break; - } - } - if (res == null) { - res = Promise.success(null); - } - return res; - } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java index 8da08da2b2..afe391dcc1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedUserActor.java @@ -73,8 +73,8 @@ private Promise doEncrypt(byte[] data) { return wrap(PromisesArray.of(theirKeys.getUserKeysGroups()) // Stage 1.1: Filtering invalid key groups and own key groups - .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) && - (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) + .filter(keysGroup -> !ignoredKeyGroups.contains(keysGroup.getKeyGroupId()) + && (!(isOwnUser && keysGroup.getKeyGroupId() == ownKeyGroupId))) // Stage 2: Pick sessions for encryption .map(keysGroup -> { @@ -96,32 +96,10 @@ private Promise doEncrypt(byte[] data) { // Stage 4: Zip Everything together .zip(src -> new EncryptedUserKeys(uid, src, new HashSet<>(ignoredKeyGroups)))); - - -// final byte[] encKey = Crypto.randomBytes(32); -// final byte[] encKeyExtended = keyPrf.calculate(encKey, "ActorPackage", 128); - - // byte[] encData; -// try { -// encData = ActorBox.closeBox(ByteStrings.intToBytes(ownKeyGroupId), data, Crypto.randomBytes(32), new ActorBoxKey(encKeyExtended)); -// } catch (IntegrityException e) { -// e.printStackTrace(); -// throw new RuntimeException(e); -// } - -// Log.d(TAG, "All Encrypted in " + (Runtime.getActorTime() - start) + " ms"); - -// return new EncryptedUserKeys( -// encryptedKeys.toArray(new EncryptedBoxKey[encryptedKeys.size()]), -// ByteStrings.merge(ByteStrings.intToBytes(ownKeyGroupId), encData)); - } private Promise doDecrypt(int senderKeyGroupId, List keys) { -// final int senderKeyGroup = ByteStrings.bytesToInt(ByteStrings.substring(data.getEncryptedPackage(), 0, 4)); -// final byte[] encPackage = ByteStrings.substring(data.getEncryptedPackage(), 4, data.getEncryptedPackage().length - 4); - // // Picking key // @@ -156,21 +134,6 @@ private Promise doDecrypt(int senderKeyGroupId, List return wrap(getSessionManager() .pickSession(uid, senderKeyGroupId, receiverPreKeyId, senderPreKeyId) .flatMap(src -> spawnSession(src).decrypt(finalKey))); - -// .map(decryptedPackage -> { -// byte[] encData; -// try { -// byte[] encKeyExtended = decryptedPackage.length >= 128 -// ? decryptedPackage -// : keyPrf.calculate(decryptedPackage, "ActorPackage", 128); -// encData = ActorBox.openBox(ByteStrings.intToBytes(senderKeyGroupId), encPackage, new ActorBoxKey(encKeyExtended)); -// Log.d(TAG, "Box size: " + encData.length); -// } catch (IOException e) { -// e.printStackTrace(); -// throw new RuntimeException(e); -// } -// return encData; -// }); } private void onKeysUpdated(UserKeys userKeys) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index 7cc463e32b..839f006409 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -10,6 +10,8 @@ import java.util.ArrayList; import java.util.HashMap; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiMessage; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiPeer; @@ -27,6 +29,7 @@ import im.actor.core.api.rpc.ResponseDialogsOrder; import im.actor.core.api.rpc.ResponseLoadArchived; import im.actor.core.api.rpc.ResponseReactionsResponse; +import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeq; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateChatClear; @@ -65,6 +68,7 @@ import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; import im.actor.core.network.RpcInternalException; +import im.actor.core.util.RandomUtils; import im.actor.core.viewmodel.Command; import im.actor.core.viewmodel.CommandCallback; import im.actor.core.viewmodel.ConversationVM; @@ -281,47 +285,52 @@ public void sendDocument(Peer peer, String fileName, String mimeType, FastThumb public Promise updateMessage(final Peer peer, final String message, final long rid) { context().getTypingModule().onMessageSent(peer); - ArrayList mentions = new ArrayList<>(); - TextContent content = TextContent.create(message, null, mentions); - if (peer.getPeerType() == PeerType.GROUP) { - Group group = groups().getValue(peer.getPeerId()); - String lowText = message.toLowerCase(); - for (GroupMember member : group.getMembers()) { - User user = users().getValue(member.getUid()); - if (user.getNick() != null) { - String nick = "@" + user.getNick().toLowerCase(); - // TODO: Better filtering - if (lowText.contains(nick + ":") - || lowText.contains(nick + " ") - || lowText.contains(" " + nick) - || lowText.endsWith(nick) - || lowText.equals(nick)) { - mentions.add(user.getUid()); + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + ApiEncryptedEditContent editContent = new ApiEncryptedEditContent( + peer.getPeerId(), rid, new ApiTextMessage(message, new ArrayList<>(), null)); + + return context().getEncryption().doSend(RandomUtils.nextRid(), editContent, peer.getPeerId()).then(r -> { + context().getEncryption().onUpdate(myUid(), r.getDate(), editContent); + }).flatMap(r -> null); + } else { + ArrayList mentions = new ArrayList<>(); + TextContent content = TextContent.create(message, null, mentions); + if (peer.getPeerType() == PeerType.GROUP) { + Group group = groups().getValue(peer.getPeerId()); + String lowText = message.toLowerCase(); + for (GroupMember member : group.getMembers()) { + User user = users().getValue(member.getUid()); + if (user.getNick() != null) { + String nick = "@" + user.getNick().toLowerCase(); + // TODO: Better filtering + if (lowText.contains(nick + ":") + || lowText.contains(nick + " ") + || lowText.contains(" " + nick) + || lowText.endsWith(nick) + || lowText.equals(nick)) { + mentions.add(user.getUid()); + } } } } + ApiMessage editMessage = new ApiTextMessage(message, content.getMentions(), content.getTextMessageEx()); + + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestUpdateMessage(apiOutPeer, rid, editMessage))) + .flatMap(responseSeqDate -> + updates().applyUpdate( + responseSeqDate.getSeq(), + responseSeqDate.getState(), + new UpdateMessageContentChanged( + new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()), + rid, + editMessage) + )); } - ApiMessage editMessage = new ApiTextMessage(message, content.getMentions(), content.getTextMessageEx()); - - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestUpdateMessage(apiOutPeer, rid, editMessage))) - .flatMap(responseSeqDate -> - updates().applyUpdate( - responseSeqDate.getSeq(), - responseSeqDate.getState(), - new UpdateMessageContentChanged( - new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()), - rid, - editMessage) - )); } - public Promise chatIsEmpty(Peer peer) { - return new Promise<>(resolver -> resolver.result(getConversationEngine(peer).getCount() == 0)); - } - - public void forwardContent(Peer peer, AbsContent content) { sendMessageActor.send(new SenderActor.ForwardContent(peer, content)); } @@ -331,22 +340,30 @@ public void sendSticker(@NotNull Peer peer, sendMessageActor.send(new SenderActor.SendSticker(peer, sticker)); } - public void saveDraft(Peer peer, String draft) { - context().getSettingsModule().setStringValue("drafts_" + peer.getUnuqueId(), draft); + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + context().getSettingsModule().setStringValue("drafts_" + peer.getUnuqueId(), draft); + } } public String loadDraft(Peer peer) { - String res = context().getSettingsModule().getStringValue("drafts_" + peer.getUnuqueId(), null); - if (res == null) { - return ""; + if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { + String res = context().getSettingsModule().getStringValue("drafts_" + peer.getUnuqueId(), null); + if (res == null) { + return ""; + } else { + return res; + } } else { - return res; + return ""; } } public Promise addReaction(final Peer peer, final long rid, final String reaction) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return Promise.failure(new RuntimeException("Unsupported in secret chats")); + } return buildOutPeer(peer) .flatMap(apiOutPeer -> api(new RequestMessageSetReaction(apiOutPeer, rid, reaction))) @@ -360,6 +377,9 @@ public Promise addReaction(final Peer peer, final long rid, final String r } public Promise removeReaction(final Peer peer, final long rid, final String reaction) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return Promise.failure(new RuntimeException("Unsupported in secret chats")); + } return buildOutPeer(peer) .flatMap(apiOutPeer -> api(new RequestMessageRemoveReaction(apiOutPeer, rid, reaction))) @@ -405,27 +425,38 @@ public Promise archiveChat(final Peer peer) { public Promise deleteChat(final Peer peer) { - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestDeleteChat(apiOutPeer))) - .flatMap(responseSeq -> - updates().applyUpdate( - responseSeq.getSeq(), - responseSeq.getState(), - new UpdateChatDelete(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId())) - )); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + // FIXME: Not actually deletes chat from dialog list + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), + peer.getPeerId(), false).map(v -> (Void) null); + } else { + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestDeleteChat(apiOutPeer))) + .flatMap(responseSeq -> + updates().applyUpdate( + responseSeq.getSeq(), + responseSeq.getState(), + new UpdateChatDelete(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId())) + )); + } } public Promise clearChat(final Peer peer) { - return buildOutPeer(peer) - .flatMap(apiOutPeer -> - api(new RequestClearChat(apiOutPeer))) - .flatMap(responseSeq -> - updates().applyUpdate( - responseSeq.getSeq(), - responseSeq.getState(), - new UpdateChatClear(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()))) - ); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), + peer.getPeerId(), false).map(v -> (Void) null); + } else { + return buildOutPeer(peer) + .flatMap(apiOutPeer -> + api(new RequestClearChat(apiOutPeer))) + .flatMap(responseSeq -> + updates().applyUpdate( + responseSeq.getSeq(), + responseSeq.getState(), + new UpdateChatClear(new ApiPeer(peer.getPeerType().toApi(), peer.getPeerId()))) + ); + } } @@ -447,7 +478,9 @@ public void loadMoreArchivedDialogs(final boolean init, final RpcCallback getHistoryActor(peer).loadMore()); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + im.actor.runtime.Runtime.dispatch(() -> getHistoryActor(peer).send(new ConversationHistoryActor.LoadMore())); + } } // diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java index fd6d4ec577..5647f4e74b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -1,6 +1,9 @@ package im.actor.core.modules.messaging; import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiEncryptedReceived; @@ -21,7 +24,10 @@ public Promise onUpdate(int senderId, long date, ApiEncryptedContent updat if (update instanceof ApiEncryptedMessageContent || update instanceof ApiEncryptedReceived || - update instanceof ApiEncryptedRead) { + update instanceof ApiEncryptedRead || + update instanceof ApiEncryptedDeleteContent || + update instanceof ApiEncryptedEditContent || + update instanceof ApiEncryptedDeleteAll) { return context().getMessagesModule().getRouter() .onEncryptedUpdate(senderId, date, update); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java index 6d27b10f5b..0059a7f373 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/MessageDeleteActor.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import im.actor.core.api.ApiEncryptedDeleteContent; import im.actor.core.api.ApiPeer; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.base.SeqUpdate; @@ -15,12 +16,14 @@ import im.actor.core.api.rpc.ResponseSeq; import im.actor.core.api.updates.UpdateMessageDelete; import im.actor.core.entity.Peer; +import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.messaging.actions.entity.Delete; import im.actor.core.modules.messaging.actions.entity.DeleteStorage; import im.actor.core.modules.ModuleActor; import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; +import im.actor.core.util.RandomUtils; import im.actor.runtime.storage.SyncKeyValue; public class MessageDeleteActor extends ModuleActor { @@ -61,32 +64,33 @@ void saveStorage() { } public void performDelete(final Peer peer, final List rids) { - final ApiOutPeer outPeer = buidOutPeer(peer); - final ApiPeer apiPeer = buildApiPeer(peer); - request(new RequestDeleteMessage(outPeer, rids), new RpcCallback() { - - @Override - public void onResult(ResponseSeq response) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + context().getEncryption().doSend(RandomUtils.nextRid(), + new ApiEncryptedDeleteContent(peer.getPeerId(), rids), peer.getPeerId()).then(r -> { + if (deleteStorage.getPendingDeletions().containsKey(peer)) { + deleteStorage.getPendingDeletions().get(peer).getRids().removeAll(rids); + saveStorage(); + } + }); + } else { + final ApiOutPeer outPeer = buidOutPeer(peer); + final ApiPeer apiPeer = buildApiPeer(peer); + api(new RequestDeleteMessage(outPeer, rids)).then(r -> { if (deleteStorage.getPendingDeletions().containsKey(peer)) { deleteStorage.getPendingDeletions().get(peer).getRids().removeAll(rids); saveStorage(); } - updates().onUpdateReceived(new SeqUpdate(response.getSeq(),response.getState(), - UpdateMessageDelete.HEADER,new UpdateMessageDelete(apiPeer, rids).toByteArray())); - } - - @Override - public void onError(RpcException e) { - - } - }); + updates().onUpdateReceived(new SeqUpdate(r.getSeq(), r.getState(), + UpdateMessageDelete.HEADER, new UpdateMessageDelete(apiPeer, rids).toByteArray())); + }); + } } public void onDeleteMessage(Peer peer, List rids) { // Add to storage if (!deleteStorage.getPendingDeletions().containsKey(peer)) { - deleteStorage.getPendingDeletions().put(peer, new Delete(peer,new ArrayList())); + deleteStorage.getPendingDeletions().put(peer, new Delete(peer, new ArrayList())); } deleteStorage.getPendingDeletions().get(peer).getRids().addAll(rids); saveStorage(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 1b428fa5ea..81aa6840ca 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -499,10 +499,7 @@ public void onError(RpcException e) { ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), rid, message); - ArrayList receivers = new ArrayList<>(); - // receivers.add(myUid()); - receivers.add(peer.getPeerId()); - context().getEncryption().doSend(rid, content, receivers).then(r -> { + context().getEncryption().doSend(rid, content, peer.getPeerId()).then(r -> { self().send(new MessageSent(peer, rid)); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 888dfee9cb..e5f75e0587 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -8,6 +8,9 @@ import im.actor.core.api.ApiDialogGroup; import im.actor.core.api.ApiDialogShort; import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptedDeleteAll; +import im.actor.core.api.ApiEncryptedDeleteContent; +import im.actor.core.api.ApiEncryptedEditContent; import im.actor.core.api.ApiEncryptedMessageContent; import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiEncryptedReceived; @@ -945,6 +948,28 @@ public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedCont } else { return onMessageRead(Peer.secret(senderId), encryptedRead.getReadDate()); } + } else if (update instanceof ApiEncryptedDeleteContent) { + ApiEncryptedDeleteContent delete = (ApiEncryptedDeleteContent) update; + int destId = senderId; + if (senderId == myUid()) { + destId = delete.getReceiverId(); + } + return onMessageDeleted(Peer.secret(destId), delete.getRid()); + } else if (update instanceof ApiEncryptedEditContent) { + ApiEncryptedEditContent editContent = (ApiEncryptedEditContent) update; + int destId = senderId; + if (senderId == myUid()) { + destId = editContent.getReceiverId(); + } + return onContentUpdate(Peer.secret(destId), editContent.getRid(), + AbsContent.fromMessage(editContent.getMessage())); + } else if (update instanceof ApiEncryptedDeleteAll) { + ApiEncryptedDeleteAll deleteAll = (ApiEncryptedDeleteAll) update; + int destId = senderId; + if (senderId == myUid()) { + destId = deleteAll.getReceiverId(); + } + return onChatClear(Peer.secret(destId)); } return Promise.success(null); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 37d5ee3272..8d54dce68a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -42,11 +42,9 @@ import static im.actor.runtime.actors.ActorSystem.system; public class RouterInt extends ActorInterface implements BusSubscriber { - - private final ModuleContext context; - + public RouterInt(final ModuleContext context) { - this.context = context; + setDest(system().actorOf("actor/router", () -> new RouterActor(context))); context.getEvents().subscribe(this, PeerChatOpened.EVENT); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java index d91d615bc5..d28f3fd24c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingActor.java @@ -26,8 +26,10 @@ public static ActorRef get(final ModuleContext messenger) { private static final int TYPING_TEXT_TIMEOUT = 7000; - private HashMap typingsCancellables = new HashMap<>(); private HashSet typings = new HashSet<>(); + private HashMap typingsCancellables = new HashMap<>(); + private HashSet secretTypings = new HashSet<>(); + private HashMap secretTypingsCancellables = new HashMap<>(); private HashMap> groupTypings = new HashMap<>(); private HashMap> groupCancellables = new HashMap<>(); @@ -71,6 +73,42 @@ private void stopPrivateTyping(int uid) { } } + @Verified + private void privateSecretTyping(int uid, ApiTypingType type) { + // Support only text typings + if (type != ApiTypingType.TEXT) { + return; + } + + if (getUser(uid) == null) { + return; + } + + if (!secretTypings.contains(uid)) { + secretTypings.add(uid); + + context().getTypingModule().getSecretTyping(uid).getTyping().change(true); + } + + if (secretTypingsCancellables.containsKey(uid)) { + secretTypingsCancellables.remove(uid).cancel(); + } + secretTypingsCancellables.put(uid, schedule(new StopSecretTyping(uid), TYPING_TEXT_TIMEOUT)); + } + + @Verified + private void stopPrivateSecretTyping(int uid) { + if (secretTypings.contains(uid)) { + secretTypings.remove(uid); + + if (secretTypingsCancellables.containsKey(uid)) { + secretTypingsCancellables.remove(uid).cancel(); + } + + context().getTypingModule().getSecretTyping(uid).getTyping().change(false); + } + } + @Verified private void groupTyping(int gid, int uid, ApiTypingType type) { // Support only text typings @@ -151,12 +189,18 @@ public void onReceive(Object message) { if (message instanceof PrivateTyping) { PrivateTyping typing = (PrivateTyping) message; privateTyping(typing.getUid(), typing.getType()); + } else if (message instanceof PrivateSecretTyping) { + PrivateSecretTyping typing = (PrivateSecretTyping) message; + privateSecretTyping(typing.getUid(), typing.getType()); } else if (message instanceof GroupTyping) { GroupTyping typing = (GroupTyping) message; groupTyping(typing.getGid(), typing.getUid(), typing.getType()); } else if (message instanceof StopTyping) { StopTyping typing = (StopTyping) message; stopPrivateTyping(typing.getUid()); + } else if (message instanceof StopSecretTyping) { + StopSecretTyping secretTyping = (StopSecretTyping) message; + stopPrivateSecretTyping(secretTyping.getUid()); } else if (message instanceof StopGroupTyping) { StopGroupTyping typing = (StopGroupTyping) message; stopGroupTyping(typing.getGid(), typing.getUid()); @@ -194,6 +238,36 @@ public int hashCode() { } } + public static class StopSecretTyping { + + private int uid; + + public StopSecretTyping(int uid) { + this.uid = uid; + } + + public int getUid() { + return uid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StopSecretTyping that = (StopSecretTyping) o; + + if (uid != that.uid) return false; + + return true; + } + + @Override + public int hashCode() { + return uid; + } + } + public static class StopGroupTyping { private int gid; private int uid; @@ -233,6 +307,7 @@ public int hashCode() { } public static class PrivateTyping { + private int uid; private ApiTypingType type; @@ -270,6 +345,45 @@ public int hashCode() { } } + public static class PrivateSecretTyping { + + private int uid; + private ApiTypingType type; + + public PrivateSecretTyping(int uid, ApiTypingType type) { + this.uid = uid; + this.type = type; + } + + public int getUid() { + return uid; + } + + public ApiTypingType getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PrivateTyping that = (PrivateTyping) o; + + if (type != that.type) return false; + if (uid != that.uid) return false; + + return true; + } + + @Override + public int hashCode() { + int result = uid; + result = 31 * result + type.getValue(); + return result; + } + } + public static class GroupTyping { private int gid; private int uid; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java index dabfdde2c8..1de205d992 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/typing/TypingModule.java @@ -19,8 +19,9 @@ public class TypingModule extends AbsModule { private ActorRef ownTypingActor; private ActorRef typingActor; - private HashMap uids = new HashMap<>(); - private HashMap groups = new HashMap<>(); + private final HashMap uids = new HashMap<>(); + private final HashMap sec_uids = new HashMap<>(); + private final HashMap groups = new HashMap<>(); public TypingModule(final ModuleContext context) { super(context); @@ -47,6 +48,15 @@ public UserTypingVM getTyping(int uid) { } } + public UserTypingVM getSecretTyping(int uid) { + synchronized (sec_uids) { + if (!sec_uids.containsKey(uid)) { + sec_uids.put(uid, new UserTypingVM(uid)); + } + return sec_uids.get(uid); + } + } + public void onTyping(Peer peer) { ownTypingActor.send(new OwnTypingActor.Typing(peer)); } From b0e0c6e44f9beb7c6745f39a0e02a98a8d82504f Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 14 Jul 2016 20:13:40 +0300 Subject: [PATCH 41/81] feat(android): Restyle DialogView --- .../controllers/dialogs/view/DialogView.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index 9835e64a4e..7c300bda0d 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -53,6 +53,7 @@ public class DialogView extends ListItemBackgroundView Date: Fri, 29 Jul 2016 16:12:41 +0300 Subject: [PATCH 42/81] fix(core): Missing changes --- .../im/actor/core/modules/messaging/dialogs/DialogsActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java index 8df64a5303..2fc23e79b4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/dialogs/DialogsActor.java @@ -349,7 +349,7 @@ private PeerDesc buildPeerDesc(Peer peer) { return new PeerDesc(u.getName(), u.getAvatar(), u.isBot(), peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED); case GROUP: Group g = getGroup(peer.getPeerId()); - return new PeerDesc(g.getTitle(), g.getAvatar(), false, false); + return new PeerDesc(g.getTitle(), g.getAvatar(), false, g.getGroupType() == GroupType.CHANNEL); default: return null; } From d09c8660b235201bb4f84495a6aa7c9e67ca267b Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 16:31:53 +0300 Subject: [PATCH 43/81] fix(core): Fixing incorrect files after merge --- actor-sdk/sdk-api/actor.json | 1086 ++++++++++++----- .../conversation/ChatActivity.java | 1 + .../controllers/dialogs/view/DialogView.java | 12 +- .../modules/encryption/EncryptionModule.java | 2 +- .../modules/messaging/MessagesModule.java | 2 +- 5 files changed, 806 insertions(+), 297 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index c0ca8999a9..abfbdfceee 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -6598,6 +6598,32 @@ ] } }, + { + "type": "update", + "content": { + "name": "ChatDropCache", + "header": 2690, + "doc": [ + "Update about cache drop", + { + "type": "reference", + "argument": "peer", + "category": "full", + "description": " Destination peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "Peer" + }, + "id": 1, + "name": "peer" + } + ] + } + }, { "type": "update", "content": { @@ -6608,7 +6634,7 @@ { "type": "reference", "argument": "dialogs", - "category": "full", + "category": "compact", "description": " New dialgos list" } ], @@ -7873,12 +7899,116 @@ ] } }, + { + "type": "enum", + "content": { + "name": "GroupPermissions", + "values": [ + { + "name": "SEND_MESSAGE", + "id": 1 + }, + { + "name": "CLEAR", + "id": 2 + }, + { + "name": "LEAVE", + "id": 3 + }, + { + "name": "DELETE", + "id": 4 + }, + { + "name": "JOIN", + "id": 5 + }, + { + "name": "VIEW_INFO", + "id": 6 + } + ] + } + }, + { + "type": "enum", + "content": { + "name": "GroupFullPermissions", + "values": [ + { + "name": "EDIT_INFO", + "id": 1 + }, + { + "name": "VIEW_MEMBERS", + "id": 2 + }, + { + "name": "INVITE_MEMBERS", + "id": 3 + }, + { + "name": "INVITE_VIA_LINK", + "id": 4 + }, + { + "name": "CALL", + "id": 5 + }, + { + "name": "EDIT_ADMIN_SETTINGS", + "id": 6 + }, + { + "name": "VIEW_ADMINS", + "id": 7 + }, + { + "name": "EDIT_ADMINS", + "id": 8 + }, + { + "name": "KICK_INVITED", + "id": 9 + }, + { + "name": "KICK_ANYONE", + "id": 10 + }, + { + "name": "EDIT_FOREIGN", + "id": 11 + }, + { + "name": "DELETE_FOREIGN", + "id": 12 + } + ] + } + }, { "type": "struct", "content": { "name": "Group", "doc": [ "Group information", + "", + "Permissions.", + "Permissions of this structure is about group messages operation, such as", + "ability to send messages, clear chat, leave group and so on. This operations", + "Can be held outside of the Group Info page.", + "", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canSendMessage. Default is FALSE.", + "1 - canClear. Default is FALSE.", + "2 - canLeave. Default is FALSE.", + "3 - canDelete. Default is FALSE.", + "4 - canJoin. Default is FALSE.", + "5 - canViewInfo. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -7929,9 +8059,15 @@ }, { "type": "reference", - "argument": "canSendMessage", + "argument": "permissions", + "category": "full", + "description": " Permissions of group object" + }, + { + "type": "reference", + "argument": "isDeleted", "category": "full", - "description": " Can user send messages. Default is equals isMember for Group and false for channels." + "description": " Is this group deleted" }, { "type": "reference", @@ -8045,10 +8181,18 @@ { "type": { "type": "opt", - "childType": "bool" + "childType": "int64" }, "id": 26, - "name": "canSendMessage" + "name": "permissions" + }, + { + "type": { + "type": "opt", + "childType": "bool" + }, + "id": 27, + "name": "isDeleted" }, { "type": { @@ -8067,7 +8211,8 @@ "childType": "bool" }, "id": 16, - "name": "isAdmin" + "name": "isAdmin", + "deprecated": "true" }, { "type": { @@ -8075,7 +8220,8 @@ "childType": "userId" }, "id": 8, - "name": "creatorUid" + "name": "creatorUid", + "deprecated": "true" }, { "type": { @@ -8086,7 +8232,8 @@ } }, "id": 9, - "name": "members" + "name": "members", + "deprecated": "true" }, { "type": { @@ -8094,7 +8241,8 @@ "childType": "date" }, "id": 10, - "name": "createDate" + "name": "createDate", + "deprecated": "true" }, { "type": { @@ -8102,7 +8250,8 @@ "childType": "string" }, "id": 17, - "name": "theme" + "name": "theme", + "deprecated": "true" }, { "type": { @@ -8110,7 +8259,8 @@ "childType": "string" }, "id": 18, - "name": "about" + "name": "about", + "deprecated": "true" } ] } @@ -8121,6 +8271,26 @@ "name": "GroupFull", "doc": [ "Goup Full information", + "Permissions.", + "Idea of Group Full mermissions is about Group Info pages. This permissions", + "are usefull only when trying to view and update group settings and not related", + "to chat messages itself.", + "Default value is ZERO, Opposide iz ONE. If Default is FALSE then ONE == TRUE.", + "If default is TRUE then ONE == FALSE.", + "Bits:", + "0 - canEditInfo. Default is FALSE.", + "1 - canViewMembers. Default is FALSE.", + "2 - canInviteMembers. Default is FALSE.", + "3 - canInviteViaLink. Default is FALSE.", + "4 - canCall. Default is FALSE.", + "5 - canEditAdminSettings. Default is FALSE.", + "6 - canViewAdmins. Default is FALSE.", + "7 - canEditAdmins. Default is FALSE.", + "8 - canKickInvited. Default is FALSE.", + "9 - canKickAnyone. Default is FALSE.", + "10 - canEditForeign. Default is FALSE.", + "11 - canDeleteForeign. Default is FALSE.", + "", { "type": "reference", "argument": "id", @@ -8137,7 +8307,7 @@ "type": "reference", "argument": "ownerUid", "category": "full", - "description": " Group owner" + "description": " Optional group owner" }, { "type": "reference", @@ -8161,27 +8331,28 @@ "type": "reference", "argument": "isAsyncMembers", "category": "full", - "description": " Is Members need to be loaded asynchronous." + "description": " Is Members need to be loaded asynchronous. Default is false." }, { "type": "reference", - "argument": "canViewMembers", + "argument": "isSharedHistory", "category": "full", - "description": " Can current user view members of the group. Default is true." + "description": " Is history shared among all users. Default is false." }, { "type": "reference", - "argument": "canInvitePeople", + "argument": "shortName", "category": "full", - "description": " Can current user invite new people. Default is true." + "description": " Group's short name" }, { "type": "reference", - "argument": "isSharedHistory", + "argument": "permissions", "category": "full", - "description": " Is history shared among all users." + "description": " Group Permissions" } ], + "expandable": "true", "attributes": [ { "type": { @@ -8201,8 +8372,11 @@ }, { "type": { - "type": "alias", - "childType": "userId" + "type": "opt", + "childType": { + "type": "alias", + "childType": "userId" + } }, "id": 5, "name": "ownerUid" @@ -8258,24 +8432,24 @@ "type": "opt", "childType": "bool" }, - "id": 8, - "name": "canViewMembers" + "id": 10, + "name": "isSharedHistory" }, { "type": { "type": "opt", - "childType": "bool" + "childType": "string" }, - "id": 9, - "name": "canInvitePeople" + "id": 14, + "name": "shortName" }, { "type": { "type": "opt", - "childType": "bool" + "childType": "int64" }, - "id": 10, - "name": "isSharedHistory" + "id": 27, + "name": "permissions" } ] } @@ -8345,7 +8519,7 @@ "doc": [ { "type": "reference", - "argument": "members", + "argument": "users", "category": "full", "description": " Group members" }, @@ -8357,6 +8531,17 @@ } ], "attributes": [ + { + "type": { + "type": "list", + "childType": { + "type": "struct", + "childType": "Member" + } + }, + "id": 3, + "name": "members" + }, { "type": { "type": "list", @@ -8366,7 +8551,7 @@ } }, "id": 1, - "name": "members" + "name": "users" }, { "type": { @@ -8674,6 +8859,46 @@ ] } }, + { + "type": "update", + "content": { + "name": "GroupShortNameChanged", + "header": 2628, + "doc": [ + "Group's short name changed", + { + "type": "reference", + "argument": "groupId", + "category": "full", + "description": " Group Id" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": " Group short name" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "groupId" + }, + "id": 1, + "name": "groupId" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "update", "content": { @@ -8743,21 +8968,15 @@ { "type": "update", "content": { - "name": "GroupCanSendMessagesChanged", - "header": 2624, + "name": "GroupDeleted", + "header": 2658, "doc": [ - "Update about can send messages changed", + "Update about group deleted", { "type": "reference", "argument": "groupId", "category": "full", "description": " Group Id" - }, - { - "type": "reference", - "argument": "canSendMessages", - "category": "full", - "description": " Can send messages" } ], "attributes": [ @@ -8768,11 +8987,6 @@ }, "id": 1, "name": "groupId" - }, - { - "type": "bool", - "id": 2, - "name": "canSendMessages" } ] } @@ -8780,10 +8994,10 @@ { "type": "update", "content": { - "name": "GroupCanViewMembersChanged", - "header": 2625, + "name": "GroupPermissionsChanged", + "header": 2663, "doc": [ - "Update about can view members changed", + "Update about group permissions changed", { "type": "reference", "argument": "groupId", @@ -8792,9 +9006,9 @@ }, { "type": "reference", - "argument": "canViewMembers", + "argument": "permissions", "category": "full", - "description": " Can view members" + "description": " New Permissions" } ], "attributes": [ @@ -8807,9 +9021,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canViewMembers" + "name": "permissions" } ] } @@ -8817,10 +9031,10 @@ { "type": "update", "content": { - "name": "GroupCanInviteMembersChanged", - "header": 2626, + "name": "GroupFullPermissionsChanged", + "header": 2664, "doc": [ - "Update about can invite members changed", + "Update about Full Group permissions changed", { "type": "reference", "argument": "groupId", @@ -8829,9 +9043,9 @@ }, { "type": "reference", - "argument": "canInviteMembers", + "argument": "permissions", "category": "full", - "description": " Can invite members" + "description": " New Permissions" } ], "attributes": [ @@ -8844,9 +9058,9 @@ "name": "groupId" }, { - "type": "bool", + "type": "int64", "id": 2, - "name": "canInviteMembers" + "name": "permissions" } ] } @@ -9568,6 +9782,50 @@ ] } }, + { + "type": "rpc", + "content": { + "name": "EditGroupShortName", + "header": 2793, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Edit Group Short Name", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "shortName", + "category": "full", + "description": "New group's short name" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "opt", + "childType": "string" + }, + "id": 2, + "name": "shortName" + } + ] + } + }, { "type": "rpc", "content": { @@ -9803,24 +10061,430 @@ "name": "SeqDate" }, "doc": [ - "Leaving group", + "Leaving group", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": "Random Id of operation" + }, + { + "type": "reference", + "argument": "optimizations", + "category": "full", + "description": "Enabled Optimizations" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "alias", + "childType": "randomId" + }, + "id": 2, + "name": "rid" + }, + { + "type": { + "type": "list", + "childType": { + "type": "enum", + "childType": "UpdateOptimization" + } + }, + "id": 3, + "name": "optimizations" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "LeaveAndDelete", + "header": 2721, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Leave group and Delete Chat", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "KickUser", + "header": 71, + "response": { + "type": "reference", + "name": "SeqDate" + }, + "doc": [ + "Kicking user from group", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "user", + "category": "full", + "description": "users for removing" + }, + { + "type": "reference", + "argument": "rid", + "category": "full", + "description": "Random Id of operation" + }, + { + "type": "reference", + "argument": "optimizations", + "category": "full", + "description": "Enabled Optimizations" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "alias", + "childType": "randomId" + }, + "id": 4, + "name": "rid" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 3, + "name": "user" + }, + { + "type": { + "type": "list", + "childType": { + "type": "enum", + "childType": "UpdateOptimization" + } + }, + "id": 5, + "name": "optimizations" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "JoinGroupByPeer", + "header": 2722, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Join group by peer", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + } + ] + } + }, + { + "type": "comment", + "content": "Administration" + }, + { + "type": "rpc", + "content": { + "name": "MakeUserAdmin", + "header": 2784, + "response": { + "type": "reference", + "name": "SeqDate" + }, + "doc": [ + "Make user admin", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "DismissUserAdmin", + "header": 2791, + "response": { + "type": "reference", + "name": "Seq" + }, + "doc": [ + "Dismissing user admin", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "userPeer", + "category": "full", + "description": "User's peer" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "userPeer" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "TransferOwnership", + "header": 2789, + "response": { + "type": "reference", + "name": "SeqDate" + }, + "doc": [ + "Transfer ownership of group", + { + "type": "reference", + "argument": "groupPeer", + "category": "full", + "description": "Group's peer" + }, + { + "type": "reference", + "argument": "newOwner", + "category": "full", + "description": "New group's owner" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "GroupOutPeer" + }, + "id": 1, + "name": "groupPeer" + }, + { + "type": { + "type": "struct", + "childType": "UserOutPeer" + }, + "id": 2, + "name": "newOwner" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "AdminSettings", + "doc": [ + "Admin Settings", + { + "type": "reference", + "argument": "showAdminsToMembers", + "category": "full", + "description": " Show admins in member list" + }, + { + "type": "reference", + "argument": "canMembersInvite", + "category": "full", + "description": " Can members of a group invite people" + }, + { + "type": "reference", + "argument": "canMembersEditGroupInfo", + "category": "full", + "description": " Can members edit group info" + }, + { + "type": "reference", + "argument": "canAdminsEditGroupInfo", + "category": "full", + "description": " Can admins edit group info" + }, + { + "type": "reference", + "argument": "showJoinLeaveMessages", + "category": "full", + "description": " Should join and leave messages be visible to members" + } + ], + "expandable": "true", + "attributes": [ + { + "type": "bool", + "id": 1, + "name": "showAdminsToMembers" + }, + { + "type": "bool", + "id": 2, + "name": "canMembersInvite" + }, + { + "type": "bool", + "id": 3, + "name": "canMembersEditGroupInfo" + }, + { + "type": "bool", + "id": 4, + "name": "canAdminsEditGroupInfo" + }, + { + "type": "bool", + "id": 5, + "name": "showJoinLeaveMessages" + } + ] + } + }, + { + "type": "rpc", + "content": { + "name": "LoadAdminSettings", + "header": 2790, + "response": { + "type": "anonymous", + "header": 2794, + "doc": [ + "Loaded settings", + { + "type": "reference", + "argument": "settings", + "category": "full", + "description": " Current group admin settings" + } + ], + "attributes": [ + { + "type": { + "type": "struct", + "childType": "AdminSettings" + }, + "id": 1, + "name": "settings" + } + ] + }, + "doc": [ + "Loading administration settings", { "type": "reference", "argument": "groupPeer", "category": "full", "description": "Group's peer" - }, - { - "type": "reference", - "argument": "rid", - "category": "full", - "description": "Random Id of operation" - }, - { - "type": "reference", - "argument": "optimizations", - "category": "full", - "description": "Enabled Optimizations" } ], "attributes": [ @@ -9831,25 +10495,6 @@ }, "id": 1, "name": "groupPeer" - }, - { - "type": { - "type": "alias", - "childType": "randomId" - }, - "id": 2, - "name": "rid" - }, - { - "type": { - "type": "list", - "childType": { - "type": "enum", - "childType": "UpdateOptimization" - } - }, - "id": 3, - "name": "optimizations" } ] } @@ -9857,37 +10502,25 @@ { "type": "rpc", "content": { - "name": "KickUser", - "header": 71, + "name": "SaveAdminSettings", + "header": 2792, "response": { "type": "reference", - "name": "SeqDate" + "name": "Void" }, "doc": [ - "Kicking user from group", + "Save administartion settings", { "type": "reference", "argument": "groupPeer", "category": "full", - "description": "Group's peer" - }, - { - "type": "reference", - "argument": "user", - "category": "full", - "description": "users for removing" - }, - { - "type": "reference", - "argument": "rid", - "category": "full", - "description": "Random Id of operation" + "description": "Group's Peer" }, { "type": "reference", - "argument": "optimizations", + "argument": "settings", "category": "full", - "description": "Enabled Optimizations" + "description": "Group's settings" } ], "attributes": [ @@ -9899,32 +10532,13 @@ "id": 1, "name": "groupPeer" }, - { - "type": { - "type": "alias", - "childType": "randomId" - }, - "id": 4, - "name": "rid" - }, { "type": { "type": "struct", - "childType": "UserOutPeer" - }, - "id": 3, - "name": "user" - }, - { - "type": { - "type": "list", - "childType": { - "type": "enum", - "childType": "UpdateOptimization" - } + "childType": "AdminSettings" }, - "id": 5, - "name": "optimizations" + "id": 2, + "name": "settings" } ] } @@ -9932,25 +10546,19 @@ { "type": "rpc", "content": { - "name": "MakeUserAdmin", - "header": 2784, + "name": "DeleteGroup", + "header": 2795, "response": { "type": "reference", - "name": "SeqDate" + "name": "Seq" }, "doc": [ - "Make user admin", + "Delete Group", { "type": "reference", "argument": "groupPeer", "category": "full", "description": "Group's peer" - }, - { - "type": "reference", - "argument": "userPeer", - "category": "full", - "description": "User's peer" } ], "attributes": [ @@ -9961,14 +10569,6 @@ }, "id": 1, "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "UserOutPeer" - }, - "id": 2, - "name": "userPeer" } ] } @@ -9976,25 +10576,19 @@ { "type": "rpc", "content": { - "name": "TransferOwnership", - "header": 2789, + "name": "ShareHistory", + "header": 2796, "response": { "type": "reference", - "name": "SeqDate" + "name": "Seq" }, "doc": [ - "Transfer ownership of group", + "Share History", { "type": "reference", "argument": "groupPeer", "category": "full", "description": "Group's peer" - }, - { - "type": "reference", - "argument": "newOwner", - "category": "full", - "description": "New group's owner" } ], "attributes": [ @@ -10005,14 +10599,6 @@ }, "id": 1, "name": "groupPeer" - }, - { - "type": { - "type": "struct", - "childType": "UserOutPeer" - }, - "id": 2, - "name": "newOwner" } ] } @@ -11923,39 +12509,9 @@ }, { "type": "reference", - "argument": "title", - "category": "full", - "description": " Peer title" - }, - { - "type": "reference", - "argument": "description", + "argument": "optMatchString", "category": "full", "description": " Description" - }, - { - "type": "reference", - "argument": "membersCount", - "category": "full", - "description": " Members count" - }, - { - "type": "reference", - "argument": "dateCreated", - "category": "full", - "description": " Group Creation Date" - }, - { - "type": "reference", - "argument": "creator", - "category": "full", - "description": " Group Creator uid" - }, - { - "type": "reference", - "argument": "isPublic", - "category": "full", - "description": " Is group public" } ], "attributes": [ @@ -11967,61 +12523,13 @@ "id": 1, "name": "peer" }, - { - "type": "string", - "id": 2, - "name": "title" - }, { "type": { "type": "opt", "childType": "string" }, "id": 3, - "name": "description" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 4, - "name": "membersCount" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "date" - } - }, - "id": 5, - "name": "dateCreated" - }, - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 6, - "name": "creator" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 7, - "name": "isPublic" - }, - { - "type": { - "type": "opt", - "childType": "bool" - }, - "id": 8, - "name": "isJoined" + "name": "optMatchString" } ] } @@ -19063,7 +19571,7 @@ { "type": "reference", "argument": "keyGroupId", - "category": "hidden", + "category": "full", "description": " Key Group Id" } ], @@ -19087,6 +19595,45 @@ ] } }, + { + "type": "struct", + "content": { + "name": "KeyGroupHolder", + "doc": [ + "Key Group Holder", + { + "type": "reference", + "argument": "uid", + "category": "full", + "description": " User's id" + }, + { + "type": "reference", + "argument": "keyGroup", + "category": "full", + "description": " Key Group" + } + ], + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "uid" + }, + { + "type": { + "type": "struct", + "childType": "EncryptionKeyGroup" + }, + "id": 2, + "name": "keyGroup" + } + ] + } + }, { "type": "rpc", "content": { @@ -19096,18 +19643,6 @@ "type": "anonymous", "header": 2664, "doc": [ - { - "type": "reference", - "argument": "seq", - "category": "full", - "description": " seq" - }, - { - "type": "reference", - "argument": "state", - "category": "full", - "description": " state" - }, { "type": "reference", "argument": "date", @@ -19118,7 +19653,7 @@ "type": "reference", "argument": "obsoleteKeyGroups", "category": "full", - "description": " obsolete key groups" + "description": " obsolete key group ids" }, { "type": "reference", @@ -19128,25 +19663,6 @@ } ], "attributes": [ - { - "type": { - "type": "opt", - "childType": "int32" - }, - "id": 1, - "name": "seq" - }, - { - "type": { - "type": "opt", - "childType": { - "type": "alias", - "childType": "seq_state" - } - }, - "id": 2, - "name": "state" - }, { "type": { "type": "opt", @@ -19155,7 +19671,7 @@ "childType": "date" } }, - "id": 3, + "id": 1, "name": "date" }, { @@ -19166,7 +19682,7 @@ "childType": "KeyGroupId" } }, - "id": 4, + "id": 2, "name": "obsoleteKeyGroups" }, { @@ -19174,10 +19690,10 @@ "type": "list", "childType": { "type": "struct", - "childType": "KeyGroupId" + "childType": "KeyGroupHolder" } }, - "id": 5, + "id": 3, "name": "missedKeyGroups" } ] diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 40f0e27244..d264ba51ad 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -14,6 +14,7 @@ import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RelativeLayout; diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java index 7c300bda0d..546b76fa3f 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/dialogs/view/DialogView.java @@ -266,15 +266,7 @@ public DialogLayout buildLayout(Dialog arg, int width, int height) { counterTextPaint.setTextAlign(Paint.Align.CENTER); counterBgPaint = createFilledPaint(style.getDialogsCounterBackgroundColor()); fillPaint = createFilledPaint(Color.BLACK); - placeholderColors = new int[]{ - context.getResources().getColor(R.color.placeholder_0), - context.getResources().getColor(R.color.placeholder_1), - context.getResources().getColor(R.color.placeholder_2), - context.getResources().getColor(R.color.placeholder_3), - context.getResources().getColor(R.color.placeholder_4), - context.getResources().getColor(R.color.placeholder_5), - context.getResources().getColor(R.color.placeholder_6), - }; + placeholderColors = ActorSDK.sharedActor().style.getDefaultAvatarPlaceholders(); avatarBorder = new Paint(); avatarBorder.setStyle(Paint.Style.STROKE); avatarBorder.setAntiAlias(true); @@ -383,7 +375,7 @@ public void onDownloaded(FileSystemReference reference) { if (arg.getSenderId() > 0) { String contentText = messenger().getFormatter().formatContentText(arg.getSenderId(), - arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid()); + arg.getMessageType(), arg.getText().replace("\n", " "), arg.getRelatedUid(), arg.isChannel()); if (arg.getPeer().getPeerType() == PeerType.GROUP) { if (messenger().getFormatter().isLargeDialogMessage(arg.getMessageType())) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 05e9673a82..3203c1f66d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -120,7 +120,7 @@ public Promise doSend(long rid, ApiEncryptedConten RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); return api(request).flatMap(r -> { - if (r.getSeq() != null && r.getSeq() != 0) { + if (r.getDate() != null) { return Promise.success(r); } return Promise.failure(new RuntimeException("Incorrect keys")); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index 839f006409..03cf11838e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -479,7 +479,7 @@ public void loadMoreArchivedDialogs(final boolean init, final RpcCallback getHistoryActor(peer).send(new ConversationHistoryActor.LoadMore())); + im.actor.runtime.Runtime.dispatch(() -> getHistoryActor(peer).loadMore()); } } From 59432560f83844cd76b87ee5da8f031006a3edeb Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 16:56:27 +0300 Subject: [PATCH 44/81] feat(core): Applying diff of encrypted keys --- .../modules/encryption/EncryptionModule.java | 51 ++++++------------- .../encryption/ratchet/KeyManager.java | 15 ++++++ .../encryption/ratchet/KeyManagerActor.java | 36 +++++++++---- .../modules/messaging/MessagesModule.java | 6 +-- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 3203c1f66d..696b1aea72 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -77,58 +77,39 @@ public Promise decrypt(int uid, ApiEncryptedBox encryptedBo return getEncryption().decrypt(uid, encryptedBox); } - public Promise doSend(ApiEncryptedContent content, int uid) { - return doSend(RandomUtils.nextRid(), content, uid, false); + public Promise doSend(ApiEncryptedContent content, int uid) { + return doSend(RandomUtils.nextRid(), content, uid); } - public Promise doSend(ApiEncryptedContent content, int uid, boolean autoPostUpdate) { - return doSend(RandomUtils.nextRid(), content, uid, autoPostUpdate); - } - - public Promise doSend(ApiEncryptedContent content, List uids) { + public Promise doSend(ApiEncryptedContent content, List uids) { return doSend(RandomUtils.nextRid(), content, uids); } - public Promise doSend(long rid, ApiEncryptedContent content, int uid) { - return doSend(rid, content, uid, false); - } - - public Promise doSend(long rid, ApiEncryptedContent content, int uid, - boolean autoPostUpdate) { + public Promise doSend(long rid, ApiEncryptedContent content, int uid) { ArrayList receiver = new ArrayList<>(); receiver.add(uid); if (uid != myUid()) { receiver.add(myUid()); } - return doSend(rid, content, receiver, autoPostUpdate); + return doSend(rid, content, receiver); } - public Promise doSend(long rid, ApiEncryptedContent content, - List uids) { - return doSend(rid, content, uids, false); - } - - public Promise doSend(long rid, ApiEncryptedContent content, - List uids, boolean autoPostUpdate) { + public Promise doSend(long rid, ApiEncryptedContent content, List uids) { ArrayList outPeers = new ArrayList<>(); for (int i : uids) { outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); } - Promise res = encrypt(uids, content).flatMap(encryptedMessage -> { - RequestSendEncryptedPackage request = new RequestSendEncryptedPackage(rid, outPeers, - encryptedMessage.getIgnoredGroups(), encryptedMessage.getEncryptedBox()); - return api(request).flatMap(r -> { - if (r.getDate() != null) { - return Promise.success(r); - } - return Promise.failure(new RuntimeException("Incorrect keys")); - }); - }); - if (autoPostUpdate) { - res.then(r -> context().getEncryption().onUpdate(myUid(), r.getDate(), content)); - } - return res; + return encrypt(uids, content) + .flatMap(m -> api(new RequestSendEncryptedPackage(rid, outPeers, m.getIgnoredGroups(), m.getEncryptedBox()))) + .flatMap(r -> { + if (r.getDate() != null) { + return Promise.success(r.getDate()); + } else { + return getKeyManager().onKeyGroupDiffReceived(r.getMissedKeyGroups(), r.getObsoleteKeyGroups()) + .flatMap(r2 -> doSend(rid, content, uids)); + } + }); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java index 1838f98220..d48ed9d25d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManager.java @@ -1,6 +1,10 @@ package im.actor.core.modules.encryption.ratchet; +import java.util.List; + import im.actor.core.api.ApiEncryptionKeyGroup; +import im.actor.core.api.ApiKeyGroupHolder; +import im.actor.core.api.ApiKeyGroupId; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.ratchet.entity.OwnIdentity; import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; @@ -132,4 +136,15 @@ public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { public Promise onKeyGroupRemoved(int uid, int gid) { return ask(new KeyManagerActor.PublicKeysGroupRemoved(uid, gid)); } + + /** + * Call this when you will receive error during encrytped message sending + * + * @param missed missed key groups + * @param obsolete obsolete key groups + * @return promise of void + */ + public Promise onKeyGroupDiffReceived(List missed, List obsolete) { + return ask(new KeyManagerActor.KeyGroupsDiff(missed, obsolete)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index 9b68643035..83e7f55de6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -8,6 +8,7 @@ import im.actor.core.api.ApiEncryptionKey; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.api.ApiEncryptionKeySignature; +import im.actor.core.api.ApiKeyGroupHolder; import im.actor.core.api.ApiKeyGroupId; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateNewKeyGroup; @@ -36,6 +37,8 @@ import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.ratchet.RatchetKeySignature; +import im.actor.runtime.promise.PromiseTools; +import im.actor.runtime.promise.Promises; import im.actor.runtime.promise.PromisesArray; import im.actor.runtime.function.Tuple2; import im.actor.runtime.storage.KeyValueStorage; @@ -443,16 +446,26 @@ private Promise onPublicKeysGroupRemoved(int uid, int keyGroupId) { return Promise.success(null); } - private Promise onUserKeysChanged(List missed, List obsolete) { - + /** + * Handling response of incorrect keys + * + * @param missed missed key groups + * @param obsolete obsolete key group id + * @return promise of void + */ + private Promise onKeysDiffReceived(List missed, List obsolete) { + ArrayList> res = new ArrayList<>(); -// UserKeys userKeys = getCachedUserKeys(uid); -// if (userKeys == null) { -// return Promise.success(null); -// } + for (ApiKeyGroupHolder gh : missed) { + res.add(onPublicKeysGroupAdded(gh.getUid(), gh.getKeyGroup())); + } + for (ApiKeyGroupId gi : obsolete) { + res.add(onPublicKeysGroupRemoved(gi.getUid(), gi.getKeyGroupId())); + } - return Promise.success(null); + return PromisesArray.ofPromises(res) + .zip(r -> null); } // @@ -585,6 +598,9 @@ public Promise onAsk(Object message) throws Exception { } else if (message instanceof PublicKeysGroupRemoved) { PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; return onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); + } else if (message instanceof KeyGroupsDiff) { + KeyGroupsDiff diff = (KeyGroupsDiff) message; + return onKeysDiffReceived(diff.getMissed(), diff.getObsolete()); } else { return super.onAsk(message); } @@ -736,15 +752,15 @@ public int getKeyGroupId() { public static class KeyGroupsDiff implements AskMessage { - private List missed; + private List missed; private List obsolete; - public KeyGroupsDiff(List missed, List obsolete) { + public KeyGroupsDiff(List missed, List obsolete) { this.missed = missed; this.obsolete = obsolete; } - public List getMissed() { + public List getMissed() { return missed; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index 03cf11838e..29c3498c57 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -291,7 +291,7 @@ public Promise updateMessage(final Peer peer, final String message, final peer.getPeerId(), rid, new ApiTextMessage(message, new ArrayList<>(), null)); return context().getEncryption().doSend(RandomUtils.nextRid(), editContent, peer.getPeerId()).then(r -> { - context().getEncryption().onUpdate(myUid(), r.getDate(), editContent); + context().getEncryption().onUpdate(myUid(), r, editContent); }).flatMap(r -> null); } else { ArrayList mentions = new ArrayList<>(); @@ -428,7 +428,7 @@ public Promise deleteChat(final Peer peer) { if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { // FIXME: Not actually deletes chat from dialog list return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), - peer.getPeerId(), false).map(v -> (Void) null); + peer.getPeerId()).map(v -> (Void) null); } else { return buildOutPeer(peer) .flatMap(apiOutPeer -> @@ -445,7 +445,7 @@ public Promise deleteChat(final Peer peer) { public Promise clearChat(final Peer peer) { if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), - peer.getPeerId(), false).map(v -> (Void) null); + peer.getPeerId()).map(v -> (Void) null); } else { return buildOutPeer(peer) .flatMap(apiOutPeer -> From 817c77f30c052d61b0481d3cbf7ed28b796446e5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 17:22:55 +0300 Subject: [PATCH 45/81] perf(core): Fixed suboptimal message change broadcast --- .../java/im/actor/core/modules/settings/SettingsModule.java | 4 ++++ .../im/actor/core/modules/settings/SettingsProcessor.java | 1 + .../im/actor/core/modules/settings/SettingsSyncActor.java | 1 + 3 files changed, 6 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java index 07a7bfbb6b..74b59bb5c3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsModule.java @@ -392,6 +392,7 @@ private void changeValue(String key, String val) { } settingsSync.send(new SettingsSyncActor.ChangeSettings(key, val)); onUpdatedSetting(key, val); + context().getSettingsModule().onSettingsChanged(); } private String readValue(String key) { @@ -400,6 +401,9 @@ private String readValue(String key) { public void onUpdatedSetting(String key, String value) { preferences().putString(STORAGE_PREFIX + key, value); + } + + public void onSettingsChanged() { eventBus.post(new SettingsChanged()); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java index 3ed4418748..22179294b8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsProcessor.java @@ -20,6 +20,7 @@ public SettingsProcessor(ModuleContext modules) { public void onSettingsChanged(String key, String value) { context().getSettingsModule().onUpdatedSetting(key, value); + context().getSettingsModule().onSettingsChanged(); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java index 280e739113..f8f0ae1e2b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/settings/SettingsSyncActor.java @@ -56,6 +56,7 @@ public void onResult(ResponseGetParameters response) { for (ApiParameter p : response.getParameters()) { context().getSettingsModule().onUpdatedSetting(p.getKey(), p.getValue()); } + context().getSettingsModule().onSettingsChanged(); preferences().putBool(SYNC_STATE_LOADED, true); } From 2344f57b39ac25de35fef03716c66fdcfd1cdd37 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 17:25:05 +0300 Subject: [PATCH 46/81] fix(core): Better message sent state handling --- .../modules/messaging/actions/SenderActor.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 81aa6840ca..f2b7a1821d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -499,18 +499,11 @@ public void onError(RpcException e) { ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), rid, message); - context().getEncryption().doSend(rid, content, peer.getPeerId()).then(r -> { - - self().send(new MessageSent(peer, rid)); - - // TODO: Replace - context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r.getDate()); - - wakeLock.releaseLock(); - }).failure(e -> { - self().send(new MessageError(peer, rid)); - wakeLock.releaseLock(); - }); + context().getEncryption().doSend(rid, content, peer.getPeerId()) + .chain(r -> context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r)) + .then(r -> self().send(new MessageSent(peer, rid))) + .failure(e -> self().send(new MessageError(peer, rid))) + .after((r, e) -> wakeLock.releaseLock()); } } From 919df73f1c49d6b2ae663fba8c0441ab8568aa0b Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 18:08:34 +0300 Subject: [PATCH 47/81] feat(android): Menu for secret chats --- .../src/main/java/im/actor/Application.java | 2 +- .../main/java/im/actor/sdk/ActorStyle.java | 19 ++++++ .../toolbar/ChatToolbarFragment.java | 60 +++++++----------- .../drawable-hdpi/ic_timer_off_white_24dp.png | Bin 0 -> 681 bytes .../res/drawable-hdpi/ic_timer_white_24dp.png | Bin 0 -> 595 bytes .../drawable-hdpi/ic_vpn_key_white_24dp.png | Bin 0 -> 341 bytes .../drawable-mdpi/ic_timer_off_white_24dp.png | Bin 0 -> 435 bytes .../res/drawable-mdpi/ic_timer_white_24dp.png | Bin 0 -> 393 bytes .../drawable-mdpi/ic_vpn_key_white_24dp.png | Bin 0 -> 236 bytes .../ic_timer_off_white_24dp.png | Bin 0 -> 796 bytes .../drawable-xhdpi/ic_timer_white_24dp.png | Bin 0 -> 765 bytes .../drawable-xhdpi/ic_vpn_key_white_24dp.png | Bin 0 -> 428 bytes .../ic_timer_off_white_24dp.png | Bin 0 -> 1261 bytes .../drawable-xxhdpi/ic_timer_white_24dp.png | Bin 0 -> 1114 bytes .../drawable-xxhdpi/ic_vpn_key_white_24dp.png | Bin 0 -> 625 bytes .../src/main/res/menu/chat_menu.xml | 28 +++++--- 16 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_timer_off_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_timer_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_timer_off_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_timer_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_timer_off_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_timer_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_timer_off_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_timer_white_24dp.png create mode 100644 actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png diff --git a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java index f032f75f34..773e61536f 100644 --- a/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java +++ b/actor-sdk/sdk-core-android/android-app/src/main/java/im/actor/Application.java @@ -46,7 +46,7 @@ public void onConfigureActorSDK() { ActorStyle style = ActorSDK.sharedActor().style; style.setDialogsActiveTextColor(0xff5882ac); - style.setShowAvatarPrivateInTitle(false); + // style.setShowAvatarPrivateInTitle(false); ActorSDK.sharedActor().setFastShareEnabled(true); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java index 95a729f7ff..f1c32fab86 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/ActorStyle.java @@ -228,6 +228,25 @@ public void setVerifiedColor(int verifiedColor) { this.verifiedColor = verifiedColor; } + private int secretChatToolbar = 0xff4CAF50; + private int secretChatStatusbar = 0xff388E3C; + + public int getSecretChatToolbar() { + return secretChatToolbar; + } + + public void setSecretChatToolbar(int secretChatToolbar) { + this.secretChatToolbar = secretChatToolbar; + } + + public int getSecretChatStatusbar() { + return secretChatStatusbar; + } + + public void setSecretChatStatusbar(int secretChatStatusbar) { + this.secretChatStatusbar = secretChatStatusbar; + } + // // List Styles // diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 8a27e39d87..3b38450a5b 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -7,6 +7,7 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; @@ -103,10 +104,13 @@ public void onConfigureActionBar(ActionBar actionBar) { actionBar.setDisplayShowCustomEnabled(true); // Coloring Toolbar - if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { - actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getAccentColor())); + if (peer.getPeerType() != PeerType.PRIVATE_ENCRYPTED) { + actionBar.setBackgroundDrawable(new ColorDrawable(style.getToolBarColor())); } else { - actionBar.setBackgroundDrawable(new ColorDrawable(ActorSDK.sharedActor().style.getToolBarColor())); + actionBar.setBackgroundDrawable(new ColorDrawable(style.getSecretChatToolbar())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getActivity().getWindow().setStatusBarColor(style.getSecretChatStatusbar()); + } } // Loading Toolbar header views @@ -225,7 +229,7 @@ public void onResume() { && !style.isShowAvatarPrivateInTitle())) { barAvatar.setVisibility(View.GONE); } - + // Global Counter bind(messenger().getGlobalState().getGlobalCounter(), (val, valueModel) -> { if (val != null && val > 0) { @@ -243,33 +247,11 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Inflating menu inflater.inflate(R.menu.chat_menu, menu); - - // Show menu for opening chat contact -// if (peer.getPeerType() == PeerType.PRIVATE) { -// menu.findItem(R.id.contact).setVisible(true); -// } else { -// menu.findItem(R.id.contact).setVisible(false); -// } - - // Show menus for leave group and group info view -// if (peer.getPeerType() == PeerType.GROUP) { -// GroupVM groupVM = groups().get(peer.getPeerId()); -// if (groupVM.isMember().get()) { -// menu.findItem(R.id.leaveGroup).setVisible(true); -// menu.findItem(R.id.groupInfo).setVisible(true); -// } else { -// menu.findItem(R.id.leaveGroup).setVisible(false); -// menu.findItem(R.id.groupInfo).setVisible(false); -// } -// if (groupVM.getGroupType() == GroupType.GROUP) { -// menu.findItem(R.id.clear).setVisible(true); -// } else { -// menu.findItem(R.id.clear).setVisible(false); -// } -// } else { -// menu.findItem(R.id.groupInfo).setVisible(false); -// menu.findItem(R.id.leaveGroup).setVisible(false); -// } + MenuItem addToContacts = menu.findItem(R.id.add_to_contacts); + MenuItem callMenu = menu.findItem(R.id.call); + MenuItem videoMenu = menu.findItem(R.id.video_call); + MenuItem keyMenu = menu.findItem(R.id.key); + MenuItem timerMenu = menu.findItem(R.id.timer); // Voice and Video calls boolean callsEnabled = ActorSDK.sharedActor().isCallsEnabled(); @@ -278,23 +260,29 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (peer.getPeerType() == PeerType.PRIVATE) { callsEnabled = !users().get(peer.getPeerId()).isBot(); } else if (peer.getPeerType() == PeerType.GROUP) { - GroupVM groupVM = groups().get(peer.getPeerId()); if (groupVM.getGroupType() == GroupType.GROUP) { - callsEnabled = groupVM.getMembersCount().get() <= MAX_USERS_FOR_CALLS; + callsEnabled = groupVM.getIsCanCall().get(); videoCallsEnabled = false; } else { callsEnabled = false; videoCallsEnabled = false; } + } else { + callsEnabled = false; + videoCallsEnabled = false; } } - menu.findItem(R.id.call).setVisible(callsEnabled); - menu.findItem(R.id.video_call).setVisible(callsEnabled && videoCallsEnabled); + callMenu.setVisible(callsEnabled); + videoMenu.setVisible(callsEnabled && videoCallsEnabled); + + // Secret Chat + keyMenu.setVisible(peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED); + timerMenu.setVisible(peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED); // Add to contacts if (peer.getPeerType() == PeerType.PRIVATE) { - menu.findItem(R.id.add_to_contacts).setVisible(!users().get(peer.getPeerId()).isContact().get()); + addToContacts.setVisible(!users().get(peer.getPeerId()).isContact().get()); } } diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_timer_off_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_timer_off_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..54253f14e52a87e107c983f3c1ba7f424de5d3cc GIT binary patch literal 681 zcmV;a0#^NrP)B4q|@O0JBGA~&Xs6iRFsiTj|fXfBhnpBkdk(s1E_Afm1OQAD)u zhZ`4CsYqpNC>mk%{9ZT@4u^T~p8I^*L>E3VGw+={pLzG5d(WMO=Xr^2B5GhANL#2o zNFnouwwC^{g|cqQ4s24Ff*DX|39JQu;5GOL{t(~kvtDp1Ahu8|bOOxN_ozGHgDm!rM(8dJ=n3d_KT`KtT(?H3lLZWfbR1R6`3beMfH6zwnWQQ# zF%zzHs7h>t$rz>bM8$AgyACa~plga@&`PD)J)c|_#$K^fCt{MCX4mh#2$hJv>{d)t z{)%ok+T#7}_S>{$$0gMfrTj33+DF#$2Gur+80VDpF{qqQ6@D|--!rNPZLwvdj8#c# zer>hLI*-0+7_?CBlus#bUf%#Cq99XCY4@KZ@WPAURK>MTH5xGo|vrB1Er!K zc}=D~qKUgqOT-TN3SNQ7bWhm9VUFsfzk0U!rM5tQw*1*(q%Qn3|9Y9I?mg$ebM@+-`qX>3^ZUKeJNHaKk6{>|V8Ur&74X_n^`AoK585?vNDBqsD+{_w zT@J>;2Dk(t;1gWaGao4XBQ{#6PzN|L*JRN-@|T}CKZI0VBr%808hTXr8h zWkC%Zg_>F3BbANO#{#!B$(TF!4$C{LB9sc=S#Y-op&_7x=|>$(HwAEm$_ECmeM}5PKDn)aZb<7 hRa6qvwSDLa{Qy1FLA5psl#c)a002ovPDHLkV1h{y0(Sra literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2967aa0804778f4d06af1e757e5571d8a303eea3 GIT binary patch literal 341 zcmV-b0jmCqP);+( zbr_5Kj9)wWl~9i%)R!$(wQ*!|FODo@l0A7^Gz+wqNWatuJHZ#OIO(kOBv^H*EGSfT zTgYJSP-bXS@Ei*nO>%BiW@@3iIu_FCIMJZv1uw3kZgKn&5_*#aZF*Tx-~rBH30fdw nqern_b&G42|a;)?nvi-~^*kWkFMU+Hx!?q3AZhFsPn~TULIT6)$>a$(PCHf&GxdamL z^I%;_hFnBtmSVvBBk(S^k4Dl1Cmu=1W4rE%ku?5tqHTh6mft7qJY-u%GhI7r9htG@ d+IHZi!VeaSQ3RgB$JYP=002ovPDHLkV1jr;yej|z literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_timer_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_timer_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..967cde4d20187977792096a70b0fdcf505fdfbea GIT binary patch literal 393 zcmV;40e1e0P)<`^_(N(K%D!R5^Xb|Eje8MHf zJCn464xB}1?#xMUX6~edAV}>K+rVAzcm#V(Q5w{N2S&gLD==o?y@)*n#W(^7yand= z0oDc2@jF&;wNCnxwod1{K~+7#6jCt5Fa^b zU{mZ$;73RbhBx0XJ_i@J)%03OraVMt?vyNlXj`OZ&WalAKG=N8vqiZcM7?>O8?6Sq n;EQ{F!L8T{|ImZHGXKmP)1Nu8dxYaf00000NkvXXu0mjf(S5MR literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..23199383848fcbe89520ecbb15c595a61b563b6d GIT binary patch literal 236 zcmVf|4=E4DS@SaGbiip&hs3`b9 zfd)A+pkfT))4vK<;7;ASfu&TSJ>_+-Adv`i3eQF&FiuGUQjmd?Lnc;sR1R@p* z{h%~a)_);3_BGqx=?t7m4l{RW&dfwBci{W7d+&Gd=iZq!XU?RA5UEyHDgpQ_+m-+% z0EZ1Ai9$XBra;q{X1$=hpk>fEP!&{1Rq?mipm9(xB*3Es8dLy%V9bFHP#)B- z@Rh$8IRHb7j&u+spgrNNZO=_a8v@&xebOVHu8qgf}60SX3@Vn%as zosql^DDJ!A0`O50t4oFu1@bVab7Fybzvlw<5+nIA*_esl48jzkPq9v9IRF#HEubcy4l6oc!EIn@hzq`o zyU#TcKEDL3P+)rDQP5{ar)AG`A`$`1>nAlE{5oy}`z*XpfnGAJ9Zu&165^Al;|7Mj zBBlaem{q1pY4NQ=X)zK34>;s-dM3iRX%YB@=2A>q(57u;0c(x};aV1y>%>hgeb_cR zltxLMw}$X}UX{b7>lH?<@1UQ!8L41OyolFy$N0=C=GNI(JLEeEG1jW5l#j7Cm7Ej* a*ZK`?T0{Vp*c3hh0000FeEGr|jzaS8T5(RCb zs0b$}{R=H7F^Z|*(!obysa5Tp(=sYJj(bS)8( z2sms4k|?y_0`lMvcm+O#4X_C+;1~Ex-`xdm|BHZnFbIkUXJ8o=K%Fq}5V#MH#3|qs zSPSg!8@Nz4?;!na%BJ=bVY2Y!;CbNQ9)X)xi{G_-E(K(1e@nIl-hwgE4KkpSXa+rC z9L&>iqnJ8Fmt6@^i(eI-Y}3riG_nZCl`qt23as%_qT033OPTqfSr`5m0a$ zg`NoxUXw7}NR|e3!u(UN1Y8VRe1m3cQR}x2AtL&01=I`sz7+Av>*hXbVLO#V?up0( zZU_gq-7vX}GUkkMu&>z&C9XeIWpNy+JouO+zthKm6#YivZGfhO6(t}CilB%}%I*T`L z?yBwdIELo#gQ(laReSw>jkchUtwf17alPPB_r!H-edyQ@ZKbPw#d)8vm{jgG0$x(Y vTBkzfH~2wkM`e}6wQ`%}C0`;S5unFk98l)|alL)-00000NkvXXu0mjfY>;4> literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..18dd4bb5c1de7610e78374e359d8d0501bbeb77e GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(Fcy2dIEGZ*dOPE^7qg*ATl!tG zqq%#x@AF+L!s6)ii%Bo0)3sJiQ!gsud)K4r6Az*U?yP*_bbHpn#**aMD#pb{l9udt zC)7UF8=rjQqon8(lA#d2RU(wn{i6|c@NksU%`u9m89Nc^$YGunB>6Q>7P z6wWYbGze)lt97)_pCz=zoWtJyvF4fTNi&2hF8XAs>^XH-fW>VV=TeqNFF}@53%OrR z3ov$E5ZdA^vzqs}^&h4R9>)hq5`xy4<+xt_BvQA`;qE0zGlLxU9;Vk(o!s0^>21rk zIuBSC8h__nrN^pL?eg$x(H*ag*C%j#@Nph`zdYxR2dm2N2g;A_9j3`k7BQaGdLcdM z(&TMFZQNhyxF$61F|uSbf52Iz`Q%5G?#`!k_U;X3+&42B9wLqZH4NA$+l7Dnzt6i7 P7z+%Zu6{1-oD!Ms9SOMxkRx}^mx9xzkALs-^>_eSyoH{O&}oqvw?IKf+{0a(5<9I;5g84@t0@75fnhh zPXMmrwTn3g9hOwA*5MO#5O{B?FRy`J5uPX^=jxQ}dc8=ZevtvZ^C79;(qB4&rBmdz zM!4oFu!!pwTkb4YNUmvK?Rb=fEz#+P>4@#GQOI#fHWWz zC<3a1HsA-7#8!O5*t+>l8%cF8By|G^fD{i6W-3qubm@}gW}uj5)8N`kY5+Dej!*J| z9+z@j+{$q~lJ+A>1!BNi0gO73bH$4sFH;TEBnehv7cMJ1bk4=Hfe(gBnh)HQ)aeVb zM3L5Vh9zE??{nZq1O!`+YL zEMe_w03%Dsd%flSDuscDG(7 z%^@WD>DdY-by=@M&}AangTKZkoRa)@|J_7A8n^|`06r7J_C^@EaY^zksFbL~kki-9 zX`nSkATh(9ON?$gB>5FI4>Jdm%1TZ_RPS}&W(wd}62~aJorqt?Dd-Xrq{_iONhaxx zjU*01HGbM5r$MNBPNBoeq*qBC6ccefIRy<6L6*6gaYsv1wq#VzCTN5RlI$`q5e-QT zhFDOq5kab%lI|)coz^i>EGL4rF|JIglGH)m z^C+jFT*+iL#b8Nd3^Y#MFPBr0NDT}Vfl8RyxHu)1=}LD9nuuUs3Kjx6B*kJXI8r&U zBBQuLQt5mHB^68leys{Y;v0EM1nj{Pt4WeB5`Xtt<+6`#YhHz-b$73lsQpR%RSFUl zKWf+6clRK4`PTTCK)Kn)2QETWiv40B$H&? zlcfEu#&(-E`CbFtB<+V;?Dj*CV(%6|T_yRwfoPAK9qjPv!L@RZN{ck_7R&y4L&yX{ z;bmzjk}O;+FU1v4FB8vW*6)qrPD{uHLE$H<&PBz?aK*C*rv%w3AOT;;RO`=1@>6*2 zxaF;cOb`@iE9QDlD26L_mZ%o|`2zUGgzo)>fC%Rz8} zq6LVmIY17O1LOcXKn{>w<{cmhXbS?0gVLZ3Xc#mBng@Lct%Ei|KS2wiXQ11lUeFQH zwyg??L1_SufR;lz=OqMo9Q#zUodE5%6;Kk?4Vv@l+bs5-l)$+U`V2Z`8=!X37f*lt z2x=>0e+RG>*j!V9^B@SEuz=bmgL(@( zXdno!1?44UIcGXhtdgu&Y>TkR;Fh2QloO0+1+?EJpsWPWPNF=KpFVbho(uYVYzk1j zWLgDza;Oqx6_V-Jsv8hwfEolB5;h3bk(@s%==YcoKqG?fL(D=zF?_O%wVTv{ z;+Rh<);cT()Wzxdo9Z-EHIx?Y%F8VV#6pe&r|&vSKp8=jDrPaD7#<1)UUgNC40DY1 zM*z@Z7(W#DE;K~|Pz$H;r<8!^IYz1@0H{U~Rg(eYtzfAL0IKBly-Epaons^s0YFJk z-y05)7*N4YG4T{za!y+8q~|CBJ>eK>jz9@ZOPC;6)yNHwk%0&R;suIBlz@6UM#dum zC~IP?p?kBcODVw$TuYtq;1xb~S`6qSXUvP#=?+oCY|>&t)20$;vXpD1g>X&@##?VX zX+MLBa$DfAijy~-vA)xlw3i+GWGn*ICm5$MpGkRe_*->l(5$qe*lI!d!J+KvIZwDh z3%c)`6^!q!iL?2*q6j`Nk zZ#AuGYZS+3#oIpYE?x7~TLI771~H*kgN`IJ)@Hbh(uDn>gLiMy#`vn$ftazns zY_&Q~c}==rW|=4foUx!wxoJR~I`5fz9g4l2(yHq^OuSIX9P4`xVA!tw0^!+Q$zRDl z3c7{GOF`QrCcEmxF5k8R`X3f6)xCr#3uBmPTE^9_fQNc>_}vgD*lSHeDK#MXTDSw` g069584iJt00(uGtBrSHV=Kufz07*qoM6N<$f)I1%C;$Ke literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png b/actor-sdk/sdk-core-android/android-sdk/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4deb9274127797df01186a1fae014a0f6e47ab76 GIT binary patch literal 625 zcmV-%0*?KOP)9s4~Z5QsnoA`pQHL?8k&0uhKn1R@ZD2t*(jB2YJ4 zM7yYfUQii*pjULMUt2<5wF1E=0?ME*34d&&mb3$P zpmT|CoTB!$0x6t(iGJL;id1d4I+yr{I)FE)31~~=A8Tm=nv`V5CmL}bs4=9ka@(O! zF6&(env>+mw97!p4gl@D3e;`+f!@$E>et^yssXfuN|L_uE6}3FWREEO$A6&*6@3BP zwb*j9|JYP}(Yp^o1;dG!4ff|%AAnvAC+ar<8uS6EY&en0r9p;3H2`F~iwzh64b|d7 zdc^=J?*q_|;Y1~Uo>L8{&zG_FL7YN>1+!RyRO1OQxefRxm%Bz|68>F?#DOa3Ni*oc;td_>zm4hvDkp$`)`=F-wtPS6@T{-B%KGOE z{aQ}{Zq)a&oCD%RoCG2Qfe1t(0uhKn1R@Y45P=9pAOaDHK(*8t>HP&Jyfsn200000 LNkvXXu0mjfDc%pc literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml index 5a8e39faa5..71a61a35a2 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/menu/chat_menu.xml @@ -26,16 +26,28 @@ android:title="" app:showAsAction="always" /> + + + + - - + + - - + + - - + + - - + + \ No newline at end of file From 5093dd7d361d63ec0c35e638b73f63a32e7a536d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 18:14:21 +0300 Subject: [PATCH 48/81] feat(scheme): Added encrypted documents support --- actor-sdk/sdk-api/actor.json | 61 +++++++++++++++++++ .../models/im/actor/api/scheme.mps | 50 +++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index abfbdfceee..4d93546f47 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -4812,6 +4812,12 @@ "argument": "ext", "category": "compact", "description": " Extension" + }, + { + "type": "reference", + "argument": "encryptionInfo", + "category": "full", + "description": " Optional information for encrypted documents" } ], "trait": { @@ -4866,6 +4872,61 @@ }, "id": 8, "name": "ext" + }, + { + "type": { + "type": "opt", + "childType": { + "type": "struct", + "childType": "DocumentEncryptionInfo" + } + }, + "id": 9, + "name": "encryptionInfo" + } + ] + } + }, + { + "type": "struct", + "content": { + "name": "DocumentEncryptionInfo", + "doc": [ + "Document encryption key", + { + "type": "reference", + "argument": "realFileSize", + "category": "full", + "description": " File Size" + }, + { + "type": "reference", + "argument": "keyAlg", + "category": "full", + "description": " Key Algorithm" + }, + { + "type": "reference", + "argument": "key", + "category": "full", + "description": " Key material" + } + ], + "attributes": [ + { + "type": "int32", + "id": 1, + "name": "realFileSize" + }, + { + "type": "string", + "id": 2, + "name": "keyAlg" + }, + { + "type": "bytes", + "id": 3, + "name": "key" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 49fec511bc..e59f3d116e 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -4406,6 +4406,11 @@ + + + + + @@ -4449,10 +4454,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 43e8f9cf7c3ce04277ca4d2c0d81683c0286b6d5 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 18:20:14 +0300 Subject: [PATCH 49/81] feat(core): Updated api classes --- .../core/api/ApiDocumentEncryptionInfo.java | 77 +++++++++++++++++++ .../im/actor/core/api/ApiDocumentMessage.java | 14 +++- .../core/entity/content/AnimationContent.java | 3 +- .../core/entity/content/DocumentContent.java | 8 +- .../core/entity/content/PhotoContent.java | 3 +- .../core/entity/content/VideoContent.java | 3 +- .../core/entity/content/VoiceContent.java | 3 +- .../messaging/actions/SenderActor.java | 3 +- 8 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentEncryptionInfo.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentEncryptionInfo.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentEncryptionInfo.java new file mode 100644 index 0000000000..9706b78415 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentEncryptionInfo.java @@ -0,0 +1,77 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiDocumentEncryptionInfo extends BserObject { + + private int realFileSize; + private String keyAlg; + private byte[] key; + + public ApiDocumentEncryptionInfo(int realFileSize, @NotNull String keyAlg, @NotNull byte[] key) { + this.realFileSize = realFileSize; + this.keyAlg = keyAlg; + this.key = key; + } + + public ApiDocumentEncryptionInfo() { + + } + + public int getRealFileSize() { + return this.realFileSize; + } + + @NotNull + public String getKeyAlg() { + return this.keyAlg; + } + + @NotNull + public byte[] getKey() { + return this.key; + } + + @Override + public void parse(BserValues values) throws IOException { + this.realFileSize = values.getInt(1); + this.keyAlg = values.getString(2); + this.key = values.getBytes(3); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.realFileSize); + if (this.keyAlg == null) { + throw new IOException(); + } + writer.writeString(2, this.keyAlg); + if (this.key == null) { + throw new IOException(); + } + writer.writeBytes(3, this.key); + } + + @Override + public String toString() { + String res = "struct DocumentEncryptionInfo{"; + res += "realFileSize=" + this.realFileSize; + res += ", keyAlg=" + this.keyAlg; + res += ", key=" + byteArrayToString(this.key); + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentMessage.java index dc58c0bd67..8c822f9cda 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiDocumentMessage.java @@ -23,8 +23,9 @@ public class ApiDocumentMessage extends ApiMessage { private String mimeType; private ApiFastThumb thumb; private ApiDocumentEx ext; + private ApiDocumentEncryptionInfo encryptionInfo; - public ApiDocumentMessage(long fileId, long accessHash, int fileSize, @NotNull String name, @NotNull String mimeType, @Nullable ApiFastThumb thumb, @Nullable ApiDocumentEx ext) { + public ApiDocumentMessage(long fileId, long accessHash, int fileSize, @NotNull String name, @NotNull String mimeType, @Nullable ApiFastThumb thumb, @Nullable ApiDocumentEx ext, @Nullable ApiDocumentEncryptionInfo encryptionInfo) { this.fileId = fileId; this.accessHash = accessHash; this.fileSize = fileSize; @@ -32,6 +33,7 @@ public ApiDocumentMessage(long fileId, long accessHash, int fileSize, @NotNull S this.mimeType = mimeType; this.thumb = thumb; this.ext = ext; + this.encryptionInfo = encryptionInfo; } public ApiDocumentMessage() { @@ -74,6 +76,11 @@ public ApiDocumentEx getExt() { return this.ext; } + @Nullable + public ApiDocumentEncryptionInfo getEncryptionInfo() { + return this.encryptionInfo; + } + @Override public void parse(BserValues values) throws IOException { this.fileId = values.getLong(1); @@ -85,6 +92,7 @@ public void parse(BserValues values) throws IOException { if (values.optBytes(8) != null) { this.ext = ApiDocumentEx.fromBytes(values.getBytes(8)); } + this.encryptionInfo = values.optObj(9, new ApiDocumentEncryptionInfo()); if (values.hasRemaining()) { setUnmappedObjects(values.buildRemaining()); } @@ -109,6 +117,9 @@ public void serialize(BserWriter writer) throws IOException { if (this.ext != null) { writer.writeBytes(8, this.ext.buildContainer()); } + if (this.encryptionInfo != null) { + writer.writeObject(9, this.encryptionInfo); + } if (this.getUnmappedObjects() != null) { SparseArray unmapped = this.getUnmappedObjects(); for (int i = 0; i < unmapped.size(); i++) { @@ -127,6 +138,7 @@ public String toString() { res += ", mimeType=" + this.mimeType; res += ", thumb=" + (this.thumb != null ? "set":"empty"); res += ", ext=" + (this.ext != null ? "set":"empty"); + res += ", encryptionInfo=" + this.encryptionInfo; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java index 2e5639957f..5a3d13f985 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java @@ -38,7 +38,8 @@ public static AnimationContent createRemoteAnimation(FileReference reference, in fastThumb.getH(), fastThumb.getImage()) : null, - new ApiDocumentExAnimation(w, h)))); + new ApiDocumentExAnimation(w, h), + null))); } private int w; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java index 22d83e7de5..3866ea7fb4 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java @@ -29,12 +29,8 @@ public static DocumentContent createRemoteDocument(FileReference reference, Fast reference.getFileSize(), reference.getFileName(), "image/jpeg", - fastThumb != null ? - new ApiFastThumb( - fastThumb.getW(), - fastThumb.getH(), - fastThumb.getImage()) : - null, + fastThumb != null ? new ApiFastThumb(fastThumb.getW(), fastThumb.getH(), fastThumb.getImage()) : null, + null, null))); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java index ba569acc38..9ddd4ce97a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java @@ -46,7 +46,8 @@ public static PhotoContent createRemotePhoto(@NotNull FileReference reference, i fastThumb.getH(), fastThumb.getImage()) : null, - new ApiDocumentExPhoto(w, h)))); + new ApiDocumentExPhoto(w, h), + null))); } private int w; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java index 0acbf23608..069eb636f6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java @@ -41,7 +41,8 @@ public static VideoContent createRemoteVideo(FileReference reference, int w, int fastThumb.getH(), fastThumb.getImage()) : null, - new ApiDocumentExVideo(w, h, duration)))); + new ApiDocumentExVideo(w, h, duration), + null))); } private int duration; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java index 7aa2ce3a88..2d1da65639 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java @@ -41,7 +41,8 @@ public static VoiceContent createRemoteAudio(@NotNull FileReference reference, i reference.getFileName(), "audio/mp3", null, - new ApiDocumentExVoice(duration)))); + new ApiDocumentExVoice(duration), + null))); } private int duration; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index f2b7a1821d..f31e18096b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -456,7 +456,8 @@ private void performSendContent(final Peer peer, final long rid, AbsContent cont source.getFileReference().getFileSize(), source.getFileReference().getFileName(), documentContent.getMimeType(), - fastThumb, documentEx); + fastThumb, documentEx, + null); } else if (content instanceof LocationContent) { message = new ApiJsonMessage(((LocationContent) content).getRawJson()); } else if (content instanceof ContactContent) { From 1b31b1309071f564e8f246e50823b72476c2c4a6 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 18:23:54 +0300 Subject: [PATCH 50/81] feat(scheme): Added timer change messages --- actor-sdk/sdk-api/actor.json | 43 +++++++++++++++++++ .../models/im/actor/api/scheme.mps | 35 +++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index 4d93546f47..bde4194961 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -20229,6 +20229,49 @@ } ] } + }, + { + "type": "struct", + "content": { + "name": "EncryptedChatTimerSet", + "doc": [ + "Encrypted message about timer setting", + { + "type": "reference", + "argument": "receiverId", + "category": "full", + "description": " Receiver User Id" + }, + { + "type": "reference", + "argument": "timerMs", + "category": "full", + "description": " Timer in MS" + } + ], + "trait": { + "name": "EncryptedContent", + "key": 8 + }, + "attributes": [ + { + "type": { + "type": "alias", + "childType": "userId" + }, + "id": 1, + "name": "receiverId" + }, + { + "type": { + "type": "opt", + "childType": "int32" + }, + "id": 2, + "name": "timerMs" + } + ] + } } ] }, diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index e59f3d116e..e8fc6f7d18 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -17179,6 +17179,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b991e631e3dc4859aae64baeb1fda4ed1c4ecbbc Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 18:31:13 +0300 Subject: [PATCH 51/81] feat(scheme): Adding self-destruct timer to encrypted message --- actor-sdk/sdk-api/actor.json | 14 ++++++++++++++ .../im.actor.api/models/im/actor/api/scheme.mps | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index bde4194961..f3edcdf9ab 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -19993,6 +19993,12 @@ "argument": "message", "category": "full", "description": " Content of message" + }, + { + "type": "reference", + "argument": "timerMs", + "category": "full", + "description": " Optional self-destruct timer" } ], "trait": { @@ -20020,6 +20026,14 @@ }, "id": 3, "name": "message" + }, + { + "type": { + "type": "opt", + "childType": "int32" + }, + "id": 4, + "name": "timerMs" } ] } diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index e8fc6f7d18..2759b980e9 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -16986,6 +16986,13 @@ + + + + + + + @@ -17007,6 +17014,11 @@ + + + + + From 7e06844d748b2ef7209847d6ddc359b1e6999b1e Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 19:24:22 +0300 Subject: [PATCH 52/81] feat(scheme): Adding random id to timer set event --- actor-sdk/sdk-api/actor.json | 23 ++++++++++++++++--- .../models/im/actor/api/scheme.mps | 19 ++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-api/actor.json b/actor-sdk/sdk-api/actor.json index f3edcdf9ab..b1f2e2165f 100644 --- a/actor-sdk/sdk-api/actor.json +++ b/actor-sdk/sdk-api/actor.json @@ -20015,7 +20015,10 @@ "name": "receiverId" }, { - "type": "int64", + "type": { + "type": "alias", + "childType": "randomId" + }, "id": 2, "name": "rid" }, @@ -20077,7 +20080,10 @@ "name": "receiverId" }, { - "type": "int64", + "type": { + "type": "alias", + "childType": "randomId" + }, "id": 2, "name": "rid" }, @@ -20127,7 +20133,10 @@ { "type": { "type": "list", - "childType": "int64" + "childType": { + "type": "alias", + "childType": "randomId" + } }, "id": 2, "name": "rid" @@ -20276,6 +20285,14 @@ "id": 1, "name": "receiverId" }, + { + "type": { + "type": "alias", + "childType": "randomId" + }, + "id": 3, + "name": "rid" + }, { "type": { "type": "opt", diff --git a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps index 2759b980e9..f57cf45406 100644 --- a/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps +++ b/actor-sdk/sdk-api/api-language/solutions/im.actor.api/models/im/actor/api/scheme.mps @@ -16977,7 +16977,9 @@ - + + + @@ -17034,7 +17036,9 @@ - + + + @@ -17081,7 +17085,9 @@ - + + + @@ -17202,6 +17208,13 @@ + + + + + + + From 867e8c797d4420c5760c1e67c12d9279d98367bc Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Fri, 29 Jul 2016 20:42:19 +0300 Subject: [PATCH 53/81] feat(core+android): Introduce encrypted router, secret chat timeout set --- .../toolbar/ChatToolbarFragment.java | 5 + .../main/java/im/actor/core/Messenger.java | 12 + .../core/api/ApiEncryptedChatTimerSet.java | 75 ++ .../actor/core/api/ApiEncryptedContent.java | 1 + .../core/api/ApiEncryptedMessageContent.java | 14 +- .../actor/core/entity/ContentDescription.java | 8 + .../im/actor/core/entity/ContentType.java | 7 +- .../actor/core/entity/content/AbsContent.java | 3 + .../entity/content/ServiceTimerChanged.java | 26 + .../java/im/actor/core/i18n/I18nEngine.java | 36 + .../modules/encryption/EncryptedRouter.java | 36 + .../encryption/EncryptedRouterActor.java | 184 +++++ .../modules/encryption/EncryptionModule.java | 29 +- .../encryption/EncryptionProcessor.java | 14 +- .../EncryptedSequenceProcessor.java | 2 +- .../{ => updates}/EncryptedUpdates.java | 2 +- .../modules/messaging/MessagesModule.java | 1 - .../messaging/MessagesProcessorEncrypted.java | 2 +- .../messaging/actions/SenderActor.java | 2 +- .../src/main/resources/AppText.json | 414 +++++----- .../src/main/resources/AppText_Ar.json | 305 ++++--- .../src/main/resources/AppText_Es.json | 392 +++++---- .../src/main/resources/AppText_Fa.json | 305 ++++--- .../src/main/resources/AppText_Pt.json | 405 +++++----- .../src/main/resources/AppText_Ru.json | 750 +++++++++--------- .../src/main/resources/AppText_Zn.json | 321 ++++---- 26 files changed, 1882 insertions(+), 1469 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedChatTimerSet.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/ServiceTimerChanged.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouter.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => updates}/EncryptedSequenceProcessor.java (84%) rename actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/{ => updates}/EncryptedUpdates.java (95%) diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index 3b38450a5b..acb7306fac 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -359,6 +359,11 @@ public void onError(final Exception e) { return true; } } + + if (item.getItemId() == R.id.timer) { + execute(messenger().setSecretChatTimer(peer.getPeerId(), 5000)); + } + return super.onOptionsItemSelected(item); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index 6304af73d1..d6ac630060 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -1228,6 +1228,18 @@ public SearchValueModel buildGlobalSearchModel() { return modules.getSearchModule().buildSearchModel(); } + /** + * Setting secret chat timer + * + * @param uid user's id + * @param timeout user's timeout + * @return promice of void + */ + @ObjectiveCName("setSecretChatTimerWithUid:withTimeout:") + public Promise setSecretChatTimer(int uid, Integer timeout) { + return modules.getEncryption().setSecretChatTimer(uid, timeout); + } + ////////////////////////////////////// // Calls ////////////////////////////////////// diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedChatTimerSet.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedChatTimerSet.java new file mode 100644 index 0000000000..13777f6305 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedChatTimerSet.java @@ -0,0 +1,75 @@ +package im.actor.core.api; +/* + * Generated by the Actor API Scheme generator. DO NOT EDIT! + */ + +import im.actor.runtime.bser.*; +import im.actor.runtime.collections.*; +import static im.actor.runtime.bser.Utils.*; +import im.actor.core.network.parser.*; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import com.google.j2objc.annotations.ObjectiveCName; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +public class ApiEncryptedChatTimerSet extends ApiEncryptedContent { + + private int receiverId; + private long rid; + private Integer timerMs; + + public ApiEncryptedChatTimerSet(int receiverId, long rid, @Nullable Integer timerMs) { + this.receiverId = receiverId; + this.rid = rid; + this.timerMs = timerMs; + } + + public ApiEncryptedChatTimerSet() { + + } + + public int getHeader() { + return 8; + } + + public int getReceiverId() { + return this.receiverId; + } + + public long getRid() { + return this.rid; + } + + @Nullable + public Integer getTimerMs() { + return this.timerMs; + } + + @Override + public void parse(BserValues values) throws IOException { + this.receiverId = values.getInt(1); + this.rid = values.getLong(3); + this.timerMs = values.optInt(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, this.receiverId); + writer.writeLong(3, this.rid); + if (this.timerMs != null) { + writer.writeInt(2, this.timerMs); + } + } + + @Override + public String toString() { + String res = "struct EncryptedChatTimerSet{"; + res += "receiverId=" + this.receiverId; + res += ", timerMs=" + this.timerMs; + res += "}"; + return res; + } + +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java index 9bc1dbfe8d..a48de5d25e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedContent.java @@ -26,6 +26,7 @@ public static ApiEncryptedContent fromBytes(byte[] src) throws IOException { case 4: return Bser.parse(new ApiEncryptedReceived(), content); case 5: return Bser.parse(new ApiEncryptedRead(), content); case 6: return Bser.parse(new ApiEncryptedDeleteAll(), content); + case 8: return Bser.parse(new ApiEncryptedChatTimerSet(), content); default: return new ApiEncryptedContentUnsupported(key, content); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java index c10af3cfa7..208501e051 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/api/ApiEncryptedMessageContent.java @@ -19,11 +19,13 @@ public class ApiEncryptedMessageContent extends ApiEncryptedContent { private int receiverId; private long rid; private ApiMessage message; + private Integer timerMs; - public ApiEncryptedMessageContent(int receiverId, long rid, @NotNull ApiMessage message) { + public ApiEncryptedMessageContent(int receiverId, long rid, @NotNull ApiMessage message, @Nullable Integer timerMs) { this.receiverId = receiverId; this.rid = rid; this.message = message; + this.timerMs = timerMs; } public ApiEncryptedMessageContent() { @@ -47,11 +49,17 @@ public ApiMessage getMessage() { return this.message; } + @Nullable + public Integer getTimerMs() { + return this.timerMs; + } + @Override public void parse(BserValues values) throws IOException { this.receiverId = values.getInt(1); this.rid = values.getLong(2); this.message = ApiMessage.fromBytes(values.getBytes(3)); + this.timerMs = values.optInt(4); } @Override @@ -63,6 +71,9 @@ public void serialize(BserWriter writer) throws IOException { } writer.writeBytes(3, this.message.buildContainer()); + if (this.timerMs != null) { + writer.writeInt(4, this.timerMs); + } } @Override @@ -71,6 +82,7 @@ public String toString() { res += "receiverId=" + this.receiverId; res += ", rid=" + this.rid; res += ", message=" + this.message; + res += ", timerMs=" + this.timerMs; res += "}"; return res; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java index 9452bee01f..7326609aeb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentDescription.java @@ -26,6 +26,7 @@ import im.actor.core.entity.content.ServiceGroupUserJoined; import im.actor.core.entity.content.ServiceGroupUserKicked; import im.actor.core.entity.content.ServiceGroupUserLeave; +import im.actor.core.entity.content.ServiceTimerChanged; import im.actor.core.entity.content.ServiceUserRegistered; import im.actor.core.entity.content.StickerContent; import im.actor.core.entity.content.TextContent; @@ -96,6 +97,13 @@ public static ContentDescription fromContent(AbsContent msg) { } else if (msg instanceof ServiceGroupUserJoined) { return new ContentDescription(ContentType.SERVICE_JOINED, "", 0, false); + } else if (msg instanceof ServiceTimerChanged) { + ServiceTimerChanged timerChanged = (ServiceTimerChanged) msg; + if (timerChanged.getTimer() > 0) { + return new ContentDescription(ContentType.SERVICE_TIMER_SET, "", 0, false); + } else { + return new ContentDescription(ContentType.SERVICE_TIMER_CLEAR, "", 0, false); + } } else if (msg instanceof ServiceContent) { return new ContentDescription(ContentType.SERVICE, ((ServiceContent) msg).getCompatText(), 0, false); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentType.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentType.java index 9320fe4403..be090f428b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentType.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ContentType.java @@ -28,6 +28,8 @@ public enum ContentType { SERVICE_CALL_MISSED(23), SERVICE_TOPIC(24), SERVICE_ABOUT(25), + SERVICE_TIMER_SET(28), + SERVICE_TIMER_CLEAR(29), UNKNOWN_CONTENT(15); int value; @@ -91,7 +93,10 @@ public static ContentType fromValue(int value) { return SERVICE_TOPIC; case 25: return SERVICE_ABOUT; - + case 28: + return SERVICE_TIMER_SET; + case 29: + return SERVICE_TIMER_CLEAR; } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AbsContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AbsContent.java index a746ffd5c7..fb84942db1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AbsContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AbsContent.java @@ -27,6 +27,7 @@ import im.actor.core.api.ApiServiceExUserKicked; import im.actor.core.api.ApiServiceExUserLeft; import im.actor.core.api.ApiServiceMessage; +import im.actor.core.api.ApiServiceTimerChanged; import im.actor.core.api.ApiStickerMessage; import im.actor.core.api.ApiTextMessage; import im.actor.core.entity.content.internal.AbsContentContainer; @@ -144,6 +145,8 @@ protected static AbsContent convertData(AbsContentContainer container) { return new ServiceCallEnded(remoteContainer); } else if (ext instanceof ApiServiceExPhoneMissed) { return new ServiceCallMissed(remoteContainer); + } else if (ext instanceof ApiServiceTimerChanged) { + return new ServiceTimerChanged(remoteContainer); } else { return new ServiceContent(remoteContainer); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/ServiceTimerChanged.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/ServiceTimerChanged.java new file mode 100644 index 0000000000..bd84ef7988 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/ServiceTimerChanged.java @@ -0,0 +1,26 @@ +package im.actor.core.entity.content; + +import im.actor.core.api.ApiServiceMessage; +import im.actor.core.api.ApiServiceTimerChanged; +import im.actor.core.entity.content.internal.ContentRemoteContainer; + +public class ServiceTimerChanged extends ServiceContent { + + public static ServiceTimerChanged create(int timer) { + return new ServiceTimerChanged(new ContentRemoteContainer( + new ApiServiceMessage("Timer changed", new ApiServiceTimerChanged(timer)))); + } + + private int timer; + + public ServiceTimerChanged(ContentRemoteContainer contentContainer) { + super(contentContainer); + + ApiServiceMessage serviceMessage = (ApiServiceMessage) contentContainer.getMessage(); + timer = ((ApiServiceTimerChanged) serviceMessage.getExt()).getTimerMs(); + } + + public int getTimer() { + return timer; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java index 1b4831bafe..062db50baa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java @@ -30,6 +30,7 @@ import im.actor.core.entity.content.ServiceGroupUserJoined; import im.actor.core.entity.content.ServiceGroupUserKicked; import im.actor.core.entity.content.ServiceGroupUserLeave; +import im.actor.core.entity.content.ServiceTimerChanged; import im.actor.core.entity.content.ServiceUserRegistered; import im.actor.core.entity.content.TextContent; import im.actor.core.modules.Modules; @@ -125,6 +126,29 @@ public String formatTyping(int count) { .replace("{count}", "" + count); } + // + // Duration + // + + @ObjectiveCName("formatDurationWithMsec:") + public String formatDuration(int msec) { + if (msec < 1000) { + return getPlural("language.format.time.seconds.full", 1) + .replace("{seconds}", "1"); + } else if (msec < 60000) { + int secs = (msec / 1000); + return getPlural("language.format.time.seconds.full", secs) + .replace("{seconds}", "" + secs); + } else if (msec < 24 * (60 * 60000)) { + int minutes = msec / 60000; + return getPlural("language.format.time.minutes.full", minutes) + .replace("{minutes}", "" + minutes); + } else { + int hours = msec / (60 * 60000); + return getPlural("language.format.time.hours.full", hours) + .replace("{hours}", "" + hours); + } + } // // Presence @@ -354,6 +378,10 @@ public String formatContentText(int senderId, ContentType contentType, String te return get("content.service.calls.ended"); case SERVICE_CALL_MISSED: return get("content.service.calls.missed"); + case SERVICE_TIMER_SET: + return getTemplateNamed(senderId, "content.service.encrypted.timer_changed.compact"); + case SERVICE_TIMER_CLEAR: + return getTemplateNamed(senderId, "content.service.encrypted.timer_disabled.compact"); case NONE: return ""; default: @@ -411,6 +439,14 @@ public String formatFullServiceMessage(int senderId, ServiceContent content, boo return get("content.service.calls.ended"); } else if (content instanceof ServiceCallMissed) { return get("content.service.calls.missed"); + } else if (content instanceof ServiceTimerChanged) { + int timer = ((ServiceTimerChanged) content).getTimer(); + if (timer > 0) { + return getTemplateNamed(senderId, "content.service.encrypted.timer_changed.full") + .replace("{timer}", formatDuration(timer)); + } else { + return getTemplateNamed(senderId, "content.service.encrypted.timer_disabled"); + } } return content.getCompatText(); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouter.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouter.java new file mode 100644 index 0000000000..b93a78ea90 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouter.java @@ -0,0 +1,36 @@ +package im.actor.core.modules.encryption; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptionKeyGroup; +import im.actor.core.modules.ModuleContext; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +import static im.actor.runtime.actors.ActorSystem.system; + +public class EncryptedRouter extends ActorInterface { + + public EncryptedRouter(ModuleContext context) { + super(system().actorOf("encryption/router", () -> new EncryptedRouterActor(context))); + } + + public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup group) { + return ask(new EncryptedRouterActor.KeyGroupAdded(uid, group)); + } + + public Promise onKeyGroupRemoved(int uid, int keyGroupId) { + return ask(new EncryptedRouterActor.KeyGroupRemoved(uid, keyGroupId)); + } + + public Promise onEncryptedUpdate(int uid, long date, ApiEncryptedContent update) { + return ask(new EncryptedRouterActor.EncryptedUpdate(uid, date, update)); + } + + public Promise onEncryptedBox(long date, int senderId, @NotNull ApiEncryptedBox encryptedBox) { + return ask(new EncryptedRouterActor.EncryptedPackageUpdate(date, senderId, encryptedBox)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java new file mode 100644 index 0000000000..9cf931bc70 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java @@ -0,0 +1,184 @@ +package im.actor.core.modules.encryption; + +import org.jetbrains.annotations.NotNull; + +import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedChatTimerSet; +import im.actor.core.api.ApiEncryptedContent; +import im.actor.core.api.ApiEncryptionKeyGroup; +import im.actor.core.api.ApiServiceMessage; +import im.actor.core.api.ApiServiceTimerChanged; +import im.actor.core.entity.Message; +import im.actor.core.entity.MessageState; +import im.actor.core.entity.Peer; +import im.actor.core.entity.content.AbsContent; +import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.ratchet.EncryptedMsg; +import im.actor.core.modules.encryption.ratchet.KeyManager; +import im.actor.core.modules.encryption.updates.EncryptedUpdates; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class EncryptedRouterActor extends ModuleActor { + + private KeyManager keyManager; + private EncryptedMsg encryptedMsg; + private EncryptedUpdates updates; + + public EncryptedRouterActor(ModuleContext context) { + super(context); + } + + @Override + public void preStart() { + super.preStart(); + updates = new EncryptedUpdates(context()); + keyManager = context().getEncryption().getKeyManager(); + encryptedMsg = context().getEncryption().getEncryption(); + } + + public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup group) { + return keyManager.onKeyGroupAdded(uid, group); + } + + public Promise onKeyGroupRemoved(int uid, int keyGroupId) { + return keyManager.onKeyGroupRemoved(uid, keyGroupId); + } + + public Promise onTimerSet(long randomId, long date, int uid, Integer timerInMs) { + return context().getMessagesModule().getRouter() + .onNewMessage(Peer.secret(uid), new Message(randomId, date, date, + myUid(), MessageState.SENT, AbsContent.fromMessage(new ApiServiceMessage("Timer set", + new ApiServiceTimerChanged(timerInMs))))); + } + + // Messages + + public Promise onEncryptedUpdate(int uid, long date, ApiEncryptedContent update) { + if (update instanceof ApiEncryptedChatTimerSet) { + ApiEncryptedChatTimerSet timerSet = (ApiEncryptedChatTimerSet) update; + int peerId = uid; + if (timerSet.getReceiverId() != myUid()) { + peerId = timerSet.getReceiverId(); + } + return onTimerSet(timerSet.getRid(), date, peerId, timerSet.getTimerMs()); + } else { + return updates.onUpdate(uid, date, update); + } + } + + public Promise onEncryptedBox(long date, int senderId, @NotNull ApiEncryptedBox encryptedBox) { + return encryptedMsg.decrypt(senderId, encryptedBox) + .flatMap(message -> onEncryptedUpdate(senderId, date, message)) + .fallback(e -> Promise.success(null)); + } + + @Override + public Promise onAsk(Object message) throws Exception { + if (message instanceof KeyGroupAdded) { + KeyGroupAdded groupAdded = (KeyGroupAdded) message; + return onKeyGroupAdded(groupAdded.getUid(), groupAdded.getGroup()); + } else if (message instanceof KeyGroupRemoved) { + KeyGroupRemoved removed = (KeyGroupRemoved) message; + return onKeyGroupRemoved(removed.getUid(), removed.getKeyGroupId()); + } else if (message instanceof EncryptedUpdate) { + EncryptedUpdate update = (EncryptedUpdate) message; + return onEncryptedUpdate(update.getUid(), update.getDate(), update.getUpdate()); + } else if (message instanceof EncryptedPackageUpdate) { + EncryptedPackageUpdate update = (EncryptedPackageUpdate) message; + return onEncryptedBox(update.getDate(), update.getSenderId(), update.getEncryptedBox()); + } else { + return super.onAsk(message); + } + } + + public static class KeyGroupAdded implements AskMessage { + + private int uid; + private ApiEncryptionKeyGroup group; + + public KeyGroupAdded(int uid, ApiEncryptionKeyGroup group) { + this.uid = uid; + this.group = group; + } + + public int getUid() { + return uid; + } + + public ApiEncryptionKeyGroup getGroup() { + return group; + } + } + + public static class KeyGroupRemoved implements AskMessage { + + private int uid; + private int keyGroupId; + + public KeyGroupRemoved(int uid, int keyGroupId) { + this.uid = uid; + this.keyGroupId = keyGroupId; + } + + public int getUid() { + return uid; + } + + public int getKeyGroupId() { + return keyGroupId; + } + } + + public static class EncryptedUpdate implements AskMessage { + + private int uid; + private long date; + private ApiEncryptedContent update; + + public EncryptedUpdate(int uid, long date, ApiEncryptedContent update) { + this.uid = uid; + this.date = date; + this.update = update; + } + + public int getUid() { + return uid; + } + + public long getDate() { + return date; + } + + public ApiEncryptedContent getUpdate() { + return update; + } + } + + public static class EncryptedPackageUpdate implements AskMessage { + + private long date; + private int senderId; + private ApiEncryptedBox encryptedBox; + + public EncryptedPackageUpdate(long date, int senderId, @NotNull ApiEncryptedBox encryptedBox) { + this.date = date; + this.senderId = senderId; + this.encryptedBox = encryptedBox; + } + + public long getDate() { + return date; + } + + public int getSenderId() { + return senderId; + } + + public ApiEncryptedBox getEncryptedBox() { + return encryptedBox; + } + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 696b1aea72..742d5ce97d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -5,6 +5,7 @@ import java.util.List; import im.actor.core.api.ApiEncryptedBox; +import im.actor.core.api.ApiEncryptedChatTimerSet; import im.actor.core.api.ApiEncryptedContent; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestSendEncryptedPackage; @@ -27,8 +28,8 @@ public class EncryptionModule extends AbsModule { private KeyManager keyManager; private SessionManager sessionManager; + private EncryptedRouter encryptedRouter; private EncryptedMsg encryption; - private EncryptedUpdates encryptedUpdates; private final HashMap users = new HashMap<>(); @@ -40,13 +41,17 @@ public void run() { keyManager = new KeyManager(context()); sessionManager = new SessionManager(context()); encryption = new EncryptedMsg(context()); - encryptedUpdates = new EncryptedUpdates(context()); + encryptedRouter = new EncryptedRouter(context()); } public KeyManager getKeyManager() { return keyManager; } + public EncryptedRouter getRouter() { + return encryptedRouter; + } + public SessionManager getSessionManager() { return sessionManager; } @@ -66,15 +71,7 @@ public EncryptedUser getEncryptedUser(int uid) { } public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { - return encryptedUpdates.onUpdate(senderId, date, update); - } - - public Promise encrypt(List uids, ApiEncryptedContent message) { - return getEncryption().encrypt(uids, message); - } - - public Promise decrypt(int uid, ApiEncryptedBox encryptedBox) { - return getEncryption().decrypt(uid, encryptedBox); + return encryptedRouter.onEncryptedUpdate(senderId, date, update); } public Promise doSend(ApiEncryptedContent content, int uid) { @@ -101,7 +98,7 @@ public Promise doSend(long rid, ApiEncryptedContent content, List outPeers.add(new ApiUserOutPeer(i, users().getValue(i).getAccessHash())); } - return encrypt(uids, content) + return getEncryption().encrypt(uids, content) .flatMap(m -> api(new RequestSendEncryptedPackage(rid, outPeers, m.getIgnoredGroups(), m.getEncryptedBox()))) .flatMap(r -> { if (r.getDate() != null) { @@ -112,4 +109,12 @@ public Promise doSend(long rid, ApiEncryptedContent content, List } }); } + + + public Promise setSecretChatTimer(int uid, Integer timeout) { + ApiEncryptedContent encryptedContent = new ApiEncryptedChatTimerSet(uid, + RandomUtils.nextRid(), timeout); + return doSend(encryptedContent, uid) + .flatMap(r -> getRouter().onEncryptedUpdate(myUid(), r, encryptedContent)); + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java index b34ad7a796..6b8079e2ed 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionProcessor.java @@ -21,23 +21,19 @@ public Promise process(Update update) { if (update instanceof UpdatePublicKeyGroupAdded) { UpdatePublicKeyGroupAdded groupAdded = (UpdatePublicKeyGroupAdded) update; return context().getEncryption() - .getKeyManager() + .getRouter() .onKeyGroupAdded(groupAdded.getUid(), groupAdded.getKeyGroup()); } else if (update instanceof UpdatePublicKeyGroupRemoved) { UpdatePublicKeyGroupRemoved groupRemoved = (UpdatePublicKeyGroupRemoved) update; return context().getEncryption() - .getKeyManager() + .getRouter() .onKeyGroupRemoved(groupRemoved.getUid(), groupRemoved.getKeyGroupId()); } else if (update instanceof UpdateEncryptedPackage) { UpdateEncryptedPackage encryptedPackage = (UpdateEncryptedPackage) update; return context().getEncryption() - .decrypt(encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()) - .flatMap(message -> context().getEncryption() - .onUpdate( - encryptedPackage.getSenderId(), - encryptedPackage.getDate(), - message)) - .fallback(e -> Promise.success(null)); + .getRouter() + .onEncryptedBox(encryptedPackage.getDate(), + encryptedPackage.getSenderId(), encryptedPackage.getEncryptedBox()); } return null; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedSequenceProcessor.java similarity index 84% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedSequenceProcessor.java index f509226154..97d73f8ec5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedSequenceProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedSequenceProcessor.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.updates; import im.actor.core.api.ApiEncryptedContent; import im.actor.runtime.actors.messages.Void; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedUpdates.java similarity index 95% rename from actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java rename to actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedUpdates.java index 526a5b6dc9..3485cf3929 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedUpdates.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/updates/EncryptedUpdates.java @@ -1,4 +1,4 @@ -package im.actor.core.modules.encryption; +package im.actor.core.modules.encryption.updates; import im.actor.core.api.ApiEncryptedContent; import im.actor.core.modules.AbsModule; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index 29c3498c57..bda6358acc 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -289,7 +289,6 @@ public Promise updateMessage(final Peer peer, final String message, final if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { ApiEncryptedEditContent editContent = new ApiEncryptedEditContent( peer.getPeerId(), rid, new ApiTextMessage(message, new ArrayList<>(), null)); - return context().getEncryption().doSend(RandomUtils.nextRid(), editContent, peer.getPeerId()).then(r -> { context().getEncryption().onUpdate(myUid(), r, editContent); }).flatMap(r -> null); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java index 5647f4e74b..0d68d34fca 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesProcessorEncrypted.java @@ -9,7 +9,7 @@ import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; -import im.actor.core.modules.encryption.EncryptedSequenceProcessor; +import im.actor.core.modules.encryption.updates.EncryptedSequenceProcessor; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index f31e18096b..b5b5b2c5e8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -498,7 +498,7 @@ public void onError(RpcException e) { } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { Log.d("SenderActor", "Pending encrypted message: " + message); ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), - rid, message); + rid, message, null); context().getEncryption().doSend(rid, content, peer.getPeerId()) .chain(r -> context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json index 53778a8d92..718cac65a6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText.json @@ -1,206 +1,212 @@ { - "app": { - "name": "Actor" - }, - - "language": { - - "code": "En", - - "format": { - "time": { - "yesterday":{ - "short": "Yest", - "full": "Yesterday" - }, - "now": "Now", - "minutes": { - "short": "{minutes} min", - "full": "{minutes} minutes" - }, - "hours": { - "short": "{hours} hrs", - "full": "{hours} hours" - } - } - }, - - "file_size": { - "bytes": "{bytes} B", - "kbytes": "{kbytes} KB", - "mbytes": "{mbytes} MB", - "gbytes": "{gbytes} GB" - }, - - "referencing": { - "you": "You", - "thee": "You" - }, - - "sequence": { - "or": ", ", - "and": " and " - } - }, - - "groups": { - "members": { - "one": "{count} member", - "other": "{count} members" - } - }, - - "presence": { - "online": "online", - "offline": "offline", - "now": "last seen just now", - "today": "last seen today at {time}", - "yesterday": "last seen yesterday at {time}", - "at_day": "last seen {date}", - "at_day_time": "last seen {date} at {time}", - "members": "{count} online" - }, - - "typing": { - "simple": "typing...", - "user": "{user} is typing...", - "group": { - "sequenced": "{users} are typing...", - "many": "{count} people are typing..." - } - }, - - "content": { - - "unknown": "Unsupported content", - "document": "Document", - "photo": "Photo", - "video": "Video", - "audio": "Audio", - "contact": "Contact", - "location": "Location", - "sticker": "Sticker", - - "service": { - "registered": { - "full": "{name} joined {app_name}", - "compact": "Joined {app_name}" - }, - "groups": { - "created": "{name} created the group", - "invited": "{name} invited {name_added}", - "joined": "{name} joined group", - "kicked": "{name} kicked {name_kicked}", - "left": "{name} left group", - "title_changed": { - "full": "{name} changed the group name to \"{title}\"", - "compact": "{name} changed the group name" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name} changed the group photo", - "avatar_removed": "{name} removed the group photo" - }, - "channels": { - "created": "Channel created", - "invited": "{name} invited {name_added}", - "joined": "{name} joined channel", - "kicked": "{name} kicked {name_kicked}", - "left": "{name} left channel", - "title_changed": { - "full": "{name} changed the channel name to \"{title}\"", - "compact": "{name} changed the channel name" - }, - "topic_changed": { - "full": "{name} changed the channel topic to \"{topic}\"", - "compact": "{name} changed the channel topic" - }, - "about_changed": { - "full": "{name} changed the channel about to \"{about}\"", - "compact": "{name} changed the channel about" - }, - "avatar_changed": "{name} changed the channel photo", - "avatar_removed": "{name} removed the channel photo" - }, - "calls": { - "missed": "Missed call", - "ended": "Call ended" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "Internal server error. Please, try again later.", - "phone": { - "empty": "Please, enter your phone number.", - "incorrect": "Entered phone number is invalid. Please, try again." - }, - "code": { - "empty": "The code is invalid. Please try again.", - "incorrect": "Entered code is incorrect. Please, try again.", - "expired": "The code has expired. Please, start authentication again." - }, - "groups": { - "already_joined": "You have already joined the group.", - "unable_to_join": "Unable to join the group." - } - }, - - "months": { - "january": { - "compact": "jan", - "full":"january" - }, - "february": { - "compact": "feb", - "full":"february" - }, - "march": { - "compact": "mar", - "full":"march" - }, - "april": { - "compact": "apr", - "full":"april" - }, - "may": { - "compact": "may", - "full":"may" - }, - "june": { - "compact": "jun", - "full":"june" - }, - "july": { - "compact": "jul", - "full":"july" - }, - "august": { - "compact": "aug", - "full":"august" - }, - "september": { - "compact": "sep", - "full":"september" - }, - "october": { - "compact": "oct", - "full":"october" - }, - "november": { - "compact": "nov", - "full":"november" - }, - "december": { - "compact": "dec", - "full":"december" - } - } + "app": { + "name": "Actor" + }, + "language": { + "code": "En", + "format": { + "time": { + "yesterday": { + "short": "Yest", + "full": "Yesterday" + }, + "now": "Now", + "seconds": { + "short": "{seconds} sec", + "full": { + "one": "{seconds} second", + "other": "{seconds} seconds" + } + }, + "minutes": { + "short": "{minutes} min", + "full": { + "one": "{minutes} minute", + "other": "{minutes} minutes" + } + }, + "hours": { + "short": "{hours} hrs", + "full": { + "one": "{hours} hour", + "other": "{hours} hours" + } + } + } + }, + "file_size": { + "bytes": "{bytes} B", + "kbytes": "{kbytes} KB", + "mbytes": "{mbytes} MB", + "gbytes": "{gbytes} GB" + }, + "referencing": { + "you": "You", + "thee": "You" + }, + "sequence": { + "or": ", ", + "and": " and " + } + }, + "groups": { + "members": { + "one": "{count} member", + "other": "{count} members" + } + }, + "presence": { + "online": "online", + "offline": "offline", + "now": "last seen just now", + "today": "last seen today at {time}", + "yesterday": "last seen yesterday at {time}", + "at_day": "last seen {date}", + "at_day_time": "last seen {date} at {time}", + "members": "{count} online" + }, + "typing": { + "simple": "typing...", + "user": "{user} is typing...", + "group": { + "sequenced": "{users} are typing...", + "many": "{count} people are typing..." + } + }, + "content": { + "unknown": "Unsupported content", + "document": "Document", + "photo": "Photo", + "video": "Video", + "audio": "Audio", + "contact": "Contact", + "location": "Location", + "sticker": "Sticker", + "service": { + "registered": { + "full": "{name} joined {app_name}", + "compact": "Joined {app_name}" + }, + "groups": { + "created": "{name} created the group", + "invited": "{name} invited {name_added}", + "joined": "{name} joined group", + "kicked": "{name} kicked {name_kicked}", + "left": "{name} left group", + "title_changed": { + "full": "{name} changed the group name to \"{title}\"", + "compact": "{name} changed the group name" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} changed the group photo", + "avatar_removed": "{name} removed the group photo" + }, + "channels": { + "created": "Channel created", + "invited": "{name} invited {name_added}", + "joined": "{name} joined channel", + "kicked": "{name} kicked {name_kicked}", + "left": "{name} left channel", + "title_changed": { + "full": "{name} changed the channel name to \"{title}\"", + "compact": "{name} changed the channel name" + }, + "topic_changed": { + "full": "{name} changed the channel topic to \"{topic}\"", + "compact": "{name} changed the channel topic" + }, + "about_changed": { + "full": "{name} changed the channel about to \"{about}\"", + "compact": "{name} changed the channel about" + }, + "avatar_changed": "{name} changed the channel photo", + "avatar_removed": "{name} removed the channel photo" + }, + "calls": { + "missed": "Missed call", + "ended": "Call ended" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "Internal server error. Please, try again later.", + "phone": { + "empty": "Please, enter your phone number.", + "incorrect": "Entered phone number is invalid. Please, try again." + }, + "code": { + "empty": "The code is invalid. Please try again.", + "incorrect": "Entered code is incorrect. Please, try again.", + "expired": "The code has expired. Please, start authentication again." + }, + "groups": { + "already_joined": "You have already joined the group.", + "unable_to_join": "Unable to join the group." + } + }, + "months": { + "january": { + "compact": "jan", + "full": "january" + }, + "february": { + "compact": "feb", + "full": "february" + }, + "march": { + "compact": "mar", + "full": "march" + }, + "april": { + "compact": "apr", + "full": "april" + }, + "may": { + "compact": "may", + "full": "may" + }, + "june": { + "compact": "jun", + "full": "june" + }, + "july": { + "compact": "jul", + "full": "july" + }, + "august": { + "compact": "aug", + "full": "august" + }, + "september": { + "compact": "sep", + "full": "september" + }, + "october": { + "compact": "oct", + "full": "october" + }, + "november": { + "compact": "nov", + "full": "november" + }, + "december": { + "compact": "dec", + "full": "december" + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json index 308c7b5142..858c9b4c2d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ar.json @@ -1,157 +1,152 @@ { - "app": { - "name": "Actor" - }, - - "language": { - "code": "Ar", - - "format": { - "time": { - "yesterday": "امس", - "now": "الان", - "minutes": "{minutes} دقايق", - "hours": "{hours} ساعات" - } - }, - - "file_size": { - "bytes": "{bytes} بايت", - "kbytes": "{kbytes} كيلو بايت", - "mbytes": "{mbytes} ميجا بايت", - "gbytes": "{gbytes} جيجا بايت" - }, - - "referencing": { - "you": "انت", - "thee": "انت" - }, - - "sequence": { - "or": ",", - "and": "," - } - }, - - "groups": { - "members": "{count} اعضاء" - }, - - "presence": { - "online": "متصل", - "offline": "غير متصل", - "now": "اخر ظهور من", - "today": "اخر ظهور اليوم {time}", - "yesterday": "اخر ظهور بالامس {time}", - "at_day": "اخر ظهور {date}", - "at_day_time": "اخر ظهور {date} على {time}", - "members": "{count} متصلين" - }, - - "typing": { - "simple": "كتابة...", - "user": "{user} يكتب...", - "group": { - "sequenced": "{users} يكتبون...", - "many": "{count} اشخاص يكتبون..." - } - }, - - "content": { - - "unknown": "غير مدعوم", - "document": "مستند", - "photo": "صورة", - "video": "فيديو", - "audio": "Audio", - "contact": "Contact", - "location": "Location", - "sticker": "Sticker", - - "service": { - "registered": { - "full": "{name} انضم لشبكة اكتور {app_name}", - "compact": "انضم لأكتور {app_name}" - }, - "groups": { - "created": "{name} انشأ المجموعة", - "invited": "{name} دعى {name_added}", - "joined": "{name} انصم للمجموعة", - "kicked": "{name} ازال {name_kicked}", - "left": "{name} غادر", - "title_changed": { - "full": "{name} غير الاسم الى \"{title}\"", - "compact": "{name} غير اسم المجموعة الى" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name} غير صورة المجموعة", - "avatar_removed": "{name} حذف صورة المجموعة" - }, - "channels": { - "created": " انشأ المجموعة", - "invited": "{name} دعى {name_added}", - "joined": "{name} انصم للمجموعة", - "kicked": "{name} ازال {name_kicked}", - "left": "{name} غادر", - "title_changed": { - "full": "{name} غير الاسم الى \"{title}\"", - "compact": "{name} غير اسم المجموعة الى" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name} غير صورة المجموعة", - "avatar_removed": "{name} حذف صورة المجموعة" - }, - "calls": { - "missed": "Missed call", - "ended": "Call ended" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "Internal server error. Please, try again later.", - "phone": { - "empty": "Please, enter your phone number.", - "incorrect": "Entered phone number is invalid. Please, try again." - }, - "code": { - "empty": "The code is invalid. Please try again.", - "incorrect": "Entered code is incorrect. Please, try again.", - "expired": "The code has expired. Please, start authentication again." - }, - "groups": { - "already_joined": "You have already joined the group.", - "unable_to_join": "Unable to join the group." - } - }, - - "months": { - "january": "يناير", - "february": "فبراير", - "march": "مارس", - "april": "ابريل", - "may": "مايو", - "june": "يونيو", - "july": "يوليو", - "august": "اغسطس", - "september": "سمبتمر", - "october": "اكتوبر", - "november": "نوفمبر", - "december": "ديسمبر" - } + "app": { + "name": "Actor" + }, + "language": { + "code": "Ar", + "format": { + "time": { + "yesterday": "امس", + "now": "الان", + "seconds": "{seconds} seconds", + "minutes": "{minutes} دقايق", + "hours": "{hours} ساعات" + } + }, + "file_size": { + "bytes": "{bytes} بايت", + "kbytes": "{kbytes} كيلو بايت", + "mbytes": "{mbytes} ميجا بايت", + "gbytes": "{gbytes} جيجا بايت" + }, + "referencing": { + "you": "انت", + "thee": "انت" + }, + "sequence": { + "or": ",", + "and": "," + } + }, + "groups": { + "members": "{count} اعضاء" + }, + "presence": { + "online": "متصل", + "offline": "غير متصل", + "now": "اخر ظهور من", + "today": "اخر ظهور اليوم {time}", + "yesterday": "اخر ظهور بالامس {time}", + "at_day": "اخر ظهور {date}", + "at_day_time": "اخر ظهور {date} على {time}", + "members": "{count} متصلين" + }, + "typing": { + "simple": "كتابة...", + "user": "{user} يكتب...", + "group": { + "sequenced": "{users} يكتبون...", + "many": "{count} اشخاص يكتبون..." + } + }, + "content": { + "unknown": "غير مدعوم", + "document": "مستند", + "photo": "صورة", + "video": "فيديو", + "audio": "Audio", + "contact": "Contact", + "location": "Location", + "sticker": "Sticker", + "service": { + "registered": { + "full": "{name} انضم لشبكة اكتور {app_name}", + "compact": "انضم لأكتور {app_name}" + }, + "groups": { + "created": "{name} انشأ المجموعة", + "invited": "{name} دعى {name_added}", + "joined": "{name} انصم للمجموعة", + "kicked": "{name} ازال {name_kicked}", + "left": "{name} غادر", + "title_changed": { + "full": "{name} غير الاسم الى \"{title}\"", + "compact": "{name} غير اسم المجموعة الى" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} غير صورة المجموعة", + "avatar_removed": "{name} حذف صورة المجموعة" + }, + "channels": { + "created": " انشأ المجموعة", + "invited": "{name} دعى {name_added}", + "joined": "{name} انصم للمجموعة", + "kicked": "{name} ازال {name_kicked}", + "left": "{name} غادر", + "title_changed": { + "full": "{name} غير الاسم الى \"{title}\"", + "compact": "{name} غير اسم المجموعة الى" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} غير صورة المجموعة", + "avatar_removed": "{name} حذف صورة المجموعة" + }, + "calls": { + "missed": "Missed call", + "ended": "Call ended" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "Internal server error. Please, try again later.", + "phone": { + "empty": "Please, enter your phone number.", + "incorrect": "Entered phone number is invalid. Please, try again." + }, + "code": { + "empty": "The code is invalid. Please try again.", + "incorrect": "Entered code is incorrect. Please, try again.", + "expired": "The code has expired. Please, start authentication again." + }, + "groups": { + "already_joined": "You have already joined the group.", + "unable_to_join": "Unable to join the group." + } + }, + "months": { + "january": "يناير", + "february": "فبراير", + "march": "مارس", + "april": "ابريل", + "may": "مايو", + "june": "يونيو", + "july": "يوليو", + "august": "اغسطس", + "september": "سمبتمر", + "october": "اكتوبر", + "november": "نوفمبر", + "december": "ديسمبر" + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json index 7cdcd77f9d..499c0df300 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Es.json @@ -1,199 +1,197 @@ { - "app": { - "name": "Actor" - }, - - "language": { - "code": "Es", - - "format": { - "time": { - "yesterday": "Ayer", - "now": "Ahora", - "minutes": { - "short": "{minutes} min", - "full": "{minutes} minutes" - }, - "hours": { - "short": "{hours} hrs", - "full": "{hours} hours" - } - } - }, - - "file_size": { - "bytes": "{bytes} B", - "kbytes": "{kbytes} KB", - "mbytes": "{mbytes} MB", - "gbytes": "{gbytes} GB" - }, - - "referencing": { - "you": "Tu", - "thee": "Tu" - }, - - "sequence": { - "or": ", ", - "and": " and " - } - }, - - "groups": { - "members": "{count} miembros" - }, - - "presence": { - "online": "En línea", - "offline": "Desconectado", - "now": "visto segundos atrás", - "today": "últ. vez {time}", - "yesterday": "últ. vez ayer {time}", - "at_day": "ult. vez el {date}", - "at_day_time": "ult. vez el {date} a las {time}", - "members": "{count} en línea" - }, - - "typing": { - "simple": "escribiendo...", - "user": "{user} está escribiendo...", - "group": { - "sequenced": "{users} estan escribiendo...", - "many": "{count} personas estan escribiendo..." - } - }, - - "content": { - - "unknown": "Contenido no compatible", - "document": "Documentos", - "photo": "Foto", - "video": "Vídeo", - "audio": "Audio", - "contact": "Contacto", - "location": "Localización", - "sticker": "Sticker", - - "service": { - "registered": { - "full": "{name} unido a la red {app_name}", - "compact": "Entro en {app_name}" - }, - "groups": { - "created": "{name} сreado el grupo", - "invited": "{name} invitó a {name_added}", - "joined": "{name} se unió al grupo", - "kicked": "{name} expulsaste a {name_kicked}", - "left": "{name} elimino el grupo", - "title_changed": { - "full": "{name} cambio el nombre del grupo a \"{title}\"", - "compact": "{name} a modificado el nombre del grupo" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name} modificada la foto de grupo", - "avatar_removed": "{name} eliminada la foto de grupo" - }, - "channels": { - "created": "Creado el canal", - "invited": "{name} invitó a {name_added}", - "joined": "{name} se unió al canal", - "kicked": "{name} expulsaste a {name_kicked}", - "left": "{name} elimino el canal", - "title_changed": { - "full": "{name} cambio el nombre del canal a \"{title}\"", - "compact": "{name} a modificado el nombre del canal" - }, - "topic_changed": { - "full": "{name} changed the canal topic to \"{topic}\"", - "compact": "{name} changed the canal topic" - }, - "about_changed": { - "full": "{name} changed the canal about to \"{about}\"", - "compact": "{name} changed the canal about" - }, - "avatar_changed": "{name} modificada la foto de canal", - "avatar_removed": "{name} eliminada la foto de canal" - }, - "calls": { - "missed": "Llamada perdida", - "ended": "Llamada finalizada" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "Error Interno del Servidor. Por favor, inténtelo de nuevo más tarde.", - "phone": { - "empty": "Por favor, introduce tu número de teléfono.", - "incorrect": "Número de teléfono introducido no es válido. Por favor, vuelve a intentarlo." - }, - "code": { - "empty": "Código no válido. Por favor, vuelve a intentarlo.", - "incorrect": "El código introducido es incorrecta. Por favor, vuelve a intentarlo.", - "expired": "Código expirado. Por favor, inicia la autenticación de nuevo." - }, - "groups": { - "already_joined": "Ya eres miembro del grupo.", - "unable_to_join": "No puede unirse al grupo." - } - }, - - "months": { - "january": { - "compact": "ene", - "full":"enero" - }, - "february": { - "compact": "feb", - "full":"febrero" - }, - "march": { - "compact": "mar", - "full":"marzo" - }, - "april": { - "compact": "apr", - "full":"abril" - }, - "may": { - "compact": "may", - "full":"mayo" - }, - "june": { - "compact": "jun", - "full":"junio" - }, - "july": { - "compact": "jul", - "full":"july" - }, - "august": { - "compact": "ago", - "full":"julio" - }, - "september": { - "compact": "sep", - "full":"septembre" - }, - "october": { - "compact": "oct", - "full":"octubre" - }, - "november": { - "compact": "nov", - "full":"noviembre" - }, - "december": { - "compact": "dic", - "full":"diciembre" - } - } + "app": { + "name": "Actor" + }, + "language": { + "code": "Es", + "format": { + "time": { + "yesterday": "Ayer", + "now": "Ahora", + "seconds": { + "short": "{minutes} sec", + "full": "{minutes} seconds" + }, + "minutes": { + "short": "{minutes} min", + "full": "{minutes} minutes" + }, + "hours": { + "short": "{hours} hrs", + "full": "{hours} hours" + } + } + }, + "file_size": { + "bytes": "{bytes} B", + "kbytes": "{kbytes} KB", + "mbytes": "{mbytes} MB", + "gbytes": "{gbytes} GB" + }, + "referencing": { + "you": "Tu", + "thee": "Tu" + }, + "sequence": { + "or": ", ", + "and": " and " + } + }, + "groups": { + "members": "{count} miembros" + }, + "presence": { + "online": "En línea", + "offline": "Desconectado", + "now": "visto segundos atrás", + "today": "últ. vez {time}", + "yesterday": "últ. vez ayer {time}", + "at_day": "ult. vez el {date}", + "at_day_time": "ult. vez el {date} a las {time}", + "members": "{count} en línea" + }, + "typing": { + "simple": "escribiendo...", + "user": "{user} está escribiendo...", + "group": { + "sequenced": "{users} estan escribiendo...", + "many": "{count} personas estan escribiendo..." + } + }, + "content": { + "unknown": "Contenido no compatible", + "document": "Documentos", + "photo": "Foto", + "video": "Vídeo", + "audio": "Audio", + "contact": "Contacto", + "location": "Localización", + "sticker": "Sticker", + "service": { + "registered": { + "full": "{name} unido a la red {app_name}", + "compact": "Entro en {app_name}" + }, + "groups": { + "created": "{name} сreado el grupo", + "invited": "{name} invitó a {name_added}", + "joined": "{name} se unió al grupo", + "kicked": "{name} expulsaste a {name_kicked}", + "left": "{name} elimino el grupo", + "title_changed": { + "full": "{name} cambio el nombre del grupo a \"{title}\"", + "compact": "{name} a modificado el nombre del grupo" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} modificada la foto de grupo", + "avatar_removed": "{name} eliminada la foto de grupo" + }, + "channels": { + "created": "Creado el canal", + "invited": "{name} invitó a {name_added}", + "joined": "{name} se unió al canal", + "kicked": "{name} expulsaste a {name_kicked}", + "left": "{name} elimino el canal", + "title_changed": { + "full": "{name} cambio el nombre del canal a \"{title}\"", + "compact": "{name} a modificado el nombre del canal" + }, + "topic_changed": { + "full": "{name} changed the canal topic to \"{topic}\"", + "compact": "{name} changed the canal topic" + }, + "about_changed": { + "full": "{name} changed the canal about to \"{about}\"", + "compact": "{name} changed the canal about" + }, + "avatar_changed": "{name} modificada la foto de canal", + "avatar_removed": "{name} eliminada la foto de canal" + }, + "calls": { + "missed": "Llamada perdida", + "ended": "Llamada finalizada" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "Error Interno del Servidor. Por favor, inténtelo de nuevo más tarde.", + "phone": { + "empty": "Por favor, introduce tu número de teléfono.", + "incorrect": "Número de teléfono introducido no es válido. Por favor, vuelve a intentarlo." + }, + "code": { + "empty": "Código no válido. Por favor, vuelve a intentarlo.", + "incorrect": "El código introducido es incorrecta. Por favor, vuelve a intentarlo.", + "expired": "Código expirado. Por favor, inicia la autenticación de nuevo." + }, + "groups": { + "already_joined": "Ya eres miembro del grupo.", + "unable_to_join": "No puede unirse al grupo." + } + }, + "months": { + "january": { + "compact": "ene", + "full": "enero" + }, + "february": { + "compact": "feb", + "full": "febrero" + }, + "march": { + "compact": "mar", + "full": "marzo" + }, + "april": { + "compact": "apr", + "full": "abril" + }, + "may": { + "compact": "may", + "full": "mayo" + }, + "june": { + "compact": "jun", + "full": "junio" + }, + "july": { + "compact": "jul", + "full": "july" + }, + "august": { + "compact": "ago", + "full": "julio" + }, + "september": { + "compact": "sep", + "full": "septembre" + }, + "october": { + "compact": "oct", + "full": "octubre" + }, + "november": { + "compact": "nov", + "full": "noviembre" + }, + "december": { + "compact": "dic", + "full": "diciembre" + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json index 0d663fece5..fe24ed4620 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Fa.json @@ -1,157 +1,152 @@ { - "app": { - "name": "Actor" - }, - - "language": { - "code": "Fa", - - "format": { - "time": { - "yesterday": "دیروز", - "now": "الآن", - "minutes": "{minutes} دقیقه", - "hours": "{hours} ساعت" - } - }, - - "file_size": { - "bytes": "{bytes} B", - "kbytes": "{kbytes} KB", - "mbytes": "{mbytes} MB", - "gbytes": "{gbytes} GB" - }, - - "referencing": { - "you": "شما", - "thee": "شما" - }, - - "sequence": { - "or": ",", - "and": "," - } - }, - - "groups": { - "members": "{count} نفر" - }, - - "presence": { - "online": "برخط", - "offline": "برون‌خط", - "now": "اخیرن اینجا بوده", - "today": "آخرین بار درساعت {time} دیده شده", - "yesterday": "آخرین بار دیروز ساعت {time} دیده شده", - "at_day": "آخرین بار در تاریخ {date} دیده شده:", - "at_day_time": "آخرین بار در تاریخ {date} و ساعت {time} دیده شده", - "members": "{count} نفر" - }, - - "typing": { - "simple": "در حال نوشتن...", - "user": "{user} در حال نوشتن ...", - "group": { - "sequenced": "{users} نفر در حال نوشتن ...", - "many": "{count} نفر در حال نوشتن ..." - } - }, - - "content": { - - "unknown": "محتوای نامشخص", - "document": "سند", - "photo": "تصویر", - "video": "ویدیو", - "audio": "صدا", - "contact": "مخاطب", - "location": "موقعیت", - "sticker": "استیکر", - - "service": { - "registered": { - "full": "{name} به {app_name} پیوست", - "compact": "به {app_name} پیوست" - }, - "groups": { - "created": "{name} گروه را ایجاد کرد", - "invited": "{name} {name_added} را دعوت کرد", - "joined": "{name} به گروه پیوست", - "kicked": "{name} {name_kicked} را از گروه حذف کرد", - "left": "{name} از گروه رفت", - "title_changed": { - "full": "{name} نام گروه را با\"{title}\" جایگزین کرد", - "compact": "{name} نام گروه را جایگزین کرد" - }, - "topic_changed": { - "full": "جناب {name} موضوع گروه را با \"{topic}\" جای‌گزین کردید", - "compact": "موضوع گروه را عوض کرد {name}" - }, - "about_changed": { - "full": "جناب {name} متن درباره گروه را با \"{about}\" جای‌گزین کردید", - "compact": "متن درباره گروه را عوض کرد {name}" - }, - "avatar_changed": "{name} تصویر گروه را عوض کرد", - "avatar_removed": "{name} تصویر گروه را عوض کرد" - }, - "channels": { - "created": " گروه را ایجاد کرد", - "invited": "{name} {name_added} را دعوت کرد", - "joined": "{name} به گروه پیوست", - "kicked": "{name} {name_kicked} را از گروه حذف کرد", - "left": "{name} از گروه رفت", - "title_changed": { - "full": "{name} نام گروه را با\"{title}\" جایگزین کرد", - "compact": "{name} نام گروه را جایگزین کرد" - }, - "topic_changed": { - "full": "جناب {name} موضوع گروه را با \"{topic}\" جای‌گزین کردید", - "compact": "موضوع گروه را عوض کرد {name}" - }, - "about_changed": { - "full": "جناب {name} متن درباره گروه را با \"{about}\" جای‌گزین کردید", - "compact": "متن درباره گروه را عوض کرد {name}" - }, - "avatar_changed": "{name} تصویر گروه را عوض کرد", - "avatar_removed": "{name} تصویر گروه را عوض کرد" - }, - "calls": { - "missed": "تماس از دست رفته", - "ended": "تماس تمام شد" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "خطای داخلی کارگزار. لطفن بعدن دوباره تلاش کنید", - "phone": { - "empty": "لطفن شماره تلفن خودتان را وارد کنید", - "incorrect": "شماره تلفن وارد شده معتبر نیست. لطفن دوباره تلاش کنید." - }, - "code": { - "empty": "کدتان معتبر نیست. لطفن دوباره تلاش کنید.", - "incorrect": "کد وارد شده معتبر نیست. لطفن دوباره تلاش کنید.", - "expired": "کدتان منقضی شده. لطفن دوباره از ابتدا شروع کنید." - }, - "groups": { - "already_joined": "شما هم‌اکنون نیز عضو گروه هستید.", - "unable_to_join": "امکان عضویت در گروه نیست" - } - }, - - "months": { - "january": "ژانویه", - "february": "فوریه", - "march": "مارس", - "april": "آوریل", - "may": "می", - "june": "ژوئن", - "july": "جولای", - "august": "آگوست", - "september": "سپتامبر", - "october": "اکتبر", - "november": "نوامبر", - "december": "دسامبر" - } + "app": { + "name": "Actor" + }, + "language": { + "code": "Fa", + "format": { + "time": { + "yesterday": "دیروز", + "now": "الآن", + "seconds": "{seconds} seconds", + "minutes": "{minutes} دقیقه", + "hours": "{hours} ساعت" + } + }, + "file_size": { + "bytes": "{bytes} B", + "kbytes": "{kbytes} KB", + "mbytes": "{mbytes} MB", + "gbytes": "{gbytes} GB" + }, + "referencing": { + "you": "شما", + "thee": "شما" + }, + "sequence": { + "or": ",", + "and": "," + } + }, + "groups": { + "members": "{count} نفر" + }, + "presence": { + "online": "برخط", + "offline": "برون‌خط", + "now": "اخیرن اینجا بوده", + "today": "آخرین بار درساعت {time} دیده شده", + "yesterday": "آخرین بار دیروز ساعت {time} دیده شده", + "at_day": "آخرین بار در تاریخ {date} دیده شده:", + "at_day_time": "آخرین بار در تاریخ {date} و ساعت {time} دیده شده", + "members": "{count} نفر" + }, + "typing": { + "simple": "در حال نوشتن...", + "user": "{user} در حال نوشتن ...", + "group": { + "sequenced": "{users} نفر در حال نوشتن ...", + "many": "{count} نفر در حال نوشتن ..." + } + }, + "content": { + "unknown": "محتوای نامشخص", + "document": "سند", + "photo": "تصویر", + "video": "ویدیو", + "audio": "صدا", + "contact": "مخاطب", + "location": "موقعیت", + "sticker": "استیکر", + "service": { + "registered": { + "full": "{name} به {app_name} پیوست", + "compact": "به {app_name} پیوست" + }, + "groups": { + "created": "{name} گروه را ایجاد کرد", + "invited": "{name} {name_added} را دعوت کرد", + "joined": "{name} به گروه پیوست", + "kicked": "{name} {name_kicked} را از گروه حذف کرد", + "left": "{name} از گروه رفت", + "title_changed": { + "full": "{name} نام گروه را با\"{title}\" جایگزین کرد", + "compact": "{name} نام گروه را جایگزین کرد" + }, + "topic_changed": { + "full": "جناب {name} موضوع گروه را با \"{topic}\" جای‌گزین کردید", + "compact": "موضوع گروه را عوض کرد {name}" + }, + "about_changed": { + "full": "جناب {name} متن درباره گروه را با \"{about}\" جای‌گزین کردید", + "compact": "متن درباره گروه را عوض کرد {name}" + }, + "avatar_changed": "{name} تصویر گروه را عوض کرد", + "avatar_removed": "{name} تصویر گروه را عوض کرد" + }, + "channels": { + "created": " گروه را ایجاد کرد", + "invited": "{name} {name_added} را دعوت کرد", + "joined": "{name} به گروه پیوست", + "kicked": "{name} {name_kicked} را از گروه حذف کرد", + "left": "{name} از گروه رفت", + "title_changed": { + "full": "{name} نام گروه را با\"{title}\" جایگزین کرد", + "compact": "{name} نام گروه را جایگزین کرد" + }, + "topic_changed": { + "full": "جناب {name} موضوع گروه را با \"{topic}\" جای‌گزین کردید", + "compact": "موضوع گروه را عوض کرد {name}" + }, + "about_changed": { + "full": "جناب {name} متن درباره گروه را با \"{about}\" جای‌گزین کردید", + "compact": "متن درباره گروه را عوض کرد {name}" + }, + "avatar_changed": "{name} تصویر گروه را عوض کرد", + "avatar_removed": "{name} تصویر گروه را عوض کرد" + }, + "calls": { + "missed": "تماس از دست رفته", + "ended": "تماس تمام شد" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "خطای داخلی کارگزار. لطفن بعدن دوباره تلاش کنید", + "phone": { + "empty": "لطفن شماره تلفن خودتان را وارد کنید", + "incorrect": "شماره تلفن وارد شده معتبر نیست. لطفن دوباره تلاش کنید." + }, + "code": { + "empty": "کدتان معتبر نیست. لطفن دوباره تلاش کنید.", + "incorrect": "کد وارد شده معتبر نیست. لطفن دوباره تلاش کنید.", + "expired": "کدتان منقضی شده. لطفن دوباره از ابتدا شروع کنید." + }, + "groups": { + "already_joined": "شما هم‌اکنون نیز عضو گروه هستید.", + "unable_to_join": "امکان عضویت در گروه نیست" + } + }, + "months": { + "january": "ژانویه", + "february": "فوریه", + "march": "مارس", + "april": "آوریل", + "may": "می", + "june": "ژوئن", + "july": "جولای", + "august": "آگوست", + "september": "سپتامبر", + "october": "اکتبر", + "november": "نوامبر", + "december": "دسامبر" + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json index f42f5e152e..23d11714e7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Pt.json @@ -1,206 +1,203 @@ { - "app": { - "name": "Actor" - }, - - "language": { - - "code": "Pt", - - "format": { - "time": { - "yesterday":{ - "short": "Ontem", - "full": "Ontem" - }, - "now": "Agora", - "minutes": { - "short": "{minutes} min", - "full": "{minutes} min" - }, - "hours": { - "short": "{hours} hs", - "full": "{hours} hs" - } - } - }, - - "file_size": { - "bytes": "{bytes} B", - "kbytes": "{kbytes} KB", - "mbytes": "{mbytes} MB", - "gbytes": "{gbytes} GB" - }, - - "referencing": { - "you": "Você", - "thee": "Você" - }, - - "sequence": { - "or": ", ", - "and": " and " - } - }, - - "groups": { - "members": { - "other": "{count} membros", - "one": "{count} membro" - } - }, - - "presence": { - "online": "online", - "offline": "offline", - "now": "visto agora mesmo", - "today": "visto hoje às {time}", - "yesterday": "visto ontem às {time}", - "at_day": "visto {date}", - "at_day_time": "visto {date} às {time}", - "members": "{count} online" - }, - - "typing": { - "simple": "digitando...", - "user": "{user} está digitando...", - "group": { - "sequenced": "{users} estão digitando...", - "many": "{count} pessoas estão digitando..." - } - }, - - "content": { - - "unknown": "Conteúdo não suportado", - "document": "Documento", - "photo": "Foto", - "video": "Vídeo", - "audio": "Audio", - "contact": "Contact", - "location": "Location", - "sticker": "Sticker", - - "service": { - "registered": { - "full": "{name} entrou na Rede {app_name}", - "compact": "Entrou no {app_name}" - }, - "groups": { - "created": "{name} criou o grupo", - "invited": "{name} convidado {name_added}", - "joined": "{name} entrou no grupo", - "kicked": "{name} removido {name_kicked}", - "left": "{name} saiu do grupo", - "title_changed": { - "full": "{name} alterou o nome do grupo para \"{title}\"", - "compact": "{name} alterou o nome do grupo" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name} alterou a foto do grupo", - "avatar_removed": "{name} removeu a foto do grupo" - }, - "channels": { - "created": "Criou o canal", - "invited": "{name} convidado {name_added}", - "joined": "{name} entrou no canal", - "kicked": "{name} removido {name_kicked}", - "left": "{name} saiu do canal", - "title_changed": { - "full": "{name} alterou o nome do canal para \"{title}\"", - "compact": "{name} alterou o nome do canal" - }, - "topic_changed": { - "full": "{name} changed the canal topic to \"{topic}\"", - "compact": "{name} changed the canal topic" - }, - "about_changed": { - "full": "{name} changed the canal about to \"{about}\"", - "compact": "{name} changed the canal about" - }, - "avatar_changed": "{name} alterou a foto do canal", - "avatar_removed": "{name} removeu a foto do canal" - }, - "calls": { - "missed": "Missed call", - "ended": "Call ended" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "Internal server error. Please, try again later.", - "phone": { - "empty": "Please, enter your phone number.", - "incorrect": "Entered phone number is invalid. Please, try again." - }, - "code": { - "empty": "The code is invalid. Please try again.", - "incorrect": "Entered code is incorrect. Please, try again.", - "expired": "The code has expired. Please, start authentication again." - }, - "groups": { - "already_joined": "You have already joined the group.", - "unable_to_join": "Unable to join the group." - } - }, - - "months": { - "january": { - "compact": "jan", - "full":"janeiro" - }, - "february": { - "compact": "fev", - "full":"fevereiro" - }, - "march": { - "compact": "mar", - "full":"março" - }, - "april": { - "compact": "abr", - "full":"abril" - }, - "may": { - "compact": "mai", - "full":"maio" - }, - "june": { - "compact": "jun", - "full":"junho" - }, - "july": { - "compact": "jul", - "full":"julho" - }, - "august": { - "compact": "ago", - "full":"agosto" - }, - "september": { - "compact": "set", - "full":"setembro" - }, - "october": { - "compact": "out", - "full":"outubro" - }, - "november": { - "compact": "nov", - "full":"novembro" - }, - "december": { - "compact": "dez", - "full":"dezembro" - } - } + "app": { + "name": "Actor" + }, + "language": { + "code": "Pt", + "format": { + "time": { + "yesterday": { + "short": "Ontem", + "full": "Ontem" + }, + "now": "Agora", + "seconds": { + "short": "{seconds} sec", + "full": "{seconds} seconds" + }, + "minutes": { + "short": "{minutes} min", + "full": "{minutes} minutes" + }, + "hours": { + "short": "{hours} hs", + "full": "{hours} hs" + } + } + }, + "file_size": { + "bytes": "{bytes} B", + "kbytes": "{kbytes} KB", + "mbytes": "{mbytes} MB", + "gbytes": "{gbytes} GB" + }, + "referencing": { + "you": "Você", + "thee": "Você" + }, + "sequence": { + "or": ", ", + "and": " and " + } + }, + "groups": { + "members": { + "other": "{count} membros", + "one": "{count} membro" + } + }, + "presence": { + "online": "online", + "offline": "offline", + "now": "visto agora mesmo", + "today": "visto hoje às {time}", + "yesterday": "visto ontem às {time}", + "at_day": "visto {date}", + "at_day_time": "visto {date} às {time}", + "members": "{count} online" + }, + "typing": { + "simple": "digitando...", + "user": "{user} está digitando...", + "group": { + "sequenced": "{users} estão digitando...", + "many": "{count} pessoas estão digitando..." + } + }, + "content": { + "unknown": "Conteúdo não suportado", + "document": "Documento", + "photo": "Foto", + "video": "Vídeo", + "audio": "Audio", + "contact": "Contact", + "location": "Location", + "sticker": "Sticker", + "service": { + "registered": { + "full": "{name} entrou na Rede {app_name}", + "compact": "Entrou no {app_name}" + }, + "groups": { + "created": "{name} criou o grupo", + "invited": "{name} convidado {name_added}", + "joined": "{name} entrou no grupo", + "kicked": "{name} removido {name_kicked}", + "left": "{name} saiu do grupo", + "title_changed": { + "full": "{name} alterou o nome do grupo para \"{title}\"", + "compact": "{name} alterou o nome do grupo" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name} alterou a foto do grupo", + "avatar_removed": "{name} removeu a foto do grupo" + }, + "channels": { + "created": "Criou o canal", + "invited": "{name} convidado {name_added}", + "joined": "{name} entrou no canal", + "kicked": "{name} removido {name_kicked}", + "left": "{name} saiu do canal", + "title_changed": { + "full": "{name} alterou o nome do canal para \"{title}\"", + "compact": "{name} alterou o nome do canal" + }, + "topic_changed": { + "full": "{name} changed the canal topic to \"{topic}\"", + "compact": "{name} changed the canal topic" + }, + "about_changed": { + "full": "{name} changed the canal about to \"{about}\"", + "compact": "{name} changed the canal about" + }, + "avatar_changed": "{name} alterou a foto do canal", + "avatar_removed": "{name} removeu a foto do canal" + }, + "calls": { + "missed": "Missed call", + "ended": "Call ended" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "Internal server error. Please, try again later.", + "phone": { + "empty": "Please, enter your phone number.", + "incorrect": "Entered phone number is invalid. Please, try again." + }, + "code": { + "empty": "The code is invalid. Please try again.", + "incorrect": "Entered code is incorrect. Please, try again.", + "expired": "The code has expired. Please, start authentication again." + }, + "groups": { + "already_joined": "You have already joined the group.", + "unable_to_join": "Unable to join the group." + } + }, + "months": { + "january": { + "compact": "jan", + "full": "janeiro" + }, + "february": { + "compact": "fev", + "full": "fevereiro" + }, + "march": { + "compact": "mar", + "full": "março" + }, + "april": { + "compact": "abr", + "full": "abril" + }, + "may": { + "compact": "mai", + "full": "maio" + }, + "june": { + "compact": "jun", + "full": "junho" + }, + "july": { + "compact": "jul", + "full": "julho" + }, + "august": { + "compact": "ago", + "full": "agosto" + }, + "september": { + "compact": "set", + "full": "setembro" + }, + "october": { + "compact": "out", + "full": "outubro" + }, + "november": { + "compact": "nov", + "full": "novembro" + }, + "december": { + "compact": "dez", + "full": "dezembro" + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json index 0f8bc7a55e..7ea280bc2f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json @@ -1,366 +1,388 @@ { - "app": { - "name": "Actor" - }, - - "language": { - - "code": "Ru", - - "format": { - "time": { - "yesterday":{ - "short": "Вчера", - "full": "Вчера" - }, - "now": "Сейчас", - "minutes": { - "short": "{minutes} мин.", - "full": "{minutes} минут" - }, - "hours": { - "short": "{hours} ч.", - "full": "{hours} часов" - } - } - }, - - "file_size": { - "bytes": "{bytes} б", - "kbytes": "{kbytes} кб", - "mbytes": "{mbytes} мб", - "gbytes": "{gbytes} гб" - }, - - "referencing": { - "you": "Вы", - "thee": "Вас" - }, - - "sequence": { - "or": ", ", - "and": " и " - } - }, - - "groups": { - "members": { - "other": "{count} участников", - "one": "{count} участник", - "few": "{count} участника", - "many": "{count} участников" - } - }, - - "presence": { - "online": "в сети", - "offline": "не в сети", - "now": { - "male": "был только что", - "female": "была только что", - "other": "был(а) только что" - }, - "today": { - "male": "был сегодня в {time}", - "female": "была сегодня в {time}", - "other": "был(а) сегодня в {time}" - }, - "yesterday": { - "male": "был вчера в {time}", - "female": "была вчера в {time}", - "other": "был(а) вчера в {time}" - }, - "at_day": { - "male": "был {date}", - "female": "была {date}", - "other": "был(а) {date}" - }, - "at_day_time": { - "male": "был {date} в {time}", - "female": "была {date} в {time}", - "other": "был(а) {date} в {time}" - }, - "members": "{count} в сети" - }, - - "typing": { - "simple": "печатает...", - "user": "{user} печатает...", - "group": { - "sequenced": "{users} печатают...", - "one": "{count} печатает...", - "few": "{count} печатают...", - "many": "{count} печатает...", - "other": "{count} печатает..." - } - }, - - "content": { - - "unknown": "Неподдерживаемое сообщение", - "document": "Документ", - "photo": "Фото", - "video": "Видео", - "audio": "Аудио", - "contact": "Контакт", - "location": "Расположение", - "sticker": "Стикер", - - "service": { - "registered": { - "full": { - "you": "{name} зарегистрировались в {app_name}", - "male": "{name} зарегистрировался в {app_name}", - "female": "{name} зарегистрировалась в {app_name}", - "other": "{name} зарегистрировался(ась) в {app_name}" - }, - "compact": { - "you": "Вы зарегистрироваись в {app_name}", - "male": "Зарегистрировался в {app_name}", - "female": "Зарегистрировалась в {app_name}", - "other": "Зарегистрировался(ась) в {app_name}" - } - }, - "groups": { - "created": { - "you": "{name} создали группу", - "male": "{name} создал группу", - "female": "{name} создала группу", - "other": "{name} создал(-а) группу" - }, - "invited": { - "you": "{name} пригласили {name_added}", - "male": "{name} пригласил {name_added}", - "female": "{name} пригласила {name_added}", - "other": "{name} пригласил(-а) {name_added}" - }, - "joined": { - "you": "{name} вошли в группу", - "male": "{name} вошел в группу", - "female": "{name} вошла в группу", - "other": "{name} вошел(-шла) в группу" - }, - "kicked": { - "you": "{name} исключили {name_kicked}", - "male": "{name} исключил {name_kicked}", - "female": "{name} исключила {name_kicked}", - "other": "{name} исключил(-а) {name_kicked}" - }, - "left": { - "you": "{name} покинули группу", - "male": "{name} покинул группу", - "female": "{name} покинула группу", - "other": "{name} покинул(-а) группу" - }, - "title_changed": { - "full": { - "you": "{name} изменили название группы на \"{title}\"", - "male": "{name} изменил название группы на \"{title}\"", - "female": "{name} изменила название группы на \"{title}\"", - "other": "{name} изменил(-а) название группы на \"{title}\"" - }, - "compact": { - "you": "{name} изменили название группы", - "male": "{name} изменил название группы", - "female": "{name} изменила название группы", - "other": "{name} изменил(-а) название группы" - } - }, - "topic_changed": { - "full": { - "you": "{name} изменили тему группы на \"{title}\"", - "male": "{name} изменил тему группы на \"{title}\"", - "female": "{name} изменила тему группы на \"{title}\"", - "other": "{name} изменил(-а) тему группы на \"{title}\"" - }, - "compact": { - "you": "{name} изменили тему группы", - "male": "{name} изменил тему группы", - "female": "{name} изменила тему группы", - "other": "{name} изменил(-а) тему группы" - } - }, - "about_changed": { - "full": { - "you": "{name} изменили описание группы на \"{title}\"", - "male": "{name} изменил описание группы на \"{title}\"", - "female": "{name} изменила описание группы на \"{title}\"", - "other": "{name} изменил(-а) описание группы на \"{title}\"" - }, - "compact": { - "you": "{name} изменили описание группы", - "male": "{name} изменил описание группы", - "female": "{name} изменила описание группы", - "other": "{name} изменил(-а) описание группы" - } - }, - "avatar_changed": { - "you": "{name} изменили фото группы", - "male": "{name} изменил фото группы", - "female": "{name} изменила фото группы", - "other": "{name} изменил(-а) фото группы" - }, - "avatar_removed": { - "you": "{name} удалили фото группы", - "male": "{name} удалил фото группы", - "female": "{name} удалила фото группы", - "other": "{name} удалил(-а) фото группы" - } - }, - "channels": { - "created": "Создан канал", - "invited": { - "you": "{name} пригласили {name_added}", - "male": "{name} пригласил {name_added}", - "female": "{name} пригласила {name_added}", - "other": "{name} пригласил(-а) {name_added}" - }, - "joined": { - "you": "{name} вошли в канал", - "male": "{name} вошел в канал", - "female": "{name} вошла в канал", - "other": "{name} вошел(-шла) в канал" - }, - "kicked": { - "you": "{name} исключили {name_kicked}", - "male": "{name} исключил {name_kicked}", - "female": "{name} исключила {name_kicked}", - "other": "{name} исключил(-а) {name_kicked}" - }, - "left": { - "you": "{name} покинули канал", - "male": "{name} покинул канал", - "female": "{name} покинула канал", - "other": "{name} покинул(-а) канал" - }, - "title_changed": { - "full": { - "you": "{name} изменили название канала на \"{title}\"", - "male": "{name} изменил название канала на \"{title}\"", - "female": "{name} изменила название канала на \"{title}\"", - "other": "{name} изменил(-а) название канала на \"{title}\"" - }, - "compact": { - "you": "{name} изменили название канала", - "male": "{name} изменил название канала", - "female": "{name} изменила название канала", - "other": "{name} изменил(-а) название канала" - } - }, - "topic_changed": { - "full": { - "you": "{name} изменили тему канала на \"{title}\"", - "male": "{name} изменил тему канала на \"{title}\"", - "female": "{name} изменила тему канала на \"{title}\"", - "other": "{name} изменил(-а) тему канала на \"{title}\"" - }, - "compact": { - "you": "{name} изменили тему канала", - "male": "{name} изменил тему канала", - "female": "{name} изменила тему канала", - "other": "{name} изменил(-а) тему канала" - } - }, - "about_changed": { - "full": { - "you": "{name} изменили описание канала на \"{title}\"", - "male": "{name} изменил описание канала на \"{title}\"", - "female": "{name} изменила описание канала на \"{title}\"", - "other": "{name} изменил(-а) описание канала на \"{title}\"" - }, - "compact": { - "you": "{name} изменили канала канала", - "male": "{name} изменил описание канала", - "female": "{name} изменила описание канала", - "other": "{name} изменил(-а) описание канала" - } - }, - "avatar_changed": { - "you": "{name} изменили фото канала", - "male": "{name} изменил фото канала", - "female": "{name} изменила фото канала", - "other": "{name} изменил(-а) фото канала" - }, - "avatar_removed": { - "you": "{name} удалили фото канала", - "male": "{name} удалил фото канала", - "female": "{name} удалила фото канала", - "other": "{name} удалил(-а) фото канала" - } - }, - "calls": { - "missed": "Пропущен вызов", - "ended": "Вызов завершен" - } - } - }, - - "errors": { - "unknown": "Неизвестная ошибка. Пожалуйста, попробуйте еще раз.", - "internal": "Внутренняя ошибка сервера. Пожалуйста, попробуйте ещё раз.", - "phone": { - "empty": "Пожалуйста, введите свой номер телефона.", - "incorrect": "Введён некорректный номер телефона. Пожалуйста, попробуйте ещё раз." - }, - "code": { - "empty": "Пожалуйста, введите код подтверждения.", - "incorrect": "Введён некорректный код. Пожалуйста, попробуйте ещё раз.", - "expired": "Срок действия кода истёк. Пожалуйста, авторизуйтесь заново." - }, - "groups": { - "already_joined": "Вы уже вступили в эту группу.", - "unable_to_join": "Невозможно присоединиться к группе." - } - }, - - "months": { - "january": { - "compact": "янв", - "full":"январь" - }, - "february": { - "compact": "фев", - "full":"февраль" - }, - "march": { - "compact": "мар", - "full":"март" - }, - "april": { - "compact": "апр", - "full":"апрель" - }, - "may": { - "compact": "май", - "full":"май" - }, - "june": { - "compact": "июн", - "full":"июнь" - }, - "july": { - "compact": "июл", - "full":"июль" - }, - "august": { - "compact": "авг", - "full":"август" - }, - "september": { - "compact": "сен", - "full":"сентябрь" - }, - "october": { - "compact": "окт", - "full":"октябрь" - }, - "november": { - "compact": "ноя", - "full":"ноябрь" - }, - "december": { - "compact": "дек", - "full":"декабрь" - } - } + "app": { + "name": "Actor" + }, + "language": { + "code": "Ru", + "format": { + "time": { + "yesterday": { + "short": "Вчера", + "full": "Вчера" + }, + "now": "Сейчас", + "seconds": { + "short": "{seconds} с.", + "full": { + "other": "{seconds} секунд", + "one": "{seconds} секунда", + "few": "{seconds} секунды", + "many": "{seconds} секунд" + } + }, + "minutes": { + "short": "{minutes} мин.", + "full": { + "other": "{minutes} минут", + "one": "{minutes} минута", + "few": "{minutes} минуты", + "many": "{minutes} минут" + } + }, + "hours": { + "short": "{hours} ч.", + "full": { + "other": "{hours} часов", + "one": "{hours} час", + "few": "{hours} часа", + "many": "{hours} часов" + } + } + } + }, + "file_size": { + "bytes": "{bytes} б", + "kbytes": "{kbytes} кб", + "mbytes": "{mbytes} мб", + "gbytes": "{gbytes} гб" + }, + "referencing": { + "you": "Вы", + "thee": "Вас" + }, + "sequence": { + "or": ", ", + "and": " и " + } + }, + "groups": { + "members": { + "other": "{count} участников", + "one": "{count} участник", + "few": "{count} участника", + "many": "{count} участников" + } + }, + "presence": { + "online": "в сети", + "offline": "не в сети", + "now": { + "male": "был только что", + "female": "была только что", + "other": "был(а) только что" + }, + "today": { + "male": "был сегодня в {time}", + "female": "была сегодня в {time}", + "other": "был(а) сегодня в {time}" + }, + "yesterday": { + "male": "был вчера в {time}", + "female": "была вчера в {time}", + "other": "был(а) вчера в {time}" + }, + "at_day": { + "male": "был {date}", + "female": "была {date}", + "other": "был(а) {date}" + }, + "at_day_time": { + "male": "был {date} в {time}", + "female": "была {date} в {time}", + "other": "был(а) {date} в {time}" + }, + "members": "{count} в сети" + }, + "typing": { + "simple": "печатает...", + "user": "{user} печатает...", + "group": { + "sequenced": "{users} печатают...", + "one": "{count} печатает...", + "few": "{count} печатают...", + "many": "{count} печатает...", + "other": "{count} печатает..." + } + }, + "content": { + "unknown": "Неподдерживаемое сообщение", + "document": "Документ", + "photo": "Фото", + "video": "Видео", + "audio": "Аудио", + "contact": "Контакт", + "location": "Расположение", + "sticker": "Стикер", + "service": { + "registered": { + "full": { + "you": "{name} зарегистрировались в {app_name}", + "male": "{name} зарегистрировался в {app_name}", + "female": "{name} зарегистрировалась в {app_name}", + "other": "{name} зарегистрировался(ась) в {app_name}" + }, + "compact": { + "you": "Вы зарегистрироваись в {app_name}", + "male": "Зарегистрировался в {app_name}", + "female": "Зарегистрировалась в {app_name}", + "other": "Зарегистрировался(ась) в {app_name}" + } + }, + "groups": { + "created": { + "you": "{name} создали группу", + "male": "{name} создал группу", + "female": "{name} создала группу", + "other": "{name} создал(а) группу" + }, + "invited": { + "you": "{name} пригласили {name_added}", + "male": "{name} пригласил {name_added}", + "female": "{name} пригласила {name_added}", + "other": "{name} пригласил(а) {name_added}" + }, + "joined": { + "you": "{name} вошли в группу", + "male": "{name} вошел в группу", + "female": "{name} вошла в группу", + "other": "{name} вошел(шла) в группу" + }, + "kicked": { + "you": "{name} исключили {name_kicked}", + "male": "{name} исключил {name_kicked}", + "female": "{name} исключила {name_kicked}", + "other": "{name} исключил(а) {name_kicked}" + }, + "left": { + "you": "{name} покинули группу", + "male": "{name} покинул группу", + "female": "{name} покинула группу", + "other": "{name} покинул(а) группу" + }, + "title_changed": { + "full": { + "you": "{name} изменили название группы на \"{title}\"", + "male": "{name} изменил название группы на \"{title}\"", + "female": "{name} изменила название группы на \"{title}\"", + "other": "{name} изменил(а) название группы на \"{title}\"" + }, + "compact": { + "you": "{name} изменили название группы", + "male": "{name} изменил название группы", + "female": "{name} изменила название группы", + "other": "{name} изменил(а) название группы" + } + }, + "topic_changed": { + "full": { + "you": "{name} изменили тему группы на \"{title}\"", + "male": "{name} изменил тему группы на \"{title}\"", + "female": "{name} изменила тему группы на \"{title}\"", + "other": "{name} изменил(а) тему группы на \"{title}\"" + }, + "compact": { + "you": "{name} изменили тему группы", + "male": "{name} изменил тему группы", + "female": "{name} изменила тему группы", + "other": "{name} изменил(а) тему группы" + } + }, + "about_changed": { + "full": { + "you": "{name} изменили описание группы на \"{title}\"", + "male": "{name} изменил описание группы на \"{title}\"", + "female": "{name} изменила описание группы на \"{title}\"", + "other": "{name} изменил(а) описание группы на \"{title}\"" + }, + "compact": { + "you": "{name} изменили описание группы", + "male": "{name} изменил описание группы", + "female": "{name} изменила описание группы", + "other": "{name} изменил(а) описание группы" + } + }, + "avatar_changed": { + "you": "{name} изменили фото группы", + "male": "{name} изменил фото группы", + "female": "{name} изменила фото группы", + "other": "{name} изменил(а) фото группы" + }, + "avatar_removed": { + "you": "{name} удалили фото группы", + "male": "{name} удалил фото группы", + "female": "{name} удалила фото группы", + "other": "{name} удалил(а) фото группы" + } + }, + "channels": { + "created": "Создан канал", + "invited": { + "you": "{name} пригласили {name_added}", + "male": "{name} пригласил {name_added}", + "female": "{name} пригласила {name_added}", + "other": "{name} пригласил(а) {name_added}" + }, + "joined": { + "you": "{name} вошли в канал", + "male": "{name} вошел в канал", + "female": "{name} вошла в канал", + "other": "{name} вошел(шла) в канал" + }, + "kicked": { + "you": "{name} исключили {name_kicked}", + "male": "{name} исключил {name_kicked}", + "female": "{name} исключила {name_kicked}", + "other": "{name} исключил(а) {name_kicked}" + }, + "left": { + "you": "{name} покинули канал", + "male": "{name} покинул канал", + "female": "{name} покинула канал", + "other": "{name} покинул(а) канал" + }, + "title_changed": { + "full": { + "you": "{name} изменили название канала на \"{title}\"", + "male": "{name} изменил название канала на \"{title}\"", + "female": "{name} изменила название канала на \"{title}\"", + "other": "{name} изменил(а) название канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили название канала", + "male": "{name} изменил название канала", + "female": "{name} изменила название канала", + "other": "{name} изменил(а) название канала" + } + }, + "topic_changed": { + "full": { + "you": "{name} изменили тему канала на \"{title}\"", + "male": "{name} изменил тему канала на \"{title}\"", + "female": "{name} изменила тему канала на \"{title}\"", + "other": "{name} изменил(а) тему канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили тему канала", + "male": "{name} изменил тему канала", + "female": "{name} изменила тему канала", + "other": "{name} изменил(а) тему канала" + } + }, + "about_changed": { + "full": { + "you": "{name} изменили описание канала на \"{title}\"", + "male": "{name} изменил описание канала на \"{title}\"", + "female": "{name} изменила описание канала на \"{title}\"", + "other": "{name} изменил(а) описание канала на \"{title}\"" + }, + "compact": { + "you": "{name} изменили канала канала", + "male": "{name} изменил описание канала", + "female": "{name} изменила описание канала", + "other": "{name} изменил(а) описание канала" + } + }, + "avatar_changed": { + "you": "{name} изменили фото канала", + "male": "{name} изменил фото канала", + "female": "{name} изменила фото канала", + "other": "{name} изменил(а) фото канала" + }, + "avatar_removed": { + "you": "{name} удалили фото канала", + "male": "{name} удалил фото канала", + "female": "{name} удалила фото канала", + "other": "{name} удалил(а) фото канала" + } + }, + "calls": { + "missed": "Пропущен вызов", + "ended": "Вызов завершен" + }, + "encrypted": { + "timer_changed": { + "full": { + "you": "{name} установили таймер самоуничтожения на {timer}", + "male": "{name} установил таймер самоуничтожения на {timer}", + "female": "{name} установила таймер самоуничтожения на {timer}", + "other": "{name} установил(а) таймер самоуничтожения на {timer}" + }, + "compact": { + "you": "{name} установили таймер самоуничтожения", + "male": "{name} установил таймер самоуничтожения", + "female": "{name} установила таймер самоуничтожения", + "other": "{name} установил(а) таймер самоуничтожения" + } + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Неизвестная ошибка. Пожалуйста, попробуйте еще раз.", + "internal": "Внутренняя ошибка сервера. Пожалуйста, попробуйте ещё раз.", + "phone": { + "empty": "Пожалуйста, введите свой номер телефона.", + "incorrect": "Введён некорректный номер телефона. Пожалуйста, попробуйте ещё раз." + }, + "code": { + "empty": "Пожалуйста, введите код подтверждения.", + "incorrect": "Введён некорректный код. Пожалуйста, попробуйте ещё раз.", + "expired": "Срок действия кода истёк. Пожалуйста, авторизуйтесь заново." + }, + "groups": { + "already_joined": "Вы уже вступили в эту группу.", + "unable_to_join": "Невозможно присоединиться к группе." + } + }, + "months": { + "january": { + "compact": "янв", + "full": "январь" + }, + "february": { + "compact": "фев", + "full": "февраль" + }, + "march": { + "compact": "мар", + "full": "март" + }, + "april": { + "compact": "апр", + "full": "апрель" + }, + "may": { + "compact": "май", + "full": "май" + }, + "june": { + "compact": "июн", + "full": "июнь" + }, + "july": { + "compact": "июл", + "full": "июль" + }, + "august": { + "compact": "авг", + "full": "август" + }, + "september": { + "compact": "сен", + "full": "сентябрь" + }, + "october": { + "compact": "окт", + "full": "октябрь" + }, + "november": { + "compact": "ноя", + "full": "ноябрь" + }, + "december": { + "compact": "дек", + "full": "декабрь" + } + } } \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json index f253e8b0ee..5fab7b757c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Zn.json @@ -1,164 +1,161 @@ { - "app": { - "name": "优聆" - }, - - "language": { - - "code": "Zn", - - "format": { - "time": { - "yesterday": "昨天", - "now": "现在", - "minutes": { - "short": "{minutes} 分", - "full": "{minutes} 分" - }, - "hours": { - "short": "{hours} 小时", - "full": "{hours} 小时" - } - } - }, - - "file_size": { - "bytes": "{bytes} B", - "kbytes": "{kbytes} KB", - "mbytes": "{mbytes} MB", - "gbytes": "{gbytes} GB" - }, - - "referencing": { - "you": "你", - "thee": "你" - }, - - "sequence": { - "or": ",", - "and": "," - } - }, - - "groups": { - "members": "{count}个成员" - }, - - "presence": { - "online": "在线", - "offline": "离线", - "now": "刚刚最后一次上线", - "today": "今天{time}最后一次上线", - "yesterday": "昨天{time}最后一次上线", - "at_day": "{date}最后一次上线", - "at_day_time": "{date} {time}最后一次上线", - "members": "{count}人在线" - }, - - "typing": { - "simple": "正在输入...", - "user": "{user}正在输入...", - "group": { - "sequenced": "{users}正在输入...", - "many": "{count}个人正在输入..." - } - }, - - "content": { - - "unknown": "不支持的内容", - "document": "文档", - "photo": "照片", - "video": "图片", - "audio": "语音", - "contact": "通讯录", - "location": "位置", - "sticker": "贴纸", - - "service": { - "registered": { - "full": "{name} 已加入 {app_name}", - "compact": "已加入 {app_name}" - }, - "groups": { - "created": "{name}创建了这个群组", - "invited": "{name}邀请{name_added}", - "joined": "{name}加入群组", - "kicked": "{name}移出{name_kicked}", - "left": "{name}退出群组", - "title_changed": { - "full": "{name}修改为名称为:\"{title}\"", - "compact": "{name}修改了群组名称" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name}修改了群组头像", - "avatar_removed": "{name}删除了群组头像" - }, - "channels": { - "created": "{name}创建了这个群组", - "invited": "{name}邀请{name_added}", - "joined": "{name}加入群组", - "kicked": "{name}移出{name_kicked}", - "left": "{name}退出群组", - "title_changed": { - "full": "{name}修改为名称为:\"{title}\"", - "compact": "{name}修改了群组名称" - }, - "topic_changed": { - "full": "{name} changed the group topic to \"{topic}\"", - "compact": "{name} changed the group topic" - }, - "about_changed": { - "full": "{name} changed the group about to \"{about}\"", - "compact": "{name} changed the group about" - }, - "avatar_changed": "{name}修改了群组头像", - "avatar_removed": "{name}删除了群组头像" - }, - "calls": { - "missed": "未接电话", - "ended": "拨号结束" - } - } - }, - - "errors": { - "unknown": "Unknown error. Please, try again later.", - "internal": "服务器内部错误,请稍候再试。", - "phone": { - "empty": "请输入你的手机号码", - "incorrect": "输入的手机号码不正确,请重新输入。" - }, - "code": { - "empty": "请正确输入验证码。", - "incorrect": "输入的验证码不正确,请重试。", - "expired": "验证码过期,请重新验证。" - }, - "groups": { - "already_joined": "你已经是群组成员。", - "unable_to_join": "无法加入群组" - } - }, - - "months": { - "january": "一月", - "february": "二月", - "march": "三月", - "april": "四月", - "may": "五月", - "june": "六月", - "july": "七月", - "august": "八月", - "september": "九月", - "october": "十月", - "november": "十一月", - "december": "十二月" - } + "app": { + "name": "优聆" + }, + "language": { + "code": "Zn", + "format": { + "time": { + "yesterday": "昨天", + "now": "现在", + "seconds": { + "short": "{seconds} 第二", + "full": "{seconds} 第二" + }, + "minutes": { + "short": "{minutes} 分", + "full": "{minutes} 分" + }, + "hours": { + "short": "{hours} 小时", + "full": "{hours} 小时" + } + } + }, + "file_size": { + "bytes": "{bytes} B", + "kbytes": "{kbytes} KB", + "mbytes": "{mbytes} MB", + "gbytes": "{gbytes} GB" + }, + "referencing": { + "you": "你", + "thee": "你" + }, + "sequence": { + "or": ",", + "and": "," + } + }, + "groups": { + "members": "{count}个成员" + }, + "presence": { + "online": "在线", + "offline": "离线", + "now": "刚刚最后一次上线", + "today": "今天{time}最后一次上线", + "yesterday": "昨天{time}最后一次上线", + "at_day": "{date}最后一次上线", + "at_day_time": "{date} {time}最后一次上线", + "members": "{count}人在线" + }, + "typing": { + "simple": "正在输入...", + "user": "{user}正在输入...", + "group": { + "sequenced": "{users}正在输入...", + "many": "{count}个人正在输入..." + } + }, + "content": { + "unknown": "不支持的内容", + "document": "文档", + "photo": "照片", + "video": "图片", + "audio": "语音", + "contact": "通讯录", + "location": "位置", + "sticker": "贴纸", + "service": { + "registered": { + "full": "{name} 已加入 {app_name}", + "compact": "已加入 {app_name}" + }, + "groups": { + "created": "{name}创建了这个群组", + "invited": "{name}邀请{name_added}", + "joined": "{name}加入群组", + "kicked": "{name}移出{name_kicked}", + "left": "{name}退出群组", + "title_changed": { + "full": "{name}修改为名称为:\"{title}\"", + "compact": "{name}修改了群组名称" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name}修改了群组头像", + "avatar_removed": "{name}删除了群组头像" + }, + "channels": { + "created": "{name}创建了这个群组", + "invited": "{name}邀请{name_added}", + "joined": "{name}加入群组", + "kicked": "{name}移出{name_kicked}", + "left": "{name}退出群组", + "title_changed": { + "full": "{name}修改为名称为:\"{title}\"", + "compact": "{name}修改了群组名称" + }, + "topic_changed": { + "full": "{name} changed the group topic to \"{topic}\"", + "compact": "{name} changed the group topic" + }, + "about_changed": { + "full": "{name} changed the group about to \"{about}\"", + "compact": "{name} changed the group about" + }, + "avatar_changed": "{name}修改了群组头像", + "avatar_removed": "{name}删除了群组头像" + }, + "calls": { + "missed": "未接电话", + "ended": "拨号结束" + }, + "encrypted": { + "timer_changed": { + "full": "{name} set self-destruct timer to {timer}", + "compact": "{name} set self-destruct timer" + }, + "timer_disabled": "{name} disabled self-destruct timer" + } + } + }, + "errors": { + "unknown": "Unknown error. Please, try again later.", + "internal": "服务器内部错误,请稍候再试。", + "phone": { + "empty": "请输入你的手机号码", + "incorrect": "输入的手机号码不正确,请重新输入。" + }, + "code": { + "empty": "请正确输入验证码。", + "incorrect": "输入的验证码不正确,请重试。", + "expired": "验证码过期,请重新验证。" + }, + "groups": { + "already_joined": "你已经是群组成员。", + "unable_to_join": "无法加入群组" + } + }, + "months": { + "january": "一月", + "february": "二月", + "march": "三月", + "april": "四月", + "may": "五月", + "june": "六月", + "july": "七月", + "august": "八月", + "september": "九月", + "october": "十月", + "november": "十一月", + "december": "十二月" + } } \ No newline at end of file From 60e282c14f02a3884b9e7c1a47a87bef52d7df8c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 00:05:29 +0300 Subject: [PATCH 54/81] feat(core): Reuse of session pre keys --- .../actor/core/modules/encryption/EncryptionModule.java | 8 ++++++++ .../modules/encryption/ratchet/EncryptedSessionActor.java | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 742d5ce97d..942ed16c82 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -19,8 +19,10 @@ import im.actor.core.modules.encryption.ratchet.SessionManager; import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; import im.actor.core.util.RandomUtils; +import im.actor.runtime.Storage; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; +import im.actor.runtime.storage.KeyValueStorage; import static im.actor.runtime.actors.ActorSystem.system; @@ -30,6 +32,7 @@ public class EncryptionModule extends AbsModule { private SessionManager sessionManager; private EncryptedRouter encryptedRouter; private EncryptedMsg encryption; + private KeyValueStorage keyValueStorage; private final HashMap users = new HashMap<>(); @@ -42,6 +45,7 @@ public void run() { sessionManager = new SessionManager(context()); encryption = new EncryptedMsg(context()); encryptedRouter = new EncryptedRouter(context()); + keyValueStorage = Storage.createKeyValue("session_temp_storage"); } public KeyManager getKeyManager() { @@ -70,6 +74,10 @@ public EncryptedUser getEncryptedUser(int uid) { } } + public KeyValueStorage getKeyValueStorage() { + return keyValueStorage; + } + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { return encryptedRouter.onEncryptedUpdate(senderId, date, update); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index f71045fa62..1455f16132 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -13,6 +13,7 @@ import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.IntegrityException; import im.actor.runtime.crypto.primitives.util.ByteStrings; +import im.actor.runtime.storage.KeyValueStorage; import static im.actor.runtime.promise.Promise.success; @@ -47,10 +48,11 @@ class EncryptedSessionActor extends ModuleActor { private final PeerSession session; // - // Key Manager reference + // Convenience references // private KeyManager keyManager; + private KeyValueStorage sessionStorage; // // Temp encryption chains @@ -75,6 +77,8 @@ public EncryptedSessionActor(ModuleContext context, PeerSession session) { public void preStart() { super.preStart(); keyManager = context().getEncryption().getKeyManager(); + sessionStorage = context().getEncryption().getKeyValueStorage(); + latestTheirEphemeralKey = sessionStorage.loadItem(session.getSid()); } private Promise onEncrypt(final byte[] data) { @@ -129,6 +133,7 @@ private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { if (latestTheirEphemeralKey == null) { latestTheirEphemeralKey = ephemeralKey; + sessionStorage.addOrUpdateItem(session.getSid(), ephemeralKey); } if (encryptionChains.size() > 0) { From 58005ce4cf9a92e861eb28e6967f9ff95478acbf Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 01:05:42 +0300 Subject: [PATCH 55/81] feat(core): Respecting self-destruct timer on message sending --- .../entity/EncryptedConversationState.java | 79 ++++++++ .../java/im/actor/core/entity/Message.java | 30 +-- .../encryption/EncryptedRouterActor.java | 21 +- .../modules/encryption/EncryptionModule.java | 12 +- .../messaging/actions/SenderActor.java | 189 +++++------------- .../actions/entity/PendingMessage.java | 10 +- .../history/ConversationHistoryActor.java | 2 +- .../modules/messaging/router/RouterActor.java | 8 +- .../actor/core/viewmodel/ConversationVM.java | 7 +- .../viewmodel/EncryptedConversationVM.java | 34 ++++ 10 files changed, 230 insertions(+), 162 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java new file mode 100644 index 0000000000..7a859528d6 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java @@ -0,0 +1,79 @@ +package im.actor.core.entity; + +import java.io.IOException; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserCreator; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; +import im.actor.runtime.mvvm.ValueDefaultCreator; +import im.actor.runtime.storage.KeyValueItem; + +public class EncryptedConversationState extends BserObject implements KeyValueItem { + + public static EncryptedConversationState fromBytes(byte[] data) throws IOException { + return Bser.parse(new EncryptedConversationState(), data); + } + + public static BserCreator CREATOR = EncryptedConversationState::new; + + public static ValueDefaultCreator DEFAULT_CREATOR = id -> + new EncryptedConversationState((int) id, false, 0, 0); + + private int uid; + private boolean isLoaded; + private int timer; + private long timerDate; + + public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate) { + this.uid = uid; + this.isLoaded = isLoaded; + this.timer = timer; + this.timerDate = timerDate; + } + + private EncryptedConversationState() { + } + + public int getUid() { + return uid; + } + + public boolean isLoaded() { + return isLoaded; + } + + public int getTimer() { + return timer; + } + + public long getTimerDate() { + return timerDate; + } + + public EncryptedConversationState editTimer(int timer, long timerDate) { + return new EncryptedConversationState(uid, isLoaded, timer, timerDate); + } + + @Override + public void parse(BserValues values) throws IOException { + uid = values.getInt(1); + isLoaded = values.getBool(2); + timer = values.getInt(3); + timerDate = values.getLong(4); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeInt(1, uid); + writer.writeBool(2, isLoaded); + writer.writeLong(3, timer); + writer.writeLong(4, timerDate); + } + + @Override + public long getEngineId() { + return uid; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Message.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Message.java index 37c511e242..6a6e4b5c78 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Message.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Message.java @@ -24,12 +24,7 @@ public static Message fromBytes(byte[] data) throws IOException { return Bser.parse(new Message(), data); } - public static final BserCreator CREATOR = new BserCreator() { - @Override - public Message createInstance() { - return new Message(); - } - }; + public static final BserCreator CREATOR = Message::new; public static final String ENTITY_NAME = "Message"; @@ -49,13 +44,15 @@ public Message createInstance() { private List reactions; @Property("readonly, nonatomic") private int contentIndex; + @Property("readonly, nonatomic") + private int timer; public Message(long rid, long sortDate, long date, int senderId, MessageState messageState, AbsContent content) { - this(rid, sortDate, date, senderId, messageState, content, new ArrayList(), 0); + this(rid, sortDate, date, senderId, messageState, content, new ArrayList<>(), 0, 0); } public Message(long rid, long sortDate, long date, int senderId, MessageState messageState, AbsContent content, - List reactions, int contentIndex) { + List reactions, int contentIndex, int timer) { this.rid = rid; this.sortDate = sortDate; this.date = date; @@ -64,6 +61,7 @@ public Message(long rid, long sortDate, long date, int senderId, MessageState me this.content = content; this.reactions = reactions; this.contentIndex = contentIndex; + this.timer = timer; } protected Message() { @@ -114,24 +112,28 @@ public AbsContent getContent() { return content; } + public int getTimer() { + return timer; + } + public Message changeState(MessageState messageState) { - return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex); + return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex, timer); } public Message changeDate(long date) { - return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex); + return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex, timer); } public Message changeAllDate(long date) { - return new Message(rid, date, date, senderId, messageState, content, reactions, contentIndex); + return new Message(rid, date, date, senderId, messageState, content, reactions, contentIndex, timer); } public Message changeContent(AbsContent content) { - return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex + 1); + return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex + 1, timer); } public Message changeReactions(List reactions) { - return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex); + return new Message(rid, sortDate, date, senderId, messageState, content, reactions, contentIndex, timer); } @Override @@ -147,6 +149,7 @@ public void parse(BserValues values) throws IOException { reactions.add(Reaction.fromBytes(react)); } contentIndex = values.getInt(8, 0); + timer = values.getInt(9, 0); } @Override @@ -159,6 +162,7 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBytes(6, AbsContent.serialize(content)); writer.writeRepeatedObj(7, reactions); writer.writeInt(8, contentIndex); + writer.writeInt(9, timer); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java index 9cf931bc70..188ea5ffb3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java @@ -8,6 +8,7 @@ import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.api.ApiServiceMessage; import im.actor.core.api.ApiServiceTimerChanged; +import im.actor.core.entity.EncryptedConversationState; import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; import im.actor.core.entity.Peer; @@ -20,12 +21,14 @@ import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.promise.Promise; +import im.actor.runtime.storage.KeyValueEngine; public class EncryptedRouterActor extends ModuleActor { private KeyManager keyManager; private EncryptedMsg encryptedMsg; private EncryptedUpdates updates; + private KeyValueEngine stateKeyValue; public EncryptedRouterActor(ModuleContext context) { super(context); @@ -37,6 +40,7 @@ public void preStart() { updates = new EncryptedUpdates(context()); keyManager = context().getEncryption().getKeyManager(); encryptedMsg = context().getEncryption().getEncryption(); + stateKeyValue = context().getEncryption().getConversationState().getEngine(); } public Promise onKeyGroupAdded(int uid, ApiEncryptionKeyGroup group) { @@ -47,7 +51,12 @@ public Promise onKeyGroupRemoved(int uid, int keyGroupId) { return keyManager.onKeyGroupRemoved(uid, keyGroupId); } - public Promise onTimerSet(long randomId, long date, int uid, Integer timerInMs) { + public Promise onTimerSet(long randomId, long date, int uid, int timerInMs) { + EncryptedConversationState state = stateKeyValue.getValue(uid); + if (state.getTimer() != timerInMs && state.getTimerDate() < date) { + stateKeyValue.addOrUpdateItem(state.editTimer(timerInMs, date)); + } + return context().getMessagesModule().getRouter() .onNewMessage(Peer.secret(uid), new Message(randomId, date, date, myUid(), MessageState.SENT, AbsContent.fromMessage(new ApiServiceMessage("Timer set", @@ -57,16 +66,20 @@ public Promise onTimerSet(long randomId, long date, int uid, Integer timer // Messages public Promise onEncryptedUpdate(int uid, long date, ApiEncryptedContent update) { + Promise res = Promise.success(null); if (update instanceof ApiEncryptedChatTimerSet) { ApiEncryptedChatTimerSet timerSet = (ApiEncryptedChatTimerSet) update; int peerId = uid; if (timerSet.getReceiverId() != myUid()) { peerId = timerSet.getReceiverId(); } - return onTimerSet(timerSet.getRid(), date, peerId, timerSet.getTimerMs()); - } else { - return updates.onUpdate(uid, date, update); + int timer = 0; + if (timerSet.getTimerMs() != null) { + timer = timerSet.getTimerMs(); + } + res = onTimerSet(timerSet.getRid(), date, peerId, timer); } + return res.chain(r -> updates.onUpdate(uid, date, update)); } public Promise onEncryptedBox(long date, int senderId, @NotNull ApiEncryptedBox encryptedBox) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 942ed16c82..725f3b6aba 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -10,6 +10,7 @@ import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSendEncryptedPackage; +import im.actor.core.entity.EncryptedConversationState; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.ratchet.EncryptedMsg; @@ -19,9 +20,12 @@ import im.actor.core.modules.encryption.ratchet.SessionManager; import im.actor.core.modules.encryption.ratchet.entity.EncryptedMessage; import im.actor.core.util.RandomUtils; +import im.actor.core.viewmodel.EncryptedConversationVM; import im.actor.runtime.Storage; import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.mvvm.MVVMCollection; import im.actor.runtime.promise.Promise; +import im.actor.runtime.storage.KeyValueEngine; import im.actor.runtime.storage.KeyValueStorage; import static im.actor.runtime.actors.ActorSystem.system; @@ -33,7 +37,7 @@ public class EncryptionModule extends AbsModule { private EncryptedRouter encryptedRouter; private EncryptedMsg encryption; private KeyValueStorage keyValueStorage; - + private MVVMCollection conversationState; private final HashMap users = new HashMap<>(); public EncryptionModule(ModuleContext context) { @@ -46,6 +50,8 @@ public void run() { encryption = new EncryptedMsg(context()); encryptedRouter = new EncryptedRouter(context()); keyValueStorage = Storage.createKeyValue("session_temp_storage"); + conversationState = Storage.createKeyValue("encrypted_chat_state", EncryptedConversationVM.CREATOR, + EncryptedConversationState.CREATOR, EncryptedConversationState.DEFAULT_CREATOR); } public KeyManager getKeyManager() { @@ -78,6 +84,10 @@ public KeyValueStorage getKeyValueStorage() { return keyValueStorage; } + public MVVMCollection getConversationState() { + return conversationState; + } + public Promise onUpdate(int senderId, long date, ApiEncryptedContent update) { return encryptedRouter.onEncryptedUpdate(senderId, date, update); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index b5b5b2c5e8..c9d24058d7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -4,6 +4,8 @@ package im.actor.core.modules.messaging.actions; +import android.location.Location; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -102,8 +104,12 @@ public void preStart() { boolean isChanged = false; ArrayList messages = pendingMessages.getPendingMessages(); for (PendingMessage pending : messages.toArray(new PendingMessage[messages.size()])) { - if (pending.getContent() instanceof TextContent) { - performSendContent(pending.getPeer(), pending.getRid(), pending.getContent()); + if (pending.getContent() instanceof TextContent || + pending.getContent() instanceof JsonContent || + pending.getContent() instanceof StickerContent || + pending.getContent() instanceof LocationContent || + pending.getContent() instanceof ContactContent) { + performSendContent(pending.getPeer(), pending.getRid(), pending.getTimer(), pending.getContent()); } else if (pending.getContent() instanceof DocumentContent) { DocumentContent documentContent = (DocumentContent) pending.getContent(); if (documentContent.getSource() instanceof FileLocalSource) { @@ -120,7 +126,7 @@ public void preStart() { } } else { performSendContent(pending.getPeer(), pending.getRid(), - pending.getContent()); + pending.getTimer(), pending.getContent()); } } } @@ -147,10 +153,6 @@ public void doSendText(@NotNull Peer peer, @NotNull String text, text = text.trim(); - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - if (autoDetect) { mentions = new ArrayList<>(); if (peer.getPeerType() == PeerType.GROUP) { @@ -177,187 +179,100 @@ public void doSendText(@NotNull Peer peer, @NotNull String text, TextContent content = TextContent.create(text, null, mentions); - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendJson(Peer peer, JsonContent content) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } // Sending sticker public void doSendSticker(@NotNull Peer peer, @NotNull Sticker sticker) { - - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - StickerContent content = StickerContent.create(sticker); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendContact(@NotNull Peer peer, @NotNull ArrayList emails, @NotNull ArrayList phones, @Nullable String name, @Nullable String base64photo) { - - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - - ContactContent content = ContactContent.create(name, phones, emails, base64photo); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendLocation(@NotNull Peer peer, @NotNull Double longitude, @NotNull Double latitude, @Nullable String street, @Nullable String place) { - - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - - LocationContent content = LocationContent.create(longitude, latitude, street, place); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doForwardContent(Peer peer, AbsContent content) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, content)); - savePending(); - - performSendContent(peer, rid, content); + PendingMessage pending = prepareSend(peer, content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } // Sending documents public void doSendDocument(Peer peer, String fileName, String mimeType, int fileSize, FastThumb fastThumb, String descriptor) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; DocumentContent documentContent = DocumentContent.createLocal(fileName, fileSize, descriptor, mimeType, fastThumb); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, documentContent); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, documentContent)); - savePending(); - - performUploadFile(rid, descriptor, fileName); + PendingMessage pending = prepareSend(peer, documentContent); + performUploadFile(pending.getRid(), descriptor, fileName); } public void doSendPhoto(Peer peer, FastThumb fastThumb, String descriptor, String fileName, int fileSize, int w, int h) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; PhotoContent photoContent = PhotoContent.createLocalPhoto(descriptor, fileName, fileSize, w, h, fastThumb); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, photoContent); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, photoContent)); - savePending(); - - performUploadFile(rid, descriptor, fileName); + PendingMessage pending = prepareSend(peer, photoContent); + performUploadFile(pending.getRid(), descriptor, fileName); } public void doSendAudio(Peer peer, String descriptor, String fileName, int fileSize, int duration) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; VoiceContent audioContent = VoiceContent.createLocalAudio(descriptor, fileName, fileSize, duration); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, audioContent); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, audioContent)); - savePending(); - - performUploadFile(rid, descriptor, fileName); + PendingMessage pending = prepareSend(peer, audioContent); + performUploadFile(pending.getRid(), descriptor, fileName); } public void doSendVideo(Peer peer, String fileName, int w, int h, int duration, FastThumb fastThumb, String descriptor, int fileSize) { - long rid = RandomUtils.nextRid(); - long date = createPendingDate(); - long sortDate = date + 365 * 24 * 60 * 60 * 1000L; VideoContent videoContent = VideoContent.createLocalVideo(descriptor, fileName, fileSize, w, h, duration, fastThumb); - - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, videoContent); - context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, videoContent)); - savePending(); - - performUploadFile(rid, descriptor, fileName); + PendingMessage pending = prepareSend(peer, videoContent); + performUploadFile(pending.getRid(), descriptor, fileName); } public void doSendAnimation(Peer peer, String fileName, int w, int h, FastThumb fastThumb, String descriptor, int fileSize) { + AnimationContent animationContent = AnimationContent.createLocalAnimation(descriptor, + fileName, fileSize, w, h, fastThumb); + PendingMessage pending = prepareSend(peer, animationContent); + performUploadFile(pending.getRid(), descriptor, fileName); + } + + private PendingMessage prepareSend(Peer peer, AbsContent content) { long rid = RandomUtils.nextRid(); long date = createPendingDate(); long sortDate = date + 365 * 24 * 60 * 60 * 1000L; - AnimationContent animationContent = AnimationContent.createLocalAnimation(descriptor, - fileName, fileSize, w, h, fastThumb); + int timer = 0; + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + timer = context().getEncryption().getConversationState().get(peer.getPeerId()).getTimer().get(); + } - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, animationContent); + Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - - pendingMessages.getPendingMessages().add(new PendingMessage(peer, rid, animationContent)); + PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer); + pendingMessages.getPendingMessages().add(pendingMessage); savePending(); - - performUploadFile(rid, descriptor, fileName); + return pendingMessage; } private void performUploadFile(long rid, String descriptor, String fileName) { @@ -397,9 +312,9 @@ private void onFileUploaded(long rid, FileReference fileReference) { return; } - pendingMessages.getPendingMessages().add(new PendingMessage(msg.getPeer(), msg.getRid(), nContent)); + pendingMessages.getPendingMessages().add(new PendingMessage(msg.getPeer(), msg.getRid(), nContent, msg.getTimer())); context().getMessagesModule().getRouter().onContentChanged(msg.getPeer(), msg.getRid(), nContent); - performSendContent(msg.getPeer(), rid, nContent); + performSendContent(msg.getPeer(), rid, msg.getTimer(), nContent); fileUplaodingWakeLocks.remove(rid).releaseLock(); } @@ -415,7 +330,7 @@ private void onFileUploadError(long rid) { // Sending content - private void performSendContent(final Peer peer, final long rid, AbsContent content) { + private void performSendContent(final Peer peer, final long rid, int timer, AbsContent content) { WakeLock wakeLock = im.actor.runtime.Runtime.makeWakeLock(); ApiMessage message; @@ -470,10 +385,10 @@ private void performSendContent(final Peer peer, final long rid, AbsContent cont return; } - performSendApiContent(peer, rid, message, wakeLock); + performSendApiContent(peer, rid, message, timer, wakeLock); } - private void performSendApiContent(final Peer peer, final long rid, ApiMessage message, final WakeLock wakeLock) { + private void performSendApiContent(final Peer peer, final long rid, ApiMessage message, int timer, final WakeLock wakeLock) { if (peer.getPeerType() == PeerType.PRIVATE || peer.getPeerType() == PeerType.GROUP) { final ApiOutPeer outPeer = buidOutPeer(peer); final ApiPeer apiPeer = buildApiPeer(peer); @@ -497,8 +412,12 @@ public void onError(RpcException e) { }); } else if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { Log.d("SenderActor", "Pending encrypted message: " + message); + Integer sTimer = null; + if (timer > 0) { + sTimer = timer; + } ApiEncryptedContent content = new ApiEncryptedMessageContent(peer.getPeerId(), - rid, message, null); + rid, message, sTimer); context().getEncryption().doSend(rid, content, peer.getPeerId()) .chain(r -> context().getMessagesModule().getRouter().onOutgoingSent(peer, rid, r)) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java index 27ae56950a..bed0bb0dd5 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java @@ -23,11 +23,13 @@ public static PendingMessage fromBytes(byte[] data) throws IOException { private long rid; private AbsContent content; private boolean isError; + private int timer; - public PendingMessage(Peer peer, long rid, AbsContent content) { + public PendingMessage(Peer peer, long rid, AbsContent content, int timer) { this.peer = peer; this.rid = rid; this.content = content; + this.timer = timer; } private PendingMessage() { @@ -50,12 +52,17 @@ public boolean isError() { return isError; } + public int getTimer() { + return timer; + } + @Override public void parse(BserValues values) throws IOException { peer = Peer.fromUniqueId(values.getLong(1)); rid = values.getLong(2); content = AbsContent.parse(values.getBytes(3)); isError = values.getBool(4, false); + timer = values.getInt(5, 0); } @Override @@ -64,5 +71,6 @@ public void serialize(BserWriter writer) throws IOException { writer.writeLong(2, rid); writer.writeBytes(3, AbsContent.serialize(content)); writer.writeBool(4, isError); + writer.writeInt(5, timer); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java index 807cf402f3..c61e173a6c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/history/ConversationHistoryActor.java @@ -123,7 +123,7 @@ private Promise applyHistory(Peer peer, List history) } messages.add(new Message(historyMessage.getRid(), historyMessage.getDate(), historyMessage.getDate(), historyMessage.getSenderUid(), - state, content, reactions, 0)); + state, content, reactions, 0, 0)); maxLoadedDate = Math.min(historyMessage.getDate(), maxLoadedDate); if (historyMessage.getState() == ApiMessageState.RECEIVED) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index e5f75e0587..ab45236ab7 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -923,8 +923,14 @@ public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedCont if (update instanceof ApiEncryptedMessageContent) { ApiEncryptedMessageContent content = (ApiEncryptedMessageContent) update; + Integer timer = content.getTimerMs(); + if (timer == null) { + timer = 0; + } + Message msg = new Message(content.getRid(), date, date, senderId, - MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage())); + MessageState.UNKNOWN, AbsContent.fromMessage(content.getMessage()), + new ArrayList<>(), 0, timer); int destId = senderId; if (senderId == myUid()) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java index 8fb7029df2..115503e221 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/ConversationVM.java @@ -8,12 +8,7 @@ public class ConversationVM extends BaseValueModel { - public static ValueModelCreator CREATOR = new ValueModelCreator() { - @Override - public ConversationVM create(ConversationState baseValue) { - return new ConversationVM(baseValue); - } - }; + public static ValueModelCreator CREATOR = baseValue -> new ConversationVM(baseValue); private BooleanValueModel isLoaded; private BooleanValueModel isEmpty; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java new file mode 100644 index 0000000000..5eef4934c3 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java @@ -0,0 +1,34 @@ +package im.actor.core.viewmodel; + +import im.actor.core.entity.EncryptedConversationState; +import im.actor.core.viewmodel.generics.IntValueModel; +import im.actor.runtime.mvvm.BaseValueModel; +import im.actor.runtime.mvvm.ValueModelCreator; + +public class EncryptedConversationVM extends BaseValueModel { + + public static ValueModelCreator CREATOR = + baseValue -> new EncryptedConversationVM(baseValue); + + private int uid; + private IntValueModel timer; + + public EncryptedConversationVM(EncryptedConversationState rawObj) { + super(rawObj); + uid = rawObj.getUid(); + timer = new IntValueModel("encrypted_" + uid + ".timer", rawObj.getTimer()); + } + + public int getUid() { + return uid; + } + + public IntValueModel getTimer() { + return timer; + } + + @Override + protected void updateValues(EncryptedConversationState rawObj) { + timer.change(rawObj.getTimer()); + } +} From 70fe79dac185ea1862a0e35715cdece47134a486 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 06:42:24 +0300 Subject: [PATCH 56/81] feat(core+android): Implemented Self-Destruct timer --- .../toolbar/ChatToolbarFragment.java | 13 +- .../src/main/res/values/ui_text.xml | 13 + .../modules/encryption/EncryptionModule.java | 8 +- .../modules/messaging/MessagesModule.java | 7 +- .../modules/messaging/actions/Destructor.java | 31 ++ .../messaging/actions/DestructorActor.java | 279 ++++++++++++++++++ .../messaging/actions/SenderActor.java | 3 +- .../entity/DestructPendingMessage.java | 63 ++++ .../entity/DestructPendingStorage.java | 56 ++++ .../actions/entity/DestructQueueMessage.java | 56 ++++ .../actions/entity/DestructQueueStorage.java | 48 +++ .../messaging/actions/entity/MessageDesc.java | 38 +++ .../modules/messaging/router/RouterActor.java | 93 ++++-- .../modules/messaging/router/RouterInt.java | 7 +- .../entity/RouterMessagesSelfDestructed.java | 25 ++ .../java/im/actor/core/util/JavaUtil.java | 16 + 16 files changed, 731 insertions(+), 25 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/Destructor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingStorage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueMessage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueStorage.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/MessageDesc.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterMessagesSelfDestructed.java diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java index acb7306fac..94ff4f8f6a 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/toolbar/ChatToolbarFragment.java @@ -3,6 +3,7 @@ import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PorterDuff; @@ -361,7 +362,17 @@ public void onError(final Exception e) { } if (item.getItemId() == R.id.timer) { - execute(messenger().setSecretChatTimer(peer.getPeerId(), 5000)); + int[] timers = new int[]{ + 0, 1000, 2000, 5000, 15000, 60000, 60 * 60000, 24 * 60 * 60000 + }; + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.timer_title) + .setItems(R.array.timer_values, (dialogInterface, i1) -> { + execute(messenger().setSecretChatTimer(peer.getPeerId(), timers[i1])); + }) + .show() + .setCanceledOnTouchOutside(true); + } return super.onOptionsItemSelected(item); diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml index ef21461134..fc103c5198 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/res/values/ui_text.xml @@ -585,6 +585,19 @@ Share with + + Self-Destruct Timer + + Off + 1 second + 2 seconds + 5 seconds + 15 seconds + 1 minute + 1 hour + 1 day + + Unable to connect. Please make sure that you\'re connected to the Internet and try again.\n\nPlease reboot your phone if the connection problem persists. diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 725f3b6aba..8c5c115874 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -45,13 +45,15 @@ public EncryptionModule(ModuleContext context) { } public void run() { + + keyValueStorage = Storage.createKeyValue("session_temp_storage"); + conversationState = Storage.createKeyValue("encrypted_chat_state", EncryptedConversationVM.CREATOR, + EncryptedConversationState.CREATOR, EncryptedConversationState.DEFAULT_CREATOR); + keyManager = new KeyManager(context()); sessionManager = new SessionManager(context()); encryption = new EncryptedMsg(context()); encryptedRouter = new EncryptedRouter(context()); - keyValueStorage = Storage.createKeyValue("session_temp_storage"); - conversationState = Storage.createKeyValue("encrypted_chat_state", EncryptedConversationVM.CREATOR, - EncryptedConversationState.CREATOR, EncryptedConversationState.DEFAULT_CREATOR); } public KeyManager getKeyManager() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index bda6358acc..af47ed818b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -54,6 +54,7 @@ import im.actor.core.events.PeerChatPreload; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.actions.DestructorActor; import im.actor.core.modules.messaging.dialogs.DialogsInt; import im.actor.core.modules.messaging.history.ArchivedDialogsActor; import im.actor.core.modules.messaging.actions.CursorReaderActor; @@ -103,13 +104,14 @@ public class MessagesModule extends AbsModule implements BusSubscriber { private ListEngine dialogs; private DialogsInt dialogsInt; + private RouterInt router; + private ActorRef dialogsHistoryActor; private ActorRef archivedDialogsActor; private ActorRef plainReadActor; private ActorRef plainReceiverActor; private ActorRef sendMessageActor; private ActorRef deletionsActor; - private RouterInt router; private final HashMap historyLoaderActors = new HashMap<>(); private MVVMCollection conversationStates; @@ -133,9 +135,10 @@ public MessagesModule(final ModuleContext context) { public void run() { + this.dialogsInt = new DialogsInt(context()); + this.router = new RouterInt(context()); - this.dialogsInt = new DialogsInt(context()); this.dialogsHistoryActor = system().actorOf("actor/dialogs/history", () -> new DialogsHistoryActor(context())); this.archivedDialogsActor = system().actorOf("actor/dialogs/archived", () -> new ArchivedDialogsActor(context())); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/Destructor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/Destructor.java new file mode 100644 index 0000000000..0b0dabc78a --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/Destructor.java @@ -0,0 +1,31 @@ +package im.actor.core.modules.messaging.actions; + +import java.util.List; + +import im.actor.core.entity.Peer; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.actions.entity.MessageDesc; +import im.actor.runtime.actors.ActorInterface; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +import static im.actor.runtime.actors.ActorSystem.system; + +public class Destructor extends ActorInterface { + + public Destructor(ModuleContext context) { + super(system().actorOf("router/destructor", () -> new DestructorActor(context))); + } + + public Promise onMessages(Peer peer, List messages) { + return ask(new DestructorActor.NewMessages(peer, messages)); + } + + public Promise onMessageRead(Peer peer, long readDate) { + return ask(new DestructorActor.MessageRead(peer, readDate)); + } + + public Promise onMessageReadByMe(Peer peer, long readDate) { + return ask(new DestructorActor.MessageReadByMe(peer, readDate)); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java new file mode 100644 index 0000000000..9a19921a15 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java @@ -0,0 +1,279 @@ +package im.actor.core.modules.messaging.actions; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +import im.actor.core.entity.Peer; +import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.messaging.actions.entity.DestructPendingMessage; +import im.actor.core.modules.messaging.actions.entity.DestructPendingStorage; +import im.actor.core.modules.messaging.actions.entity.DestructQueueMessage; +import im.actor.core.modules.messaging.actions.entity.DestructQueueStorage; +import im.actor.core.modules.messaging.actions.entity.MessageDesc; +import im.actor.core.util.JavaUtil; +import im.actor.runtime.Runtime; +import im.actor.runtime.Storage; +import im.actor.runtime.actors.ActorCancellable; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; +import im.actor.runtime.promise.PromisesArray; +import im.actor.runtime.storage.KeyValueStorage; + +public class DestructorActor extends ModuleActor { + + private static final int DEFAULT_DELAY = 15000; + + private KeyValueStorage keyValueStorage; + private DestructQueueStorage destructQueueStorage = new DestructQueueStorage(); + private ActorCancellable checkCancellable = null; + private boolean isDestructing = false; + + public DestructorActor(ModuleContext context) { + super(context); + } + + @Override + public void preStart() { + super.preStart(); + + keyValueStorage = Storage.createKeyValue("self_destruct"); + + // Load Destruction Queue + byte[] data = keyValueStorage.loadItem(0); + if (data != null) { + destructQueueStorage = DestructQueueStorage.fromBytes(data); + } + + scheduleCheck(); + } + + // + // Receiving messages + // + + public Promise onMessages(Peer peer, List messages) { + DestructPendingStorage storage = getStorage(peer); + if (storage == null) { + storage = new DestructPendingStorage(); + } + for (MessageDesc d : messages) { + DestructPendingMessage p = new DestructPendingMessage(d.getRid(), + d.getDate(), d.isOut(), d.getTimer()); + if (d.isNeedExplicitRead()) { + storage.getIndividualMessages().add(p); + } else { + storage.getMessages().add(p); + } + } + saveStorage(peer, storage); + return Promise.success(null); + } + + // + // Reading and starting self-destroy + // + + public Promise onMessageRead(Peer peer, long readDate) { + return onMessageRead(peer, readDate, true); + } + + public Promise onMessageReadByMe(Peer peer, long readDate) { + return onMessageRead(peer, readDate, false); + } + + public Promise onMessageRead(Peer peer, long readDate, boolean isOut) { + DestructPendingStorage pendingStorage = getStorage(peer); + if (pendingStorage != null) { + ArrayList queue = new ArrayList<>(); + for (DestructPendingMessage dp : pendingStorage.getMessages()) { + if (dp.isOut() == isOut && dp.getDate() <= readDate) { + queue.add(dp); + } + } + + if (queue.size() > 0) { + + // Adding to queue + for (DestructPendingMessage dp : queue) { + destructQueueStorage.getQueue().add(new DestructQueueMessage(peer, + dp.getRid(), dp.getTimer() + readDate)); + } + Collections.sort(destructQueueStorage.getQueue(), (destructQueueMessage, t1) -> { + return JavaUtil.compare(destructQueueMessage.getDestructDate(), t1.getDestructDate()); + }); + keyValueStorage.addOrUpdateItem(0, destructQueueStorage.toByteArray()); + + // Remove from pending + pendingStorage.getMessages().removeAll(queue); + saveStorage(peer, pendingStorage); + + // Checking queue + scheduleCheck(); + } + } + + return Promise.success(null); + } + + // + // Queue Checking + // + + private void scheduleCheck() { + if (checkCancellable != null) { + checkCancellable.cancel(); + checkCancellable = null; + } + if (isDestructing) { + return; + } + if (destructQueueStorage.getQueue().size() == 0) { + checkCancellable = schedule(new CheckQueue(), DEFAULT_DELAY); + } else { + long time = Runtime.getCurrentSyncedTime(); + long delta = destructQueueStorage.getQueue().get(0).getDestructDate() - time; + if (delta < 0) { + checkQueue(); + } else { + checkCancellable = schedule(new CheckQueue(), delta); + } + } + } + + private void checkQueue() { + if (isDestructing) { + return; + } + long time = Runtime.getCurrentSyncedTime(); + List pendingMessages = null; + for (DestructQueueMessage q : destructQueueStorage.getQueue()) { + if (q.getDestructDate() <= time) { + if (pendingMessages == null) { + pendingMessages = new ArrayList<>(); + } + pendingMessages.add(q); + } + } + if (pendingMessages != null) { + isDestructing = true; + HashMap> messages = new HashMap<>(); + for (DestructQueueMessage p : pendingMessages) { + if (!messages.containsKey(p.getPeer())) { + messages.put(p.getPeer(), new ArrayList<>()); + } + messages.get(p.getPeer()).add(p.getRid()); + } + + ArrayList> res = new ArrayList<>(); + for (Peer p : messages.keySet()) { + res.add(context().getMessagesModule().getRouter().onMessagesDestructed(p, + messages.get(p))); + } + final List finalPendingMessages = pendingMessages; + PromisesArray.ofPromises(res) + .zip(r -> null) + .then(r -> { + destructQueueStorage.getQueue().removeAll(finalPendingMessages); + keyValueStorage.addOrUpdateItem(0, destructQueueStorage.toByteArray()); + isDestructing = false; + scheduleCheck(); + }); + } else { + scheduleCheck(); + } + } + + // + // Tools + // + + private DestructPendingStorage getStorage(Peer peer) { + byte[] data = keyValueStorage.loadItem(peer.getUnuqueId()); + if (data == null) { + return null; + } + try { + return DestructPendingStorage.fromBytes(data); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private void saveStorage(Peer peer, DestructPendingStorage storage) { + if (storage.getMessages().size() == 0 && storage.getIndividualMessages().size() == 0) { + keyValueStorage.removeItem(peer.getUnuqueId()); + } else { + keyValueStorage.addOrUpdateItem(peer.getUnuqueId(), storage.toByteArray()); + } + } + + // + // Messages + // + + @Override + public Promise onAsk(Object message) throws Exception { + if (message instanceof MessageRead) { + MessageRead messageRead = (MessageRead) message; + return onMessageRead(messageRead.peer, messageRead.readDate); + } else if (message instanceof MessageReadByMe) { + MessageReadByMe readByMe = (MessageReadByMe) message; + return onMessageReadByMe(readByMe.peer, readByMe.readDate); + } else if (message instanceof NewMessages) { + NewMessages newMessages = (NewMessages) message; + return onMessages(newMessages.peer, newMessages.messages); + } else { + return super.onAsk(message); + } + } + + @Override + public void onReceive(Object message) { + if (message instanceof CheckQueue) { + checkQueue(); + } else { + super.onReceive(message); + } + } + + public static class MessageRead implements AskMessage { + private final Peer peer; + private final long readDate; + + public MessageRead(Peer peer, long readDate) { + this.peer = peer; + this.readDate = readDate; + } + } + + public static class MessageReadByMe implements AskMessage { + private final Peer peer; + private final long readDate; + + public MessageReadByMe(Peer peer, long readDate) { + this.peer = peer; + this.readDate = readDate; + } + } + + public static class NewMessages implements AskMessage { + private final Peer peer; + private final List messages; + + public NewMessages(Peer peer, List messages) { + this.peer = peer; + this.messages = messages; + } + } + + private static class CheckQueue { + + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index c9d24058d7..9da34010a8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -267,7 +267,8 @@ private PendingMessage prepareSend(Peer peer, AbsContent content) { timer = context().getEncryption().getConversationState().get(peer.getPeerId()).getTimer().get(); } - Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content); + Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content, + new ArrayList<>(), 0, timer); context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer); pendingMessages.getPendingMessages().add(pendingMessage); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingMessage.java new file mode 100644 index 0000000000..0bb9df405a --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingMessage.java @@ -0,0 +1,63 @@ +package im.actor.core.modules.messaging.actions.entity; + +import java.io.IOException; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class DestructPendingMessage extends BserObject { + + public static DestructPendingMessage fromBytes(byte[] data) throws IOException { + return Bser.parse(new DestructPendingMessage(), data); + } + + private long rid; + private long date; + private boolean isOut; + private int timer; + + public DestructPendingMessage(long rid, long date, boolean isOut, int timer) { + this.rid = rid; + this.date = date; + this.isOut = isOut; + this.timer = timer; + } + + private DestructPendingMessage() { + + } + + public long getRid() { + return rid; + } + + public long getDate() { + return date; + } + + public boolean isOut() { + return isOut; + } + + public int getTimer() { + return timer; + } + + @Override + public void parse(BserValues values) throws IOException { + rid = values.getLong(1); + date = values.getLong(2); + isOut = values.getBool(3); + timer = values.getInt(4); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeLong(1, rid); + writer.writeLong(2, date); + writer.writeBool(3, isOut); + writer.writeInt(4, timer); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingStorage.java new file mode 100644 index 0000000000..7c0fcb2341 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructPendingStorage.java @@ -0,0 +1,56 @@ +package im.actor.core.modules.messaging.actions.entity; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class DestructPendingStorage extends BserObject { + + public static DestructPendingStorage fromBytes(byte[] data) throws IOException { + return Bser.parse(new DestructPendingStorage(), data); + } + + private List individualMessages; + private List messages; + + public DestructPendingStorage(List individualMessages, List messages) { + this.individualMessages = individualMessages; + this.messages = messages; + } + + public DestructPendingStorage() { + this.individualMessages = new ArrayList<>(); + this.messages = new ArrayList<>(); + } + + public List getIndividualMessages() { + return individualMessages; + } + + public List getMessages() { + return messages; + } + + @Override + public void parse(BserValues values) throws IOException { + individualMessages.clear(); + for (byte[] b : values.getRepeatedBytes(1)) { + individualMessages.add(DestructPendingMessage.fromBytes(b)); + } + messages.clear(); + for (byte[] b : values.getRepeatedBytes(2)) { + messages.add(DestructPendingMessage.fromBytes(b)); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeRepeatedObj(1, individualMessages); + writer.writeRepeatedObj(2, messages); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueMessage.java new file mode 100644 index 0000000000..0b1ca4926c --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueMessage.java @@ -0,0 +1,56 @@ +package im.actor.core.modules.messaging.actions.entity; + +import java.io.IOException; + +import im.actor.core.entity.Peer; +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class DestructQueueMessage extends BserObject { + + public static DestructQueueMessage fromBytes(byte[] data) throws IOException { + return Bser.parse(new DestructQueueMessage(), data); + } + + private Peer peer; + private long rid; + private long destructDate; + + public DestructQueueMessage(Peer peer, long rid, long destructDate) { + this.peer = peer; + this.rid = rid; + this.destructDate = destructDate; + } + + private DestructQueueMessage() { + + } + + public Peer getPeer() { + return peer; + } + + public long getRid() { + return rid; + } + + public long getDestructDate() { + return destructDate; + } + + @Override + public void parse(BserValues values) throws IOException { + peer = Peer.fromUniqueId(values.getLong(1)); + rid = values.getLong(2); + destructDate = values.getLong(3); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeLong(1, peer.getUnuqueId()); + writer.writeLong(2, rid); + writer.writeLong(3, destructDate); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueStorage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueStorage.java new file mode 100644 index 0000000000..72b2655250 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/DestructQueueStorage.java @@ -0,0 +1,48 @@ +package im.actor.core.modules.messaging.actions.entity; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class DestructQueueStorage extends BserObject { + + public static DestructQueueStorage fromBytes(byte[] data) { + try { + return Bser.parse(new DestructQueueStorage(), data); + } catch (IOException e) { + e.printStackTrace(); + return new DestructQueueStorage(); + } + } + + private List queue; + + public DestructQueueStorage(List queue) { + this.queue = queue; + } + + public DestructQueueStorage() { + this.queue = new ArrayList<>(); + } + + public List getQueue() { + return queue; + } + + @Override + public void parse(BserValues values) throws IOException { + for (byte[] b : values.getRepeatedBytes(1)) { + queue.add(DestructQueueMessage.fromBytes(b)); + } + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeRepeatedObj(1, queue); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/MessageDesc.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/MessageDesc.java new file mode 100644 index 0000000000..4fe5112974 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/MessageDesc.java @@ -0,0 +1,38 @@ +package im.actor.core.modules.messaging.actions.entity; + +public class MessageDesc { + + private long rid; + private boolean isOut; + private long date; + private int timer; + private boolean isNeedExplicitRead; + + public MessageDesc(long rid, boolean isOut, long date, int timer, boolean isNeedExplicitRead) { + this.rid = rid; + this.isOut = isOut; + this.date = date; + this.timer = timer; + this.isNeedExplicitRead = isNeedExplicitRead; + } + + public long getRid() { + return rid; + } + + public boolean isOut() { + return isOut; + } + + public long getDate() { + return date; + } + + public int getTimer() { + return timer; + } + + public boolean isNeedExplicitRead() { + return isNeedExplicitRead; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index ab45236ab7..4cf0449321 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -39,6 +39,7 @@ import im.actor.core.entity.Reaction; import im.actor.core.entity.User; import im.actor.core.entity.content.AbsContent; +import im.actor.core.entity.content.DocumentContent; import im.actor.core.entity.content.TextContent; import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleActor; @@ -46,7 +47,9 @@ import im.actor.core.modules.api.ApiSupportConfiguration; import im.actor.core.modules.messaging.actions.CursorReaderActor; import im.actor.core.modules.messaging.actions.CursorReceiverActor; +import im.actor.core.modules.messaging.actions.Destructor; import im.actor.core.modules.messaging.actions.SenderActor; +import im.actor.core.modules.messaging.actions.entity.MessageDesc; import im.actor.core.modules.messaging.dialogs.DialogsInt; import im.actor.core.modules.messaging.history.entity.DialogHistory; import im.actor.core.modules.messaging.router.entity.ActiveDialogGroup; @@ -64,6 +67,7 @@ import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageOnlyActive; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; +import im.actor.core.modules.messaging.router.entity.RouterMessagesSelfDestructed; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; @@ -101,6 +105,9 @@ public class RouterActor extends ModuleActor { // Active Dialogs private ActiveDialogStorage activeDialogStorage; + // Self-Destructor + private Destructor destructor; + public RouterActor(ModuleContext context) { super(context); @@ -112,6 +119,11 @@ public void preStart() { conversationStates = context().getMessagesModule().getConversationStates().getEngine(); + // + // Self-Destructor + // + destructor = new Destructor(context()); + // // Loading Active Dialogs // @@ -296,6 +308,23 @@ private Promise onNewMessages(Peer peer, List messages) { Promise res = getDialogsRouter().onMessage(peer, topMessage, state.getUnreadCount()); + // + // Update Self-Destructor + // + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + ArrayList pendingDescs = new ArrayList<>(); + for (Message m : messages) { + if (m.getTimer() > 0) { + pendingDescs.add(new MessageDesc(m.getRid(), m.getSenderId() == myUid(), + m.getSortDate(), m.getTimer(), m.getContent() instanceof DocumentContent)); + } + } + if (pendingDescs.size() > 0) { + res = res.chain(r -> destructor.onMessages(peer, pendingDescs)); + } + } + + // // Playing notifications // @@ -361,7 +390,18 @@ private Promise onOutgoingSent(Peer peer, long rid, long date) { updateChatState(peer); // Notify dialogs - return getDialogsRouter().onMessage(peer, updatedMsg, -1); + Promise res = getDialogsRouter().onMessage(peer, updatedMsg, -1); + + // Self-Destruct + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && updatedMsg.getTimer() > 0) { + MessageDesc desc = new MessageDesc(rid, true, date, updatedMsg.getTimer(), + msg.getContent() instanceof DocumentContent); + ArrayList descs = new ArrayList<>(); + descs.add(desc); + res = res.chain(r -> destructor.onMessages(peer, descs)); + } + + return res; } else { return Promise.success(null); } @@ -517,6 +557,10 @@ private Promise onMessageDeleted(Peer peer, List rids) { return getDialogsRouter().onMessageDeleted(peer, head); } + private Promise onMessageDestructed(Peer peer, List rids) { + return onMessageDeleted(peer, rids); + } + private Promise onChatClear(Peer peer) { conversation(peer).clear(); @@ -532,38 +576,41 @@ private Promise onChatClear(Peer peer) { return getDialogsRouter().onChatClear(peer); } - private Promise onChatDropCache(Peer peer) { - return context().getMessagesModule().getHistoryActor(peer).reset(); - } - - private Promise onChatReset(Peer peer) { - - Log.d(TAG, "onChatReset"); + private Promise onChatDelete(Peer peer) { conversation(peer).clear(); ConversationState state = conversationStates.getValue(peer.getUnuqueId()); - state = state.changeIsLoaded(false); - conversationStates.addOrUpdateItem(state); + if (!state.isLoaded()) { + state = state.changeIsLoaded(true); + conversationStates.addOrUpdateItem(state); + } updateChatState(peer); - return Promise.success(null); + return getDialogsRouter().onChatDelete(peer); } - private Promise onChatDelete(Peer peer) { + + // + // Cache invalidation + // + + private Promise onChatDropCache(Peer peer) { + return context().getMessagesModule().getHistoryActor(peer).reset(); + } + + private Promise onChatReset(Peer peer) { conversation(peer).clear(); ConversationState state = conversationStates.getValue(peer.getUnuqueId()); - if (!state.isLoaded()) { - state = state.changeIsLoaded(true); - conversationStates.addOrUpdateItem(state); - } + state = state.changeIsLoaded(false); + conversationStates.addOrUpdateItem(state); updateChatState(peer); - return getDialogsRouter().onChatDelete(peer); + return Promise.success(null); } @@ -578,6 +625,11 @@ private Promise onMessageRead(Peer peer, long date) { if (date > state.getOutReadDate()) { state = state.changeOutReadDate(date); res = getDialogsRouter().onPeerReadChanged(peer, date); + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + res = res.chain(r -> destructor.onMessageRead(peer, date)); + } + isChanged = true; } else { res = Promise.success(null); @@ -620,6 +672,10 @@ private Promise onMessageReadByMe(Peer peer, long date, int counter) { Promise res = getDialogsRouter().onCounterChanged(peer, counter); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + res = res.chain(r -> destructor.onMessageReadByMe(peer, date)); + } + notifyActiveDialogsVM(); context().getNotificationsModule().onOwnRead(peer, date); @@ -1067,6 +1123,9 @@ public Promise onAsk(Object message) throws Exception { } else if (message instanceof RouterResetChat) { RouterResetChat resetChat = (RouterResetChat) message; return onChatReset(resetChat.getPeer()); + } else if (message instanceof RouterMessagesSelfDestructed) { + RouterMessagesSelfDestructed selfDestructed = (RouterMessagesSelfDestructed) message; + return onMessageDestructed(selfDestructed.getPeer(), selfDestructed.getRids()); } else { return super.onAsk(message); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index 8d54dce68a..f2498ebaf6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -26,6 +26,7 @@ import im.actor.core.modules.messaging.router.entity.RouterDifferenceStart; import im.actor.core.modules.messaging.router.entity.RouterEncryptedUpdate; import im.actor.core.modules.messaging.router.entity.RouterMessageUpdate; +import im.actor.core.modules.messaging.router.entity.RouterMessagesSelfDestructed; import im.actor.core.modules.messaging.router.entity.RouterNewMessages; import im.actor.core.modules.messaging.router.entity.RouterOutgoingError; import im.actor.core.modules.messaging.router.entity.RouterOutgoingMessage; @@ -42,7 +43,7 @@ import static im.actor.runtime.actors.ActorSystem.system; public class RouterInt extends ActorInterface implements BusSubscriber { - + public RouterInt(final ModuleContext context) { setDest(system().actorOf("actor/router", () -> new RouterActor(context))); @@ -118,6 +119,10 @@ public Promise onMessagesDeleted(Peer peer, List rids) { return ask(new RouterDeletedMessages(peer, rids)); } + public Promise onMessagesDestructed(Peer peer, List rids) { + return ask(new RouterMessagesSelfDestructed(peer, rids)); + } + // // History diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterMessagesSelfDestructed.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterMessagesSelfDestructed.java new file mode 100644 index 0000000000..d72a18fb36 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterMessagesSelfDestructed.java @@ -0,0 +1,25 @@ +package im.actor.core.modules.messaging.router.entity; + +import java.util.List; + +import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterMessagesSelfDestructed implements AskMessage, RouterMessageOnlyActive { + private final Peer peer; + private final List rids; + + public RouterMessagesSelfDestructed(Peer peer, List rids) { + this.peer = peer; + this.rids = rids; + } + + public Peer getPeer() { + return peer; + } + + public List getRids() { + return rids; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java index 670e0b0a00..3680205a2e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java @@ -82,4 +82,20 @@ public static long[] unbox(List list) { return res; } + /** + * Compare Longs + * + * @param a first value + * @param b second value + * @return result + */ + public static int compare(long a, long b) { + if (a == b) { + return 0; + } else if (a > b) { + return -1; + } else { + return 1; + } + } } From 991b387b3160b04aa60605459f38f147498862a0 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 06:51:00 +0300 Subject: [PATCH 57/81] fix(core): Fixing compilation error --- .../im/actor/core/modules/messaging/actions/SenderActor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 9da34010a8..9567ae47fa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -4,8 +4,6 @@ package im.actor.core.modules.messaging.actions; -import android.location.Location; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; From f5c1d5463f50d89b0d1035611b2a9095eeac0290 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 07:05:26 +0300 Subject: [PATCH 58/81] fix(iOS): Fixing j2objc compatibility --- .../actor/core/modules/encryption/EncryptedRouterActor.java | 3 +++ .../im/actor/core/modules/encryption/EncryptionModule.java | 3 +++ .../core/modules/encryption/ratchet/KeyManagerActor.java | 5 +++++ .../core/modules/messaging/actions/CursorReaderActor.java | 6 ++++++ .../core/modules/messaging/actions/CursorReceiverActor.java | 5 +++++ 5 files changed, 22 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java index 188ea5ffb3..cd9425d223 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptedRouterActor.java @@ -25,6 +25,9 @@ public class EncryptedRouterActor extends ModuleActor { + // j2objc workaround + private static final Void DUMB = null; + private KeyManager keyManager; private EncryptedMsg encryptedMsg; private EncryptedUpdates updates; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java index 8c5c115874..b6d2357c23 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/EncryptionModule.java @@ -32,6 +32,9 @@ public class EncryptionModule extends AbsModule { + // j2objc workaround + private static final Void DUMB = null; + private KeyManager keyManager; private SessionManager sessionManager; private EncryptedRouter encryptedRouter; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java index 83e7f55de6..71520d36a6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/KeyManagerActor.java @@ -16,6 +16,7 @@ import im.actor.core.api.rpc.RequestLoadPublicKey; import im.actor.core.api.rpc.RequestLoadPublicKeyGroups; import im.actor.core.api.rpc.RequestUploadPreKey; +import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.Configuration; @@ -50,6 +51,10 @@ */ public class KeyManagerActor extends ModuleActor { + // j2objc workaround + private static final Void DUMB = null; + private static final ResponseVoid DUMB2 = null; + private static final String TAG = "KeyManagerActor"; private KeyValueStorage encryptionKeysStorage; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java index 740198b227..d91cc65100 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReaderActor.java @@ -7,13 +7,19 @@ import im.actor.core.api.ApiEncryptedRead; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageRead; +import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; import im.actor.core.util.RandomUtils; +import im.actor.runtime.actors.messages.Void; public class CursorReaderActor extends CursorActor { + // j2objc workaround + private static final ResponseVoid DUMB = null; + private static final Long DUMB2 = null; + public CursorReaderActor(ModuleContext context) { super(CURSOR_READ, context); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java index 7ec25d567f..0ac0cc9762 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/CursorReceiverActor.java @@ -7,6 +7,7 @@ import im.actor.core.api.ApiEncryptedReceived; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.rpc.RequestMessageReceived; +import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.Peer; import im.actor.core.entity.PeerType; import im.actor.core.modules.ModuleContext; @@ -14,6 +15,10 @@ public class CursorReceiverActor extends CursorActor { + // j2objc workaround + private static final ResponseVoid DUMB = null; + private static final Long DUMB2 = null; + public CursorReceiverActor(ModuleContext context) { super(CURSOR_RECEIVED, context); } From 27cac98ccb08339069af6d4555dcf09878004911 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 07:07:19 +0300 Subject: [PATCH 59/81] feat(*): Version bump --- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-push/build.gradle | 2 +- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 +- actor-sdk/sdk-core-ios/VERSION | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index e25667ecd2..a9e24fe106 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "3.0" +def baseVersion = "4.0" android { compileSdkVersion 24 diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 7e6e58cf81..68eec1a276 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "3.0" +def baseVersion = "4.0" android { compileSdkVersion 24 diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 270da1981f..8536b01333 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -16,7 +16,7 @@ apply plugin: 'me.tatarka.retrolambda' group = 'im.actor' version = '0.0.1' -def baseVersion = "3.0" +def baseVersion = "4.0" android { diff --git a/actor-sdk/sdk-core-ios/VERSION b/actor-sdk/sdk-core-ios/VERSION index 415b19fc36..389f7740ee 100644 --- a/actor-sdk/sdk-core-ios/VERSION +++ b/actor-sdk/sdk-core-ios/VERSION @@ -1 +1 @@ -2.0 \ No newline at end of file +4.0 \ No newline at end of file From f6d711a51cf7450bd08955032208df13ca417e3d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 08:35:45 +0300 Subject: [PATCH 60/81] fix(core): Improvements on message read detection --- actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift | 1 + .../ActorSDK/Sources/ActorCore/ActorCoreExt.swift | 6 ++++++ .../sdk-core-ios/ActorSDK/Sources/ActorStyle.swift | 6 ++++++ .../Content/Dialogs List/Cells/AADialogCell.swift | 7 ++++++- .../Conversation/ConversationViewController.swift | 13 ++++++++++--- .../modules/messaging/actions/DestructorActor.java | 1 - .../core/modules/messaging/router/RouterActor.java | 8 ++++++++ .../src/main/java/im/actor/core/util/JavaUtil.java | 2 +- 8 files changed, 38 insertions(+), 6 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift index 3773b66dca..a618721a2b 100644 --- a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift @@ -29,6 +29,7 @@ import ActorSDK ActorSDK.sharedActor().authStrategy = .PhoneEmail ActorSDK.sharedActor().style.dialogAvatarSize = 58 + ActorSDK.sharedActor().style.dialogTitleSecureColor = UIColor.greenColor() // Creating Actor ActorSDK.sharedActor().createActor() diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift index fe8ee59a22..4ad2a85850 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/ActorCoreExt.swift @@ -173,6 +173,12 @@ public extension ACPeer { return self.peerType.ordinal() == ACPeerType.PRIVATE().ordinal() } } + + public var isPrivateSecret: Bool { + get { + return self.peerType.ordinal() == ACPeerType.PRIVATE_ENCRYPTED().ordinal() + } + } } public extension ACMessage { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift index 807674f9b6..c9cb033d3d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift @@ -539,6 +539,12 @@ public class ActorStyle { set(v) { _dialogTitleColor = v } } + private var _dialogTitleSecureColor: UIColor? + public var dialogTitleSecureColor: UIColor { + get { return _dialogTitleSecureColor != nil ? _dialogTitleSecureColor! : dialogTitleColor } + set(v) { _dialogTitleSecureColor = v } + } + private var _dialogTextColor: UIColor? public var dialogTextColor: UIColor { get { return _dialogTextColor != nil ? _dialogTextColor! : dialogTitleColor.alpha(0.64) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift index f86667f9a8..8ec44c4e9b 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Dialogs List/Cells/AADialogCell.swift @@ -255,7 +255,12 @@ public class AADialogCell: AATableViewCell, AABindedCell { let title = NSMutableAttributedString(string: config.item.dialogTitle) title.yy_font = UIFont.mediumSystemFontOfSize(17) - title.yy_color = appStyle.dialogTitleColor + if config.item.peer.isPrivateSecret { + title.yy_color = appStyle.dialogTitleSecureColor + } else { + title.yy_color = appStyle.dialogTitleColor + } + let titleContainer = YYTextContainer(size: CGSize(width: config.titleWidth, height: 1000)) titleContainer.maximumNumberOfRows = 1 titleContainer.truncationType = .End diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 9fe626f1ad..09407d4611 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -300,11 +300,11 @@ public class ConversationViewController: super.viewWillAppear(animated) // Installing bindings - if (peer.peerType.ordinal() == ACPeerType.PRIVATE().ordinal()) { + if (peer.peerType == ACPeerType.PRIVATE() || peer.peerType == ACPeerType.PRIVATE_ENCRYPTED()) { let user = Actor.getUserWithUid(peer.peerId) let nameModel = user.getNameModel() - let blockStatus = user.isBlockedModel().get().booleanValue() + // let blockStatus = user.isBlockedModel().get().booleanValue() binder.bind(nameModel, closure: { (value: NSString?) -> () in self.titleView.text = String(value!) @@ -314,7 +314,14 @@ public class ConversationViewController: self.avatarView.bind(user.getNameModel().get(), id: Int(user.getId()), avatar: value) }) - binder.bind(Actor.getTypingWithUid(peer.peerId), valueModel2: user.getPresenceModel(), closure:{ (typing:JavaLangBoolean?, presence:ACUserPresence?) -> () in + + let typingModel: ARValueModel + if peer.peerType == ACPeerType.PRIVATE() { + typingModel = Actor.getTypingWithUid(peer.peerId) + } else { + typingModel = Actor.getSecretTypingWithUid(peer.peerId) + } + binder.bind(typingModel, valueModel2: user.getPresenceModel(), closure:{ (typing:JavaLangBoolean?, presence:ACUserPresence?) -> () in if (typing != nil && typing!.booleanValue()) { self.subtitleView.text = Actor.getFormatter().formatTyping() diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java index 9a19921a15..e560c9270b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 4cf0449321..c7ce8849d1 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -321,6 +321,10 @@ private Promise onNewMessages(Peer peer, List messages) { } if (pendingDescs.size() > 0) { res = res.chain(r -> destructor.onMessages(peer, pendingDescs)); + if (isRead) { + long readDate = state.getInReadDate(); + res = res.chain(r -> destructor.onMessageReadByMe(peer, readDate)); + } } } @@ -775,6 +779,10 @@ private void markAsReadIfNeeded(Peer peer) { getDialogsRouter().onCounterChanged(peer, 0); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + destructor.onMessageReadByMe(peer, inMaxMessageDate); + } + context().getNotificationsModule().onOwnRead(peer, inMaxMessageDate); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java index 3680205a2e..ba26766bae 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/util/JavaUtil.java @@ -92,7 +92,7 @@ public static long[] unbox(List list) { public static int compare(long a, long b) { if (a == b) { return 0; - } else if (a > b) { + } else if (a < b) { return -1; } else { return 1; From 09ba411837d75bf3578e61c93e9603223d5f0370 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 23:15:26 +0300 Subject: [PATCH 61/81] feat(core): Ephemeral Keys ratcheting --- .../ratchet/EncryptedSessionActor.java | 195 ++++++++++++------ .../ratchet/EncryptedSessionChain.java | 8 +- .../ratchet/entity/SessionState.java | 92 +++++++++ .../messaging/actions/DestructorActor.java | 2 +- .../crypto/primitives/util/ByteStrings.java | 9 + 5 files changed, 235 insertions(+), 71 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/SessionState.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java index 1455f16132..1bfd863751 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionActor.java @@ -1,12 +1,15 @@ package im.actor.core.modules.encryption.ratchet; +import java.io.IOException; import java.util.ArrayList; import im.actor.core.api.ApiEncyptedBoxKey; import im.actor.core.entity.encryption.PeerSession; import im.actor.core.modules.ModuleContext; +import im.actor.core.modules.encryption.ratchet.entity.PrivateKey; import im.actor.core.modules.encryption.ratchet.entity.PublicKey; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.encryption.ratchet.entity.SessionState; import im.actor.runtime.*; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.promise.Promise; @@ -55,12 +58,13 @@ class EncryptedSessionActor extends ModuleActor { private KeyValueStorage sessionStorage; // - // Temp encryption chains + // Internal State // - private byte[] latestTheirEphemeralKey; - private ArrayList encryptionChains = new ArrayList<>(); + private SessionState sessionState; + private EncryptedSessionChain encryptionChain = null; private ArrayList decryptionChains = new ArrayList<>(); + private boolean isFreezed = false; // // Constructors and Methods @@ -78,78 +82,79 @@ public void preStart() { super.preStart(); keyManager = context().getEncryption().getKeyManager(); sessionStorage = context().getEncryption().getKeyValueStorage(); - latestTheirEphemeralKey = sessionStorage.loadItem(session.getSid()); + + sessionState = new SessionState(); + byte[] data = sessionStorage.loadItem(session.getSid()); + if (data != null) { + try { + sessionState = SessionState.fromBytes(data); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void saveState() { + sessionStorage.addOrUpdateItem(session.getSid(), sessionState.toByteArray()); } + // + // Encryption + // + private Promise onEncrypt(final byte[] data) { - // - // Stage 1: Pick Their Ephemeral key. Use already received or pick random pre key. // Stage 2: Pick Encryption Chain // Stage 3: Decrypt - // + // + // Stage 1: Pick Their Ephemeral key + // - After this stage we will have public their key and own private key + // for encryption chain + // Promise ephemeralKey; - if (latestTheirEphemeralKey != null) { - ephemeralKey = success(latestTheirEphemeralKey); - Log.d(TAG, "Picked cached"); + if (sessionState.getLatestTheirKey() != null) { + ephemeralKey = success(sessionState.getLatestTheirKey()); } else { - ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()) - .map(PublicKey::getPublicKey); - Log.d(TAG, "Picked from server #" + uid + " " + session.getTheirKeyGroupId()); + ephemeralKey = keyManager.getUserRandomPreKey(uid, session.getTheirKeyGroupId()).map(key -> { + // This can be called only for the first time of sending message in this session + // So we need to save ephemeral key and generate new initial private key + // for this session + sessionState = sessionState.updateKeys(key.getPublicKey()); + saveState(); + return key.getPublicKey(); + }); } - return ephemeralKey - .map(publicKey -> pickEncryptChain(publicKey)) + return wrap(ephemeralKey + .map(publicKey -> pickEncryptChain()) .map(encryptedSessionChain -> encrypt(encryptedSessionChain, data)) .map(bytes -> new ApiEncyptedBoxKey(session.getUid(), session.getTheirKeyGroupId(), - "curve25519", bytes)); + "curve25519", bytes))); } - private Promise onDecrypt(ApiEncyptedBoxKey data) { + private EncryptedSessionChain pickEncryptChain() { - // - // Stage 1: Parsing message header - // Stage 2: Picking decryption chain - // Stage 3: Decryption of message - // Stage 4: Saving their ephemeral key - // - - byte[] material = data.getEncryptedKey(); - - // final long ownEphemeralKey0Id = ByteStrings.bytesToLong(data, 0); - // final long theirEphemeralKey0Id = ByteStrings.bytesToLong(data, 8); - final byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); - final byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); -// Log.d(TAG, "Sender Ephemeral " + Crypto.keyHash(senderEphemeralKey)); -// Log.d(TAG, "Receiver Ephemeral " + Crypto.keyHash(receiverEphemeralKey)); - - return pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) - .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)); - //.then(decryptedPackage -> latestTheirEphemeralKey = senderEphemeralKey); - } - - private EncryptedSessionChain pickEncryptChain(byte[] ephemeralKey) { - - if (latestTheirEphemeralKey == null) { - latestTheirEphemeralKey = ephemeralKey; - sessionStorage.addOrUpdateItem(session.getSid(), ephemeralKey); - } - - if (encryptionChains.size() > 0) { - return encryptionChains.get(0); + // Dispose existing encryption chain if their public keys changes + // If not return latest one + if (encryptionChain != null) { + if (ByteStrings.isEquals(sessionState.getLatestTheirKey(), encryptionChain.getTheirPublicKey()) && + ByteStrings.isEquals(sessionState.getLatestOwnPublicKey(), encryptionChain.getOwnPublicKey())) { + return encryptionChain; + } else { + encryptionChain = null; + } } - EncryptedSessionChain chain = new EncryptedSessionChain(session, - Curve25519.keyGenPrivate(Crypto.randomBytes(32)), ephemeralKey); - - encryptionChains.add(0, chain); + encryptionChain = new EncryptedSessionChain(session, + sessionState.getLatestOwnPrivateKey(), + sessionState.getLatestOwnPublicKey(), + sessionState.getLatestTheirKey()); - return chain; + return encryptionChain; } private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { - byte[] encrypted; try { encrypted = chain.encrypt(data); @@ -157,18 +162,43 @@ private byte[] encrypt(EncryptedSessionChain chain, byte[] data) { e.printStackTrace(); throw new RuntimeException(e); } + return encrypted; + } + + // + // Decryption + // -// Log.d(TAG, "!Sender Ephemeral " + Crypto.keyHash(Curve25519.keyGenPublic(chain.getOwnPrivateKey()))); -// Log.d(TAG, "!Receiver Ephemeral " + Crypto.keyHash(chain.getTheirPublicKey())); + private Promise onDecrypt(ApiEncyptedBoxKey data) { - return encrypted; + // + // Stage 1: Parsing message header + // Stage 2: Picking decryption chain + // Stage 3: Decryption of message + // Stage 4: Saving their ephemeral key + // + + byte[] material = data.getEncryptedKey(); + byte[] senderEphemeralKey = ByteStrings.substring(material, 16, 32); + byte[] receiverEphemeralKey = ByteStrings.substring(material, 48, 32); + + return wrap(pickDecryptChain(senderEphemeralKey, receiverEphemeralKey) + .map(encryptedSessionChain -> decrypt(encryptedSessionChain, material)) + .then(r -> { + // Update Session State keys + if (sessionState.getLatestTheirKey() == null || + ByteStrings.isEquals(sessionState.getLatestTheirKey(), senderEphemeralKey)) { + sessionState = sessionState.updateKeys(senderEphemeralKey); + saveState(); + } + })); } - private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { + private Promise pickDecryptChain(final byte[] theirEphemeralKey, final byte[] ephemeralKey) { EncryptedSessionChain pickedChain = null; for (EncryptedSessionChain c : decryptionChains) { - if (ByteStrings.isEquals(Curve25519.keyGenPublic(c.getOwnPrivateKey()), ephemeralKey)) { + if (ByteStrings.isEquals(c.getOwnPublicKey(), ephemeralKey)) { pickedChain = c; break; } @@ -177,18 +207,27 @@ private Promise pickDecryptChain(final byte[] theirEpheme return Promise.success(pickedChain); } + return findOwnPreKey(ephemeralKey).map(privateKey -> { + EncryptedSessionChain chain = new EncryptedSessionChain(session, privateKey, + ephemeralKey, theirEphemeralKey); + decryptionChains.add(0, chain); + if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { + decryptionChains.remove(MAX_DECRYPT_CHAINS) + .safeErase(); + } + return chain; + }); + } + private Promise findOwnPreKey(byte[] ephemeralKey) { + if (ByteStrings.isEquals(ephemeralKey, sessionState.getLatestOwnPublicKey())) { + return Promise.success(sessionState.getLatestOwnPrivateKey()); + } + if (ByteStrings.isEquals(ephemeralKey, sessionState.getPrevOwnPublicKey())) { + return Promise.success(sessionState.getPrevOwnPrivateKey()); + } return context().getEncryption().getKeyManager().getOwnPreKey(ephemeralKey) - .map(src1 -> { - EncryptedSessionChain chain = new EncryptedSessionChain(session, - src1.getKey(), theirEphemeralKey); - decryptionChains.add(0, chain); - if (decryptionChains.size() > MAX_DECRYPT_CHAINS) { - decryptionChains.remove(MAX_DECRYPT_CHAINS) - .safeErase(); - } - return chain; - }); + .map(PrivateKey::getKey); } private byte[] decrypt(EncryptedSessionChain chain, byte[] data) { @@ -202,6 +241,18 @@ private byte[] decrypt(EncryptedSessionChain chain, byte[] data) { return decrypted; } + // + // Tools + // + + private Promise wrap(Promise promise) { + isFreezed = true; + return promise.after((r, e) -> { + isFreezed = false; + unstashAll(); + }); + } + // // Actor Messages // @@ -209,8 +260,16 @@ private byte[] decrypt(EncryptedSessionChain chain, byte[] data) { @Override public Promise onAsk(Object message) throws Exception { if (message instanceof EncryptPackage) { + if (isFreezed) { + stash(); + return null; + } return onEncrypt(((EncryptPackage) message).getData()); } else if (message instanceof DecryptPackage) { + if (isFreezed) { + stash(); + return null; + } DecryptPackage decryptPackage = (DecryptPackage) message; return onDecrypt(decryptPackage.getData()); } else { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java index 3f4c6b9695..fb5478f071 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSessionChain.java @@ -22,10 +22,10 @@ public class EncryptedSessionChain { private int sentCounter; private byte[] rootChainKey; - public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] theirPublicKey) { + public EncryptedSessionChain(PeerSession session, byte[] ownPrivateKey, byte[] ownPublicKey, byte[] theirPublicKey) { this.session = session; this.ownPrivateKey = ownPrivateKey; - this.ownPublicKey = Curve25519.keyGenPublic(ownPrivateKey); + this.ownPublicKey = ownPublicKey; this.theirPublicKey = theirPublicKey; this.sentCounter = 0; this.rootChainKey = RatchetRootChainKey.makeRootChainKey( @@ -46,6 +46,10 @@ public byte[] getTheirPublicKey() { return theirPublicKey; } + public byte[] getOwnPublicKey() { + return ownPublicKey; + } + public byte[] decrypt(byte[] data) throws IntegrityException { if (data.length < 80) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/SessionState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/SessionState.java new file mode 100644 index 0000000000..8260dce831 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/entity/SessionState.java @@ -0,0 +1,92 @@ +package im.actor.core.modules.encryption.ratchet.entity; + +import java.io.IOException; + +import im.actor.runtime.Crypto; +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; +import im.actor.runtime.crypto.Curve25519; + +public class SessionState extends BserObject { + + public static SessionState fromBytes(byte[] data) throws IOException { + return Bser.parse(new SessionState(), data); + } + + private byte[] prevOwnPrivateKey; + private byte[] prevOwnPublicKey; + private byte[] latestOwnPrivateKey; + private byte[] latestOwnPublicKey; + private byte[] latestTheirKey; + + public SessionState(byte[] prevOwnPrivateKey, byte[] prevOwnPublicKey, + byte[] latestOwnPrivateKey, byte[] latestOwnPublicKey, + byte[] latestTheirKey) { + this.prevOwnPrivateKey = prevOwnPrivateKey; + this.prevOwnPublicKey = prevOwnPublicKey; + this.latestOwnPrivateKey = latestOwnPrivateKey; + this.latestOwnPublicKey = latestOwnPublicKey; + this.latestTheirKey = latestTheirKey; + } + + public SessionState() { + + } + + public byte[] getPrevOwnPrivateKey() { + return prevOwnPrivateKey; + } + + public byte[] getPrevOwnPublicKey() { + return prevOwnPublicKey; + } + + public byte[] getLatestOwnPrivateKey() { + return latestOwnPrivateKey; + } + + public byte[] getLatestOwnPublicKey() { + return latestOwnPublicKey; + } + + public byte[] getLatestTheirKey() { + return latestTheirKey; + } + + public SessionState updateKeys(byte[] theirKey) { + byte[] nPrivate = Curve25519.keyGenPrivate(Crypto.randomBytes(32)); + byte[] nPublic = Curve25519.keyGenPublic(nPrivate); + return new SessionState(latestOwnPrivateKey, latestOwnPublicKey, nPrivate, nPublic, + theirKey); + } + + @Override + public void parse(BserValues values) throws IOException { + prevOwnPrivateKey = values.optBytes(1); + prevOwnPublicKey = values.optBytes(2); + latestOwnPrivateKey = values.optBytes(3); + latestOwnPublicKey = values.optBytes(4); + latestTheirKey = values.optBytes(5); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + if (prevOwnPrivateKey != null) { + writer.writeBytes(1, prevOwnPrivateKey); + } + if (prevOwnPublicKey != null) { + writer.writeBytes(2, prevOwnPublicKey); + } + if (latestOwnPrivateKey != null) { + writer.writeBytes(3, latestOwnPrivateKey); + } + if (latestOwnPublicKey != null) { + writer.writeBytes(4, latestOwnPublicKey); + } + if (latestTheirKey != null) { + writer.writeBytes(5, latestTheirKey); + } + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java index e560c9270b..6487e588b0 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/DestructorActor.java @@ -275,4 +275,4 @@ public NewMessages(Peer peer, List messages) { private static class CheckQueue { } -} +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/ByteStrings.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/ByteStrings.java index 7a811ba57e..ecfe46a6c5 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/ByteStrings.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/ByteStrings.java @@ -104,6 +104,15 @@ public static void write(byte[] dest, int destOffset, byte[] src, int srcOffset, } public static boolean isEquals(byte[] a, byte[] b) { + if (a == null && b == null) { + return true; + } + if (a != null && b == null) { + return false; + } + if (a == null) { + return false; + } if (a.length != b.length) { return false; } From 1deb2e60189f9c45d3db89e8cae195c5ffb6e946 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Sat, 30 Jul 2016 23:55:56 +0300 Subject: [PATCH 62/81] feat(core+runtime): Speed up encryption --- .../main/java/im/actor/core/Messenger.java | 1 + .../encryption/ratchet/EncryptedMsgActor.java | 4 +-- .../encryption/ratchet/EncryptedSession.java | 2 +- .../im/actor/runtime/binary/BinaryOp.java | 18 +++++++++++++ .../im/actor/runtime/binary/BinaryOp.java | 26 +++++++++++++++++++ .../im/actor/runtime/actors/ActorSystem.java | 9 ------- .../runtime/crypto/primitives/util/Pack.java | 12 +++------ .../im/actor/runtime/binary/BinaryOp.java | 12 +++++++++ 8 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/binary/BinaryOp.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-js/src/main/java/im/actor/runtime/binary/BinaryOp.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/template/java/im/actor/runtime/binary/BinaryOp.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java index d6ac630060..95da897a69 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/Messenger.java @@ -108,6 +108,7 @@ public Messenger(@NotNull Configuration configuration) { ActorSystem.system().setTraceInterface(new ActorTrace()); ActorSystem.system().addDispatcher("network_manager", 1); ActorSystem.system().addDispatcher("heavy", 2); + ActorSystem.system().addDispatcher("encrypt"); // Configure dispatcher // timing.section("Dispatcher"); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java index c14c1678e0..162dbb6155 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedMsgActor.java @@ -55,8 +55,8 @@ public void preStart() { private Promise doEncrypt(ApiEncryptedContent message, List uids) throws IOException { // Generate Encryption Keys - byte[] encKey = Crypto.randomBytes(32); - byte[] encKeyExtended = KEY_PRF.calculate(encKey, "ActorPackage", 128); + // byte[] encKey = Crypto.randomBytes(32); + byte[] encKeyExtended = Crypto.randomBytes(128);//KEY_PRF.calculate(encKey, "ActorPackage", 128); // Encrypt Data byte[] encryptedData; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java index 783c0a413c..7f3fc14696 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/encryption/ratchet/EncryptedSession.java @@ -24,7 +24,7 @@ public class EncryptedSession extends ActorInterface { */ public EncryptedSession(PeerSession session, ModuleContext context) { super(system().actorOf("encryption/uid_" + session.getUid() + "/session_" + - RandomUtils.nextRid(), () -> new EncryptedSessionActor(context, session))); + RandomUtils.nextRid(), "encrypt", () -> new EncryptedSessionActor(context, session))); this.session = session; } diff --git a/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/binary/BinaryOp.java b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/binary/BinaryOp.java new file mode 100644 index 0000000000..8c3ca76aa1 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-generic/src/main/java/im/actor/runtime/binary/BinaryOp.java @@ -0,0 +1,18 @@ +package im.actor.runtime.binary; + +public class BinaryOp { + + public static void intToBigEndian(int n, byte[] bs, int off) { + bs[off] = (byte) (n >>> 24); + bs[++off] = (byte) (n >>> 16); + bs[++off] = (byte) (n >>> 8); + bs[++off] = (byte) (n); + } + + public static void intToLittleEndian(int n, byte[] bs, int off) { + bs[off] = (byte) (n); + bs[++off] = (byte) (n >>> 8); + bs[++off] = (byte) (n >>> 16); + bs[++off] = (byte) (n >>> 24); + } +} diff --git a/actor-sdk/sdk-core/runtime/runtime-js/src/main/java/im/actor/runtime/binary/BinaryOp.java b/actor-sdk/sdk-core/runtime/runtime-js/src/main/java/im/actor/runtime/binary/BinaryOp.java new file mode 100644 index 0000000000..8aed903dbc --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-js/src/main/java/im/actor/runtime/binary/BinaryOp.java @@ -0,0 +1,26 @@ +package im.actor.runtime.binary; + +public class BinaryOp { + + public static void intToBigEndian(int n, byte[] bs, int off) { + bs[off] = jsWrap((byte) (n >>> 24)); + bs[++off] = jsWrap((byte) (n >>> 16)); + bs[++off] = jsWrap((byte) (n >>> 8)); + bs[++off] = jsWrap((byte) (n)); + } + + public static void intToLittleEndian(int n, byte[] bs, int off) { + bs[off] = jsWrap((byte) (n)); + bs[++off] = jsWrap((byte) (n >>> 8)); + bs[++off] = jsWrap((byte) (n >>> 16)); + bs[++off] = jsWrap((byte) (n >>> 24)); + } + + public static int jsWrap(int val) { + return val & 0xFFFFFFFF; + } + + public static byte jsWrap(byte val) { + return (byte) (val & 0xFF); + } +} diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/ActorSystem.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/ActorSystem.java index 8a491fee14..01cd75e926 100755 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/ActorSystem.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/actors/ActorSystem.java @@ -139,15 +139,6 @@ public ActorRef actorOf(String path, String dispatcher, ActorCreator creator, Ac .changeSupervisor(supervisor), path); } - public ActorRef actorOf(String path, String dispatcher, final Constructor constructor) { - return actorOf(Props.create(new ActorCreator() { - @Override - public Actor create() { - return constructor.create(); - } - }).changeDispatcher(dispatcher), path); - } - /** * Getting current trace interface for actor system * diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/Pack.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/Pack.java index 42f18ac972..50b050d324 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/Pack.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/util/Pack.java @@ -2,6 +2,8 @@ import com.google.j2objc.annotations.AutoreleasePool; +import im.actor.runtime.binary.BinaryOp; + // Disabling Bounds checks for speeding up calculations /*-[ @@ -45,10 +47,7 @@ public static byte[] intToBigEndian(int n) { } public static void intToBigEndian(int n, byte[] bs, int off) { - bs[off] = jsWrap((byte) (n >>> 24)); - bs[++off] = jsWrap((byte) (n >>> 16)); - bs[++off] = jsWrap((byte) (n >>> 8)); - bs[++off] = jsWrap((byte) (n)); + BinaryOp.intToBigEndian(n, bs, off); } public static byte[] intToBigEndian(int[] ns) { @@ -137,10 +136,7 @@ public static byte[] intToLittleEndian(int n) { } public static void intToLittleEndian(int n, byte[] bs, int off) { - bs[off] = jsWrap((byte) (n)); - bs[++off] = jsWrap((byte) (n >>> 8)); - bs[++off] = jsWrap((byte) (n >>> 16)); - bs[++off] = jsWrap((byte) (n >>> 24)); + BinaryOp.intToLittleEndian(n, bs, off); } public static byte[] intToLittleEndian(int[] ns) { diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/template/java/im/actor/runtime/binary/BinaryOp.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/template/java/im/actor/runtime/binary/BinaryOp.java new file mode 100644 index 0000000000..32a9351909 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/template/java/im/actor/runtime/binary/BinaryOp.java @@ -0,0 +1,12 @@ +package im.actor.runtime.binary; + +public class BinaryOp { + + public static void intToBigEndian(int n, byte[] bs, int off) { + throw new RuntimeException("DUMB"); + } + + public static void intToLittleEndian(int n, byte[] bs, int off) { + throw new RuntimeException("DUMB"); + } +} From a5f83c0dcc4c0951c39a08e7e02fd7f37b1e5324 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 16 Aug 2016 17:14:21 +0300 Subject: [PATCH 63/81] fix(android): Fixing compilation error, upgraded build tools --- actor-sdk/sdk-core-android/android-app/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-maps/build.gradle | 2 +- actor-sdk/sdk-core-android/android-google-push/build.gradle | 2 +- actor-sdk/sdk-core-android/android-sdk/build.gradle | 2 +- .../im/actor/sdk/controllers/conversation/ChatActivity.java | 1 - actor-sdk/sdk-core/core/core-android/build.gradle | 2 +- actor-sdk/sdk-core/runtime/runtime-android/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/actor-sdk/sdk-core-android/android-app/build.gradle b/actor-sdk/sdk-core-android/android-app/build.gradle index 7a49b9cbef..c013ad78f6 100644 --- a/actor-sdk/sdk-core-android/android-app/build.gradle +++ b/actor-sdk/sdk-core-android/android-app/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' } } diff --git a/actor-sdk/sdk-core-android/android-google-maps/build.gradle b/actor-sdk/sdk-core-android/android-google-maps/build.gradle index a9e24fe106..2bf27e6c08 100644 --- a/actor-sdk/sdk-core-android/android-google-maps/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-maps/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-google-push/build.gradle b/actor-sdk/sdk-core-android/android-google-push/build.gradle index 68eec1a276..9e70045e49 100644 --- a/actor-sdk/sdk-core-android/android-google-push/build.gradle +++ b/actor-sdk/sdk-core-android/android-google-push/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-sdk/build.gradle b/actor-sdk/sdk-core-android/android-sdk/build.gradle index 8536b01333..b9ba8e8877 100644 --- a/actor-sdk/sdk-core-android/android-sdk/build.gradle +++ b/actor-sdk/sdk-core-android/android-sdk/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.github.dcendents:android-maven-plugin:1.2' classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3" classpath 'me.tatarka:gradle-retrolambda:3.2.5' diff --git a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java index 235ee7a2e6..7c6e6b53b5 100644 --- a/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java +++ b/actor-sdk/sdk-core-android/android-sdk/src/main/java/im/actor/sdk/controllers/conversation/ChatActivity.java @@ -106,7 +106,6 @@ public void onCreate(Bundle saveInstance) { // if (saveInstance == null) { - Peer peer = Peer.fromUniqueId(getIntent().getExtras().getLong(EXTRA_CHAT_PEER)); chatFragment = ActorSDK.sharedActor().getDelegate().fragmentForChat(peer); if (chatFragment == null) { chatFragment = ChatFragment.create(peer); diff --git a/actor-sdk/sdk-core/core/core-android/build.gradle b/actor-sdk/sdk-core/core/core-android/build.gradle index e5012203aa..390b823d44 100644 --- a/actor-sdk/sdk-core/core/core-android/build.gradle +++ b/actor-sdk/sdk-core/core/core-android/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' } } diff --git a/actor-sdk/sdk-core/runtime/runtime-android/build.gradle b/actor-sdk/sdk-core/runtime/runtime-android/build.gradle index 519b7d21a8..ef3cd88fdd 100644 --- a/actor-sdk/sdk-core/runtime/runtime-android/build.gradle +++ b/actor-sdk/sdk-core/runtime/runtime-android/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.1.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1d10ddc013..64e4da8767 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Apr 09 16:04:30 MSK 2016 +#Tue Aug 16 18:04:31 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From 1fc7a8b2870ded7a98ee477c82880d453a49f0e3 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Tue, 16 Aug 2016 18:53:20 +0300 Subject: [PATCH 64/81] feat(iOS): Start Secret Chat button --- .../sdk-core-ios/ActorApp/AppDelegate.swift | 2 + .../ActorSDK.xcodeproj/project.pbxproj | 4 ++ .../Resources/Base.lproj/Localizable.strings | 2 + .../ic_secret.imageset/Contents.json | 22 +++++++++++ .../ic_secret.imageset/Private 2-64.png | Bin 0 -> 1492 bytes .../ic_secret.imageset/Private 2-96.png | Bin 0 -> 2348 bytes .../Resources/es.lproj/Localizable.strings | 2 + .../Resources/pt.lproj/Localizable.strings | 2 + .../Resources/ru.lproj/Localizable.strings | 3 ++ .../zh-Hans.lproj/Localizable.strings | 2 + .../ActorSDK/Sources/ActorSDK.swift | 3 ++ .../Compose/AAComposeController.swift | 22 ++++++++++- .../Compose/AAComposeSecretController.swift | 35 ++++++++++++++++++ .../Contacts/AAContactsViewController.swift | 16 ++++++++ 14 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Contents.json create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Private 2-64.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Private 2-96.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeSecretController.swift diff --git a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift index a3fad99ce5..5e2a1f96a3 100644 --- a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift @@ -23,6 +23,8 @@ import ActorSDK ActorSDK.sharedActor().enableVideoCalls = true + ActorSDK.sharedActor().enableSecretChats = true + // Setting Development Push Id ActorSDK.sharedActor().apiPushId = 868547 diff --git a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj index f096cb3892..5342b436c9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj +++ b/actor-sdk/sdk-core-ios/ActorSDK.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 060135081C95ED4C00A18C4E /* YYTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 060135021C95ED4C00A18C4E /* YYTransaction.m */; }; 0601BBB21CA4C7DE00AEFA81 /* ElegantPresentations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0601BBB11CA4C7DE00AEFA81 /* ElegantPresentations.swift */; }; 0601BBB41CA4C80D00AEFA81 /* ElegantPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0601BBB31CA4C80D00AEFA81 /* ElegantPresentationController.swift */; }; + 060DD6EE1D636675001A8333 /* AAComposeSecretController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060DD6ED1D636675001A8333 /* AAComposeSecretController.swift */; }; 06129AA61C8359FB0099286B /* CocoaLifecycleRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06129AA51C8359FB0099286B /* CocoaLifecycleRuntime.swift */; }; 06129AA91C8394700099286B /* AAAudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06129AA81C8394700099286B /* AAAudioManager.swift */; }; 06129AAB1C83B80B0099286B /* AAAudioRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06129AAA1C83B80A0099286B /* AAAudioRouter.swift */; }; @@ -387,6 +388,7 @@ 060135021C95ED4C00A18C4E /* YYTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTransaction.m; sourceTree = ""; }; 0601BBB11CA4C7DE00AEFA81 /* ElegantPresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElegantPresentations.swift; sourceTree = ""; }; 0601BBB31CA4C80D00AEFA81 /* ElegantPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElegantPresentationController.swift; sourceTree = ""; }; + 060DD6ED1D636675001A8333 /* AAComposeSecretController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAComposeSecretController.swift; sourceTree = ""; }; 06129AA51C8359FB0099286B /* CocoaLifecycleRuntime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaLifecycleRuntime.swift; sourceTree = ""; }; 06129AA81C8394700099286B /* AAAudioManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAudioManager.swift; sourceTree = ""; }; 06129AAA1C83B80A0099286B /* AAAudioRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAAudioRouter.swift; sourceTree = ""; }; @@ -1288,6 +1290,7 @@ isa = PBXGroup; children = ( 066A52FA1BC52FA8000E606E /* AAComposeController.swift */, + 060DD6ED1D636675001A8333 /* AAComposeSecretController.swift */, 066A52FB1BC52FA8000E606E /* AAGroupCreateViewController.swift */, 066A52FC1BC52FA8000E606E /* AAGroupMembersController.swift */, ); @@ -2132,6 +2135,7 @@ 065974A71BC62B3600B8C7DF /* ViewExtensions.swift in Sources */, 066A52F11BC52B02000E606E /* AASettingsNotificationsViewController.swift in Sources */, 061850FF1C95CBF000C522D5 /* YYTextAsyncLayer.m in Sources */, + 060DD6EE1D636675001A8333 /* AAComposeSecretController.swift in Sources */, 065A06B61C6CEFE00012EA09 /* CocoaWebRTCRuntime.swift in Sources */, 15D35F5A1C20187E00E3717A /* AATimer.m in Sources */, 066A52381BC4EEBA000E606E /* AAHeaderCell.swift in Sources */, diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index e54b96b6de..68ed831027 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -417,6 +417,8 @@ "CreateChannel" = "Create Channel"; +"CreateSecret" = "Start Secret Chat"; + "CreateGroupTitle" = "Create Group"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Contents.json b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Contents.json new file mode 100644 index 0000000000..d8f1eb3398 --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Private 2-64.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Private 2-96.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Private 2-64.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret.imageset/Private 2-64.png new file mode 100644 index 0000000000000000000000000000000000000000..a10fd6d6c5e096dfa1b0567d7b39a64032363716 GIT binary patch literal 1492 zcmV;_1uOcAP)NklJ-t7X6)d1k-2O@I2Vlf_7FPAP+cEFgDyb_Vu zi5)Ybp5D6(06hLO(cCKbT}0ldKPWvSfC-TNUCeG|toth%u>yd{BJw0t5HCU7Rmlxz z5Kec^JTQqlo{pfJUpoT;UVoF4nvP4Hq9}2@3t>)-Ccmh_hC3;=hUbg|fJfe&Y!21$ zt&Gi%C4>k0KtlsI+>9_T0st{dylW|g?sy#~_9@H{8#FAKpS(cMamy$Gcp2kWm=I4K zQgWRL2R8^K0OYhk2ICcY3&K!1cmW{!d@{73F&^qdTSAF(5t1feBb=h4l!I{DGnN+s zjMu(>#(0_}N@lAS=JyxDn6e;H9Y~B#@c@8A)OrLDF-b(o9rj)-hn7p2V4EHC3P4AM zdo?lABji2G#+W?-Q0!~E1BGEhgrd-$o&l+qOiCMJa4uapl+27(u!{x^K+*MDt1syR zK&?kH1Uikjv#QsRMEhSU?rR_dILZS8>>!RKg3joUG(({UfE|PmueQMHDQLa#$sab3 zqbxut*rXjJEGV|+EdcBQcp}=~+8lDh5kgQH#vt7oZlG_&x0<_Z0Jv`Mxjy}Ff$W8x z4*5HMm|-46*o0VgVof1h+h<9MDt(*)5QDnD9|eH@1IILrh6Fau$RQbk-Yl4Cz>uvlC;?!zaJ2N1$gU*% zGM=)i*_0TR^*X#p90TGPM*=N5%7(#K6)FK>6Nfiu?Zg;N{;0LPQ8v*#5XX_#rzX`c zMwbj!%0?0pog(XoT}mV=(*wZBEt^UJXxA1gL6$w?ZK?u#ut{6#$a+LeF)X4NIh7Is zN|#6zGQ*x8Yi7o_&De&`X8>fd+Zg^=8L&|ba|Y}a0QMMkGnHE1+XPt^0!;uQ#6EI> zT_Gw7dp*Z00FELD*rcQY^g?cL08lE}kcgB)uafyzHX!u&3cO>Ns)?3CHe@y$wAGE! zh?2Hv@YME^v2ze~Q`gl9&9uTJO4=U17youu8R<|puWbRndNFj201PjNW|qSv0N_qmd@oh;e$ z*gsIvLI)!@@$lJGtGTZ99M7nsrRztYKeDmk>>GM}e)(A)NJb7vR{-E00~V4m&>hAQ zKGjg{0id;7kH=?@t-S`!UT3M_%@-a3@=)k4T4oKHx!;~>xmso~0C{pdMiA@@)1556 z0N@p3i8`+t{+K|>jVB4Zo>CnF01)z>oMQliV=wxBm7!+}MgdR|NQTM;K67HT@9IEU z^4yWudT0+5+|LMNOay>ZI8bI_L-Y}v(ZmPEF<#FcG2TskJ6RL}#=rxhZJ+JuJ8W3h z0_VVBFz1f;aUHTf-$M+X0YH_b|EK)e_ILG*4S1JF-|S;{c%9hy>?KqgwgLcce=$0L z(?g#Cg&BBiL>Nn7n8+W}7-@@}bFKnFC&5#A$-dZN70?%=Y9)$Nt1niS6kq)!MmP!f zJc$u_kI=|pcj?`m|ITANP#qR6&n>GNP%;iMV1|8duC`nzuI+1cRF&mMuc`v0o~(hx u|95I+OVNEWUAOb{y2oQlQ0z`la5CI}U1c-q8v8SqUn@9Id_gZTod#X;I zQ)jO|kN ?&*1~XXQ~&mB6!8TYofO9sr!I1P=fn0Gup=lbgR+3wXYi^0JcMkEN8~ zPL`*;`6~nPVkzadQVPHSTS`IbuNX)8!wrmYODR8>Qog(WHh%rM$F2kb0)FErYy^(r zBMU$P2tQw6#agZa078HB_5%n<6sYQ}Qpy)MK#pj$*~Xawyj@E9$f@bSC@Mj#DbiZ_ z`#%neQV0D2jKj;(0HERTl~Ud@2p*?eDFcn=Z%?UygWvpUr>*M(i2vPyILw(J2>>+n z$wcsQGt|IOv=9KlQXRC{D**Aqtq&f+m!3TW04M>$Gx%14R?xi96NKIq3SeM`?+@q- zB>;#@b4voAeJL9N28r-n&K1GKy$JryLLccz%Wq(XfRNV5EC3KVf@g5LTt)D@Un3v@ z03f1iF(nxX<$T$hG<|y0w(x-jnGlMDH4rH1!sPDtXb_?;fSKwy4a!05@c|6t9tz{Y z2WVPCaR4D~9FYL@gim~pv%9L+j~q4Bqa&tJ5TXF+3IE+p2X>Gtp*;9AS;9bw0N^PL zzX}}({aFSGy8z&DzgL|9`)%eK#*+tW`m7lgjoJ8f;z2Cor5$?R0f2}BhpSV>uSPnl zhE!_uPG?J*FyT0vO=lIxio(gV6&>vWfJy2&Ka=wb4G9rv8B?^9=LmT-N{fzg){Na< zRsm>rB1`zAdBteJXy#ivJg~soDbFf)Sw)e7cqI6F|C-06=8%q3Br__0WXo*N< z`dJhcp-YQ1J7f<4@Uhf>b0f~tF`!=+_0X zEdaj=UDrb3fV{e827u^mFe%fd1=w%y3RH0zmT_do`{5^F2mDoieLf_j1-cutHacQ8dmNWR|w>HLZ_8 zsTnJG)G7C(GAV10VFZ9SrRcm5nnGevCfq#0MLi2=L#^JK_NaZGAL>%~2msB+q*>`~ zMCmPOM%2Ah$JVl9qd`4)>drq#0AQ{%x4sjN=ss$2SA-V*oybX=4u4t0Xj=LR08-DA z^Jfm*BM_2pa&4DLVV39j)Ngqfij{eyY0Pe^R6O&C_!|o&Mg5O55~{2qGX0RCXh zmS)!N71NOx&A z3Ev9qb*@=*ZD?H?=r&-gL`(2MfJRQ`-NGq$Bf0Z^};sZM!iO*aF8&}}Ih z8?&MSa57)>@eBY$H)dt*v46?{XupYX8lG*KQBW)9-rb!0lmQ6eoC^Rtrrk|TJY@iM z`eC}0#9{%hXvjO|mjtY%gv_(NBR|nPgKU*YtKOwkMAinEPJawWubm$tWNZ4biQ@p#Et-WzPn4Q`b59E zK92>oj^=w;ptO*%1b`h~q`D*6*>~9;P2R?Qvb|BE&ilB|PkR7pMMV5B>#Bm8@UHOr zp;r~u0qdFGE)`utkSl#?UESb5!sWF9I?BwBh(#XFci$4SGUAJUN8cL5tQU)L9VS@; z0683aVbjIr{#SQSy|AeYq_r$)_WoCtsKMe!_u75BgsIr|gtl^A{G} z`Tt5fda9fTz5-YXAm)rO&PXMQQIq$O52_vvr4O{p{2is0koOFO;+1Rq8OZ|9>hC^y~#{wev(w@>Pbc*?J0RbP;;J{$_U7PJO*r##{Wl z{XQB1*^$wEyLugl4kRV?Ugqh!)AZE$nw<%Nq$a>%!XwuhJlgJk7UznlLy3n_&Cy|~ zS0I(10HIQb=asaU_R#c7X-D78a!CrB39Y`$T)xtL_Db4XXMMjs0(;2XLjHLz?sqyPDh_Sl>2P{VF4%O_5k2y0R99%j%I&@ S5t*X^0000) -> () in + + r.height = 56 + + r.closure = { (cell) -> () in + cell.bind("ic_secret", actionTitle: AALocalized("CreateSecret")) + } + + r.selectAction = { () -> Bool in + self.navigateNext(AAComposeSecretController(), removeCurrent: true) + return false + } + } + } + section.custom { (r:AACustomRow) -> () in r.height = 56 @@ -54,10 +71,11 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis } public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer_userWithInt_(contact.uid)) { + let peer = ACPeer_userWithInt_(contact.uid) + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(peer) { navigateDetail(customController) } else { - navigateDetail(ConversationViewController(peer: ACPeer_userWithInt_(contact.uid))) + navigateDetail(ConversationViewController(peer: peer)) } return false } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeSecretController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeSecretController.swift new file mode 100644 index 0000000000..1a66acc5de --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeSecretController.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2014-2016 Actor LLC. +// + +import UIKit + +public class AAComposeSecretController: AAContactsListContentController, AAContactsListContentControllerDelegate { + + public override init() { + super.init() + + self.delegate = self + self.isSearchAutoHide = false + + self.navigationItem.title = AALocalized("ComposeTitle") + + if AADevice.isiPad { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: AALocalized("NavigationCancel"), style: UIBarButtonItemStyle.Plain, target: self, action: #selector(AAViewController.dismiss)) + } + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func contactDidTap(controller: AAContactsListContentController, contact: ACContact) -> Bool { + let peer = ACPeer_secretWithInt_(contact.uid) + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(peer) { + navigateDetail(customController) + } else { + navigateDetail(ConversationViewController(peer: peer)) + } + return false + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift index 390efcae58..f86ea1e2fa 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift @@ -47,6 +47,22 @@ public class AAContactsViewController: AAContactsListContentController, AAContac public func willAddContacts(controller: AAContactsListContentController, section: AAManagedSection) { + if ActorSDK.sharedActor().enableSecretChats { + section.custom { (r:AACustomRow) -> () in + + r.height = 56 + + r.closure = { (cell) -> () in + cell.bind("ic_secret", actionTitle: AALocalized("CreateSecret")) + } + + r.selectAction = { () -> Bool in + self.navigateNext(AAComposeSecretController(), removeCurrent: false) + return false + } + } + } + section.custom { (r: AACustomRow) -> () in r.height = 56 From 204d3fae1058ac25c8cd592c374719702e5771da Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 17 Aug 2016 11:44:19 +0300 Subject: [PATCH 65/81] feat(iOS): Setting self-destruct timer, better navigation controller titles --- .../Resources/Base.lproj/Localizable.strings | 3 +- .../ic_secret_title.imageset/Contents.json | 22 +++++++ .../Lock Filled-48.png | Bin 0 -> 628 bytes .../Lock Filled-64.png | Bin 0 -> 817 bytes .../ic_timer_22.imageset/Contents.json | 22 +++++++ .../ic_timer_22.imageset/Timer-44.png | Bin 0 -> 985 bytes .../ic_timer_22.imageset/Timer-66.png | Bin 0 -> 1433 bytes .../Resources/es.lproj/Localizable.strings | 5 +- .../Resources/pt.lproj/Localizable.strings | 5 +- .../Resources/ru.lproj/Localizable.strings | 4 +- .../zh-Hans.lproj/Localizable.strings | 4 +- .../ActorSDK/Sources/ActorStyle.swift | 2 + .../Compose/AAComposeController.swift | 2 +- .../Contacts/AAContactsViewController.swift | 2 +- .../ConversationViewController.swift | 60 +++++++++++++++--- .../User/AAUserViewController.swift | 23 ++++++- 16 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Contents.json create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Lock Filled-48.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Lock Filled-64.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Contents.json create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-44.png create mode 100644 actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-66.png diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings index 68ed831027..4d59fd807e 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Base.lproj/Localizable.strings @@ -417,8 +417,6 @@ "CreateChannel" = "Create Channel"; -"CreateSecret" = "Start Secret Chat"; - "CreateGroupTitle" = "Create Group"; @@ -696,6 +694,7 @@ "ActionUnmute" = "Unmute"; +"ActionStartSecret" = "Start Secret Chat"; "ActionDelete" = "Delete"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Contents.json b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Contents.json new file mode 100644 index 0000000000..41fcb0d4c3 --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Lock Filled-48.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Lock Filled-64.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Lock Filled-48.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_secret_title.imageset/Lock Filled-48.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf6abbf27c21feb6ec338fe603cb5ce43cb066d GIT binary patch literal 628 zcmV-)0*n2LP)pMS}{ zw{NqXY)mk6n~WSk8i7Q?WdPd%HUTUozdZuD18@m|pIyvL0fIdd;4!c#`LdLty#R0w z;Kjhn2vNfF}TY^i+WL0U}Y7*pr7y zf{LijLV)r9U;q)wQ;Bu$y08#z z6o-dCjCJdj~Os+3|e|VlDG%sOPlv*_emD07BM$nOl@hUWgXxj^L0_q z?oq36P8Le0Z0?yl*0%;&9j)60rwIG5_N)*>BQUFiR-TbaAXU(nCAa+EIAHo0hcA+mUJ_{vhrDXAbd!#capvx0?Hu( zp!jo1cXlfDR?_3yAZ7zV@kfQ?Khr9IE8 z@V6_+ctFV3;BE=t0A3~YWzkR`Q}$@D4Fujl@EDXaJON;`_;E3|vijK_hzEOJ zYEuSJ04NUx+La0Ei@> zT)-4T!#>%feaskeBklI`gAThr&1ElpfY}c-nZGt4^Yi-H0kf+dW~X;E^|fc>mHJkz(-r_5;a5^>k1W-|8~_?9!vai>-(z+w z^$i!O)e8{=A_mk6L=cQ1`1`oaJF-)(#ff?eu6y99Ry(pYHmUcZg3Epm*>g&00000NkvXXu0mjf;reX~ literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Contents.json b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Contents.json new file mode 100644 index 0000000000..d69d800e77 --- /dev/null +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Timer-44.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Timer-66.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-44.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-44.png new file mode 100644 index 0000000000000000000000000000000000000000..9c70cb528a7bd0439cc2a1b45000bbd13d2427b4 GIT binary patch literal 985 zcmV;~119{5P)kipVthzThlqy*MR>@x|V47Uebp!i7E(N1GfJM zlCDd76h5BBPf0H%eJVwLEWlGd0Dms&et3ago=Rc@S-UL21_krT<7Ut~ptEdWHedsX z?bqSUD@kW_H0J>0;FhGvOUsma?@d=j$lb6B(KoEa_JPegd?V?Zqzg$nsH%28>>xgjtkq zG>1KQGs{)drVdrHkNEbMEUpIvb%EjIx(WIUM*rLACxPf4nR2mx*gjn8dLn6W7 zgj@iVQOGPcg-Q&L>Og&L&@tF#m6tnZIGg|)O>cRWhbS235|Y2O2jZn;k(u7=aOoAL zy5l|?hO_na5?~ynY1rn>R{UE;p+vRVH=tF#)kno%fUQ@&P5cKn#sZ>J|F-igHhfVUo6%FHgE=p5$@w+ z1Mrk}+D?s4#VXC+lCG9SPLcrf2PUVNDzNCEZUI;gG9LUtCfeM{$D@aF00000NkvXX Hu0mjfsYcL! literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-66.png b/actor-sdk/sdk-core-ios/ActorSDK/Resources/Images.xcassets/ic_timer_22.imageset/Timer-66.png new file mode 100644 index 0000000000000000000000000000000000000000..d25191f8c122ac31dc5a8783baac7dbbadc131fe GIT binary patch literal 1433 zcmV;K1!nq*P)$#04jh~08{`}0I2{{0Z;)+2T%dbyV2gPW!YNUl0TrC zn@ldYwEMI_OL9(R-S$94z8DcMMTE~U5&0fIKf-rSB|T}{d%p7+#LykU`6?nG!_R!r zg;#6|1T%kKL~dqu0Lo_(c~{d?wglp#nQegF6%i~(e_l%f`XC}_HGpgtM9L^0i->+= zD6fNs5L+g-J%LaKu@-=!ymcd7=4Ei3ViI;B5y^+S_r=bq^Rv31j=F|DF4K0 zPUG;M}DHHeA2BAZcR@GHpFmfPJ{%3%|AmnVKb*RTcUK$))GVvgIIBINcb@jMB?Y+Mq&fGyLQ9vfK5Y4S70j;0*mGuMPmkK`HR&3 zLm*K4Q@G(OScN+k6$r|uYGs&miv|peEzKsUsiG?df*YA7B5TWQw%4-b0^!UoAsG@U3pNK#{e`Zr9G zY_x?m+i?#-*lbbr!OFT@Y*-XGSjU>J;{|}Q0#D5rLkd}6jIUWc&DvOY3J6b2&^d~05yX}oLbqtv>W2C!fa01< z22u@}7l6==aS^uIT7aNW1wPh47BRY{R#`Ze=@JlX9<4#dRp*dSH8WR^+i`N11c6Xv zDb>~*hq8Bpwi*a@v-vxv>zrquQg*9?h!<32;a78wW8`ccEUm(1CuSqw-v1pc-KEl~ zTEvx2pQG#kr8eidUN>)9SktTMZu!vV zkY}|^dJkQ#9>Z7D&^d7LF`WB)Yk1Di_Iz1qF{;&4-PQpOF4fgsyxlqkG zFr&u;7_Ri7$b1o(>7nSL2qMKx@U@rtmr=Vrime-x2Vgvh$&zP|*3f3+n6XQ^50mM5 zw&-B*gAVrCI31`*Ba4q0s=_SR=ov*GQrLIcQ{$MB9$y^Yx5@2n#k<;jf?*+K560y{ zF#GZP;vQgF2&{v$95}jz<^hp{vDG6%STGPYCb%G=pch06#+D9!sC)!QJ^zI}0K~PI zS^`3A413613oxSblB>@)U}FUwp0}@05EyX>w@jrVQmK{iunq)ab7slL%PEX&PXSIk z3^x@7%3^lZ8u2TKO8`j$z!lgRnRI$@4hVG>2G>LEY7P3@G8W?UQHL3ky#T-j#OFXc zkHw_8T_GNCE(q-p!LS7T%AtiqT;YT7dcmML#T>rQCv^xG(2P6#?SNV!ED$V&SsQ!V zUH?5o$`DX^?Bp*lfcawqvjM08wLw_Lm_>b;JNF*|a-N~e0|3ja-dcjN!ZO2{x_(G` nd=~>k;rJhE;7ZkIeu&^-C<|uIbO_H800000NkvXXu0mjf2|kNr literal 0 HcmV?d00001 diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings index 01ff2f9956..d3ab0fc7e9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/es.lproj/Localizable.strings @@ -415,8 +415,6 @@ "CreateChannel" = "Crear canal"; -"CreateSecret" = "Start Secret Chat"; - "CreateGroupTitle" = "Crear grupo"; @@ -689,6 +687,9 @@ "ActionUnmute" = "No silenciado"; +"ActionStartSecret" = "Start Secret Chat"; + + "ActionDelete" = "Eliminar"; "ActionDeleteMessage" = "¿Seguro desea borrar el chat?"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings index 7c9de18082..c35d2d0fc4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/pt.lproj/Localizable.strings @@ -399,8 +399,6 @@ "CreateChannel" = "Create Channel"; -"CreateSecret" = "Start Secret Chat"; - "CreateGroupTitle" = "Criar Grupo"; @@ -671,6 +669,9 @@ "ActionUnmute" = "Unmute"; +"ActionStartSecret" = "Start Secret Chat"; + + "ActionDelete" = "Delete"; "ActionDeleteMessage" = "Are you sure want to delete chat?"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings index 4c3c7cdc09..bfc6510fb0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/ru.lproj/Localizable.strings @@ -427,8 +427,6 @@ "CreateChannel" = "Новый канал"; -"CreateSecret" = "Новый секретный чат"; - "CreateGroupTitle" = "Новая группа"; @@ -692,6 +690,8 @@ "ActionUnmute" = "Включить оповещения"; +"ActionStartSecret" = "Новый секретный чат"; + "ActionDelete" = "Удалить"; "ActionDeleteChannel" = "Удалить канал"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings index 248b8562d1..81f3d7cfa4 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings +++ b/actor-sdk/sdk-core-ios/ActorSDK/Resources/zh-Hans.lproj/Localizable.strings @@ -398,8 +398,6 @@ "CreateChannel" = "Create Channel"; -"CreateSecret" = "Start Secret Chat"; - "CreateGroupTitle" = "创建群组"; @@ -676,6 +674,8 @@ "ActionUnmute" = "Unmute"; +"ActionStartSecret" = "Start Secret Chat"; + "ActionDelete" = "Delete"; diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift index c9cb033d3d..5e7bb6efef 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift @@ -56,6 +56,8 @@ public class ActorStyle { public var navigationTintColor: UIColor = UIColor(rgb: 0x5085CB) /// Navigation Bar title color public var navigationTitleColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) + /// Navigation Bar title secret color + public var navigationTitleSecretColor: UIColor = UIColor(rgb: 0x32914d) /// Navigation Bar subtitle color, default is 0.8 alhpa of navigationTitleColor public var navigationSubtitleColor: UIColor { get { return _navigationSubtitleColor != nil ? _navigationSubtitleColor! : navigationTitleColor.alpha(0.8) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift index 80033c4c5d..4ff995c320 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Compose/AAComposeController.swift @@ -31,7 +31,7 @@ public class AAComposeController: AAContactsListContentController, AAContactsLis r.height = 56 r.closure = { (cell) -> () in - cell.bind("ic_secret", actionTitle: AALocalized("CreateSecret")) + cell.bind("ic_secret", actionTitle: AALocalized("ActionStartSecret")) } r.selectAction = { () -> Bool in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift index f86ea1e2fa..1a5f52f6ae 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Contacts/AAContactsViewController.swift @@ -53,7 +53,7 @@ public class AAContactsViewController: AAContactsListContentController, AAContac r.height = 56 r.closure = { (cell) -> () in - cell.bind("ic_secret", actionTitle: AALocalized("CreateSecret")) + cell.bind("ic_secret", actionTitle: AALocalized("ActionStartSecret")) } r.selectAction = { () -> Bool in diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift index 09407d4611..a57941fa6f 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Conversation/ConversationViewController.swift @@ -205,7 +205,11 @@ public class ConversationViewController: titleView.textAlignment = NSTextAlignment.Center titleView.lineBreakMode = NSLineBreakMode.ByTruncatingTail titleView.autoresizingMask = UIViewAutoresizing.FlexibleWidth - titleView.textColor = appStyle.navigationTitleColor + if peer.isPrivateSecret { + titleView.textColor = appStyle.navigationTitleSecretColor + } else { + titleView.textColor = appStyle.navigationTitleColor + } subtitleView.font = UIFont.systemFontOfSize(13) subtitleView.adjustsFontSizeToFitWidth = true @@ -234,21 +238,26 @@ public class ConversationViewController: } if (ActorSDK.sharedActor().enableCalls && !isBot && peer.isPrivate) { if ActorSDK.sharedActor().enableVideoCalls { - let callButtonView = AACallButton(image: UIImage.bundled("ic_call_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) + let callButtonView = AABarButton(image: UIImage.bundled("ic_call_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) callButtonView.viewDidTap = onCallTap let callButtonItem = UIBarButtonItem(customView: callButtonView) - let videoCallButtonView = AACallButton(image: UIImage.bundled("ic_video_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) + let videoCallButtonView = AABarButton(image: UIImage.bundled("ic_video_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) videoCallButtonView.viewDidTap = onVideoCallTap let callVideoButtonItem = UIBarButtonItem(customView: videoCallButtonView) self.navigationItem.rightBarButtonItems = [barItem, callVideoButtonItem, callButtonItem] } else { - let callButtonView = AACallButton(image: UIImage.bundled("ic_call_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) + let callButtonView = AABarButton(image: UIImage.bundled("ic_call_outline_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) callButtonView.viewDidTap = onCallTap let callButtonItem = UIBarButtonItem(customView: callButtonView) self.navigationItem.rightBarButtonItems = [barItem, callButtonItem] } + } else if peer.isPrivateSecret { + let timerButtonView = AABarButton(image: UIImage.bundled("ic_timer_22")?.tintImage(ActorSDK.sharedActor().style.navigationTintColor)) + timerButtonView.viewDidTap = onTimerTap + let callButtonItem = UIBarButtonItem(customView: timerButtonView) + self.navigationItem.rightBarButtonItems = [barItem, callButtonItem] } else { self.navigationItem.rightBarButtonItems = [barItem] } @@ -306,8 +315,22 @@ public class ConversationViewController: let nameModel = user.getNameModel() // let blockStatus = user.isBlockedModel().get().booleanValue() - binder.bind(nameModel, closure: { (value: NSString?) -> () in - self.titleView.text = String(value!) + binder.bind(nameModel, closure: { (value: String!) -> () in + + if self.peer.isPrivateSecret { + + let attachment = NSTextAttachment() + attachment.image = UIImage.bundled("ic_secret_title")? + .tintImage(self.appStyle.navigationTitleSecretColor) + attachment.bounds = CGRectMake(0, 0, 12, 12) + + let title = NSMutableAttributedString() + title.appendAttributedString(NSAttributedString(attachment: attachment)) + title.appendAttributedString(NSAttributedString(string: " \(value)")) + self.titleView.attributedText = title + } else { + self.titleView.text = value + } self.navigationView.sizeToFit() }) binder.bind(user.getAvatarModel(), closure: { (value: ACAvatar?) -> () in @@ -514,7 +537,7 @@ public class ConversationViewController: func onAvatarTap() { let id = Int(peer.peerId) var controller: AAViewController! - if (peer.peerType.ordinal() == ACPeerType.PRIVATE().ordinal()) { + if (peer.isPrivate || peer.isPrivateSecret) { controller = ActorSDK.sharedActor().delegate.actorControllerForUser(id) if controller == nil { controller = AAUserViewController(uid: id) @@ -555,6 +578,26 @@ public class ConversationViewController: } } + func onTimerTap() { + alertSheet { (a) in + a.action("disable", closure: { + self.executePromise(Actor.setSecretChatTimerWithUid(self.peer.peerId, withTimeout: nil)) + }) + a.action("1 sec", closure: { + self.executePromise(Actor.setSecretChatTimerWithUid(self.peer.peerId, withTimeout: JavaLangInteger(int: 1000))) + }) + a.action("2 sec", closure: { + self.executePromise(Actor.setSecretChatTimerWithUid(self.peer.peerId, withTimeout: JavaLangInteger(int: 2000))) + }) + a.action("3 sec", closure: { + self.executePromise(Actor.setSecretChatTimerWithUid(self.peer.peerId, withTimeout: JavaLangInteger(int: 3000))) + }) + a.action("1 minute", closure: { + self.executePromise(Actor.setSecretChatTimerWithUid(self.peer.peerId, withTimeout: JavaLangInteger(int: 60000))) + }) + } + } + //////////////////////////////////////////////////////////// // MARK: - Text bar actions //////////////////////////////////////////////////////////// @@ -1105,7 +1148,8 @@ class AABarAvatarView : AAAvatarView { } } -class AACallButton: UIImageView { +class AABarButton: UIImageView { + override init(image: UIImage?) { super.init(image: image) } diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift index d322040fd1..6a5252db67 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/User/AAUserViewController.swift @@ -64,8 +64,8 @@ class AAUserViewController: AAContentTableController { } } + // Profile: Starting Voice Call if (ActorSDK.sharedActor().enableCalls && !self.isBot) { - // Profile: Starting Voice Call s.action("CallsStartAudio") { (r) -> () in r.selectAction = { () -> Bool in self.execute(Actor.doCallWithUid(jint(self.uid))) @@ -77,15 +77,32 @@ class AAUserViewController: AAContentTableController { // Profile: Send messages s.action("ProfileSendMessage") { (r) -> () in r.selectAction = { () -> Bool in - if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(ACPeer.userWithInt(jint(self.uid))) { + let peer = ACPeer.userWithInt(jint(self.uid)) + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(peer) { self.navigateDetail(customController) } else { - self.navigateDetail(ConversationViewController(peer: ACPeer.userWithInt(jint(self.uid)))) + self.navigateDetail(ConversationViewController(peer: peer)) } self.popover?.dismissPopoverAnimated(true) return false } } + + // Profile: Starting Secret Chat + if (ActorSDK.sharedActor().enableSecretChats && !self.isBot) { + s.action("ActionStartSecret") { (r) -> () in + r.selectAction = { () -> Bool in + let peer = ACPeer.secretWithInt(jint(self.uid)) + if let customController = ActorSDK.sharedActor().delegate.actorControllerForConversation(peer) { + self.navigateDetail(customController) + } else { + self.navigateDetail(ConversationViewController(peer: peer)) + } + self.popover?.dismissPopoverAnimated(true) + return false + } + } + } } let nick = self.user.getNickModel().get() From d763ab7b3cca1ebede04df180c2ffca3944859af Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 17 Aug 2016 18:57:00 +0300 Subject: [PATCH 66/81] fix(iOS): Fixing database multithreading --- .../Providers/CocoaStorageRuntime.swift | 18 +- .../Providers/Storage/FMDBKeyValue.swift | 87 ++-- .../Providers/Storage/FMDBList.swift | 454 +++++++++--------- 3 files changed, 283 insertions(+), 276 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift index 18c18f7bbc..2e7c7fc71c 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaStorageRuntime.swift @@ -6,12 +6,12 @@ import Foundation @objc class CocoaStorageRuntime : NSObject, ARStorageRuntime { - let dbPath: String; + let dbQueue: FMDatabaseQueue let preferences = UDPreferencesStorage() override init() { - self.dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, - .UserDomainMask, true)[0].asNS.stringByAppendingPathComponent("actor.db") + dbQueue = FMDatabaseQueue(path: NSSearchPathForDirectoriesInDomains(.DocumentDirectory, + .UserDomainMask, true)[0].asNS.stringByAppendingPathComponent("actor.db")) } func createPreferencesStorage() -> ARPreferencesStorage! { @@ -19,19 +19,17 @@ import Foundation } func createKeyValueWithName(name: String!) -> ARKeyValueStorage! { - return FMDBKeyValue(databasePath: dbPath, tableName: name) + return FMDBKeyValue(dbQueue: dbQueue, tableName: name) } func createListWithName(name: String!) -> ARListStorage! { - return FMDBList(databasePath: dbPath, tableName: name) + return FMDBList(dbQueue: dbQueue, tableName: name) } func resetStorage() { preferences.clear() - - let db = FMDatabase(path: dbPath) - db.open() - db.executeStatements("select 'drop table ' || name || ';' from sqlite_master where type = 'table';") - db.close() + dbQueue.inDatabase { (db) in + db.executeStatements("select 'drop table ' || name || ';' from sqlite_master where type = 'table';") + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift index 982e1ab713..ba302befd9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBKeyValue.swift @@ -6,9 +6,8 @@ import Foundation @objc class FMDBKeyValue: NSObject, ARKeyValueStorage { - var db :FMDatabase! + let dbQueue: FMDatabaseQueue - let databasePath: String let tableName: String let queryCreate: String @@ -21,8 +20,8 @@ import Foundation var isTableChecked: Bool = false - init(databasePath: String, tableName: String) { - self.databasePath = databasePath + init(dbQueue: FMDatabaseQueue, tableName: String) { + self.dbQueue = dbQueue self.tableName = tableName // Queries @@ -46,72 +45,76 @@ import Foundation } isTableChecked = true - self.db = FMDatabase(path: databasePath) - self.db.open() - if (!db.tableExists(tableName)) { - db.executeUpdate(queryCreate) + dbQueue.inDatabase { (db) in + if (!db.tableExists(self.tableName)) { + db.executeUpdate(self.queryCreate) + } } } func addOrUpdateItems(values: JavaUtilList!) { checkTable() - db.beginTransaction() - for i in 0.. IOSByteArray! { checkTable() - let result = db.dataForQuery(queryItem, key.toNSNumber()) - if (result == nil) { - return nil + var res: IOSByteArray! = nil + dbQueue.inDatabase { (db) in + let result = db.dataForQuery(self.queryItem, key.toNSNumber()) + if (result == nil) { + return + } + res = result.toJavaBytes() } - return result.toJavaBytes() + return res } func loadAllItems() -> JavaUtilList! { checkTable() let res = JavaUtilArrayList() - - if let result = db.executeQuery(queryAll) { - while(result.next()) { - res.addWithId(ARKeyValueRecord(key: jlong(result.longLongIntForColumn("ID")), withData: result.dataForColumn("BYTES").toJavaBytes())) + dbQueue.inDatabase { (db) in + if let result = db.executeQuery(self.queryAll) { + while(result.next()) { + res.addWithId(ARKeyValueRecord(key: jlong(result.longLongIntForColumn("ID")), withData: result.dataForColumn("BYTES").toJavaBytes())) + } } } - return res } @@ -125,22 +128,22 @@ import Foundation } let res = JavaUtilArrayList() - - if let result = db.executeQuery(queryItems, ids) { - while(result.next()) { - // TODO: Optimize lookup - res.addWithId(ARKeyValueRecord(key: jlong(result.longLongIntForColumn("ID")), withData: result.dataForColumn("BYTES").toJavaBytes())) + dbQueue.inDatabase { (db) in + if let result = db.executeQuery(self.queryItems, ids) { + while(result.next()) { + // TODO: Optimize lookup + res.addWithId(ARKeyValueRecord(key: jlong(result.longLongIntForColumn("ID")), withData: result.dataForColumn("BYTES").toJavaBytes())) + } } } - return res } func clear() { checkTable() - db.beginTransaction() - db.executeUpdate(queryDeleteAll) - db.commit() + dbQueue.inTransaction { (db, rollout) in + db.executeUpdate(self.queryDeleteAll) + } } } \ No newline at end of file diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift index c775773f2b..0a1439bc8d 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/Storage/FMDBList.swift @@ -6,73 +6,72 @@ import Foundation class FMDBList : NSObject, ARListStorageDisplayEx { - var db :FMDatabase? = nil; - var isTableChecked: Bool = false; + let dbQueue: FMDatabaseQueue + let tableName: String - let databasePath: String; - let tableName: String; - - let queryCreate: String; - let queryCreateIndex: String; - let queryCreateFilter: String; + var isTableChecked: Bool = false + + let queryCreate: String + let queryCreateIndex: String + let queryCreateFilter: String - let queryCount: String; + let queryCount: String let queryEmpty: String - let queryAdd: String; - let queryItem: String; + let queryAdd: String + let queryItem: String - let queryDelete: String; - let queryDeleteAll: String; + let queryDelete: String + let queryDeleteAll: String - let queryForwardFirst: String; - let queryForwardMore: String; + let queryForwardFirst: String + let queryForwardMore: String - let queryForwardFilterFirst: String; - let queryForwardFilterMore: String; + let queryForwardFilterFirst: String + let queryForwardFilterMore: String - let queryBackwardFirst: String; - let queryBackwardMore: String; - let queryBackwardFilterFirst: String; - let queryBackwardFilterMore: String; + let queryBackwardFirst: String + let queryBackwardMore: String + let queryBackwardFilterFirst: String + let queryBackwardFilterMore: String - let queryCenterBackward: String; - let queryCenterForward: String; + let queryCenterBackward: String + let queryCenterForward: String - init (databasePath: String, tableName: String){ - self.databasePath = databasePath - self.tableName = tableName; + init (dbQueue: FMDatabaseQueue, tableName: String){ + self.dbQueue = dbQueue + self.tableName = tableName self.queryCreate = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + // "\"ID\" INTEGER NOT NULL," + // 0: id "\"SORT_KEY\" INTEGER NOT NULL," + // 1: sortKey "\"QUERY\" TEXT," + // 2: query "\"BYTES\" BLOB NOT NULL," + // 3: bytes - "PRIMARY KEY(\"ID\"));"; + "PRIMARY KEY(\"ID\"));" self.queryCreateIndex = "CREATE INDEX IF NOT EXISTS IDX_ID_SORT ON " + tableName + " (\"SORT_KEY\");" self.queryCreateFilter = "CREATE INDEX IF NOT EXISTS IDX_ID_QUERY_SORT ON " + tableName + " (\"QUERY\", \"SORT_KEY\");" - self.queryCount = "SELECT COUNT(*) FROM " + tableName + ";"; + self.queryCount = "SELECT COUNT(*) FROM " + tableName + ";" self.queryEmpty = "EXISTS (SELECT * FROM " + tableName + ");" - self.queryAdd = "REPLACE INTO " + tableName + " (\"ID\",\"QUERY\",\"SORT_KEY\",\"BYTES\") VALUES (?,?,?,?)"; - self.queryItem = "SELECT \"ID\",\"QUERY\",\"SORT_KEY\",\"BYTES\" FROM " + tableName + " WHERE \"ID\" = ?;"; + self.queryAdd = "REPLACE INTO " + tableName + " (\"ID\",\"QUERY\",\"SORT_KEY\",\"BYTES\") VALUES (?,?,?,?)" + self.queryItem = "SELECT \"ID\",\"QUERY\",\"SORT_KEY\",\"BYTES\" FROM " + tableName + " WHERE \"ID\" = ?;" - self.queryDeleteAll = "DELETE FROM " + tableName + ";"; - self.queryDelete = "DELETE FROM " + tableName + " WHERE \"ID\"= ?;"; + self.queryDeleteAll = "DELETE FROM " + tableName + ";" + self.queryDelete = "DELETE FROM " + tableName + " WHERE \"ID\"= ?;" - self.queryForwardFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " ORDER BY SORT_KEY DESC LIMIT ?"; - self.queryForwardMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" < ? ORDER BY SORT_KEY DESC LIMIT ?"; + self.queryForwardFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " ORDER BY SORT_KEY DESC LIMIT ?" + self.queryForwardMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" < ? ORDER BY SORT_KEY DESC LIMIT ?" - self.queryBackwardFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " ORDER BY SORT_KEY ASC LIMIT ?"; - self.queryBackwardMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" > ? ORDER BY SORT_KEY ASC LIMIT ?"; + self.queryBackwardFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " ORDER BY SORT_KEY ASC LIMIT ?" + self.queryBackwardMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" > ? ORDER BY SORT_KEY ASC LIMIT ?" self.queryCenterForward = queryForwardMore - self.queryCenterBackward = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" >= ? ORDER BY SORT_KEY ASC LIMIT ?"; + self.queryCenterBackward = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"SORT_KEY\" >= ? ORDER BY SORT_KEY ASC LIMIT ?" - self.queryForwardFilterFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"QUERY\" LIKE ? OR \"QUERY\" LIKE ? ORDER BY SORT_KEY DESC LIMIT ?"; - self.queryForwardFilterMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE (\"QUERY\" LIKE ? OR \"QUERY\" LIKE ?) AND \"SORT_KEY\" < ? ORDER BY SORT_KEY DESC LIMIT ?"; + self.queryForwardFilterFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"QUERY\" LIKE ? OR \"QUERY\" LIKE ? ORDER BY SORT_KEY DESC LIMIT ?" + self.queryForwardFilterMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE (\"QUERY\" LIKE ? OR \"QUERY\" LIKE ?) AND \"SORT_KEY\" < ? ORDER BY SORT_KEY DESC LIMIT ?" - self.queryBackwardFilterFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"QUERY\" LIKE ? OR \"QUERY\" LIKE ? ORDER BY SORT_KEY ASC LIMIT ?"; - self.queryBackwardFilterMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE (\"QUERY\" LIKE ? OR \"QUERY\" LIKE ?) AND \"SORT_KEY\" > ? ORDER BY SORT_KEY ASC LIMIT ?"; + self.queryBackwardFilterFirst = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE \"QUERY\" LIKE ? OR \"QUERY\" LIKE ? ORDER BY SORT_KEY ASC LIMIT ?" + self.queryBackwardFilterMore = "SELECT \"ID\", \"QUERY\",\"SORT_KEY\", \"BYTES\" FROM " + tableName + " WHERE (\"QUERY\" LIKE ? OR \"QUERY\" LIKE ?) AND \"SORT_KEY\" > ? ORDER BY SORT_KEY ASC LIMIT ?" } func checkTable() { @@ -81,122 +80,126 @@ class FMDBList : NSObject, ARListStorageDisplayEx { } isTableChecked = true; - self.db = FMDatabase(path: databasePath) - self.db!.open() - if (!db!.tableExists(tableName)) { - db!.executeUpdate(queryCreate) - db!.executeUpdate(queryCreateIndex) - db!.executeUpdate(queryCreateFilter) + dbQueue.inDatabase { (db) in + if (!db!.tableExists(self.tableName)) { + db!.executeUpdate(self.queryCreate) + db!.executeUpdate(self.queryCreateIndex) + db!.executeUpdate(self.queryCreateFilter) + } } } func updateOrAddWithValue(valueContainer: ARListEngineRecord!) { - checkTable(); + checkTable() let start = NSDate() - // db!.beginTransaction() - db!.executeUpdate(queryAdd, withArgumentsInArray: [valueContainer.getKey().toNSNumber(), valueContainer.dbQuery(), valueContainer.getOrder().toNSNumber(), - valueContainer.getData().toNSData()]) - // db!.commit() + dbQueue.inDatabase { (db) in + db.executeUpdate(self.queryAdd, withArgumentsInArray: [valueContainer.getKey().toNSNumber(), valueContainer.dbQuery(), valueContainer.getOrder().toNSNumber(), + valueContainer.getData().toNSData()]) + + } log("updateOrAddWithValue \(tableName): \(valueContainer.getData().length()) in \(Int((NSDate().timeIntervalSinceDate(start)*1000)))") } func updateOrAddWithList(items: JavaUtilList!) { - checkTable(); + checkTable() - db!.beginTransaction() - for i in 0.. jint { - checkTable(); + checkTable() - let result = db!.executeQuery(queryCount) - if (result == nil) { - return 0; - } - if (result!.next()) { - let res = jint(result!.intForColumnIndex(0)) - result?.close() - return res - } else { - result?.close() + var res: jint = 0 + dbQueue.inDatabase { (db) in + let result = db.executeQuery(self.queryCount) + if (result == nil) { + return; + } + if (result!.next()) { + res = jint(result!.intForColumnIndex(0)) + result?.close() + } else { + result?.close() + } } - - return 0; + return res; } func isEmpty() -> Bool { - checkTable(); + checkTable() - let result = db!.executeQuery(queryEmpty) - if (result == nil) { - return false; - } - if (result!.next()) { - let res = result!.intForColumnIndex(0) - result?.close() - return res > 0 - } else { + var res: Bool = false + dbQueue.inDatabase { (db) in + let result = db!.executeQuery(self.queryEmpty) + if (result == nil) { + return + } + if (result!.next()) { + res = result!.intForColumnIndex(0) > 0 + } result?.close() } - - return false; + return res } func clear() { - checkTable(); + checkTable() - db!.beginTransaction() - db!.executeUpdate(queryDeleteAll); - db!.commit() + dbQueue.inTransaction { (db, rollout) in + db.executeUpdate(self.queryDeleteAll) + } } func loadItemWithKey(key: jlong) -> ARListEngineRecord! { - checkTable(); + checkTable() - let result = db!.executeQuery(queryItem, key.toNSNumber()); - if (result == nil) { - return nil - } - if (result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); - if (query is NSNull){ - query = nil + var res: ARListEngineRecord! = nil + + dbQueue.inDatabase { (db) in + let result = db!.executeQuery(self.queryItem, key.toNSNumber()) + if (result == nil) { + return + } + if (result!.next()) { + var query: AnyObject! = result!.objectForColumnName("QUERY") + if (query is NSNull){ + query = nil + } + res = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) } - let res = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - result?.close() - return res; - } else { result?.close() - return nil } + + + return res } func loadAllItems() -> JavaUtilList! { @@ -206,152 +209,155 @@ class FMDBList : NSObject, ARListStorageDisplayEx { } func loadForwardWithSortKey(sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { - checkTable(); - var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryForwardFirst, limit.toNSNumber()); - } else { - result = db!.executeQuery(queryForwardMore, sortingKey!.toNSNumber(), limit.toNSNumber()); - } + checkTable() - if (result == nil) { - NSLog(db!.lastErrorMessage()) - return nil - } - - let res: JavaUtilArrayList = JavaUtilArrayList(); - - let queryIndex = result!.columnIndexForName("QUERY") - let idIndex = result!.columnIndexForName("ID") - let sortKeyIndex = result!.columnIndexForName("SORT_KEY") - let bytesIndex = result!.columnIndexForName("BYTES") - var dataSize = 0 - var rowCount = 0 - - while(result!.next()) { - let key = jlong(result!.longLongIntForColumnIndex(idIndex)) - let order = jlong(result!.longLongIntForColumnIndex(sortKeyIndex)) - var query: AnyObject! = result!.objectForColumnIndex(queryIndex) - if (query is NSNull) { - query = nil + let res = JavaUtilArrayList() + dbQueue.inDatabase { (db) in + var result : FMResultSet? = nil + if (sortingKey == nil) { + result = db!.executeQuery(self.queryForwardFirst, limit.toNSNumber()) + } else { + result = db!.executeQuery(self.queryForwardMore, sortingKey!.toNSNumber(), limit.toNSNumber()) } - let data = result!.dataForColumnIndex(bytesIndex).toJavaBytes() - dataSize += Int(data.length()) - rowCount += 1 - let record = ARListEngineRecord(key: key, withOrder: order, withQuery: query as! String?, withData: data) - res.addWithId(record) + if (result == nil) { + NSLog(db!.lastErrorMessage()) + return + } + let queryIndex = result!.columnIndexForName("QUERY") + let idIndex = result!.columnIndexForName("ID") + let sortKeyIndex = result!.columnIndexForName("SORT_KEY") + let bytesIndex = result!.columnIndexForName("BYTES") + var dataSize = 0 + var rowCount = 0 + + while(result!.next()) { + let key = jlong(result!.longLongIntForColumnIndex(idIndex)) + let order = jlong(result!.longLongIntForColumnIndex(sortKeyIndex)) + var query: AnyObject! = result!.objectForColumnIndex(queryIndex) + if (query is NSNull) { + query = nil + } + let data = result!.dataForColumnIndex(bytesIndex).toJavaBytes() + dataSize += Int(data.length()) + rowCount += 1 + + let record = ARListEngineRecord(key: key, withOrder: order, withQuery: query as! String?, withData: data) + res.addWithId(record) + } + result!.close() } - result!.close() - - return res; + return res } func loadForwardWithQuery(query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { - checkTable(); + checkTable() - var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); - } else { - result = db!.executeQuery(queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); - } - if (result == nil) { - NSLog(db!.lastErrorMessage()) - return nil - } - - let res: JavaUtilArrayList = JavaUtilArrayList(); - - while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); - if (query is NSNull) { - query = nil + let res = JavaUtilArrayList() + dbQueue.inDatabase { (db) in + var result : FMResultSet? = nil + if (sortingKey == nil) { + result = db!.executeQuery(self.queryForwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()) + } else { + result = db!.executeQuery(self.queryForwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()) } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + if (result == nil) { + NSLog(db!.lastErrorMessage()) + return + } + + while(result!.next()) { + var query: AnyObject! = result!.objectForColumnName("QUERY") + if (query is NSNull) { + query = nil + } + let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) + res.addWithId(record) + } + result!.close() } - result!.close() - - return res; - + return res } func loadBackwardWithSortKey(sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { - checkTable(); - var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryBackwardFirst, limit.toNSNumber()); - } else { - result = db!.executeQuery(queryBackwardMore, sortingKey!.toNSNumber(), limit.toNSNumber()); - } - if (result == nil) { - NSLog(db!.lastErrorMessage()) - return nil - } + checkTable() - let res: JavaUtilArrayList = JavaUtilArrayList(); - - while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); - if (query is NSNull) { - query = nil + let res = JavaUtilArrayList() + dbQueue.inDatabase { (db) in + var result : FMResultSet? = nil + if (sortingKey == nil) { + result = db!.executeQuery(self.queryBackwardFirst, limit.toNSNumber()) + } else { + result = db!.executeQuery(self.queryBackwardMore, sortingKey!.toNSNumber(), limit.toNSNumber()) } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + if (result == nil) { + NSLog(db!.lastErrorMessage()) + return + } + + while(result!.next()) { + var query: AnyObject! = result!.objectForColumnName("QUERY") + if (query is NSNull) { + query = nil + } + let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) + res.addWithId(record) + } + result!.close() } - result!.close() - return res; + return res } func loadBackwardWithQuery(query: String!, withSortKey sortingKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { - checkTable(); - - var result : FMResultSet? = nil; - if (sortingKey == nil) { - result = db!.executeQuery(queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()); - } else { - result = db!.executeQuery(queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()); - } - if (result == nil) { - NSLog(db!.lastErrorMessage()) - return nil - } + checkTable() - let res: JavaUtilArrayList = JavaUtilArrayList(); - - while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); - if (query is NSNull) { - query = nil + let res = JavaUtilArrayList() + dbQueue.inDatabase { (db) in + var result : FMResultSet? = nil + if (sortingKey == nil) { + result = db!.executeQuery(self.queryBackwardFilterFirst, query + "%", "% " + query + "%", limit.toNSNumber()) + } else { + result = db!.executeQuery(self.queryBackwardFilterMore, query + "%", "% " + query + "%", sortingKey!.toNSNumber(), limit.toNSNumber()) } - let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) - res.addWithId(record) + if (result == nil) { + NSLog(db!.lastErrorMessage()) + return + } + + while(result!.next()) { + var query: AnyObject! = result!.objectForColumnName("QUERY") + if (query is NSNull) { + query = nil + } + let record = ARListEngineRecord(key: jlong(result!.longLongIntForColumn("ID")), withOrder: jlong(result!.longLongIntForColumn("SORT_KEY")), withQuery: query as! String?, withData: result!.dataForColumn("BYTES").toJavaBytes()) + res.addWithId(record) + } + result!.close() } - result!.close() - - return res; + return res } func loadCenterWithSortKey(centerSortKey: JavaLangLong!, withLimit limit: jint) -> JavaUtilList! { - checkTable(); + checkTable() - let res: JavaUtilArrayList = JavaUtilArrayList(); - res.addAllWithJavaUtilCollection(loadSlise(db!.executeQuery(queryCenterBackward, centerSortKey.toNSNumber(), limit.toNSNumber()))) - res.addAllWithJavaUtilCollection(loadSlise(db!.executeQuery(queryCenterForward, centerSortKey.toNSNumber(), limit.toNSNumber()))) + let res: JavaUtilArrayList = JavaUtilArrayList() + dbQueue.inDatabase { (db) in + res.addAllWithJavaUtilCollection(self.loadSlise(db.executeQuery(self.queryCenterBackward, centerSortKey.toNSNumber(), limit.toNSNumber()), db: db)) + res.addAllWithJavaUtilCollection(self.loadSlise(db.executeQuery(self.queryCenterForward, centerSortKey.toNSNumber(), limit.toNSNumber()), db: db)) + } return res } - func loadSlise(result: FMResultSet?) -> JavaUtilList! { + func loadSlise(result: FMResultSet?, db: FMDatabase) -> JavaUtilList! { if (result == nil) { - NSLog(db!.lastErrorMessage()) + NSLog(db.lastErrorMessage()) return nil } - let res: JavaUtilArrayList = JavaUtilArrayList(); + let res: JavaUtilArrayList = JavaUtilArrayList() while(result!.next()) { - var query: AnyObject! = result!.objectForColumnName("QUERY"); + var query: AnyObject! = result!.objectForColumnName("QUERY") if (query is NSNull) { query = nil } @@ -359,6 +365,6 @@ class FMDBList : NSObject, ARListStorageDisplayEx { res.addWithId(record) } result!.close() - return res; + return res } } \ No newline at end of file From bd5821e6f322ff40cba4f113638174f65f4ed6cc Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 17 Aug 2016 18:57:36 +0300 Subject: [PATCH 67/81] wip(core): Uploading encrypted files --- .../actor/core/modules/file/FilesModule.java | 7 +- .../core/modules/file/UploadManager.java | 22 +- .../actor/core/modules/file/UploadTask.java | 205 ++++++++++++------ .../modules/file/entity/EncryptionInfo.java | 47 ++++ .../messaging/actions/SenderActor.java | 85 +++++--- .../actions/entity/PendingMessage.java | 16 +- .../avatar/GroupAvatarChangeActor.java | 2 +- .../profile/avatar/OwnAvatarChangeActor.java | 2 +- .../modes/CBCBlockCipherStream.java | 65 ++++++ .../files/SequenceFileSystemInputFile.java | 32 +++ .../runtime/files/SequenceInputFile.java | 11 + 11 files changed, 381 insertions(+), 113 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/entity/EncryptionInfo.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceFileSystemInputFile.java create mode 100644 actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceInputFile.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java index a13a13b37d..2fbb4139fa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java @@ -10,6 +10,7 @@ import im.actor.core.modules.AbsModule; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.file.entity.Downloaded; +import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.core.util.BaseKeyValueEngine; import im.actor.core.viewmodel.FileCallback; import im.actor.core.viewmodel.FileEventCallback; @@ -121,8 +122,10 @@ public void unbindUploadFile(long rid, UploadFileCallback callback) { uploadManager.send(new UploadManager.UnbindUpload(rid, callback)); } - public void requestUpload(long rid, String descriptor, String fileName, ActorRef requester) { - uploadManager.send(new UploadManager.StartUpload(rid, descriptor, fileName), requester); + public void requestUpload(long rid, String descriptor, String fileName, + EncryptionInfo encryptionInfo, ActorRef requester) { + uploadManager.send(new UploadManager.StartUpload(rid, descriptor, fileName, encryptionInfo), + requester); } public void cancelUpload(long rid) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java index f0b7e56887..0e88d239a2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java @@ -11,6 +11,7 @@ import im.actor.core.modules.ModuleContext; import im.actor.core.modules.file.entity.Downloaded; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.core.util.RandomUtils; import im.actor.core.viewmodel.UploadFileCallback; import im.actor.runtime.Log; @@ -37,11 +38,12 @@ public UploadManager(ModuleContext context) { // Tasks - public void startUpload(long rid, String descriptor, String fileName, ActorRef requestActor) { + public void startUpload(long rid, String descriptor, String fileName, + EncryptionInfo encryptionInfo, ActorRef requestActor) { if (LOG) { Log.d(TAG, "Starting upload #" + rid + " with descriptor " + descriptor); } - QueueItem queueItem = new QueueItem(rid, descriptor, fileName, requestActor); + QueueItem queueItem = new QueueItem(rid, descriptor, fileName, encryptionInfo, requestActor); queueItem.isStopped = false; queue.add(queueItem); checkQueue(); @@ -294,7 +296,7 @@ private void checkQueue() { final QueueItem finalPendingQueue = pendingQueue; pendingQueue.taskRef = system().actorOf(Props.create(() -> new UploadTask(finalPendingQueue.rid, finalPendingQueue.fileDescriptor, - finalPendingQueue.fileName, self(), context())).changeDispatcher("heavy"), "actor/upload/task_" + RandomUtils.nextRid()); + finalPendingQueue.fileName, finalPendingQueue.encryptionInfo, self(), context())).changeDispatcher("heavy"), "actor/upload/task_" + RandomUtils.nextRid()); } private QueueItem findItem(long rid) { @@ -312,15 +314,17 @@ private class QueueItem { private boolean isStopped; private boolean isStarted; private float progress; + private EncryptionInfo encryptionInfo; private ActorRef taskRef; private ActorRef requestActor; private String fileName; - private QueueItem(long rid, String fileDescriptor, String fileName, ActorRef requestActor) { + private QueueItem(long rid, String fileDescriptor, String fileName, EncryptionInfo encryptionInfo, ActorRef requestActor) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.requestActor = requestActor; this.fileName = fileName; + this.encryptionInfo = encryptionInfo; } } @@ -331,7 +335,7 @@ public void onReceive(Object message) { if (message instanceof StartUpload) { StartUpload startUpload = (StartUpload) message; startUpload(startUpload.getRid(), startUpload.getFileDescriptor(), - startUpload.getFileName(), sender()); + startUpload.getFileName(), startUpload.getEncryptionInfo(), sender()); } else if (message instanceof StopUpload) { StopUpload cancelUpload = (StopUpload) message; stopUpload(cancelUpload.getRid()); @@ -369,11 +373,13 @@ public static class StartUpload { private long rid; private String fileDescriptor; private String fileName; + private EncryptionInfo encryptionInfo; - public StartUpload(long rid, String fileDescriptor, String fileName) { + public StartUpload(long rid, String fileDescriptor, String fileName, EncryptionInfo encryptionInfo) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.fileName = fileName; + this.encryptionInfo = encryptionInfo; } public long getRid() { @@ -387,6 +393,10 @@ public String getFileDescriptor() { public String getFileName() { return fileName; } + + public EncryptionInfo getEncryptionInfo() { + return encryptionInfo; + } } public static class BindUpload { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index eb89b913e5..0653658e02 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -12,17 +12,26 @@ import im.actor.core.entity.FileReference; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; +import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; +import im.actor.runtime.Crypto; import im.actor.runtime.HTTP; import im.actor.runtime.Log; import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ActorCancellable; +import im.actor.runtime.crypto.BlockCipher; import im.actor.runtime.crypto.CRC32; +import im.actor.runtime.crypto.primitives.Padding; +import im.actor.runtime.crypto.primitives.modes.CBCBlockCipher; +import im.actor.runtime.crypto.primitives.modes.CBCBlockCipherStream; +import im.actor.runtime.crypto.primitives.padding.PKCS7Padding; import im.actor.runtime.files.FileSystemReference; import im.actor.runtime.files.InputFile; import im.actor.runtime.files.OutputFile; +import im.actor.runtime.files.SequenceFileSystemInputFile; +import im.actor.runtime.files.SequenceInputFile; import im.actor.runtime.http.HTTPError; import im.actor.runtime.http.HTTPResponse; import im.actor.runtime.promise.Promise; @@ -35,6 +44,7 @@ public class UploadTask extends ModuleActor { private static final int SIM_BLOCKS_COUNT = 4; private static final int NOTIFY_THROTTLE = 1000; private static final int DEFAULT_RETRY = 15; + private static final int IV_SIZE = 16; private final String TAG; private final boolean LOG; @@ -42,11 +52,14 @@ public class UploadTask extends ModuleActor { private long rid; private String fileName; private String descriptor; + private EncryptionInfo encryptionInfo; + private BlockCipher encryptionCipher; + private byte[] encryptionIv; private boolean isWriteToDestProvider = false; private FileSystemReference srcReference; - private InputFile inputFile; + private SequenceInputFile inputFile; private FileSystemReference destReference; private OutputFile outputFile; @@ -54,6 +67,7 @@ public class UploadTask extends ModuleActor { private ActorRef manager; private boolean isCompleted = false; + private int finalSize; private int blockSize = 128 * 1024; private int blocksCount; private int nextBlock = 0; @@ -68,12 +82,14 @@ public class UploadTask extends ModuleActor { private float currentProgress; private boolean alreadyInTemp; - public UploadTask(long rid, String descriptor, String fileName, ActorRef manager, ModuleContext context) { + public UploadTask(long rid, String descriptor, String fileName, EncryptionInfo encryptionInfo, + ActorRef manager, ModuleContext context) { super(context); this.LOG = context.getConfiguration().isEnableFilesLogging(); this.rid = rid; this.fileName = fileName; this.descriptor = descriptor; + this.encryptionInfo = encryptionInfo; this.manager = manager; this.TAG = "UploadTask{" + rid + "}"; } @@ -103,45 +119,52 @@ public void preStart() { } } - srcReference.openRead() - .flatMap(f -> { - inputFile = f; - if (isWriteToDestProvider) { - return destReference.openWrite(srcReference.getSize()); - } else { - return Promise.success(null); - } - }) - .flatMap(f -> { - outputFile = f; - - crc32 = new CRC32(); + srcReference.openRead().flatMap(f -> { + inputFile = new SequenceFileSystemInputFile(f); + if (isWriteToDestProvider) { + return destReference.openWrite(srcReference.getSize()); + } else { + return Promise.success(null); + } + }).flatMap(f -> { + outputFile = f; + + // CRC + crc32 = new CRC32(); + + // Size & Encryption + if (encryptionInfo != null) { + encryptionIv = Crypto.randomBytes(16); + encryptionCipher = new CBCBlockCipherStream(encryptionIv, Crypto.createAES128(encryptionInfo.getEncryptionKey())); + finalSize = 16/*IV*/ + (int) (Math.ceil(srcReference.getSize() / (float) encryptionCipher.getBlockSize())); + } else { + finalSize = srcReference.getSize(); + } - blocksCount = srcReference.getSize() / blockSize; - if (srcReference.getSize() % blockSize != 0) { - blocksCount++; - } + // Blocks + blocksCount = finalSize / blockSize; + if (finalSize % blockSize != 0) { + blocksCount++; + } - if (LOG) { - Log.d(TAG, "Starting uploading " + blocksCount + " blocks"); - Log.d(TAG, "Requesting upload config..."); - } + if (LOG) { + Log.d(TAG, "Starting uploading " + blocksCount + " blocks"); + Log.d(TAG, "Requesting upload config..."); + } - return api(new RequestGetFileUploadUrl(srcReference.getSize())); - }) - .then(r -> { - if (LOG) { - Log.d(TAG, "Upload config loaded"); - } - uploadConfig = r.getUploadKey(); - checkQueue(); - }) - .failure(e -> { - if (LOG) { - Log.w(TAG, "Error during initialization of upload"); - } - reportError(); - }); + return api(new RequestGetFileUploadUrl(finalSize)); + }).then(r -> { + if (LOG) { + Log.d(TAG, "Upload config loaded"); + } + uploadConfig = r.getUploadKey(); + checkQueue(); + }).failure(e -> { + if (LOG) { + Log.w(TAG, "Error during initialization of upload"); + } + reportError(); + }); } private void checkQueue() { @@ -164,32 +187,25 @@ private void checkQueue() { outputFile.close(); } - request(new RequestCommitFileUpload(uploadConfig, fileName), new RpcCallback() { - @Override - public void onResult(ResponseCommitFileUpload response) { - if (LOG) { - Log.d(TAG, "Upload completed..."); - } - - FileReference location = new FileReference(response.getUploadedFileLocation(), - fileName, srcReference.getSize()); - - if (isWriteToDestProvider || alreadyInTemp) { - FileSystemReference reference = Storage.commitTempFile(alreadyInTemp ? srcReference : destReference, location.getFileId(), - location.getFileName()); - reportComplete(location, reference); - } else { - reportComplete(location, srcReference); - } + api(new RequestCommitFileUpload(uploadConfig, fileName)).then(r -> { + if (LOG) { + Log.d(TAG, "Upload completed..."); } - - @Override - public void onError(RpcException e) { - if (LOG) { - Log.w(TAG, "Upload complete error"); - } - reportError(); + FileReference location = new FileReference(r.getUploadedFileLocation(), fileName, + finalSize); + + if (isWriteToDestProvider || alreadyInTemp) { + FileSystemReference reference = Storage.commitTempFile(alreadyInTemp ? srcReference : destReference, location.getFileId(), + location.getFileName()); + reportComplete(location, reference); + } else { + reportComplete(location, srcReference); + } + }).failure(e -> { + if (LOG) { + Log.w(TAG, "Upload complete error"); } + reportError(); }); return; } @@ -202,12 +218,27 @@ public void onError(RpcException e) { private void loadPart(final int blockIndex) { int size = blockSize; int fileOffset = blockIndex * blockSize; - if ((blockIndex + 1) * blockSize > srcReference.getSize()) { - size = srcReference.getSize() - blockIndex * blockSize; + + // Calculating appropriate read block size + if (encryptionInfo != null) { + if (blockIndex == 0) { + if (srcReference.getSize() - IV_SIZE > blockSize) { + size = blockSize - IV_SIZE; + } else { + size = srcReference.getSize(); + } + } else { + if ((blockIndex + 1) * blockSize - IV_SIZE > srcReference.getSize()) { + size = srcReference.getSize() - blockIndex * blockSize; + } + } + } else { + if ((blockIndex + 1) * blockSize > srcReference.getSize()) { + size = srcReference.getSize() - blockIndex * blockSize; + } } - // TODO: Validate file part load ordering - inputFile.read(fileOffset, size).then(filePart -> { + inputFile.readBlock(size).then(filePart -> { if (isCompleted) { return; } @@ -232,7 +263,47 @@ private void loadPart(final int blockIndex) { } uploadCount++; - uploadPart(blockIndex, filePart.getContents(), 0); + + // Block Encryption + if (encryptionInfo != null) { + + // Result Block + int destBlockCount = (int) Math.ceil(filePart.getContents().length / + (float) encryptionCipher.getBlockSize()); + int destBlockSize = destBlockCount * encryptionCipher.getBlockSize(); + int destBlockOffset = 0; + if (blockIndex == 0) { + destBlockOffset = 16; + } + + // Block for uploading + byte[] res = new byte[destBlockSize + destBlockOffset]; + + // Appending IV if needed + if (blockIndex == 0) { + for (int i = 0; i < IV_SIZE; i++) { + res[i] = encryptionIv[i]; + } + } + + // Encrypting Block + for (int i = 0; i < destBlockCount; i++) { + if (i == destBlockCount - 1) { + byte[] tmp = new byte[encryptionCipher.getBlockSize()]; + for (int j = 0; j < encryptionCipher.getBlockSize() && i * encryptionCipher.getBlockSize() + j < filePart.getContents().length; j++) { + tmp[j] = filePart.getContents()[j]; + } + encryptionCipher.encryptBlock(tmp, 0, res, destBlockOffset + i * encryptionCipher.getBlockSize()); + } else { + encryptionCipher.encryptBlock(filePart.getContents(), i * encryptionCipher.getBlockSize(), + res, destBlockOffset + i * encryptionCipher.getBlockSize()); + } + } + + uploadPart(blockIndex, res, 0); + } else { + uploadPart(blockIndex, filePart.getContents(), 0); + } checkQueue(); }).failure(e -> { if (isCompleted) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/entity/EncryptionInfo.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/entity/EncryptionInfo.java new file mode 100644 index 0000000000..8c16de6a15 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/entity/EncryptionInfo.java @@ -0,0 +1,47 @@ +package im.actor.core.modules.file.entity; + +import java.io.IOException; + +import im.actor.runtime.bser.Bser; +import im.actor.runtime.bser.BserObject; +import im.actor.runtime.bser.BserValues; +import im.actor.runtime.bser.BserWriter; + +public class EncryptionInfo extends BserObject { + + public static EncryptionInfo fromBytes(byte[] data) throws IOException { + return Bser.parse(new EncryptionInfo(), data); + } + + private byte[] encryptionKey; + private byte[] macKey; + + public EncryptionInfo(byte[] encryptionKey, byte[] macKey) { + this.encryptionKey = encryptionKey; + this.macKey = macKey; + } + + private EncryptionInfo() { + + } + + public byte[] getEncryptionKey() { + return encryptionKey; + } + + public byte[] getMacKey() { + return macKey; + } + + @Override + public void parse(BserValues values) throws IOException { + encryptionKey = values.getBytes(1); + macKey = values.getBytes(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeBytes(1, encryptionKey); + writer.writeBytes(2, macKey); + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 9567ae47fa..89c0a9244f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; +import im.actor.core.api.ApiDocumentEncryptionInfo; import im.actor.core.api.ApiDocumentExAnimation; import im.actor.core.api.ApiDocumentExVoice; import im.actor.core.api.ApiEncryptedContent; @@ -59,6 +60,7 @@ import im.actor.core.entity.content.internal.ContentRemoteContainer; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.file.UploadManager; +import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.core.modules.messaging.actions.entity.PendingMessage; import im.actor.core.modules.messaging.actions.entity.PendingMessagesStorage; import im.actor.core.modules.ModuleActor; @@ -67,6 +69,7 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; +import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; @@ -107,14 +110,16 @@ public void preStart() { pending.getContent() instanceof StickerContent || pending.getContent() instanceof LocationContent || pending.getContent() instanceof ContactContent) { - performSendContent(pending.getPeer(), pending.getRid(), pending.getTimer(), pending.getContent()); + performSendContent(pending.getPeer(), pending.getRid(), pending.getTimer(), + pending.getEncryptionInfo(), pending.getContent()); } else if (pending.getContent() instanceof DocumentContent) { DocumentContent documentContent = (DocumentContent) pending.getContent(); if (documentContent.getSource() instanceof FileLocalSource) { if (Storage.isFsPersistent()) { performUploadFile(pending.getRid(), ((FileLocalSource) documentContent.getSource()).getFileDescriptor(), - ((FileLocalSource) documentContent.getSource()).getFileName()); + ((FileLocalSource) documentContent.getSource()).getFileName(), + pending.getEncryptionInfo()); } else { List rids = new ArrayList<>(); rids.add(pending.getRid()); @@ -124,7 +129,7 @@ public void preStart() { } } else { performSendContent(pending.getPeer(), pending.getRid(), - pending.getTimer(), pending.getContent()); + pending.getTimer(), pending.getEncryptionInfo(), pending.getContent()); } } } @@ -177,21 +182,21 @@ public void doSendText(@NotNull Peer peer, @NotNull String text, TextContent content = TextContent.create(text, null, mentions); - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } public void doSendJson(Peer peer, JsonContent content) { - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } // Sending sticker public void doSendSticker(@NotNull Peer peer, @NotNull Sticker sticker) { StickerContent content = StickerContent.create(sticker); - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } public void doSendContact(@NotNull Peer peer, @@ -199,21 +204,21 @@ public void doSendContact(@NotNull Peer peer, @Nullable String name, @Nullable String base64photo) { ContactContent content = ContactContent.create(name, phones, emails, base64photo); - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } public void doSendLocation(@NotNull Peer peer, @NotNull Double longitude, @NotNull Double latitude, @Nullable String street, @Nullable String place) { LocationContent content = LocationContent.create(longitude, latitude, street, place); - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } public void doForwardContent(Peer peer, AbsContent content) { - PendingMessage pending = prepareSend(peer, content); - performSendContent(peer, pending.getRid(), pending.getTimer(), content); + PendingMessage pending = prepareSend(peer, content, false); + performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); } // Sending documents @@ -222,41 +227,41 @@ public void doSendDocument(Peer peer, String fileName, String mimeType, int file FastThumb fastThumb, String descriptor) { DocumentContent documentContent = DocumentContent.createLocal(fileName, fileSize, descriptor, mimeType, fastThumb); - PendingMessage pending = prepareSend(peer, documentContent); - performUploadFile(pending.getRid(), descriptor, fileName); + PendingMessage pending = prepareSend(peer, documentContent, true); + performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); } public void doSendPhoto(Peer peer, FastThumb fastThumb, String descriptor, String fileName, int fileSize, int w, int h) { PhotoContent photoContent = PhotoContent.createLocalPhoto(descriptor, fileName, fileSize, w, h, fastThumb); - PendingMessage pending = prepareSend(peer, photoContent); - performUploadFile(pending.getRid(), descriptor, fileName); + PendingMessage pending = prepareSend(peer, photoContent, true); + performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); } public void doSendAudio(Peer peer, String descriptor, String fileName, int fileSize, int duration) { VoiceContent audioContent = VoiceContent.createLocalAudio(descriptor, fileName, fileSize, duration); - PendingMessage pending = prepareSend(peer, audioContent); - performUploadFile(pending.getRid(), descriptor, fileName); + PendingMessage pending = prepareSend(peer, audioContent, true); + performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); } public void doSendVideo(Peer peer, String fileName, int w, int h, int duration, FastThumb fastThumb, String descriptor, int fileSize) { VideoContent videoContent = VideoContent.createLocalVideo(descriptor, fileName, fileSize, w, h, duration, fastThumb); - PendingMessage pending = prepareSend(peer, videoContent); - performUploadFile(pending.getRid(), descriptor, fileName); + PendingMessage pending = prepareSend(peer, videoContent, true); + performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); } public void doSendAnimation(Peer peer, String fileName, int w, int h, FastThumb fastThumb, String descriptor, int fileSize) { AnimationContent animationContent = AnimationContent.createLocalAnimation(descriptor, fileName, fileSize, w, h, fastThumb); - PendingMessage pending = prepareSend(peer, animationContent); - performUploadFile(pending.getRid(), descriptor, fileName); + PendingMessage pending = prepareSend(peer, animationContent, true); + performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); } - private PendingMessage prepareSend(Peer peer, AbsContent content) { + private PendingMessage prepareSend(Peer peer, AbsContent content, boolean isFile) { long rid = RandomUtils.nextRid(); long date = createPendingDate(); long sortDate = date + 365 * 24 * 60 * 60 * 1000L; @@ -268,15 +273,19 @@ private PendingMessage prepareSend(Peer peer, AbsContent content) { Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content, new ArrayList<>(), 0, timer); context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer); + EncryptionInfo encryptionInfo = null; + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && isFile) { + encryptionInfo = new EncryptionInfo(Crypto.randomBytes(16), Crypto.randomBytes(32)); + } + PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer, encryptionInfo); pendingMessages.getPendingMessages().add(pendingMessage); savePending(); return pendingMessage; } - private void performUploadFile(long rid, String descriptor, String fileName) { + private void performUploadFile(long rid, String descriptor, String fileName, EncryptionInfo encryptionInfo) { fileUplaodingWakeLocks.put(rid, Runtime.makeWakeLock()); - context().getFilesModule().requestUpload(rid, descriptor, fileName, self()); + context().getFilesModule().requestUpload(rid, descriptor, fileName, encryptionInfo, self()); } private void onFileUploaded(long rid, FileReference fileReference) { @@ -311,9 +320,10 @@ private void onFileUploaded(long rid, FileReference fileReference) { return; } - pendingMessages.getPendingMessages().add(new PendingMessage(msg.getPeer(), msg.getRid(), nContent, msg.getTimer())); + pendingMessages.getPendingMessages().add(new PendingMessage(msg.getPeer(), msg.getRid(), + nContent, msg.getTimer(), msg.getEncryptionInfo())); context().getMessagesModule().getRouter().onContentChanged(msg.getPeer(), msg.getRid(), nContent); - performSendContent(msg.getPeer(), rid, msg.getTimer(), nContent); + performSendContent(msg.getPeer(), rid, msg.getTimer(), msg.getEncryptionInfo(), nContent); fileUplaodingWakeLocks.remove(rid).releaseLock(); } @@ -329,7 +339,7 @@ private void onFileUploadError(long rid) { // Sending content - private void performSendContent(final Peer peer, final long rid, int timer, AbsContent content) { + private void performSendContent(final Peer peer, final long rid, int timer, EncryptionInfo encryptionInfo, AbsContent content) { WakeLock wakeLock = im.actor.runtime.Runtime.makeWakeLock(); ApiMessage message; @@ -341,7 +351,6 @@ private void performSendContent(final Peer peer, final long rid, int timer, AbsC FileRemoteSource source = (FileRemoteSource) documentContent.getSource(); ApiDocumentEx documentEx = null; - if (content instanceof PhotoContent) { PhotoContent photoContent = (PhotoContent) content; documentEx = new ApiDocumentExPhoto(photoContent.getW(), photoContent.getH()); @@ -356,7 +365,6 @@ private void performSendContent(final Peer peer, final long rid, int timer, AbsC documentEx = new ApiDocumentExVoice(voiceContent.getDuration()); } - ApiFastThumb fastThumb = null; if (documentContent.getFastThumb() != null) { fastThumb = new ApiFastThumb( @@ -365,13 +373,20 @@ private void performSendContent(final Peer peer, final long rid, int timer, AbsC documentContent.getFastThumb().getImage()); } + ApiDocumentEncryptionInfo apiEncryptionInfo = null; + if (encryptionInfo != null) { + apiEncryptionInfo = new ApiDocumentEncryptionInfo(source.getSize(), "aes128-sha256", + ByteStrings.merge(encryptionInfo.getEncryptionKey(), + encryptionInfo.getMacKey())); + } + message = new ApiDocumentMessage(source.getFileReference().getFileId(), source.getFileReference().getAccessHash(), source.getFileReference().getFileSize(), source.getFileReference().getFileName(), documentContent.getMimeType(), fastThumb, documentEx, - null); + apiEncryptionInfo); } else if (content instanceof LocationContent) { message = new ApiJsonMessage(((LocationContent) content).getRawJson()); } else if (content instanceof ContactContent) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java index bed0bb0dd5..3aae2dafce 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java @@ -6,6 +6,7 @@ import java.io.IOException; +import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.runtime.bser.Bser; import im.actor.runtime.bser.BserObject; import im.actor.runtime.bser.BserValues; @@ -24,12 +25,14 @@ public static PendingMessage fromBytes(byte[] data) throws IOException { private AbsContent content; private boolean isError; private int timer; + private EncryptionInfo encryptionInfo; - public PendingMessage(Peer peer, long rid, AbsContent content, int timer) { + public PendingMessage(Peer peer, long rid, AbsContent content, int timer, EncryptionInfo encryptionInfo) { this.peer = peer; this.rid = rid; this.content = content; this.timer = timer; + this.encryptionInfo = encryptionInfo; } private PendingMessage() { @@ -56,6 +59,10 @@ public int getTimer() { return timer; } + public EncryptionInfo getEncryptionInfo() { + return encryptionInfo; + } + @Override public void parse(BserValues values) throws IOException { peer = Peer.fromUniqueId(values.getLong(1)); @@ -63,6 +70,10 @@ public void parse(BserValues values) throws IOException { content = AbsContent.parse(values.getBytes(3)); isError = values.getBool(4, false); timer = values.getInt(5, 0); + byte[] dt = values.getBytes(6); + if (dt != null) { + encryptionInfo = EncryptionInfo.fromBytes(dt); + } } @Override @@ -72,5 +83,8 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBytes(3, AbsContent.serialize(content)); writer.writeBool(4, isError); writer.writeInt(5, timer); + if (encryptionInfo != null) { + writer.writeObject(6, encryptionInfo); + } } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java index df6fbbc424..0f85911b43 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java @@ -51,7 +51,7 @@ public void changeAvatar(int gid, String descriptor) { tasksMap.put(rid, gid); context().getGroupsModule().getAvatarVM(gid).getUploadState().change(new AvatarUploadState(descriptor, true)); - context().getFilesModule().requestUpload(rid, descriptor, "avatar.jpg", self()); + context().getFilesModule().requestUpload(rid, descriptor, "avatar.jpg", null, self()); } public void uploadCompleted(final long rid, FileReference fileReference) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java index d5eb441c52..6e27cc5ad2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java @@ -38,7 +38,7 @@ public void changeAvatar(String descriptor) { context().getProfileModule().getOwnAvatarVM().getUploadState().change(new AvatarUploadState(descriptor, true)); - context().getFilesModule().requestUpload(currentChangeTask, descriptor, "avatar.jpg", self()); + context().getFilesModule().requestUpload(currentChangeTask, descriptor, "avatar.jpg", null, self()); } public void uploadCompleted(final long rid, FileReference fileReference) { diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java new file mode 100644 index 0000000000..aa549070bd --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java @@ -0,0 +1,65 @@ +package im.actor.runtime.crypto.primitives.modes; + +import im.actor.runtime.crypto.BlockCipher; + +/*-[ +#define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 +]-*/ + +public class CBCBlockCipherStream implements BlockCipher { + + private byte[] iv; + private BlockCipher baseCipher; + private int blockSize; + private byte[] workingSet; + + public CBCBlockCipherStream(byte[] iv, BlockCipher baseCipher) { + if (iv.length != baseCipher.getBlockSize()) { + throw new RuntimeException("Incorrect iv size"); + } + + this.baseCipher = baseCipher; + this.blockSize = baseCipher.getBlockSize(); + this.iv = iv; + this.workingSet = new byte[blockSize]; + } + + @Override + public void encryptBlock(byte[] data, int offset, byte[] dest, int destOffset) { + + // DATA = SRC_DATA ^ IV + for (int j = 0; j < blockSize; j++) { + workingSet[j] = (byte) ((data[offset + j] & 0xFF) ^ (iv[j] & 0xFF)); + } + + // DEST = encrypt(DATA) + baseCipher.encryptBlock(workingSet, 0, dest, destOffset); + + // IV = DEST + for (int j = 0; j < blockSize; j++) { + iv[j] = dest[destOffset + j]; + } + } + + @Override + public void decryptBlock(byte[] data, int offset, byte[] dest, int destOffset) { + + // DEST = decrypt(DATA) + baseCipher.decryptBlock(data, offset, dest, destOffset); + + // DEST_RES = DEST ^ IV + for (int j = 0; j < blockSize; j++) { + dest[destOffset + j] = (byte) ((dest[destOffset + j] & 0xFF) ^ (iv[j] & 0xFF)); + } + + // IV = DATA + for (int j = 0; j < blockSize; j++) { + iv[j] = data[offset + j]; + } + } + + @Override + public int getBlockSize() { + return baseCipher.getBlockSize(); + } +} diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceFileSystemInputFile.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceFileSystemInputFile.java new file mode 100644 index 0000000000..e8174d76be --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceFileSystemInputFile.java @@ -0,0 +1,32 @@ +package im.actor.runtime.files; + +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public class SequenceFileSystemInputFile implements SequenceInputFile { + + // j2objc workaround + private static final FilePart DUMB = null; + + private final InputFile inputFile; + private int currentOffset; + + private Promise prev = Promise.success(null); + + public SequenceFileSystemInputFile(InputFile inputFile) { + this.inputFile = inputFile; + } + + @Override + public synchronized Promise readBlock(int blockSize) { + int offset = currentOffset; + currentOffset += blockSize; + prev = prev.flatMap(r -> inputFile.read(offset, blockSize)); + return prev; + } + + @Override + public synchronized Promise close() { + return prev.flatMap(r -> inputFile.close()); + } +} \ No newline at end of file diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceInputFile.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceInputFile.java new file mode 100644 index 0000000000..4d237eef83 --- /dev/null +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/files/SequenceInputFile.java @@ -0,0 +1,11 @@ +package im.actor.runtime.files; + +import im.actor.runtime.actors.messages.Void; +import im.actor.runtime.promise.Promise; + +public interface SequenceInputFile { + + Promise readBlock(int blockSize); + + Promise close(); +} From 958f745e3cbdc11682b56f57f2a27adaf8c7a4e1 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 17 Aug 2016 20:57:59 +0300 Subject: [PATCH 68/81] wip(core): Working on downloading encrypted files --- .../im/actor/core/entity/AvatarImage.java | 2 +- .../im/actor/core/entity/FileReference.java | 21 +- .../im/actor/core/entity/ImageLocation.java | 3 +- .../java/im/actor/core/entity/Sticker.java | 6 +- .../core/entity/content/DocumentContent.java | 3 +- .../actor/core/modules/file/DownloadTask.java | 211 +++++++++--------- .../actor/core/modules/file/UploadTask.java | 2 +- .../sequence/processor/UpdateProcessor.java | 3 +- .../im/actor/runtime/promise/Promises.java | 100 ++++++++- 9 files changed, 235 insertions(+), 116 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/AvatarImage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/AvatarImage.java index 53f11b1183..45c3cc58f6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/AvatarImage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/AvatarImage.java @@ -80,7 +80,7 @@ protected void applyWrapped(@NotNull ApiAvatarImage wrapped) { this.width = wrapped.getWidth(); this.height = wrapped.getHeight(); this.fileReference = new FileReference(wrapped.getFileLocation(), - "avatar.jpg", wrapped.getFileSize()); + "avatar.jpg", wrapped.getFileSize(), null); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/FileReference.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/FileReference.java index 6454a67cb7..4a600df19c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/FileReference.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/FileReference.java @@ -10,7 +10,9 @@ import java.io.IOException; +import im.actor.core.api.ApiDocumentEncryptionInfo; import im.actor.core.api.ApiFileLocation; +import im.actor.runtime.bser.Bser; import im.actor.runtime.bser.BserValues; import im.actor.runtime.bser.BserWriter; @@ -28,11 +30,15 @@ public class FileReference extends WrapperEntity { private int fileSize; @Property("readonly, nonatomic") private String fileName; + @Property("readonly, nonatomic") + private ApiDocumentEncryptionInfo encryptionInfo; - public FileReference(ApiFileLocation fileLocation, String fileName, int fileSize) { + public FileReference(ApiFileLocation fileLocation, String fileName, int fileSize, + ApiDocumentEncryptionInfo encryptionInfo) { super(RECORD_ID, fileLocation); this.fileSize = fileSize; this.fileName = fileName; + this.encryptionInfo = encryptionInfo; } public FileReference(byte[] data) throws IOException { @@ -59,6 +65,10 @@ public String getFileName() { return fileName; } + public ApiDocumentEncryptionInfo getEncryptionInfo() { + return encryptionInfo; + } + @Override public void parse(BserValues values) throws IOException { // Is Old layout @@ -73,6 +83,11 @@ public void parse(BserValues values) throws IOException { fileSize = values.getInt(3); fileName = values.getString(4); + + byte[] data = values.optBytes(6); + if (data != null) { + encryptionInfo = Bser.parse(new ApiDocumentEncryptionInfo(), data); + } } @Override @@ -82,6 +97,10 @@ public void serialize(BserWriter writer) throws IOException { writer.writeInt(3, fileSize); writer.writeString(4, fileName); + if (encryptionInfo != null) { + writer.writeObject(6, encryptionInfo); + } + // Write wrapper super.serialize(writer); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ImageLocation.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ImageLocation.java index 9a6c1c75fa..332a609c3a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ImageLocation.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/ImageLocation.java @@ -22,7 +22,8 @@ public ImageLocation(@NotNull ApiImageLocation imageLocation, @NotNull String fi reference = new FileReference( imageLocation.getFileLocation(), fileName, - imageLocation.getFileSize()); + imageLocation.getFileSize(), + null); } public int getWidth() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Sticker.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Sticker.java index 5d7d0f0ad9..6f945b148c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Sticker.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/Sticker.java @@ -67,14 +67,14 @@ protected void applyWrapped(@NotNull ApiStickerDescriptor wrapped) { emoji = wrapped.getEmoji(); id = wrapped.getId(); image128Location = wrapped.getImage128(); - image128 = new FileReference(image128Location.getFileLocation(), "sticker.webp", image128Location.getFileSize()); + image128 = new FileReference(image128Location.getFileLocation(), "sticker.webp", image128Location.getFileSize(), null); if (wrapped.getImage256() != null) { image256Location = wrapped.getImage256(); - image256 = new FileReference(image256Location.getFileLocation(), "sticker.webp", image256Location.getFileSize()); + image256 = new FileReference(image256Location.getFileLocation(), "sticker.webp", image256Location.getFileSize(), null); } if (wrapped.getImage512() != null) { image512Location = wrapped.getImage512(); - image512 = new FileReference(wrapped.getImage512().getFileLocation(), "sticker.webp", wrapped.getImage512().getFileSize()); + image512 = new FileReference(wrapped.getImage512().getFileLocation(), "sticker.webp", wrapped.getImage512().getFileSize(), null); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java index 3866ea7fb4..5e9f40003d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java @@ -44,7 +44,8 @@ public DocumentContent(ContentRemoteContainer contentContainer) { ApiDocumentMessage doc = ((ApiDocumentMessage) contentContainer.getMessage()); source = new FileRemoteSource(new FileReference( new ApiFileLocation(doc.getFileId(), doc.getAccessHash()), doc.getName(), - doc.getFileSize())); + doc.getFileSize(), + doc.getEncryptionInfo())); mimeType = doc.getMimeType(); name = doc.getName(); fastThumb = doc.getThumb() != null ? new FastThumb(doc.getThumb()) : null; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java index 93200d7de6..832662fb1c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java @@ -4,17 +4,31 @@ package im.actor.core.modules.file; +import org.jetbrains.annotations.NotNull; + import im.actor.core.entity.FileReference; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; +import im.actor.runtime.Crypto; import im.actor.runtime.HTTP; import im.actor.runtime.Log; import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ActorCancellable; +import im.actor.runtime.crypto.BlockCipher; +import im.actor.runtime.crypto.primitives.aes.AESFastEngine; +import im.actor.runtime.crypto.primitives.modes.CBCBlockCipherStream; +import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.files.FileSystemReference; import im.actor.runtime.files.OutputFile; +import im.actor.runtime.function.Consumer; +import im.actor.runtime.function.Supplier; import im.actor.runtime.http.HTTPError; +import im.actor.runtime.http.HTTPResponse; +import im.actor.runtime.promise.Promise; +import im.actor.runtime.promise.PromiseFunc; +import im.actor.runtime.promise.PromiseResolver; +import im.actor.runtime.promise.Promises; public class DownloadTask extends ModuleActor { @@ -26,6 +40,7 @@ public class DownloadTask extends ModuleActor { private final boolean LOG; private FileReference fileReference; + private BlockCipher encryptionCipher; private ActorRef manager; private FileSystemReference destReference; @@ -40,9 +55,6 @@ public class DownloadTask extends ModuleActor { private String fileUrl; private int blockSize = 128 * 1024; private int blocksCount; - private int nextBlock = 0; - private int currentDownloads = 0; - private int downloaded = 0; public DownloadTask(FileReference fileReference, ActorRef manager, ModuleContext context) { super(context); @@ -78,16 +90,6 @@ public void preStart() { }); } - @Override - public void onReceive(Object message) { - if (message instanceof Retry) { - Retry retry = (Retry) message; - retryPart(retry.getBlockIndex(), retry.getFileOffset(), retry.getAttempt()); - } else { - super.onReceive(message); - } - } - private void requestUrl() { if (LOG) { Log.d(TAG, "Loading url..."); @@ -116,7 +118,54 @@ private void startDownload() { if (LOG) { Log.d(TAG, "Starting downloading " + blocksCount + " blocks"); } - checkQueue(); + + Promises.traverseParallel(SIM_BLOCKS_COUNT, partsSupplier(), new Consumer() { + + int index = 0; + + @Override + public void apply(HTTPResponse r) { + reportProgress((index + 1) / (float) blocksCount); + + if (fileReference.getEncryptionInfo() != null) { + + int offset = 0; + + if (index == 0) { + encryptionCipher = new CBCBlockCipherStream(ByteStrings.substring(r.getContent(), 0, 16), + Crypto.createAES128((ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 16)))); + + offset = 16; + } + + byte[] dest = new byte[r.getContent().length - offset]; + for (int i = 0; i < dest.length / encryptionCipher.getBlockSize(); i++) { + encryptionCipher.decryptBlock(r.getContent(), i * encryptionCipher.getBlockSize(), + dest, 0); + } + + if (index == 0) { + if (!outputFile.write(index * blockSize, dest, 0, dest.length)) { + throw new RuntimeException("Unable to write file"); + } + } else { + if (!outputFile.write(index * blockSize - 16, dest, 0, dest.length)) { + throw new RuntimeException("Unable to write file"); + } + } + } else { + if (!outputFile.write(index * blockSize, r.getContent(), 0, r.getContent().length)) { + throw new RuntimeException("Unable to write file"); + } + } + + index++; + } + }).then(r -> { + completeDownload(); + }).failure(e -> { + completeWithError(); + }); } private void completeDownload() { @@ -145,82 +194,61 @@ private void completeDownload() { reportComplete(reference); } - private void checkQueue() { - if (isCompleted) { - return; - } - - if (LOG) { - Log.d(TAG, "checkQueue " + currentDownloads + "/" + nextBlock); - } - if (currentDownloads == 0 && nextBlock >= blocksCount) { - completeDownload(); - } else if (currentDownloads < SIM_BLOCKS_COUNT && nextBlock < blocksCount) { - currentDownloads++; - int blockIndex = nextBlock++; - int offset = blockIndex * blockSize; - - if (LOG) { - Log.d(TAG, "Starting part #" + blockIndex + " download"); - } - - downloadPart(blockIndex, offset, 0); - - checkQueue(); - } else { - if (LOG) { - Log.d(TAG, "Task queue is full"); - } - } + private void completeWithError() { + reportError(); } + // Downloading parts - private void retryPart(int blockIndex, int fileOffset, int attempt) { - if (isCompleted) { - return; - } - - if (LOG) { - Log.d(TAG, "Trying again part #" + blockIndex + " download"); - } - - downloadPart(blockIndex, fileOffset, attempt); - } + private Supplier> partsSupplier() { + return new Supplier>() { + int nextBlock = 0; - private void downloadPart(final int blockIndex, final int fileOffset, final int attempt) { - HTTP.getMethod(fileUrl, fileOffset, blockSize, fileReference.getFileSize()).then(r -> { - downloaded++; - if (LOG) { - Log.d(TAG, "Download part #" + blockIndex + " completed"); - } - if (!outputFile.write(fileOffset, r.getContent(), 0, r.getContent().length)) { - reportError(); - return; + @Override + public Promise get() { + if (nextBlock >= blocksCount) { + return null; + } + int blockIndex = nextBlock++; + int offset = blockIndex * blockSize; + return downloadPartPromise(blockIndex, offset, 0); } - currentDownloads--; - reportProgress(downloaded / (float) blocksCount); - checkQueue(); - }).failure(e -> { - if ((e instanceof HTTPError) - && ((((HTTPError) e).getErrorCode() >= 500 - && ((HTTPError) e).getErrorCode() < 600) - || ((HTTPError) e).getErrorCode() == 0)) { - // Server on unknown error - int retryInSecs = DEFAULT_RETRY; + }; + } + private Promise downloadPartPromise(int blockIndex, int fileOffset, int attempt) { + return new Promise<>((PromiseFunc) resolver -> { + HTTP.getMethod(fileUrl, fileOffset, blockSize, fileReference.getFileSize()).then(r -> { if (LOG) { - Log.w(TAG, "Download part #" + blockIndex + " failure #" + ((HTTPError) e).getErrorCode() + " trying again in " + retryInSecs + " sec, attempt #" + (attempt + 1)); + Log.d(TAG, "Download part #" + blockIndex + " completed"); } - - self().send(new Retry(blockIndex, fileOffset, attempt + 1)); - } else { - if (LOG) { - Log.d(TAG, "Download part #" + blockIndex + " failure"); + resolver.result(r); + }).failure(e -> { + if ((e instanceof HTTPError) + && ((((HTTPError) e).getErrorCode() >= 500 + && ((HTTPError) e).getErrorCode() < 600) + || ((HTTPError) e).getErrorCode() == 0)) { + // Server on unknown error + int retryInSecs = DEFAULT_RETRY; + + if (LOG) { + Log.w(TAG, "Download part #" + blockIndex + " failure #" + ((HTTPError) e).getErrorCode() + " trying again in " + retryInSecs + " sec, attempt #" + (attempt + 1)); + } + + schedule((Runnable) () -> { + downloadPartPromise(blockIndex, fileOffset, attempt + 1).pipeTo(resolver); + }, retryInSecs * 1000L); + } else { + if (LOG) { + Log.d(TAG, "Download part #" + blockIndex + " failure"); + } + resolver.error(e); } - reportError(); - } + }); }); } + // Reporting + private void reportError() { if (isCompleted) { return; @@ -267,29 +295,4 @@ private void reportComplete(FileSystemReference reference) { isCompleted = true; manager.send(new DownloadManager.OnDownloaded(fileReference.getFileId(), reference)); } - - private class Retry { - - private int blockIndex; - private int fileOffset; - private int attempt; - - public Retry(int blockIndex, int fileOffset, int attempt) { - this.blockIndex = blockIndex; - this.fileOffset = fileOffset; - this.attempt = attempt; - } - - public int getBlockIndex() { - return blockIndex; - } - - public int getFileOffset() { - return fileOffset; - } - - public int getAttempt() { - return attempt; - } - } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index 0653658e02..e8515439e3 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -192,7 +192,7 @@ private void checkQueue() { Log.d(TAG, "Upload completed..."); } FileReference location = new FileReference(r.getUploadedFileLocation(), fileName, - finalSize); + finalSize, null); // Need to pass Encryption Info? if (isWriteToDestProvider || alreadyInTemp) { FileSystemReference reference = Storage.commitTempFile(alreadyInTemp ? srcReference : destReference, location.getFileId(), diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java index 97b2afc3e7..591694744c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateProcessor.java @@ -139,7 +139,6 @@ public Promise applyDifferenceUpdate(List updates) { pending.add(() -> messagesProcessor.onDifferenceEnd()); - return Promises.traverse(pending) - .map(v -> null); + return Promises.traverse(pending); } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java index c976bc840b..99b3a58de6 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java @@ -2,10 +2,15 @@ import com.google.j2objc.annotations.ObjectiveCName; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; import java.util.List; import im.actor.runtime.Log; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.function.Consumer; +import im.actor.runtime.function.Function; import im.actor.runtime.function.Supplier; import im.actor.runtime.function.Tuple2; import im.actor.runtime.function.Tuple3; @@ -95,13 +100,104 @@ public static Promise> tuple(Promise * @param type of promises * @return promise */ - public static Promise traverse(List>> queue) { + public static Promise traverse(List>> queue) { if (queue.size() == 0) { return Promise.success(null); } return queue.remove(0).get() - .flatMap(v -> traverse(queue)); + .flatMap(v -> traverse(queue)) + .map(r -> null); + } + + /** + * Executing promises in parallel but return results consequently + * + * @param parallelFraction number of parallel promises + * @param next supplier of next promise + * @param consumer Consumer of results + * @param type of promises + */ + public static Promise traverseParallel(int parallelFraction, Supplier> next, Consumer consumer) { + return new Promise<>((PromiseFunc) resolver -> { + TraverseHolder state = new TraverseHolder<>(next, consumer, resolver); + for (int i = 0; i < parallelFraction; i++) { + state.spawn(); + } + }); + } + + private static class TraverseHolder { + + public Supplier> next; + public Consumer consumer; + public Promise latestPromise; + public PromiseResolver resolver; + public boolean isEnded; + public boolean isFetchingEnded; + + public TraverseHolder(Supplier> next, Consumer consumer, PromiseResolver resolver) { + this.next = next; + this.consumer = consumer; + this.resolver = resolver; + } + + + public void spawn() { + if (isFetchingEnded || isEnded) { + return; + } + Promise p = next.get(); + if (p == null) { + isFetchingEnded = true; + if (latestPromise == null) { + isEnded = true; + resolver.result(null); + } else { + latestPromise.then(t -> { + if (!isEnded) { + isEnded = true; + } + resolver.result(null); + }); + } + return; + } + + Promise chained; + if (latestPromise == null) { + chained = p; + } else { + Promise forChain = latestPromise; + chained = p.chain(r -> forChain); + } + latestPromise = chained; + + + chained.then(t -> { + if (isEnded) { + return; + } + + try { + consumer.apply(t); + } catch (Exception e) { + isEnded = true; + resolver.error(e); + return; + } + + spawn(); + }); + + chained.failure(e -> { + if (isEnded) { + return; + } + isEnded = true; + resolver.error(e); + }); + } } } \ No newline at end of file From 3e808f5500b65df6187835f154f59f1c1e5e5b5c Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 24 Aug 2016 17:51:49 +0300 Subject: [PATCH 69/81] feat(iOS): iOS compatibility --- .../Cell/AABubbleBaseFileCell.swift | 2 +- .../actor/core/modules/file/DownloadTask.java | 23 +++++++++++-------- .../actor/core/modules/file/UploadTask.java | 5 ++++ .../im/actor/runtime/promise/Promises.java | 3 +++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift index e8edb7ed1b..8f8c5702a9 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/Controllers/Content/Conversation/Cell/AABubbleBaseFileCell.swift @@ -90,7 +90,7 @@ public class AABubbleBaseFileCell: AABubbleCell { self.fileStateChanged(reference, progress: nil, isPaused: false, isUploading: false, selfGeneration: selfGeneration) }) - Actor.bindRawFileWithReference(ACFileReference(ARApiFileLocation: file.reference.getFileLocation(), withNSString: file.reference.fileName, withInt: file.reference.fileSize), autoStart: autoDownload, withCallback: bindedDownloadCallback) + Actor.bindRawFileWithReference(file.reference, autoStart: autoDownload, withCallback: bindedDownloadCallback) } else { fatalError("Unsupported message type") } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java index 832662fb1c..5e06eebe2b 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java @@ -4,8 +4,6 @@ package im.actor.core.modules.file; -import org.jetbrains.annotations.NotNull; - import im.actor.core.entity.FileReference; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; @@ -15,8 +13,8 @@ import im.actor.runtime.Storage; import im.actor.runtime.actors.ActorRef; import im.actor.runtime.actors.ActorCancellable; +import im.actor.runtime.actors.messages.Void; import im.actor.runtime.crypto.BlockCipher; -import im.actor.runtime.crypto.primitives.aes.AESFastEngine; import im.actor.runtime.crypto.primitives.modes.CBCBlockCipherStream; import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.files.FileSystemReference; @@ -27,11 +25,13 @@ import im.actor.runtime.http.HTTPResponse; import im.actor.runtime.promise.Promise; import im.actor.runtime.promise.PromiseFunc; -import im.actor.runtime.promise.PromiseResolver; import im.actor.runtime.promise.Promises; public class DownloadTask extends ModuleActor { + // j2objc workaround + private static final Void DUMB = null; + private static final int SIM_BLOCKS_COUNT = 4; private static final int NOTIFY_THROTTLE = 1000; private static final int DEFAULT_RETRY = 15; @@ -132,16 +132,18 @@ public void apply(HTTPResponse r) { int offset = 0; if (index == 0) { - encryptionCipher = new CBCBlockCipherStream(ByteStrings.substring(r.getContent(), 0, 16), - Crypto.createAES128((ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 16)))); - + byte[] iv = ByteStrings.substring(r.getContent(), 0, 16); + byte[] key = ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 16); + Log.d(TAG, "File IV: " + Crypto.hex(iv)); + Log.d(TAG, "File Key: " + Crypto.hex(key)); + encryptionCipher = new CBCBlockCipherStream(iv, Crypto.createAES128(key)); offset = 16; } byte[] dest = new byte[r.getContent().length - offset]; - for (int i = 0; i < dest.length / encryptionCipher.getBlockSize(); i++) { - encryptionCipher.decryptBlock(r.getContent(), i * encryptionCipher.getBlockSize(), - dest, 0); + for (int i = 0; i < (dest.length - offset) / encryptionCipher.getBlockSize(); i++) { + encryptionCipher.decryptBlock(r.getContent(), offset + i * encryptionCipher.getBlockSize(), + dest, i * encryptionCipher.getBlockSize()); } if (index == 0) { @@ -197,6 +199,7 @@ private void completeDownload() { private void completeWithError() { reportError(); } + // Downloading parts private Supplier> partsSupplier() { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index e8515439e3..9d735c7d39 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -27,6 +27,7 @@ import im.actor.runtime.crypto.primitives.modes.CBCBlockCipher; import im.actor.runtime.crypto.primitives.modes.CBCBlockCipherStream; import im.actor.runtime.crypto.primitives.padding.PKCS7Padding; +import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.files.FileSystemReference; import im.actor.runtime.files.InputFile; import im.actor.runtime.files.OutputFile; @@ -136,6 +137,10 @@ public void preStart() { if (encryptionInfo != null) { encryptionIv = Crypto.randomBytes(16); encryptionCipher = new CBCBlockCipherStream(encryptionIv, Crypto.createAES128(encryptionInfo.getEncryptionKey())); + + Log.d(TAG, "File IV: " + Crypto.hex(encryptionIv)); + Log.d(TAG, "File Key: " + Crypto.hex(encryptionInfo.getEncryptionKey())); + finalSize = 16/*IV*/ + (int) (Math.ceil(srcReference.getSize() / (float) encryptionCipher.getBlockSize())); } else { finalSize = srcReference.getSize(); diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java index 99b3a58de6..81af89f631 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/promise/Promises.java @@ -21,6 +21,9 @@ */ public class Promises { + // j2objc workaround + private static final Void DUMB = null; + @ObjectiveCName("logWithTag:withResolver:withFunc:") public static Promise log(final String TAG, final PromiseResolver resolver, final PromiseFunc func) { return new Promise(r -> func.exec(r)).then(t -> { From 2725b5f3ab74bea043810390994653db9e767081 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Wed, 24 Aug 2016 18:44:15 +0300 Subject: [PATCH 70/81] fix(core): Fixing not making difference on encrypted message from unknown user --- .../src/main/java/im/actor/core/modules/file/UploadTask.java | 1 + .../core/modules/sequence/processor/UpdateValidator.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index 9d735c7d39..2138023296 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -4,6 +4,7 @@ package im.actor.core.modules.file; +import im.actor.core.api.ApiDocumentEncryptionInfo; import im.actor.core.api.rpc.RequestCommitFileUpload; import im.actor.core.api.rpc.RequestGetFileUploadPartUrl; import im.actor.core.api.rpc.RequestGetFileUploadUrl; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java index 21f55884fd..858ec3e252 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/sequence/processor/UpdateValidator.java @@ -11,6 +11,7 @@ import im.actor.core.api.updates.UpdateContactRegistered; import im.actor.core.api.updates.UpdateContactsAdded; import im.actor.core.api.updates.UpdateContactsRemoved; +import im.actor.core.api.updates.UpdateEncryptedPackage; import im.actor.core.api.updates.UpdateGroupExtChanged; import im.actor.core.api.updates.UpdateGroupFullExtChanged; import im.actor.core.api.updates.UpdateGroupMemberAdminChanged; @@ -104,7 +105,11 @@ public boolean isCausesInvalidation(Update update) { } else if (update instanceof UpdateGroupExtChanged) { UpdateGroupExtChanged extChanged = (UpdateGroupExtChanged) update; groups.add(extChanged.getGroupId()); + } else if (update instanceof UpdateEncryptedPackage) { + UpdateEncryptedPackage updateEncryptedPackage = (UpdateEncryptedPackage) update; + users.add(updateEncryptedPackage.getSenderId()); } + if (!hasUsers(users)) { return true; } From 82dc23ba6d6303ddae1a7016dcd67ef70993b11d Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 25 Aug 2016 20:01:22 +0300 Subject: [PATCH 71/81] feat(core+iOS+android): Working encrypted file transfer --- .../ActorCore/Providers/CocoaCrypto.swift | 10 ++-- .../ActorSDK/Sources/ActorSDK.swift | 2 +- .../core/entity/content/AnimationContent.java | 2 +- .../core/entity/content/DocumentContent.java | 2 +- .../core/entity/content/PhotoContent.java | 2 +- .../core/entity/content/VideoContent.java | 2 +- .../core/entity/content/VoiceContent.java | 2 +- .../core/modules/file/DownloadManager.java | 9 ++- .../actor/core/modules/file/DownloadTask.java | 4 +- .../actor/core/modules/file/FilesModule.java | 7 +-- .../core/modules/file/UploadManager.java | 24 ++++---- .../actor/core/modules/file/UploadTask.java | 57 ++++++++++-------- .../messaging/actions/SenderActor.java | 60 +++++++------------ .../actions/entity/PendingMessage.java | 20 +++---- .../avatar/GroupAvatarChangeActor.java | 2 +- .../profile/avatar/OwnAvatarChangeActor.java | 2 +- .../core/network/mtp/actors/ManagerActor.java | 4 +- .../runtime/cocoa/CocoaCryptoProvider.java | 6 +- .../crypto/CocoaCryptoProxyProvider.java | 4 +- .../main/java/im/actor/runtime/Crypto.java | 15 ++++- .../java/im/actor/runtime/CryptoRuntime.java | 2 +- .../actor/runtime/DefaultCryptoRuntime.java | 2 +- .../im/actor/runtime/crypto/box/ActorBox.java | 5 +- .../modes/CBCBlockCipherStream.java | 35 +++++------ .../main/java/im/actor/runtime/util/Hex.java | 8 +++ 25 files changed, 148 insertions(+), 140 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift index 71c6adf5bf..a0bdee0fc0 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorCore/Providers/CocoaCrypto.swift @@ -11,8 +11,8 @@ class CocoaCrypto: NSObject, ARCocoaCryptoProxyProvider { return SHA256Digest() } - func createAES128WithKey(key: IOSByteArray!) -> ARBlockCipher! { - return AES128(key: key) + func createAES256WithKey(key: IOSByteArray!) -> ARBlockCipher! { + return AES256(key: key) } } @@ -55,7 +55,7 @@ class SHA256Digest: NSObject, ARDigest { } } -class AES128: NSObject, ARBlockCipher { +class AES256: NSObject, ARBlockCipher { var encryptor = UnsafeMutablePointer.alloc(1) var decryptor = UnsafeMutablePointer.alloc(1) @@ -91,7 +91,7 @@ class AES128: NSObject, ARBlockCipher { .advancedBy(Int(destOffset)) var bytesOut: Int = 0 - CCCryptorUpdate(encryptor.memory, src, 16, dst, 32, &bytesOut) + CCCryptorUpdate(encryptor.memory, src, 16, dst, 16, &bytesOut) } func decryptBlock(data: IOSByteArray!, withOffset offset: jint, toDest dest: IOSByteArray!, withOffset destOffset: jint) { @@ -102,7 +102,7 @@ class AES128: NSObject, ARBlockCipher { .advancedBy(Int(destOffset)) var bytesOut: Int = 0 - CCCryptorUpdate(decryptor.memory, src, 16, dst, 32, &bytesOut) + CCCryptorUpdate(decryptor.memory, src, 16, dst, 16, &bytesOut) } func getBlockSize() -> jint { diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift index 4703f16fd2..60c7569305 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorSDK.swift @@ -285,7 +285,7 @@ import DZNWebViewController builder.setVoiceCallsEnabled(jboolean(enableCalls)) builder.setVideoCallsEnabled(jboolean(enableCalls)) builder.setIsEnabledGroupedChatList(false) - // builder.setEnableFilesLogging(true) + builder.setEnableFilesLogging(true) // Creating messenger messenger = ACCocoaMessenger(configuration: builder.build()) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java index 5a3d13f985..e0f7d5124e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/AnimationContent.java @@ -39,7 +39,7 @@ public static AnimationContent createRemoteAnimation(FileReference reference, in fastThumb.getImage()) : null, new ApiDocumentExAnimation(w, h), - null))); + reference.getEncryptionInfo()))); } private int w; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java index 5e9f40003d..be49db2ecf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/DocumentContent.java @@ -31,7 +31,7 @@ public static DocumentContent createRemoteDocument(FileReference reference, Fast "image/jpeg", fastThumb != null ? new ApiFastThumb(fastThumb.getW(), fastThumb.getH(), fastThumb.getImage()) : null, null, - null))); + reference.getEncryptionInfo()))); } protected FileSource source; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java index 9ddd4ce97a..d16d17e8e2 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/PhotoContent.java @@ -47,7 +47,7 @@ public static PhotoContent createRemotePhoto(@NotNull FileReference reference, i fastThumb.getImage()) : null, new ApiDocumentExPhoto(w, h), - null))); + reference.getEncryptionInfo()))); } private int w; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java index 069eb636f6..0aafc41446 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VideoContent.java @@ -42,7 +42,7 @@ public static VideoContent createRemoteVideo(FileReference reference, int w, int fastThumb.getImage()) : null, new ApiDocumentExVideo(w, h, duration), - null))); + reference.getEncryptionInfo()))); } private int duration; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java index 2d1da65639..93568db5aa 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/content/VoiceContent.java @@ -42,7 +42,7 @@ public static VoiceContent createRemoteAudio(@NotNull FileReference reference, i "audio/mp3", null, new ApiDocumentExVoice(duration), - null))); + reference.getEncryptionInfo()))); } private int duration; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java index 56f0c8a54d..0dc01dcfee 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java @@ -58,6 +58,7 @@ public void requestState(long fileId, final FileCallback callback) { FileSystemReference reference = Storage.fileFromDescriptor(downloaded1.getDescriptor()); boolean isExist = reference.isExist(); int fileSize = reference.getSize(); + if (isExist && fileSize == downloaded1.getFileSize()) { if (LOG) { Log.d(TAG, "- Downloaded"); @@ -379,8 +380,12 @@ public void onDownloaded(final long fileId, final FileSystemReference reference) return; } - downloaded.addOrUpdateItem(new Downloaded(queueItem.fileReference.getFileId(), - queueItem.fileReference.getFileSize(), reference.getDescriptor())); + int fileSize = queueItem.fileReference.getFileSize(); + if (queueItem.fileReference.getEncryptionInfo() != null) { + fileSize = queueItem.fileReference.getFileSize(); + } + downloaded.addOrUpdateItem(new Downloaded(queueItem.fileReference.getFileId(), fileSize, + reference.getDescriptor())); queue.remove(queueItem); queueItem.taskRef.send(PoisonPill.INSTANCE); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java index 5e06eebe2b..dd54f0a61f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java @@ -133,10 +133,10 @@ public void apply(HTTPResponse r) { if (index == 0) { byte[] iv = ByteStrings.substring(r.getContent(), 0, 16); - byte[] key = ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 16); + byte[] key = ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 32); Log.d(TAG, "File IV: " + Crypto.hex(iv)); Log.d(TAG, "File Key: " + Crypto.hex(key)); - encryptionCipher = new CBCBlockCipherStream(iv, Crypto.createAES128(key)); + encryptionCipher = new CBCBlockCipherStream(iv, Crypto.createAES256(key)); offset = 16; } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java index 2fbb4139fa..3bf890039e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/FilesModule.java @@ -122,10 +122,9 @@ public void unbindUploadFile(long rid, UploadFileCallback callback) { uploadManager.send(new UploadManager.UnbindUpload(rid, callback)); } - public void requestUpload(long rid, String descriptor, String fileName, - EncryptionInfo encryptionInfo, ActorRef requester) { - uploadManager.send(new UploadManager.StartUpload(rid, descriptor, fileName, encryptionInfo), - requester); + public void requestUpload(long rid, String descriptor, String fileName, boolean encrypt, + ActorRef requester) { + uploadManager.send(new UploadManager.StartUpload(rid, descriptor, fileName, encrypt), requester); } public void cancelUpload(long rid) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java index 0e88d239a2..c18fa3418f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadManager.java @@ -39,11 +39,11 @@ public UploadManager(ModuleContext context) { // Tasks public void startUpload(long rid, String descriptor, String fileName, - EncryptionInfo encryptionInfo, ActorRef requestActor) { + boolean doEncrypt, ActorRef requestActor) { if (LOG) { Log.d(TAG, "Starting upload #" + rid + " with descriptor " + descriptor); } - QueueItem queueItem = new QueueItem(rid, descriptor, fileName, encryptionInfo, requestActor); + QueueItem queueItem = new QueueItem(rid, descriptor, fileName, doEncrypt, requestActor); queueItem.isStopped = false; queue.add(queueItem); checkQueue(); @@ -296,7 +296,7 @@ private void checkQueue() { final QueueItem finalPendingQueue = pendingQueue; pendingQueue.taskRef = system().actorOf(Props.create(() -> new UploadTask(finalPendingQueue.rid, finalPendingQueue.fileDescriptor, - finalPendingQueue.fileName, finalPendingQueue.encryptionInfo, self(), context())).changeDispatcher("heavy"), "actor/upload/task_" + RandomUtils.nextRid()); + finalPendingQueue.fileName, finalPendingQueue.doEncrypt, self(), context())).changeDispatcher("heavy"), "actor/upload/task_" + RandomUtils.nextRid()); } private QueueItem findItem(long rid) { @@ -314,17 +314,17 @@ private class QueueItem { private boolean isStopped; private boolean isStarted; private float progress; - private EncryptionInfo encryptionInfo; + private boolean doEncrypt; private ActorRef taskRef; private ActorRef requestActor; private String fileName; - private QueueItem(long rid, String fileDescriptor, String fileName, EncryptionInfo encryptionInfo, ActorRef requestActor) { + private QueueItem(long rid, String fileDescriptor, String fileName, boolean doEncrypt, ActorRef requestActor) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.requestActor = requestActor; this.fileName = fileName; - this.encryptionInfo = encryptionInfo; + this.doEncrypt = doEncrypt; } } @@ -335,7 +335,7 @@ public void onReceive(Object message) { if (message instanceof StartUpload) { StartUpload startUpload = (StartUpload) message; startUpload(startUpload.getRid(), startUpload.getFileDescriptor(), - startUpload.getFileName(), startUpload.getEncryptionInfo(), sender()); + startUpload.getFileName(), startUpload.isDoEncrypt(), sender()); } else if (message instanceof StopUpload) { StopUpload cancelUpload = (StopUpload) message; stopUpload(cancelUpload.getRid()); @@ -373,13 +373,13 @@ public static class StartUpload { private long rid; private String fileDescriptor; private String fileName; - private EncryptionInfo encryptionInfo; + private boolean doEncrypt; - public StartUpload(long rid, String fileDescriptor, String fileName, EncryptionInfo encryptionInfo) { + public StartUpload(long rid, String fileDescriptor, String fileName, boolean doEncrypt) { this.rid = rid; this.fileDescriptor = fileDescriptor; this.fileName = fileName; - this.encryptionInfo = encryptionInfo; + this.doEncrypt = doEncrypt; } public long getRid() { @@ -394,8 +394,8 @@ public String getFileName() { return fileName; } - public EncryptionInfo getEncryptionInfo() { - return encryptionInfo; + public boolean isDoEncrypt() { + return doEncrypt; } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index 2138023296..d7d87ca396 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -8,14 +8,9 @@ import im.actor.core.api.rpc.RequestCommitFileUpload; import im.actor.core.api.rpc.RequestGetFileUploadPartUrl; import im.actor.core.api.rpc.RequestGetFileUploadUrl; -import im.actor.core.api.rpc.ResponseCommitFileUpload; -import im.actor.core.api.rpc.ResponseGetFileUploadUrl; import im.actor.core.entity.FileReference; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; -import im.actor.core.modules.file.entity.EncryptionInfo; -import im.actor.core.network.RpcCallback; -import im.actor.core.network.RpcException; import im.actor.runtime.Crypto; import im.actor.runtime.HTTP; import im.actor.runtime.Log; @@ -24,19 +19,16 @@ import im.actor.runtime.actors.ActorCancellable; import im.actor.runtime.crypto.BlockCipher; import im.actor.runtime.crypto.CRC32; -import im.actor.runtime.crypto.primitives.Padding; -import im.actor.runtime.crypto.primitives.modes.CBCBlockCipher; import im.actor.runtime.crypto.primitives.modes.CBCBlockCipherStream; -import im.actor.runtime.crypto.primitives.padding.PKCS7Padding; import im.actor.runtime.crypto.primitives.util.ByteStrings; import im.actor.runtime.files.FileSystemReference; -import im.actor.runtime.files.InputFile; import im.actor.runtime.files.OutputFile; import im.actor.runtime.files.SequenceFileSystemInputFile; import im.actor.runtime.files.SequenceInputFile; import im.actor.runtime.http.HTTPError; import im.actor.runtime.http.HTTPResponse; import im.actor.runtime.promise.Promise; +import im.actor.runtime.util.Hex; public class UploadTask extends ModuleActor { @@ -51,11 +43,12 @@ public class UploadTask extends ModuleActor { private final String TAG; private final boolean LOG; - private long rid; - private String fileName; - private String descriptor; - private EncryptionInfo encryptionInfo; + private final long rid; + private final String fileName; + private final String descriptor; + private final boolean isEncrypted; private BlockCipher encryptionCipher; + private byte[] encryptionKey; private byte[] encryptionIv; private boolean isWriteToDestProvider = false; @@ -84,14 +77,14 @@ public class UploadTask extends ModuleActor { private float currentProgress; private boolean alreadyInTemp; - public UploadTask(long rid, String descriptor, String fileName, EncryptionInfo encryptionInfo, + public UploadTask(long rid, String descriptor, String fileName, boolean isEncrypted, ActorRef manager, ModuleContext context) { super(context); this.LOG = context.getConfiguration().isEnableFilesLogging(); this.rid = rid; this.fileName = fileName; this.descriptor = descriptor; - this.encryptionInfo = encryptionInfo; + this.isEncrypted = isEncrypted; this.manager = manager; this.TAG = "UploadTask{" + rid + "}"; } @@ -134,17 +127,21 @@ public void preStart() { // CRC crc32 = new CRC32(); - // Size & Encryption - if (encryptionInfo != null) { + // File Size + finalSize = srcReference.getSize(); + + // Encryption + if (isEncrypted) { + encryptionKey = Crypto.randomBytes(32); encryptionIv = Crypto.randomBytes(16); - encryptionCipher = new CBCBlockCipherStream(encryptionIv, Crypto.createAES128(encryptionInfo.getEncryptionKey())); Log.d(TAG, "File IV: " + Crypto.hex(encryptionIv)); - Log.d(TAG, "File Key: " + Crypto.hex(encryptionInfo.getEncryptionKey())); + Log.d(TAG, "File Key: " + Crypto.hex(encryptionKey)); - finalSize = 16/*IV*/ + (int) (Math.ceil(srcReference.getSize() / (float) encryptionCipher.getBlockSize())); - } else { - finalSize = srcReference.getSize(); + encryptionCipher = new CBCBlockCipherStream(encryptionIv, Crypto.createAES256(encryptionKey)); + + // Overwrite file size + finalSize = 16/*IV*/ + Crypto.paddedLength(srcReference.getSize(), encryptionCipher.getBlockSize()); } // Blocks @@ -197,8 +194,17 @@ private void checkQueue() { if (LOG) { Log.d(TAG, "Upload completed..."); } + + ApiDocumentEncryptionInfo encryptionInfo; + if (isEncrypted) { + encryptionInfo = new ApiDocumentEncryptionInfo(srcReference.getSize(), + "aes128-hmac", encryptionKey); + } else { + encryptionInfo = null; + } + FileReference location = new FileReference(r.getUploadedFileLocation(), fileName, - finalSize, null); // Need to pass Encryption Info? + finalSize, encryptionInfo); if (isWriteToDestProvider || alreadyInTemp) { FileSystemReference reference = Storage.commitTempFile(alreadyInTemp ? srcReference : destReference, location.getFileId(), @@ -226,7 +232,7 @@ private void loadPart(final int blockIndex) { int fileOffset = blockIndex * blockSize; // Calculating appropriate read block size - if (encryptionInfo != null) { + if (isEncrypted) { if (blockIndex == 0) { if (srcReference.getSize() - IV_SIZE > blockSize) { size = blockSize - IV_SIZE; @@ -271,8 +277,7 @@ private void loadPart(final int blockIndex) { uploadCount++; // Block Encryption - if (encryptionInfo != null) { - + if (isEncrypted) { // Result Block int destBlockCount = (int) Math.ceil(filePart.getContents().length / (float) encryptionCipher.getBlockSize()); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java index 89c0a9244f..3371b6383c 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/SenderActor.java @@ -27,11 +27,8 @@ import im.actor.core.api.ApiDocumentMessage; import im.actor.core.api.ApiOutPeer; import im.actor.core.api.ApiTextMessage; -import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.base.SeqUpdate; -import im.actor.core.api.rpc.RequestSendEncryptedPackage; import im.actor.core.api.rpc.RequestSendMessage; -import im.actor.core.api.rpc.ResponseSendEncryptedPackage; import im.actor.core.api.rpc.ResponseSeqDate; import im.actor.core.api.updates.UpdateMessageSent; import im.actor.core.entity.FileReference; @@ -60,7 +57,6 @@ import im.actor.core.entity.content.internal.ContentRemoteContainer; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.file.UploadManager; -import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.core.modules.messaging.actions.entity.PendingMessage; import im.actor.core.modules.messaging.actions.entity.PendingMessagesStorage; import im.actor.core.modules.ModuleActor; @@ -69,8 +65,6 @@ import im.actor.core.network.RpcException; import im.actor.runtime.*; import im.actor.runtime.Runtime; -import im.actor.runtime.crypto.primitives.util.ByteStrings; -import im.actor.runtime.function.Consumer; import im.actor.runtime.power.WakeLock; /*-[ @@ -110,8 +104,7 @@ public void preStart() { pending.getContent() instanceof StickerContent || pending.getContent() instanceof LocationContent || pending.getContent() instanceof ContactContent) { - performSendContent(pending.getPeer(), pending.getRid(), pending.getTimer(), - pending.getEncryptionInfo(), pending.getContent()); + performSendContent(pending.getPeer(), pending.getRid(), pending.getTimer(), pending.getContent()); } else if (pending.getContent() instanceof DocumentContent) { DocumentContent documentContent = (DocumentContent) pending.getContent(); if (documentContent.getSource() instanceof FileLocalSource) { @@ -119,7 +112,7 @@ public void preStart() { performUploadFile(pending.getRid(), ((FileLocalSource) documentContent.getSource()).getFileDescriptor(), ((FileLocalSource) documentContent.getSource()).getFileName(), - pending.getEncryptionInfo()); + pending.isEncryptedFile()); } else { List rids = new ArrayList<>(); rids.add(pending.getRid()); @@ -129,7 +122,7 @@ public void preStart() { } } else { performSendContent(pending.getPeer(), pending.getRid(), - pending.getTimer(), pending.getEncryptionInfo(), pending.getContent()); + pending.getTimer(), pending.getContent()); } } } @@ -183,12 +176,12 @@ public void doSendText(@NotNull Peer peer, @NotNull String text, TextContent content = TextContent.create(text, null, mentions); PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendJson(Peer peer, JsonContent content) { PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } // Sending sticker @@ -196,7 +189,7 @@ public void doSendSticker(@NotNull Peer peer, @NotNull Sticker sticker) { StickerContent content = StickerContent.create(sticker); PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendContact(@NotNull Peer peer, @@ -205,7 +198,7 @@ public void doSendContact(@NotNull Peer peer, @Nullable String base64photo) { ContactContent content = ContactContent.create(name, phones, emails, base64photo); PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doSendLocation(@NotNull Peer peer, @@ -213,12 +206,12 @@ public void doSendLocation(@NotNull Peer peer, @Nullable String street, @Nullable String place) { LocationContent content = LocationContent.create(longitude, latitude, street, place); PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } public void doForwardContent(Peer peer, AbsContent content) { PendingMessage pending = prepareSend(peer, content, false); - performSendContent(peer, pending.getRid(), pending.getTimer(), pending.getEncryptionInfo(), content); + performSendContent(peer, pending.getRid(), pending.getTimer(), content); } // Sending documents @@ -228,21 +221,21 @@ public void doSendDocument(Peer peer, String fileName, String mimeType, int file DocumentContent documentContent = DocumentContent.createLocal(fileName, fileSize, descriptor, mimeType, fastThumb); PendingMessage pending = prepareSend(peer, documentContent, true); - performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); + performUploadFile(pending.getRid(), descriptor, fileName, pending.isEncryptedFile()); } public void doSendPhoto(Peer peer, FastThumb fastThumb, String descriptor, String fileName, int fileSize, int w, int h) { PhotoContent photoContent = PhotoContent.createLocalPhoto(descriptor, fileName, fileSize, w, h, fastThumb); PendingMessage pending = prepareSend(peer, photoContent, true); - performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); + performUploadFile(pending.getRid(), descriptor, fileName, pending.isEncryptedFile()); } public void doSendAudio(Peer peer, String descriptor, String fileName, int fileSize, int duration) { VoiceContent audioContent = VoiceContent.createLocalAudio(descriptor, fileName, fileSize, duration); PendingMessage pending = prepareSend(peer, audioContent, true); - performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); + performUploadFile(pending.getRid(), descriptor, fileName, pending.isEncryptedFile()); } public void doSendVideo(Peer peer, String fileName, int w, int h, int duration, @@ -250,7 +243,7 @@ public void doSendVideo(Peer peer, String fileName, int w, int h, int duration, VideoContent videoContent = VideoContent.createLocalVideo(descriptor, fileName, fileSize, w, h, duration, fastThumb); PendingMessage pending = prepareSend(peer, videoContent, true); - performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); + performUploadFile(pending.getRid(), descriptor, fileName, pending.isEncryptedFile()); } public void doSendAnimation(Peer peer, String fileName, int w, int h, @@ -258,7 +251,7 @@ public void doSendAnimation(Peer peer, String fileName, int w, int h, AnimationContent animationContent = AnimationContent.createLocalAnimation(descriptor, fileName, fileSize, w, h, fastThumb); PendingMessage pending = prepareSend(peer, animationContent, true); - performUploadFile(pending.getRid(), descriptor, fileName, pending.getEncryptionInfo()); + performUploadFile(pending.getRid(), descriptor, fileName, pending.isEncryptedFile()); } private PendingMessage prepareSend(Peer peer, AbsContent content, boolean isFile) { @@ -273,19 +266,16 @@ private PendingMessage prepareSend(Peer peer, AbsContent content, boolean isFile Message message = new Message(rid, sortDate, date, myUid(), MessageState.PENDING, content, new ArrayList<>(), 0, timer); context().getMessagesModule().getRouter().onOutgoingMessage(peer, message); - EncryptionInfo encryptionInfo = null; - if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && isFile) { - encryptionInfo = new EncryptionInfo(Crypto.randomBytes(16), Crypto.randomBytes(32)); - } - PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer, encryptionInfo); + boolean isEncrypted = peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && isFile; + PendingMessage pendingMessage = new PendingMessage(peer, rid, content, timer, isEncrypted); pendingMessages.getPendingMessages().add(pendingMessage); savePending(); return pendingMessage; } - private void performUploadFile(long rid, String descriptor, String fileName, EncryptionInfo encryptionInfo) { + private void performUploadFile(long rid, String descriptor, String fileName, boolean encrypt) { fileUplaodingWakeLocks.put(rid, Runtime.makeWakeLock()); - context().getFilesModule().requestUpload(rid, descriptor, fileName, encryptionInfo, self()); + context().getFilesModule().requestUpload(rid, descriptor, fileName, encrypt, self()); } private void onFileUploaded(long rid, FileReference fileReference) { @@ -321,9 +311,9 @@ private void onFileUploaded(long rid, FileReference fileReference) { } pendingMessages.getPendingMessages().add(new PendingMessage(msg.getPeer(), msg.getRid(), - nContent, msg.getTimer(), msg.getEncryptionInfo())); + nContent, msg.getTimer(), msg.isEncryptedFile())); context().getMessagesModule().getRouter().onContentChanged(msg.getPeer(), msg.getRid(), nContent); - performSendContent(msg.getPeer(), rid, msg.getTimer(), msg.getEncryptionInfo(), nContent); + performSendContent(msg.getPeer(), rid, msg.getTimer(), nContent); fileUplaodingWakeLocks.remove(rid).releaseLock(); } @@ -339,7 +329,7 @@ private void onFileUploadError(long rid) { // Sending content - private void performSendContent(final Peer peer, final long rid, int timer, EncryptionInfo encryptionInfo, AbsContent content) { + private void performSendContent(final Peer peer, final long rid, int timer, AbsContent content) { WakeLock wakeLock = im.actor.runtime.Runtime.makeWakeLock(); ApiMessage message; @@ -373,12 +363,8 @@ private void performSendContent(final Peer peer, final long rid, int timer, Encr documentContent.getFastThumb().getImage()); } - ApiDocumentEncryptionInfo apiEncryptionInfo = null; - if (encryptionInfo != null) { - apiEncryptionInfo = new ApiDocumentEncryptionInfo(source.getSize(), "aes128-sha256", - ByteStrings.merge(encryptionInfo.getEncryptionKey(), - encryptionInfo.getMacKey())); - } + ApiDocumentEncryptionInfo apiEncryptionInfo = + source.getFileReference().getEncryptionInfo(); message = new ApiDocumentMessage(source.getFileReference().getFileId(), source.getFileReference().getAccessHash(), diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java index 3aae2dafce..3097c65261 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/actions/entity/PendingMessage.java @@ -6,7 +6,6 @@ import java.io.IOException; -import im.actor.core.modules.file.entity.EncryptionInfo; import im.actor.runtime.bser.Bser; import im.actor.runtime.bser.BserObject; import im.actor.runtime.bser.BserValues; @@ -25,14 +24,14 @@ public static PendingMessage fromBytes(byte[] data) throws IOException { private AbsContent content; private boolean isError; private int timer; - private EncryptionInfo encryptionInfo; + private boolean isEncryptedFile; - public PendingMessage(Peer peer, long rid, AbsContent content, int timer, EncryptionInfo encryptionInfo) { + public PendingMessage(Peer peer, long rid, AbsContent content, int timer, boolean isEncryptedFile) { this.peer = peer; this.rid = rid; this.content = content; this.timer = timer; - this.encryptionInfo = encryptionInfo; + this.isEncryptedFile = isEncryptedFile; } private PendingMessage() { @@ -59,8 +58,8 @@ public int getTimer() { return timer; } - public EncryptionInfo getEncryptionInfo() { - return encryptionInfo; + public boolean isEncryptedFile() { + return isEncryptedFile; } @Override @@ -70,10 +69,7 @@ public void parse(BserValues values) throws IOException { content = AbsContent.parse(values.getBytes(3)); isError = values.getBool(4, false); timer = values.getInt(5, 0); - byte[] dt = values.getBytes(6); - if (dt != null) { - encryptionInfo = EncryptionInfo.fromBytes(dt); - } + isEncryptedFile = values.getBool(7, false); } @Override @@ -83,8 +79,6 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBytes(3, AbsContent.serialize(content)); writer.writeBool(4, isError); writer.writeInt(5, timer); - if (encryptionInfo != null) { - writer.writeObject(6, encryptionInfo); - } + writer.writeBool(7, isEncryptedFile); } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java index 0f85911b43..d7b8ae8093 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/GroupAvatarChangeActor.java @@ -51,7 +51,7 @@ public void changeAvatar(int gid, String descriptor) { tasksMap.put(rid, gid); context().getGroupsModule().getAvatarVM(gid).getUploadState().change(new AvatarUploadState(descriptor, true)); - context().getFilesModule().requestUpload(rid, descriptor, "avatar.jpg", null, self()); + context().getFilesModule().requestUpload(rid, descriptor, "avatar.jpg", false, self()); } public void uploadCompleted(final long rid, FileReference fileReference) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java index 6e27cc5ad2..7e1595e382 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/profile/avatar/OwnAvatarChangeActor.java @@ -38,7 +38,7 @@ public void changeAvatar(String descriptor) { context().getProfileModule().getOwnAvatarVM().getUploadState().change(new AvatarUploadState(descriptor, true)); - context().getFilesModule().requestUpload(currentChangeTask, descriptor, "avatar.jpg", null, self()); + context().getFilesModule().requestUpload(currentChangeTask, descriptor, "avatar.jpg", false, self()); } public void uploadCompleted(final long rid, FileReference fileReference) { diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/mtp/actors/ManagerActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/mtp/actors/ManagerActor.java index adb270a646..bf833f0ac8 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/mtp/actors/ManagerActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/network/mtp/actors/ManagerActor.java @@ -85,7 +85,7 @@ public ManagerActor(MTProto mtProto) { if (this.authKey != null) { this.authProtoKey = new ActorProtoKey(this.authKey); this.serverUSDecryptor = new CBCHmacBox( - Crypto.createAES128(this.authProtoKey.getServerKey()), + Crypto.createAES256(this.authProtoKey.getServerKey()), Crypto.createSHA256(), this.authProtoKey.getServerMacKey()); this.serverRUDecryptor = new CBCHmacBox( @@ -93,7 +93,7 @@ public ManagerActor(MTProto mtProto) { new Streebog256(), this.authProtoKey.getServerMacRussianKey()); this.clientUSEncryptor = new CBCHmacBox( - Crypto.createAES128(this.authProtoKey.getClientKey()), + Crypto.createAES256(this.authProtoKey.getClientKey()), Crypto.createSHA256(), this.authProtoKey.getClientMacKey()); this.clientRUEncryptor = new CBCHmacBox( diff --git a/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/CocoaCryptoProvider.java b/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/CocoaCryptoProvider.java index 3b81365845..f54510ec29 100644 --- a/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/CocoaCryptoProvider.java +++ b/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/CocoaCryptoProvider.java @@ -30,10 +30,10 @@ public Digest SHA256() { } @Override - public BlockCipher AES128(byte[] key) { + public BlockCipher AES256(byte[] key) { if (proxyProvider != null) { - return proxyProvider.createAES128(key); + return proxyProvider.createAES256(key); } - return super.AES128(key); + return super.AES256(key); } } diff --git a/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/crypto/CocoaCryptoProxyProvider.java b/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/crypto/CocoaCryptoProxyProvider.java index 144ddabcb3..e6a335d31a 100644 --- a/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/crypto/CocoaCryptoProxyProvider.java +++ b/actor-sdk/sdk-core/runtime/runtime-cocoa/src/main/java/im/actor/runtime/cocoa/crypto/CocoaCryptoProxyProvider.java @@ -10,6 +10,6 @@ public interface CocoaCryptoProxyProvider { @ObjectiveCName("createSHA256") Digest createSHA256(); - @ObjectiveCName("createAES128WithKey:") - BlockCipher createAES128(byte[] key); + @ObjectiveCName("createAES256WithKey:") + BlockCipher createAES256(byte[] key); } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java index e80e9ed2c9..26b4be8052 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java @@ -26,8 +26,8 @@ public static Digest createSHA256() { return runtime.SHA256(); } - public static BlockCipher createAES128(byte[] key) { - return runtime.AES128(key); + public static BlockCipher createAES256(byte[] key) { + return runtime.AES256(key); } public static byte[] MD5(byte[] data) { @@ -97,4 +97,15 @@ public static String hex(byte[] bytes) { public static byte[] fromHex(String hex) { return Hex.fromHex(hex); } + + /** + * Calculating padded length + * + * @param sourceLength source length + * @param blockSize block size + * @return padded length + */ + public static int paddedLength(int sourceLength, int blockSize) { + return sourceLength + (blockSize - sourceLength % blockSize - 1); + } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/CryptoRuntime.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/CryptoRuntime.java index d57e59b96d..462a91606d 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/CryptoRuntime.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/CryptoRuntime.java @@ -14,7 +14,7 @@ public interface CryptoRuntime { Digest SHA256(); - BlockCipher AES128(byte[] key); + BlockCipher AES256(byte[] key); void waitForCryptoLoaded(); } \ No newline at end of file diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/DefaultCryptoRuntime.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/DefaultCryptoRuntime.java index 09c14514e3..ff4de7c672 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/DefaultCryptoRuntime.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/DefaultCryptoRuntime.java @@ -13,7 +13,7 @@ public Digest SHA256() { } @Override - public BlockCipher AES128(byte[] key) { + public BlockCipher AES256(byte[] key) { return new AESFastEngine(key); } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java index f98143f1f2..f299ed2b1b 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/box/ActorBox.java @@ -3,7 +3,6 @@ import im.actor.runtime.Crypto; import im.actor.runtime.crypto.IntegrityException; -import im.actor.runtime.crypto.primitives.aes.AESFastEngine; import im.actor.runtime.crypto.primitives.kuznechik.KuznechikFastEngine; import im.actor.runtime.crypto.primitives.padding.PKCS7Padding; import im.actor.runtime.crypto.primitives.streebog.Streebog256; @@ -34,7 +33,7 @@ public class ActorBox { */ public static byte[] openBox(byte[] header, byte[] cipherText, ActorBoxKey key) throws IntegrityException { CBCHmacBox aesCipher = - new CBCHmacBox(Crypto.createAES128(key.getKeyAES()), Crypto.createSHA256(), key.getMacAES()); + new CBCHmacBox(Crypto.createAES256(key.getKeyAES()), Crypto.createSHA256(), key.getMacAES()); CBCHmacBox kuzCipher = new CBCHmacBox(new KuznechikFastEngine(key.getKeyKuz()), new Streebog256(), key.getMacKuz()); byte[] kuzPackage = aesCipher.decryptPackage(header, @@ -68,7 +67,7 @@ public static byte[] openBox(byte[] header, byte[] cipherText, ActorBoxKey key) * @throws IntegrityException */ public static byte[] closeBox(byte[] header, byte[] plainText, byte[] random32, ActorBoxKey key) throws IntegrityException { - CBCHmacBox aesCipher = new CBCHmacBox(Crypto.createAES128(key.getKeyAES()), Crypto.createSHA256(), key.getMacAES()); + CBCHmacBox aesCipher = new CBCHmacBox(Crypto.createAES256(key.getKeyAES()), Crypto.createSHA256(), key.getMacAES()); CBCHmacBox kuzCipher = new CBCHmacBox(new KuznechikFastEngine(key.getKeyKuz()), new Streebog256(), key.getMacKuz()); // Calculating padding diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java index aa549070bd..393b7928c5 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/crypto/primitives/modes/CBCBlockCipherStream.java @@ -27,18 +27,19 @@ public CBCBlockCipherStream(byte[] iv, BlockCipher baseCipher) { @Override public void encryptBlock(byte[] data, int offset, byte[] dest, int destOffset) { - // DATA = SRC_DATA ^ IV - for (int j = 0; j < blockSize; j++) { - workingSet[j] = (byte) ((data[offset + j] & 0xFF) ^ (iv[j] & 0xFF)); - } +// // DATA = SRC_DATA ^ IV +// for (int j = 0; j < blockSize; j++) { +// workingSet[j] = (byte) ((data[offset + j] & 0xFF) ^ (iv[j] & 0xFF)); +// } // DEST = encrypt(DATA) - baseCipher.encryptBlock(workingSet, 0, dest, destOffset); + // baseCipher.encryptBlock(workingSet, 0, dest, destOffset); + baseCipher.encryptBlock(data, offset, dest, destOffset); - // IV = DEST - for (int j = 0; j < blockSize; j++) { - iv[j] = dest[destOffset + j]; - } +// // IV = DEST +// for (int j = 0; j < blockSize; j++) { +// iv[j] = dest[destOffset + j]; +// } } @Override @@ -47,15 +48,15 @@ public void decryptBlock(byte[] data, int offset, byte[] dest, int destOffset) { // DEST = decrypt(DATA) baseCipher.decryptBlock(data, offset, dest, destOffset); - // DEST_RES = DEST ^ IV - for (int j = 0; j < blockSize; j++) { - dest[destOffset + j] = (byte) ((dest[destOffset + j] & 0xFF) ^ (iv[j] & 0xFF)); - } +// // DEST_RES = DEST ^ IV +// for (int j = 0; j < blockSize; j++) { +// dest[destOffset + j] = (byte) ((dest[destOffset + j] & 0xFF) ^ (iv[j] & 0xFF)); +// } - // IV = DATA - for (int j = 0; j < blockSize; j++) { - iv[j] = data[offset + j]; - } +// // IV = DATA +// for (int j = 0; j < blockSize; j++) { +// iv[j] = data[offset + j]; +// } } @Override diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/util/Hex.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/util/Hex.java index 391bb160b8..5506ccb3df 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/util/Hex.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/util/Hex.java @@ -49,4 +49,12 @@ public static byte[] fromHex(String hex) { } return res; } + + public static byte[] fromHexReverse(String hex) { + byte[] res = new byte[hex.length() / 2]; + for (int i = 0; i < res.length; i++) { + res[res.length - i - 1] = (byte) ((fromHexShort(hex.charAt(i * 2)) << 4) + fromHexShort(hex.charAt(i * 2 + 1))); + } + return res; + } } From 4d0506fc02ce0b4a8a5e66ed59ce5858ce0b5fee Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 25 Aug 2016 20:20:05 +0300 Subject: [PATCH 72/81] fix(core): Fixing encrypted file size handling in DownloadManager --- .../core/modules/file/DownloadManager.java | 2 +- .../actor/core/modules/file/DownloadTask.java | 17 ++++++++++++++--- .../src/main/java/im/actor/runtime/Crypto.java | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java index 0dc01dcfee..9fc6dec563 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadManager.java @@ -382,7 +382,7 @@ public void onDownloaded(final long fileId, final FileSystemReference reference) int fileSize = queueItem.fileReference.getFileSize(); if (queueItem.fileReference.getEncryptionInfo() != null) { - fileSize = queueItem.fileReference.getFileSize(); + fileSize = queueItem.fileReference.getEncryptionInfo().getRealFileSize(); } downloaded.addOrUpdateItem(new Downloaded(queueItem.fileReference.getFileId(), fileSize, reference.getDescriptor())); diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java index dd54f0a61f..c3aef7f0be 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/DownloadTask.java @@ -79,7 +79,11 @@ public void preStart() { return; } - destReference.openWrite(fileReference.getFileSize()).then(r -> { + int destSize = fileReference.getFileSize(); + if (fileReference.getEncryptionInfo() != null) { + destSize = fileReference.getFileSize(); + } + destReference.openWrite(destSize).then(r -> { outputFile = r; requestUrl(); }).failure(e -> { @@ -130,16 +134,23 @@ public void apply(HTTPResponse r) { if (fileReference.getEncryptionInfo() != null) { int offset = 0; + int padding = 0; if (index == 0) { byte[] iv = ByteStrings.substring(r.getContent(), 0, 16); byte[] key = ByteStrings.substring(fileReference.getEncryptionInfo().getKey(), 0, 32); Log.d(TAG, "File IV: " + Crypto.hex(iv)); Log.d(TAG, "File Key: " + Crypto.hex(key)); + Log.d(TAG, "File Size: " + fileReference.getFileSize()); + Log.d(TAG, "File Real Size: " + fileReference.getEncryptionInfo().getRealFileSize()); encryptionCipher = new CBCBlockCipherStream(iv, Crypto.createAES256(key)); offset = 16; } + if (index == blocksCount - 1) { + padding = (fileReference.getFileSize() - 16 - fileReference.getEncryptionInfo().getRealFileSize()); + } + byte[] dest = new byte[r.getContent().length - offset]; for (int i = 0; i < (dest.length - offset) / encryptionCipher.getBlockSize(); i++) { encryptionCipher.decryptBlock(r.getContent(), offset + i * encryptionCipher.getBlockSize(), @@ -147,11 +158,11 @@ public void apply(HTTPResponse r) { } if (index == 0) { - if (!outputFile.write(index * blockSize, dest, 0, dest.length)) { + if (!outputFile.write(index * blockSize, dest, 0, dest.length - padding)) { throw new RuntimeException("Unable to write file"); } } else { - if (!outputFile.write(index * blockSize - 16, dest, 0, dest.length)) { + if (!outputFile.write(index * blockSize - 16, dest, 0, dest.length - padding)) { throw new RuntimeException("Unable to write file"); } } diff --git a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java index 26b4be8052..45b8424489 100644 --- a/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java +++ b/actor-sdk/sdk-core/runtime/runtime-shared/src/main/java/im/actor/runtime/Crypto.java @@ -106,6 +106,11 @@ public static byte[] fromHex(String hex) { * @return padded length */ public static int paddedLength(int sourceLength, int blockSize) { - return sourceLength + (blockSize - sourceLength % blockSize - 1); + if (sourceLength % blockSize != 0) { + return sourceLength + (blockSize - sourceLength % blockSize); + } else { + return sourceLength; + } + } } From 5b89de465ddff7394271f1457c95a342180a5e5a Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 25 Aug 2016 20:34:40 +0300 Subject: [PATCH 73/81] fix(core): Fixing chat clear/delete of secret chats --- .../core/modules/messaging/MessagesModule.java | 9 ++++----- .../modules/messaging/router/RouterActor.java | 8 ++++++++ .../modules/messaging/router/RouterInt.java | 9 +++++++++ .../router/entity/RouterSecretChatCleared.java | 18 ++++++++++++++++++ .../router/entity/RouterSecretChatDeleted.java | 18 ++++++++++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatCleared.java create mode 100644 actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatDeleted.java diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java index af47ed818b..7caf96729a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/MessagesModule.java @@ -428,9 +428,8 @@ public Promise archiveChat(final Peer peer) { public Promise deleteChat(final Peer peer) { if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { - // FIXME: Not actually deletes chat from dialog list - return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), - peer.getPeerId()).map(v -> (Void) null); + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), peer.getPeerId()) + .flatMap(v -> getRouter().onSecretChatDeleted(peer)); } else { return buildOutPeer(peer) .flatMap(apiOutPeer -> @@ -446,8 +445,8 @@ public Promise deleteChat(final Peer peer) { public Promise clearChat(final Peer peer) { if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { - return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), - peer.getPeerId()).map(v -> (Void) null); + return context().getEncryption().doSend(new ApiEncryptedDeleteAll(peer.getPeerId()), peer.getPeerId()) + .flatMap(v -> getRouter().onSecretChatCleared(peer)); } else { return buildOutPeer(peer) .flatMap(apiOutPeer -> diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index e5fe2bc380..426598f4cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -74,6 +74,8 @@ import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; import im.actor.core.modules.messaging.router.entity.RouterResetChat; +import im.actor.core.modules.messaging.router.entity.RouterSecretChatCleared; +import im.actor.core.modules.messaging.router.entity.RouterSecretChatDeleted; import im.actor.core.network.parser.Update; import im.actor.core.util.JavaUtil; import im.actor.core.viewmodel.DialogGroup; @@ -1136,6 +1138,12 @@ public Promise onAsk(Object message) throws Exception { } else if (message instanceof RouterMessagesSelfDestructed) { RouterMessagesSelfDestructed selfDestructed = (RouterMessagesSelfDestructed) message; return onMessageDestructed(selfDestructed.getPeer(), selfDestructed.getRids()); + } else if (message instanceof RouterSecretChatDeleted) { + RouterSecretChatDeleted chatDeleted = (RouterSecretChatDeleted) message; + return onChatDelete(chatDeleted.getPeer()); + } else if (message instanceof RouterSecretChatCleared) { + RouterSecretChatCleared chatCleared = (RouterSecretChatCleared) message; + return onChatClear(chatCleared.getPeer()); } else { return super.onAsk(message); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java index f2498ebaf6..c78cfba9c6 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterInt.java @@ -33,6 +33,8 @@ import im.actor.core.modules.messaging.router.entity.RouterOutgoingSent; import im.actor.core.modules.messaging.router.entity.RouterPeersChanged; import im.actor.core.modules.messaging.router.entity.RouterResetChat; +import im.actor.core.modules.messaging.router.entity.RouterSecretChatCleared; +import im.actor.core.modules.messaging.router.entity.RouterSecretChatDeleted; import im.actor.core.network.parser.Update; import im.actor.runtime.actors.ActorInterface; import im.actor.runtime.actors.messages.Void; @@ -123,6 +125,13 @@ public Promise onMessagesDestructed(Peer peer, List rids) { return ask(new RouterMessagesSelfDestructed(peer, rids)); } + public Promise onSecretChatDeleted(Peer peer) { + return ask(new RouterSecretChatDeleted(peer)); + } + + public Promise onSecretChatCleared(Peer peer) { + return ask(new RouterSecretChatCleared(peer)); + } // // History diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatCleared.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatCleared.java new file mode 100644 index 0000000000..5322642c20 --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatCleared.java @@ -0,0 +1,18 @@ +package im.actor.core.modules.messaging.router.entity; + +import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterSecretChatCleared implements AskMessage, RouterMessageOnlyActive { + + private Peer peer; + + public RouterSecretChatCleared(Peer peer) { + this.peer = peer; + } + + public Peer getPeer() { + return peer; + } +} diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatDeleted.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatDeleted.java new file mode 100644 index 0000000000..1fe027aacd --- /dev/null +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/entity/RouterSecretChatDeleted.java @@ -0,0 +1,18 @@ +package im.actor.core.modules.messaging.router.entity; + +import im.actor.core.entity.Peer; +import im.actor.runtime.actors.ask.AskMessage; +import im.actor.runtime.actors.messages.Void; + +public class RouterSecretChatDeleted implements AskMessage, RouterMessageOnlyActive { + + private Peer peer; + + public RouterSecretChatDeleted(Peer peer) { + this.peer = peer; + } + + public Peer getPeer() { + return peer; + } +} From 858707d98158072e51e40ff2d4155c604e9c3d45 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 25 Aug 2016 20:35:45 +0300 Subject: [PATCH 74/81] fix(iOS): Fixing secret chat dialog style --- actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift | 1 - .../sdk-core-ios/ActorSDK/Sources/ActorStyle.swift | 11 +++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift index 5e2a1f96a3..1b6aef3f4e 100644 --- a/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift +++ b/actor-sdk/sdk-core-ios/ActorApp/AppDelegate.swift @@ -31,7 +31,6 @@ import ActorSDK ActorSDK.sharedActor().authStrategy = .PhoneEmail ActorSDK.sharedActor().style.dialogAvatarSize = 58 - ActorSDK.sharedActor().style.dialogTitleSecureColor = UIColor.greenColor() ActorSDK.sharedActor().autoJoinGroups = ["actor_news"] diff --git a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift index 5e7bb6efef..a95f1b74ae 100644 --- a/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift +++ b/actor-sdk/sdk-core-ios/ActorSDK/Sources/ActorStyle.swift @@ -44,6 +44,8 @@ public class ActorStyle { public var vcBgColor = UIColor.whiteColor() /// View Controller background color for settings public var vcBackyardColor = UIColor(rgb: 0xf0eff5) + /// App's secret chats color + public var vcSecretColor: UIColor = UIColor(rgb: 0x32914d) // // UINavigationBar @@ -57,7 +59,12 @@ public class ActorStyle { /// Navigation Bar title color public var navigationTitleColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0xDE/255.0) /// Navigation Bar title secret color - public var navigationTitleSecretColor: UIColor = UIColor(rgb: 0x32914d) + private var _navigationTitleSecretColor: UIColor? + public var navigationTitleSecretColor: UIColor { + get { return _navigationTitleSecretColor != nil ? _navigationTitleSecretColor! : vcSecretColor } + set(v) { _navigationTitleSecretColor = v } + } + /// Navigation Bar subtitle color, default is 0.8 alhpa of navigationTitleColor public var navigationSubtitleColor: UIColor { get { return _navigationSubtitleColor != nil ? _navigationSubtitleColor! : navigationTitleColor.alpha(0.8) } @@ -543,7 +550,7 @@ public class ActorStyle { private var _dialogTitleSecureColor: UIColor? public var dialogTitleSecureColor: UIColor { - get { return _dialogTitleSecureColor != nil ? _dialogTitleSecureColor! : dialogTitleColor } + get { return _dialogTitleSecureColor != nil ? _dialogTitleSecureColor! : vcSecretColor } set(v) { _dialogTitleSecureColor = v } } From 89fdb774556ddc55d1382563befde41046d12221 Mon Sep 17 00:00:00 2001 From: Steve Kite Date: Thu, 25 Aug 2016 20:50:56 +0300 Subject: [PATCH 75/81] fix(core): Fixing encrypted file size in download manager after upload --- .../src/main/java/im/actor/core/modules/file/UploadTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java index d7d87ca396..01479313bb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/file/UploadTask.java @@ -202,9 +202,9 @@ private void checkQueue() { } else { encryptionInfo = null; } - + FileReference location = new FileReference(r.getUploadedFileLocation(), fileName, - finalSize, encryptionInfo); + srcReference.getSize(), encryptionInfo); if (isWriteToDestProvider || alreadyInTemp) { FileSystemReference reference = Storage.commitTempFile(alreadyInTemp ? srcReference : destReference, location.getFileId(), From e770b8678cc64c4299ea08f53afde74a8ae4a42b Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 14 Sep 2016 17:07:09 +0300 Subject: [PATCH 76/81] fix(core): self destruct timer plurals --- .../src/main/java/im/actor/core/i18n/I18nEngine.java | 2 +- .../core/core-shared/src/main/resources/AppText_Ru.json | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java index 062db50baa..c2ff61be00 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/i18n/I18nEngine.java @@ -381,7 +381,7 @@ public String formatContentText(int senderId, ContentType contentType, String te case SERVICE_TIMER_SET: return getTemplateNamed(senderId, "content.service.encrypted.timer_changed.compact"); case SERVICE_TIMER_CLEAR: - return getTemplateNamed(senderId, "content.service.encrypted.timer_disabled.compact"); + return getTemplateNamed(senderId, "content.service.encrypted.timer_disabled"); case NONE: return ""; default: diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json index 0690310e70..b9d0bf5c96 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json +++ b/actor-sdk/sdk-core/core/core-shared/src/main/resources/AppText_Ru.json @@ -314,7 +314,12 @@ "other": "{name} установил(а) таймер самоуничтожения" } }, - "timer_disabled": "{name} disabled self-destruct timer" + "timer_disabled": { + "you": "{name} отключили таймер самоуничтожения", + "male": "{name} отключил таймер самоуничтожения", + "female": "{name} отключила таймер самоуничтожения", + "other": "{name} отключил(а) таймер самоуничтожения" + } } } }, From a40d3481dad419d4d555493fb8332ba26ed82439 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Wed, 14 Sep 2016 21:28:00 +0300 Subject: [PATCH 77/81] wip(core): add encrypted messages to global counter --- .../entity/EncryptedConversationState.java | 31 +++++++++- .../java/im/actor/core/modules/AbsModule.java | 1 + .../modules/messaging/router/RouterActor.java | 58 ++++++++++++++++++- .../viewmodel/EncryptedConversationVM.java | 7 +++ 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java index 7a859528d6..7dec03155a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java @@ -1,6 +1,7 @@ package im.actor.core.entity; import java.io.IOException; +import java.util.ArrayList; import im.actor.runtime.bser.Bser; import im.actor.runtime.bser.BserCreator; @@ -19,18 +20,21 @@ public static EncryptedConversationState fromBytes(byte[] data) throws IOExcepti public static BserCreator CREATOR = EncryptedConversationState::new; public static ValueDefaultCreator DEFAULT_CREATOR = id -> - new EncryptedConversationState((int) id, false, 0, 0); + new EncryptedConversationState((int) id, false, 0, 0, new ArrayList<>()); private int uid; private boolean isLoaded; private int timer; private long timerDate; + private ArrayList unreadMessagesRids; - public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate) { + + public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate, ArrayList unreadMessagesRids) { this.uid = uid; this.isLoaded = isLoaded; this.timer = timer; this.timerDate = timerDate; + this.unreadMessagesRids = unreadMessagesRids; } private EncryptedConversationState() { @@ -48,12 +52,28 @@ public int getTimer() { return timer; } + public int getUnreadCount() { + return unreadMessagesRids.size(); + } + public long getTimerDate() { return timerDate; } public EncryptedConversationState editTimer(int timer, long timerDate) { - return new EncryptedConversationState(uid, isLoaded, timer, timerDate); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, unreadMessagesRids); + } + + public EncryptedConversationState addUnreadMessages(ArrayList rids) { + ArrayList res = new ArrayList<>(); + res.addAll(unreadMessagesRids); + res.addAll(rids); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); + } + + public EncryptedConversationState readAll() { + ArrayList res = new ArrayList<>(); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); } @Override @@ -62,6 +82,10 @@ public void parse(BserValues values) throws IOException { isLoaded = values.getBool(2); timer = values.getInt(3); timerDate = values.getLong(4); + unreadMessagesRids = new ArrayList<>(); + for (long i : values.getRepeatedLong(5)) { + unreadMessagesRids.add(i); + } } @Override @@ -70,6 +94,7 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBool(2, isLoaded); writer.writeLong(3, timer); writer.writeLong(4, timerDate); + writer.writeRepeatedLong(5, unreadMessagesRids); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/AbsModule.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/AbsModule.java index fbbaeb858c..44aa2a869f 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/AbsModule.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/AbsModule.java @@ -63,6 +63,7 @@ public abstract class AbsModule { public static final String STORAGE_BLOB = "blob"; public static final long BLOB_DIALOGS_ACTIVE = 0; + public static final long BLOB_ENCTYPTED_DIALOGS_ACTIVE_GROUP = 1; private ModuleContext context; diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 03ea59e4a6..ecc6d2f34a 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -31,6 +31,7 @@ import im.actor.core.entity.Avatar; import im.actor.core.entity.ContentDescription; import im.actor.core.entity.ConversationState; +import im.actor.core.entity.EncryptedConversationState; import im.actor.core.entity.Group; import im.actor.core.entity.Message; import im.actor.core.entity.MessageState; @@ -103,9 +104,11 @@ public class RouterActor extends ModuleActor { // Storage private KeyValueEngine conversationStates; + private KeyValueEngine encryptedConversationStates; // Active Dialogs private ActiveDialogStorage activeDialogStorage; + private ActiveDialogGroup activeEncryptedDialogGroupStorage; // Self-Destructor private Destructor destructor; @@ -120,6 +123,8 @@ public void preStart() { super.preStart(); conversationStates = context().getMessagesModule().getConversationStates().getEngine(); + encryptedConversationStates = context().getEncryption().getConversationState().getEngine(); + // // Self-Destructor @@ -138,6 +143,17 @@ public void preStart() { e.printStackTrace(); } } + + activeEncryptedDialogGroupStorage = new ActiveDialogGroup("EncryptedStuff", "Encrypted", new ArrayList<>()); + byte[] groupData = context().getStorageModule().getBlobStorage().loadItem(AbsModule.BLOB_ENCTYPTED_DIALOGS_ACTIVE_GROUP); + if (groupData != null) { + try { + activeEncryptedDialogGroupStorage = new ActiveDialogGroup(groupData); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (!activeDialogStorage.isLoaded()) { api(new RequestLoadGroupedDialogs(ApiSupportConfiguration.OPTIMIZATIONS)) .chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroups())) @@ -269,6 +285,36 @@ private Promise onNewMessages(Peer peer, List messages) { // // Updating Counter // + + boolean needNotifyActiveDialogsVM = false; + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && !isConversationVisible) { + + // Add this encrypted dialog to active if we don't have it there already + if (!activeEncryptedDialogGroupStorage.getPeers().contains(peer)) { + activeEncryptedDialogGroupStorage.getPeers().add(peer); + context().getStorageModule().getBlobStorage() + .addOrUpdateItem(AbsModule.BLOB_ENCTYPTED_DIALOGS_ACTIVE_GROUP, activeEncryptedDialogGroupStorage.toByteArray()); + } + + // update counter + ArrayList incoming = new ArrayList<>(); + for (Message m : messages) { + + if (m.getSenderId() != myUid()) { + incoming.add(m.getRid()); + } + } + + EncryptedConversationState encryptedConversationState = encryptedConversationStates.getValue(peer.getPeerId()); + encryptedConversationState = encryptedConversationState.addUnreadMessages(incoming); + + encryptedConversationStates.addOrUpdateItem(encryptedConversationState); + + needNotifyActiveDialogsVM = true; + } + + boolean isRead = false; if (unreadCount != 0) { if (isConversationVisible) { @@ -305,10 +351,14 @@ private Promise onNewMessages(Peer peer, List messages) { } conversationStates.addOrUpdateItem(state); - notifyActiveDialogsVM(); + needNotifyActiveDialogsVM = true; + } } + if (needNotifyActiveDialogsVM) { + notifyActiveDialogsVM(); + } // // Marking As Received @@ -876,6 +926,12 @@ private void notifyActiveDialogsVM() { } groups.add(new DialogGroup(i.getTitle(), i.getKey(), dialogSmalls)); } + + for (Peer p : activeEncryptedDialogGroupStorage.getPeers()) { + int unreadCount = encryptedConversationStates.getValue(p.getPeerId()).getUnreadCount(); + counter += unreadCount; + } + context().getMessagesModule().getDialogGroupsVM().getGroupsValueModel().change(groups); context().getConductor().getGlobalStateVM().onGlobalCounterChanged(counter); } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java index 5eef4934c3..cf1b9b864d 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/viewmodel/EncryptedConversationVM.java @@ -12,11 +12,13 @@ public class EncryptedConversationVM extends BaseValueModel Date: Thu, 15 Sep 2016 20:16:38 +0300 Subject: [PATCH 78/81] wip(android): update counter on read encrypted messages --- .../entity/EncryptedConversationState.java | 28 +++++++++------ .../modules/messaging/router/RouterActor.java | 36 +++++++++++++++---- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java index 7dec03155a..6a74888d4e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java @@ -26,15 +26,15 @@ public static EncryptedConversationState fromBytes(byte[] data) throws IOExcepti private boolean isLoaded; private int timer; private long timerDate; - private ArrayList unreadMessagesRids; + private ArrayList unreadMessagesSortDate; - public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate, ArrayList unreadMessagesRids) { + public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate, ArrayList unreadMessagesSortDate) { this.uid = uid; this.isLoaded = isLoaded; this.timer = timer; this.timerDate = timerDate; - this.unreadMessagesRids = unreadMessagesRids; + this.unreadMessagesSortDate = unreadMessagesSortDate; } private EncryptedConversationState() { @@ -53,7 +53,7 @@ public int getTimer() { } public int getUnreadCount() { - return unreadMessagesRids.size(); + return unreadMessagesSortDate.size(); } public long getTimerDate() { @@ -61,13 +61,19 @@ public long getTimerDate() { } public EncryptedConversationState editTimer(int timer, long timerDate) { - return new EncryptedConversationState(uid, isLoaded, timer, timerDate, unreadMessagesRids); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, unreadMessagesSortDate); } - public EncryptedConversationState addUnreadMessages(ArrayList rids) { + public EncryptedConversationState addUnreadMessages(ArrayList sortDates) { ArrayList res = new ArrayList<>(); - res.addAll(unreadMessagesRids); - res.addAll(rids); + res.addAll(unreadMessagesSortDate); + res.addAll(sortDates); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); + } + + public EncryptedConversationState read(long rid) { + ArrayList res = new ArrayList<>(unreadMessagesSortDate); + res.remove(rid); return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); } @@ -82,9 +88,9 @@ public void parse(BserValues values) throws IOException { isLoaded = values.getBool(2); timer = values.getInt(3); timerDate = values.getLong(4); - unreadMessagesRids = new ArrayList<>(); + unreadMessagesSortDate = new ArrayList<>(); for (long i : values.getRepeatedLong(5)) { - unreadMessagesRids.add(i); + unreadMessagesSortDate.add(i); } } @@ -94,7 +100,7 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBool(2, isLoaded); writer.writeLong(3, timer); writer.writeLong(4, timerDate); - writer.writeRepeatedLong(5, unreadMessagesRids); + writer.writeRepeatedLong(5, unreadMessagesSortDate); } @Override diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index ecc6d2f34a..f5035d6eeb 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -288,7 +288,10 @@ private Promise onNewMessages(Peer peer, List messages) { boolean needNotifyActiveDialogsVM = false; - if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED && !isConversationVisible) { + boolean encrypted = peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED; + EncryptedConversationState encryptedConversationState = encryptedConversationStates.getValue(peer.getPeerId()); + + if (encrypted && !isConversationVisible) { // Add this encrypted dialog to active if we don't have it there already if (!activeEncryptedDialogGroupStorage.getPeers().contains(peer)) { @@ -302,11 +305,10 @@ private Promise onNewMessages(Peer peer, List messages) { for (Message m : messages) { if (m.getSenderId() != myUid()) { - incoming.add(m.getRid()); + incoming.add(m.getSortDate()); } } - EncryptedConversationState encryptedConversationState = encryptedConversationStates.getValue(peer.getPeerId()); encryptedConversationState = encryptedConversationState.addUnreadMessages(incoming); encryptedConversationStates.addOrUpdateItem(encryptedConversationState); @@ -372,13 +374,13 @@ private Promise onNewMessages(Peer peer, List messages) { // // Updating Dialog List // - Promise res = getDialogsRouter().onMessage(peer, topMessage, state.getUnreadCount()); + Promise res = getDialogsRouter().onMessage(peer, topMessage, encrypted ? encryptedConversationState.getUnreadCount() : state.getUnreadCount()); // // Update Self-Destructor // - if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + if (encrypted) { ArrayList pendingDescs = new ArrayList<>(); for (Message m : messages) { if (m.getTimer() > 0) { @@ -738,8 +740,17 @@ private Promise onMessageReceived(Peer peer, long date) { private Promise onMessageReadByMe(Peer peer, long date, int counter) { + boolean encrypted = peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED; + ConversationState state = conversationStates.getValue(peer.getUnuqueId()); + if (encrypted) { + EncryptedConversationState encryptedConversationState = encryptedConversationStates.getValue(peer.getPeerId()); + encryptedConversationState = encryptedConversationState.read(date); + encryptedConversationStates.addOrUpdateItem(encryptedConversationState); + } + + if (state.getInReadDate() >= date) { return Promise.success(null); } @@ -841,6 +852,7 @@ private void markAsReadIfNeeded(Peer peer) { ConversationState state = conversationStates.getValue(peer.getUnuqueId()); long inMaxMessageDate = state.getInMaxMessageDate(); //check UnreadCount for zero, because it can be loaded from server (after login) + boolean needUpdateDialogs = false; if (state.getUnreadCount() != 0 || state.getInReadDate() < inMaxMessageDate) { state = state .changeCounter(0) @@ -850,9 +862,8 @@ private void markAsReadIfNeeded(Peer peer) { context().getMessagesModule().getPlainReadActor() .send(new CursorReaderActor.MarkRead(peer, inMaxMessageDate)); - notifyActiveDialogsVM(); + needUpdateDialogs = true; - getDialogsRouter().onCounterChanged(peer, 0); if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { destructor.onMessageReadByMe(peer, inMaxMessageDate); @@ -860,6 +871,17 @@ private void markAsReadIfNeeded(Peer peer) { context().getNotificationsModule().onOwnRead(peer, inMaxMessageDate); } + + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).readAll()); + needUpdateDialogs = true; + } + + if (needUpdateDialogs) { + getDialogsRouter().onCounterChanged(peer, 0); + notifyActiveDialogsVM(); + } + } } From 31a2f9e5e9ebb9ffec434ffa3f65dd71f906b205 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 16 Sep 2016 17:23:02 +0300 Subject: [PATCH 79/81] feat(core): hide encrypted message content rom notifications, fix notifications encrypted counter --- .../actor/core/modules/messaging/router/RouterActor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index f5035d6eeb..f3d31264cf 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -419,12 +419,19 @@ private Promise onNewMessages(Peer peer, List messages) { messagesCount += activeDialogueUnreadCount; } } + for (Peer activePeer : activeEncryptedDialogGroupStorage.getPeers()) { + int activeDialogueUnreadCount = encryptedConversationStates.getValue(activePeer.getPeerId()).getUnreadCount(); + if (activeDialogueUnreadCount > 0) { + dialogsCount++; + messagesCount += activeDialogueUnreadCount; + } + } context().getNotificationsModule().onInMessage( peer, m.getSenderId(), m.getSortDate(), - ContentDescription.fromContent(m.getContent()), + ContentDescription.fromContent(peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED ? TextContent.create("[secret message]", null, null) : m.getContent()), hasCurrentMention, messagesCount, dialogsCount); From b9ed6a311c22ef4ef3c0309085624d39d714d047 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 16 Sep 2016 19:03:07 +0300 Subject: [PATCH 80/81] fix(core): mark as read all encrypted messages after read date --- .../entity/EncryptedConversationState.java | 96 +++++++++++++++---- .../modules/messaging/router/RouterActor.java | 14 ++- 2 files changed, 85 insertions(+), 25 deletions(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java index 6a74888d4e..2d6084c47e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/entity/EncryptedConversationState.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import im.actor.runtime.bser.Bser; import im.actor.runtime.bser.BserCreator; @@ -26,15 +27,15 @@ public static EncryptedConversationState fromBytes(byte[] data) throws IOExcepti private boolean isLoaded; private int timer; private long timerDate; - private ArrayList unreadMessagesSortDate; + private ArrayList unreadMessages; - public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate, ArrayList unreadMessagesSortDate) { + public EncryptedConversationState(int uid, boolean isLoaded, int timer, long timerDate, ArrayList shortMessages) { this.uid = uid; this.isLoaded = isLoaded; this.timer = timer; this.timerDate = timerDate; - this.unreadMessagesSortDate = unreadMessagesSortDate; + this.unreadMessages = shortMessages; } private EncryptedConversationState() { @@ -53,7 +54,7 @@ public int getTimer() { } public int getUnreadCount() { - return unreadMessagesSortDate.size(); + return unreadMessages.size(); } public long getTimerDate() { @@ -61,24 +62,40 @@ public long getTimerDate() { } public EncryptedConversationState editTimer(int timer, long timerDate) { - return new EncryptedConversationState(uid, isLoaded, timer, timerDate, unreadMessagesSortDate); + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, unreadMessages); } - public EncryptedConversationState addUnreadMessages(ArrayList sortDates) { - ArrayList res = new ArrayList<>(); - res.addAll(unreadMessagesSortDate); - res.addAll(sortDates); + public EncryptedConversationState addUnreadMessages(List messages) { + ArrayList res = new ArrayList<>(); + res.addAll(this.unreadMessages); + res.addAll(messages); return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); } - public EncryptedConversationState read(long rid) { - ArrayList res = new ArrayList<>(unreadMessagesSortDate); - res.remove(rid); + public EncryptedConversationState readBefore(long sortDate) { + ArrayList res = new ArrayList<>(unreadMessages); + ShortMessage m; + for (int i = res.size() - 1; i >= 0; i--) { + m = res.get(i); + if (m.getSortDate() <= sortDate) { + res.remove(m); + } + } + return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); + } + + public EncryptedConversationState read(List ridsToRead) { + ArrayList res = new ArrayList<>(unreadMessages); + ArrayList rem = new ArrayList<>(); + for (Long rid : ridsToRead) { + rem.add(new ShortMessage(0, rid)); + } + res.removeAll(rem); return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); } public EncryptedConversationState readAll() { - ArrayList res = new ArrayList<>(); + ArrayList res = new ArrayList<>(); return new EncryptedConversationState(uid, isLoaded, timer, timerDate, res); } @@ -88,9 +105,11 @@ public void parse(BserValues values) throws IOException { isLoaded = values.getBool(2); timer = values.getInt(3); timerDate = values.getLong(4); - unreadMessagesSortDate = new ArrayList<>(); - for (long i : values.getRepeatedLong(5)) { - unreadMessagesSortDate.add(i); + unreadMessages = new ArrayList<>(); + for (byte[] i : values.getRepeatedBytes(5)) { + ShortMessage m = new ShortMessage(); + Bser.parse(m, i); + unreadMessages.add(m); } } @@ -100,11 +119,54 @@ public void serialize(BserWriter writer) throws IOException { writer.writeBool(2, isLoaded); writer.writeLong(3, timer); writer.writeLong(4, timerDate); - writer.writeRepeatedLong(5, unreadMessagesSortDate); + writer.writeRepeatedObj(5, unreadMessages); } @Override public long getEngineId() { return uid; } + + public static class ShortMessage extends BserObject { + private long sortDate; + private long rid; + + public ShortMessage() { + } + + public ShortMessage(long sortDate, long rid) { + this.sortDate = sortDate; + this.rid = rid; + } + + public long getSortDate() { + return sortDate; + } + + public long getRid() { + return rid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShortMessage that = (ShortMessage) o; + + return sortDate == that.sortDate || rid == that.rid; + } + + @Override + public void parse(BserValues values) throws IOException { + sortDate = values.getLong(1); + rid = values.getLong(2); + } + + @Override + public void serialize(BserWriter writer) throws IOException { + writer.writeLong(1, sortDate); + writer.writeLong(2, rid); + } + } } diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index f3d31264cf..7aa9713369 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -301,11 +301,11 @@ private Promise onNewMessages(Peer peer, List messages) { } // update counter - ArrayList incoming = new ArrayList<>(); + ArrayList incoming = new ArrayList<>(); for (Message m : messages) { if (m.getSenderId() != myUid()) { - incoming.add(m.getSortDate()); + incoming.add(new EncryptedConversationState.ShortMessage(m.getSortDate(), m.getRid())); } } @@ -431,7 +431,7 @@ private Promise onNewMessages(Peer peer, List messages) { peer, m.getSenderId(), m.getSortDate(), - ContentDescription.fromContent(peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED ? TextContent.create("[secret message]", null, null) : m.getContent()), + ContentDescription.fromContent(peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED ? TextContent.create("new secret message", null, null) : m.getContent()), hasCurrentMention, messagesCount, dialogsCount); @@ -642,6 +642,8 @@ private Promise onMessageDeleted(Peer peer, List rids) { } } + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).read(rids)); + return getDialogsRouter().onMessageDeleted(peer, head); } @@ -752,12 +754,9 @@ private Promise onMessageReadByMe(Peer peer, long date, int counter) { ConversationState state = conversationStates.getValue(peer.getUnuqueId()); if (encrypted) { - EncryptedConversationState encryptedConversationState = encryptedConversationStates.getValue(peer.getPeerId()); - encryptedConversationState = encryptedConversationState.read(date); - encryptedConversationStates.addOrUpdateItem(encryptedConversationState); + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).readBefore(date)); } - if (state.getInReadDate() >= date) { return Promise.success(null); } @@ -1124,7 +1123,6 @@ public Promise onEncryptedUpdate(int senderId, long date, ApiEncryptedCont } else if (update instanceof ApiEncryptedRead) { ApiEncryptedRead encryptedRead = (ApiEncryptedRead) update; if (senderId == myUid()) { - // TODO: Fix Counter return onMessageReadByMe(Peer.secret(encryptedRead.getReceiverId()), encryptedRead.getReadDate(), 0); } else { return onMessageRead(Peer.secret(senderId), encryptedRead.getReadDate()); From 00e6b13a05d5b93a4545b530959bcfc94cd41534 Mon Sep 17 00:00:00 2001 From: kor_ka Date: Fri, 16 Sep 2016 20:34:03 +0300 Subject: [PATCH 81/81] fix(core): mark encrypted messages as read after destruct/message delete/chat delete --- .../modules/messaging/router/RouterActor.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java index 7aa9713369..b5df94f98e 100644 --- a/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java +++ b/actor-sdk/sdk-core/core/core-shared/src/main/java/im/actor/core/modules/messaging/router/RouterActor.java @@ -642,12 +642,19 @@ private Promise onMessageDeleted(Peer peer, List rids) { } } - encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).read(rids)); + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).read(rids)); + notifyActiveDialogsVM(); + } return getDialogsRouter().onMessageDeleted(peer, head); } private Promise onMessageDestructed(Peer peer, List rids) { + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).read(rids)); + notifyActiveDialogsVM(); + } return onMessageDeleted(peer, rids); } @@ -661,6 +668,11 @@ private Promise onChatClear(Peer peer) { conversationStates.addOrUpdateItem(state); } + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).readAll()); + notifyActiveDialogsVM(); + } + updateChatState(peer); return getDialogsRouter().onChatClear(peer); @@ -676,6 +688,11 @@ private Promise onChatDelete(Peer peer) { conversationStates.addOrUpdateItem(state); } + if (peer.getPeerType() == PeerType.PRIVATE_ENCRYPTED) { + encryptedConversationStates.addOrUpdateItem(encryptedConversationStates.getValue(peer.getPeerId()).readAll()); + notifyActiveDialogsVM(); + } + updateChatState(peer); return getDialogsRouter().onChatDelete(peer);