/*
 * Copyright (c) 2015-2017, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * \file mle_service_security.c
 * \brief Add short description about this file!!!
 *
 */

#include "nsconfig.h"
#include <string.h>
#include <ns_types.h>
#include "ns_trace.h"
#include "common_functions.h"
#include "ccmLIB.h"
#include "nsdynmemLIB.h"
#include "Core/include/address.h"
#include "Core/include/ns_buffer.h"
#include "MLE/mle.h"
#include "mac_common_defines.h"
#include "Service_Libs/mle_service/mle_service_security.h"

#define TRACE_GROUP "mlSS"

typedef struct {
    mle_security_components_t security_params;
    int8_t interface_id;
    ns_list_link_t link;
} mle_service_security_isntance_list_t;

typedef NS_LIST_HEAD(mle_service_security_isntance_list_t, link) mle_service_security_instance_list_t;

static mle_service_security_instance_list_t srv_security_instance_list;


static mle_security_key_t * mle_service_security_key_entry_get(mle_security_components_t *sec_ptr, bool primary );

static mle_service_security_isntance_list_t * mle_service_security_instance_get(int8_t interface_id)
{
    ns_list_foreach(mle_service_security_isntance_list_t, cur_ptr, &srv_security_instance_list) {
        if (cur_ptr->interface_id == interface_id) {
            return cur_ptr;
        }
    }
    return NULL;
}

mle_security_components_t * mle_service_security_params_get(int8_t interface_id)
{
    ns_list_foreach(mle_service_security_isntance_list_t, cur_ptr, &srv_security_instance_list) {
        if (cur_ptr->interface_id == interface_id) {
            return &cur_ptr->security_params;
        }
    }
    return NULL;
}


int mle_service_security_instance_allocate(int8_t interface_id)
{
    mle_service_security_isntance_list_t *srv_ptr = mle_service_security_instance_get(interface_id);

    if (!srv_ptr) {
        srv_ptr = ns_dyn_mem_alloc(sizeof(mle_service_security_isntance_list_t));
        if (!srv_ptr) {
            return -1;
        }
        ns_list_add_to_start(&srv_security_instance_list, srv_ptr);
        srv_ptr->interface_id = interface_id;
        srv_ptr->security_params.key_req = NULL;
        srv_ptr->security_params.security_notify = NULL;
        srv_ptr->security_params.security_frame_counter = 0;
        srv_ptr->security_params.sec_level = 0;
    }

    return 0;
}

int mle_service_security_instance_delete(int8_t interface_id)
{
    ns_list_foreach(mle_service_security_isntance_list_t, cur_ptr, &srv_security_instance_list) {
        if (cur_ptr->interface_id == interface_id) {
            ns_list_remove(&srv_security_instance_list, cur_ptr);
            ns_dyn_mem_free(cur_ptr);
            return 0;
        }
    }
    return -1;
}

mle_security_key_t * mle_service_security_key_get(mle_security_components_t *sec_ptr, uint8_t key_id )
{
    uint8_t i;
    mle_security_key_t *key;
    for (i=0; i< MLE_MAX_KEY_TABLE_SIZE; i++) {
        key = &sec_ptr->mle_security_key_table[i];

        if (key->key_valid && key->key_id == key_id) {
            return key;
        }
    }
    return NULL;
}


static mle_security_key_t * mle_service_security_key_get_free_entry(mle_security_components_t *sec_ptr, bool primary )
{
    uint8_t i;
    mle_security_key_t *key;
    for (i=0; i< MLE_MAX_KEY_TABLE_SIZE; i++) {
        key = &sec_ptr->mle_security_key_table[i];

        if (!key->key_valid && key->primary_key == primary) {
            return key;
        }
    }
    return NULL;
}

static mle_security_key_t * mle_service_security_key_entry_get(mle_security_components_t *sec_ptr, bool primary )
{
    uint8_t i;
    mle_security_key_t *key;
    for (i=0; i< MLE_MAX_KEY_TABLE_SIZE; i++) {
        key = &sec_ptr->mle_security_key_table[i];

        if (key->key_valid && key->primary_key == primary) {
            return key;
        }
    }
    return NULL;
}

void mle_service_security_parameters_init(mle_security_components_t *sec_ptr)
{
    uint8_t i;
    for (i=0; i< MLE_MAX_KEY_TABLE_SIZE; i++) {
        sec_ptr->mle_security_key_table[i].key_id = 0xff;
        if (i) {
            sec_ptr->mle_security_key_table[i].primary_key = false;
        } else {
            sec_ptr->mle_security_key_table[i].primary_key = true;
        }
        sec_ptr->mle_security_key_table[i].key_valid = false;
        sec_ptr->mle_security_key_table[i].pending_primary = false;
        memset(sec_ptr->mle_security_key_table[i].aes_key,0,16);
    }
}

uint32_t mle_service_security_get_framecounter(mle_security_components_t *sec_ptr, bool increment)
{
    if (increment) {
        return ++sec_ptr->security_frame_counter;
    }
    return sec_ptr->security_frame_counter;
}

