content: package im.actor.core.modules.encryption;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  
  import im.actor.core.api.ApiEncryptionKey;
  import im.actor.core.api.ApiEncryptionKeyGroup;
  import im.actor.core.api.ApiEncryptionKeySignature;
  import im.actor.core.api.ApiUserOutPeer;
  import im.actor.core.api.rpc.RequestCreateNewKeyGroup;
  import im.actor.core.api.rpc.RequestLoadPrePublicKeys;
  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.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.ModuleActor;
  import im.actor.core.util.RandomUtils;
  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.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;
  
  /**
   * Key Management Actor.
   * 1) Generates and uploads own keys.
   * 2) Downloads and manages updates about foreign keys
   */
  public class KeyManagerActor extends ModuleActor {
  
      private static final String TAG = "KeyManagerActor";
  
      private KeyValueStorage encryptionKeysStorage;
      private HashMap<Integer, UserKeys> cachedUserKeys = new HashMap<Integer, UserKeys>();
      private PrivateKeyStorage ownKeys;
  
      private boolean isReady = false;
  
      public KeyManagerActor(ModuleContext context) {
          super(context);
      }
  
      @Override
      public void preStart() {
  
          Log.d(TAG, "Starting KeyManager...");
  
          //
          // Initialization key storage
          //
  
          encryptionKeysStorage = Storage.createKeyValue("encryption_keys");
  
          //
          // Initialization own private keys
          //
  
          ownKeys = null;
          byte[] ownKeysStorage = encryptionKeysStorage.loadItem(0);
          if (ownKeysStorage != null) {
              try {
                  ownKeys = new PrivateKeyStorage(ownKeysStorage);
  
                  // If we need re-save key storage
                  if (ownKeys.isWasRegenerated()) {
                      encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray());
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
  
          if (ownKeys == null) {
              Curve25519KeyPair identityPrivate = Curve25519.keyGen(Crypto.randomBytes(64));
              Curve25519KeyPair key0 = Curve25519.keyGen(Crypto.randomBytes(64));
  
              ownKeys = new PrivateKeyStorage(0,
                      new PrivateKey(RandomUtils.nextRid(), "curve25519", identityPrivate.getPrivateKey(),
                              identityPrivate.getPublicKey()),
                      new PrivateKey[]{
                              new PrivateKey(RandomUtils.nextRid(), "curve25519", key0.getPrivateKey(),
                                      key0.getPublicKey())
                      },
                      new PrivateKey[0]);
              encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray());
          }
  
          //
          // Creating new key group if needed
          //
  
          if (ownKeys.getKeyGroupId() == 0) {
  
              ApiEncryptionKey identityKey = ownKeys.getIdentityKey().toApiKey();
  
              ArrayList<ApiEncryptionKey> keys = ManagedList.of(ownKeys.getKeys())
                      .map(PrivateKey.TO_API);
              ArrayList<ApiEncryptionKeySignature> signatures = ManagedList.of(ownKeys.getKeys())
                      .map(PrivateKey.SIGN(ownKeys.getIdentityKey()));
  
              Log.d(TAG, "Creation of new key group");
              api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(new Consumer<ResponseCreateNewKeyGroup>() {
                  @Override
                  public void apply(ResponseCreateNewKeyGroup response) {
                      ownKeys = ownKeys.setGroupId(response.getKeyGroupId());
                      encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray());
                      onMainKeysReady();
                  }
              }).failure(new Consumer<Exception>() {
                  @Override
                  public void apply(Exception e) {
                      Log.w(TAG, "Keys upload error");
                      Log.e(TAG, e);
  
                      // Just ignore
                  }
              });
          } else {
              onMainKeysReady();
          }
      }
  
      private void onMainKeysReady() {
          Log.d(TAG, "Main Keys are ready");
  
          //
          // Generation required pre keys
          //
  
          int missingKeysCount = Math.max(0, Configuration.EPHEMERAL_KEYS_COUNT - ownKeys.getPreKeys().length);
          if (missingKeysCount > 0) {
              ownKeys = ownKeys.appendPreKeys(
                      ManagedList.of(PrivateKey.GENERATOR, missingKeysCount)
                              .toArray(new PrivateKey[0]));
              encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray());
          }
  
          //
          // Uploading own pre keys
          //
  
          final ManagedList<PrivateKey> pendingEphermalKeys =
                  ManagedList.of(ownKeys.getPreKeys())
                          .filter(PrivateKey.NOT_UPLOADED);
  
          if (pendingEphermalKeys.size() > 0) {
  
              ArrayList<ApiEncryptionKey> uploadingKeys =
                      pendingEphermalKeys.map(PrivateKey.TO_API);
              ArrayList<ApiEncryptionKeySignature> uploadingSignatures =
                      pendingEphermalKeys.map(PrivateKey.SIGN(ownKeys.getIdentityKey()));
  
              api(new RequestUploadPreKey(ownKeys.getKeyGroupId(), uploadingKeys, uploadingSignatures))
                      .then(new Consumer<ResponseVoid>() {
                          @Override
                          public void apply(ResponseVoid responseVoid) {
                              ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()]));
                              encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray());
                              onAllKeysReady();
                          }
                      })
                      .failure(new Consumer<Exception>() {
                          @Override
                          public void apply(Exception e) {
                              Log.w(TAG, "Ephemeral keys upload error");
                              Log.e(TAG, e);
  
                              // Ignore. This will freeze all encryption operations.
                          }
                      });
          } else {
              onAllKeysReady();
          }
      }
  
      private void onAllKeysReady() {
  
          //
          // Finished starting key manager
          //
  
          Log.d(TAG, "Key Manager started with key group #" + ownKeys.getKeyGroupId());
          isReady = true;
          unstashAll();
      }
  
      //
      // Own Keys fetching
      //
  
      /**
       * Fetching Own Identity key and group id
       */
      private Promise<OwnIdentity> fetchOwnIdentity() {
          return Promise.success(new OwnIdentity(ownKeys.getKeyGroupId(), ownKeys.getIdentityKey()));
      }
  
      /**
       * Fetching own private pre key by public key
       *
       * @param publicKey public key material for search
       */
      private Promise<PrivateKey> fetchPreKey(byte[] publicKey) {
          try {
              return Promise.success(ManagedList.of(ownKeys.getPreKeys())
                      .filter(PrivateKey.PRE_KEY_EQUALS(publicKey))
                      .first());
          } catch (Exception e) {
              Log.d(TAG, "Unable to find own pre key " + Crypto.keyHash(publicKey));
              for (PrivateKey p : ownKeys.getPreKeys()) {
                  Log.d(TAG, "Have: " + Crypto.keyHash(p.getPublicKey()));
              }
              throw e;
          }
      }
  
      /**
       * Fetching own pre key by id
       *
       * @param keyId pre key id
       */
      private Promise<PrivateKey> fetchPreKey(long keyId) {
          try {
              return Promise.success(ManagedList.of(ownKeys.getPreKeys())
                      .filter(PrivateKey.PRE_KEY_EQUALS_ID(keyId))
                      .first());
          } catch (Exception e) {
              Log.d(TAG, "Unable to find own pre key #" + keyId);
              throw e;
          }
      }
  
      /**
       * Fetching own random pre key
       */
      private Promise<PrivateKey> fetchPreKey() {
          return PromisesArray.of(ownKeys.getPreKeys())
                  .random();
      }
  
      //
      // User keys fetching
      //
  
      /**
       * Fetching all user's key groups
       *
       * @param uid User's id
       */
      private Promise<UserKeys> fetchUserGroups(final int uid) {
  
          User user = users().getValue(uid);
          if (user == null) {
              throw new RuntimeException("Unable to find user #" + uid);
          }
  
          final UserKeys userKeys = getCachedUserKeys(uid);
          if (userKeys != null) {
              return Promise.success(userKeys);
          }
  
          return api(new RequestLoadPublicKeyGroups(new ApiUserOutPeer(uid, user.getAccessHash())))
                  .map(new Function<ResponsePublicKeyGroups, ArrayList<UserKeysGroup>>() {
                      @Override
                      public ArrayList<UserKeysGroup> apply(ResponsePublicKeyGroups response) {
                          ArrayList<UserKeysGroup> keysGroups = new ArrayList<>();
                          for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) {
                              UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup);
                              if (validatedKeysGroup != null) {
                                  keysGroups.add(validatedKeysGroup);
                              }
                          }
                          return keysGroups;
                      }
                  })
                  .map(new Function<ArrayList<UserKeysGroup>, UserKeys>() {
                      @Override
                      public UserKeys apply(ArrayList<UserKeysGroup> userKeysGroups) {
                          UserKeys userKeys = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()]));
                          cacheUserKeys(userKeys);
                          return userKeys;
                      }
                  });
      }
  
      /**
       * Fetching user's pre key by key id
       *
       * @param uid        User's id
       * @param keyGroupId User's key group id
       * @param keyId      Key id
       */
      private Promise<PublicKey> fetchUserPreKey(final int uid, final int keyGroupId, final long keyId) {
  
          User user = users().getValue(uid);
          if (user == null) {
              throw new RuntimeException("Unable to find user #" + uid);
          }
  
          return pickUserGroup(uid, keyGroupId)
                  .flatMap(new Function<Tuple2<UserKeysGroup, UserKeys>, Promise<PublicKey>>() {
                      @Override
                      public Promise<PublicKey> apply(final Tuple2<UserKeysGroup, UserKeys> keysGroup) {
  
                          //
                          // Searching in cache
                          //
  
                          for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) {
                              if (p.getKeyId() == keyId) {
                                  return Promise.success(p);
                              }
                          }
  
                          //
                          // Downloading pre key
                          //
  
                          ArrayList<Long> ids = new ArrayList<Long>();
                          ids.add(keyId);
                          final UserKeysGroup finalKeysGroup = keysGroup.getT1();
  
                          return api(new RequestLoadPublicKey(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId, ids))
                                  .map(new Function<ResponsePublicKeys, PublicKey>() {
                                      @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 isMatch");
                                          }
  
                                          PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial());
                                          UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey);
                                          cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId())
                                                  .addUserKeyGroup(userKeysGroup));
  
                                          return pkey;
                                      }
                                  });
                      }
                  });
      }
  
      /**
       * Fetching user's random pre key
       *
       * @param uid        User's id
       * @param keyGroupId User's key group id
       */
      private Promise<PublicKey> fetchUserPreKey(final int uid, final int keyGroupId) {
          return pickUserGroup(uid, keyGroupId)
                  .flatMap(new Function<Tuple2<UserKeysGroup, UserKeys>, Promise<PublicKey>>() {
                      @Override
                      public Promise<PublicKey> apply(final Tuple2<UserKeysGroup, UserKeys> keyGroups) {
                          return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId))
                                  .map(new Function<ResponsePublicKeys, PublicKey>() {
                                      @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 isMatch");
                                          }
  
                                          return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial());
                                      }
                                  });
                      }
                  });
      }
  
      //
      // Keys updates handling
      //
  
      /**
       * Handler for adding new key group
       *
       * @param uid      User's id
       * @param keyGroup Added key group
       */
      private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) {
          UserKeys userKeys = getCachedUserKeys(uid);
          if (userKeys == null) {
              return;
          }
          UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup);
          if (validatedKeysGroup != null) {
              UserKeys updatedUserKeys = userKeys.addUserKeyGroup(validatedKeysGroup);
              cacheUserKeys(updatedUserKeys);
              context().getEncryption().getEncryptedChatManager(uid)
                      .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys));
          }
      }
  
      /**
       * Handler for removing key group
       *
       * @param uid        User's id
       * @param keyGroupId Removed key group id
       */
      private void onPublicKeysGroupRemoved(int uid, int keyGroupId) {
          UserKeys userKeys = getCachedUserKeys(uid);
          if (userKeys == null) {
              return;
          }
  
          UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId);
          cacheUserKeys(updatedUserKeys);
          context().getEncryption().getEncryptedChatManager(uid)
                  .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys));
      }
  
      //
      // Helper methods
      //
  
      private UserKeysGroup validateUserKeysGroup(int uid, ApiEncryptionKeyGroup keyGroup) {
          if (!"curve25519".equals(keyGroup.getIdentityKey().getKeyAlg())) {
              // Anything other than curve25519 is not supported
              Log.w(TAG, "(uid:" + uid + ") Unsupported identity key alg " + keyGroup.getIdentityKey().getKeyAlg());
              return null;
          }
  
          PublicKey identity = new PublicKey(
                  keyGroup.getIdentityKey().getKeyId(),
                  keyGroup.getIdentityKey().getKeyAlg(),
                  keyGroup.getIdentityKey().getKeyMaterial());
  
          ArrayList<PublicKey> keys = new ArrayList<PublicKey>();
  
          key_loop:
          for (ApiEncryptionKey key : keyGroup.getKeys()) {
  
              //
              // Validating signatures
              //
              for (ApiEncryptionKeySignature sig : keyGroup.getSignatures()) {
                  if (!sig.getSignatureAlg().equals("Ed25519")) {
                      // Anything other than Ed25519 is not supported
                      Log.w(TAG, "(uid:" + uid + ") Unsupported signature algorithm " + sig.getSignatureAlg());
                      continue;
                  }
                  if (sig.getKeyId() != key.getKeyId()) {
                      continue;
                  }
  
                  byte[] keyForSign = RatchetKeySignature.hashForSignature(
                          key.getKeyId(),
                          key.getKeyAlg(),
                          key.getKeyMaterial());
  
                  if (!Curve25519.verifySignature(identity.getPublicKey(), keyForSign, sig.getSignature())) {
                      Log.w(TAG, "(uid:" + uid + ") Unable to verify signature for " + Crypto.keyHash(key.getKeyMaterial()) + " key");
                      continue key_loop;
                  }
              }
  
              //
              // Adding key to collection
              //
  
              keys.add(new PublicKey(
                      key.getKeyId(),
                      key.getKeyAlg(),
                      key.getKeyMaterial()));
          }
  
          if (keys.size() == 0) {
              Log.w(TAG, "(uid:" + uid + ") No valid keys in key group #" + keyGroup.getKeyGroupId());
          }
  
          return new UserKeysGroup(keyGroup.getKeyGroupId(), identity, keys.toArray(new PublicKey[keys.size()]),
                  new PublicKey[0]);
      }
  
      private Promise<Tuple2<UserKeysGroup, UserKeys>> pickUserGroup(int uid, final int keyGroupId) {
          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;
                  });
      }
  
      private UserKeys getCachedUserKeys(int uid) {
          UserKeys userKeys = cachedUserKeys.get(uid);
          if (userKeys == null) {
              byte[] cached = encryptionKeysStorage.loadItem(uid);
              if (cached != null) {
                  try {
                      userKeys = new UserKeys(cached);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          return userKeys;
      }
  
      private void cacheUserKeys(UserKeys userKeys) {
          encryptionKeysStorage.addOrUpdateItem(userKeys.getUid(), userKeys.toByteArray());
          cachedUserKeys.put(userKeys.getUid(), userKeys);
      }
  
      //
      // Messages
      //
  
      @Override
      public void onReceive(Object message) {
          if (!isReady
                  && (message instanceof AskIntRequest
                  || message instanceof PublicKeysGroupAdded
                  || message instanceof PublicKeysGroupRemoved)) {
              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);
          }
      }
  
      @Override
      public Promise onAsk(Object message) throws Exception {
          if (message instanceof FetchOwnKey) {
              return fetchOwnIdentity();
          } else if (message instanceof FetchOwnPreKeyByPublic) {
              return fetchPreKey(((FetchOwnPreKeyByPublic) message).getPublicKey());
          } else if (message instanceof FetchOwnPreKeyById) {
              return fetchPreKey(((FetchOwnPreKeyById) message).getKeyId());
          } else if (message instanceof FetchUserKeys) {
              return fetchUserGroups(((FetchUserKeys) message).getUid());
          } else if (message instanceof FetchUserPreKey) {
              return fetchUserPreKey(((FetchUserPreKey) message).getUid(), ((FetchUserPreKey) message).getKeyGroup(), ((FetchUserPreKey) message).getKeyId());
          } else if (message instanceof FetchUserPreKeyRandom) {
              return fetchUserPreKey(((FetchUserPreKeyRandom) message).getUid(), ((FetchUserPreKeyRandom) message).getKeyGroup());
          } else if (message instanceof FetchOwnRandomPreKey) {
              return fetchPreKey();
          } else {
              return super.onAsk(message);
          }
      }
  
      //
      // Own Keys
      //
  
      public static class FetchOwnKey implements AskMessage<OwnIdentity> {
  
      }
  
      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<PrivateKey> {
  
      }
  
      public static class FetchOwnPreKeyByPublic implements AskMessage<PrivateKey> {
  
          private byte[] publicKey;
  
          public FetchOwnPreKeyByPublic(byte[] publicKey) {
              this.publicKey = publicKey;
          }
  
          public byte[] getPublicKey() {
              return publicKey;
          }
      }
  
      public static class FetchOwnPreKeyById implements AskMessage<PrivateKey> {
  
          private long keyId;
  
          public FetchOwnPreKeyById(long keyId) {
              this.keyId = keyId;
          }
  
          public long getKeyId() {
              return keyId;
          }
      }
  
      //
      // Users Keys
      //
  
      public static class FetchUserKeys implements AskMessage<UserKeys> {
          private int uid;
  
          public FetchUserKeys(int uid) {
              this.uid = uid;
          }
  
          public int getUid() {
              return uid;
          }
      }
  
      public static class FetchUserPreKey implements AskMessage<PublicKey> {
  
          private int uid;
          private int keyGroup;
          private long keyId;
  
          public FetchUserPreKey(int uid, int keyGroup, long keyId) {
              this.keyGroup = keyGroup;
              this.uid = uid;
              this.keyId = keyId;
          }
  
          public int getUid() {
              return uid;
          }
  
          public long getKeyId() {
              return keyId;
          }
  
          public int getKeyGroup() {
              return keyGroup;
          }
      }
  
      public static class FetchUserPreKeyRandom implements AskMessage<PublicKey> {
  
          private int uid;
          private int keyGroup;
  
          public FetchUserPreKeyRandom(int uid, int keyGroup) {
              this.keyGroup = keyGroup;
              this.uid = uid;
          }
  
          public int getUid() {
              return uid;
          }
  
          public int getKeyGroup() {
              return keyGroup;
          }
      }
  
      //
      // Updates handling
      //
  
      public static class PublicKeysGroupAdded {
  
          private int uid;
          private ApiEncryptionKeyGroup publicKeyGroup;
  
          public PublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup publicKeyGroup) {
              this.uid = uid;
              this.publicKeyGroup = publicKeyGroup;
          }
  
          public int getUid() {
              return uid;
          }
  
          public ApiEncryptionKeyGroup getPublicKeyGroup() {
              return publicKeyGroup;
          }
      }
  
      public static class PublicKeysGroupRemoved {
  
          private int uid;
          private int keyGroupId;
  
          public PublicKeysGroupRemoved(int uid, int keyGroupId) {
              this.uid = uid;
              this.keyGroupId = keyGroupId;
          }
  
          public int getUid() {
              return uid;
          }
  
          public int getKeyGroupId() {
              return keyGroupId;
          }
      }
  }