#include <stdio.h>
#include <stdlib.h>
#include <check.h>
#include <pthread.h>

#include "../src/signal_protocol.h"
#include "curve.h"
#include "session_record.h"
#include "session_state.h"
#include "session_cipher.h"
#include "session_builder.h"
#include "protocol.h"
#include "test_common.h"

static signal_protocol_address alice_address = {
        "+14159998888", 12, 1
};

static signal_protocol_address bob_address = {
        "+14151231234", 12, 1
};

signal_context *global_context;
pthread_mutex_t global_mutex;
pthread_mutexattr_t global_mutex_attr;

ec_key_pair *alice_signed_pre_key;
ec_key_pair *bob_signed_pre_key;
int32_t alice_signed_pre_key_id;
int32_t bob_signed_pre_key_id;

int is_session_id_equal(signal_protocol_store_context *alice_store, signal_protocol_store_context *bob_store);
int current_session_version(signal_protocol_store_context *store, const signal_protocol_address *address);
session_pre_key_bundle *create_alice_pre_key_bundle(signal_protocol_store_context *store);
session_pre_key_bundle *create_bob_pre_key_bundle(signal_protocol_store_context *store);

void test_lock(void *user_data)
{
    pthread_mutex_lock(&global_mutex);
}

void test_unlock(void *user_data)
{
    pthread_mutex_unlock(&global_mutex);
}

void test_setup()
{
    int result;

    pthread_mutexattr_init(&global_mutex_attr);
    pthread_mutexattr_settype(&global_mutex_attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&global_mutex, &global_mutex_attr);

    result = signal_context_create(&global_context, 0);
    ck_assert_int_eq(result, 0);
    signal_context_set_log_function(global_context, test_log);

    setup_test_crypto_provider(global_context);

    result = signal_context_set_locking_functions(global_context, test_lock, test_unlock);
    ck_assert_int_eq(result, 0);

    result = curve_generate_key_pair(global_context, &alice_signed_pre_key);
    ck_assert_int_eq(result, 0);

    result = curve_generate_key_pair(global_context, &bob_signed_pre_key);
    ck_assert_int_eq(result, 0);

    alice_signed_pre_key_id = (rand() & 0x7FFFFFFF) % PRE_KEY_MEDIUM_MAX_VALUE;
    bob_signed_pre_key_id = (rand() & 0x7FFFFFFF) % PRE_KEY_MEDIUM_MAX_VALUE;
}

void test_teardown()
{
    SIGNAL_UNREF(alice_signed_pre_key);
    SIGNAL_UNREF(bob_signed_pre_key);
    signal_context_destroy(global_context);

    pthread_mutex_destroy(&global_mutex);
    pthread_mutexattr_destroy(&global_mutex_attr);
}