uint8_t mle_service_security_get_default_key_id(mle_security_components_t *sec_ptr)
{
    mle_security_key_t *default_key = mle_service_security_key_entry_get(sec_ptr, true );
    if (default_key) {
        return default_key->key_id;
    }
    return 0;
}

uint8_t *mle_service_security_get_default_key(mle_security_components_t *sec_ptr)
{
    mle_security_key_t *default_key = mle_service_security_key_entry_get(sec_ptr, true );
    if (default_key) {
        return default_key->aes_key;
    }
    return 0;
}

uint8_t mle_service_security_get_next_key_id(mle_security_components_t *sec_ptr)
{
    mle_security_key_t *default_key = mle_service_security_key_entry_get(sec_ptr, false );
    if (default_key) {
        return default_key->key_id;
    }
    return 0;
}

uint8_t *mle_service_security_get_next_key(mle_security_components_t *sec_ptr)
{
    mle_security_key_t *default_key = mle_service_security_key_entry_get(sec_ptr, false );
    if (default_key) {
        return default_key->aes_key;
    }
    return 0;
}

void mle_service_security_header_buf_init(mle_security_components_t *sec_ptr, mle_security_header_t *securityHeader, bool keySrcValid, uint32_t keySrc)
{
    securityHeader->securityLevel = sec_ptr->sec_level;
    if (securityHeader->securityLevel) {
        securityHeader->frameCounter = mle_service_security_get_framecounter(sec_ptr,true);
        securityHeader->KeyIndex = mle_service_security_get_default_key_id(sec_ptr);
        if (keySrcValid) {
            securityHeader->KeyIdMode = MAC_KEY_ID_MODE_SRC4_IDX;
            common_write_32_bit(keySrc, securityHeader->Keysource);
        } else {
            securityHeader->KeyIdMode = MAC_KEY_ID_MODE_IDX;
        }
    }
}

uint8_t *mle_service_security_get_key(mle_security_header_t *securityHeader, mle_security_components_t *sec_ptr, int8_t interfaceId)
{
    if (securityHeader->KeyIdMode == MAC_KEY_ID_MODE_SRC4_IDX) {
        if (sec_ptr->key_req) {
            uint32_t keySeq = common_read_32_bit(securityHeader->Keysource);
            return sec_ptr->key_req(interfaceId, securityHeader->KeyIndex, keySeq);
        }

    } else if (securityHeader->KeyIdMode == MAC_KEY_ID_MODE_IDX) {

        mle_security_key_t *key;

        key = mle_service_security_key_get(sec_ptr, securityHeader->KeyIndex);
        if (key) {

            if (key->pending_primary) {
                //Switch Key's
                mle_security_key_t *primary = mle_service_security_key_entry_get(sec_ptr, true);
                if (!primary) {
                    return NULL;
                }
                primary->primary_key = false;
                key->pending_primary = false;
                key->primary_key = true;
                sec_ptr->security_frame_counter = 0;
                //Inform Mac
                if (sec_ptr->security_notify) {
                    //Notify User
                    sec_ptr->security_notify(interfaceId,MLE_SEC_KEY_UPDATE_NOTIFY, securityHeader->KeyIndex);
                }

            }
            return key->aes_key;
        } else {
            if (sec_ptr->security_notify) {
                //Notify User
                return sec_ptr->security_notify(interfaceId,MLE_SEC_UNKNOWN_KEY, securityHeader->KeyIndex);
            }
        }
    }
    return NULL;
}

bool mle_service_security_key_update_trig(uint8_t interface_id,mle_security_components_t *sec_ptr, uint8_t keyId)
{
    mle_security_key_t *key = mle_service_security_key_get(sec_ptr, keyId);
    if (!key) {
        return false;
    }

    if (!key->pending_primary) {
        return false;
    }

    //Switch Key's
    mle_security_key_t *primary = mle_service_security_key_entry_get(sec_ptr, true);
    if (!primary) {
        return false;
    }

    mle_service_reset_frame_counters(interface_id);
    primary->primary_key = false;
    key->pending_primary = false;
    key->primary_key = true;

    return true;
}

bool mle_service_security_key_set(mle_security_components_t *sec_ptr, const uint8_t *key, uint8_t keyId, bool set_primary)
{
    bool key_changed = false;
    if (!sec_ptr) {
        return false;
    }

    mle_security_key_t *key_entry = mle_service_security_key_entry_get(sec_ptr, set_primary);
    if (!key_entry ) {
        //Define first key
        key_entry = mle_service_security_key_get_free_entry(sec_ptr, set_primary);
        if (!key_entry ) {
            return false;
        }
        key_entry->key_valid = true;
    }

    if (memcmp(key_entry->aes_key,key,16) != 0) {
        key_changed = true;
    }

    key_entry->key_id = keyId;
    memcpy(key_entry->aes_key, key, 16);
    key_entry->primary_key = set_primary;
    key_entry->pending_primary = !set_primary;

    if (set_primary) {
        //Clean Old Pending if Primary is configured
        key_entry = mle_service_security_key_entry_get(sec_ptr, !set_primary);
        if (key_entry) {
            key_entry->key_valid = false;
        }
    }

    return key_changed;
}


