/*
 * Copyright (c) 2019, 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.
 */

#include "nsconfig.h"
#include <string.h>
#include "ns_types.h"
#include "ns_list.h"
#include "ns_trace.h"
#include "nsdynmemLIB.h"
#include "common_functions.h"
#include "6LoWPAN/ws/ws_config.h"
#include "ns_file_system.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "Service_Libs/utils/ns_file.h"
#include "Security/protocols/sec_prot_certs.h"
#include "Security/protocols/sec_prot_keys.h"
#include "6LoWPAN/ws/ws_pae_nvm_store.h"
#include "6LoWPAN/ws/ws_pae_nvm_data.h"
#include "6LoWPAN/ws/ws_pae_controller.h"
#include "6LoWPAN/ws/ws_pae_time.h"

#ifdef HAVE_WS

#define TRACE_GROUP "wsnv"

#define PAE_NVM_FIELD_NOT_SET            0   // Field is not present
#define PAE_NVM_FIELD_SET                1   // Field is present

void ws_pae_nvm_store_generic_tlv_create(nvm_tlv_t *tlv_entry, uint16_t tag, uint16_t length)
{
    tlv_entry->tag = tag;
    tlv_entry->len = length;
}

nvm_tlv_t *ws_pae_nvm_store_generic_tlv_allocate_and_create(uint16_t tag, uint16_t length)
{
    nvm_tlv_t *tlv_entry = ns_dyn_mem_alloc(length + sizeof(nvm_tlv_t));
    if (!tlv_entry) {
        return NULL;
    }
    tlv_entry->tag = tag;
    tlv_entry->len = length;

    return tlv_entry;
}

void ws_pae_nvm_store_generic_tlv_free(nvm_tlv_t *tlv_entry)
{
    if (!tlv_entry) {
        return;
    }

    ns_dyn_mem_free(tlv_entry);
}

void ws_pae_nvm_store_nw_info_tlv_create(nvm_tlv_t *tlv_entry, uint16_t pan_id, char *nw_name, sec_prot_gtk_keys_t *gtks)
{
    int len;
    tlv_entry->tag = PAE_NVM_NW_INFO_TAG;
    tlv_entry->len = PAE_NVM_NW_INFO_LEN;

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    tlv = common_write_16_bit(pan_id, tlv);

    memset(tlv, 0, 33);
    // use strnlen & memset instead of strncpy to avoid gcc warning:
    // call to __builtin___strncpy_chk will always overflow destination buffer [-Werror]
    len = strlen(nw_name);
    if (len > 32) {
        len = 32;
    }
    memcpy((char *)tlv, nw_name, len);
    tlv += 33;

    uint64_t current_time = ws_pae_current_time_get();

    for (uint8_t i = 0; i < GTK_NUM; i++) {
        if (gtks && sec_prot_keys_gtk_is_set(gtks, i)) {
            *tlv++ = PAE_NVM_FIELD_SET; // GTK is set

            uint64_t expirytime = sec_prot_keys_gtk_exptime_from_lifetime_get(gtks, i, current_time);
            // Sets stored expiration time to GTKs; no need to update anymore to NVM if not changed
            sec_prot_keys_gtk_expirytime_set(gtks, i, expirytime);

            tlv = common_write_64_bit(expirytime, tlv);

            uint8_t status = sec_prot_keys_gtk_status_get(gtks, i);
            *tlv++ = status;

            uint8_t install_order = sec_prot_keys_gtk_install_order_get(gtks, i);
            *tlv++ = install_order;

            uint8_t *gtk = sec_prot_keys_gtk_get(gtks, i);
            memcpy(tlv, gtk, GTK_LEN);
            tlv += GTK_LEN;
        } else {
            *tlv++ = PAE_NVM_FIELD_NOT_SET; // GTK is not set
            memset(tlv, 0, 8 + 1 + 1 + GTK_LEN);
            tlv += 8 + 1 + 1 + GTK_LEN;
        }
    }

    tr_debug("NVM NW_INFO write PAN ID %i name: %s", pan_id, nw_name);

}

