/*
 * Copyright (c) 2020, 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 "fhss_config.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "6LoWPAN/ws/ws_config.h"
#include "Security/protocols/sec_prot_cfg.h"
#include "Security/kmp/kmp_addr.h"
#include "Security/kmp/kmp_api.h"
#include "Security/kmp/kmp_socket_if.h"
#include "Security/protocols/sec_prot_certs.h"
#include "Security/protocols/sec_prot_keys.h"
#include "6LoWPAN/ws/ws_cfg_settings.h"
#include "6LoWPAN/ws/ws_pae_controller.h"
#include "6LoWPAN/ws/ws_pae_timers.h"
#include "6LoWPAN/ws/ws_pae_auth.h"
#include "6LoWPAN/ws/ws_pae_lib.h"
#include "6LoWPAN/ws/ws_pae_nvm_store.h"
#include "6LoWPAN/ws/ws_pae_nvm_data.h"
#include "6LoWPAN/ws/ws_pae_time.h"
#include "6LoWPAN/ws/ws_pae_key_storage.h"

#ifdef HAVE_WS

#define TRACE_GROUP "wsks"

#define KEY_STORAGE_INDEX_FILE                        "key_storage_index"
#define KEY_STORAGE_FILE                              "key_storage_00"
#define KEY_STORAGE_FILE_LEN                          sizeof(KEY_STORAGE_FILE)

// Storage array header size
#define STORAGE_ARRAY_HEADER_LEN                      sizeof(key_storage_nvm_tlv_entry_t)

// Update the key storage reference time on write operations, if entry reference time differs more than a day
#define KEY_STORAGE_REF_TIME_UPDATE_THRESHOLD         86400

/* Force key storage reference time update, if reference time differs more 31 days */
#define KEY_STORAGE_REF_TIME_UPDATE_FORCE_THRESHOLD   GTK_DEFAULT_LIFETIME + 86400

typedef enum {
    WRITE_SET = 0,
    TIME_SET,
    PMK_CNT_SET,
    PMK_SET,
    PTK_SET,
    EUI64_SET,
    PTKEUI64_SET,
    GTKHASH_SET,
    GTKHASH4WH_SET,
    PMKLTIME_SET,
    PTKLTIME_SET,
} field_set_t;

#define FIELD_SET(field) (field_set | (1 << field))
#define FIELD_IS_SET(field) (field_set & (1 << field))

typedef struct {
    ns_list_link_t link;                                /**< Link */
    const void *instance;                               /**< Instance; for support of multiple authenticators */
    key_storage_nvm_tlv_entry_t *storage_array_handle;  /**< Key storage array handle (NVM header + array) */
    sec_prot_keys_storage_t *storage_array;             /**< Key storage array */
    uint16_t size;                                      /**< Array size in bytes */
    uint16_t entries;                                   /**< Entries in array */
    uint16_t free_entries;                              /**< Free entries in array */
    bool allocated : 1;                                 /**< Allocated */
    bool modified : 1;                                  /**< Array modified */
} key_storage_array_t;

typedef struct {
    uint8_t settings_set;                               /**< Settings set, do not use defaults */
    uint8_t storages_empty;                             /**< Number of empty i.e. to be allocated storages */
    uint16_t storage_default_size;                      /**< Default size for storages */
    uint16_t replace_index;                             /**< Index to replace when storages are full */
    uint64_t store_bitfield;                            /**< Bitfield of stored files */
    uint16_t store_timer_timeout;                       /**< Storing timing timeout */
    uint16_t store_timer;                               /**< Storing timer */
    uint32_t restart_cnt;                               /**< Re-start counter */
} key_storage_params_t;

static key_storage_params_t key_storage_params;

static NS_LIST_DEFINE(key_storage_array_list, key_storage_array_t, link);

static int8_t ws_pae_key_storage_allocate(const void *instance, uint16_t key_storage_size, void *storage);
static void ws_pae_key_storage_clear(key_storage_array_t *key_storage_array);
static void ws_pae_key_storage_list_all_free(void);
static sec_prot_keys_storage_t *ws_pae_key_storage_get(const void *instance, const uint8_t *eui64, key_storage_array_t **key_storage_array, bool return_free);
static sec_prot_keys_storage_t *ws_pae_key_storage_replace(const void *instance, key_storage_array_t **key_storage_array);
static void ws_pae_key_storage_trace(uint16_t field_set, sec_prot_keys_storage_t *key_storage, key_storage_array_t *key_storage_array);
static void ws_pae_key_storage_timer_expiry_set(void);
static int8_t ws_pae_key_storage_array_time_update_entry(uint64_t time_difference, sec_prot_keys_storage_t *storage_array_entry);
static int8_t ws_pae_key_storage_array_time_check_and_update_all(key_storage_array_t *key_storage_array, bool modified);
static int8_t ws_pae_key_storage_array_counters_check_and_update_all(key_storage_array_t *key_storage_array);
static int8_t ws_pae_key_storage_array_lifetime_update(uint32_t time_difference, uint16_t *lifetime);
static int8_t ws_pae_key_storage_array_lifetime_get(uint32_t time_difference, uint16_t short_lifetime, uint32_t *lifetime);
static void ws_pae_key_storage_array_pmk_invalid(sec_prot_keys_storage_t *storage_array);
static void ws_pae_key_storage_array_ptk_invalid(sec_prot_keys_storage_t *storage_array);

static void ws_pae_key_storage_filename_set(char *file_name, uint8_t file_number)
{
    // Name is "key_storage_00" where 00 is replaced by file number
    strcpy(file_name, KEY_STORAGE_FILE);
    file_name[12] = (file_number / 10) + 'A'; // 0 is ASCII 'A', 1='B',..., F='P'
    file_name[13] = (file_number % 10) + 'A'; // same as above
}

