summaryrefslogtreecommitdiff
path: root/libs/libaxolotl/src/session_state.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libaxolotl/src/session_state.c')
-rw-r--r--libs/libaxolotl/src/session_state.c1879
1 files changed, 1879 insertions, 0 deletions
diff --git a/libs/libaxolotl/src/session_state.c b/libs/libaxolotl/src/session_state.c
new file mode 100644
index 0000000000..db787ebf72
--- /dev/null
+++ b/libs/libaxolotl/src/session_state.c
@@ -0,0 +1,1879 @@
+#include "session_state.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "hkdf.h"
+#include "curve.h"
+#include "ratchet.h"
+#include "axolotl_internal.h"
+#include "LocalStorageProtocol.pb-c.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;
+ message_keys_node *message_keys_head;
+} 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
+{
+ axolotl_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;
+
+ axolotl_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,
+ axolotl_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,
+ axolotl_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,
+ axolotl_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,
+ axolotl_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, axolotl_context *global_context)
+{
+ session_state *result = malloc(sizeof(session_state));
+ if(!result) {
+ return AX_ERR_NOMEM;
+ }
+ memset(result, 0, sizeof(session_state));
+ AXOLOTL_INIT(result, session_state_destroy);
+ result->session_version = 2;
+ result->global_context = global_context;
+
+ *state = result;
+ return 0;
+}
+
+int session_state_serialize(axolotl_buffer **buffer, session_state *state)
+{
+ int result = 0;
+ size_t result_size = 0;
+ Textsecure__SessionStructure *state_structure = 0;
+ axolotl_buffer *result_buf = 0;
+ size_t len = 0;
+ uint8_t *data = 0;
+
+ state_structure = malloc(sizeof(Textsecure__SessionStructure));
+ if(!state_structure) {
+ result = AX_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 = axolotl_buffer_alloc(len);
+ if(!result_buf) {
+ result = AX_ERR_NOMEM;
+ goto complete;
+ }
+
+ data = axolotl_buffer_data(result_buf);
+ result_size = textsecure__session_structure__pack(state_structure, data);
+ if(result_size != len) {
+ axolotl_buffer_free(result_buf);
+ result = AX_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, axolotl_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 = AX_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) {
+ AXOLOTL_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) {
+ result = ec_public_key_serialize_protobuf(
+ &session_structure->localidentitypublic, state->local_identity_public);
+ if(result < 0) {
+ goto complete;
+ }
+ session_structure->has_localidentitypublic = 1;
+ }
+
+ if(state->remote_identity_public) {
+ result = ec_public_key_serialize_protobuf(
+ &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 = AX_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 i = 0;
+
+ unsigned int count;
+ session_state_receiver_chain *cur_node;
+ DL_COUNT(state->receiver_chain_head, cur_node, count);
+
+ if(count > SIZE_MAX / sizeof(Textsecure__SessionStructure__Chain *)) {
+ result = AX_ERR_NOMEM;
+ goto complete;
+ }
+
+ session_structure->receiverchains = malloc(sizeof(Textsecure__SessionStructure__Chain *) * count);
+ if(!session_structure->receiverchains) {
+ result = AX_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 = AX_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 = AX_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 = AX_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;
+ }
+
+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;
+ }
+ }
+
+ 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_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 = AX_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 i = 0;
+
+ unsigned int count;
+ 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 = AX_ERR_NOMEM;
+ goto complete;
+ }
+
+ chain_structure->messagekeys = malloc(sizeof(Textsecure__SessionStructure__Chain__MessageKey *) * count);
+ if(!chain_structure->messagekeys) {
+ result = AX_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 = AX_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 = AX_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 = AX_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 = AX_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, axolotl_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);
+ AXOLOTL_UNREF(kdf);
+ if(result < 0) {
+ goto complete;
+ }
+ }
+
+ if(session_structure->has_previouscounter) {
+ result_state->previous_counter = session_structure->previouscounter;
+ }
+
+ if(session_structure->senderchain) {
+ 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 = AX_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;
+ }
+ }
+
+complete:
+ if(result >= 0) {
+ *state = result_state;
+ }
+ else {
+ if(result_state) {
+ AXOLOTL_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,
+ axolotl_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:
+ AXOLOTL_UNREF(local_base_key_public);
+ AXOLOTL_UNREF(local_base_key_private);
+ AXOLOTL_UNREF(local_ratchet_key_public);
+ AXOLOTL_UNREF(local_ratchet_key_private);
+ AXOLOTL_UNREF(local_identity_key_public);
+ AXOLOTL_UNREF(local_identity_key_private);
+
+ if(result < 0) {
+ AXOLOTL_UNREF(local_base_key);
+ AXOLOTL_UNREF(local_ratchet_key);
+ AXOLOTL_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,
+ axolotl_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,
+ axolotl_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:
+ AXOLOTL_UNREF(kdf);
+ AXOLOTL_UNREF(sender_ratchet_key_public);
+ AXOLOTL_UNREF(sender_ratchet_key_private);
+ if(result < 0) {
+ AXOLOTL_UNREF(sender_ratchet_key_pair);
+ AXOLOTL_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,
+ axolotl_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 = AX_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:
+ AXOLOTL_UNREF(kdf);
+ if(result < 0) {
+ AXOLOTL_UNREF(sender_ratchet_key);
+ AXOLOTL_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);
+ axolotl_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, axolotl_context *global_context)
+{
+ int result = 0;
+ axolotl_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 = axolotl_buffer_data(buffer);
+ len = axolotl_buffer_len(buffer);
+
+ result = session_state_deserialize(state, data, len, global_context);
+ if(result < 0) {
+ goto complete;
+ }
+
+complete:
+ if(buffer) {
+ axolotl_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;
+}
+
+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) {
+ AXOLOTL_UNREF(state->local_identity_public);
+ }
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->remote_identity_public);
+ }
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->root_key);
+ }
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->sender_chain.sender_ratchet_key_pair);
+ }
+ AXOLOTL_REF(sender_ratchet_key_pair);
+ state->sender_chain.sender_ratchet_key_pair = sender_ratchet_key_pair;
+
+ if(state->sender_chain.chain_key) {
+ AXOLOTL_UNREF(state->sender_chain.chain_key);
+ }
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->sender_chain.chain_key);
+ }
+ AXOLOTL_REF(chain_key);
+ state->sender_chain.chain_key = chain_key;
+ return 0;
+ }
+ else {
+ return AX_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);
+ axolotl_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 AX_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);
+ axolotl_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 AX_ERR_NOMEM;
+ }
+ memset(node, 0, sizeof(session_state_receiver_chain));
+
+ AXOLOTL_REF(sender_ratchet_key);
+ node->sender_ratchet_key = sender_ratchet_key;
+ AXOLOTL_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) {
+ axolotl_log(state->global_context, AX_LOG_WARNING, "Couldn't find receiver chain to set chain key on");
+ result = AX_ERR_UNKNOWN;
+ goto complete;
+ }
+
+ AXOLOTL_UNREF(node->chain_key);
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_base_key);
+ state->pending_key_exchange.local_base_key = 0;
+ }
+ if(state->pending_key_exchange.local_ratchet_key) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_ratchet_key);
+ state->pending_key_exchange.local_ratchet_key = 0;
+ }
+ if(state->pending_key_exchange.local_identity_key) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_identity_key);
+ state->pending_key_exchange.local_identity_key = 0;
+ }
+
+ AXOLOTL_REF(our_base_key);
+ AXOLOTL_REF(our_ratchet_key);
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->pending_pre_key.base_key);
+ state->pending_pre_key.base_key = 0;
+ }
+
+ AXOLOTL_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) {
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->alice_base_key);
+ }
+ AXOLOTL_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;
+}
+
+static void session_state_free_sender_chain(session_state *state)
+{
+ if(state->sender_chain.sender_ratchet_key_pair) {
+ AXOLOTL_UNREF(state->sender_chain.sender_ratchet_key_pair);
+ state->sender_chain.sender_ratchet_key_pair = 0;
+ }
+ if(state->sender_chain.chain_key) {
+ AXOLOTL_UNREF(state->sender_chain.chain_key);
+ state->sender_chain.chain_key = 0;
+ }
+
+ if(state->sender_chain.message_keys_head) {
+ message_keys_node *cur_node;
+ message_keys_node *tmp_node;
+ DL_FOREACH_SAFE(state->sender_chain.message_keys_head, cur_node, tmp_node) {
+ DL_DELETE(state->sender_chain.message_keys_head, cur_node);
+ axolotl_explicit_bzero(&cur_node->message_key, sizeof(ratchet_message_keys));
+ free(cur_node);
+ }
+ state->sender_chain.message_keys_head = 0;
+ }
+}
+
+static void session_state_free_receiver_chain_node(session_state_receiver_chain *node)
+{
+ if(node->sender_ratchet_key) {
+ AXOLOTL_UNREF(node->sender_ratchet_key);
+ }
+ if(node->chain_key) {
+ AXOLOTL_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);
+ axolotl_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(axolotl_type_base *type)
+{
+ session_state *state = (session_state *)type;
+
+ if(state->local_identity_public) {
+ AXOLOTL_UNREF(state->local_identity_public);
+ }
+ if(state->remote_identity_public) {
+ AXOLOTL_UNREF(state->remote_identity_public);
+ }
+ if(state->root_key) {
+ AXOLOTL_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) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_base_key);
+ }
+ if(state->pending_key_exchange.local_ratchet_key) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_ratchet_key);
+ }
+ if(state->pending_key_exchange.local_identity_key) {
+ AXOLOTL_UNREF(state->pending_key_exchange.local_identity_key);
+ }
+ }
+ if(state->has_pending_pre_key) {
+ if(state->pending_pre_key.base_key) {
+ AXOLOTL_UNREF(state->pending_pre_key.base_key);
+ }
+ }
+ if(state->alice_base_key) {
+ AXOLOTL_UNREF(state->alice_base_key);
+ }
+ free(state);
+}