/****************************************************************************
 * apps/testing/mtd_config_fs/mtd_config_fs_test_main.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/mtd/configdata.h>

#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <nuttx/crc8.h>
#include <debug.h>
#include <assert.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#define TEST_KEY1       "testkey1"
#define TEST_KEY2       "testkey2"
#define TEST_DATA1      "abc"
#define TEST_DATA2      "def"

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* Allocation Table Entry */

begin_packed_struct struct nvs_ate
{
  uint32_t id;           /* data id */
  uint16_t offset;       /* data offset within sector */
  uint16_t len;          /* data len within sector */
  uint16_t key_len;      /* key string len */
  uint8_t  part;         /* part of a multipart data - future extension */
  uint8_t  crc8;         /* crc8 check of the ate entry */
  uint8_t  expired;      /* 0xFF-newest entry, others-old entry */
  uint8_t  reserved[3];  /* for future extension */
} end_packed_struct;

/* Pre-allocated simulated flash */

struct mtdnvs_ctx_s
{
  char mountdir[CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_MAXNAME];
  struct mallinfo mmbefore;
  struct mallinfo mmprevious;
  struct mallinfo mmafter;
  uint8_t         erasestate;
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fnv_hash_32
 ****************************************************************************/

static uint32_t nvs_fnv_hash(FAR const uint8_t *input, uint32_t len)
{
  uint32_t i = 0;
  uint32_t hval = 2166136261;

  /* FNV-1 hash each octet in the buffer */

  while (i++ < len)
    {
      /* multiply by the 32 bit FNV magic prime mod 2^32 */

      hval *= 0x01000193;

      /* xor the bottom with the current octet */

      hval ^= *input++;
    }

  return hval;
}

/****************************************************************************
 * Name: fill_crc8_update
 ****************************************************************************/

static void fill_crc8_update(FAR struct nvs_ate *entry)
{
  uint8_t ate_crc;

  ate_crc = crc8part((FAR const uint8_t *)entry,
                     offsetof(struct nvs_ate, crc8), 0xff);
  entry->crc8 = ate_crc;
}

/****************************************************************************
 * Name: fill_gc_done_ate
 ****************************************************************************/

static void fill_corrupted_close_ate(FAR struct mtdnvs_ctx_s *ctx,
                                     FAR struct nvs_ate *close_ate)
{
  memset((FAR void *)close_ate, ctx->erasestate,
    sizeof(struct nvs_ate));
  close_ate->id = 0xffffffff;
  close_ate->len = 0U;
}

/****************************************************************************
 * Name: fill_close_ate
 ****************************************************************************/

static void fill_close_ate(FAR struct mtdnvs_ctx_s *ctx,
                           FAR struct nvs_ate *close_ate, int offset)
{
  memset((FAR void *)close_ate, ctx->erasestate,
    sizeof(struct nvs_ate));
  close_ate->id = 0xffffffff;
  close_ate->len = 0U;
  close_ate->offset = offset;
  fill_crc8_update(close_ate);
}

/****************************************************************************
 * Name: fill_gc_done_ate
 ****************************************************************************/

static void fill_gc_done_ate(FAR struct mtdnvs_ctx_s *ctx,
                             FAR struct nvs_ate *gc_done_ate)
{
  memset((FAR void *)gc_done_ate, ctx->erasestate,
    sizeof(struct nvs_ate));
  gc_done_ate->id = 0xffffffff;
  gc_done_ate->len = 0U;
  fill_crc8_update(gc_done_ate);
}

/****************************************************************************
 * Name: fill_ate
 ****************************************************************************/

static void fill_ate(FAR struct mtdnvs_ctx_s *ctx, FAR struct nvs_ate *ate,
                     FAR const char *key, uint16_t len, uint16_t offset,
                     bool expired)
{
  memset((FAR void *)ate, ctx->erasestate,
    sizeof(struct nvs_ate));
  ate->id = nvs_fnv_hash((FAR const uint8_t *)key, strlen(key) + 1)
    % 0xfffffffd + 1;
  ate->len = len;
  ate->offset = offset;
  ate->key_len = strlen(key) + 1;
  fill_crc8_update(ate);
  ate->expired = expired ? 0x7f : 0xff;
}

/****************************************************************************
 * Name: fill_corrupted_ate
 ****************************************************************************/

static void fill_corrupted_ate(FAR struct mtdnvs_ctx_s *ctx,
                               FAR struct nvs_ate *ate, FAR const char *key,
                               uint16_t len, uint16_t offset)
{
  memset((FAR void *)ate, ctx->erasestate,
    sizeof(struct nvs_ate));
  ate->id = nvs_fnv_hash((FAR const uint8_t *)key, strlen(key) + 1)
    % 0xfffffffd + 1;
  ate->len = len;
  ate->offset = offset;
  ate->key_len = strlen(key) + 1;
}

/****************************************************************************
 * Name: mtdnvs_showmemusage
 ****************************************************************************/

static void mtdnvs_showmemusage(struct mallinfo *mmbefore,
                                struct mallinfo *mmafter)
{
  printf("VARIABLE  BEFORE   AFTER    DELTA\n");
  printf("======== ======== ======== ========\n");
  printf("arena    %8x %8x %8x\n", mmbefore->arena   , mmafter->arena,
                                   mmafter->arena    - mmbefore->arena);
  printf("ordblks  %8d %8d %8d\n", mmbefore->ordblks , mmafter->ordblks,
                                   mmafter->ordblks  - mmbefore->ordblks);
  printf("mxordblk %8x %8x %8x\n", mmbefore->mxordblk, mmafter->mxordblk,
                                   mmafter->mxordblk - mmbefore->mxordblk);
  printf("uordblks %8x %8x %8x\n", mmbefore->uordblks, mmafter->uordblks,
                                   mmafter->uordblks - mmbefore->uordblks);
  printf("fordblks %8x %8x %8x\n", mmbefore->fordblks, mmafter->fordblks,
                                   mmafter->fordblks - mmbefore->fordblks);
}

/****************************************************************************
 * Name: mtdnvs_endmemusage
 ****************************************************************************/

static void mtdnvs_endmemusage(FAR struct mtdnvs_ctx_s *ctx)
{
  ctx->mmafter = mallinfo();

  printf("\nFinal memory usage:\n");
  mtdnvs_showmemusage(&ctx->mmbefore, &ctx->mmafter);
}

/****************************************************************************
 * Name: show_useage
 ****************************************************************************/

static void show_useage(void)
{
  printf("Usage : mtdconfig_fs_test [OPTION [ARG]] ...\n");
  printf("-h    show this help statement\n");
  printf("-m    mount point to be tested e.g. [%s]\n",
          CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME);
}

/****************************************************************************
 * Name: teardown
 ****************************************************************************/

static int teardown(void)
{
  int fd;
  int ret;

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      return -errno;
    }

