text: /*
   * Copyright (c) 2016-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 "NWK_INTERFACE/Include/protocol.h"
  #include "Common_Protocols/ipv6_constants.h"
  #include "socket_api.h"
  #include "6LoWPAN/ws/ws_config.h"
  #include "Security/kmp/kmp_addr.h"
  #include "Security/kmp/kmp_api.h"
  #include "Security/PANA/pana_eap_header.h"
  #include "Security/eapol/eapol_helper.h"
  #include "Security/protocols/sec_prot_certs.h"
  #include "Security/protocols/sec_prot_keys.h"
  #include "Security/protocols/sec_prot.h"
  #include "Security/protocols/sec_prot_lib.h"
  
  #ifdef HAVE_WS
  
  #define TRACE_GROUP "spke"
  
  static const uint8_t empty_hash[GTK_HASH_LEN] = {0};
  
  sec_prot_keys_t *sec_prot_keys_create(sec_prot_gtk_keys_t *gtks, const sec_prot_certs_t *certs)
  {
      sec_prot_keys_t *sec_keys = ns_dyn_mem_alloc(sizeof(sec_prot_keys_t));
      if (!sec_keys) {
          return NULL;
      }
  
      sec_prot_keys_init(sec_keys, gtks, certs);
  
      return sec_keys;
  }
  
  void sec_prot_keys_init(sec_prot_keys_t *sec_keys, sec_prot_gtk_keys_t *gtks, const sec_prot_certs_t *certs)
  {
      memset(sec_keys, 0, sizeof(sec_prot_keys_t));
      sec_keys->pmk_lifetime = PMK_LIFETIME_INSTALL;
      sec_keys->ptk_lifetime = PTK_LIFETIME_INSTALL;
      sec_keys->pmk_key_replay_cnt = 0;
      sec_keys->gtks = gtks;
      sec_keys->certs = certs;
      sec_keys->gtkl = 0;
      sec_keys->gtk_set_index = -1;
      sec_keys->pmk_set = false;
      sec_keys->ptk_set = false;
      sec_keys->pmk_key_replay_cnt_set = false;
      sec_keys->updated = false;
      sec_keys->ptk_eui_64_set = false;
      sec_keys->pmk_mismatch = false;
      sec_keys->ptk_mismatch = false;
      sec_prot_keys_ptk_installed_gtk_hash_clear_all(sec_keys);
  }
  
  void sec_prot_keys_delete(sec_prot_keys_t *sec_keys)
  {
      ns_dyn_mem_free(sec_keys);
  }
  
  sec_prot_gtk_keys_t *sec_prot_keys_gtks_create(void)
  {
      sec_prot_gtk_keys_t *gtks = ns_dyn_mem_alloc(sizeof(sec_prot_gtk_keys_t));
      if (!gtks) {
          return NULL;
      }
  
      sec_prot_keys_gtks_init(gtks);
  
      return gtks;
  }
  
  void sec_prot_keys_gtks_init(sec_prot_gtk_keys_t *gtks)
  {
      memset(gtks, 0, sizeof(sec_prot_gtk_keys_t));
      gtks->updated = false;
  }
  
  void sec_prot_keys_gtks_delete(sec_prot_gtk_keys_t *gtks)
  {
      ns_dyn_mem_free(gtks);
  }
  
  void sec_prot_keys_pmk_write(sec_prot_keys_t *sec_keys, uint8_t *pmk)
  {
      memcpy(sec_keys->pmk, pmk, PMK_LEN);
      sec_keys->pmk_key_replay_cnt = 0;
      sec_keys->pmk_key_replay_cnt_set = false;
      sec_keys->pmk_lifetime = PMK_LIFETIME_INSTALL;
      sec_keys->pmk_set = true;
      sec_keys->updated = true;
  }
  
  void sec_prot_keys_pmk_delete(sec_prot_keys_t *sec_keys)
  {
      memset(sec_keys->pmk, 0, PMK_LEN);
      sec_keys->pmk_key_replay_cnt = 0;
      sec_keys->pmk_key_replay_cnt_set = false;
      sec_keys->pmk_lifetime = PMK_LIFETIME_INSTALL;
      sec_keys->pmk_set = false;
      sec_keys->updated = true;
  }
  
  uint8_t *sec_prot_keys_pmk_get(sec_prot_keys_t *sec_keys)
  {
      if (!sec_keys->pmk_set) {
          return NULL;
      }
  
      return sec_keys->pmk;
  }
  
  uint64_t sec_prot_keys_pmk_replay_cnt_get(sec_prot_keys_t *sec_keys)
  {
      return sec_keys->pmk_key_replay_cnt;
  }
  
  void sec_prot_keys_pmk_replay_cnt_set(sec_prot_keys_t *sec_keys, uint64_t counter)
  {
      sec_keys->pmk_key_replay_cnt_set = true;
      sec_keys->pmk_key_replay_cnt = counter;
  }
  
  void sec_prot_keys_pmk_replay_cnt_increment(sec_prot_keys_t *sec_keys)
  {
      // Start from zero i.e. does not increment on first call
      if (!sec_keys->pmk_key_replay_cnt_set) {
          sec_keys->pmk_key_replay_cnt_set = true;
          return;
      }
      sec_keys->pmk_key_replay_cnt++;
  }
  
  bool sec_prot_keys_pmk_replay_cnt_compare(uint64_t received_counter, sec_prot_keys_t *sec_keys)
  {
      // If previous value is set must be greater
      if (sec_keys->pmk_key_replay_cnt_set && received_counter > sec_keys->pmk_key_replay_cnt) {
          return true;
      } else if (!sec_keys->pmk_key_replay_cnt_set && received_counter >= sec_keys->pmk_key_replay_cnt) {
          // Otherwise allows also same value e.g. zero
          return true;
      }
  
      return false;
  }
  
  void sec_prot_keys_pmk_mismatch_set(sec_prot_keys_t *sec_keys)
  {
      sec_keys->pmk_mismatch = true;
  }
  
  void sec_prot_keys_pmk_mismatch_reset(sec_prot_keys_t *sec_keys)
  {
      sec_keys->pmk_mismatch = false;
  }
  
  bool sec_prot_keys_pmk_mismatch_is_set(sec_prot_keys_t *sec_keys)
  {
      return sec_keys->pmk_mismatch;
  }
  
  bool sec_prot_keys_pmk_lifetime_decrement(sec_prot_keys_t *sec_keys, uint32_t default_lifetime, uint8_t seconds)
  {
      if (!sec_keys->pmk_set) {
          return false;
      }
  
      if (sec_keys->pmk_lifetime == PMK_LIFETIME_INSTALL) {
          sec_keys->pmk_lifetime = default_lifetime;
      }
  
      if (sec_keys->pmk_lifetime > seconds) {
          sec_keys->pmk_lifetime -= seconds;
      } else {
          if (sec_keys->pmk_lifetime > 0) {
              sec_keys->pmk_lifetime = 0;
              sec_prot_keys_ptk_delete(sec_keys);
              sec_prot_keys_pmk_delete(sec_keys);
              return true;
          }
      }
      return false;
  }
  
  void sec_prot_keys_ptk_write(sec_prot_keys_t *sec_keys, uint8_t *ptk)
  {
      memcpy(sec_keys->ptk, ptk, PTK_LEN);
      sec_keys->ptk_lifetime = PTK_LIFETIME_INSTALL;
      sec_keys->ptk_set = true;
      sec_keys->updated = true;
  }
  
  void sec_prot_keys_ptk_delete(sec_prot_keys_t *sec_keys)
  {
      memset(sec_keys->ptk, 0, PTK_LEN);
      sec_keys->ptk_lifetime = PTK_LIFETIME_INSTALL;
      sec_keys->ptk_set = false;
      sec_keys->updated = true;
  }
  
  uint8_t *sec_prot_keys_ptk_get(sec_prot_keys_t *sec_keys)
  {
      if (!sec_keys->ptk_set) {
          return NULL;
      }
  
      return sec_keys->ptk;
  }
  
  void sec_prot_keys_ptk_mismatch_set(sec_prot_keys_t *sec_keys)
  {
      sec_keys->ptk_mismatch = true;
  }
  
  void sec_prot_keys_ptk_mismatch_reset(sec_prot_keys_t *sec_keys)
  {
      sec_keys->ptk_mismatch = false;
  }
  
  bool sec_prot_keys_ptk_mismatch_is_set(sec_prot_keys_t *sec_keys)
  {
      return sec_keys->ptk_mismatch;
  }
  
  void sec_prot_keys_ptk_eui_64_write(sec_prot_keys_t *sec_keys, const uint8_t *eui_64)
  {
      memcpy(sec_keys->ptk_eui_64, eui_64, 8);
      sec_keys->ptk_eui_64_set = true;
      sec_keys->updated = true;
  }
  
  uint8_t *sec_prot_keys_ptk_eui_64_get(sec_prot_keys_t *sec_keys)
  {
      if (!sec_keys->ptk_eui_64_set) {
          return NULL;
      }
  
      return sec_keys->ptk_eui_64;
  }
  
  void sec_prot_keys_ptk_eui_64_delete(sec_prot_keys_t *sec_keys)
  {
      memset(sec_keys->ptk_eui_64, 0, 8);
      sec_keys->ptk_eui_64_set = false;
      sec_keys->updated = true;
  }
  
  bool sec_prot_keys_ptk_lifetime_decrement(sec_prot_keys_t *sec_keys, uint32_t default_lifetime, uint8_t seconds)
  {
      if (!sec_keys->ptk_set) {
          return false;
      }
  
      if (sec_keys->ptk_lifetime == PTK_LIFETIME_INSTALL) {
          sec_keys->ptk_lifetime = default_lifetime;
      }
  
      if (sec_keys->ptk_lifetime > seconds) {
          sec_keys->ptk_lifetime -= seconds;
      } else {
          if (sec_keys->ptk_lifetime > 0) {
              sec_prot_keys_ptk_delete(sec_keys);
              sec_keys->ptk_lifetime = 0;
              return true;
          }
      }
      return false;
  }
  
  bool sec_prot_keys_are_updated(sec_prot_keys_t *sec_keys)
  {
      return sec_keys->updated;
  }
  
  void sec_prot_keys_updated_reset(sec_prot_keys_t *sec_keys)
  {
      sec_keys->updated = false;
  }
  
  uint8_t sec_prot_keys_fresh_gtkl_get(sec_prot_gtk_keys_t *gtks)
  {
      uint8_t gtkl = 0;
  
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_status_is_live(gtks, i)) {
              gtkl |= 1 << i;
          }
      }
  
      return gtkl;
  }
  
  void sec_prot_keys_gtkl_set(sec_prot_keys_t *sec_keys, uint8_t gtkl)
  {
      sec_keys->gtkl = gtkl;
  }
  
  bool sec_prot_keys_gtkl_gtk_is_live(sec_prot_keys_t *sec_keys, uint8_t index)
  {
      if (index >= GTK_NUM) {
          return false;
      }
  
      if (sec_keys->gtkl & (1 << index)) {
          return true;
      }
  
      return false;
  }
  
  int8_t sec_prot_keys_gtkl_gtk_live_set(sec_prot_keys_t *sec_keys, uint8_t index)
  {
      if (index >= GTK_NUM) {
          return -1;
      }
  
      sec_keys->gtkl |= (1 << index);
  
      return 0;
  }
  
  int8_t sec_prot_keys_gtk_insert_index_set(sec_prot_keys_t *sec_keys, uint8_t index)
  {
      if (index >= GTK_NUM || !sec_keys->gtks->gtk[index].set) {
          return -1;
      }
  
      sec_keys->gtk_set_index = index;
      return 0;
  }
  
  int8_t sec_prot_keys_gtk_insert_index_get(sec_prot_keys_t *sec_keys)
  {
      return sec_keys->gtk_set_index;
  }
  
  void sec_prot_keys_gtk_insert_index_clear(sec_prot_keys_t *sec_keys)
  {
      sec_keys->gtk_set_index = -1;
  }
  
  void sec_prot_keys_gtkl_from_gtk_insert_index_set(sec_prot_keys_t *sec_keys)
  {
      if (sec_keys->gtk_set_index >= 0) {
          sec_prot_keys_gtkl_gtk_live_set(sec_keys, sec_keys->gtk_set_index);
          sec_prot_keys_gtk_insert_index_clear(sec_keys);
      }
  }
  
  int8_t sec_prot_keys_gtk_insert_index_from_gtkl_get(sec_prot_keys_t *sec_keys)
  {
      // Get currently active key index
      int8_t active_index = sec_prot_keys_gtk_status_active_get(sec_keys->gtks);
  
      if (active_index >= 0 && !sec_prot_keys_gtkl_gtk_is_live(sec_keys, active_index)) {
          // If currently active key is not live on remote, inserts it
          sec_prot_keys_gtk_insert_index_set(sec_keys, active_index);
          return active_index;
      }
  
      // Checks all keys
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_status_is_live(sec_keys->gtks, i)) {
              // If key is live, but not indicated on GTKL inserts it
              if (!sec_prot_keys_gtkl_gtk_is_live(sec_keys, i)) {
                  sec_prot_keys_gtk_insert_index_set(sec_keys, i);
                  return i;
              }
          }
      }
  
      return -1;
  }
  
  uint8_t *sec_prot_keys_get_gtk_to_insert(sec_prot_keys_t *sec_keys, uint8_t *index)
  {
      if (sec_keys->gtk_set_index >= 0 && sec_keys->gtks->gtk[sec_keys->gtk_set_index].set) {
          *index = sec_keys->gtk_set_index;
          return sec_keys->gtks->gtk[sec_keys->gtk_set_index].key;
      } else {
          return NULL;
      }
  }
  
  int8_t sec_prot_keys_gtk_set(sec_prot_gtk_keys_t *gtks, uint8_t index, uint8_t *gtk, uint32_t lifetime)
  {
      if (!gtk || index >= GTK_NUM) {
          return -1;
      }
  
      // If same GTK is given again, do not update
      if (gtks->gtk[index].set && memcmp(gtks->gtk[index].key, gtk, GTK_LEN) == 0) {
          return -1;
      }
  
      sec_prot_keys_gtk_clear(gtks, index);
      uint8_t install_order = sec_prot_keys_gtk_install_order_last_get(gtks);
  
      gtks->gtk[index].set = true;
      gtks->gtk[index].lifetime = lifetime;
      gtks->gtk[index].status = GTK_STATUS_NEW;
      gtks->gtk[index].install_order = install_order;
      memcpy(gtks->gtk[index].key, gtk, GTK_LEN);
  
      gtks->updated = true;
  
      return 0;
  }
  
  int8_t sec_prot_keys_gtk_clear(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (!gtks || index >= GTK_NUM) {
          return -1;
      }
  
      gtks->gtk[index].set = false;
      gtks->gtk[index].lifetime = 0;   // Should be provided by authenticator
      gtks->gtk[index].status = GTK_STATUS_NEW;
      memset(gtks->gtk[index].key, 0, GTK_LEN);
  
      sec_prot_keys_gtk_install_order_update(gtks);
  
      return 0;
  }
  
  bool sec_prot_keys_gtk_is_set(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return false;
      }
  
      return true;
  }
  
  uint8_t *sec_prot_keys_gtk_get(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return NULL;
      }
  
      return gtks->gtk[index].key;
  }
  
  uint32_t sec_prot_keys_gtk_lifetime_get(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return 0;
      }
  
      return gtks->gtk[index].lifetime;
  }
  
  uint32_t sec_prot_keys_gtk_lifetime_decrement(sec_prot_gtk_keys_t *gtks, uint8_t index, uint16_t seconds)
  {
      if (gtks->gtk[index].lifetime > seconds) {
          gtks->gtk[index].lifetime -= seconds;
      } else {
          gtks->gtk[index].lifetime = 0;
      }
  
      return gtks->gtk[index].lifetime;
  }
  
  bool sec_prot_keys_gtks_are_updated(sec_prot_gtk_keys_t *gtks)
  {
      return gtks->updated;
  }
  
  void sec_prot_keys_gtks_updated_set(sec_prot_gtk_keys_t *gtks)
  {
      gtks->updated = true;
  }
  
  void sec_prot_keys_gtks_updated_reset(sec_prot_gtk_keys_t *gtks)
  {
      gtks->updated = false;
  }
  
  void sec_prot_keys_gtk_status_fresh_set(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return;
      }
  
      // Active key remains as active, old keys are never reused
      if (gtks->gtk[index].status < GTK_STATUS_FRESH) {
          gtks->gtk[index].status = GTK_STATUS_FRESH;
      }
  }
  
  void sec_prot_keys_gtk_status_all_fresh_set(sec_prot_gtk_keys_t *gtks)
  {
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          sec_prot_keys_gtk_status_fresh_set(gtks, i);
      }
  }
  
  int8_t sec_prot_keys_gtk_status_active_set(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return -1;
      }
  
      // If key is valid to be taken into use sets it active
      if (gtks->gtk[index].status == GTK_STATUS_FRESH) {
          // Sets previously active key old
          for (uint8_t i = 0; i < GTK_NUM; i++) {
              // Sets previously active key old
              if (gtks->gtk[i].status == GTK_STATUS_ACTIVE) {
                  gtks->gtk[i].status = GTK_STATUS_OLD;
              }
          }
          gtks->gtk[index].status = GTK_STATUS_ACTIVE;
          return 0;
      }
  
      return -1;
  }
  
  int8_t sec_prot_keys_gtk_status_active_get(sec_prot_gtk_keys_t *gtks)
  {
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (gtks->gtk[i].status == GTK_STATUS_ACTIVE) {
              return i;
          }
      }
  
      return -1;
  }
  
  bool sec_prot_keys_gtk_status_is_live(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return false;
      }
  
      if (gtks->gtk[index].status == GTK_STATUS_FRESH || gtks->gtk[index].status == GTK_STATUS_ACTIVE) {
          return true;
      }
  
      return false;
  }
  
  void sec_prot_keys_gtk_status_new_set(sec_prot_gtk_keys_t *gtks, uint8_t index)
  {
      if (index >= GTK_NUM || !gtks->gtk[index].set) {
          return;
      }
  
      gtks->gtk[index].status = GTK_STATUS_NEW;
  }
  
  void sec_prot_keys_gtks_hash_generate(sec_prot_gtk_keys_t *gtks, uint8_t *gtkhash)
  {
      memset(gtkhash, 0, GTK_ALL_HASHES_LEN);
  
      uint8_t *gtk_hash_ptr = gtkhash;
  
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              uint8_t *gtk = sec_prot_keys_gtk_get(gtks, i);
              sec_prot_lib_gtkhash_generate(gtk, gtk_hash_ptr);
          }
          gtk_hash_ptr += GTK_HASH_LEN;
      }
  }
  
  int8_t sec_prot_keys_gtk_hash_generate(uint8_t *gtk, uint8_t *gtk_hash)
  {
      return sec_prot_lib_gtkhash_generate(gtk, gtk_hash);
  }
  
  int8_t sec_prot_keys_gtk_valid_check(uint8_t *gtk)
  {
      uint8_t gtk_hash[8];
      sec_prot_lib_gtkhash_generate(gtk, gtk_hash);
  
      // Checks if GTK hash for the GTK would be all zero
      if (memcmp(gtk_hash, empty_hash, GTK_HASH_LEN) == 0) {
          return -1;
      }
  
      return 0;
  }
  
  gtk_mismatch_e sec_prot_keys_gtks_hash_update(sec_prot_gtk_keys_t *gtks, uint8_t *gtkhash)
  {
      uint8_t *gtk_hash_ptr = gtkhash;
  
      gtk_mismatch_e mismatch = GTK_NO_MISMATCH;
  
      for (uint8_t i = 0; i < GTK_NUM; i++, gtk_hash_ptr += 8) {
          // If hash is not set, stop using the key
          if (sec_prot_keys_gtk_hash_empty(gtk_hash_ptr)) {
              if (sec_prot_keys_gtk_is_set(gtks, i)) {
                  uint32_t lifetime = sec_prot_keys_gtk_lifetime_get(gtks, i);
                  if (lifetime > GTK_EXPIRE_MISMATCH_TIME) {
                      tr_info("GTK mismatch %i expired time, lifetime: %"PRIu32"", i, lifetime);
                      if (mismatch < GTK_LIFETIME_MISMATCH) {
                          mismatch = GTK_LIFETIME_MISMATCH;
                      }
                  }
                  sec_prot_keys_gtk_clear(gtks, i);
              }
          } else {
              // Check is hash matches to existing key
              uint8_t gtk_hash[8];
              uint8_t *gtk = sec_prot_keys_gtk_get(gtks, i);
              if (!gtk) {
                  // Hash set but GTK is not known, set mismatch
                  tr_info("GTK mismatch: %i", i);
                  if (mismatch < GTK_HASH_MISMATCH) {
                      mismatch = GTK_HASH_MISMATCH;
                  }
                  continue;
              }
  
              sec_prot_lib_gtkhash_generate(gtk, gtk_hash);
  
              if (memcmp(gtk_hash, gtk_hash_ptr, 8) == 0) {
                  // Key is fresh (or active, if old do not change state)
                  sec_prot_keys_gtk_status_fresh_set(gtks, i);
              } else {
                  // Hash does not match, set mismatch and delete key
                  tr_info("GTK mismatch: %i", i);
                  if (mismatch < GTK_HASH_MISMATCH) {
                      mismatch = GTK_HASH_MISMATCH;
                  }
                  sec_prot_keys_gtk_clear(gtks, i);
              }
          }
      }
  
      return mismatch;
  }
  
  bool sec_prot_keys_gtk_hash_empty(uint8_t *gtkhash)
  {
      if (memcmp(gtkhash, empty_hash, GTK_HASH_LEN) == 0) {
          return true;
      } else {
          return false;
      }
  }
  
  int8_t sec_prot_keys_gtk_install_order_last_get(sec_prot_gtk_keys_t *gtks)
  {
      int8_t install_order = -1;
  
      // Gets the last key index
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              if (gtks->gtk[i].install_order > install_order) {
                  install_order = gtks->gtk[i].install_order;
              }
          }
      }
  
      return install_order + 1;
  }
  
  int8_t sec_prot_keys_gtk_install_order_last_index_get(sec_prot_gtk_keys_t *gtks)
  {
      int8_t install_order = -1;
      int8_t index = -1;
  
      // Gets the last key index
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              if (gtks->gtk[i].install_order > install_order) {
                  install_order = gtks->gtk[i].install_order;
                  index = i;
              }
          }
      }
  
      return index;
  }
  
  uint32_t sec_prot_keys_gtk_install_order_last_lifetime_get(sec_prot_gtk_keys_t *gtks)
  {
      uint32_t lifetime = 0;
      int8_t install_order = -1;
  
      // Gets the last key index
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              if (gtks->gtk[i].install_order > install_order) {
                  install_order = gtks->gtk[i].install_order;
                  lifetime = gtks->gtk[i].lifetime;
              }
          }
      }
  
      return lifetime;
  }
  
  int8_t sec_prot_keys_gtk_install_order_first_index_get(sec_prot_gtk_keys_t *gtks)
  {
      // Gets the first key index
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              if (gtks->gtk[i].install_order == GTK_INSTALL_ORDER_FIRST) {
                  return i;
              }
          }
      }
  
      return -1;
  }
  
  int8_t sec_prot_keys_gtk_install_order_second_index_get(sec_prot_gtk_keys_t *gtks)
  {
      // Gets the first key index
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              if (gtks->gtk[i].install_order == GTK_INSTALL_ORDER_SECOND) {
                  return i;
              }
          }
      }
  
      return -1;
  }
  
  void sec_prot_keys_gtk_install_order_update(sec_prot_gtk_keys_t *gtks)
  {
      int8_t ordered_indexes[4] = {-1, -1, -1, -1};
  
      // Creates table of ordered indexes
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              ordered_indexes[gtks->gtk[i].install_order] = i;
          }
      }
  
      // Updates indexes of the GTKs
      uint8_t new_install_order = 0;
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (ordered_indexes[i] >= 0) {
              gtks->gtk[ordered_indexes[i]].install_order = new_install_order++;
          }
      }
  }
  
  int8_t sec_prot_keys_gtk_install_index_get(sec_prot_gtk_keys_t *gtks)
  {
      // Gets the index of the last key to be installed
      int8_t install_index = sec_prot_keys_gtk_install_order_last_index_get(gtks);
      if (install_index < 0) {
          install_index = 0;
      }
  
      // Checks if there is free index, and available uses that for new GTK
      for (uint8_t ctr = 0, i = install_index; ctr < GTK_NUM; ctr++) {
          if (!sec_prot_keys_gtk_is_set(gtks, i)) {
              install_index = i;
              break;
          }
          i++;
          if (i >= GTK_NUM) {
              i = 0;
          }
      }
  
      return install_index;
  }
  
  uint8_t sec_prot_keys_gtk_count(sec_prot_gtk_keys_t *gtks)
  {
      uint8_t count = 0;
  
      for (uint8_t i = 0; i < GTK_NUM; i++) {
          if (sec_prot_keys_gtk_is_set(gtks, i)) {
              count++;
          }
      }
  
      return count;
  }
  
  void sec_prot_keys_ptk_installed_gtk_hash_clear_all(sec_prot_keys_t *sec_keys)
  {
      for (uint8_t index = 0; index < GTK_NUM; index++) {
          memset(sec_keys->ins_gtk_hash[sec_keys->gtk_set_index].hash, 0, INS_GTK_HASH_LEN);
      }
      sec_keys->ins_gtk_hash_set = 0;
  }
  
  void sec_prot_keys_ptk_installed_gtk_hash_set(sec_prot_keys_t *sec_keys)
  {
      if (sec_keys->gtk_set_index >= 0) {
          uint8_t *gtk = sec_prot_keys_gtk_get(sec_keys->gtks, sec_keys->gtk_set_index);
          if (!gtk) {
              return;
          }
          uint8_t gtk_hash[GTK_HASH_LEN];
          if (sec_prot_keys_gtk_hash_generate(gtk, gtk_hash) < 0) {
              return;
          }
          /* Store two byte hash. This is long enough for the GTK installed check, since
           * possible conflict between hashes causes only that 4WH is initiated/is not
           * initiated instead of GKH.
           */
          memcpy(sec_keys->ins_gtk_hash[sec_keys->gtk_set_index].hash, gtk_hash, INS_GTK_HASH_LEN);
          sec_keys->ins_gtk_hash_set |= (1 << sec_keys->gtk_set_index);
      }
  }
  
  bool sec_prot_keys_ptk_installed_gtk_hash_mismatch_check(sec_prot_keys_t *sec_keys, uint8_t gtk_index)
  {
      if ((sec_keys->ins_gtk_hash_set & (1 << sec_keys->gtk_set_index)) == 0) {
          return false;
      }
  
      uint8_t *gtk = sec_prot_keys_gtk_get(sec_keys->gtks, gtk_index);
      if (!gtk) {
          return false;
      }
  
      // Calculated GTK hash for the current GTK on the defined index
      uint8_t gtk_hash[GTK_HASH_LEN];
      if (sec_prot_keys_gtk_hash_generate(gtk, gtk_hash) < 0) {
          return false;
      }
  
      // If PTK has been used to install different GTK to index than the current one, trigger mismatch
      if (memcmp(sec_keys->ins_gtk_hash[sec_keys->gtk_set_index].hash, gtk_hash, INS_GTK_HASH_LEN) != 0) {
          return true;
      }
  
      return false;
  }
  
  #endif /* HAVE_WS */
  