int8_t ws_pae_key_storage_memory_set(uint8_t key_storages_number, const uint16_t *key_storage_size, void **key_storages)
{
    for (uint8_t index = 0; index < key_storages_number; index++) {
        if (ws_pae_key_storage_allocate(NULL, key_storage_size[index], (sec_prot_keys_storage_t *) key_storages[index]) < 0) {
            return -1;
        }
    }

    key_storage_params.storages_empty = 0;

    return 0;
}

int8_t ws_pae_key_storage_settings_set(uint8_t alloc_max_number, uint16_t alloc_size, uint16_t storing_interval)
{
    key_storage_params.settings_set = true;
    key_storage_params.storages_empty = alloc_max_number;
    key_storage_params.storage_default_size = alloc_size;
    key_storage_params.store_timer = storing_interval;
    key_storage_params.store_timer_timeout = storing_interval;

    return 0;
}

void ws_pae_key_storage_init(void)
{
    if (!key_storage_params.settings_set) {
        key_storage_params.storages_empty = DEFAULT_NUMBER_OF_STORAGES;
        key_storage_params.storage_default_size = STORAGE_ARRAY_HEADER_LEN + (sizeof(sec_prot_keys_storage_t) * DEFAULT_NUMBER_OF_ENTRIES_IN_ONE_STORAGE);
        key_storage_params.store_timer = DEFAULT_STORING_INTERVAL;
        key_storage_params.store_timer_timeout = DEFAULT_STORING_INTERVAL;
    }
    key_storage_params.replace_index = 0;
    key_storage_params.store_bitfield = 0,
    key_storage_params.restart_cnt = 0;
}

void ws_pae_key_storage_delete(void)
{
    ws_pae_key_storage_list_all_free();
}

static int8_t ws_pae_key_storage_allocate(const void *instance, uint16_t key_storage_size, void *new_storage_array)
{
    key_storage_array_t *key_storage_array = ns_dyn_mem_alloc(sizeof(key_storage_array_t));
    if (!key_storage_array) {
        return -1;
    }

    if (new_storage_array == NULL) {
        key_storage_array->storage_array_handle = ns_dyn_mem_alloc(key_storage_size);
        if (!key_storage_array->storage_array_handle) {
            return -1;
        }
        key_storage_array->allocated = true;
    } else {
        key_storage_array->storage_array_handle = new_storage_array;
        key_storage_array->allocated = false;
    }
    key_storage_array->storage_array = (sec_prot_keys_storage_t *)(((uint8_t *)key_storage_array->storage_array_handle) + STORAGE_ARRAY_HEADER_LEN);
    key_storage_array->size = key_storage_size;
    key_storage_array->entries = (key_storage_size - STORAGE_ARRAY_HEADER_LEN) / sizeof(sec_prot_keys_storage_t);
    key_storage_array->free_entries = key_storage_array->entries;
    key_storage_array->instance = instance;
    key_storage_array->modified = false;

    ws_pae_nvm_store_key_storage_tlv_create((nvm_tlv_t *) key_storage_array->storage_array_handle, key_storage_array->size);

    ws_pae_key_storage_clear(key_storage_array);

    ns_list_add_to_end(&key_storage_array_list, key_storage_array);

    tr_info("KeyS new %s, array: %p entries: %i", key_storage_array->allocated ? "allocated" : "static", (void *) key_storage_array->storage_array, key_storage_array->entries);

    return 0;
}

static void ws_pae_key_storage_clear(key_storage_array_t *key_storage_array)
{
    key_storage_array->storage_array_handle->reference_time = ws_pae_current_time_get();
    key_storage_array->storage_array_handle->reference_restart_cnt = key_storage_params.restart_cnt;

    sec_prot_keys_storage_t *storage_array = (sec_prot_keys_storage_t *) key_storage_array->storage_array;
    // Set all entries empty
    for (uint16_t index = 0; index < key_storage_array->entries; index++) {
        if (key_storage_array->allocated) {
            memset(&storage_array[index], 0, sizeof(sec_prot_keys_storage_t));
        }
        storage_array[index].eui_64_set = false;
    }
}

static void ws_pae_key_storage_list_all_free(void)
{
    ns_list_foreach_safe(key_storage_array_t, entry, &key_storage_array_list) {
        if (entry->allocated) {
            ns_dyn_mem_free(entry->storage_array_handle);
        }
        ns_list_remove(&key_storage_array_list, entry);
        ns_dyn_mem_free(entry);
    }
}

static sec_prot_keys_storage_t *ws_pae_key_storage_get(const void *instance, const uint8_t *eui64, key_storage_array_t **key_storage_array, bool return_free)
{
    sec_prot_keys_storage_t *free_key_storage = NULL;
    *key_storage_array = NULL;
    uint16_t free_index = 0;

    ns_list_foreach(key_storage_array_t, entry, &key_storage_array_list) {
        // Checks whether storage is for this authenticator
        if (entry->instance != NULL && entry->instance != instance) {
            tr_info("KeyS get instance other");
            continue;
        }
        // Checks entries in storage array
        sec_prot_keys_storage_t *storage_array = (sec_prot_keys_storage_t *) entry->storage_array;
        for (uint16_t index = 0; index < entry->entries; index++) {
            if (!storage_array[index].eui_64_set) {
                // Stores free array entry and initiates data on it to zero
                if (return_free && free_key_storage == NULL) {
                    *key_storage_array = entry;
                    free_key_storage = &storage_array[index];
                    memset(&storage_array[index], 0, sizeof(sec_prot_keys_storage_t));
                    free_index = index;
                }
                continue;
            }
            // Searches for matching entry
            if (memcmp(&storage_array[index].ptk_eui_64, eui64, 8) == 0) {
                *key_storage_array = entry;
                // If not already reserved for authenticator instance, reserve array for it
                entry->instance = instance;
                tr_info("KeyS get array: %p i: %i eui64: %s", (void *) entry->storage_array, index, tr_array(eui64, 8));
                return &storage_array[index];
            }
        }
    }

    // If not already reserved for authenticator instance, reserve array for it
    if (free_key_storage != NULL && (*key_storage_array)->instance == NULL) {
        (*key_storage_array)->instance = instance;
    }

    tr_info("KeyS get array %s: %p i: %i free: %p eui64: %s", *key_storage_array ? "" : "(not alloc)", *key_storage_array ? (void *)(*key_storage_array)->storage_array : NULL, *key_storage_array ? free_index : 0, (void *)free_key_storage, tr_array(eui64, 8));
    // If matching entry is not found, returns free entry
    return free_key_storage;
}