  ret = ioctl(fd, MTDIOC_BULKERASE, NULL);
  if (ret < 0)
    {
      printf("%s:clear failed, ret=%d\n", __func__, ret);
      return -errno;
    }

  close(fd);

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      return ret;
    }

  return ret;
}

extern int find_mtddriver(FAR const char *pathname,
                        FAR struct inode **ppinode);

/****************************************************************************
 * Name: setup
 ****************************************************************************/

static int setup(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  FAR struct inode *sys_node;

  ret = find_mtddriver(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME,
    &sys_node);
  if (ret < 0)
    {
      printf("ERROR: open %s failed: %d\n",
        CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, ret);
      return -errno;
    }

  ret = MTD_IOCTL(sys_node->u.i_mtd, MTDIOC_ERASESTATE,
                 (unsigned long)((uintptr_t)&ctx->erasestate));
  if (ret < 0)
    {
      printf("ERROR: MTD ioctl(MTDIOC_ERASESTATE) failed: %d\n", ret);
      return ret;
    }

  ret = mtdconfig_register(sys_node->u.i_mtd);
  if (ret < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, ret);
      return ret;
    }

  return ret;
}

/****************************************************************************
 * Name: test_nvs_mount
 ****************************************************************************/

static void test_nvs_mount(struct mtdnvs_ctx_s *ctx)
{
  int ret;

  printf("%s: test begin\n", __func__);

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:mtdconfig_register failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: execute_long_pattern_write
 ****************************************************************************/

static int execute_long_pattern_write(const char *key)
{
  struct config_data_s data;
  int i;
  int fd;
  int ret;
  uint8_t rd_buf[512];
  uint8_t wr_buf[512];
  char pattern[4];

  pattern[0] = 0xde;
  pattern[1] = 0xad;
  pattern[2] = 0xbe;
  pattern[3] = 0xef;

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      return -errno;
    }

  strlcpy(data.name, key, sizeof(data.name));
  data.configdata = rd_buf;
  data.len = sizeof(rd_buf);
  ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
  if (ret != -1 || errno != ENOENT)
    {
      printf("%s:CFGDIOC_GETCONFIG unexpected failure: %d\n",
        __func__, ret);
      goto err_fd;
    }

  for (i = 0; i < sizeof(wr_buf); i += sizeof(pattern))
    {
      memcpy(wr_buf + i, pattern, sizeof(pattern));
    }

  data.configdata = wr_buf;
  data.len = sizeof(wr_buf);

  ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto err_fd;
    }

  data.configdata = rd_buf;
  data.len = sizeof(rd_buf);
  ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto err_fd;
    }

  if (memcmp(wr_buf, rd_buf, sizeof(rd_buf)) != 0)
    {
      printf("%s:RD buff should be equal to the WR buff\n", __func__);
      ret = -EIO;
      goto err_fd;
    }

err_fd:
  close(fd);

  return ret;
}

/****************************************************************************
 * Name: test_nvs_write
 ****************************************************************************/