int8_t ws_pae_nvm_store_nw_info_tlv_read(nvm_tlv_t *tlv_entry, uint16_t *pan_id, char *nw_name, sec_prot_gtk_keys_t *gtks)
{
    if (!tlv_entry || !pan_id || !nw_name) {
        return -1;
    }

    if (tlv_entry->tag != PAE_NVM_NW_INFO_TAG || tlv_entry->len != PAE_NVM_NW_INFO_LEN) {
        return -1;
    }

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    if (*pan_id == 0xffff) {
        // If application has not set pan_id read it from NVM
        *pan_id = common_read_16_bit(tlv);
    }
    tlv += 2;

    if (strlen(nw_name) == 0) {
        // If application has not set network name read it from NVM
        memset(nw_name, 0, 33);
        strncpy(nw_name, (char *) tlv, 32);
    }
    tlv += 33;

    uint64_t current_time = ws_pae_current_time_get();

    tr_debug("NVM NW_INFO current time: %"PRIi64, current_time);

    if (gtks && sec_prot_keys_gtk_count(gtks) == 0) {
        // If application has not set GTKs read them from NVM
        for (uint8_t i = 0; i < GTK_NUM; i++) {
            if (*tlv++ == PAE_NVM_FIELD_SET) { /* GTK is set */
                uint64_t expirytime = common_read_64_bit(tlv);
                uint32_t lifetime = 0;
                if (ws_pae_time_diff_calc(current_time, expirytime, &lifetime, true) < 0) {
                    tlv += 8 + 1 + 1 + GTK_LEN;
                    tr_debug("GTK index %i, expired expiry time: %"PRIi64", lifetime: %"PRIi32, i, expirytime, lifetime);
                    continue;
                }
                tlv += 8;

                uint8_t status = *tlv++;

                uint8_t install_order = *tlv++;

                tr_debug("GTK index: %i, status: %i, install order %i, expiry time: %"PRIi64", lifetime: %"PRIi32, i, status, install_order, expirytime, lifetime);

                sec_prot_keys_gtk_set(gtks, i, tlv, lifetime);
                sec_prot_keys_gtk_expirytime_set(gtks, i, expirytime);
                tlv += GTK_LEN;
                sec_prot_keys_gtk_status_set(gtks, i, status);
                sec_prot_keys_gtk_install_order_set(gtks, i, install_order);
            } else {
                tlv += 8 + 1 + 1 + GTK_LEN;
            }
        }
        sec_prot_keys_gtks_updated_reset(gtks);
    }

    tr_debug("NVM NW_INFO read PAN ID %i name: %s", *pan_id, nw_name);

    return 0;
}

void ws_pae_nvm_store_keys_tlv_create(nvm_tlv_t *tlv_entry, sec_prot_keys_t *sec_keys)
{
    tlv_entry->tag = PAE_NVM_KEYS_TAG;
    tlv_entry->len = PAE_NVM_KEYS_LEN;

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    uint8_t *eui_64 = sec_prot_keys_ptk_eui_64_get(sec_keys);
    if (eui_64) {
        *tlv++ = PAE_NVM_FIELD_SET;
        memcpy(tlv, eui_64, 8);
    } else {
        *tlv++ = PAE_NVM_FIELD_NOT_SET;
        memset(tlv, 0, 8);
    }
    tlv += 8;

    uint8_t *pmk = sec_prot_keys_pmk_get(sec_keys);
    if (pmk) {
        *tlv++ = PAE_NVM_FIELD_SET;
        uint32_t lifetime = sec_prot_keys_pmk_lifetime_get(sec_keys);
        tlv = common_write_32_bit(lifetime, tlv);
        memcpy(tlv, pmk, PMK_LEN);
    } else {
        *tlv++ = PAE_NVM_FIELD_NOT_SET;
        memset(tlv, 0, 4 + PMK_LEN);
    }
    tlv += PMK_LEN;

    uint64_t counter = sec_prot_keys_pmk_replay_cnt_get(sec_keys);
    tlv = common_write_64_bit(counter, tlv);

    uint8_t *ptk = sec_prot_keys_ptk_get(sec_keys);
    if (ptk) {
        *tlv++ = PAE_NVM_FIELD_SET;
        uint32_t lifetime = sec_prot_keys_ptk_lifetime_get(sec_keys);
        tlv = common_write_32_bit(lifetime, tlv);
        memcpy(tlv, ptk, PTK_LEN);
    } else {
        *tlv++ = PAE_NVM_FIELD_NOT_SET;
        memset(tlv, 0, 4 + PTK_LEN);
    }
    tlv += PTK_LEN;

    tr_debug("NVM KEYS write");
}

int8_t ws_pae_nvm_store_keys_tlv_read(nvm_tlv_t *tlv_entry, sec_prot_keys_t *sec_keys)
{
    if (!tlv_entry || !sec_keys) {
        return -1;
    }

    if (tlv_entry->tag != PAE_NVM_KEYS_TAG || tlv_entry->len != PAE_NVM_KEYS_LEN) {
        return -1;
    }

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    // EUI-64 set */
    if (*tlv++ == PAE_NVM_FIELD_SET) {
        sec_prot_keys_ptk_eui_64_write(sec_keys, tlv);
    }
    tlv += 8;

    // PMK set
    if (*tlv++ == PAE_NVM_FIELD_SET) {
        uint32_t lifetime = common_read_32_bit(tlv);
        tlv += 4;
        sec_prot_keys_pmk_write(sec_keys, tlv, lifetime);
    } else {
        tlv += 4;
    }
    tlv += PMK_LEN;

    uint64_t counter = common_read_64_bit(tlv);
    tlv += 8;
    sec_prot_keys_pmk_replay_cnt_set(sec_keys, counter);

    // PTK set
    if (*tlv++ == PAE_NVM_FIELD_SET) {
        uint32_t lifetime = common_read_32_bit(tlv);
        tlv += 4;
        sec_prot_keys_ptk_write(sec_keys, tlv, lifetime);
    } else {
        tlv += 4;
    }

    tlv += PTK_LEN;

    sec_prot_keys_updated_reset(sec_keys);

    tr_debug("NVM KEYS read");

    return 0;
}

