#include "session_state.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "hkdf.h"
#include "curve.h"
#include "ratchet.h"
#include "LocalStorageProtocol.pb-c.h"
#include "signal_protocol_internal.h"

#include "utlist.h"

#define MAX_MESSAGE_KEYS 2000

typedef struct message_keys_node
{
    ratchet_message_keys message_key;
    struct message_keys_node *prev, *next;
} message_keys_node;

typedef struct session_state_sender_chain
{
    ec_key_pair *sender_ratchet_key_pair;
    ratchet_chain_key *chain_key;
} session_state_sender_chain;

typedef struct session_state_receiver_chain
{
    ec_public_key *sender_ratchet_key;
    ratchet_chain_key *chain_key;
    message_keys_node *message_keys_head;
    struct session_state_receiver_chain *prev, *next;
} session_state_receiver_chain;

typedef struct session_pending_key_exchange
{
    uint32_t sequence;
    ec_key_pair *local_base_key;
    ec_key_pair *local_ratchet_key;
    ratchet_identity_key_pair *local_identity_key;
} session_pending_key_exchange;

typedef struct session_pending_pre_key
{
    int has_pre_key_id;
    uint32_t pre_key_id;
    uint32_t signed_pre_key_id;
    ec_public_key *base_key;
} session_pending_pre_key;

struct session_state
{
    signal_type_base base;

    uint32_t session_version;
    ec_public_key *local_identity_public;
    ec_public_key *remote_identity_public;

    ratchet_root_key *root_key;
    uint32_t previous_counter;

    int has_sender_chain;
    session_state_sender_chain sender_chain;

    session_state_receiver_chain *receiver_chain_head;

    int has_pending_key_exchange;
    session_pending_key_exchange pending_key_exchange;

    int has_pending_pre_key;
    session_pending_pre_key pending_pre_key;

    uint32_t remote_registration_id;
    uint32_t local_registration_id;

    int needs_refresh;
    ec_public_key *alice_base_key;

    uint8_t local_is_alice;

    signal_context *global_context;
};

static int session_state_serialize_prepare_sender_chain(
        session_state_sender_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure);
static int session_state_serialize_prepare_receiver_chain(
        session_state_receiver_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure);
static void session_state_serialize_prepare_chain_free(
        Textsecure__SessionStructure__Chain *chain_structure);
static int session_state_serialize_prepare_chain_chain_key(
        ratchet_chain_key *chain_key,
        Textsecure__SessionStructure__Chain *chain_structure);
static int session_state_serialize_prepare_chain_message_keys_list(
        message_keys_node *message_keys_head,
        Textsecure__SessionStructure__Chain *chain_structure);
static int session_state_serialize_prepare_message_keys(
        ratchet_message_keys *message_key,
        Textsecure__SessionStructure__Chain__MessageKey *message_key_structure);
static void session_state_serialize_prepare_message_keys_free(
        Textsecure__SessionStructure__Chain__MessageKey *message_key_structure);
static int session_state_serialize_prepare_pending_key_exchange(
        session_pending_key_exchange *exchange,
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure);
static void session_state_serialize_prepare_pending_key_exchange_free(
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure);
static int session_state_serialize_prepare_pending_pre_key(
        session_pending_pre_key *pre_key,
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure);
static void session_state_serialize_prepare_pending_pre_key_free(
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure);

static int session_state_deserialize_protobuf_pending_key_exchange(
        session_pending_key_exchange *result_exchange,
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure,
        signal_context *global_context);
static int session_state_deserialize_protobuf_pending_pre_key(
        session_pending_pre_key *result_pre_key,
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure,
        signal_context *global_context);
static int session_state_deserialize_protobuf_sender_chain(
        uint32_t session_version,
        session_state_sender_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure,
        signal_context *global_context);
static int session_state_deserialize_protobuf_receiver_chain(
        uint32_t session_version,
        session_state_receiver_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure,
        signal_context *global_context);

static void session_state_free_sender_chain(session_state *state);
static void session_state_free_receiver_chain_node(session_state_receiver_chain *node);
static void session_state_free_receiver_chain(session_state *state);
static session_state_receiver_chain *session_state_find_receiver_chain(const session_state *state, const ec_public_key *sender_ephemeral);

int session_state_create(session_state **state, signal_context *global_context)
{
    session_state *result = malloc(sizeof(session_state));
    if(!result) {
        return SG_ERR_NOMEM;
    }
    memset(result, 0, sizeof(session_state));
    SIGNAL_INIT(result, session_state_destroy);
    result->session_version = 2;
    result->global_context = global_context;

    *state = result;
    return 0;
}

int session_state_serialize(signal_buffer **buffer, session_state *state)
{
    int result = 0;
    size_t result_size = 0;
    Textsecure__SessionStructure *state_structure = 0;
    signal_buffer *result_buf = 0;
    size_t len = 0;
    uint8_t *data = 0;

    state_structure = malloc(sizeof(Textsecure__SessionStructure));
    if(!state_structure) {
        result = SG_ERR_NOMEM;
        goto complete;
    }
    textsecure__session_structure__init(state_structure);

    result = session_state_serialize_prepare(state, state_structure);
    if(result < 0) {
        goto complete;
    }

    len = textsecure__session_structure__get_packed_size(state_structure);

    result_buf = signal_buffer_alloc(len);
    if(!result_buf) {
        result = SG_ERR_NOMEM;
        goto complete;
    }

    data = signal_buffer_data(result_buf);
    result_size = textsecure__session_structure__pack(state_structure, data);
    if(result_size != len) {
        signal_buffer_free(result_buf);
        result = SG_ERR_INVALID_PROTO_BUF;
        result_buf = 0;
        goto complete;
    }

complete:
    if(state_structure) {
        session_state_serialize_prepare_free(state_structure);
    }
    if(result >= 0) {
        *buffer = result_buf;
    }
    return result;
}

int session_state_deserialize(session_state **state, const uint8_t *data, size_t len, signal_context *global_context)
{
    int result = 0;
    session_state *result_state = 0;
    Textsecure__SessionStructure *session_structure = 0;

    session_structure = textsecure__session_structure__unpack(0, len, data);
    if(!session_structure) {
        result = SG_ERR_INVALID_PROTO_BUF;
        goto complete;
    }

    result = session_state_deserialize_protobuf(&result_state, session_structure, global_context);
    if(result < 0) {
        goto complete;
    }

complete:
    if(session_structure) {
        textsecure__session_structure__free_unpacked(session_structure, 0);
    }
    if(result_state) {
        if(result < 0) {
            SIGNAL_UNREF(result_state);
        }
        else {
            *state = result_state;
        }
    }
    return result;
}