static void test_nvs_write(struct mtdnvs_ctx_s *ctx)
{
  int ret;

  printf("%s: test begin\n", __func__);

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:mtdconfig_register failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = execute_long_pattern_write(TEST_KEY1);
  if (ret < 0)
    {
      printf("%s:execute_long_pattern_write failed, ret=%d\n",
        __func__, ret);
      goto test_fail;
    }

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_corrupt_expire
 * Description: Test that startup correctly handles invalid expire.
 ****************************************************************************/

static void test_nvs_corrupt_expire(struct mtdnvs_ctx_s *ctx)
{
  struct nvs_ate ate;
  int mtd_fd = -1;
  int nvs_fd = -1;
  int ret;
  int i;
  uint8_t erase_value = ctx->erasestate;
  struct config_data_s data;
  char rd_buf[50];

  printf("%s: test begin\n", __func__);

  mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR);
  if (mtd_fd < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd);
      goto test_fail;
    }

  /* write valid data */

  ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1);
  if (ret < 0)
    {
      printf("%s:write key1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = write(mtd_fd, TEST_DATA1, strlen(TEST_DATA1) + 1);
  if (ret < 0)
    {
      printf("%s:write data1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write valid data again, simulate overwrite data */

  ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1);
  if (ret < 0)
    {
      printf("%s:write key1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = write(mtd_fd, TEST_DATA2, strlen(TEST_DATA2) + 1);
  if (ret < 0)
    {
      printf("%s:write data2 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff */

  for (i = 2 * (strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2);
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* Write ate */

  fill_ate(ctx, &ate, TEST_KEY1, strlen(TEST_DATA2) + 1,
    strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2, false);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fill_ate(ctx, &ate, TEST_KEY1, strlen(TEST_DATA1) + 1, 0, false);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write gc_done ate */

  fill_gc_done_ate(ctx, &ate);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write close ate, mark section 0 as closed */

  fill_close_ate(ctx, &ate,
    CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write close ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  close(mtd_fd);
  mtd_fd = -1;

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  nvs_fd = open("/dev/config", 0);
  if (nvs_fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, nvs_fd);
      goto test_fail;
    }

  strlcpy(data.name, TEST_KEY1, sizeof(data.name));
  data.configdata = (FAR uint8_t *)rd_buf;
  data.len = sizeof(rd_buf);

  ret = ioctl(nvs_fd, CFGDIOC_FIRSTCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_FIRSTCONFIG unexpected failure: %d\n",
        __func__, ret);
      goto test_fail;
    }

  if (strncmp(rd_buf, TEST_DATA2, sizeof(rd_buf)) != 0)
    {
      printf("%s:unexpected value\n", __func__);
      goto test_fail;
    }

  ret = ioctl(nvs_fd, CFGDIOC_NEXTCONFIG, &data);
  if (ret != -1 || errno != ENOENT)
    {
      printf("%s:CFGDIOC_NEXTCONFIG should return ENOENT, but: %d\n",
        __func__, ret);
      goto test_fail;
    }

  close(nvs_fd);
  nvs_fd = -1;

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (mtd_fd >= 0)
    {
      close(mtd_fd);
    }

  if (nvs_fd >= 0)
    {
      close(nvs_fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_corrupted_write
 ****************************************************************************/

static void test_nvs_corrupted_write(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  char rd_buf[512];
  char wr_buf_1[] = TEST_DATA1;
  char wr_buf_2[] = TEST_DATA2;
  char key1[] = TEST_KEY1;
  int mtd_fd = -1;
  int nvs_fd = -1;
  int i;
  uint8_t erase_value = ctx->erasestate;
  struct nvs_ate ate;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);

  mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR);
  if (mtd_fd < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd);
      goto test_fail;
    }

  /* lets simulate loss of part of data */

  /* write valid data */

  ret = write(mtd_fd, key1, sizeof(key1));
  if (ret != sizeof(key1))
    {
      printf("%s:write key1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = write(mtd_fd, wr_buf_1, sizeof(wr_buf_1));
  if (ret != sizeof(wr_buf_1))
    {
      printf("%s:write data1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* power loss occurs after we write data */

  ret = write(mtd_fd, key1, sizeof(key1));
  if (ret != sizeof(key1))
    {
      printf("%s:write key1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = write(mtd_fd, wr_buf_2, sizeof(wr_buf_1));
  if (ret != sizeof(wr_buf_2))
    {
      printf("%s:write data2 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff */

  for (i = 2 * (sizeof(key1) + sizeof(wr_buf_1));
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 3 * 16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* write ate */

  fill_ate(ctx, &ate, key1, sizeof(wr_buf_1), 0, false);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write gc_done ate */

  fill_gc_done_ate(ctx, &ate);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  close(mtd_fd);
  mtd_fd = -1;

  /* now start up */

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  nvs_fd = open("/dev/config", 0);
  if (nvs_fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, nvs_fd);
      goto test_fail;
    }

  strlcpy(data.name, TEST_KEY1, sizeof(data.name));
  data.configdata = (FAR uint8_t *)rd_buf;
  data.len = sizeof(rd_buf);
  ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data);
  if (ret < 0)
    {
      printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  if (strncmp(rd_buf, wr_buf_1, sizeof(rd_buf)) != 0)
    {
      printf("%s:failed, RD buff should be equal to the first WR buff "
        "because subsequent write operation has failed\n",
        __func__);
      goto test_fail;
    }

  close(nvs_fd);
  nvs_fd = -1;

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  printf("%s: failed\n", __func__);
  if (nvs_fd > 0)
    {
      close(nvs_fd);
    }

  if (mtd_fd > 0)
    {
      close(mtd_fd);
    }
}

/****************************************************************************
 * Name: test_nvs_gc
 ****************************************************************************/

static void test_nvs_gc(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  int fd = -1;
  uint8_t buf[44];
  uint8_t rd_buf[44];
  struct config_data_s data;
  uint16_t i;
  uint16_t id;
  const uint16_t max_id = 10;

  /* 4096 * 2 / (44 + 4 + 16)  = 128, 129 write will trigger GC. */

  const uint16_t max_writes = 129;

  printf("%s: test begin\n", __func__);

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  for (i = 0; i < max_writes; i++)
    {
      id = (i % max_id);
      uint8_t id_data = id + max_id * (i / max_id);

      memset(buf, id_data, sizeof(buf));

      /* 4 byte key */

      snprintf(data.name, sizeof(data.name), "k%02d", id);
      data.configdata = buf;
      data.len = sizeof(buf);

      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
      if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
    }

  for (id = 0; id < max_id; id++)
    {
      /* 4 byte key */

      snprintf(data.name, sizeof(data.name), "k%02d", id);
      data.configdata = rd_buf;
      data.len = sizeof(rd_buf);

      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
      if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      for (i = 0; i < sizeof(rd_buf); i++)
        {
          rd_buf[i] = rd_buf[i] % max_id;
          buf[i] = id;
        }

      if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0)
        {
          printf("RD buff should be equal to the WR buff\n");
          goto test_fail;
        }
    }

  close(fd);
  fd = -1;

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  for (id = 0; id < max_id; id++)
    {
      /* 4 byte key */

      snprintf(data.name, sizeof(data.name), "k%02d", id);
      data.configdata = rd_buf;
      data.len = sizeof(rd_buf);

      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
      if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      for (i = 0; i < sizeof(rd_buf); i++)
        {
          rd_buf[i] = rd_buf[i] % max_id;
          buf[i] = id;
        }

      if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0)
        {
          printf("RD buff should be equal to the WR buff\n");
          goto test_fail;
        }
    }

  close(fd);
  fd = -1;

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: write_content
 ****************************************************************************/

static int write_content(uint16_t max_id, uint16_t begin, uint16_t end)
{
  uint8_t buf[44];
  int fd = -1;
  int ret;
  struct config_data_s data;
  uint16_t i;

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      return -errno;
    }

  for (i = begin; i < end; i++)
    {
      uint8_t id = (i % max_id);
      uint8_t id_data = id + max_id * (i / max_id);

      memset(buf, id_data, sizeof(buf));

      /* 4 byte key */

      snprintf(data.name, sizeof(data.name), "k%02d", id);
      data.configdata = buf;
      data.len = sizeof(buf);

      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
      if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
    }

  close(fd);
  return ret;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  return ret;
}

/****************************************************************************
 * Name: check_content
 ****************************************************************************/

static int check_content(uint16_t max_id)
{
  uint8_t rd_buf[44];
  uint8_t buf[44];
  int fd = -1;
  int ret;
  struct config_data_s data;

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      return -errno;
    }

  for (uint16_t id = 0; id < max_id; id++)
    {
      /* 4 byte key */

      snprintf(data.name, sizeof(data.name), "k%02d", id);
      data.configdata = rd_buf;
      data.len = sizeof(rd_buf);

      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
      if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      for (uint16_t i = 0; i < sizeof(rd_buf); i++)
        {
          rd_buf[i] = rd_buf[i] % max_id;
          buf[i] = id;
        }

      if (memcmp(buf, rd_buf, sizeof(rd_buf)) != 0)
        {
          printf("RD buff should be equal to the WR buff\n");
          goto test_fail;
        }
    }

  close(fd);
  return ret;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  return ret;
}

/****************************************************************************
 * Name: test_nvs_gc_3sectors
 * Description: Full round of GC over 3 sectors
 ****************************************************************************/

static void test_nvs_gc_3sectors(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  const uint16_t max_id = 64;

  /* 4096 * 2 / (44 + 4 + 16)  = 128, 129 write will trigger GC. */

  const uint16_t max_writes = 129;

  /* 4096 / (44 + 4 + 16) = 64, 129 + 64  write will trigger 2st GC. */

  const uint16_t max_writes_2 = 129 + 64;
  const uint16_t max_writes_3 = 129 + 64 + 64;
  const uint16_t max_writes_4 = 129 + 64 + 64 + 64;

  printf("%s: test begin\n", __func__);

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* Trigger 1st GC */

  ret = write_content(max_id, 0, max_writes);
  if (ret < 0)
    {
      printf("%s:1st GC write failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:1st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:1st mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:1st setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:1st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* Trigger 2nd GC */

  ret = write_content(max_id, max_writes, max_writes_2);
  if (ret < 0)
    {
      printf("%s:2st GC write failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:2st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:2st mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:2st setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:2st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* Trigger 3rd GC */

  ret = write_content(max_id, max_writes_2, max_writes_3);
  if (ret < 0)
    {
      printf("%s:3st GC write failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:3st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:3st mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:3st setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:3st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* Trigger 4th GC */

  ret = write_content(max_id, max_writes_3, max_writes_4);
  if (ret < 0)
    {
      printf("%s:4st GC write failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:4st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = mtdconfig_unregister();
  if (ret < 0)
    {
      printf("%s:4st mtdconfig_unregister failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:4st setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = check_content(max_id);
  if (ret < 0)
    {
      printf("%s:4st GC check failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_corrupted_sector_close
 ****************************************************************************/

static void test_nvs_corrupted_sector_close(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  uint8_t rd_buf[512];
  uint8_t wr_buf[512];
  char key1[] = TEST_KEY1;
  int mtd_fd = -1;
  int nvs_fd = -1;
  int loop_section;
  int i;
  uint8_t erase_value = ctx->erasestate;
  struct nvs_ate ate;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);

  mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR);
  if (mtd_fd < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd);
      goto test_fail;
    }

  /* lets simulate loss of close at the beginning of gc */

  for (loop_section = 0;
    loop_section <
      CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 1;
    loop_section++)
    {
      /* write valid data */

      for (i = 0; i < 2 ; i++)
        {
          ret = write(mtd_fd, key1, sizeof(key1));
          if (ret != sizeof(key1))
            {
              printf("%s:write key1 failed, ret=%d\n", __func__, ret);
              goto test_fail;
            }

          memset(wr_buf, 'A' + i, sizeof(wr_buf));
          wr_buf[sizeof(wr_buf) - 1] = '\0';

          ret = write(mtd_fd, wr_buf, sizeof(wr_buf));
          if (ret != sizeof(wr_buf))
            {
              printf("%s:write data1 failed, ret=%d\n", __func__, ret);
              goto test_fail;
            }
        }

      /* set unused flash to 0xff */

      for (i = 2 * (sizeof(key1) + sizeof(wr_buf));
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16;
        i++)
        {
          ret = write(mtd_fd, &erase_value, sizeof(erase_value));
          if (ret != sizeof(erase_value))
            {
              printf("%s:erase failed, ret=%d\n", __func__, ret);
              goto test_fail;
            }
        }

      /* write ate 2 times */

      fill_ate(ctx, &ate, key1, sizeof(wr_buf),
        sizeof(wr_buf) + sizeof(key1),
        (loop_section ==
        CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 2)
        ? false : true);
      ret = write(mtd_fd, &ate, sizeof(ate));
      if (ret != sizeof(ate))
        {
          printf("%s:write ate failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }

      fill_ate(ctx, &ate, key1, sizeof(wr_buf), 0, true);
      ret = write(mtd_fd, &ate, sizeof(ate));
      if (ret != sizeof(ate))
        {
          printf("%s:write ate failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }

      /* write gc_done ate */

      fill_gc_done_ate(ctx, &ate);
      ret = write(mtd_fd, &ate, sizeof(ate));
      if (ret != sizeof(ate))
        {
          printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }

      if (loop_section ==
        CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_COUNT - 2)
        {
          fill_corrupted_close_ate(ctx, &ate);
        }
      else
        {
          fill_close_ate(ctx, &ate,
            CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 4 * 16);
        }

      ret =  write(mtd_fd, &ate, sizeof(ate));
      if (ret != sizeof(ate))
        {
          printf("%s:write close ate failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  close(mtd_fd);
  mtd_fd = -1;

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  nvs_fd = open("/dev/config", 0);
  if (nvs_fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, nvs_fd);
      goto test_fail;
    }

  strlcpy(data.name, TEST_KEY1, sizeof(data.name));
  data.configdata = rd_buf;
  data.len = sizeof(rd_buf);

  ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  if (memcmp(rd_buf, wr_buf, sizeof(rd_buf)) != 0)
    {
      printf("%s:strncmp failed\n", __func__);
      ret = -EACCES;
      goto test_fail;
    }

  /* Ensure that the NVS is able to store new content. */

  if (execute_long_pattern_write(TEST_KEY2) != 0)
    {
      printf("%s:write again failed\n", __func__);
      ret = -EACCES;
      goto test_fail;
    }

  close(nvs_fd);
  nvs_fd = -1;

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (nvs_fd >= 0)
    {
      close(nvs_fd);
    }

  if (mtd_fd >= 0)
    {
      close(mtd_fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_full_sector
 * Description: Test case when storage become full,
 *              so only deletion is possible.
 ****************************************************************************/

static void test_nvs_full_sector(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  uint16_t filling_id = 0;
  uint16_t i;
  uint16_t data_read;
  int fd = -1;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  while (1)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
      data.configdata = (FAR uint8_t *)&filling_id;
      data.len = sizeof(filling_id);

      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
      if (ret == -1 && errno == ENOSPC)
        {
          break;
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      filling_id++;
    }

  /* check whether can delete whatever from full storage */

  snprintf(data.name, sizeof(data.name), "k%04x", 1);
  data.configdata = NULL;
  data.len = 0;

  ret = ioctl(fd, CFGDIOC_DELCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_DELCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* the last sector is full now, test re-initialization */

  close(fd);
  fd = -1;
  mtdconfig_unregister();

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
  data.configdata = (FAR uint8_t *)&filling_id;
  data.len = sizeof(filling_id);

  ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* sanitycheck on NVS content */

  for (i = 0; i <= filling_id; i++)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", i);
      data.configdata = (FAR uint8_t *)&data_read;
      data.len = sizeof(data_read);

      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);

      if (i == 1)
        {
          if (ret != -1 || errno != ENOENT)
            {
              printf("%s:shouldn't found the entry: %d\n", __func__, i);
              ret = -EIO;
              goto test_fail;
            }
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
      else
        {
          if (data_read != i)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, i);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, i);
              ret = -EIO;
              goto test_fail;
            }
        }
    }

  close(fd);

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_gc_corrupt_close_ate
 * Description: Test that garbage-collection can
 *      recover all ate's even when the last ate, ie close_ate,
 *      is corrupt. In this test the close_ate is set to point to the
 *      last ate at -5. A valid ate is however present at -6.
 *      Since the close_ate has an invalid crc8, the offset
 *      should not be used and a recover of the
 *      last ate should be done instead.
 ****************************************************************************/

static void test_nvs_gc_corrupt_close_ate(struct mtdnvs_ctx_s *ctx)
{
  struct nvs_ate ate;
  struct nvs_ate close_ate;
  int mtd_fd = -1;
  int nvs_fd = -1;
  int ret;
  int i;
  uint8_t erase_value = ctx->erasestate;
  struct config_data_s data;
  char rd_buf[50];

  printf("%s: test begin\n", __func__);

  mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR);
  if (mtd_fd < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd);
      goto test_fail;
    }

  /* write valid data */

  ret = write(mtd_fd, TEST_KEY1, strlen(TEST_KEY1) + 1);
  if (ret != strlen(TEST_KEY1) + 1)
    {
      printf("%s:write key1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  ret = write(mtd_fd, TEST_DATA1, strlen(TEST_DATA1) + 1);
  if (ret != strlen(TEST_DATA1) + 1)
    {
      printf("%s:write data1 failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff */

  for (i = strlen(TEST_KEY1) + strlen(TEST_DATA1) + 2;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 6 * 16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* Write valid ate at -6 */

  fill_ate(ctx, &ate, TEST_KEY1, strlen(TEST_DATA1) + 1, 0, false);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff */

  for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 5 * 16;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 2 * 16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* write gc_done ate */

  fill_gc_done_ate(ctx, &ate);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write invalid close ate, mark section 0 as closed */

  fill_corrupted_close_ate(ctx, &close_ate);
  ret = write(mtd_fd, &close_ate, sizeof(close_ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff in section 1 */

  for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE -  16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* write invalid close ate, mark section 1 as closed */

  fill_corrupted_close_ate(ctx, &close_ate);
  ret = write(mtd_fd, &close_ate, sizeof(close_ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  close(mtd_fd);
  mtd_fd = -1;

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  nvs_fd = open("/dev/config", 0);
  if (nvs_fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, nvs_fd);
      goto test_fail;
    }

  strlcpy(data.name, TEST_KEY1, sizeof(data.name));
  data.configdata = (FAR uint8_t *)rd_buf;
  data.len = sizeof(rd_buf);

  ret = ioctl(nvs_fd, CFGDIOC_GETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:NVSIOC_READ unexpected failure: %d\n", __func__, ret);
      goto test_fail;
    }

  if (strncmp(rd_buf, TEST_DATA1, sizeof(rd_buf)) != 0)
    {
      printf("%s:unexpected value\n", __func__);
      goto test_fail;
    }

  close(nvs_fd);
  nvs_fd = -1;

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (nvs_fd >= 0)
    {
      close(nvs_fd);
    }

  if (mtd_fd >= 0)
    {
      close(mtd_fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_gc_corrupt_ate
 * Description: Test that garbage-collection correctly handles corrupt ate's.
 ****************************************************************************/

static void test_nvs_gc_corrupt_ate(struct mtdnvs_ctx_s *ctx)
{
  struct nvs_ate ate;
  struct nvs_ate close_ate;
  int mtd_fd = -1;
  int ret;
  int i;
  uint8_t erase_value = ctx->erasestate;

  printf("%s: test begin\n", __func__);

  fill_corrupted_ate(ctx, &ate, TEST_KEY1, 10, 0);

  mtd_fd = open(CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME, O_RDWR);
  if (mtd_fd < 0)
    {
      printf("%s:mtdnvs_register failed, ret=%d\n", __func__, mtd_fd);
      goto test_fail;
    }

  /* set unused flash to 0xff */

  for (i = 0 ;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2; i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* Write invalid ate */

  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2 + 16;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE - 32; i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* write gc_done ate */

  fill_gc_done_ate(ctx, &ate);
  ret = write(mtd_fd, &ate, sizeof(ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* write close ate, mark section 0 as closed */

  fill_close_ate(ctx, &close_ate,
    CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE / 2);
  ret = write(mtd_fd, &close_ate, sizeof(close_ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* set unused flash to 0xff in section 1 */

  for (i = CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE;
        i < CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_FLASH_SECTION_SIZE -  16;
        i++)
    {
      ret = write(mtd_fd, &erase_value, sizeof(erase_value));
      if (ret != sizeof(erase_value))
        {
          printf("%s:erase failed, ret=%d\n", __func__, ret);
          goto test_fail;
        }
    }

  /* write close ate, mark section 1 as closed */

  ret = write(mtd_fd, &close_ate, sizeof(close_ate));
  if (ret != sizeof(ate))
    {
      printf("%s:write gc_done ate failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  close(mtd_fd);
  mtd_fd = -1;

  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  /* at the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (mtd_fd >= 0)
    {
      close(mtd_fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_gc_touched_deleted_ate
 * Description: Test case when writing and gc touched A prev entry
 *   which was deleted.
 ****************************************************************************/

static void test_nvs_gc_touched_deleted_ate(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  uint16_t filling_id = 0;
  uint16_t i;
  uint16_t data_read;
  int fd = -1;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);
  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  while (1)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
      data.configdata = (FAR uint8_t *)&filling_id;
      data.len = sizeof(filling_id);

      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);

      /* ENOSPC will be accompanied by gc 3 times(if 3 blocks)
       * block will change three times:
       * block:    0,               1,           2
       *           A                B            gc
       *  (gc==1) gc                B            A
       *  (gc==2)  B                gc           A
       *  (gc==3)  B                A            gc
       */

      if (ret == -1 && errno == ENOSPC)
        {
          break;
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      filling_id++;
    }

  /* Now delete last record.
   * The layout should be:
   * block:    0,               1,           2
   *           B(deleted)       A            gc
   */

  snprintf(data.name, sizeof(data.name), "k%04x", filling_id - 1);
  data.configdata = NULL;
  data.len = 0;
  ret = ioctl(fd, CFGDIOC_DELCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_DELCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* Now input B again,
   * we should trigger gc for once, and we needn't search for
   * the old one again because it is expired.
   */

  filling_id -= 1;
  snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
  data.configdata = (FAR uint8_t *)&filling_id;
  data.len = sizeof(filling_id);
  ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* Sanitycheck on NVS content */

  for (i = 0; i <= filling_id; i++)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", i);
      data.configdata = (FAR uint8_t *)&data_read;
      data.len = sizeof(data_read);

      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);

      if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
      else
        {
          if (data_read != i)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, i);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, i);
              ret = -EIO;
              goto test_fail;
            }
        }
    }

  close(fd);

  /* At the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_gc_touched_expired_ate
 * Description: Test case when writing and  gc touched A prev entry
 *   which was moved by gc.
 ****************************************************************************/

static void test_nvs_gc_touched_expired_ate(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  uint16_t filling_id = 0;
  uint16_t update_id;
  uint16_t i;
  uint16_t data_read;
  int fd = -1;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);
  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  while (1)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
      data.configdata = (FAR uint8_t *)&filling_id;
      data.len = sizeof(filling_id);
      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
      if (ret == -1 && errno == ENOSPC)
        {
          break;
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      filling_id++;
    }

  /* Now delete,
   * the layout should be:
   * block:    0,               1,           2
   *           B                A(deleted)   gc
   */

  snprintf(data.name, sizeof(data.name), "k%04x", 1);
  data.configdata = NULL;
  data.len = 0;
  ret = ioctl(fd, CFGDIOC_DELCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_DELCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* The last sector is full now, test re-initialization */

  close(fd);
  fd = -1;
  mtdconfig_unregister();
  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  /* Now update A.
   * It should trigger gc for twice, and we need to search for
   * the old one again as gc has touched the old one. We will
   * find it and expire the old A.
   */

  update_id = 3;
  snprintf(data.name, sizeof(data.name), "k%04x", 2);
  data.configdata = (FAR uint8_t *)&update_id;
  data.len = sizeof(update_id);
  ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* Sanitycheck on NVS content */

  for (i = 0; i <= filling_id - 1; i++)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", i);
      data.configdata = (FAR uint8_t *)&data_read;
      data.len = sizeof(data_read);
      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
      if (i == 1)
        {
          if (ret != -1 || errno != ENOENT)
            {
              printf("%s:shouldn't found the entry: %d\n", __func__, i);
              ret = -EIO;
              goto test_fail;
            }
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
      else if (i == 2)
        {
          if (data_read != 3)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, 3);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, 3);
              ret = -EIO;
              goto test_fail;
            }
        }
      else
        {
          if (data_read != i)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, i);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, i);
              ret = -EIO;
              goto test_fail;
            }
        }
    }

  close(fd);

  /* At the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Name: test_nvs_gc_not_touched_expired_ate
 * Description: Test case when writing and  gc not touched a prev entry
 *   which was moved by gc.
 ****************************************************************************/

static void test_nvs_gc_not_touched_expired_ate(struct mtdnvs_ctx_s *ctx)
{
  int ret;
  uint16_t filling_id = 0;
  uint16_t update_id;
  uint16_t i;
  uint16_t data_read;
  int fd = -1;
  struct config_data_s data;

  printf("%s: test begin\n", __func__);
  ret = setup(ctx);
  if (ret < 0)
    {
      printf("%s:setup failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  fd = open("/dev/config", 0);
  if (fd < 0)
    {
      printf("%s:open failed, ret=%d\n", __func__, fd);
      goto test_fail;
    }

  while (1)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", filling_id);
      data.configdata = (FAR uint8_t *)&filling_id;
      data.len = sizeof(filling_id);
      ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
      if (ret == -1 && errno == ENOSPC)
        {
          break;
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }

      filling_id++;
    }

  /* Now delete last record,
   * the layout should be:
   * block:    0,               1,           2
   *           B(deleted)       A            gc
   */

  snprintf(data.name, sizeof(data.name), "k%04x", filling_id - 1);
  data.configdata = NULL;
  data.len = 0;
  ret = ioctl(fd, CFGDIOC_DELCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_DELCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* Now udpate A again.
   * We should trigger gc for once, and we won't need to search for
   * the old one again after gc.
   */

  update_id = 3;
  snprintf(data.name, sizeof(data.name), "k%04x", 2);
  data.configdata = (FAR uint8_t *)&update_id;
  data.len = sizeof(update_id);
  ret = ioctl(fd, CFGDIOC_SETCONFIG, &data);
  if (ret != 0)
    {
      printf("%s:CFGDIOC_SETCONFIG failed, ret=%d\n", __func__, ret);
      ret = -EIO;
      goto test_fail;
    }

  /* Sanitycheck on NVS content */

  for (i = 0; i <= filling_id - 1; i++)
    {
      snprintf(data.name, sizeof(data.name), "k%04x", i);
      data.configdata = (FAR uint8_t *)&data_read;
      data.len = sizeof(data_read);
      ret = ioctl(fd, CFGDIOC_GETCONFIG, &data);
      if (i == filling_id - 1)
        {
          if (ret != -1 || errno != ENOENT)
            {
              printf("%s:shouldn't found the entry: %d\n", __func__, i);
              ret = -EIO;
              goto test_fail;
            }
        }
      else if (ret != 0)
        {
          printf("%s:CFGDIOC_GETCONFIG failed, ret=%d\n", __func__, ret);
          ret = -EIO;
          goto test_fail;
        }
      else if (i == 2)
        {
          if (data_read != 3)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, 3);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, 3);
              ret = -EIO;
              goto test_fail;
            }
        }
      else
        {
          if (data_read != i)
            {
              printf("%s:read data %d \n", __func__, data_read);
              printf("%s:read expected  %d\n", __func__, i);
              printf("%s:read unexpected data: %d instead of %d\n",
                __func__, data_read, i);
              ret = -EIO;
              goto test_fail;
            }
        }
    }

  close(fd);

  /* At the end of test, erase all blocks */

  ret = teardown();
  if (ret < 0)
    {
      printf("%s:teardown failed, ret=%d\n", __func__, ret);
      goto test_fail;
    }

  printf("%s: success\n", __func__);
  return;

test_fail:
  if (fd >= 0)
    {
      close(fd);
    }

  printf("%s: failed\n", __func__);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: fstest_main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  FAR struct mtdnvs_ctx_s *ctx;
  int option;

  ctx = malloc(sizeof(struct mtdnvs_ctx_s));
  if (ctx == NULL)
    {
      printf("malloc ctx feild,exit!\n");
      exit(1);
    }

  memset(ctx, 0, sizeof(struct mtdnvs_ctx_s));

  strlcpy(ctx->mountdir, CONFIG_TESTING_MTD_CONFIG_FAIL_SAFE_MOUNTPT_NAME,
    sizeof(ctx->mountdir));

  /* Opt Parse */

  while ((option = getopt(argc, argv, ":m:hn:")) != -1)
    {
      switch (option)
        {
          case 'm':
            strlcpy(ctx->mountdir, optarg,
                    sizeof(ctx->mountdir));
            break;
          case 'h':
            show_useage();
            free(ctx);
            exit(0);
          case ':':
            printf("Error: Missing required argument\n");
            free(ctx);
            exit(1);
          case '?':
            printf("Error: Unrecognized option\n");
            free(ctx);
            exit(1);
        }
    }

  /* Set up memory monitoring */

  ctx->mmbefore = mallinfo();
  ctx->mmprevious = ctx->mmbefore;

  test_nvs_mount(ctx);
  test_nvs_write(ctx);
  test_nvs_corrupt_expire(ctx);
  test_nvs_corrupted_write(ctx);
  test_nvs_gc(ctx);
  test_nvs_gc_3sectors(ctx);
  test_nvs_corrupted_sector_close(ctx);
  test_nvs_full_sector(ctx);
  test_nvs_gc_corrupt_close_ate(ctx);
  test_nvs_gc_corrupt_ate(ctx);
  test_nvs_gc_touched_deleted_ate(ctx);
  test_nvs_gc_touched_expired_ate(ctx);
  test_nvs_gc_not_touched_expired_ate(ctx);

  /* Show memory usage */

  mtdnvs_endmemusage(ctx);
  fflush(stdout);
  free(ctx);
  return 0;
}

