public void testMessageKeyLimits() throws Exception {
    SessionRecord aliceSessionRecord = new SessionRecord();
    SessionRecord bobSessionRecord = new SessionRecord();

    initializeSessionsV3(aliceSessionRecord.getSessionState(), bobSessionRecord.getSessionState());

    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    aliceStore.storeSession(new AxolotlAddress("+14159999999", 1), aliceSessionRecord);
    bobStore.storeSession(new AxolotlAddress("+14158888888", 1), bobSessionRecord);

    SessionCipher aliceCipher =
        new SessionCipher(aliceStore, new AxolotlAddress("+14159999999", 1));
    SessionCipher bobCipher = new SessionCipher(bobStore, new AxolotlAddress("+14158888888", 1));

    List<CiphertextMessage> inflight = new LinkedList<>();

    for (int i = 0; i < 2010; i++) {
      inflight.add(
          aliceCipher.encrypt("you've never been so hungry, you've never been so cold".getBytes()));
    }

    bobCipher.decrypt(new WhisperMessage(inflight.get(1000).serialize()));
    bobCipher.decrypt(new WhisperMessage(inflight.get(inflight.size() - 1).serialize()));

    try {
      bobCipher.decrypt(new WhisperMessage(inflight.get(0).serialize()));
      throw new AssertionError("Should have failed!");
    } catch (DuplicateMessageException dme) {
      // good
    }
  }
  private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException {
    ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair();
    int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE);
    byte[] bobSignature =
        Curve.calculateSignature(
            bobStore.getIdentityKeyPair().getPrivateKey(),
            bobSignedPreKey.getPublicKey().serialize());

    PreKeyBundle bobPreKeyBundle =
        new PreKeyBundle(
            1,
            1,
            bobUnsignedPreKeyId,
            bobUnsignedPreKey.getPublicKey(),
            bobSignedPreKeyId,
            bobSignedPreKey.getPublicKey(),
            bobSignature,
            bobStore.getIdentityKeyPair().getPublicKey());

    bobStore.storeSignedPreKey(
        bobSignedPreKeyId,
        new SignedPreKeyRecord(
            bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature));
    bobStore.storePreKey(
        bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey));

    return bobPreKeyBundle;
  }
  public void testBasicSimultaneousInitiate()
      throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException,
          InvalidMessageException, DuplicateMessageException, LegacyMessageException,
          InvalidKeyIdException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore);
    PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore);

    SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS);
    SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS);

    SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS);
    SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS);

    aliceSessionBuilder.process(bobPreKeyBundle);
    bobSessionBuilder.process(alicePreKeyBundle);

    CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes());
    CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes());

    assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE);
    assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    byte[] alicePlaintext =
        aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize()));
    byte[] bobPlaintext =
        bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize()));

    assertTrue(new String(alicePlaintext).equals("sample message"));
    assertTrue(new String(bobPlaintext).equals("hey there"));

    assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3);
    assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3);

    assertFalse(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes());

    assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] responsePlaintext =
        bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize()));

    assertTrue(new String(responsePlaintext).equals("second message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));

    CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes());

    assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE);

    byte[] finalPlaintext =
        aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize()));

    assertTrue(new String(finalPlaintext).equals("third message"));
    assertTrue(isSessionIdEqual(aliceStore, bobStore));
  }
  private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord)
      throws DuplicateMessageException, LegacyMessageException, InvalidMessageException,
          NoSuchAlgorithmException, NoSessionException {
    AxolotlStore aliceStore = new TestInMemoryAxolotlStore();
    AxolotlStore bobStore = new TestInMemoryAxolotlStore();

    aliceStore.storeSession(new AxolotlAddress("+14159999999", 1), aliceSessionRecord);
    bobStore.storeSession(new AxolotlAddress("+14158888888", 1), bobSessionRecord);

    SessionCipher aliceCipher =
        new SessionCipher(aliceStore, new AxolotlAddress("+14159999999", 1));
    SessionCipher bobCipher = new SessionCipher(bobStore, new AxolotlAddress("+14158888888", 1));

    byte[] alicePlaintext = "This is a plaintext message.".getBytes();
    CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);
    byte[] bobPlaintext = bobCipher.decrypt(new WhisperMessage(message.serialize()));

    assertTrue(Arrays.equals(alicePlaintext, bobPlaintext));

    byte[] bobReply = "This is a message from Bob.".getBytes();
    CiphertextMessage reply = bobCipher.encrypt(bobReply);
    byte[] receivedReply = aliceCipher.decrypt(new WhisperMessage(reply.serialize()));

    assertTrue(Arrays.equals(bobReply, receivedReply));

    List<CiphertextMessage> aliceCiphertextMessages = new ArrayList<>();
    List<byte[]> alicePlaintextMessages = new ArrayList<>();

    for (int i = 0; i < 50; i++) {
      alicePlaintextMessages.add(("смерть за смерть " + i).getBytes());
      aliceCiphertextMessages.add(aliceCipher.encrypt(("смерть за смерть " + i).getBytes()));
    }

    long seed = System.currentTimeMillis();

    Collections.shuffle(aliceCiphertextMessages, new Random(seed));
    Collections.shuffle(alicePlaintextMessages, new Random(seed));

    for (int i = 0; i < aliceCiphertextMessages.size() / 2; i++) {
      byte[] receivedPlaintext =
          bobCipher.decrypt(new WhisperMessage(aliceCiphertextMessages.get(i).serialize()));
      assertTrue(Arrays.equals(receivedPlaintext, alicePlaintextMessages.get(i)));
    }

    List<CiphertextMessage> bobCiphertextMessages = new ArrayList<>();
    List<byte[]> bobPlaintextMessages = new ArrayList<>();

    for (int i = 0; i < 20; i++) {
      bobPlaintextMessages.add(("смерть за смерть " + i).getBytes());
      bobCiphertextMessages.add(bobCipher.encrypt(("смерть за смерть " + i).getBytes()));
    }

    seed = System.currentTimeMillis();

    Collections.shuffle(bobCiphertextMessages, new Random(seed));
    Collections.shuffle(bobPlaintextMessages, new Random(seed));

    for (int i = 0; i < bobCiphertextMessages.size() / 2; i++) {
      byte[] receivedPlaintext =
          aliceCipher.decrypt(new WhisperMessage(bobCiphertextMessages.get(i).serialize()));
      assertTrue(Arrays.equals(receivedPlaintext, bobPlaintextMessages.get(i)));
    }

    for (int i = aliceCiphertextMessages.size() / 2; i < aliceCiphertextMessages.size(); i++) {
      byte[] receivedPlaintext =
          bobCipher.decrypt(new WhisperMessage(aliceCiphertextMessages.get(i).serialize()));
      assertTrue(Arrays.equals(receivedPlaintext, alicePlaintextMessages.get(i)));
    }

    for (int i = bobCiphertextMessages.size() / 2; i < bobCiphertextMessages.size(); i++) {
      byte[] receivedPlaintext =
          aliceCipher.decrypt(new WhisperMessage(bobCiphertextMessages.get(i).serialize()));
      assertTrue(Arrays.equals(receivedPlaintext, bobPlaintextMessages.get(i)));
    }
  }
 private boolean isSessionIdEqual(AxolotlStore aliceStore, AxolotlStore bobStore) {
   return Arrays.equals(
       aliceStore.loadSession(BOB_ADDRESS).getSessionState().getAliceBaseKey(),
       bobStore.loadSession(ALICE_ADDRESS).getSessionState().getAliceBaseKey());
 }