text: /*
   * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <string.h>
  #include <errno.h>
  
  #include "mesh_common.h"
  #include "settings_uid.h"
  #include "settings.h"
  
  #if CONFIG_BLE_MESH_SETTINGS
  
  enum settings_type {
      SETTINGS_CORE,
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
      SETTINGS_UID,
  #endif
  };
  
  struct settings_context {
      char *nvs_name;
      bt_mesh_nvs_handle_t handle;
  
      int (*settings_init)(void);
      int (*settings_load)(void);
      int (*settings_commit)(void);
  #if CONFIG_BLE_MESH_DEINIT
      int (*settings_deinit)(void);
      int (*settings_erase)(void);
  #endif /* CONFIG_BLE_MESH_DEINIT */
  };
  
  static struct settings_context settings_ctx[] = {
      [SETTINGS_CORE] = {
          .nvs_name = "mesh_core",
          .settings_init = settings_core_init,
          .settings_load = settings_core_load,
          .settings_commit = settings_core_commit,
  #if CONFIG_BLE_MESH_DEINIT
          .settings_deinit = settings_core_deinit,
          .settings_erase = settings_core_erase,
  #endif /* CONFIG_BLE_MESH_DEINIT */
      },
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
      [SETTINGS_UID] = {
          .nvs_name = "mesh_user_id",
          .settings_init = settings_uid_init,
          .settings_load = settings_uid_load,
          .settings_commit = NULL,
  #if CONFIG_BLE_MESH_DEINIT
          .settings_deinit = settings_uid_deinit,
          .settings_erase = settings_uid_erase,
  #endif /* CONFIG_BLE_MESH_DEINIT */
      },
  #endif /* CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE */
  };
  
  /* API used to initialize, load and commit BLE Mesh related settings */
  
  int bt_mesh_settings_nvs_open(const char* name, bt_mesh_nvs_handle_t *handle)
  {
  #if CONFIG_BLE_MESH_SPECIFIC_PARTITION
      return nvs_open_from_partition(CONFIG_BLE_MESH_PARTITION_NAME, name, NVS_READWRITE, handle);
  #else
      return nvs_open(name, NVS_READWRITE, handle);
  #endif
  }
  
  void bt_mesh_settings_nvs_close(bt_mesh_nvs_handle_t handle)
  {
      nvs_close(handle);
  }
  
  void bt_mesh_settings_init_foreach(void)
  {
      int err = 0;
      int i;
  
  #if CONFIG_BLE_MESH_SPECIFIC_PARTITION
      err = nvs_flash_init_partition(CONFIG_BLE_MESH_PARTITION_NAME);
      if (err != ESP_OK) {
          BT_ERR("Init mesh partition failed, name %s, err %d", CONFIG_BLE_MESH_PARTITION_NAME, err);
          return;
      }
  #endif /* CONFIG_BLE_MESH_SPECIFIC_PARTITION */
  
      for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) {
          struct settings_context *ctx = &settings_ctx[i];
  
          /* Settings initialization is always needed. */
          if (ctx->settings_init && ctx->settings_init()) {
              BT_ERR("Init settings failed, name %s", ctx->nvs_name);
              return;
          }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
          /* If multiple nvs namespace functionality is enabled,
           * no need to perform the following operations during
           * initialization. And they will be performed when the
           * application layer tries to open settings.
           */
          if (i != SETTINGS_UID) {
              continue;
          }
  #endif /* CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE */
  
          err = bt_mesh_settings_nvs_open(ctx->nvs_name, &ctx->handle);
          if (err) {
              BT_ERR("Open nvs failed, name %s, err %d", ctx->nvs_name, err);
              return;
          }
  
          if (ctx->settings_load && ctx->settings_load()) {
              BT_ERR("Load settings failed, name %s", ctx->nvs_name);
              return;
          }
  
          /* If not using multiple nvs namespaces, we will follow the normal
           * procedure, i.e. restoring all the mesh information.
           * If using multiple nvs namespaces, we will only restore user_id.
           */
          if (ctx->settings_commit && ctx->settings_commit()) {
              BT_ERR("Commit settings failed, name %s", ctx->nvs_name);
              return;
          }
      }
  }
  
  #if CONFIG_BLE_MESH_DEINIT
  void bt_mesh_settings_deinit_foreach(bool erase)
  {
      int i;
  
      for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) {
          struct settings_context *ctx = &settings_ctx[i];
  
          if (ctx->settings_deinit && ctx->settings_deinit()) {
              BT_ERR("Deinit settings failed, name %s", ctx->nvs_name);
              continue;
          }
  
          if (erase && ctx->settings_erase && ctx->settings_erase()) {
              BT_ERR("Erase settings failed, name %s", ctx->nvs_name);
              continue;
          }
  
          bt_mesh_settings_nvs_close(ctx->handle);
      }
  
  #if CONFIG_BLE_MESH_SPECIFIC_PARTITION
      nvs_flash_deinit_partition(CONFIG_BLE_MESH_PARTITION_NAME);
  #endif
  }
  #endif /* CONFIG_BLE_MESH_DEINIT */
  
  int bt_mesh_settings_direct_open(bt_mesh_nvs_handle_t *handle)
  {
      int err = 0;
      int i;
  
  #if CONFIG_BLE_MESH_SPECIFIC_PARTITION
      err = nvs_flash_init_partition(CONFIG_BLE_MESH_PARTITION_NAME);
      if (err != ESP_OK) {
          BT_ERR("Init mesh partition failed, name %s, err %d", CONFIG_BLE_MESH_PARTITION_NAME, err);
          return -EIO;
      }
  #endif /* CONFIG_BLE_MESH_SPECIFIC_PARTITION */
  
      for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) {
          struct settings_context *ctx = &settings_ctx[i];
  
          err = bt_mesh_settings_nvs_open(ctx->nvs_name, &ctx->handle);
          if (err) {
              BT_ERR("Open nvs failed, name %s, err %d", ctx->nvs_name, err);
              return -EIO;
          }
  
          if (i == SETTINGS_CORE && handle) {
              *handle = ctx->handle;
          }
      }
  
      return 0;
  }
  
  void bt_mesh_settings_direct_close(void)
  {
      int i;
  
      for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) {
          bt_mesh_settings_nvs_close(settings_ctx[i].handle);
      }
  
  #if CONFIG_BLE_MESH_SPECIFIC_PARTITION
      nvs_flash_deinit_partition(CONFIG_BLE_MESH_PARTITION_NAME);
  #endif
  }
  
  /* API used to get BLE Mesh related nvs handle */
  
  static inline bt_mesh_nvs_handle_t settings_get_nvs_handle(enum settings_type type)
  {
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
      if (type == SETTINGS_CORE) {
          extern bt_mesh_nvs_handle_t get_core_settings_handle(void);
          return get_core_settings_handle();
      }
  #endif
      return settings_ctx[type].handle;
  }
  
  /* API used to store/erase BLE Mesh related settings */
  
  static int settings_save(bt_mesh_nvs_handle_t handle, const char *key, const uint8_t *val, size_t len)
  {
      int err = 0;
  
      if (key == NULL) {
          BT_ERR("%s, Invalid parameter", __func__);
          return -EINVAL;
      }
  
      BT_DBG("nvs %s, key %s", val ? "set" : "erase", key);
  
      if (val) {
          err = nvs_set_blob(handle, key, val, len);
      } else {
          err = nvs_erase_key(handle, key);
          if (err == ESP_ERR_NVS_NOT_FOUND) {
              BT_DBG("%s not exists", key);
              return 0;
          }
      }
      if (err != ESP_OK) {
          BT_ERR("Failed to %s %s data (err %d)",
                  val ? "set" : "erase", key, err);
          return -EIO;
      }
  
      err = nvs_commit(handle);
      if (err != ESP_OK) {
          BT_ERR("Failed to commit settings (err %d)", err);
          return -EIO;
      }
  
      return 0;
  }
  
  int bt_mesh_save_settings(bt_mesh_nvs_handle_t handle, const char *key,
                            const uint8_t *val, size_t len)
  {
      int err = 0;
      bt_mesh_settings_lock();
      err = settings_save(handle, key, val, len);
      bt_mesh_settings_unlock();
      return err;
  }
  
  int bt_mesh_save_core_settings(const char *key, const uint8_t *val, size_t len)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_CORE);
      return bt_mesh_save_settings(handle, key, val, len);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  int bt_mesh_save_uid_settings(const char *key, const uint8_t *val, size_t len)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_UID);
      return bt_mesh_save_settings(handle, key, val, len);
  }
  #endif
  
  int bt_mesh_erase_settings(bt_mesh_nvs_handle_t handle, const char *key)
  {
      return bt_mesh_save_settings(handle, key, NULL, 0);
  }
  
  int bt_mesh_erase_core_settings(const char *key)
  {
      return bt_mesh_save_core_settings(key, NULL, 0);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  int bt_mesh_erase_uid_settings(const char *name)
  {
      return bt_mesh_save_uid_settings(name, NULL, 0);
  }
  #endif
  
  /* API used to load BLE Mesh related settings */
  
  static int settings_load(bt_mesh_nvs_handle_t handle, const char *key,
                           uint8_t *buf, size_t buf_len, bool *exist)
  {
      int err = 0;
  
      if (key == NULL || buf == NULL || exist == NULL) {
          BT_ERR("%s, Invalid parameter", __func__);
          return -EINVAL;
      }
  
      err = nvs_get_blob(handle, key, buf, &buf_len);
      if (err != ESP_OK) {
          if (err == ESP_ERR_NVS_NOT_FOUND) {
              BT_DBG("Settings %s not found", key);
              *exist = false;
              return 0;
          }
  
          BT_ERR("Failed to get %s data (err %d)", key, err);
          return -EIO;
      }
  
      *exist = true;
      return 0;
  }
  
  int bt_mesh_load_settings(bt_mesh_nvs_handle_t handle, const char *key,
                            uint8_t *buf, size_t buf_len, bool *exist)
  {
      int err = 0;
      bt_mesh_settings_lock();
      err = settings_load(handle, key, buf, buf_len, exist);
      bt_mesh_settings_unlock();
      return err;
  }
  
  int bt_mesh_load_core_settings(const char *key, uint8_t *buf, size_t buf_len, bool *exist)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_CORE);
      return bt_mesh_load_settings(handle, key, buf, buf_len, exist);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  int bt_mesh_load_uid_settings(const char *key, uint8_t *buf, size_t buf_len, bool *exist)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_UID);
      return bt_mesh_load_settings(handle, key, buf, buf_len, exist);
  }
  #endif
  
  /* API used to get length of BLE Mesh related settings */
  
  static size_t settings_get_length(bt_mesh_nvs_handle_t handle, const char *key)
  {
      size_t len = 0U;
      int err = 0;
  
      if (key == NULL) {
          BT_ERR("%s, Invalid parameter", __func__);
          return 0;
      }
  
      err = nvs_get_blob(handle, key, NULL, &len);
      if (err != ESP_OK) {
          if (err != ESP_ERR_NVS_NOT_FOUND) {
              BT_ERR("Failed to get %s length (err %d)", key, err);
          }
          return 0;
      }
  
      return len;
  }
  
  /* API used to get BLE Mesh related items. Here items mean model key, NetKey/AppKey
   * Index, etc. which are going to be used as the prefix of the nvs keys of the BLE
   * Mesh settings.
   */
  
  static struct net_buf_simple *settings_get_item(bt_mesh_nvs_handle_t handle, const char *key)
  {
      struct net_buf_simple *buf = NULL;
      size_t length = 0U;
      bool exist = false;
      int err = 0;
  
      length = settings_get_length(handle, key);
      if (!length) {
          BT_DBG("Empty %s", key);
          return NULL;
      }
  
      buf = bt_mesh_alloc_buf(length);
      if (!buf) {
          BT_ERR("%s, Out of memory", __func__);
          /* TODO: in this case, erase all related settings? */
          return NULL;
      }
  
      err = settings_load(handle, key, buf->data, length, &exist);
      if (err) {
          BT_ERR("Failed to load %s", key);
          /* TODO: in this case, erase all related settings? */
          bt_mesh_free_buf(buf);
          return NULL;
      }
  
      if (exist == false) {
          bt_mesh_free_buf(buf);
          return NULL;
      }
  
      buf->len = length;
      return buf;
  }
  
  struct net_buf_simple *bt_mesh_get_settings_item(bt_mesh_nvs_handle_t handle, const char *key)
  {
      struct net_buf_simple *buf = NULL;
      bt_mesh_settings_lock();
      buf = settings_get_item(handle, key);
      bt_mesh_settings_unlock();
      return buf;
  }
  
  struct net_buf_simple *bt_mesh_get_core_settings_item(const char *key)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_CORE);
      return bt_mesh_get_settings_item(handle, key);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  struct net_buf_simple *bt_mesh_get_uid_settings_item(const char *key)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_UID);
      return bt_mesh_get_settings_item(handle, key);
  }
  #endif
  
  /* API used to check if the settings item exists */
  
  static bool is_settings_item_exist(struct net_buf_simple *buf, const uint16_t val)
  {
      struct net_buf_simple_state state = {0};
      size_t length = 0U;
      int i;
  
      if (!buf) {
          return false;
      }
  
      net_buf_simple_save(buf, &state);
  
      length = buf->len;
      for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) {
          uint16_t item = net_buf_simple_pull_le16(buf);
          if (item == val) {
              net_buf_simple_restore(buf, &state);
              return true;
          }
      }
  
      net_buf_simple_restore(buf, &state);
      return false;
  }
  
  /* API used to add the settings item */
  
  static int settings_add_item(bt_mesh_nvs_handle_t handle, const char *key, const uint16_t val)
  {
      struct net_buf_simple *store = NULL;
      struct net_buf_simple *buf = NULL;
      size_t length = 0U;
      int err = 0;
  
      buf = settings_get_item(handle, key);
  
      /* Check if val already exists */
      if (is_settings_item_exist(buf, val) == true) {
          BT_DBG("0x%04x already exists", val);
          bt_mesh_free_buf(buf);
          return 0;
      }
  
      length = (buf ? buf->len : 0) + sizeof(val);
  
      store = bt_mesh_alloc_buf(length);
      if (!store) {
          BT_ERR("%s, Out of memory", __func__);
          bt_mesh_free_buf(buf);
          return -ENOMEM;
      }
  
      if (buf) {
          net_buf_simple_add_mem(store, buf->data, buf->len);
      }
      net_buf_simple_add_mem(store, &val, sizeof(val));
  
      err = settings_save(handle, key, store->data, store->len);
  
      bt_mesh_free_buf(store);
      bt_mesh_free_buf(buf);
      return err;
  }
  
  int bt_mesh_add_settings_item(bt_mesh_nvs_handle_t handle, const char *key, const uint16_t val)
  {
      int err = 0;
      bt_mesh_settings_lock();
      err = settings_add_item(handle, key, val);
      bt_mesh_settings_unlock();
      return err;
  }
  
  int bt_mesh_add_core_settings_item(const char *key, const uint16_t val)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_CORE);
      return bt_mesh_add_settings_item(handle, key, val);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  int bt_mesh_add_uid_settings_item(const char *key, const uint16_t val)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_UID);
      return bt_mesh_add_settings_item(handle, key, val);
  }
  #endif
  
  /* API used to remove the settings item */
  
  static int settings_remove_item(bt_mesh_nvs_handle_t handle, const char *key, const uint16_t val)
  {
      struct net_buf_simple *store = NULL;
      struct net_buf_simple *buf = NULL;
      size_t length = 0U;
      size_t buf_len = 0U;
      int err = 0;
      int i;
  
      buf = settings_get_item(handle, key);
  
      /* Check if val does exist */
      if (is_settings_item_exist(buf, val) == false) {
          BT_DBG("0x%04x not exists", val);
          bt_mesh_free_buf(buf);
          return 0;
      }
  
      length = buf->len - sizeof(val);
      if (!length) {
          settings_save(handle, key, NULL, 0);
          bt_mesh_free_buf(buf);
          return 0;
      }
  
      store = bt_mesh_alloc_buf(length);
      if (!store) {
          BT_ERR("%s, Out of memory", __func__);
          bt_mesh_free_buf(buf);
          return -ENOMEM;
      }
  
      buf_len = buf->len;
      for (i = 0; i < buf_len / SETTINGS_ITEM_SIZE; i++) {
          uint16_t item = net_buf_simple_pull_le16(buf);
          if (item != val) {
              net_buf_simple_add_le16(store, item);
          }
      }
  
      err = settings_save(handle, key, store->data, store->len);
  
      bt_mesh_free_buf(store);
      bt_mesh_free_buf(buf);
      return err;
  }
  
  int bt_mesh_remove_settings_item(bt_mesh_nvs_handle_t handle, const char *key, const uint16_t val)
  {
      int err = 0;
      bt_mesh_settings_lock();
      err = settings_remove_item(handle, key, val);
      bt_mesh_settings_unlock();
      return err;
  }
  
  int bt_mesh_remove_core_settings_item(const char *key, const uint16_t val)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_CORE);
      return bt_mesh_remove_settings_item(handle, key, val);
  }
  
  #if CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE
  int bt_mesh_remove_uid_settings_item(const char *key, const uint16_t val)
  {
      bt_mesh_nvs_handle_t handle = settings_get_nvs_handle(SETTINGS_UID);
      return bt_mesh_remove_settings_item(handle, key, val);
  }
  #endif
  
  int bt_mesh_settings_erase_key(bt_mesh_nvs_handle_t handle, const char *key)
  {
      int err = 0;
  
      err = nvs_erase_key(handle, key);
      if (err != ESP_OK) {
          if (err == ESP_ERR_NVS_NOT_FOUND) {
              return 0;
          }
  
          BT_ERR("Failed to erase %s (err %d)", key, err);
          return -EIO;
      }
  
      err = nvs_commit(handle);
      if (err != ESP_OK) {
          BT_ERR("Failed to commit nvs (err %d)", err);
          return -EIO;
      }
  
      return 0;
  }
  
  int bt_mesh_settings_erase_all(bt_mesh_nvs_handle_t handle)
  {
      int err = 0;
  
      err = nvs_erase_all(handle);
      if (err != ESP_OK) {
          BT_ERR("Failed to erase all (err %d)", err);
          return -EIO;
      }
  
      err = nvs_commit(handle);
      if (err != ESP_OK) {
          BT_ERR("Failed to commit nvs (err %d)", err);
          return -EIO;
      }
  
      return 0;
  }
  #endif /* CONFIG_BLE_MESH_SETTINGS */
  