bool ws_pae_key_storage_supp_delete(const void *instance, const uint8_t *eui64)
{
    (void) instance;

    bool deleted = false;

    ns_list_foreach(key_storage_array_t, entry, &key_storage_array_list) {
        // Checks entries in storage array
        sec_prot_keys_storage_t *storage_array = (sec_prot_keys_storage_t *) entry->storage_array;
        for (uint16_t index = 0; index < entry->entries; index++) {
            if (!storage_array[index].eui_64_set) {
                continue;
            }
            // Searches for matching entry
            if (memcmp(&storage_array[index].ptk_eui_64, eui64, 8) == 0) {
                memset(&storage_array[index], 0, sizeof(sec_prot_keys_storage_t));
                tr_info("KeyS delete array: %p i: %i eui64: %s", (void *) entry->storage_array, index, tr_array(eui64, 8));
                entry->modified = true;
                deleted = true;
            }
        }
    }

    if (deleted) {
        // Trigger storing to NVM right away to keep the stored data in sync
        ws_pae_key_storage_timer_expiry_set();
    }

    return deleted;
}

static sec_prot_keys_storage_t *ws_pae_key_storage_replace(const void *instance, key_storage_array_t **key_storage_array)
{
    uint16_t replace_index = key_storage_params.replace_index;
    sec_prot_keys_storage_t *storage_array = NULL;
    uint16_t storage_array_index = 0;

    ns_list_foreach(key_storage_array_t, entry, &key_storage_array_list) {
        // Checks whether storage is for this authenticator
        if (entry->instance != instance) {
            continue;
        }
        // Finds correct key storage array
        if (replace_index >= entry->entries) {
            replace_index -= entry->entries;
            continue;
        }
        if (key_storage_array) {
            *key_storage_array = entry;
        }
        // Sets array and index and sets replace index to next
        storage_array = (sec_prot_keys_storage_t *) entry->storage_array;
        storage_array_index = replace_index;
        key_storage_params.replace_index++;

        tr_info("KeyS replace array: %p i: %i eui64: %s", (void *) entry->storage_array, replace_index, tr_array(storage_array[replace_index].ptk_eui_64, 8));
    }

    // If replace index is not found it means that index has gone past the last entry
    if (storage_array == NULL) {
        /* Gets first key storage array and sets the array and sets index to zero and sets
           (next) replace index to first */
        key_storage_array_t *key_storage_array_entry = ns_list_get_first(&key_storage_array_list);
        if (key_storage_array_entry == NULL) {
            return NULL;
        }
        if (key_storage_array) {
            *key_storage_array = key_storage_array_entry;
        }
        storage_array = (sec_prot_keys_storage_t *) key_storage_array_entry->storage_array;
        storage_array_index = 0;
        key_storage_params.replace_index = 1;

        tr_info("KeyS replace array: %p i: %i eui64: %s", (void *) key_storage_array_entry->storage_array, storage_array_index, tr_array(key_storage_array_entry->storage_array[storage_array_index].ptk_eui_64, 8));
    }

    // Deletes any previous data
    memset(&storage_array[storage_array_index], 0, sizeof(sec_prot_keys_storage_t));

    return &storage_array[storage_array_index];
}

