From f9216fddb106d46bac2d13d9dfe175b4a475a789 Mon Sep 17 00:00:00 2001 From: Paul Fariello Date: Mon, 25 Feb 2019 18:07:11 +0140 Subject: Add signal store backend and OMEMO start command --- src/omemo/omemo.c | 80 +++++++++++++++- src/omemo/omemo.h | 6 +- src/omemo/store.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/omemo/store.h | 34 +++++++ 4 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 src/omemo/store.c create mode 100644 src/omemo/store.h (limited to 'src/omemo') diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c index e4926baa..32fdce42 100644 --- a/src/omemo/omemo.c +++ b/src/omemo/omemo.c @@ -4,10 +4,12 @@ #include #include #include +#include #include #include "config/account.h" #include "log.h" +#include "omemo/store.h" #include "omemo/crypto.h" #include "omemo/omemo.h" #include "ui/ui.h" @@ -30,6 +32,11 @@ struct omemo_context_t { uint32_t registration_id; signal_protocol_key_helper_pre_key_list_node *pre_keys_head; session_signed_pre_key *signed_pre_key; + signal_protocol_store_context *store; + GHashTable *session_store; + GHashTable *pre_key_store; + GHashTable *signed_pre_key_store; + identity_key_store_t identity_key_store; }; static omemo_context omemo_ctx; @@ -73,6 +80,53 @@ omemo_init(void) signal_context_set_locking_functions(omemo_ctx.signal, lock, unlock); + signal_protocol_store_context_create(&omemo_ctx.store, omemo_ctx.signal); + + omemo_ctx.session_store = session_store_new(); + signal_protocol_session_store session_store = { + .load_session_func = load_session, + .get_sub_device_sessions_func = get_sub_device_sessions, + .store_session_func = store_session, + .contains_session_func = contains_session, + .delete_session_func = delete_session, + .delete_all_sessions_func = delete_all_sessions, + .destroy_func = NULL, + .user_data = omemo_ctx.session_store + }; + signal_protocol_store_context_set_session_store(omemo_ctx.store, &session_store); + + omemo_ctx.pre_key_store = pre_key_store_new(); + signal_protocol_pre_key_store pre_key_store = { + .load_pre_key = load_pre_key, + .store_pre_key = store_pre_key, + .contains_pre_key = contains_pre_key, + .remove_pre_key = remove_pre_key, + .destroy_func = NULL, + .user_data = omemo_ctx.pre_key_store + }; + signal_protocol_store_context_set_pre_key_store(omemo_ctx.store, &pre_key_store); + + omemo_ctx.signed_pre_key_store = signed_pre_key_store_new(); + signal_protocol_signed_pre_key_store signed_pre_key_store = { + .load_signed_pre_key = load_signed_pre_key, + .store_signed_pre_key = store_signed_pre_key, + .contains_signed_pre_key = contains_signed_pre_key, + .remove_signed_pre_key = remove_signed_pre_key, + .destroy_func = NULL, + .user_data = omemo_ctx.pre_key_store + }; + signal_protocol_store_context_set_signed_pre_key_store(omemo_ctx.store, &signed_pre_key_store); + + identity_key_store_new(&omemo_ctx.identity_key_store); + signal_protocol_identity_key_store identity_key_store = { + .get_identity_key_pair = get_identity_key_pair, + .get_local_registration_id = get_local_registration_id, + .save_identity = save_identity, + .is_trusted_identity = is_trusted_identity, + }; + signal_protocol_store_context_set_identity_key_store(omemo_ctx.store, &identity_key_store); + + loaded = FALSE; omemo_ctx.device_list = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)g_list_free); } @@ -103,9 +157,19 @@ omemo_generate_crypto_materials(ProfAccount *account) } void -omemo_start_session(ProfAccount *account, char *barejid) +omemo_start_session(const char *const barejid) { + GList *device_list = g_hash_table_lookup(omemo_ctx.device_list, barejid); + if (!device_list) { + omemo_devicelist_request(barejid); + /* TODO handle response */ + return; + } + GList *device_id; + for (device_id = device_list; device_id != NULL; device_id = device_id->next) { + omemo_bundle_request(barejid, GPOINTER_TO_INT(device_id->data), omemo_start_device_session_handle_bundle, free, strdup(barejid)); + } } gboolean @@ -151,7 +215,7 @@ omemo_signed_prekey_signature(unsigned char **output, size_t *length) } void -omemo_prekeys(GList ** const prekeys, GList ** const ids, GList ** const lengths) +omemo_prekeys(GList **prekeys, GList **ids, GList **lengths) { signal_protocol_key_helper_pre_key_list_node *p; for (p = omemo_ctx.pre_keys_head; p != NULL; p = signal_protocol_key_helper_key_list_next(p)) { @@ -184,6 +248,18 @@ omemo_set_device_list(const char *const jid, GList * device_list) g_hash_table_insert(omemo_ctx.device_list, strdup(jid), device_list); } +void +omemo_start_device_session(const char *const jid, uint32_t device_id, const unsigned char *const prekey, size_t prekey_len) +{ + signal_protocol_address address = { + jid, strlen(jid), device_id + }; + + session_builder *builder; + session_builder_create(&builder, omemo_ctx.store, &address, omemo_ctx.signal); + //session_builder_process_pre_key_bundle(builder, prekey); +} + static void lock(void *user_data) { diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h index 31d942b8..26bd6e65 100644 --- a/src/omemo/omemo.h +++ b/src/omemo/omemo.h @@ -11,8 +11,10 @@ uint32_t omemo_device_id(void); void omemo_identity_key(unsigned char **output, size_t *length); void omemo_signed_prekey(unsigned char **output, size_t *length); void omemo_signed_prekey_signature(unsigned char **output, size_t *length); -void omemo_prekeys(GList ** const prekeys, GList ** const ids, GList ** const lengths); +void omemo_prekeys(GList **prekeys, GList **ids, GList **lengths); void omemo_set_device_list(const char *const jid, GList * device_list); -void omemo_start_session(ProfAccount *account, char *barejid); +void omemo_start_session(const char *const barejid); +void omemo_start_device_session(const char *const jid, uint32_t device_id, const unsigned char *const prekey, size_t prekey_len); + gboolean omemo_loaded(void); diff --git a/src/omemo/store.c b/src/omemo/store.c new file mode 100644 index 00000000..384eb9ce --- /dev/null +++ b/src/omemo/store.c @@ -0,0 +1,267 @@ +#include +#include + +#include "omemo/store.h" + +GHashTable * +session_store_new(void) +{ + return g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); +} + +GHashTable * +pre_key_store_new(void) +{ + return g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)signal_buffer_free); +} + +GHashTable * +signed_pre_key_store_new(void) +{ + return g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)signal_buffer_free); +} + +void +identity_key_store_new(identity_key_store_t *identity_key_store) +{ + identity_key_store->identity_key_store = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)signal_buffer_free); + identity_key_store->private = NULL; + identity_key_store->public = NULL; +} + +int +load_session(signal_buffer **record, const signal_protocol_address *address, + void *user_data) +{ + GHashTable *session_store = (GHashTable *)user_data; + GHashTable *device_store = NULL; + + device_store = g_hash_table_lookup(session_store, address->name); + if (!device_store) { + *record = NULL; + return SG_SUCCESS; + } + + signal_buffer *original = g_hash_table_lookup(device_store, GINT_TO_POINTER(address->device_id)); + *record = signal_buffer_copy(original); + return SG_SUCCESS; +} + +int +get_sub_device_sessions(signal_int_list **sessions, const char *name, + size_t name_len, void *user_data) +{ + GHashTable *session_store = (GHashTable *)user_data; + GHashTable *device_store = NULL; + GHashTableIter iter; + gpointer key, value; + + device_store = g_hash_table_lookup(session_store, name); + if (!device_store) { + return SG_SUCCESS; + } + + *sessions = signal_int_list_alloc(); + g_hash_table_iter_init(&iter, device_store); + while (g_hash_table_iter_next(&iter, &key, &value)) { + signal_int_list_push_back(*sessions, GPOINTER_TO_INT(key)); + } + + + return SG_SUCCESS; +} + +int +store_session(const signal_protocol_address *address, uint8_t *record, + size_t record_len, void *user_data) +{ + GHashTable *session_store = (GHashTable *)user_data; + GHashTable *device_store = NULL; + + device_store = g_hash_table_lookup(session_store, (void *)address->name); + if (!device_store) { + device_store = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)signal_buffer_free); + g_hash_table_insert(session_store, strdup(address->name), device_store); + } + + signal_buffer *buffer = signal_buffer_create(record, record_len); + g_hash_table_insert(device_store, GINT_TO_POINTER(address->device_id), buffer); + return SG_SUCCESS; +} + +int +contains_session(const signal_protocol_address *address, void *user_data) +{ + signal_buffer *record; + load_session(&record, address, user_data); + return record != NULL; +} + +int +delete_session(const signal_protocol_address *address, void *user_data) +{ + GHashTable *session_store = (GHashTable *)user_data; + GHashTable *device_store = NULL; + + device_store = g_hash_table_lookup(session_store, address->name); + if (!device_store) { + return SG_SUCCESS; + } + + return g_hash_table_remove(device_store, GINT_TO_POINTER(address->device_id)); +} + +int +delete_all_sessions(const char *name, size_t name_len, void *user_data) +{ + GHashTable *session_store = (GHashTable *)user_data; + GHashTable *device_store = NULL; + + device_store = g_hash_table_lookup(session_store, name); + if (!device_store) { + return SG_SUCCESS; + } + + guint len = g_hash_table_size(device_store); + g_hash_table_remove_all(device_store); + return len; +} + +int +load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data) +{ + GHashTable *pre_key_store = (GHashTable *)user_data; + + *record = g_hash_table_lookup(pre_key_store, GINT_TO_POINTER(pre_key_id)); + return SG_SUCCESS; +} + +int +store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, + void *user_data) +{ + GHashTable *pre_key_store = (GHashTable *)user_data; + + signal_buffer *buffer = signal_buffer_create(record, record_len); + g_hash_table_insert(pre_key_store, GINT_TO_POINTER(pre_key_id), buffer); + return SG_SUCCESS; +} + +int +contains_pre_key(uint32_t pre_key_id, void *user_data) +{ + signal_buffer *record; + load_pre_key(&record, pre_key_id, user_data); + + return record != NULL; +} + +int +remove_pre_key(uint32_t pre_key_id, void *user_data) +{ + GHashTable *pre_key_store = (GHashTable *)user_data; + + return g_hash_table_remove(pre_key_store, GINT_TO_POINTER(pre_key_id)); +} + +int +load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, + void *user_data) +{ + GHashTable *signed_pre_key_store = (GHashTable *)user_data; + + *record = g_hash_table_lookup(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id)); + return SG_SUCCESS; +} + +int +store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, + size_t record_len, void *user_data) +{ + GHashTable *signed_pre_key_store = (GHashTable *)user_data; + + signal_buffer *buffer = signal_buffer_create(record, record_len); + g_hash_table_insert(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id), buffer); + return SG_SUCCESS; +} + +int +contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) +{ + signal_buffer *record; + load_signed_pre_key(&record, signed_pre_key_id, user_data); + + return record != NULL; +} + +int +remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) +{ + GHashTable *signed_pre_key_store = (GHashTable *)user_data; + + return g_hash_table_remove(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id)); +} + +int +get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, + void *user_data) +{ + identity_key_store_t *identity_key_store = (identity_key_store_t *)user_data; + + *public_data = identity_key_store->public; + *private_data = identity_key_store->private; + + return SG_SUCCESS; +} + +int +get_local_registration_id(void *user_data, uint32_t *registration_id) +{ + identity_key_store_t *identity_key_store = (identity_key_store_t *)user_data; + + *registration_id = identity_key_store->registration_id; + + return SG_SUCCESS; +} + +int +save_identity(const signal_protocol_address *address, uint8_t *key_data, + size_t key_len, void *user_data) +{ + identity_key_store_t *identity_key_store = (identity_key_store_t *)user_data; + char *node = g_strdup_printf("%s:%d", address->name, address->device_id); + + signal_buffer *buffer = signal_buffer_create(key_data, key_len); + g_hash_table_insert(identity_key_store->identity_key_store, node, buffer); + + return SG_SUCCESS; +} + +int +is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, + size_t key_len, void *user_data) +{ + identity_key_store_t *identity_key_store = (identity_key_store_t *)user_data; + char *node = g_strdup_printf("%s:%d", address->name, address->device_id); + + signal_buffer *buffer = signal_buffer_create(key_data, key_len); + signal_buffer *original = g_hash_table_lookup(identity_key_store->identity_key_store, node); + + return original == NULL || signal_buffer_compare(buffer, original); +} + +int +store_sender_key(const signal_protocol_sender_key_name *sender_key_name, + uint8_t *record, size_t record_len, uint8_t *user_record, + size_t user_record_len, void *user_data) +{ + return SG_SUCCESS; +} + +int +load_sender_key(signal_buffer **record, signal_buffer **user_record, + const signal_protocol_sender_key_name *sender_key_name, + void *user_data) +{ + return SG_SUCCESS; +} diff --git a/src/omemo/store.h b/src/omemo/store.h new file mode 100644 index 00000000..e99c514b --- /dev/null +++ b/src/omemo/store.h @@ -0,0 +1,34 @@ +#include + +typedef struct { + signal_buffer *public; + signal_buffer *private; + uint32_t registration_id; + GHashTable * identity_key_store; +} identity_key_store_t; + +GHashTable * session_store_new(void); +GHashTable * pre_key_store_new(void); +GHashTable * signed_pre_key_store_new(void); +void identity_key_store_new(identity_key_store_t *identity_key_store); + +int load_session(signal_buffer **record, const signal_protocol_address *address, void *user_data); +int get_sub_device_sessions(signal_int_list **sessions, const char *name, size_t name_len, void *user_data); +int store_session(const signal_protocol_address *address, uint8_t *record, size_t record_len, void *user_data); +int contains_session(const signal_protocol_address *address, void *user_data); +int delete_session(const signal_protocol_address *address, void *user_data); +int delete_all_sessions(const char *name, size_t name_len, void *user_data); +int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data); +int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data); +int contains_pre_key(uint32_t pre_key_id, void *user_data); +int remove_pre_key(uint32_t pre_key_id, void *user_data); +int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data); +int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data); +int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data); +int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data); +int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data); +int get_local_registration_id(void *user_data, uint32_t *registration_id); +int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data); +int is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data); +int store_sender_key(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len, uint8_t *user_record, size_t user_record_len, void *user_data); +int load_sender_key(signal_buffer **record, signal_buffer **user_record, const signal_protocol_sender_key_name *sender_key_name, void *user_data); -- cgit 1.4.1-2-gfad0 anisanti/profani-tty/blame/tests/test_roster_list.c?id=4bb38ac011b823bd8d2a4ba4beb776f757843d10'>^
1
2
3
4
5
6
7
8
9
                 





                   

                    
                        
 
                                             
 
                  
                                         

                      

 
                                       
 
                  
                                                 
                                         
                                              
                  

 
                                        
 
                  
                                                 
                                         
                                
 

                                                           

 
                                        
 
                  

                                                 
                                         
 

                                              

 
                                                    
 
                  

                                                 
                                         
 

                                                 
 


                                                            

 
                                          
 
                  


                                                 
                                         
 

                                              

 
                                               
 
                  


                                                 
                                         


                                                              
 



                                                           

 
                                                   
 
                  



                                                 
                                         


                                                              
 




                                                           

 
                                                
 
                  



                                                 
                                         


                                                              
 




                                                           

 
                                             
 
                  



                                                 
                                         


                                                              
 




                                                           
 
 
                                    
 
                  


                                                 
 
                               
 
                                               
                                       

                 
                  

 
                                     
 
                  


                                                 
 
                                              
                                        
                 
                  

 
                                    
 
                  


                                                 
 
                                             
                                         
                 
                  

 
                                    
 
                  


                                                 
 
                                               

                        

 
                                             
 
                  
                                                

                        

 
                                                           
 
                  


                                                 
 

                                                 
                                          

                  
                  

 
                                              
 
                  









                                                





                                                 
                                         




                  
                  

 
                                                                    
 
                  


                                                 
 


                                                 
                                          

                  
                  
 
#include <glib.h>
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>