int session_state_serialize_prepare(session_state *state, Textsecure__SessionStructure *session_structure)
{
    int result = 0;

    assert(state);
    assert(session_structure);

    session_structure->has_sessionversion = 1;
    session_structure->sessionversion = state->session_version;

    if(state->local_identity_public) {
        if (state->session_version < 4) {
            result = ec_public_key_serialize_protobuf(
                    &session_structure->localidentitypublic, state->local_identity_public);
        } else {
            result = ec_public_key_serialize_protobuf_ed(
                    &session_structure->localidentitypublic, state->local_identity_public);
        }
        if(result < 0) {
            goto complete;
        }
        session_structure->has_localidentitypublic = 1;
    }

    if(state->remote_identity_public) {
        if (state->session_version < 4) {
            result = ec_public_key_serialize_protobuf(
                    &session_structure->remoteidentitypublic, state->remote_identity_public);
        } else {
            result = ec_public_key_serialize_protobuf_ed(
                    &session_structure->remoteidentitypublic, state->remote_identity_public);
        }
        if(result < 0) {
            goto complete;
        }
        session_structure->has_remoteidentitypublic = 1;
    }

    if(state->root_key) {
        result = ratchet_root_key_get_key_protobuf(
                state->root_key, &session_structure->rootkey);
        if(result < 0) {
            goto complete;
        }
        session_structure->has_rootkey = 1;
    }

    session_structure->has_previouscounter = 1;
    session_structure->previouscounter = state->previous_counter;


    if(state->has_sender_chain) {
        session_structure->senderchain = malloc(sizeof(Textsecure__SessionStructure__Chain));
        if(!session_structure->senderchain) {
            result = SG_ERR_NOMEM;
            goto complete;
        }
        textsecure__session_structure__chain__init(session_structure->senderchain);
        result = session_state_serialize_prepare_sender_chain(
                &state->sender_chain, session_structure->senderchain);
        if(result < 0) {
            goto complete;
        }
    }

    if(state->receiver_chain_head) {
        size_t count, i = 0;
        session_state_receiver_chain *cur_node;
        DL_COUNT(state->receiver_chain_head, cur_node, count);

        if(count > SIZE_MAX / sizeof(Textsecure__SessionStructure__Chain *)) {
            result = SG_ERR_NOMEM;
            goto complete;
        }

        session_structure->receiverchains = malloc(sizeof(Textsecure__SessionStructure__Chain *) * count);
        if(!session_structure->receiverchains) {
            result = SG_ERR_NOMEM;
            goto complete;
        }

        DL_FOREACH(state->receiver_chain_head, cur_node) {
            session_structure->receiverchains[i] = malloc(sizeof(Textsecure__SessionStructure__Chain));
            if(!session_structure->receiverchains[i]) {
                result = SG_ERR_NOMEM;
                break;
            }
            textsecure__session_structure__chain__init(session_structure->receiverchains[i]);
            result = session_state_serialize_prepare_receiver_chain(cur_node, session_structure->receiverchains[i]);
            if(result < 0) {
                break;
            }
            i++;
        }
        session_structure->n_receiverchains = i;
        if(result < 0) {
            goto complete;
        }
    }

    if(state->has_pending_key_exchange) {
        session_structure->pendingkeyexchange = malloc(sizeof(Textsecure__SessionStructure__PendingKeyExchange));
        if(!session_structure->pendingkeyexchange) {
            result = SG_ERR_NOMEM;
            goto complete;
        }
        textsecure__session_structure__pending_key_exchange__init(session_structure->pendingkeyexchange);
        result = session_state_serialize_prepare_pending_key_exchange(
                &state->pending_key_exchange,
                session_structure->pendingkeyexchange);
        if(result < 0) {
            goto complete;
        }
    }

    if(state->has_pending_pre_key) {
        session_structure->pendingprekey = malloc(sizeof(Textsecure__SessionStructure__PendingPreKey));
        if(!session_structure->pendingprekey) {
            result = SG_ERR_NOMEM;
            goto complete;
        }
        textsecure__session_structure__pending_pre_key__init(session_structure->pendingprekey);
        result = session_state_serialize_prepare_pending_pre_key(
                &state->pending_pre_key,
                session_structure->pendingprekey);
        if(result < 0) {
            goto complete;
        }
    }

    session_structure->has_remoteregistrationid = 1;
    session_structure->remoteregistrationid = state->remote_registration_id;

    session_structure->has_localregistrationid = 1;
    session_structure->localregistrationid = state->local_registration_id;

    session_structure->has_needsrefresh = 1;
    session_structure->needsrefresh = state->needs_refresh;

    if(state->alice_base_key) {
        result = ec_public_key_serialize_protobuf(
                &session_structure->alicebasekey, state->alice_base_key);
        if(result < 0) {
            goto complete;
        }
        session_structure->has_alicebasekey = 1;
    }

    session_structure->has_localisalice = 1;
    session_structure->localisalice = state->local_is_alice;

complete:
    return result;
}

static int session_state_serialize_prepare_sender_chain(
        session_state_sender_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure)
{
    int result = 0;

    if(chain->sender_ratchet_key_pair) {
        ec_public_key *public_key = 0;
        ec_private_key *private_key = 0;

        public_key = ec_key_pair_get_public(chain->sender_ratchet_key_pair);
        result = ec_public_key_serialize_protobuf(&chain_structure->senderratchetkey, public_key);
        if(result < 0) {
            goto complete;
        }
        chain_structure->has_senderratchetkey = 1;

        private_key = ec_key_pair_get_private(chain->sender_ratchet_key_pair);
        result = ec_private_key_serialize_protobuf(&chain_structure->senderratchetkeyprivate, private_key);
        if(result < 0) {
            goto complete;
        }
        chain_structure->has_senderratchetkeyprivate = 1;
    }

    if(chain->chain_key) {
        result = session_state_serialize_prepare_chain_chain_key(chain->chain_key, chain_structure);
        if(result < 0) {
            goto complete;
        }
    }

complete:
    return result;
}

static int session_state_serialize_prepare_receiver_chain(
        session_state_receiver_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure)
{
    int result = 0;

    if(chain->sender_ratchet_key) {
        result = ec_public_key_serialize_protobuf(&chain_structure->senderratchetkey, chain->sender_ratchet_key);
        if(result < 0) {
            goto complete;
        }
        chain_structure->has_senderratchetkey = 1;
    }

    if(chain->chain_key) {
        result = session_state_serialize_prepare_chain_chain_key(chain->chain_key, chain_structure);
        if(result < 0) {
            goto complete;
        }
    }

    if(chain->message_keys_head) {
        result = session_state_serialize_prepare_chain_message_keys_list(chain->message_keys_head, chain_structure);
        if(result < 0) {
            goto complete;
        }
    }

complete:
    return result;
}