int8_t ws_pae_key_storage_supp_write(const void *instance, supp_entry_t *pae_supp)
{
    uint8_t *eui_64 = pae_supp->addr.eui_64;

    key_storage_array_t *key_storage_array;
    sec_prot_keys_storage_t *key_storage = ws_pae_key_storage_get(instance, eui_64, &key_storage_array, true);

    // If cannot find existing or empty storage and there is room for storages tries to allocate more storage
    if (key_storage == NULL && key_storage_params.storages_empty > 0) {
        if (ws_pae_key_storage_allocate(instance, key_storage_params.storage_default_size, NULL) >= 0) {
            key_storage_params.storages_empty--;
            key_storage = ws_pae_key_storage_get(instance, eui_64, &key_storage_array, true);
        }
    }

    // If still cannot find empty storage, replaces old one
    if (key_storage == NULL) {
        key_storage = ws_pae_key_storage_replace(instance, &key_storage_array);
    }

    // On failure exit
    if (key_storage == NULL) {
        return -1;
    }

    // Calculate time difference between storage array reference time and current time
    uint32_t time_difference;
    if (ws_pae_time_diff_calc(ws_pae_current_time_get(), key_storage_array->storage_array_handle->reference_time, &time_difference, false) < 0) {
        tr_error("KeyS write time err: %"PRIi64", ref: %"PRIi64", diff: %"PRIi32, ws_pae_current_time_get(), key_storage_array->storage_array_handle->reference_time, time_difference);
        return -1;
    }

    sec_prot_keys_t *sec_keys = &pae_supp->sec_keys;

    // If replay counter is near to exhaust delete PMK and PTK to refresh the counter
    if (sec_keys->pmk_key_replay_cnt_set && (sec_keys->pmk_key_replay_cnt & PMK_KEY_REPLAY_CNT_LIMIT_MASK) >= PMK_KEY_REPLAY_CNT_LIMIT) {
        tr_error("KeyS write PMK cnt limit eui64: %s", tr_array(eui_64, 8));
        sec_prot_keys_ptk_delete(&pae_supp->sec_keys);
        sec_prot_keys_pmk_delete(&pae_supp->sec_keys);
    }

    uint16_t field_set = 1 << WRITE_SET;

    if (key_storage->pmk_key_replay_cnt_set != sec_keys->pmk_key_replay_cnt_set ||
            key_storage->pmk_key_replay_cnt != sec_keys->pmk_key_replay_cnt) {
        /* Does not set modified variable since counter updates are never stored to NVM.
           Instead counter is increased on re-start. */
        key_storage->pmk_key_replay_cnt = sec_keys->pmk_key_replay_cnt & PMK_KEY_REPLAY_CNT_LIMIT_MASK;
        key_storage->pmk_key_replay_cnt_set = sec_keys->pmk_key_replay_cnt_set;
        field_set |= 1 << PMK_CNT_SET;
    }

    if (key_storage->pmk_set != sec_keys->pmk_set ||
            memcmp(key_storage->pmk, sec_keys->pmk, PMK_LEN) != 0) {
        key_storage_array->modified = true;
        key_storage->pmk_set = sec_keys->pmk_set;
        memcpy(key_storage->pmk, sec_keys->pmk, PMK_LEN);
        field_set |= 1 << PMK_SET;
    }

    if (key_storage->ptk_set != sec_keys->ptk_set ||
            memcmp(key_storage->ptk, sec_keys->ptk, PTK_LEN) != 0) {
        key_storage_array->modified = true;
        key_storage->ptk_set = sec_keys->ptk_set;
        memcpy(key_storage->ptk, sec_keys->ptk, PTK_LEN);
        field_set |= 1 << PTK_SET;
    }

    if (key_storage->eui_64_set != true ||
            memcmp(key_storage->ptk_eui_64, eui_64, 8) != 0) {
        key_storage_array->modified = true;
        key_storage->eui_64_set = true;
        memcpy(key_storage->ptk_eui_64, eui_64, 8);
        field_set |= 1 << EUI64_SET;
    }

    if (key_storage->ptk_eui_64_set != sec_keys->ptk_eui_64_set) {
        key_storage_array->modified = true;
        key_storage->ptk_eui_64_set = sec_keys->ptk_eui_64_set;
        field_set |= 1 << PTKEUI64_SET;
    }

    if (key_storage->ins_gtk_hash_set != sec_keys->ins_gtk_hash_set ||
            memcmp(key_storage->ins_gtk_hash, sec_keys->ins_gtk_hash, sizeof(sec_keys->ins_gtk_hash)) != 0) {
        key_storage_array->modified = true;
        key_storage->ins_gtk_hash_set = sec_keys->ins_gtk_hash_set;
        memcpy(key_storage->ins_gtk_hash, sec_keys->ins_gtk_hash, sizeof(sec_keys->ins_gtk_hash));
        field_set |= 1 << GTKHASH_SET;
    }
    if (key_storage->ins_gtk_4wh_hash_set != sec_keys->ins_gtk_hash_set) {
        key_storage->ins_gtk_4wh_hash_set = sec_keys->ins_gtk_hash_set;
        field_set |= 1 << GTKHASH4WH_SET;
    }

    if (sec_keys->pmk_set) {
        // PMK lifetime is: time difference between reference and current time + lifetime
        uint64_t pmk_lifetime = time_difference + sec_keys->pmk_lifetime;
        if (pmk_lifetime > SEC_MAXIMUM_LIFETIME) {
            pmk_lifetime = 0;
        }
        uint16_t short_time = ws_pae_time_to_short_convert(pmk_lifetime);
        // Compares the time from active supplicant entry to stored one, and if time
        if (!key_storage->pmk_lifetime_set || !ws_pae_time_from_short_time_compare(key_storage->pmk_lifetime, short_time)) {
            key_storage_array->modified = true;
            key_storage->pmk_lifetime_set = true;
            key_storage->pmk_lifetime = short_time;
            field_set |= 1 << PMKLTIME_SET;
        }
    } else {
        key_storage->pmk_lifetime_set = false;
        key_storage->pmk_lifetime = 0;
        field_set |= 1 << PMKLTIME_SET;
    }

    if (sec_keys->ptk_set) {
        // PTK lifetime is: time difference between reference and current time + lifetime
        uint64_t ptk_lifetime = time_difference + sec_keys->ptk_lifetime;
        if (ptk_lifetime > SEC_MAXIMUM_LIFETIME) {
            ptk_lifetime = 0;
        }
        uint16_t short_time = ws_pae_time_to_short_convert(ptk_lifetime);
        if (!key_storage->ptk_lifetime_set || !ws_pae_time_from_short_time_compare(key_storage->ptk_lifetime, short_time)) {
            key_storage_array->modified = true;
            key_storage->ptk_lifetime_set = true;
            key_storage->ptk_lifetime = short_time;
            field_set |= 1 << PTKLTIME_SET;
        }
    } else {
        key_storage->ptk_lifetime_set = false;
        key_storage->ptk_lifetime = 0;
        field_set |= 1 << PTKLTIME_SET;
    }

    ws_pae_key_storage_trace(field_set, key_storage, key_storage_array);

    return 0;
}