START_TEST(test_basic_simultaneous_initiate)
{
    int result = 0;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the pre key bundles */
    session_pre_key_bundle *alice_pre_key_bundle =
            create_alice_pre_key_bundle(alice_store);
    session_pre_key_bundle *bob_pre_key_bundle =
            create_bob_pre_key_bundle(bob_store);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Process the pre key bundles */
    result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    /* Encrypt a pair of messages */
    static const char message_for_bob_data[] = "hey there";
    size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
    ciphertext_message *message_for_bob = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)message_for_bob_data, message_for_bob_len,
            &message_for_bob);
    ck_assert_int_eq(result, 0);

    static const char message_for_alice_data[] = "sample message";
    size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
    ciphertext_message *message_for_alice = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)message_for_alice_data, message_for_alice_len,
            &message_for_alice);
    ck_assert_int_eq(result, 0);

    /* Verify message types */
    ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
    ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Copy the messages before decrypting */
    pre_key_signal_message *message_for_alice_copy = 0;
    result = pre_key_signal_message_copy(&message_for_alice_copy,
            (pre_key_signal_message *)message_for_alice, global_context);
    ck_assert_int_eq(result, 0);

    pre_key_signal_message *message_for_bob_copy = 0;
    result = pre_key_signal_message_copy(&message_for_bob_copy,
            (pre_key_signal_message *)message_for_bob, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the messages */
    signal_buffer *alice_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(alice_session_cipher, message_for_alice_copy, 0, &alice_plaintext);
    ck_assert_int_eq(result, 0);

    signal_buffer *bob_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the messages decrypted correctly */
    uint8_t *alice_plaintext_data = signal_buffer_data(alice_plaintext);
    size_t alice_plaintext_len = signal_buffer_len(alice_plaintext);
    ck_assert_int_eq(message_for_alice_len, alice_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_alice_data, alice_plaintext_data, alice_plaintext_len), 0);

    uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
    size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
    ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

    /* Verify that the session versions are correct and the IDs are not equal */
    ck_assert_int_eq(current_session_version(alice_store, &bob_address), 3);
    ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the message before decrypting */
    signal_message *alice_response_copy = 0;
    result = signal_message_copy(&alice_response_copy,
            (signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Bob decrypt the response */
    signal_buffer *response_plaintext = 0;
    result = session_cipher_decrypt_signal_message(bob_session_cipher, alice_response_copy, 0, &response_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *response_plaintext_data = signal_buffer_data(response_plaintext);
    size_t response_plaintext_len = signal_buffer_len(response_plaintext);
    ck_assert_int_eq(alice_response_len, response_plaintext_len);
    ck_assert_int_eq(memcmp(alice_response_data, response_plaintext_data, response_plaintext_len), 0);

    /* Verify that the session IDs are now equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    signal_buffer_free(response_plaintext);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    signal_buffer_free(alice_plaintext);
    signal_buffer_free(bob_plaintext);
    SIGNAL_UNREF(message_for_alice_copy);
    SIGNAL_UNREF(message_for_bob_copy);
    SIGNAL_UNREF(message_for_alice);
    SIGNAL_UNREF(message_for_bob);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    SIGNAL_UNREF(alice_pre_key_bundle);
    SIGNAL_UNREF(bob_pre_key_bundle);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

START_TEST(test_lost_simultaneous_initiate)
{
    int result = 0;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the pre key bundles */
    session_pre_key_bundle *alice_pre_key_bundle =
            create_alice_pre_key_bundle(alice_store);
    session_pre_key_bundle *bob_pre_key_bundle =
            create_bob_pre_key_bundle(bob_store);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Process the pre key bundles */
    result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    /* Encrypt a pair of messages */
    static const char message_for_bob_data[] = "hey there";
    size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
    ciphertext_message *message_for_bob = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)message_for_bob_data, message_for_bob_len,
            &message_for_bob);
    ck_assert_int_eq(result, 0);

    static const char message_for_alice_data[] = "sample message";
    size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
    ciphertext_message *message_for_alice = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)message_for_alice_data, message_for_alice_len,
            &message_for_alice);
    ck_assert_int_eq(result, 0);

    /* Verify message types */
    ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
    ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Copy the message for Bob before decrypting */
    pre_key_signal_message *message_for_bob_copy = 0;
    result = pre_key_signal_message_copy(&message_for_bob_copy,
            (pre_key_signal_message *)message_for_bob, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the message */
    signal_buffer *bob_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
    size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
    ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

    /* Verify that the session version is correct */
    ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_PREKEY_TYPE);

    /* Copy the message before decrypting */
    pre_key_signal_message *alice_response_copy = 0;
    result = pre_key_signal_message_copy(&alice_response_copy,
            (pre_key_signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Bob decrypt the response */
    signal_buffer *response_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, alice_response_copy, 0, &response_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *response_plaintext_data = signal_buffer_data(response_plaintext);
    size_t response_plaintext_len = signal_buffer_len(response_plaintext);
    ck_assert_int_eq(alice_response_len, response_plaintext_len);
    ck_assert_int_eq(memcmp(alice_response_data, response_plaintext_data, response_plaintext_len), 0);

    /* Verify that the session IDs are now equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    signal_buffer_free(response_plaintext);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    signal_buffer_free(bob_plaintext);
    SIGNAL_UNREF(message_for_bob_copy);
    SIGNAL_UNREF(message_for_alice);
    SIGNAL_UNREF(message_for_bob);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    SIGNAL_UNREF(alice_pre_key_bundle);
    SIGNAL_UNREF(bob_pre_key_bundle);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

START_TEST(test_simultaneous_initiate_lost_message)
{
    int result = 0;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the pre key bundles */
    session_pre_key_bundle *alice_pre_key_bundle =
            create_alice_pre_key_bundle(alice_store);
    session_pre_key_bundle *bob_pre_key_bundle =
            create_bob_pre_key_bundle(bob_store);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Process the pre key bundles */
    result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    /* Encrypt a pair of messages */
    static const char message_for_bob_data[] = "hey there";
    size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
    ciphertext_message *message_for_bob = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)message_for_bob_data, message_for_bob_len,
            &message_for_bob);
    ck_assert_int_eq(result, 0);

    static const char message_for_alice_data[] = "sample message";
    size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
    ciphertext_message *message_for_alice = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)message_for_alice_data, message_for_alice_len,
            &message_for_alice);
    ck_assert_int_eq(result, 0);

    /* Verify message types */
    ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
    ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Copy the messages before decrypting */
    pre_key_signal_message *message_for_alice_copy = 0;
    result = pre_key_signal_message_copy(&message_for_alice_copy,
            (pre_key_signal_message *)message_for_alice, global_context);
    ck_assert_int_eq(result, 0);

    pre_key_signal_message *message_for_bob_copy = 0;
    result = pre_key_signal_message_copy(&message_for_bob_copy,
            (pre_key_signal_message *)message_for_bob, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the messages */
    signal_buffer *alice_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(alice_session_cipher, message_for_alice_copy, 0, &alice_plaintext);
    ck_assert_int_eq(result, 0);

    signal_buffer *bob_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the messages decrypted correctly */
    uint8_t *alice_plaintext_data = signal_buffer_data(alice_plaintext);
    size_t alice_plaintext_len = signal_buffer_len(alice_plaintext);
    ck_assert_int_eq(message_for_alice_len, alice_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_alice_data, alice_plaintext_data, alice_plaintext_len), 0);

    uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
    size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
    ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

    /* Verify that the session versions are correct and the IDs are not equal */
    ck_assert_int_eq(current_session_version(alice_store, &bob_address), 3);
    ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the message before decrypting */
    signal_message *alice_response_copy = 0;
    result = signal_message_copy(&alice_response_copy,
            (signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /*
     * Intentionally skip Bob decrypting the response, and continue
     * with the rest of the test.
     */

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    signal_buffer_free(alice_plaintext);
    signal_buffer_free(bob_plaintext);
    SIGNAL_UNREF(message_for_alice_copy);
    SIGNAL_UNREF(message_for_bob_copy);
    SIGNAL_UNREF(message_for_alice);
    SIGNAL_UNREF(message_for_bob);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    SIGNAL_UNREF(alice_pre_key_bundle);
    SIGNAL_UNREF(bob_pre_key_bundle);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

START_TEST(test_simultaneous_initiate_repeated_messages)
{
    int result = 0;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the pre key bundles */
    session_pre_key_bundle *alice_pre_key_bundle =
            create_alice_pre_key_bundle(alice_store);
    session_pre_key_bundle *bob_pre_key_bundle =
            create_bob_pre_key_bundle(bob_store);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Process the pre key bundles */
    result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    /* Encrypt a pair of messages */
    static const char message_for_bob_data[] = "hey there";
    size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
    ciphertext_message *message_for_bob = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)message_for_bob_data, message_for_bob_len,
            &message_for_bob);
    ck_assert_int_eq(result, 0);

    static const char message_for_alice_data[] = "sample message";
    size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
    ciphertext_message *message_for_alice = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)message_for_alice_data, message_for_alice_len,
            &message_for_alice);
    ck_assert_int_eq(result, 0);

    /* Verify message types */
    ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
    ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Copy the messages before decrypting */
    pre_key_signal_message *message_for_alice_copy = 0;
    result = pre_key_signal_message_copy(&message_for_alice_copy,
            (pre_key_signal_message *)message_for_alice, global_context);
    ck_assert_int_eq(result, 0);

    pre_key_signal_message *message_for_bob_copy = 0;
    result = pre_key_signal_message_copy(&message_for_bob_copy,
            (pre_key_signal_message *)message_for_bob, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the messages */
    signal_buffer *alice_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(alice_session_cipher, message_for_alice_copy, 0, &alice_plaintext);
    ck_assert_int_eq(result, 0);

    signal_buffer *bob_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the messages decrypted correctly */
    uint8_t *alice_plaintext_data = signal_buffer_data(alice_plaintext);
    size_t alice_plaintext_len = signal_buffer_len(alice_plaintext);
    ck_assert_int_eq(message_for_alice_len, alice_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_alice_data, alice_plaintext_data, alice_plaintext_len), 0);

    uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
    size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
    ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
    ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

    /* Verify that the session versions are correct and the IDs are not equal */
    ck_assert_int_eq(current_session_version(alice_store, &bob_address), 3);
    ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    int i;
    for(i = 0; i < 50; i++) {
        fprintf(stderr, "Simultaneous initiate tests, iteration: %d\n", i);

        /* Encrypt a pair of messages */
        static const char message_for_bob_repeat_data[] = "hey there";
        size_t message_for_bob_repeat_len = sizeof(message_for_bob_repeat_data) - 1;
        ciphertext_message *message_for_bob_repeat = 0;
        result = session_cipher_encrypt(alice_session_cipher,
                (uint8_t *)message_for_bob_repeat_data, message_for_bob_repeat_len,
                &message_for_bob_repeat);
        ck_assert_int_eq(result, 0);

        static const char message_for_alice_repeat_data[] = "sample message";
        size_t message_for_alice_repeat_len = sizeof(message_for_alice_repeat_data) - 1;
        ciphertext_message *message_for_alice_repeat = 0;
        result = session_cipher_encrypt(bob_session_cipher,
                (uint8_t *)message_for_alice_repeat_data, message_for_alice_repeat_len,
                &message_for_alice_repeat);
        ck_assert_int_eq(result, 0);

        /* Verify message types */
        ck_assert_int_eq(ciphertext_message_get_type(message_for_bob_repeat), CIPHERTEXT_SIGNAL_TYPE);
        ck_assert_int_eq(ciphertext_message_get_type(message_for_alice_repeat), CIPHERTEXT_SIGNAL_TYPE);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Copy the messages before decrypting */
        signal_message *message_for_alice_repeat_copy = 0;
        result = signal_message_copy(&message_for_alice_repeat_copy,
                (signal_message *)message_for_alice_repeat, global_context);
        ck_assert_int_eq(result, 0);

        signal_message *message_for_bob_repeat_copy = 0;
        result = signal_message_copy(&message_for_bob_repeat_copy,
                (signal_message *)message_for_bob_repeat, global_context);
        ck_assert_int_eq(result, 0);

        /* Decrypt the messages */
        signal_buffer *alice_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(alice_session_cipher, message_for_alice_repeat_copy, 0, &alice_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        signal_buffer *bob_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(bob_session_cipher, message_for_bob_repeat_copy, 0, &bob_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        /* Verify that the messages decrypted correctly */
        uint8_t *alice_repeat_plaintext_data = signal_buffer_data(alice_repeat_plaintext);
        size_t alice_repeat_plaintext_len = signal_buffer_len(alice_repeat_plaintext);
        ck_assert_int_eq(message_for_alice_repeat_len, alice_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_alice_repeat_data, alice_repeat_plaintext_data, alice_repeat_plaintext_len), 0);

        uint8_t *bob_repeat_plaintext_data = signal_buffer_data(bob_repeat_plaintext);
        size_t bob_repeat_plaintext_len = signal_buffer_len(bob_repeat_plaintext);
        ck_assert_int_eq(message_for_bob_repeat_len, bob_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_bob_repeat_data, bob_repeat_plaintext_data, bob_repeat_plaintext_len), 0);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Cleanup */
        signal_buffer_free(bob_repeat_plaintext);
        signal_buffer_free(alice_repeat_plaintext);
        SIGNAL_UNREF(message_for_bob_repeat_copy);
        SIGNAL_UNREF(message_for_alice_repeat_copy);
        SIGNAL_UNREF(message_for_bob_repeat);
        SIGNAL_UNREF(message_for_alice_repeat);
    }

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the message before decrypting */
    signal_message *alice_response_copy = 0;
    result = signal_message_copy(&alice_response_copy,
            (signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Bob decrypt the response */
    signal_buffer *response_plaintext = 0;
    result = session_cipher_decrypt_signal_message(bob_session_cipher, alice_response_copy, 0, &response_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *response_plaintext_data = signal_buffer_data(response_plaintext);
    size_t response_plaintext_len = signal_buffer_len(response_plaintext);
    ck_assert_int_eq(alice_response_len, response_plaintext_len);
    ck_assert_int_eq(memcmp(alice_response_data, response_plaintext_data, response_plaintext_len), 0);

    /* Verify that the session IDs are now equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    signal_buffer_free(response_plaintext);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    signal_buffer_free(alice_plaintext);
    signal_buffer_free(bob_plaintext);
    SIGNAL_UNREF(message_for_alice_copy);
    SIGNAL_UNREF(message_for_bob_copy);
    SIGNAL_UNREF(message_for_alice);
    SIGNAL_UNREF(message_for_bob);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    SIGNAL_UNREF(alice_pre_key_bundle);
    SIGNAL_UNREF(bob_pre_key_bundle);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

START_TEST(test_repeated_simultaneous_initiate_repeated_messages)
{
    int result = 0;
    int i;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    for(i = 0; i < 15; i++) {
        /* Create the pre key bundles */
        session_pre_key_bundle *alice_pre_key_bundle =
                create_alice_pre_key_bundle(alice_store);
        session_pre_key_bundle *bob_pre_key_bundle =
                create_bob_pre_key_bundle(bob_store);

        /* Process the pre key bundles */
        result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
        ck_assert_int_eq(result, 0);

        result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
        ck_assert_int_eq(result, 0);

        /* Encrypt a pair of messages */
        static const char message_for_bob_data[] = "hey there";
        size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
        ciphertext_message *message_for_bob = 0;
        result = session_cipher_encrypt(alice_session_cipher,
                (uint8_t *)message_for_bob_data, message_for_bob_len,
                &message_for_bob);
        ck_assert_int_eq(result, 0);

        static const char message_for_alice_data[] = "sample message";
        size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
        ciphertext_message *message_for_alice = 0;
        result = session_cipher_encrypt(bob_session_cipher,
                (uint8_t *)message_for_alice_data, message_for_alice_len,
                &message_for_alice);
        ck_assert_int_eq(result, 0);

        /* Verify message types */
        ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
        ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Copy the messages before decrypting */
        pre_key_signal_message *message_for_alice_copy = 0;
        result = pre_key_signal_message_copy(&message_for_alice_copy,
                (pre_key_signal_message *)message_for_alice, global_context);
        ck_assert_int_eq(result, 0);

        pre_key_signal_message *message_for_bob_copy = 0;
        result = pre_key_signal_message_copy(&message_for_bob_copy,
                (pre_key_signal_message *)message_for_bob, global_context);
        ck_assert_int_eq(result, 0);

        /* Decrypt the messages */
        signal_buffer *alice_plaintext = 0;
        result = session_cipher_decrypt_pre_key_signal_message(alice_session_cipher, message_for_alice_copy, 0, &alice_plaintext);
        ck_assert_int_eq(result, 0);

        signal_buffer *bob_plaintext = 0;
        result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
        ck_assert_int_eq(result, 0);

        /* Verify that the messages decrypted correctly */
        uint8_t *alice_plaintext_data = signal_buffer_data(alice_plaintext);
        size_t alice_plaintext_len = signal_buffer_len(alice_plaintext);
        ck_assert_int_eq(message_for_alice_len, alice_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_alice_data, alice_plaintext_data, alice_plaintext_len), 0);

        uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
        size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
        ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

        /* Verify that the session versions are correct and the IDs are not equal */
        ck_assert_int_eq(current_session_version(alice_store, &bob_address), 3);
        ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Cleanup */
        signal_buffer_free(alice_plaintext);
        signal_buffer_free(bob_plaintext);
        SIGNAL_UNREF(message_for_alice_copy);
        SIGNAL_UNREF(message_for_bob_copy);
        SIGNAL_UNREF(message_for_alice);
        SIGNAL_UNREF(message_for_bob);
        SIGNAL_UNREF(alice_pre_key_bundle);
        SIGNAL_UNREF(bob_pre_key_bundle);
    }

    for(i = 0; i < 50; i++) {
        fprintf(stderr, "Simultaneous initiate tests, iteration: %d\n", i);

        /* Encrypt a pair of messages */
        static const char message_for_bob_repeat_data[] = "hey there";
        size_t message_for_bob_repeat_len = sizeof(message_for_bob_repeat_data) - 1;
        ciphertext_message *message_for_bob_repeat = 0;
        result = session_cipher_encrypt(alice_session_cipher,
                (uint8_t *)message_for_bob_repeat_data, message_for_bob_repeat_len,
                &message_for_bob_repeat);
        ck_assert_int_eq(result, 0);

        static const char message_for_alice_repeat_data[] = "sample message";
        size_t message_for_alice_repeat_len = sizeof(message_for_alice_repeat_data) - 1;
        ciphertext_message *message_for_alice_repeat = 0;
        result = session_cipher_encrypt(bob_session_cipher,
                (uint8_t *)message_for_alice_repeat_data, message_for_alice_repeat_len,
                &message_for_alice_repeat);
        ck_assert_int_eq(result, 0);

        /* Verify message types */
        ck_assert_int_eq(ciphertext_message_get_type(message_for_bob_repeat), CIPHERTEXT_SIGNAL_TYPE);
        ck_assert_int_eq(ciphertext_message_get_type(message_for_alice_repeat), CIPHERTEXT_SIGNAL_TYPE);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Copy the messages before decrypting */
        signal_message *message_for_alice_repeat_copy = 0;
        result = signal_message_copy(&message_for_alice_repeat_copy,
                (signal_message *)message_for_alice_repeat, global_context);
        ck_assert_int_eq(result, 0);

        signal_message *message_for_bob_repeat_copy = 0;
        result = signal_message_copy(&message_for_bob_repeat_copy,
                (signal_message *)message_for_bob_repeat, global_context);
        ck_assert_int_eq(result, 0);

        /* Decrypt the messages */
        signal_buffer *alice_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(alice_session_cipher, message_for_alice_repeat_copy, 0, &alice_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        signal_buffer *bob_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(bob_session_cipher, message_for_bob_repeat_copy, 0, &bob_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        /* Verify that the messages decrypted correctly */
        uint8_t *alice_repeat_plaintext_data = signal_buffer_data(alice_repeat_plaintext);
        size_t alice_repeat_plaintext_len = signal_buffer_len(alice_repeat_plaintext);
        ck_assert_int_eq(message_for_alice_repeat_len, alice_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_alice_repeat_data, alice_repeat_plaintext_data, alice_repeat_plaintext_len), 0);

        uint8_t *bob_repeat_plaintext_data = signal_buffer_data(bob_repeat_plaintext);
        size_t bob_repeat_plaintext_len = signal_buffer_len(bob_repeat_plaintext);
        ck_assert_int_eq(message_for_bob_repeat_len, bob_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_bob_repeat_data, bob_repeat_plaintext_data, bob_repeat_plaintext_len), 0);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Cleanup */
        signal_buffer_free(bob_repeat_plaintext);
        signal_buffer_free(alice_repeat_plaintext);
        SIGNAL_UNREF(message_for_bob_repeat_copy);
        SIGNAL_UNREF(message_for_alice_repeat_copy);
        SIGNAL_UNREF(message_for_bob_repeat);
        SIGNAL_UNREF(message_for_alice_repeat);
    }

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the message before decrypting */
    signal_message *alice_response_copy = 0;
    result = signal_message_copy(&alice_response_copy,
            (signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Bob decrypt the response */
    signal_buffer *response_plaintext = 0;
    result = session_cipher_decrypt_signal_message(bob_session_cipher, alice_response_copy, 0, &response_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *response_plaintext_data = signal_buffer_data(response_plaintext);
    size_t response_plaintext_len = signal_buffer_len(response_plaintext);
    ck_assert_int_eq(alice_response_len, response_plaintext_len);
    ck_assert_int_eq(memcmp(alice_response_data, response_plaintext_data, response_plaintext_len), 0);

    /* Verify that the session IDs are now equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    signal_buffer_free(response_plaintext);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

START_TEST(test_repeated_simultaneous_initiate_lost_message_repeated_messages)
{
    int result = 0;
    int i;

    /* Create the data stores */
    signal_protocol_store_context *alice_store = 0;
    setup_test_store_context(&alice_store, global_context);
    signal_protocol_store_context *bob_store = 0;
    setup_test_store_context(&bob_store, global_context);

    /* Create the session builders */
    session_builder *alice_session_builder = 0;
    result = session_builder_create(&alice_session_builder, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_builder *bob_session_builder = 0;
    result = session_builder_create(&bob_session_builder, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the session ciphers */
    session_cipher *alice_session_cipher = 0;
    result = session_cipher_create(&alice_session_cipher, alice_store, &bob_address, global_context);
    ck_assert_int_eq(result, 0);

    session_cipher *bob_session_cipher = 0;
    result = session_cipher_create(&bob_session_cipher, bob_store, &alice_address, global_context);
    ck_assert_int_eq(result, 0);

    /* Create the pre key bundles, intentionally skipping Alice's */
    session_pre_key_bundle *bob_lost_pre_key_bundle =
            create_bob_pre_key_bundle(bob_store);

    /* Process the pre key bundles, intentionally skipping Alice's */
    result = session_builder_process_pre_key_bundle(alice_session_builder, bob_lost_pre_key_bundle);
    ck_assert_int_eq(result, 0);

    /* Encrypt a pair of messages, intentionally skipping Alice's */
    static const char lost_message_for_bob_data[] = "hey there";
    size_t lost_message_for_bob_len = sizeof(lost_message_for_bob_data) - 1;
    ciphertext_message *lost_message_for_bob = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)lost_message_for_bob_data, lost_message_for_bob_len,
            &lost_message_for_bob);
    ck_assert_int_eq(result, 0);

    for(i = 0; i < 15; i++) {
        /* Create the pre key bundles */
        session_pre_key_bundle *alice_pre_key_bundle =
                create_alice_pre_key_bundle(alice_store);
        session_pre_key_bundle *bob_pre_key_bundle =
                create_bob_pre_key_bundle(bob_store);

        /* Process the pre key bundles */
        result = session_builder_process_pre_key_bundle(alice_session_builder, bob_pre_key_bundle);
        ck_assert_int_eq(result, 0);

        result = session_builder_process_pre_key_bundle(bob_session_builder, alice_pre_key_bundle);
        ck_assert_int_eq(result, 0);

        /* Encrypt a pair of messages */
        static const char message_for_bob_data[] = "hey there";
        size_t message_for_bob_len = sizeof(message_for_bob_data) - 1;
        ciphertext_message *message_for_bob = 0;
        result = session_cipher_encrypt(alice_session_cipher,
                (uint8_t *)message_for_bob_data, message_for_bob_len,
                &message_for_bob);
        ck_assert_int_eq(result, 0);

        static const char message_for_alice_data[] = "sample message";
        size_t message_for_alice_len = sizeof(message_for_alice_data) - 1;
        ciphertext_message *message_for_alice = 0;
        result = session_cipher_encrypt(bob_session_cipher,
                (uint8_t *)message_for_alice_data, message_for_alice_len,
                &message_for_alice);
        ck_assert_int_eq(result, 0);

        /* Verify message types */
        ck_assert_int_eq(ciphertext_message_get_type(message_for_bob), CIPHERTEXT_PREKEY_TYPE);
        ck_assert_int_eq(ciphertext_message_get_type(message_for_alice), CIPHERTEXT_PREKEY_TYPE);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Copy the messages before decrypting */
        pre_key_signal_message *message_for_alice_copy = 0;
        result = pre_key_signal_message_copy(&message_for_alice_copy,
                (pre_key_signal_message *)message_for_alice, global_context);
        ck_assert_int_eq(result, 0);

        pre_key_signal_message *message_for_bob_copy = 0;
        result = pre_key_signal_message_copy(&message_for_bob_copy,
                (pre_key_signal_message *)message_for_bob, global_context);
        ck_assert_int_eq(result, 0);

        /* Decrypt the messages */
        signal_buffer *alice_plaintext = 0;
        result = session_cipher_decrypt_pre_key_signal_message(alice_session_cipher, message_for_alice_copy, 0, &alice_plaintext);
        ck_assert_int_eq(result, 0);

        signal_buffer *bob_plaintext = 0;
        result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher, message_for_bob_copy, 0, &bob_plaintext);
        ck_assert_int_eq(result, 0);

        /* Verify that the messages decrypted correctly */
        uint8_t *alice_plaintext_data = signal_buffer_data(alice_plaintext);
        size_t alice_plaintext_len = signal_buffer_len(alice_plaintext);
        ck_assert_int_eq(message_for_alice_len, alice_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_alice_data, alice_plaintext_data, alice_plaintext_len), 0);

        uint8_t *bob_plaintext_data = signal_buffer_data(bob_plaintext);
        size_t bob_plaintext_len = signal_buffer_len(bob_plaintext);
        ck_assert_int_eq(message_for_bob_len, bob_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_bob_data, bob_plaintext_data, bob_plaintext_len), 0);

        /* Verify that the session versions are correct and the IDs are not equal */
        ck_assert_int_eq(current_session_version(alice_store, &bob_address), 3);
        ck_assert_int_eq(current_session_version(bob_store, &alice_address), 3);
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Cleanup */
        signal_buffer_free(alice_plaintext);
        signal_buffer_free(bob_plaintext);
        SIGNAL_UNREF(message_for_alice_copy);
        SIGNAL_UNREF(message_for_bob_copy);
        SIGNAL_UNREF(message_for_alice);
        SIGNAL_UNREF(message_for_bob);
        SIGNAL_UNREF(alice_pre_key_bundle);
        SIGNAL_UNREF(bob_pre_key_bundle);
    }

    for(i = 0; i < 50; i++) {
        fprintf(stderr, "Simultaneous initiate tests, iteration: %d\n", i);

        /* Encrypt a pair of messages */
        static const char message_for_bob_repeat_data[] = "hey there";
        size_t message_for_bob_repeat_len = sizeof(message_for_bob_repeat_data) - 1;
        ciphertext_message *message_for_bob_repeat = 0;
        result = session_cipher_encrypt(alice_session_cipher,
                (uint8_t *)message_for_bob_repeat_data, message_for_bob_repeat_len,
                &message_for_bob_repeat);
        ck_assert_int_eq(result, 0);

        static const char message_for_alice_repeat_data[] = "sample message";
        size_t message_for_alice_repeat_len = sizeof(message_for_alice_repeat_data) - 1;
        ciphertext_message *message_for_alice_repeat = 0;
        result = session_cipher_encrypt(bob_session_cipher,
                (uint8_t *)message_for_alice_repeat_data, message_for_alice_repeat_len,
                &message_for_alice_repeat);
        ck_assert_int_eq(result, 0);

        /* Verify message types */
        ck_assert_int_eq(ciphertext_message_get_type(message_for_bob_repeat), CIPHERTEXT_SIGNAL_TYPE);
        ck_assert_int_eq(ciphertext_message_get_type(message_for_alice_repeat), CIPHERTEXT_SIGNAL_TYPE);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Copy the messages before decrypting */
        signal_message *message_for_alice_repeat_copy = 0;
        result = signal_message_copy(&message_for_alice_repeat_copy,
                (signal_message *)message_for_alice_repeat, global_context);
        ck_assert_int_eq(result, 0);

        signal_message *message_for_bob_repeat_copy = 0;
        result = signal_message_copy(&message_for_bob_repeat_copy,
                (signal_message *)message_for_bob_repeat, global_context);
        ck_assert_int_eq(result, 0);

        /* Decrypt the messages */
        signal_buffer *alice_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(alice_session_cipher, message_for_alice_repeat_copy, 0, &alice_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        signal_buffer *bob_repeat_plaintext = 0;
        result = session_cipher_decrypt_signal_message(bob_session_cipher, message_for_bob_repeat_copy, 0, &bob_repeat_plaintext);
        ck_assert_int_eq(result, 0);

        /* Verify that the messages decrypted correctly */
        uint8_t *alice_repeat_plaintext_data = signal_buffer_data(alice_repeat_plaintext);
        size_t alice_repeat_plaintext_len = signal_buffer_len(alice_repeat_plaintext);
        ck_assert_int_eq(message_for_alice_repeat_len, alice_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_alice_repeat_data, alice_repeat_plaintext_data, alice_repeat_plaintext_len), 0);

        uint8_t *bob_repeat_plaintext_data = signal_buffer_data(bob_repeat_plaintext);
        size_t bob_repeat_plaintext_len = signal_buffer_len(bob_repeat_plaintext);
        ck_assert_int_eq(message_for_bob_repeat_len, bob_repeat_plaintext_len);
        ck_assert_int_eq(memcmp(message_for_bob_repeat_data, bob_repeat_plaintext_data, bob_repeat_plaintext_len), 0);

        /* Verify that the session IDs are not equal */
        ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

        /* Cleanup */
        signal_buffer_free(bob_repeat_plaintext);
        signal_buffer_free(alice_repeat_plaintext);
        SIGNAL_UNREF(message_for_bob_repeat_copy);
        SIGNAL_UNREF(message_for_alice_repeat_copy);
        SIGNAL_UNREF(message_for_bob_repeat);
        SIGNAL_UNREF(message_for_alice_repeat);
    }

    /* Prepare Alice's response */
    static const char alice_response_data[] = "second message";
    size_t alice_response_len = sizeof(alice_response_data) - 1;
    ciphertext_message *alice_response = 0;
    result = session_cipher_encrypt(alice_session_cipher,
            (uint8_t *)alice_response_data, alice_response_len,
            &alice_response);
    ck_assert_int_eq(result, 0);

    /* Verify response message type */
    ck_assert_int_eq(ciphertext_message_get_type(alice_response), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the message before decrypting */
    signal_message *alice_response_copy = 0;
    result = signal_message_copy(&alice_response_copy,
            (signal_message *)alice_response, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Bob decrypt the response */
    signal_buffer *response_plaintext = 0;
    result = session_cipher_decrypt_signal_message(bob_session_cipher, alice_response_copy, 0, &response_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the message decrypted correctly */
    uint8_t *response_plaintext_data = signal_buffer_data(response_plaintext);
    size_t response_plaintext_len = signal_buffer_len(response_plaintext);
    ck_assert_int_eq(alice_response_len, response_plaintext_len);
    ck_assert_int_eq(memcmp(alice_response_data, response_plaintext_data, response_plaintext_len), 0);

    /* Verify that the session IDs are now equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Prepare Bob's final message */
    static const char final_message_data[] = "third message";
    size_t final_message_len = sizeof(final_message_data) - 1;
    ciphertext_message *final_message = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)final_message_data, final_message_len,
            &final_message);
    ck_assert_int_eq(result, 0);

    /* Verify final message type */
    ck_assert_int_eq(ciphertext_message_get_type(final_message), CIPHERTEXT_SIGNAL_TYPE);

    /* Copy the final message before decrypting */
    signal_message *final_message_copy = 0;
    result = signal_message_copy(&final_message_copy,
            (signal_message *)final_message, global_context);
    ck_assert_int_eq(result, 0);

    /* Have Alice decrypt the final message */
    signal_buffer *final_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher, final_message_copy, 0, &final_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the final message decrypted correctly */
    uint8_t *final_plaintext_data = signal_buffer_data(final_plaintext);
    size_t final_plaintext_len = signal_buffer_len(final_plaintext);
    ck_assert_int_eq(final_message_len, final_plaintext_len);
    ck_assert_int_eq(memcmp(final_message_data, final_plaintext_data, final_plaintext_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Copy the lost message before decrypting */
    pre_key_signal_message *lost_message_for_bob_copy = 0;
    result = pre_key_signal_message_copy(&lost_message_for_bob_copy,
            (pre_key_signal_message *)lost_message_for_bob, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the lost message */
    signal_buffer *lost_message_for_bob_plaintext = 0;
    result = session_cipher_decrypt_pre_key_signal_message(bob_session_cipher,
            lost_message_for_bob_copy, 0, &lost_message_for_bob_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the lost message decrypted correctly */
    uint8_t *lost_bob_plaintext_data = signal_buffer_data(lost_message_for_bob_plaintext);
    size_t lost_bob_plaintext_len = signal_buffer_len(lost_message_for_bob_plaintext);
    ck_assert_int_eq(lost_message_for_bob_len, lost_bob_plaintext_len);
    ck_assert_int_eq(memcmp(lost_message_for_bob_data, lost_bob_plaintext_data, lost_bob_plaintext_len), 0);

    /* Verify that the session IDs are not equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 0);

    /* Encrypt an unexpected final message */
    static const char blast_from_the_past_data[] = "unexpected!";
    size_t blast_from_the_past_len = sizeof(blast_from_the_past_data) - 1;
    ciphertext_message *blast_from_the_past = 0;
    result = session_cipher_encrypt(bob_session_cipher,
            (uint8_t *)blast_from_the_past_data, blast_from_the_past_len,
            &blast_from_the_past);
    ck_assert_int_eq(result, 0);

    /* Copy the unexpected message before decrypting */
    signal_message *blast_from_the_past_copy = 0;
    result = signal_message_copy(&blast_from_the_past_copy,
            (signal_message *)blast_from_the_past, global_context);
    ck_assert_int_eq(result, 0);

    /* Decrypt the unexpected message */
    signal_buffer *blast_from_the_past_plaintext = 0;
    result = session_cipher_decrypt_signal_message(alice_session_cipher,
            blast_from_the_past_copy, 0, &blast_from_the_past_plaintext);
    ck_assert_int_eq(result, 0);

    /* Verify that the unexpected message decrypted correctly */
    uint8_t *blast_from_the_past_decrypted_data = signal_buffer_data(blast_from_the_past_plaintext);
    size_t blast_from_the_past_decrypted_len = signal_buffer_len(blast_from_the_past_plaintext);
    ck_assert_int_eq(blast_from_the_past_len, blast_from_the_past_decrypted_len);
    ck_assert_int_eq(memcmp(blast_from_the_past_data, blast_from_the_past_decrypted_data, blast_from_the_past_decrypted_len), 0);

    /* Verify that the session IDs are equal */
    ck_assert_int_eq(is_session_id_equal(alice_store, bob_store), 1);

    /* Cleanup */
    signal_buffer_free(blast_from_the_past_plaintext);
    SIGNAL_UNREF(blast_from_the_past_copy);
    SIGNAL_UNREF(blast_from_the_past);
    signal_buffer_free(lost_message_for_bob_plaintext);
    SIGNAL_UNREF(lost_message_for_bob_copy);
    signal_buffer_free(final_plaintext);
    SIGNAL_UNREF(final_message_copy);
    SIGNAL_UNREF(final_message);
    signal_buffer_free(response_plaintext);
    SIGNAL_UNREF(alice_response_copy);
    SIGNAL_UNREF(alice_response);
    SIGNAL_UNREF(bob_lost_pre_key_bundle);
    SIGNAL_UNREF(lost_message_for_bob);
    session_cipher_free(alice_session_cipher);
    session_cipher_free(bob_session_cipher);
    session_builder_free(alice_session_builder);
    session_builder_free(bob_session_builder);
    signal_protocol_store_context_destroy(alice_store);
    signal_protocol_store_context_destroy(bob_store);
}
END_TEST

int is_session_id_equal(signal_protocol_store_context *alice_store, signal_protocol_store_context *bob_store)
{
    int result = 0;
    int version = 2;
    session_record *alice_store_record = 0;
    session_record *bob_store_record = 0;
    ec_public_key *alice_store_alice_base_key = 0;
    ec_public_key *bob_store_alice_base_key = 0;

    result = signal_protocol_session_load_session(alice_store, &alice_store_record, &bob_address, version);
    ck_assert_int_eq(result, 0);

    result = signal_protocol_session_load_session(bob_store, &bob_store_record, &alice_address, version);
    ck_assert_int_eq(result, 0);

    alice_store_alice_base_key =
            session_state_get_alice_base_key(session_record_get_state(alice_store_record));
    bob_store_alice_base_key =
            session_state_get_alice_base_key(session_record_get_state(bob_store_record));

    if(ec_public_key_compare(alice_store_alice_base_key, bob_store_alice_base_key) == 0) {
        result = 1;
    }
    else {
        result = 0;
    }

    SIGNAL_UNREF(alice_store_record);
    SIGNAL_UNREF(bob_store_record);
    return result;
}

int current_session_version(signal_protocol_store_context *store, const signal_protocol_address *address)
{
    int result = 0;
    int version = 2;
    session_record *record = 0;
    session_state *state = 0;

    result = signal_protocol_session_load_session(store, &record, address, version);
    ck_assert_int_eq(result, 0);

    state = session_record_get_state(record);

    result = session_state_get_session_version(state);

    SIGNAL_UNREF(record);

    return result;
}

session_pre_key_bundle *create_alice_pre_key_bundle(signal_protocol_store_context *store)
{
    int result = 0;

    ec_key_pair *alice_unsigned_pre_key = 0;
    curve_generate_key_pair(global_context, &alice_unsigned_pre_key);
    ck_assert_int_eq(result, 0);

    int alice_unsigned_pre_key_id = (rand() & 0x7FFFFFFF) % PRE_KEY_MEDIUM_MAX_VALUE;

    ratchet_identity_key_pair *alice_identity_key_pair = 0;
    result = signal_protocol_identity_get_key_pair(store, &alice_identity_key_pair);
    ck_assert_int_eq(result, 0);

    ec_public_key *alice_signed_pre_key_public = ec_key_pair_get_public(alice_signed_pre_key);

    signal_buffer *alice_signed_pre_key_public_serialized = 0;
    result = ec_public_key_serialize(&alice_signed_pre_key_public_serialized, alice_signed_pre_key_public);
    ck_assert_int_eq(result, 0);

    signal_buffer *alice_signed_pre_key_public_serialized_omemo = 0;
    result = ec_public_key_serialize_omemo(&alice_signed_pre_key_public_serialized_omemo, alice_signed_pre_key_public);
    ck_assert_int_eq(result, 0);

    signal_buffer *signature = 0;
    result = curve_calculate_signature(global_context, &signature,
            ratchet_identity_key_pair_get_private(alice_identity_key_pair),
            signal_buffer_data(alice_signed_pre_key_public_serialized),
            signal_buffer_len(alice_signed_pre_key_public_serialized));
    ck_assert_int_eq(result, 0);

    signal_buffer *signature_omemo = 0;
    result = curve_calculate_signature(global_context, &signature_omemo,
            ratchet_identity_key_pair_get_private(alice_identity_key_pair),
            signal_buffer_data(alice_signed_pre_key_public_serialized_omemo),
            signal_buffer_len(alice_signed_pre_key_public_serialized_omemo));
    ck_assert_int_eq(result, 0);

    session_pre_key_bundle *alice_pre_key_bundle = 0;
    result = session_pre_key_bundle_create(&alice_pre_key_bundle,
            1, 1,
            alice_unsigned_pre_key_id,
            ec_key_pair_get_public(alice_unsigned_pre_key),
            alice_signed_pre_key_id, alice_signed_pre_key_public,
            signal_buffer_data(signature), signal_buffer_len(signature),
            ratchet_identity_key_pair_get_public(alice_identity_key_pair));
    ck_assert_int_eq(result, 0);

    session_signed_pre_key *signed_pre_key_record = 0;
    result = session_signed_pre_key_create(&signed_pre_key_record,
            alice_signed_pre_key_id, time(0), alice_signed_pre_key,
            signal_buffer_data(signature), signal_buffer_len(signature),
            signal_buffer_data(signature_omemo), signal_buffer_len(signature_omemo));
    ck_assert_int_eq(result, 0);

    result = signal_protocol_signed_pre_key_store_key(store, signed_pre_key_record);
    ck_assert_int_eq(result, 0);

    session_pre_key *pre_key_record = 0;
    result = session_pre_key_create(&pre_key_record, alice_unsigned_pre_key_id, alice_unsigned_pre_key);
    ck_assert_int_eq(result, 0);

    result = signal_protocol_pre_key_store_key(store, pre_key_record);
    ck_assert_int_eq(result, 0);

    SIGNAL_UNREF(pre_key_record);
    SIGNAL_UNREF(signed_pre_key_record);
    SIGNAL_UNREF(alice_identity_key_pair);
    SIGNAL_UNREF(alice_unsigned_pre_key);
    signal_buffer_free(alice_signed_pre_key_public_serialized);
    signal_buffer_free(alice_signed_pre_key_public_serialized_omemo);
    signal_buffer_free(signature);
    signal_buffer_free(signature_omemo);

    return alice_pre_key_bundle;
}

session_pre_key_bundle *create_bob_pre_key_bundle(signal_protocol_store_context *store)
{
    int result = 0;

    ec_key_pair *bob_unsigned_pre_key = 0;
    curve_generate_key_pair(global_context, &bob_unsigned_pre_key);
    ck_assert_int_eq(result, 0);

    int bob_unsigned_pre_key_id = (rand() & 0x7FFFFFFF) % PRE_KEY_MEDIUM_MAX_VALUE;

    ratchet_identity_key_pair *bob_identity_key_pair = 0;
    result = signal_protocol_identity_get_key_pair(store, &bob_identity_key_pair);
    ck_assert_int_eq(result, 0);

    ec_public_key *bob_signed_pre_key_public = ec_key_pair_get_public(bob_signed_pre_key);

    signal_buffer *bob_signed_pre_key_public_serialized = 0;
    result = ec_public_key_serialize(&bob_signed_pre_key_public_serialized, bob_signed_pre_key_public);
    ck_assert_int_eq(result, 0);

    signal_buffer *bob_signed_pre_key_public_serialized_omemo = 0;
    result = ec_public_key_serialize_omemo(&bob_signed_pre_key_public_serialized_omemo, bob_signed_pre_key_public);
    ck_assert_int_eq(result, 0);

    signal_buffer *signature = 0;
    result = curve_calculate_signature(global_context, &signature,
            ratchet_identity_key_pair_get_private(bob_identity_key_pair),
            signal_buffer_data(bob_signed_pre_key_public_serialized),
            signal_buffer_len(bob_signed_pre_key_public_serialized));
    ck_assert_int_eq(result, 0);

    signal_buffer *signature_omemo = 0;
    result = curve_calculate_signature(global_context, &signature_omemo,
            ratchet_identity_key_pair_get_private(bob_identity_key_pair),
            signal_buffer_data(bob_signed_pre_key_public_serialized_omemo),
            signal_buffer_len(bob_signed_pre_key_public_serialized_omemo));
    ck_assert_int_eq(result, 0);

    session_pre_key_bundle *bob_pre_key_bundle = 0;
    result = session_pre_key_bundle_create(&bob_pre_key_bundle,
            1, 1,
            bob_unsigned_pre_key_id,
            ec_key_pair_get_public(bob_unsigned_pre_key),
            bob_signed_pre_key_id, bob_signed_pre_key_public,
            signal_buffer_data(signature), signal_buffer_len(signature),
            ratchet_identity_key_pair_get_public(bob_identity_key_pair));
    ck_assert_int_eq(result, 0);

    session_signed_pre_key *signed_pre_key_record = 0;
    result = session_signed_pre_key_create(&signed_pre_key_record,
            bob_signed_pre_key_id, time(0), bob_signed_pre_key,
            signal_buffer_data(signature), signal_buffer_len(signature),
            signal_buffer_data(signature_omemo), signal_buffer_len(signature_omemo));
    ck_assert_int_eq(result, 0);

    result = signal_protocol_signed_pre_key_store_key(store, signed_pre_key_record);
    ck_assert_int_eq(result, 0);

    session_pre_key *pre_key_record = 0;
    result = session_pre_key_create(&pre_key_record, bob_unsigned_pre_key_id, bob_unsigned_pre_key);
    ck_assert_int_eq(result, 0);

    result = signal_protocol_pre_key_store_key(store, pre_key_record);
    ck_assert_int_eq(result, 0);

    SIGNAL_UNREF(pre_key_record);
    SIGNAL_UNREF(signed_pre_key_record);
    SIGNAL_UNREF(bob_identity_key_pair);
    SIGNAL_UNREF(bob_unsigned_pre_key);
    signal_buffer_free(bob_signed_pre_key_public_serialized);
    signal_buffer_free(bob_signed_pre_key_public_serialized_omemo);
    signal_buffer_free(signature);
    signal_buffer_free(signature_omemo);

    return bob_pre_key_bundle;
}

Suite *simultaneous_initiate_suite(void)
{
    Suite *suite = suite_create("simultaneous_initiate");

    TCase *tcase = tcase_create("case");
    tcase_add_checked_fixture(tcase, test_setup, test_teardown);
    tcase_add_test(tcase, test_basic_simultaneous_initiate);
    tcase_add_test(tcase, test_lost_simultaneous_initiate);
    tcase_add_test(tcase, test_simultaneous_initiate_lost_message);
    tcase_add_test(tcase, test_simultaneous_initiate_repeated_messages);
    tcase_add_test(tcase, test_repeated_simultaneous_initiate_repeated_messages);
    tcase_add_test(tcase, test_repeated_simultaneous_initiate_lost_message_repeated_messages);
    suite_add_tcase(suite, tcase);

    return suite;
}

int main(void)
{
    int number_failed;
    Suite *suite;
    SRunner *runner;

    suite = simultaneous_initiate_suite();
    runner = srunner_create(suite);

    srunner_run_all(runner, CK_VERBOSE);
    number_failed = srunner_ntests_failed(runner);
    srunner_free(runner);
    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
