#include <string.h>
#include <stdio.h>
#include "session.h"

#define MAX_SESSION_COUNT  16
typedef struct _session_id_tracker_t {
    uint32_t          session_id;
} session_id_tracker_t;

int generate_session_id(uint32_t *session_id);
int is_session_id_valid(uint32_t session_id);

session_id_tracker_t *g_session_id_tracker[MAX_SESSION_COUNT];
dh_session_t g_dest_session_info_map[MAX_SESSION_COUNT];
uint32_t g_session_count = 0;
int verify_peer_enclave_trust(sgx_dh_session_enclave_identity_t *peer_enclave_identity) {
    if (!peer_enclave_identity) {
        return ERROR;
    }

    if (!(peer_enclave_identity->attributes.flags & SGX_FLAGS_INITTED)) {
        return ERROR;
    }

    return SUCCESS;
}
int  session_request(sgx_dh_msg1_t *dh_msg1,
                     uint32_t *session_id ) {
    sgx_dh_session_t sgx_dh_session;
    sgx_status_t status = SGX_SUCCESS;

    if (!session_id || !dh_msg1) {
        return ERROR;
    }
    //Intialize the session as a session responder
    status = sgx_dh_init_session(SGX_DH_SESSION_RESPONDER, &sgx_dh_session);
    if (SGX_SUCCESS != status) {
        printf("sgx_dh_init_session failed\n");
        return ERROR;
    }

    //get a new SessionID
    if (generate_session_id(session_id) < 0) {
        return ERROR;    //no more sessions available
    }

    //Allocate memory for the session id tracker
    g_session_id_tracker[*session_id] = (session_id_tracker_t *)malloc(sizeof(
                                            session_id_tracker_t));
    if (!g_session_id_tracker[*session_id]) {
        printf("g_session_id_tracker  failed\n");
        return ERROR;
    }

    memset(g_session_id_tracker[*session_id], 0, sizeof(session_id_tracker_t));
    g_session_id_tracker[*session_id]->session_id = *session_id;

    //Generate Message1 that will be returned to Source Enclave
    status = sgx_dh_responder_gen_msg1((sgx_dh_msg1_t *)dh_msg1, &sgx_dh_session);
    if (SGX_SUCCESS != status) {
        free(g_session_id_tracker[*session_id]);
        printf("sgx_dh_responder_gen_msg1  failed\n");
        return ERROR;
    }
    memcpy(&g_dest_session_info_map[*session_id].in_progress.dh_session, &sgx_dh_session,
           sizeof(sgx_dh_session_t));
    //Store the session information under the corresponding source enlave id key
    g_dest_session_info_map[*session_id].status = IN_PROGRESS;

    return SUCCESS;
}
int exchange_report(sgx_dh_msg2_t *dh_msg2,
                    sgx_dh_msg3_t *dh_msg3,
                    uint32_t session_id) {
    sgx_key_128bit_t dh_aek;   // Session key
    dh_session_t *session_info;
    int  status = SUCCESS;
    sgx_dh_session_t sgx_dh_session;
    sgx_dh_session_enclave_identity_t initiator_identity;

    if (!dh_msg2 || !dh_msg3 || !is_session_id_valid(session_id)) {
        return ERROR;
    }

    memset(&dh_aek, 0, sizeof(sgx_key_128bit_t));
    do {
        //Retrieve the session information for the corresponding source enclave id
        session_info  = &g_dest_session_info_map[session_id];

        memcpy(&sgx_dh_session, &session_info->in_progress.dh_session, sizeof(sgx_dh_session_t));

        dh_msg3->msg3_body.additional_prop_length = 0;
        //Process message 2 from source enclave and obtain message 3
        sgx_status_t se_ret = sgx_dh_responder_proc_msg2(dh_msg2,
                              dh_msg3,
                              &sgx_dh_session,
                              &dh_aek,
                              &initiator_identity);
        if (SGX_SUCCESS != se_ret) {
            status = ERROR;
            printf("sgx_dh_responder_proc_msg2 failed\n");
            break;
        }

        //Verify source enclave's trust
        if (verify_peer_enclave_trust(&initiator_identity) != SUCCESS) {
            status = ERROR;
            break;
        }

        //save the session ID, status and initialize the session nonce
        session_info->session_id = session_id;
        session_info->status = ACTIVE;
        session_info->active.counter = 0;
        memcpy(session_info->active.AEK, &dh_aek, sizeof(sgx_key_128bit_t));
        memset(&dh_aek, 0, sizeof(sgx_key_128bit_t));
        g_session_count++;
    } while (0);

    if (status != SUCCESS) {
        end_session(session_id);
    }

    return status;
}

int end_session(uint32_t session_id) {
    int status = SUCCESS;
    int i;
    dh_session_t *session_info;
    if (!is_session_id_valid(session_id)) { return ERROR; }
    //Get the session information from the map corresponding to the source enclave id
    session_info = &g_dest_session_info_map[session_id];

    //Erase the session information for the current session
    //Update the session id tracker
    if (g_session_count > 0) {
        //check if session exists
        for (i = 1; i <= MAX_SESSION_COUNT; i++) {
            if (g_session_id_tracker[i - 1] != NULL &&
                    g_session_id_tracker[i - 1]->session_id == session_id) {
                memset(g_session_id_tracker[i - 1], 0, sizeof(session_id_tracker_t));
                free(g_session_id_tracker[i - 1]);
                g_session_count--;
                break;
            }
        }
    }
    return status;
}

int is_session_id_valid(uint32_t session_id) {
    if (session_id >= MAX_SESSION_COUNT || session_id < 0) { return 0; }
    return (g_session_id_tracker[session_id] != NULL);

}

dh_session_t *get_session_info(uint32_t session_id ) {
    if (!is_session_id_valid(session_id)) {
        return NULL;
    }
    return &(g_dest_session_info_map[session_id]);

}

int generate_session_id(uint32_t *session_id) {
    if (!session_id) {
        return ERROR;
    }
    //if the session structure is untintialized, set that as the next session ID
    for (int i = 0; i < MAX_SESSION_COUNT; i++) {
        if (g_session_id_tracker[i] == NULL) {
            *session_id = i;
            return SUCCESS;
        }
    }
    return ERROR;
}