#include "contact.h"
#include "roster_list.h"

void empty_list_when_none_added(void **state)
{
    roster_init();
    GSList *list = roster_get_contacts();
    assert_null(list);
    roster_free();
}

void contains_one_element(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    assert_int_equal(1, g_slist_length(list));
    roster_free();
}

void first_element_correct(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    PContact james = list->data;

    assert_string_equal("James", p_contact_barejid(james));
    roster_free();
}

void contains_two_elements(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();

    assert_int_equal(2, g_slist_length(list));
    roster_free();
}

void first_and_second_elements_correct(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();

    PContact first = list->data;
    PContact second = (g_slist_next(list))->data;

    assert_string_equal("Dave", p_contact_barejid(first));
    assert_string_equal("James", p_contact_barejid(second));
    roster_free();
}

void contains_three_elements(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();

    assert_int_equal(3, g_slist_length(list));
    roster_free();
}

void first_three_elements_correct(void **state)
{
    roster_init();
    roster_add("Bob", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("James", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    PContact bob = list->data;
    PContact dave = (g_slist_next(list))->data;
    PContact james = (g_slist_next(g_slist_next(list)))->data;

    assert_string_equal("James", p_contact_barejid(james));
    assert_string_equal("Dave", p_contact_barejid(dave));
    assert_string_equal("Bob", p_contact_barejid(bob));
    roster_free();
}

void add_twice_at_beginning_adds_once(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    PContact first = list->data;
    PContact second = (g_slist_next(list))->data;
    PContact third = (g_slist_next(g_slist_next(list)))->data;

    assert_int_equal(3, g_slist_length(list));
    assert_string_equal("Bob", p_contact_barejid(first));
    assert_string_equal("Dave", p_contact_barejid(second));
    assert_string_equal("James", p_contact_barejid(third));
    roster_free();
}

void add_twice_in_middle_adds_once(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    PContact first = list->data;
    PContact second = (g_slist_next(list))->data;
    PContact third = (g_slist_next(g_slist_next(list)))->data;

    assert_int_equal(3, g_slist_length(list));
    assert_string_equal("Bob", p_contact_barejid(first));
    assert_string_equal("Dave", p_contact_barejid(second));
    assert_string_equal("James", p_contact_barejid(third));
    roster_free();
}

void add_twice_at_end_adds_once(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);
    roster_add("James", NULL, NULL, NULL, FALSE);
    GSList *list = roster_get_contacts();
    PContact first = list->data;
    PContact second = (g_slist_next(list))->data;
    PContact third = (g_slist_next(g_slist_next(list)))->data;

    assert_int_equal(3, g_slist_length(list));
    assert_string_equal("Bob", p_contact_barejid(first));
    assert_string_equal("Dave", p_contact_barejid(second));
    assert_string_equal("James", p_contact_barejid(third));
    roster_free();
}

void find_first_exists(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *search = strdup("B");

    char *result = roster_find_contact(search);
    assert_string_equal("Bob", result);
    free(result);
    free(search);
    roster_free();
}

void find_second_exists(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *result = roster_find_contact("Dav");
    assert_string_equal("Dave", result);
    free(result);
    roster_free();
}

void find_third_exists(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *result = roster_find_contact("Ja");
    assert_string_equal("James", result);
    free(result);
    roster_free();
}

void find_returns_null(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *result = roster_find_contact("Mike");
    assert_null(result);
    roster_free();
}

void find_on_empty_returns_null(void **state)
{
    roster_init();
    char *result = roster_find_contact("James");
    assert_null(result);
    roster_free();
}

void find_twice_returns_second_when_two_match(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Jamie", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *result1 = roster_find_contact("Jam");
    char *result2 = roster_find_contact(result1);
    assert_string_equal("Jamie", result2);
    free(result1);
    free(result2);
    roster_free();
}

void find_five_times_finds_fifth(void **state)
{
    roster_init();
    roster_add("Jama", NULL, NULL, NULL, FALSE);
    roster_add("Jamb", NULL, NULL, NULL, FALSE);
    roster_add("Mike", NULL, NULL, NULL, FALSE);
    roster_add("Dave", NULL, NULL, NULL, FALSE);
    roster_add("Jamm", NULL, NULL, NULL, FALSE);
    roster_add("Jamn", NULL, NULL, NULL, FALSE);
    roster_add("Matt", NULL, NULL, NULL, FALSE);
    roster_add("Jamo", NULL, NULL, NULL, FALSE);
    roster_add("Jamy", NULL, NULL, NULL, FALSE);
    roster_add("Jamz", NULL, NULL, NULL, FALSE);

    char *result1 = roster_find_contact("Jam");
    char *result2 = roster_find_contact(result1);
    char *result3 = roster_find_contact(result2);
    char *result4 = roster_find_contact(result3);
    char *result5 = roster_find_contact(result4);
    assert_string_equal("Jamo", result5);
    free(result1);
    free(result2);
    free(result3);
    free(result4);
    free(result5);
    roster_free();
}

void find_twice_returns_first_when_two_match_and_reset(void **state)
{
    roster_init();
    roster_add("James", NULL, NULL, NULL, FALSE);
    roster_add("Jamie", NULL, NULL, NULL, FALSE);
    roster_add("Bob", NULL, NULL, NULL, FALSE);

    char *result1 = roster_find_contact("Jam");
    roster_reset_search_attempts();
    char *result2 = roster_find_contact(result1);
    assert_string_equal("James", result2);
    free(result1);
    free(result2);
    roster_free();
}