supp_entry_t *ws_pae_key_storage_supp_read(const void *instance, const uint8_t *eui_64, sec_prot_gtk_keys_t *gtks, const sec_prot_certs_t *certs)
{
    key_storage_array_t *key_storage_array;
    sec_prot_keys_storage_t *key_storage = ws_pae_key_storage_get(instance, eui_64, &key_storage_array, false);
    if (key_storage == NULL) {
        return NULL;
    }

    uint8_t pmk_invalid = false;
    uint8_t ptk_invalid = false;

    uint16_t field_set = 0;

    // Calculate time difference between storage array reference time and current time
    uint32_t time_difference;
    if (ws_pae_time_diff_calc(ws_pae_current_time_get(), key_storage_array->storage_array_handle->reference_time, &time_difference, false) < 0) {
        tr_error("KeyS read time err");
        pmk_invalid = true;
        field_set |= 1 << TIME_SET;
    }

    uint32_t pmk_lifetime = 0;
    uint32_t ptk_lifetime = 0;

    if (!pmk_invalid) {
        // Calculate PMK lifetime
        if (ws_pae_key_storage_array_lifetime_get(time_difference, key_storage->pmk_lifetime, &pmk_lifetime) < 0) {
            // PMK expired, all keys are invalid
            pmk_invalid = true;
            ptk_invalid = true;
            field_set |= 1 << TIME_SET;
        }
        field_set |= 1 << PMKLTIME_SET;
    }

    if (!pmk_invalid) {
        // Calculate PTK lifetime
        if (ws_pae_key_storage_array_lifetime_get(time_difference, key_storage->ptk_lifetime, &ptk_lifetime) < 0) {
            // PMK expired, invalidate PTK related fields, PMK is still valid
            ptk_invalid = true;
            field_set |= 1 << TIME_SET;
        }
        field_set |= 1 << PTKLTIME_SET;
    }

    supp_entry_t *pae_supp = ns_dyn_mem_temporary_alloc(sizeof(supp_entry_t));
    if (!pae_supp) {
        return NULL;
    }
    ws_pae_lib_supp_init(pae_supp);

    // Adds relay address data
    kmp_address_init(KMP_ADDR_EUI_64_AND_IP, &pae_supp->addr, key_storage->ptk_eui_64);

    sec_prot_keys_t *sec_keys = &pae_supp->sec_keys;
    sec_prot_keys_init(sec_keys, gtks, certs);

    // PMK is invalid, do not retrieve any security key data
    if (pmk_invalid) {
        ws_pae_key_storage_trace(field_set, key_storage, key_storage_array);
        return pae_supp;
    }

    // Set PMK security key data
    if (key_storage->pmk_key_replay_cnt_set) {
        // LSB 16 bits is the replay counter
        sec_keys->pmk_key_replay_cnt = key_storage->pmk_key_replay_cnt;

        uint32_t restart_cnt_diff;
        if (key_storage_params.restart_cnt >= key_storage_array->storage_array_handle->reference_restart_cnt) {
            restart_cnt_diff = key_storage_params.restart_cnt - key_storage_array->storage_array_handle->reference_restart_cnt;
        } else {
            restart_cnt_diff = key_storage_params.restart_cnt;
        }
        // MSB 32 bits is the restart count
        sec_keys->pmk_key_replay_cnt |= ((uint64_t) restart_cnt_diff) << 32;
        field_set |= 1 << PMK_CNT_SET;
    } else {
        sec_keys->pmk_key_replay_cnt = 0;
    }
    sec_keys->pmk_key_replay_cnt_set = key_storage->pmk_key_replay_cnt_set;

    memcpy(sec_keys->pmk, key_storage->pmk, PMK_LEN);
    sec_keys->pmk_set = key_storage->pmk_set;

    field_set |= ((uint16_t)sec_keys->pmk_set) << PMK_SET;

    sec_keys->pmk_lifetime = pmk_lifetime;

    field_set |= 1 << PMKLTIME_SET;

    // PTK is invalid, do not retrieve any PTK security key data
    if (ptk_invalid) {
        ws_pae_key_storage_trace(field_set, key_storage, key_storage_array);
        return pae_supp;
    }

    // Set PTK security key data
    memcpy(sec_keys->ptk, key_storage->ptk, PTK_LEN);
    sec_keys->ptk_set = key_storage->ptk_set;

    field_set |= ((uint16_t)sec_keys->ptk_set) << PTK_SET;

    memcpy(sec_keys->ptk_eui_64, key_storage->ptk_eui_64, 8);
    sec_keys->ptk_eui_64_set = key_storage->ptk_eui_64_set;

    field_set |= ((uint16_t) sec_keys->ptk_eui_64_set) << PTKEUI64_SET;

    memcpy(sec_keys->ins_gtk_hash, key_storage->ins_gtk_hash, sizeof(sec_keys->ins_gtk_hash));
    sec_keys->ins_gtk_hash_set = key_storage->ins_gtk_hash_set;
    sec_keys->ins_gtk_4wh_hash_set = sec_keys->ins_gtk_hash_set;

    field_set |= ((uint16_t) sec_keys->ins_gtk_hash_set) << GTKHASH_SET;
    field_set |= ((uint16_t) sec_keys->ins_gtk_4wh_hash_set) << GTKHASH4WH_SET;

    sec_keys->ptk_lifetime = ptk_lifetime;

    field_set |= 1 << PTKLTIME_SET;

    ws_pae_key_storage_trace(field_set, key_storage, key_storage_array);

    return pae_supp;
}