static int session_state_serialize_prepare_chain_chain_key(
        ratchet_chain_key *chain_key,
        Textsecure__SessionStructure__Chain *chain_structure)
{
    int result = 0;
    chain_structure->chainkey = malloc(sizeof(Textsecure__SessionStructure__Chain__ChainKey));
    if(!chain_structure->chainkey) {
        result = SG_ERR_NOMEM;
        goto complete;
    }
    textsecure__session_structure__chain__chain_key__init(chain_structure->chainkey);

    chain_structure->chainkey->has_index = 1;
    chain_structure->chainkey->index = ratchet_chain_key_get_index(chain_key);

    result = ratchet_chain_key_get_key_protobuf(chain_key, &chain_structure->chainkey->key);
    if(result < 0) {
        goto complete;
    }
    chain_structure->chainkey->has_key = 1;

complete:
    return result;
}

static int session_state_serialize_prepare_chain_message_keys_list(
        message_keys_node *message_keys_head,
        Textsecure__SessionStructure__Chain *chain_structure)
{
    int result = 0;
    size_t count, i = 0;
    message_keys_node *cur_node;
    DL_COUNT(message_keys_head, cur_node, count);

    if(count == 0) {
        goto complete;
    }

    if(count > SIZE_MAX / sizeof(Textsecure__SessionStructure__Chain__MessageKey *)) {
        result = SG_ERR_NOMEM;
        goto complete;
    }

    chain_structure->messagekeys = malloc(sizeof(Textsecure__SessionStructure__Chain__MessageKey *) * count);
    if(!chain_structure->messagekeys) {
        result = SG_ERR_NOMEM;
        goto complete;
    }

    DL_FOREACH(message_keys_head, cur_node) {
        chain_structure->messagekeys[i] = malloc(sizeof(Textsecure__SessionStructure__Chain__MessageKey));
        if(!chain_structure->messagekeys[i]) {
            result = SG_ERR_NOMEM;
            break;
        }
        textsecure__session_structure__chain__message_key__init(chain_structure->messagekeys[i]);

        result = session_state_serialize_prepare_message_keys(&cur_node->message_key, chain_structure->messagekeys[i]);
        if(result < 0) {
            break;
        }
        i++;
    }
    chain_structure->n_messagekeys = i;
    if(result < 0) {
        goto complete;
    }

complete:
    return result;
}

static int session_state_serialize_prepare_message_keys(
        ratchet_message_keys *message_key,
        Textsecure__SessionStructure__Chain__MessageKey *message_key_structure)
{
    int result = 0;

    message_key_structure->has_index = 1;
    message_key_structure->index = message_key->counter;

    message_key_structure->cipherkey.data = malloc(sizeof(message_key->cipher_key));
    if(!message_key_structure->cipherkey.data) {
        result = SG_ERR_NOMEM;
        goto complete;
    }
    memcpy(message_key_structure->cipherkey.data, message_key->cipher_key, sizeof(message_key->cipher_key));
    message_key_structure->cipherkey.len = sizeof(message_key->cipher_key);
    message_key_structure->has_cipherkey = 1;

    message_key_structure->mackey.data = malloc(sizeof(message_key->mac_key));
    if(!message_key_structure->mackey.data) {
        result = SG_ERR_NOMEM;
        goto complete;
    }
    memcpy(message_key_structure->mackey.data, message_key->mac_key, sizeof(message_key->mac_key));
    message_key_structure->mackey.len = sizeof(message_key->mac_key);
    message_key_structure->has_mackey = 1;

    message_key_structure->iv.data = malloc(sizeof(message_key->iv));
    if(!message_key_structure->iv.data) {
        result = SG_ERR_NOMEM;
        goto complete;
    }
    memcpy(message_key_structure->iv.data, message_key->iv, sizeof(message_key->iv));
    message_key_structure->iv.len = sizeof(message_key->iv);
    message_key_structure->has_iv = 1;

complete:
    return result;
}

static void session_state_serialize_prepare_message_keys_free(
        Textsecure__SessionStructure__Chain__MessageKey *message_key_structure)
{
    if(message_key_structure->has_cipherkey) {
        free(message_key_structure->cipherkey.data);
    }
    if(message_key_structure->has_mackey) {
        free(message_key_structure->mackey.data);
    }
    if(message_key_structure->has_iv) {
        free(message_key_structure->iv.data);
    }
    free(message_key_structure);
}