void ws_pae_nvm_store_frame_counter_tlv_create(nvm_tlv_t *tlv_entry, uint32_t restart_cnt, uint16_t pan_version, frame_counters_t *counters)
{
    tlv_entry->tag = PAE_NVM_FRAME_COUNTER_TAG;
    tlv_entry->len = PAE_NVM_FRAME_COUNTER_LEN;

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    tlv = common_write_32_bit(restart_cnt, tlv);

    uint64_t stored_time = ws_pae_current_time_get();
    tlv = common_write_64_bit(stored_time, tlv);

    tlv = common_write_16_bit(pan_version, tlv);

    for (uint8_t index = 0; index < GTK_NUM; index++) {
        if (!counters->counter[index].set) {
            *tlv++ = PAE_NVM_FIELD_NOT_SET;
            memset(tlv, 0, GTK_LEN + 4);
            tlv += GTK_LEN + 4;
            continue;
        }
        *tlv++ = PAE_NVM_FIELD_SET;
        memcpy(tlv, counters->counter[index].gtk, GTK_LEN);
        tlv += GTK_LEN;
        tlv = common_write_32_bit(counters->counter[index].frame_counter, tlv);
    }

    tr_debug("NVM FRAME COUNTER write");
}

int8_t ws_pae_nvm_store_frame_counter_tlv_read(nvm_tlv_t *tlv_entry, uint32_t *restart_cnt, uint64_t *stored_time, uint16_t *pan_version, frame_counters_t *counters)
{
    if (!tlv_entry || !counters) {
        return -1;
    }

    if (tlv_entry->tag != PAE_NVM_FRAME_COUNTER_TAG || tlv_entry->len != PAE_NVM_FRAME_COUNTER_LEN) {
        return -1;
    }

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    *restart_cnt = common_read_32_bit(tlv);
    tlv += 4;

    *stored_time = common_read_64_bit(tlv);
    tlv += 8;

    *pan_version = common_read_16_bit(tlv);
    tlv += 2;

    for (uint8_t index = 0; index < GTK_NUM; index++) {
        // Frame counter not set
        if (*tlv++ == PAE_NVM_FIELD_NOT_SET) {
            counters->counter[index].set = false;
            tlv += GTK_LEN + 4;
            continue;
        }
        // Frame counter is set, read GTK key and counter values
        counters->counter[index].set = true;
        memcpy(counters->counter[index].gtk, tlv, GTK_LEN);
        tlv += GTK_LEN;
        counters->counter[index].frame_counter = common_read_32_bit(tlv);
        tlv += 4;
    }

    tr_debug("NVM FRAME COUNTER read");

    return 0;
}

void ws_pae_nvm_store_key_storage_index_tlv_create(nvm_tlv_t *tlv_entry, uint64_t bitfield)
{
    tlv_entry->tag = PAE_NVM_KEY_STORAGE_INDEX_TAG;
    tlv_entry->len = PAE_NVM_KEY_STORAGE_INDEX_LEN;

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;

    tlv = common_write_64_bit(bitfield, tlv);

    tr_debug("NVM KEY STORAGE INDEX write");
}

int8_t ws_pae_nvm_store_key_storage_index_tlv_read(nvm_tlv_t *tlv_entry, uint64_t *bitfield)
{
    if (!tlv_entry || !bitfield) {
        return -1;
    }

    if (tlv_entry->tag != PAE_NVM_KEY_STORAGE_INDEX_TAG || tlv_entry->len != PAE_NVM_KEY_STORAGE_INDEX_LEN) {
        return -1;
    }

    uint8_t *tlv = ((uint8_t *) &tlv_entry->tag) + NVM_TLV_FIXED_LEN;
    *bitfield = common_read_64_bit(tlv);
    tlv += 8;

    tr_debug("NVM KEY STORAGE INDEX read");

    return 0;
}

void ws_pae_nvm_store_key_storage_tlv_create(nvm_tlv_t *tlv_entry, uint16_t length)
{
    memset(tlv_entry, 0, sizeof(key_storage_nvm_tlv_entry_t));

    tlv_entry->tag = PAE_NVM_KEY_STORAGE_TAG;
    tlv_entry->len = length - sizeof(nvm_tlv_t);

    tr_debug("NVM KEY STORAGE create");
}

int8_t ws_pae_nvm_store_key_storage_tlv_read(nvm_tlv_t *tlv_entry, uint16_t length)
{
    if (!tlv_entry || !length) {
        return -1;
    }

    if (tlv_entry->tag != PAE_NVM_KEY_STORAGE_TAG || tlv_entry->len != length - sizeof(nvm_tlv_t)) {
        return -1;
    }

    tr_debug("NVM KEY STORAGE read");

    return 0;
}

#endif /* HAVE_WS */