static void ws_pae_key_storage_trace(uint16_t field_set, sec_prot_keys_storage_t *key_storage, key_storage_array_t *key_storage_array)
{
    tr_info("KeyS %s %s%"PRIi64" %"PRIi64" %s%s%i %s%s%s%s%s %s%s%i %i %s%i %i",
            FIELD_IS_SET(WRITE_SET) ? "write" : "read",
            FIELD_IS_SET(TIME_SET) ? "TIME " : "", FIELD_IS_SET(TIME_SET) ? ws_pae_current_time_get() : 0, FIELD_IS_SET(TIME_SET) ? key_storage_array->storage_array_handle->reference_time : 0,
            FIELD_IS_SET(PMK_SET) ? "PMK " : "",
            FIELD_IS_SET(PMK_CNT_SET) ? "PMK CNT " : "", key_storage->pmk_key_replay_cnt,
            FIELD_IS_SET(PTK_SET) ? "PTK " : "",
            FIELD_IS_SET(EUI64_SET) ? "EUI64 " : "",
            FIELD_IS_SET(PTKEUI64_SET) ? "PTKEUI64 " : "",
            FIELD_IS_SET(GTKHASH_SET) ? "GTKHASH " : "", tr_array((uint8_t *)key_storage->ins_gtk_hash, 8),
            FIELD_IS_SET(GTKHASH4WH_SET) ? "GTKHASH4WH " : "",
            FIELD_IS_SET(PMKLTIME_SET) ? "PMKLTIME " : "", STIME_TIME_GET(key_storage->pmk_lifetime), STIME_FORMAT_GET(key_storage->pmk_lifetime),
            FIELD_IS_SET(PTKLTIME_SET) ? "PTKLTIME " : "", STIME_TIME_GET(key_storage->ptk_lifetime), STIME_FORMAT_GET(key_storage->ptk_lifetime)
           );
}

int8_t ws_pae_key_storage_store(void)
{
    uint8_t entry_offset = 0;
    uint64_t store_bitfield = 0;

    ns_list_foreach(key_storage_array_t, entry, &key_storage_array_list) {
        // Bitfield is set for all entries (also that are non-modified in this write)
        store_bitfield |= ((uint64_t) 1) << entry_offset;

        /* Checks whether array reference time needs to be updated */
        int8_t ret_value = ws_pae_key_storage_array_time_check_and_update_all(entry, entry->modified);
        if (ret_value == 1) {
            entry->modified = true;
        } else if (ret_value < 0) {
            // On error clears the whole array
            ws_pae_key_storage_clear(entry);
            entry->modified = true;
        }

        /* On large network there could be different time thresholds for entries full / all updated
           and half empty entries/not all updated where it is likely that data will still be modified */
        if (!entry->modified) {
            entry_offset++;
            // If not pending for storing; skips file write
            continue;
        }

        char filename[KEY_STORAGE_FILE_LEN];
        ws_pae_key_storage_filename_set(filename, entry_offset);
        tr_info("KeyS write array: %p file: %s", (void *) entry->storage_array, filename);
        nvm_tlv_t *tlv = (nvm_tlv_t *) entry->storage_array_handle;
        ws_pae_nvm_store_tlv_file_write(filename, tlv);

        // No longer pending for storing
        entry->modified = false;
        entry_offset++;
    }

    if (key_storage_params.store_bitfield != store_bitfield) {
        key_storage_params.store_bitfield = store_bitfield;
        nvm_tlv_t *tlv = ws_pae_nvm_store_generic_tlv_allocate_and_create(
                             PAE_NVM_KEY_STORAGE_INDEX_TAG, PAE_NVM_KEY_STORAGE_INDEX_LEN);
        ws_pae_nvm_store_key_storage_index_tlv_create(tlv, key_storage_params.store_bitfield);
        ws_pae_nvm_store_tlv_file_write(KEY_STORAGE_INDEX_FILE, tlv);
        ws_pae_nvm_store_generic_tlv_free(tlv);
    }

    return 0;
}

void ws_pae_key_storage_read(uint32_t restart_cnt)
{
    key_storage_params.store_bitfield = 0;
    key_storage_params.restart_cnt = restart_cnt;

    nvm_tlv_t *tlv = ws_pae_nvm_store_generic_tlv_allocate_and_create(
                         PAE_NVM_KEY_STORAGE_INDEX_TAG, PAE_NVM_KEY_STORAGE_INDEX_LEN);

    if (ws_pae_nvm_store_tlv_file_read(KEY_STORAGE_INDEX_FILE, tlv) < 0) {
        ws_pae_nvm_store_generic_tlv_free(tlv);
        return;
    }

    uint64_t store_bitfield;
    if (ws_pae_nvm_store_key_storage_index_tlv_read(tlv, &store_bitfield) >= 0) {
        key_storage_params.store_bitfield = store_bitfield;
    }

    ws_pae_nvm_store_generic_tlv_free(tlv);

    if (key_storage_params.store_bitfield == 0) {
        return;
    }

    tr_info("KeyS init store bitf: %"PRIx64, store_bitfield);

    key_storage_array_t *key_storage_array = ns_list_get_first(&key_storage_array_list);
    key_storage_array_t *key_storage_array_prev = NULL;

    for (uint8_t entry_offset = 0; entry_offset < 64; entry_offset++) {
        // There are no more fields
        if (store_bitfield == 0) {
            break;
        }

        if (key_storage_array == NULL && key_storage_params.storages_empty > 0) {
            if (ws_pae_key_storage_allocate(NULL, key_storage_params.storage_default_size, NULL) >= 0) {
                key_storage_params.storages_empty--;
            }
        }

        if (key_storage_array_prev != NULL) {
            key_storage_array = ns_list_get_next(&key_storage_array_list, key_storage_array_prev);
        } else if (key_storage_array == NULL) {
            key_storage_array = ns_list_get_first(&key_storage_array_list);
        }

        if (key_storage_array == NULL) {
            break;
        }
        key_storage_array_prev = key_storage_array;

        // If set on bitfield read
        if ((store_bitfield & (((uint64_t) 1) << entry_offset)) == 0) {
            continue;
        }
        store_bitfield &= ~(((uint64_t) 1) << entry_offset);

        tlv = (nvm_tlv_t *) key_storage_array->storage_array_handle;
        ws_pae_nvm_store_key_storage_tlv_create(tlv, key_storage_array->size);

        char filename[KEY_STORAGE_FILE_LEN];
        ws_pae_key_storage_filename_set(filename, entry_offset);

        tr_info("KeyS init read array: %p file: %s", (void *) key_storage_array->storage_array, filename);
        if (ws_pae_nvm_store_tlv_file_read(filename, tlv) < 0) {
            ws_pae_key_storage_clear(key_storage_array);
            // On error, re-use current one
            key_storage_array_prev = NULL;
            continue;
        }

        bool read_error = false;
        if (ws_pae_nvm_store_key_storage_tlv_read(tlv, key_storage_array->size) < 0) {
            ws_pae_key_storage_clear(key_storage_array);
            read_error = true;
        }

        if (read_error) {
            // On error, re-use current one
            key_storage_array_prev = NULL;
            continue;
        }

        // Calculate time difference between storage array reference time and current time
        uint32_t time_difference;
        if (ws_pae_time_diff_calc(ws_pae_current_time_get(), key_storage_array->storage_array_handle->reference_time, &time_difference, false) < 0) {
            tr_error("KeyS read array time err: %"PRIi64", ref: %"PRIi64", diff: %"PRIi32, ws_pae_current_time_get(), key_storage_array->storage_array_handle->reference_time, time_difference);
            ws_pae_key_storage_clear(key_storage_array);
        }

        // Checks and updates PMK counters
        if (ws_pae_key_storage_array_counters_check_and_update_all(key_storage_array) < 0) {
            tr_error("KeyS read array cnt err");
            // On error clears the whole array
            ws_pae_key_storage_clear(key_storage_array);
        }

        // Entry set, go to next
        key_storage_array = NULL;
    }
}

