/****************************************************************************
 * wireless/bluetooth/bt_keys.c
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 *   Copyright (c) 2016, Intel Corporation
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS
 * ; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <string.h>
#include <debug.h>

#include <nuttx/wireless/bluetooth/bt_core.h>
#include <nuttx/wireless/bluetooth/bt_hci.h>

#include "bt_hcicore.h"
#include "bt_smp.h"
#include "bt_keys.h"

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

#define bt_keys_foreach(list, cur, member) \
  for (cur = list; *cur; cur = &(*cur)->member)

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

static struct bt_keys_s g_key_pool[CONFIG_BLUETOOTH_MAX_PAIRED];

static FAR struct bt_keys_s *g_ltks;
static FAR struct bt_keys_s *g_slave_ltks;
static FAR struct bt_keys_s *g_irks;
static FAR struct bt_keys_s *g_local_csrks;
static FAR struct bt_keys_s *g_remote_csrks;

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

FAR struct bt_keys_s *bt_keys_get_addr(FAR const bt_addr_le_t *addr)
{
  FAR struct bt_keys_s *keys;
  int i;

  wlinfo("%s\n", bt_addr_le_str(addr));

  for (i = 0; i < CONFIG_BLUETOOTH_MAX_PAIRED; i++)
    {
      keys = &g_key_pool[i];

      if (!bt_addr_le_cmp(&keys->addr, addr))
        {
          return keys;
        }

      if (!bt_addr_le_cmp(&keys->addr, BT_ADDR_LE_ANY))
        {
          bt_addr_le_copy(&keys->addr, addr);
          wlinfo("created %p for %s\n", keys, bt_addr_le_str(addr));
          return keys;
        }
    }

  wlinfo("unable to create keys for %s\n", bt_addr_le_str(addr));

  return NULL;
}

void bt_keys_clear(FAR struct bt_keys_s *keys, int type)
{
  FAR struct bt_keys_s **cur;

  wlinfo("keys for %s type %d\n", bt_addr_le_str(&keys->addr), type);

  if (((type & keys->keys) & BT_KEYS_SLAVE_LTK))
    {
      bt_keys_foreach(&g_slave_ltks, cur, slave_ltk.next)
        {
          if (*cur == keys)
            {
              *cur = (*cur)->slave_ltk.next;
              break;
            }
        }

      keys->keys &= ~BT_KEYS_SLAVE_LTK;
    }

  if (((type & keys->keys) & BT_KEYS_LTK))
    {
      bt_keys_foreach(&g_ltks, cur, ltk.next)
        {
          if (*cur == keys)
            {
              *cur = (*cur)->ltk.next;
              break;
            }
        }

      keys->keys &= ~BT_KEYS_LTK;
    }

  if (((type & keys->keys) & BT_KEYS_IRK))
    {
      bt_keys_foreach(&g_irks, cur, irk.next)
        {
          if (*cur == keys)
            {
              *cur = (*cur)->irk.next;
              break;
            }
        }

      keys->keys &= ~BT_KEYS_IRK;
    }

  if (((type & keys->keys) & BT_KEYS_LOCAL_CSRK))
    {
      bt_keys_foreach(&g_local_csrks, cur, local_csrk.next)
        {
          if (*cur == keys)
            {
              *cur = (*cur)->local_csrk.next;
              break;
            }
        }

      keys->keys &= ~BT_KEYS_LOCAL_CSRK;
    }

  if (((type & keys->keys) & BT_KEYS_REMOTE_CSRK))
    {
      bt_keys_foreach(&g_remote_csrks, cur, remote_csrk.next)
        {
          if (*cur == keys)
            {
              *cur = (*cur)->remote_csrk.next;
              break;
            }
        }

      keys->keys &= ~BT_KEYS_REMOTE_CSRK;
    }

  if (!keys->keys)
    {
      memset(keys, 0, sizeof(*keys));
    }
}

FAR struct bt_keys_s *bt_keys_find(int type, FAR const bt_addr_le_t *addr)
{
  FAR struct bt_keys_s **cur;

  wlinfo("type %d %s\n", type, bt_addr_le_str(addr));

  switch (type)
    {
    case BT_KEYS_SLAVE_LTK:
      bt_keys_foreach(&g_slave_ltks, cur, slave_ltk.next)
        {
          if (!bt_addr_le_cmp(&(*cur)->addr, addr))
            {
              break;
            }
        }

      return *cur;

    case BT_KEYS_LTK:
      bt_keys_foreach(&g_ltks, cur, ltk.next)
        {
          if (!bt_addr_le_cmp(&(*cur)->addr, addr))
            {
              break;
            }
        }

      return *cur;

    case BT_KEYS_IRK:
      bt_keys_foreach(&g_irks, cur, irk.next)
        {
          if (!bt_addr_le_cmp(&(*cur)->addr, addr))
            {
              break;
            }
        }

      return *cur;

    case BT_KEYS_LOCAL_CSRK:
      bt_keys_foreach(&g_local_csrks, cur, local_csrk.next)
        {
          if (!bt_addr_le_cmp(&(*cur)->addr, addr))
            {
              break;
            }
        }

      return *cur;

    case BT_KEYS_REMOTE_CSRK:
      bt_keys_foreach(&g_remote_csrks, cur, remote_csrk.next)
        {
          if (!bt_addr_le_cmp(&(*cur)->addr, addr))
            {
              break;
            }
        }

      return *cur;

    default:
      return NULL;
    }
}

void bt_keys_add_type(FAR struct bt_keys_s *keys, int type)
{
  if ((keys->keys & type) != 0)
    {
      return;
    }

  switch (type)
    {
    case BT_KEYS_SLAVE_LTK:
      keys->slave_ltk.next   = g_slave_ltks;
      g_slave_ltks           = keys;
      break;

    case BT_KEYS_LTK:
      keys->ltk.next         = g_ltks;
      g_ltks                 = keys;
      break;

    case BT_KEYS_IRK:
      keys->irk.next         = g_irks;
      g_irks                 = keys;
      break;

    case BT_KEYS_LOCAL_CSRK:
      keys->local_csrk.next  = g_local_csrks;
      g_local_csrks          = keys;
      break;

    case BT_KEYS_REMOTE_CSRK:
      keys->remote_csrk.next = g_remote_csrks;
      g_remote_csrks         = keys;
      break;

    default:
      wlerr("ERROR: Unknown key type %d\n", type);
      return;
    }

  keys->keys |= type;
}

FAR struct bt_keys_s *bt_keys_get_type(int type,
                                       FAR const bt_addr_le_t *addr)
{
  FAR struct bt_keys_s *keys;

  wlinfo("type %d %s\n", type, bt_addr_le_str(addr));

  keys = bt_keys_find(type, addr);
  if (keys)
    {
      return keys;
    }

  keys = bt_keys_get_addr(addr);
  if (!keys)
    {
      return NULL;
    }

  bt_keys_add_type(keys, type);
  return keys;
}

FAR struct bt_keys_s *bt_keys_find_irk(FAR const bt_addr_le_t * addr)
{
  FAR struct bt_keys_s **cur;

  wlinfo("%s\n", bt_addr_le_str(addr));

  if (!bt_addr_le_is_rpa(addr))
    {
      return NULL;
    }

  bt_keys_foreach(&g_irks, cur, irk.next)
    {
      FAR struct bt_irk_s *irk = &(*cur)->irk;

      if (!bt_addr_cmp((bt_addr_t *) addr->val, &irk->rpa))
        {
          wlinfo("cached RPA %s for %s\n", bt_addr_str(&irk->rpa),
                 bt_addr_le_str(&(*cur)->addr));
          return *cur;
        }

      if (bt_smp_irk_matches(irk->val, (bt_addr_t *) addr->val))
        {
          FAR struct bt_keys_s *match = *cur;

          wlinfo("RPA %s matches %s\n", bt_addr_str(&irk->rpa),
                 bt_addr_le_str(&(*cur)->addr));

          bt_addr_copy(&irk->rpa, (bt_addr_t *) addr->val);

          /* Move to the beginning of the list for faster future lookups. */

          if (match != g_irks)
            {
              /* Remove match from list */

              *cur = irk->next;

              /* Add match to the beginning */

              irk->next = g_irks;
              g_irks    = match;
            }

          return match;
        }
    }

  wlinfo("No IRK for %s\n", bt_addr_le_str(addr));
  return NULL;
}