static int session_state_serialize_prepare_pending_key_exchange(
        session_pending_key_exchange *exchange,
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure)
{
    int result = 0;

    exchange_structure->has_sequence = 1;
    exchange_structure->sequence = exchange->sequence;

    if(exchange->local_base_key) {
        ec_public_key *public_key = 0;
        ec_private_key *private_key = 0;

        public_key = ec_key_pair_get_public(exchange->local_base_key);
        result = ec_public_key_serialize_protobuf(&exchange_structure->localbasekey, public_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localbasekey = 1;

        private_key = ec_key_pair_get_private(exchange->local_base_key);
        result = ec_private_key_serialize_protobuf(&exchange_structure->localbasekeyprivate, private_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localbasekeyprivate = 1;
    }

    if(exchange->local_ratchet_key) {
        ec_public_key *public_key;
        ec_private_key *private_key;

        public_key = ec_key_pair_get_public(exchange->local_ratchet_key);
        result = ec_public_key_serialize_protobuf(&exchange_structure->localratchetkey, public_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localratchetkey = 1;

        private_key = ec_key_pair_get_private(exchange->local_ratchet_key);
        result = ec_private_key_serialize_protobuf(&exchange_structure->localratchetkeyprivate, private_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localratchetkeyprivate = 1;
    }

    if(exchange->local_identity_key) {
        ec_public_key *public_key;
        ec_private_key *private_key;

        public_key = ratchet_identity_key_pair_get_public(exchange->local_identity_key);
        result = ec_public_key_serialize_protobuf(&exchange_structure->localidentitykey, public_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localidentitykey = 1;

        private_key = ratchet_identity_key_pair_get_private(exchange->local_identity_key);
        result = ec_private_key_serialize_protobuf(&exchange_structure->localidentitykeyprivate, private_key);
        if(result < 0) {
            goto complete;
        }
        exchange_structure->has_localidentitykeyprivate = 1;
    }

complete:
    return result;
}

static int session_state_serialize_prepare_pending_pre_key(
        session_pending_pre_key *pre_key,
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure)
{
    int result = 0;

    if(pre_key->has_pre_key_id) {
        pre_key_structure->has_prekeyid = 1;
        pre_key_structure->prekeyid = pre_key->pre_key_id;
    }

    pre_key_structure->has_signedprekeyid = 1;
    pre_key_structure->signedprekeyid = (int32_t)pre_key->signed_pre_key_id;

    if(pre_key->base_key) {
        result = ec_public_key_serialize_protobuf(&pre_key_structure->basekey, pre_key->base_key);
        if(result < 0) {
            goto complete;
        }
        pre_key_structure->has_basekey = 1;
    }

complete:
    return result;
}

void session_state_serialize_prepare_free(Textsecure__SessionStructure *session_structure)
{
    assert(session_structure);

    if(session_structure->has_localidentitypublic) {
        free(session_structure->localidentitypublic.data);
    }

    if(session_structure->has_remoteidentitypublic) {
        free(session_structure->remoteidentitypublic.data);
    }

    if(session_structure->has_rootkey) {
        free(session_structure->rootkey.data);
    }

    if(session_structure->senderchain) {
        session_state_serialize_prepare_chain_free(session_structure->senderchain);
    }

    if(session_structure->receiverchains) {
        unsigned int i;
        for(i = 0; i < session_structure->n_receiverchains; i++) {
            if(session_structure->receiverchains[i]) {
                session_state_serialize_prepare_chain_free(session_structure->receiverchains[i]);
            }
        }
        free(session_structure->receiverchains);
    }

    if(session_structure->pendingkeyexchange) {
        session_state_serialize_prepare_pending_key_exchange_free(session_structure->pendingkeyexchange);
    }

    if(session_structure->pendingprekey) {
        session_state_serialize_prepare_pending_pre_key_free(session_structure->pendingprekey);
    }

    if(session_structure->has_alicebasekey) {
        free(session_structure->alicebasekey.data);
    }

    free(session_structure);
}

static void session_state_serialize_prepare_chain_free(
        Textsecure__SessionStructure__Chain *chain_structure)
{
    if(chain_structure->has_senderratchetkey) {
        free(chain_structure->senderratchetkey.data);
    }
    if(chain_structure->has_senderratchetkeyprivate) {
        free(chain_structure->senderratchetkeyprivate.data);
    }
    if(chain_structure->chainkey) {
        if(chain_structure->chainkey->has_key) {
            free(chain_structure->chainkey->key.data);
        }
        free(chain_structure->chainkey);
    }
    if(chain_structure->messagekeys) {
        unsigned int i;
        for(i = 0; i < chain_structure->n_messagekeys; i++) {
            if(chain_structure->messagekeys[i]) {
                session_state_serialize_prepare_message_keys_free(chain_structure->messagekeys[i]);
            }
        }
        free(chain_structure->messagekeys);
    }
    free(chain_structure);
}

static void session_state_serialize_prepare_pending_key_exchange_free(
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure)
{
    if(exchange_structure->has_localbasekey) {
        free(exchange_structure->localbasekey.data);
    }
    if(exchange_structure->has_localbasekeyprivate) {
        free(exchange_structure->localbasekeyprivate.data);
    }
    if(exchange_structure->has_localratchetkey) {
        free(exchange_structure->localratchetkey.data);
    }
    if(exchange_structure->has_localratchetkeyprivate) {
        free(exchange_structure->localratchetkeyprivate.data);
    }
    if(exchange_structure->has_localidentitykey) {
        free(exchange_structure->localidentitykey.data);
    }
    if(exchange_structure->has_localidentitykeyprivate) {
        free(exchange_structure->localidentitykeyprivate.data);
    }
    free(exchange_structure);
}

static void session_state_serialize_prepare_pending_pre_key_free(
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure)
{
    if(pre_key_structure->has_basekey) {
        free(pre_key_structure->basekey.data);
    }

    free(pre_key_structure);
}

int session_state_deserialize_protobuf(session_state **state, Textsecure__SessionStructure *session_structure, signal_context *global_context)
{
    int result = 0;
    session_state *result_state  = 0;

    result = session_state_create(&result_state, global_context);
    if(result < 0) {
        goto complete;
    }

    if(session_structure->has_sessionversion) {
        result_state->session_version = session_structure->sessionversion;
    }

    if(session_structure->has_localidentitypublic) {
        result = curve_decode_point(
                &result_state->local_identity_public,
                session_structure->localidentitypublic.data,
                session_structure->localidentitypublic.len,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    if(session_structure->has_remoteidentitypublic) {
        result = curve_decode_point(
                &result_state->remote_identity_public,
                session_structure->remoteidentitypublic.data,
                session_structure->remoteidentitypublic.len,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    if(session_structure->has_rootkey) {
        hkdf_context *kdf = 0;
        result = hkdf_create(&kdf, (int)result_state->session_version, global_context);
        if(result < 0) {
            goto complete;
        }

        result = ratchet_root_key_create(
                &result_state->root_key, kdf,
                session_structure->rootkey.data,
                session_structure->rootkey.len, global_context);
        SIGNAL_UNREF(kdf);
        if(result < 0) {
            goto complete;
        }
    }

    if(session_structure->has_previouscounter) {
        result_state->previous_counter = session_structure->previouscounter;
    }

    if(session_structure->senderchain) {
        result = session_state_deserialize_protobuf_sender_chain(
                result_state->session_version,
                &result_state->sender_chain, session_structure->senderchain,
                global_context);
        if(result < 0) {
            goto complete;
        }
        result_state->has_sender_chain = 1;
    }

    if(session_structure->n_receiverchains > 0) {
        unsigned int i;
        for(i = 0; i < session_structure->n_receiverchains; i++) {
            session_state_receiver_chain *node = malloc(sizeof(session_state_receiver_chain));
            if(!node) {
                result = SG_ERR_NOMEM;
                goto complete;
            }
            memset(node, 0, sizeof(session_state_receiver_chain));

            result = session_state_deserialize_protobuf_receiver_chain(
                    result_state->session_version,
                    node, session_structure->receiverchains[i],
                    global_context);
            if(result < 0) {
                free(node);
                goto complete;
            }

            DL_APPEND(result_state->receiver_chain_head, node);
        }
    }

    if(session_structure->pendingkeyexchange) {
        result = session_state_deserialize_protobuf_pending_key_exchange(
                &result_state->pending_key_exchange,
                session_structure->pendingkeyexchange, global_context);
        if(result < 0) {
            goto complete;
        }
        result_state->has_pending_key_exchange = 1;
    }

    if(session_structure->pendingprekey) {
        result = session_state_deserialize_protobuf_pending_pre_key(
                &result_state->pending_pre_key,
                session_structure->pendingprekey, global_context);
        if(result < 0) {
            goto complete;
        }
        result_state->has_pending_pre_key = 1;
    }

    if(session_structure->has_remoteregistrationid) {
        result_state->remote_registration_id = session_structure->remoteregistrationid;
    }

    if(session_structure->has_localregistrationid) {
        result_state->local_registration_id = session_structure->localregistrationid;
    }

    if(session_structure->has_needsrefresh) {
        result_state->needs_refresh = session_structure->needsrefresh;
    }

    if(session_structure->has_alicebasekey) {
        result = curve_decode_point(
                &result_state->alice_base_key,
                session_structure->alicebasekey.data,
                session_structure->alicebasekey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    if (session_structure->has_localisalice) {
        result_state->local_is_alice = session_structure->localisalice;
    } else {
        result_state->local_is_alice = 1; // We are alice by default
    }

complete:
    if(result >= 0) {
        *state = result_state;
    }
    else {
        if(result_state) {
            SIGNAL_UNREF(result_state);
        }
    }
    return result;
}

static int session_state_deserialize_protobuf_pending_key_exchange(
        session_pending_key_exchange *result_exchange,
        Textsecure__SessionStructure__PendingKeyExchange *exchange_structure,
        signal_context *global_context)
{
    int result = 0;

    ec_key_pair *local_base_key = 0;
    ec_public_key *local_base_key_public = 0;
    ec_private_key *local_base_key_private = 0;

    ec_key_pair *local_ratchet_key = 0;
    ec_public_key *local_ratchet_key_public = 0;
    ec_private_key *local_ratchet_key_private = 0;

    ratchet_identity_key_pair *local_identity_key = 0;
    ec_public_key *local_identity_key_public = 0;
    ec_private_key *local_identity_key_private = 0;

    if(exchange_structure->has_localbasekey && exchange_structure->has_localbasekeyprivate) {
        result = curve_decode_point(&local_base_key_public,
                exchange_structure->localbasekey.data,
                exchange_structure->localbasekey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = curve_decode_private_point(&local_base_key_private,
                exchange_structure->localbasekeyprivate.data,
                exchange_structure->localbasekeyprivate.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = ec_key_pair_create(&local_base_key,
                local_base_key_public, local_base_key_private);
        if(result < 0) {
            goto complete;
        }
    }

    if(exchange_structure->has_localratchetkey && exchange_structure->has_localratchetkeyprivate) {
        result = curve_decode_point(&local_ratchet_key_public,
                exchange_structure->localratchetkey.data,
                exchange_structure->localratchetkey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = curve_decode_private_point(&local_ratchet_key_private,
                exchange_structure->localratchetkeyprivate.data,
                exchange_structure->localratchetkeyprivate.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = ec_key_pair_create(&local_ratchet_key,
                local_ratchet_key_public, local_ratchet_key_private);
        if(result < 0) {
            goto complete;
        }
    }

    if(exchange_structure->has_localidentitykey && exchange_structure->has_localidentitykeyprivate) {
        result = curve_decode_point(&local_identity_key_public,
                exchange_structure->localidentitykey.data,
                exchange_structure->localidentitykey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = curve_decode_private_point(&local_identity_key_private,
                exchange_structure->localidentitykeyprivate.data,
                exchange_structure->localidentitykeyprivate.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = ratchet_identity_key_pair_create(&local_identity_key,
                local_identity_key_public,
                local_identity_key_private);
        if(result < 0) {
            goto complete;
        }
    }

    result_exchange->sequence = exchange_structure->sequence;
    result_exchange->local_base_key = local_base_key;
    result_exchange->local_ratchet_key = local_ratchet_key;
    result_exchange->local_identity_key = local_identity_key;

complete:
    SIGNAL_UNREF(local_base_key_public);
    SIGNAL_UNREF(local_base_key_private);
    SIGNAL_UNREF(local_ratchet_key_public);
    SIGNAL_UNREF(local_ratchet_key_private);
    SIGNAL_UNREF(local_identity_key_public);
    SIGNAL_UNREF(local_identity_key_private);

    if(result < 0) {
        SIGNAL_UNREF(local_base_key);
        SIGNAL_UNREF(local_ratchet_key);
        SIGNAL_UNREF(local_identity_key);
    }

    return result;
}

static int session_state_deserialize_protobuf_pending_pre_key(
        session_pending_pre_key *result_pre_key,
        Textsecure__SessionStructure__PendingPreKey *pre_key_structure,
        signal_context *global_context)
{
    int result = 0;

    if(pre_key_structure->has_basekey) {
        ec_public_key *base_key = 0;
        result = curve_decode_point(&base_key,
                pre_key_structure->basekey.data,
                pre_key_structure->basekey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }
        result_pre_key->base_key = base_key;
    }

    if(pre_key_structure->has_prekeyid) {
        result_pre_key->has_pre_key_id = 1;
        result_pre_key->pre_key_id = pre_key_structure->prekeyid;
    }

    if(pre_key_structure->has_signedprekeyid) {
        result_pre_key->signed_pre_key_id = (uint32_t)pre_key_structure->signedprekeyid;
    }

complete:
    return result;
}

static int session_state_deserialize_protobuf_sender_chain(
        uint32_t session_version,
        session_state_sender_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure,
        signal_context *global_context)
{
    int result = 0;
    hkdf_context *kdf = 0;
    ec_key_pair *sender_ratchet_key_pair = 0;
    ec_public_key *sender_ratchet_key_public = 0;
    ec_private_key *sender_ratchet_key_private = 0;
    ratchet_chain_key *sender_chain_key = 0;

    if(chain_structure->has_senderratchetkey && chain_structure->has_senderratchetkeyprivate) {
        result = curve_decode_point(&sender_ratchet_key_public,
                chain_structure->senderratchetkey.data,
                chain_structure->senderratchetkey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = curve_decode_private_point(&sender_ratchet_key_private,
                chain_structure->senderratchetkeyprivate.data,
                chain_structure->senderratchetkeyprivate.len,
                global_context);
        if(result < 0) {
            goto complete;
        }

        result = ec_key_pair_create(&sender_ratchet_key_pair,
                sender_ratchet_key_public, sender_ratchet_key_private);
        if(result < 0) {
            goto complete;
        }
    }

    if(chain_structure->chainkey && chain_structure->chainkey->has_key && chain_structure->chainkey->has_index) {
        result = hkdf_create(&kdf, (int)session_version, global_context);
        if(result < 0) {
            goto complete;
        }

        result = ratchet_chain_key_create(
                &sender_chain_key, kdf,
                chain_structure->chainkey->key.data,
                chain_structure->chainkey->key.len,
                chain_structure->chainkey->index,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    chain->sender_ratchet_key_pair = sender_ratchet_key_pair;
    chain->chain_key = sender_chain_key;

complete:
    SIGNAL_UNREF(kdf);
    SIGNAL_UNREF(sender_ratchet_key_public);
    SIGNAL_UNREF(sender_ratchet_key_private);
    if(result < 0) {
        SIGNAL_UNREF(sender_ratchet_key_pair);
        SIGNAL_UNREF(sender_chain_key);
    }
    return result;
}

static int session_state_deserialize_protobuf_receiver_chain(
        uint32_t session_version,
        session_state_receiver_chain *chain,
        Textsecure__SessionStructure__Chain *chain_structure,
        signal_context *global_context)
{
    int result = 0;

    hkdf_context *kdf = 0;
    ec_public_key *sender_ratchet_key = 0;
    ratchet_chain_key *chain_key = 0;
    message_keys_node *message_keys_head = 0;

    if(chain_structure->has_senderratchetkey) {
        result = curve_decode_point(&sender_ratchet_key,
                chain_structure->senderratchetkey.data,
                chain_structure->senderratchetkey.len,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    if(chain_structure->chainkey && chain_structure->chainkey->has_key && chain_structure->chainkey->has_index) {
        result = hkdf_create(&kdf, (int)session_version, global_context);
        if(result < 0) {
            goto complete;
        }

        result = ratchet_chain_key_create(
                &chain_key, kdf,
                chain_structure->chainkey->key.data,
                chain_structure->chainkey->key.len,
                chain_structure->chainkey->index,
                global_context);
        if(result < 0) {
            goto complete;
        }
    }

    if(chain_structure->n_messagekeys > 0) {
        unsigned int i;
        for(i = 0; i < chain_structure->n_messagekeys; i++) {
            Textsecure__SessionStructure__Chain__MessageKey *key_structure =
                    chain_structure->messagekeys[i];

            message_keys_node *node = malloc(sizeof(message_keys_node));
            if(!node) {
                result = SG_ERR_NOMEM;
                goto complete;
            }
            memset(node, 0, sizeof(message_keys_node));

            if(key_structure->has_index) {
                node->message_key.counter = key_structure->index;
            }
            if(key_structure->has_cipherkey && key_structure->cipherkey.len == sizeof(node->message_key.cipher_key)) {
                memcpy(node->message_key.cipher_key, key_structure->cipherkey.data, key_structure->cipherkey.len);
            }
            if(key_structure->has_mackey && key_structure->mackey.len == sizeof(node->message_key.mac_key)) {
                memcpy(node->message_key.mac_key, key_structure->mackey.data, key_structure->mackey.len);
            }
            if(key_structure->has_iv && key_structure->iv.len == sizeof(node->message_key.iv)) {
                memcpy(node->message_key.iv, key_structure->iv.data, key_structure->iv.len);
            }

            DL_APPEND(message_keys_head, node);
        }
    }

    chain->sender_ratchet_key = sender_ratchet_key;
    chain->chain_key = chain_key;
    chain->message_keys_head = message_keys_head;

complete:
    SIGNAL_UNREF(kdf);
    if(result < 0) {
        SIGNAL_UNREF(sender_ratchet_key);
        SIGNAL_UNREF(chain_key);
        if(message_keys_head) {
            message_keys_node *cur_node;
            message_keys_node *tmp_node;
            DL_FOREACH_SAFE(message_keys_head, cur_node, tmp_node) {
                DL_DELETE(message_keys_head, cur_node);
                signal_explicit_bzero(&cur_node->message_key, sizeof(ratchet_message_keys));
                free(cur_node);
            }
        }
    }
    return result;
}

int session_state_copy(session_state **state, session_state *other_state, signal_context *global_context)
{
    int result = 0;
    signal_buffer *buffer = 0;
    size_t len = 0;
    uint8_t *data = 0;

    assert(other_state);
    assert(global_context);

    result = session_state_serialize(&buffer, other_state);
    if(result < 0) {
        goto complete;
    }

    data = signal_buffer_data(buffer);
    len = signal_buffer_len(buffer);

    result = session_state_deserialize(state, data, len, global_context);
    if(result < 0) {
        goto complete;
    }

complete:
    if(buffer) {
        signal_buffer_free(buffer);
    }
    return result;
}

void session_state_set_session_version(session_state *state, uint32_t version)
{
    assert(state);
    state->session_version = version;
}

uint32_t session_state_get_session_version(const session_state *state)
{
    assert(state);
    return state->session_version;
}

const struct ratchet_kdf_infos* session_state_get_kdf_infos(const session_state *state)
{
    assert(state);
    return ratchet_kdf_infos_for_session_version(state->session_version);
}

void session_state_set_local_identity_key(session_state *state, ec_public_key *identity_key)
{
    assert(state);
    assert(identity_key);
    if(state->local_identity_public) {
        SIGNAL_UNREF(state->local_identity_public);
    }
    SIGNAL_REF(identity_key);
    state->local_identity_public = identity_key;
}

ec_public_key *session_state_get_local_identity_key(const session_state *state)
{
    assert(state);
    return state->local_identity_public;
}

void session_state_set_remote_identity_key(session_state *state, ec_public_key *identity_key)
{
    assert(state);
    assert(identity_key);
    if(state->remote_identity_public) {
        SIGNAL_UNREF(state->remote_identity_public);
    }
    SIGNAL_REF(identity_key);
    state->remote_identity_public = identity_key;
}

ec_public_key *session_state_get_remote_identity_key(const session_state *state)
{
    assert(state);
    return state->remote_identity_public;
}

void session_state_set_root_key(session_state *state, ratchet_root_key *root_key)
{
    assert(state);
    assert(root_key);
    if(state->root_key) {
        SIGNAL_UNREF(state->root_key);
    }
    SIGNAL_REF(root_key);
    state->root_key = root_key;
}

ratchet_root_key *session_state_get_root_key(const session_state *state)
{
    assert(state);
    return state->root_key;
}

void session_state_set_previous_counter(session_state *state, uint32_t counter)
{
    assert(state);
    state->previous_counter = counter;
}

uint32_t session_state_get_previous_counter(const session_state *state)
{
    assert(state);
    return state->previous_counter;
}

void session_state_set_sender_chain(session_state *state, ec_key_pair *sender_ratchet_key_pair, ratchet_chain_key *chain_key)
{
    assert(state);
    assert(sender_ratchet_key_pair);
    assert(chain_key);

    state->has_sender_chain = 1;

    if(state->sender_chain.sender_ratchet_key_pair) {
        SIGNAL_UNREF(state->sender_chain.sender_ratchet_key_pair);
    }
    SIGNAL_REF(sender_ratchet_key_pair);
    state->sender_chain.sender_ratchet_key_pair = sender_ratchet_key_pair;

    if(state->sender_chain.chain_key) {
        SIGNAL_UNREF(state->sender_chain.chain_key);
    }
    SIGNAL_REF(chain_key);
    state->sender_chain.chain_key = chain_key;
}

ec_public_key *session_state_get_sender_ratchet_key(const session_state *state)
{
    assert(state);
    if(state->sender_chain.sender_ratchet_key_pair) {
        return ec_key_pair_get_public(state->sender_chain.sender_ratchet_key_pair);
    }
    else {
        return 0;
    }
}

ec_key_pair *session_state_get_sender_ratchet_key_pair(const session_state *state)
{
    assert(state);
    return state->sender_chain.sender_ratchet_key_pair;
}

ratchet_chain_key *session_state_get_sender_chain_key(const session_state *state)
{
    assert(state);
    return state->sender_chain.chain_key;
}

int session_state_set_sender_chain_key(session_state *state, ratchet_chain_key *chain_key)
{
    assert(state);
    if(state->has_sender_chain) {
        if(state->sender_chain.chain_key) {
            SIGNAL_UNREF(state->sender_chain.chain_key);
        }
        SIGNAL_REF(chain_key);
        state->sender_chain.chain_key = chain_key;
        return 0;
    }
    else {
        return SG_ERR_UNKNOWN;
    }
}

int session_state_has_sender_chain(const session_state *state)
{
    assert(state);
    return state->has_sender_chain;
}

int session_state_has_message_keys(session_state *state, ec_public_key *sender_ephemeral, uint32_t counter)
{
    session_state_receiver_chain *chain = 0;
    message_keys_node *cur_node = 0;

    assert(state);
    assert(sender_ephemeral);

    chain = session_state_find_receiver_chain(state, sender_ephemeral);
    if(!chain) {
        return 0;
    }

    DL_FOREACH(chain->message_keys_head, cur_node) {
        if(cur_node->message_key.counter == counter) {
            return 1;
        }
    }

    return 0;
}

int session_state_remove_message_keys(session_state *state,
        ratchet_message_keys *message_keys_result,
        ec_public_key *sender_ephemeral, uint32_t counter)
{
    session_state_receiver_chain *chain = 0;
    message_keys_node *cur_node = 0;
    message_keys_node *tmp_node = 0;

    assert(state);
    assert(message_keys_result);
    assert(sender_ephemeral);

    chain = session_state_find_receiver_chain(state, sender_ephemeral);
    if(!chain) {
        return 0;
    }

    DL_FOREACH_SAFE(chain->message_keys_head, cur_node, tmp_node) {
        if(cur_node->message_key.counter == counter) {
            memcpy(message_keys_result, &(cur_node->message_key), sizeof(ratchet_message_keys));
            DL_DELETE(chain->message_keys_head, cur_node);
            signal_explicit_bzero(&cur_node->message_key, sizeof(ratchet_message_keys));
            free(cur_node);
            return 1;
        }
    }

    return 0;
}

int session_state_set_message_keys(session_state *state,
        ec_public_key *sender_ephemeral, ratchet_message_keys *message_keys)
{
    session_state_receiver_chain *chain = 0;
    message_keys_node *node = 0;
    int count;

    assert(state);
    assert(sender_ephemeral);
    assert(message_keys);

    chain = session_state_find_receiver_chain(state, sender_ephemeral);
    if(!chain) {
        return 0;
    }

    node = malloc(sizeof(message_keys_node));
    if(!node) {
        return SG_ERR_NOMEM;
    }
    memcpy(&(node->message_key), message_keys, sizeof(ratchet_message_keys));
    node->prev = 0;
    node->next = 0;

    DL_APPEND(chain->message_keys_head, node);

    DL_COUNT(chain->message_keys_head, node, count);
    while(count > MAX_MESSAGE_KEYS) {
        node = chain->message_keys_head;
        DL_DELETE(chain->message_keys_head, node);
        signal_explicit_bzero(&node->message_key, sizeof(ratchet_message_keys));
        free(node);
        --count;
    }

    return 0;
}

int session_state_add_receiver_chain(session_state *state, ec_public_key *sender_ratchet_key, ratchet_chain_key *chain_key)
{
    session_state_receiver_chain *node;
    int count;

    assert(state);
    assert(sender_ratchet_key);
    assert(chain_key);

    node = malloc(sizeof(session_state_receiver_chain));
    if(!node) {
        return SG_ERR_NOMEM;
    }
    memset(node, 0, sizeof(session_state_receiver_chain));

    SIGNAL_REF(sender_ratchet_key);
    node->sender_ratchet_key = sender_ratchet_key;
    SIGNAL_REF(chain_key);
    node->chain_key = chain_key;

    DL_APPEND(state->receiver_chain_head, node);

    DL_COUNT(state->receiver_chain_head, node, count);
    while(count > 5) {
        node = state->receiver_chain_head;
        DL_DELETE(state->receiver_chain_head, node);
        session_state_free_receiver_chain_node(node);
        --count;
    }

    return 0;
}

int session_state_set_receiver_chain_key(session_state *state, ec_public_key *sender_ephemeral, ratchet_chain_key *chain_key)
{
    int result = 0;
    session_state_receiver_chain *node;

    assert(state);
    assert(sender_ephemeral);
    assert(chain_key);

    node = session_state_find_receiver_chain(state, sender_ephemeral);
    if(!node) {
        signal_log(state->global_context, SG_LOG_WARNING, "Couldn't find receiver chain to set chain key on");
        result = SG_ERR_UNKNOWN;
        goto complete;
    }

    SIGNAL_UNREF(node->chain_key);
    SIGNAL_REF(chain_key);
    node->chain_key = chain_key;

complete:
    return result;
}

static session_state_receiver_chain *session_state_find_receiver_chain(const session_state *state, const ec_public_key *sender_ephemeral)
{
    session_state_receiver_chain *result = 0;

    session_state_receiver_chain *cur_node;
    DL_FOREACH(state->receiver_chain_head, cur_node) {
        if(ec_public_key_compare(cur_node->sender_ratchet_key, sender_ephemeral) == 0) {
            result = cur_node;
            break;
        }
    }

    return result;
}

ratchet_chain_key *session_state_get_receiver_chain_key(session_state *state, ec_public_key *sender_ephemeral)
{
    ratchet_chain_key *result = 0;
    session_state_receiver_chain *node = session_state_find_receiver_chain(state, sender_ephemeral);

    if(node) {
        result = node->chain_key;
    }

    return result;
}

void session_state_set_pending_key_exchange(session_state *state,
        uint32_t sequence,
        ec_key_pair *our_base_key, ec_key_pair *our_ratchet_key,
        ratchet_identity_key_pair *our_identity_key)
{
    assert(state);
    assert(our_base_key);
    assert(our_ratchet_key);
    assert(our_identity_key);

    if(state->pending_key_exchange.local_base_key) {
        SIGNAL_UNREF(state->pending_key_exchange.local_base_key);
        state->pending_key_exchange.local_base_key = 0;
    }
    if(state->pending_key_exchange.local_ratchet_key) {
        SIGNAL_UNREF(state->pending_key_exchange.local_ratchet_key);
        state->pending_key_exchange.local_ratchet_key = 0;
    }
    if(state->pending_key_exchange.local_identity_key) {
        SIGNAL_UNREF(state->pending_key_exchange.local_identity_key);
        state->pending_key_exchange.local_identity_key = 0;
    }

    SIGNAL_REF(our_base_key);
    SIGNAL_REF(our_ratchet_key);
    SIGNAL_REF(our_identity_key);

    state->has_pending_key_exchange = 1;
    state->pending_key_exchange.sequence = sequence;
    state->pending_key_exchange.local_base_key = our_base_key;
    state->pending_key_exchange.local_ratchet_key = our_ratchet_key;
    state->pending_key_exchange.local_identity_key = our_identity_key;
}

uint32_t session_state_get_pending_key_exchange_sequence(session_state *state)
{
    assert(state);
    if(state->has_pending_key_exchange) {
        return state->pending_key_exchange.sequence;
    }
    else {
        return 0;
    }
}

ec_key_pair *session_state_get_pending_key_exchange_base_key(const session_state *state)
{
    assert(state);
    if(state->has_pending_key_exchange) {
        return state->pending_key_exchange.local_base_key;
    }
    else {
        return 0;
    }
}

ec_key_pair *session_state_get_pending_key_exchange_ratchet_key(const session_state *state)
{
    assert(state);
    if(state->has_pending_key_exchange) {
        return state->pending_key_exchange.local_ratchet_key;
    }
    else {
        return 0;
    }
}

ratchet_identity_key_pair *session_state_get_pending_key_exchange_identity_key(const session_state *state)
{
    assert(state);
    if(state->has_pending_key_exchange) {
        return state->pending_key_exchange.local_identity_key;
    }
    else {
        return 0;
    }
}

int session_state_has_pending_key_exchange(const session_state *state)
{
    assert(state);
    return state->has_pending_key_exchange;
}

void session_state_set_unacknowledged_pre_key_message(session_state *state,
        const uint32_t *pre_key_id, uint32_t signed_pre_key_id, ec_public_key *base_key)
{
    assert(state);
    assert(base_key);

    if(state->pending_pre_key.base_key) {
        SIGNAL_UNREF(state->pending_pre_key.base_key);
        state->pending_pre_key.base_key = 0;
    }

    SIGNAL_REF(base_key);

    state->has_pending_pre_key = 1;
    if(pre_key_id) {
        state->pending_pre_key.has_pre_key_id = 1;
        state->pending_pre_key.pre_key_id = *pre_key_id;
    }
    else {
        state->pending_pre_key.has_pre_key_id = 0;
        state->pending_pre_key.pre_key_id = 0;
    }
    state->pending_pre_key.signed_pre_key_id = signed_pre_key_id;
    state->pending_pre_key.base_key = base_key;
}

int session_state_unacknowledged_pre_key_message_has_pre_key_id(const session_state *state)
{
    assert(state);
    return state->pending_pre_key.has_pre_key_id;
}

uint32_t session_state_unacknowledged_pre_key_message_get_pre_key_id(const session_state *state)
{
    assert(state);
    assert(state->pending_pre_key.has_pre_key_id);
    return state->pending_pre_key.pre_key_id;
}

uint32_t session_state_unacknowledged_pre_key_message_get_signed_pre_key_id(const session_state *state)
{
    assert(state);
    return state->pending_pre_key.signed_pre_key_id;
}

ec_public_key *session_state_unacknowledged_pre_key_message_get_base_key(const session_state *state)
{
    assert(state);
    return state->pending_pre_key.base_key;
}

int session_state_has_unacknowledged_pre_key_message(const session_state *state)
{
    assert(state);
    return state->has_pending_pre_key;
}

void session_state_clear_unacknowledged_pre_key_message(session_state *state)
{
    assert(state);
    if(state->pending_pre_key.base_key) {
        SIGNAL_UNREF(state->pending_pre_key.base_key);
    }
    memset(&state->pending_pre_key, 0, sizeof(state->pending_pre_key));
    state->has_pending_pre_key = 0;
}

void session_state_set_remote_registration_id(session_state *state, uint32_t id)
{
    assert(state);
    state->remote_registration_id = id;
}

uint32_t session_state_get_remote_registration_id(const session_state *state)
{
    assert(state);
    return state->remote_registration_id;
}

void session_state_set_local_registration_id(session_state *state, uint32_t id)
{
    assert(state);
    state->local_registration_id = id;
}

uint32_t session_state_get_local_registration_id(const session_state *state)
{
    assert(state);
    return state->local_registration_id;
}

void session_state_set_needs_refresh(session_state *state, int value)
{
    assert(state);
    assert(value == 0 || value == 1);
    state->needs_refresh = value;
}

int session_state_get_needs_refresh(const session_state *state)
{
    assert(state);
    return state->needs_refresh;
}

void session_state_set_alice_base_key(session_state *state, ec_public_key *key)
{
    assert(state);
    assert(key);

    if(state->alice_base_key) {
        SIGNAL_UNREF(state->alice_base_key);
    }
    SIGNAL_REF(key);
    state->alice_base_key = key;
}

ec_public_key *session_state_get_alice_base_key(const session_state *state)
{
    assert(state);
    return state->alice_base_key;
}

void session_state_set_local_is_alice(session_state *state, uint8_t local_is_alice)
{
    assert(state);
    state->local_is_alice = local_is_alice;
}

uint8_t session_state_get_local_is_alice(const session_state *state)
{
    assert(state);
    return state->local_is_alice;
}

static void session_state_free_sender_chain(session_state *state)
{
    if(state->sender_chain.sender_ratchet_key_pair) {
        SIGNAL_UNREF(state->sender_chain.sender_ratchet_key_pair);
        state->sender_chain.sender_ratchet_key_pair = 0;
    }
    if(state->sender_chain.chain_key) {
        SIGNAL_UNREF(state->sender_chain.chain_key);
        state->sender_chain.chain_key = 0;
    }
}

static void session_state_free_receiver_chain_node(session_state_receiver_chain *node)
{
    if(node->sender_ratchet_key) {
        SIGNAL_UNREF(node->sender_ratchet_key);
    }
    if(node->chain_key) {
        SIGNAL_UNREF(node->chain_key);
    }

    if(node->message_keys_head) {
        message_keys_node *cur_node;
        message_keys_node *tmp_node;
        DL_FOREACH_SAFE(node->message_keys_head, cur_node, tmp_node) {
            DL_DELETE(node->message_keys_head, cur_node);
            signal_explicit_bzero(&cur_node->message_key, sizeof(ratchet_message_keys));
            free(cur_node);
        }
        node->message_keys_head = 0;
    }

    free(node);
}

static void session_state_free_receiver_chain(session_state *state)
{
    session_state_receiver_chain *cur_node;
    session_state_receiver_chain *tmp_node;
    DL_FOREACH_SAFE(state->receiver_chain_head, cur_node, tmp_node) {
        DL_DELETE(state->receiver_chain_head, cur_node);
        session_state_free_receiver_chain_node(cur_node);
    }
    state->receiver_chain_head = 0;
}

void session_state_destroy(signal_type_base *type)
{
    session_state *state = (session_state *)type;

    if(state->local_identity_public) {
        SIGNAL_UNREF(state->local_identity_public);
    }
    if(state->remote_identity_public) {
        SIGNAL_UNREF(state->remote_identity_public);
    }
    if(state->root_key) {
        SIGNAL_UNREF(state->root_key);
    }
    session_state_free_sender_chain(state);
    session_state_free_receiver_chain(state);
    if(state->has_pending_key_exchange) {
        if(state->pending_key_exchange.local_base_key) {
            SIGNAL_UNREF(state->pending_key_exchange.local_base_key);
        }
        if(state->pending_key_exchange.local_ratchet_key) {
            SIGNAL_UNREF(state->pending_key_exchange.local_ratchet_key);
        }
        if(state->pending_key_exchange.local_identity_key) {
            SIGNAL_UNREF(state->pending_key_exchange.local_identity_key);
        }
    }
    if(state->has_pending_pre_key) {
        if(state->pending_pre_key.base_key) {
            SIGNAL_UNREF(state->pending_pre_key.base_key);
        }
    }
    if(state->alice_base_key) {
        SIGNAL_UNREF(state->alice_base_key);
    }
    free(state);
}