void ws_pae_key_storage_remove(void)
{
    nvm_tlv_t *tlv = ws_pae_nvm_store_generic_tlv_allocate_and_create(
                         PAE_NVM_KEY_STORAGE_INDEX_TAG, PAE_NVM_KEY_STORAGE_INDEX_LEN);

    uint64_t store_bitfield = 0;
    if (ws_pae_nvm_store_tlv_file_read(KEY_STORAGE_INDEX_FILE, tlv) >= 0) {
        ws_pae_nvm_store_key_storage_index_tlv_read(tlv, &store_bitfield);
    }
    ws_pae_nvm_store_generic_tlv_free(tlv);

    ws_pae_nvm_store_tlv_file_remove(KEY_STORAGE_INDEX_FILE);

    tr_info("KeyS remove store bitf: %"PRIx64, store_bitfield);

    if (store_bitfield == 0) {
        return;
    }

    for (uint8_t entry_offset = 0; entry_offset < 64; entry_offset++) {
        // If set on bitfield delete
        if ((store_bitfield & (((uint64_t) 1) << entry_offset)) == 0) {
            continue;
        }

        char filename[KEY_STORAGE_FILE_LEN];
        ws_pae_key_storage_filename_set(filename, entry_offset);

        tr_info("KeyS remove file: %s", filename);
        ws_pae_nvm_store_tlv_file_remove(filename);
    }
}

void ws_pae_key_storage_timer(uint16_t seconds)
{
    if (key_storage_params.store_timer > seconds) {
        key_storage_params.store_timer -= seconds;
    } else {
        key_storage_params.store_timer = key_storage_params.store_timer_timeout;
        ws_pae_key_storage_store();
    }

    ws_pae_current_time_update(seconds);
}

static void ws_pae_key_storage_timer_expiry_set(void)
{
    // Expire in 30 seconds
    key_storage_params.store_timer = 30;
}

uint16_t ws_pae_key_storage_storing_interval_get(void)
{
    return key_storage_params.store_timer_timeout;
}

static int8_t ws_pae_key_storage_array_time_update_entry(uint64_t time_difference, sec_prot_keys_storage_t *storage_array_entry)
{
    if (storage_array_entry->pmk_lifetime_set) {
#ifdef EXTRA_DEBUG_INFO
        tr_debug("KeyS time update diff: %"PRIi64" PMK OLD t: %i %i eui64: %s", time_difference, STIME_TIME_GET(storage_array_entry->pmk_lifetime), STIME_FORMAT_GET(storage_array_entry->pmk_lifetime), tr_array(storage_array_entry->ptk_eui_64, 8));
#endif
        // Calculate new PMK lifetime
        if (ws_pae_key_storage_array_lifetime_update(time_difference, &storage_array_entry->pmk_lifetime) < 0) {
            tr_info("KeyS time update PMK expired diff: %"PRIi64" t: %i %i eui64: %s", time_difference, STIME_TIME_GET(storage_array_entry->pmk_lifetime),  STIME_FORMAT_GET(storage_array_entry->pmk_lifetime), tr_array(storage_array_entry->ptk_eui_64, 8));
            // PMK expired, whole entry is invalid
            ws_pae_key_storage_array_pmk_invalid(storage_array_entry);
            return -1;
        }
#ifdef EXTRA_DEBUG_INFO
        tr_debug("KeyS time update PMK NEW t: %i %i", STIME_TIME_GET(storage_array_entry->pmk_lifetime), STIME_FORMAT_GET(storage_array_entry->pmk_lifetime));
#endif
    }

    if (storage_array_entry->pmk_lifetime_set) {
#ifdef EXTRA_DEBUG_INFO
        tr_debug("KeyS time update diff: %"PRIi64" PTK OLD t: %i %i eui64: %s", time_difference, STIME_TIME_GET(storage_array_entry->ptk_lifetime), STIME_FORMAT_GET(storage_array_entry->ptk_lifetime), tr_array(storage_array_entry->ptk_eui_64, 8));
#endif
        // Calculate new PTK lifetime
        if (ws_pae_key_storage_array_lifetime_update(time_difference, &storage_array_entry->ptk_lifetime) < 0) {
            tr_info("KeyS time update PTK expired diff: %"PRIi64" t: %i %i eui64: %s", time_difference, STIME_TIME_GET(storage_array_entry->ptk_lifetime), STIME_FORMAT_GET(storage_array_entry->ptk_lifetime), tr_array(storage_array_entry->ptk_eui_64, 8));
            // PTK is invalid, invalidate PTK related fields
            ws_pae_key_storage_array_ptk_invalid(storage_array_entry);
            // PMK is still valid
            return 0;
        }
#ifdef EXTRA_DEBUG_INFO
        tr_debug("KeyS time update PTK NEW t: %i %i", STIME_TIME_GET(storage_array_entry->ptk_lifetime), STIME_FORMAT_GET(storage_array_entry->ptk_lifetime));
#endif
    }
    return 0;
}

static int8_t ws_pae_key_storage_array_time_check_and_update_all(key_storage_array_t *key_storage_array, bool modified)
{
    uint64_t reference_time = key_storage_array->storage_array_handle->reference_time;
    uint64_t current_time = ws_pae_current_time_get();

    uint32_t time_difference;
    if (ws_pae_time_diff_calc(current_time, reference_time, &time_difference, false) < 0) {
        tr_error("KeyS array time all err: %"PRIi64", ref: %"PRIi64", diff: %"PRIi32, ws_pae_current_time_get(), reference_time, time_difference);
        return -1;
    }

    if (modified) {
        // Updates once a day on write
        if (time_difference < KEY_STORAGE_REF_TIME_UPDATE_THRESHOLD) {
            return 0;
        }
    } else if (time_difference < KEY_STORAGE_REF_TIME_UPDATE_FORCE_THRESHOLD) {
        // Or every 31 days also when no other writes triggered
        return 0;
    }

    // Checks entries in storage array
    sec_prot_keys_storage_t *storage_array = (sec_prot_keys_storage_t *) key_storage_array->storage_array;
    for (uint16_t index = 0; index < key_storage_array->entries; index++) {
        if (!storage_array[index].eui_64_set) {
            continue;
        }
        // Updates lifetimes on the entry
        ws_pae_key_storage_array_time_update_entry(time_difference, &storage_array[index]);
    }

    // Entries are now on current time; update reference time
    key_storage_array->storage_array_handle->reference_time = current_time;
    return 1;
}

static int8_t ws_pae_key_storage_array_counters_check_and_update_all(key_storage_array_t *key_storage_array)
{
    // Checks entries in storage array
    sec_prot_keys_storage_t *storage_array = (sec_prot_keys_storage_t *) key_storage_array->storage_array;
    for (uint16_t index = 0; index < key_storage_array->entries; index++) {
        if (!storage_array[index].eui_64_set) {
            continue;
        }

        // GTK 4WH hash information is not stored to NVM and might be obsolete (changing it does not trigger NVM write)
        storage_array[index].ins_gtk_4wh_hash_set = 0;

        if (!storage_array[index].pmk_key_replay_cnt_set) {
            continue;
        }

        // Sanity check for replay counter
        if (storage_array[index].pmk_key_replay_cnt >= PMK_KEY_REPLAY_CNT_LIMIT) {
            ws_pae_key_storage_array_pmk_invalid(&storage_array[index]);
            ws_pae_key_storage_array_ptk_invalid(&storage_array[index]);
            continue;
        }

        /* Resets replay counter (lower part). When generating 64bit replay counter used on EAPOL,
           re-start count is set to replay counter MSB 32bits. So the lower part of the counter
           is always fresh after re-start. Thus, each power cycle of device has LSB 32bits of replay
           counter space to use. In practice uses only LSB 16bits since counter is limited to 60000,
           before generating new PMK. */
        storage_array[index].pmk_key_replay_cnt = 0;
    }

    return 0;
}

static int8_t ws_pae_key_storage_array_lifetime_update(uint32_t time_difference, uint16_t *lifetime)
{
    uint32_t entry_lifetime = ws_pae_time_from_short_convert(*lifetime);
    *lifetime = 0;

    // If lifetime has expired, return failure
    if (time_difference >= entry_lifetime) {
        return -1;
    }
    entry_lifetime -= time_difference;
    *lifetime = ws_pae_time_to_short_convert(entry_lifetime);
    // If conversion results zero lifetime return failure
    if (*lifetime == 0) {
        return -1;
    }

    // Lifetime is valid
    return 0;
}

static int8_t ws_pae_key_storage_array_lifetime_get(uint32_t time_difference, uint16_t short_lifetime, uint32_t *lifetime)
{
    *lifetime = ws_pae_time_from_short_convert(short_lifetime);

    // If lifetime has expired, return failure
    if (time_difference >= *lifetime) {
        return -1;
    }
    *lifetime -= time_difference;

    return 0;
}

static void ws_pae_key_storage_array_pmk_invalid(sec_prot_keys_storage_t *storage_array)
{
    memset(storage_array, 0, sizeof(sec_prot_keys_storage_t));
}

static void ws_pae_key_storage_array_ptk_invalid(sec_prot_keys_storage_t *storage_array)
{
    memset(storage_array->ptk, 0, PTK_LEN);
    storage_array->ptk_set = false;
    storage_array->ptk_eui_64_set = false;
    memset(storage_array->ins_gtk_hash, 0, sizeof(storage_array->ins_gtk_hash));
    storage_array->ins_gtk_hash_set = false;
    storage_array->ptk_lifetime = 0;
}

#endif /* HAVE_WS */

