/*
 * 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.
 */

#include <string.h>

#include "host/ble_store.h"
#include "ble_hs_priv.h"

int
ble_store_read(int obj_type, const union ble_store_key *key,
               union ble_store_value *val)
{
    int rc;

    ble_hs_lock();

    if (ble_hs_cfg.store_read_cb == NULL) {
        rc = BLE_HS_ENOTSUP;
    } else {
        rc = ble_hs_cfg.store_read_cb(obj_type, key, val);
    }

    ble_hs_unlock();

    return rc;
}

int
ble_store_write(int obj_type, const union ble_store_value *val)
{
    int rc;

    if (ble_hs_cfg.store_write_cb == NULL) {
        return BLE_HS_ENOTSUP;
    }

    while (1) {
        ble_hs_lock();
        rc = ble_hs_cfg.store_write_cb(obj_type, val);
        ble_hs_unlock();

        switch (rc) {
        case 0:
            return 0;
        case BLE_HS_ESTORE_CAP:
            /* Record didn't fit.  Give the application the opportunity to free
             * up some space.
             */
            rc = ble_store_overflow_event(obj_type, val);
            if (rc != 0) {
                return rc;
            }

            /* Application made room for the record; try again. */
            break;

        default:
            return rc;
        }
    }
}

int
ble_store_delete(int obj_type, const union ble_store_key *key)
{
    int rc;

    ble_hs_lock();

    if (ble_hs_cfg.store_delete_cb == NULL) {
        rc = BLE_HS_ENOTSUP;
    } else {
        rc = ble_hs_cfg.store_delete_cb(obj_type, key);
    }

    ble_hs_unlock();

    return rc;
}

static int
ble_store_status(struct ble_store_status_event *event)
{
    int rc;

    BLE_HS_DBG_ASSERT(!ble_hs_locked_by_cur_task());

    if (ble_hs_cfg.store_status_cb == NULL) {
        rc = BLE_HS_ENOTSUP;
    } else {
        rc = ble_hs_cfg.store_status_cb(event, ble_hs_cfg.store_status_arg);
    }

    return rc;
}

int
ble_store_overflow_event(int obj_type, const union ble_store_value *value)
{
    struct ble_store_status_event event;

    event.event_code = BLE_STORE_EVENT_OVERFLOW;
    event.overflow.obj_type = obj_type;
    event.overflow.value = value;

    return ble_store_status(&event);
}

int
ble_store_full_event(int obj_type, uint16_t conn_handle)
{
    struct ble_store_status_event event;

    event.event_code = BLE_STORE_EVENT_FULL;
    event.full.obj_type = obj_type;
    event.full.conn_handle = conn_handle;

    return ble_store_status(&event);
}

int
ble_store_read_our_sec(const struct ble_store_key_sec *key_sec,
                       struct ble_store_value_sec *value_sec)
{
    const union ble_store_key *store_key;
    union ble_store_value *store_value;
    int rc;

    BLE_HS_DBG_ASSERT(key_sec->peer_addr.type == BLE_ADDR_PUBLIC ||
                      key_sec->peer_addr.type == BLE_ADDR_RANDOM ||
                      ble_addr_cmp(&key_sec->peer_addr, BLE_ADDR_ANY) == 0);

    store_key = (void *)key_sec;
    store_value = (void *)value_sec;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_OUR_SEC, store_key, store_value);
    return rc;
}

static int
ble_store_persist_sec(int obj_type,
                      const struct ble_store_value_sec *value_sec)
{
    union ble_store_value *store_value;
    int rc;

    BLE_HS_DBG_ASSERT(value_sec->peer_addr.type == BLE_ADDR_PUBLIC ||
                      value_sec->peer_addr.type == BLE_ADDR_RANDOM);
    BLE_HS_DBG_ASSERT(value_sec->ltk_present ||
                      value_sec->irk_present ||
                      value_sec->csrk_present);

    store_value = (void *)value_sec;
    rc = ble_store_write(obj_type, store_value);
    return rc;
}

int
ble_store_write_our_sec(const struct ble_store_value_sec *value_sec)
{
    int rc;

    rc = ble_store_persist_sec(BLE_STORE_OBJ_TYPE_OUR_SEC, value_sec);
    return rc;
}

int
ble_store_delete_our_sec(const struct ble_store_key_sec *key_sec)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key_sec;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_OUR_SEC, store_key);
    return rc;
}

int
ble_store_delete_peer_sec(const struct ble_store_key_sec *key_sec)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key_sec;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_PEER_SEC, store_key);
    return rc;
}

int
ble_store_read_peer_sec(const struct ble_store_key_sec *key_sec,
                        struct ble_store_value_sec *value_sec)
{
    union ble_store_value *store_value;
    union ble_store_key *store_key;
    int rc;

    BLE_HS_DBG_ASSERT(key_sec->peer_addr.type == BLE_ADDR_PUBLIC ||
                      key_sec->peer_addr.type == BLE_ADDR_RANDOM);

    store_key = (void *)key_sec;
    store_value = (void *)value_sec;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_PEER_SEC, store_key, store_value);

    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_store_write_peer_sec(const struct ble_store_value_sec *value_sec)
{
    int rc;

    rc = ble_store_persist_sec(BLE_STORE_OBJ_TYPE_PEER_SEC, value_sec);
    if (rc != 0) {
        return rc;
    }

    if (ble_addr_cmp(&value_sec->peer_addr, BLE_ADDR_ANY) &&
        value_sec->irk_present) {

        /* Write the peer IRK to the controller keycache
         * There is not much to do here if it fails */
        rc = ble_hs_pvcy_add_entry(value_sec->peer_addr.val,
                                          value_sec->peer_addr.type,
                                          value_sec->irk);
        if (rc != 0) {
            return rc;
        }
    }

    return 0;
}

int
ble_store_read_cccd(const struct ble_store_key_cccd *key,
                    struct ble_store_value_cccd *out_value)
{
    union ble_store_value *store_value;
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    store_value = (void *)out_value;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_CCCD, store_key, store_value);
    return rc;
}

int
ble_store_write_cccd(const struct ble_store_value_cccd *value)
{
    union ble_store_value *store_value;
    int rc;

    store_value = (void *)value;
    rc = ble_store_write(BLE_STORE_OBJ_TYPE_CCCD, store_value);
    return rc;
}

int
ble_store_delete_cccd(const struct ble_store_key_cccd *key)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_CCCD, store_key);
    return rc;
}

void
ble_store_key_from_value_cccd(struct ble_store_key_cccd *out_key,
                              const struct ble_store_value_cccd *value)
{
    out_key->peer_addr = value->peer_addr;
    out_key->chr_val_handle = value->chr_val_handle;
    out_key->idx = 0;
}

void
ble_store_key_from_value_sec(struct ble_store_key_sec *out_key,
                             const struct ble_store_value_sec *value)
{
    out_key->peer_addr = value->peer_addr;
    out_key->idx = 0;
}

#if MYNEWT_VAL(ENC_ADV_DATA)
int
ble_store_read_ead(const struct ble_store_key_ead *key,
                   struct ble_store_value_ead *out_value)
{
    union ble_store_value *store_value;
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    store_value = (void *)out_value;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_ENC_ADV_DATA, store_key, store_value);
    return rc;
}

int
ble_store_write_ead(const struct ble_store_value_ead *value)
{
    union ble_store_value *store_value;
    int rc;

    store_value = (void *)value;
    rc = ble_store_write(BLE_STORE_OBJ_TYPE_ENC_ADV_DATA, store_value);
    return rc;
}

int
ble_store_delete_ead(const struct ble_store_key_ead *key)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_ENC_ADV_DATA, store_key);
    return rc;
}

void
ble_store_key_from_value_ead(struct ble_store_key_ead *out_key,
                             const struct ble_store_value_ead *value)
{
    out_key->peer_addr = value->peer_addr;
    out_key->idx = 0;
}
#endif
// local irk
int
ble_store_read_local_irk(const struct ble_store_key_local_irk *key,
                   struct ble_store_value_local_irk *out_value)
{
    union ble_store_value *store_value;
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    store_value = (void *)out_value;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_LOCAL_IRK, store_key, store_value);
    return rc;
}

int
ble_store_write_local_irk(const struct ble_store_value_local_irk *value)
{
    union ble_store_value *store_value;
    int rc;

    store_value = (void *)value;
    rc = ble_store_write(BLE_STORE_OBJ_TYPE_LOCAL_IRK, store_value);
    return rc;
}

int
ble_store_delete_local_irk(const struct ble_store_key_local_irk *key)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_LOCAL_IRK, store_key);
    return rc;
}

void
ble_store_key_from_value_local_irk(struct ble_store_key_local_irk *out_key,
                             const struct ble_store_value_local_irk *value)
{
    out_key->addr = value->addr;
    out_key->idx = 0;
}

//
int
ble_store_read_rpa_rec(const struct ble_store_key_rpa_rec *key,
                   struct ble_store_value_rpa_rec *out_value)
{
    union ble_store_value *store_value;
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    store_value = (void *)out_value;
    rc = ble_store_read(BLE_STORE_OBJ_TYPE_PEER_ADDR, store_key, store_value);
    return rc;
}

int
ble_store_write_rpa_rec(const struct ble_store_value_rpa_rec *value)
{
    union ble_store_value *store_value;
    int rc;

    store_value = (void *)value;
    rc = ble_store_write(BLE_STORE_OBJ_TYPE_PEER_ADDR, store_value);
    return rc;
}

int
ble_store_delete_rpa_rec(const struct ble_store_key_rpa_rec *key)
{
    union ble_store_key *store_key;
    int rc;

    store_key = (void *)key;
    rc = ble_store_delete(BLE_STORE_OBJ_TYPE_PEER_ADDR, store_key);
    return rc;
}
void
ble_store_key_from_value_rpa_rec(struct ble_store_key_rpa_rec *out_key,
                             const struct ble_store_value_rpa_rec *value)
{
    out_key->peer_rpa_addr = value->peer_rpa_addr;
    out_key->idx = 0;
}

void
ble_store_key_from_value(int obj_type,
                         union ble_store_key *out_key,
                         const union ble_store_value *value)
{
    switch (obj_type) {
    case BLE_STORE_OBJ_TYPE_OUR_SEC:
    case BLE_STORE_OBJ_TYPE_PEER_SEC:
        ble_store_key_from_value_sec(&out_key->sec, &value->sec);
        break;

    case BLE_STORE_OBJ_TYPE_CCCD:
        ble_store_key_from_value_cccd(&out_key->cccd, &value->cccd);
        break;
#if MYNEWT_VAL(ENC_ADV_DATA)
    case BLE_STORE_OBJ_TYPE_ENC_ADV_DATA:
        ble_store_key_from_value_ead(&out_key->ead, &value->ead);
        break;
#endif
    case BLE_STORE_OBJ_TYPE_PEER_ADDR:
         ble_store_key_from_value_rpa_rec(&out_key->rpa_rec, &value->rpa_rec);
         break;

   case BLE_STORE_OBJ_TYPE_LOCAL_IRK:
        ble_store_key_from_value_local_irk(&out_key->local_irk, &value->local_irk);
         break;

    default:
        BLE_HS_DBG_ASSERT(0);
        break;
    }
}

int
ble_store_iterate(int obj_type,
                  ble_store_iterator_fn *callback,
                  void *cookie)
{
    union ble_store_key key;
    union ble_store_value value;
    int idx = 0;
    uint8_t *pidx;
    int rc;

    /* a magic value to retrieve anything */
    memset(&key, 0, sizeof(key));
    switch(obj_type) {
    case BLE_STORE_OBJ_TYPE_PEER_SEC:
    case BLE_STORE_OBJ_TYPE_OUR_SEC:
        key.sec.peer_addr = *BLE_ADDR_ANY;
        pidx = &key.sec.idx;
        break;
    case BLE_STORE_OBJ_TYPE_CCCD:
        key.cccd.peer_addr = *BLE_ADDR_ANY;
        pidx = &key.cccd.idx;
        break;
#if MYNEWT_VAL(ENC_ADV_DATA)
    case BLE_STORE_OBJ_TYPE_ENC_ADV_DATA:
        key.ead.peer_addr = *BLE_ADDR_ANY;
        pidx = &key.ead.idx;
        break;
#endif
    case BLE_STORE_OBJ_TYPE_PEER_ADDR:
        key.rpa_rec.peer_rpa_addr = *BLE_ADDR_ANY;
        pidx = &key.rpa_rec.idx;
        break;
    case BLE_STORE_OBJ_TYPE_LOCAL_IRK:
        key.local_irk.addr = *BLE_ADDR_ANY;
        pidx = &key.local_irk.idx;
        break;
    default:
        BLE_HS_DBG_ASSERT(0);
        return BLE_HS_EINVAL;
    }

    while (1) {
        *pidx = idx;
        rc = ble_store_read(obj_type, &key, &value);
        switch (rc) {
        case 0:
            if (callback != NULL) {
                rc = callback(obj_type, &value, cookie);
                if (rc != 0) {
                    /* User function indicates to stop iterating. */
                    return 0;
                }
            }
            break;

        case BLE_HS_ENOENT:
            /* No more entries. */
            return 0;

        default:
            /* Read error. */
            return rc;
        }

        idx++;
    }
}

/**
 * Deletes all objects from the BLE host store.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
ble_store_clear(void)
{
    const uint8_t obj_types[] = {
        BLE_STORE_OBJ_TYPE_OUR_SEC,
        BLE_STORE_OBJ_TYPE_PEER_SEC,
        BLE_STORE_OBJ_TYPE_CCCD,
        BLE_STORE_OBJ_TYPE_PEER_ADDR,
        BLE_STORE_OBJ_TYPE_LOCAL_IRK,
#if MYNEWT_VAL(ENC_ADV_DATA)
        BLE_STORE_OBJ_TYPE_ENC_ADV_DATA,
#endif
    };
    union ble_store_key key;
    int obj_type;
    int rc;
    unsigned int i;

    /* A zeroed key will always retrieve the first value. */
    memset(&key, 0, sizeof key);

    for (i = 0; i < sizeof obj_types / sizeof obj_types[0]; i++) {
        obj_type = obj_types[i];

        do {
            rc = ble_store_delete(obj_type, &key);
        } while (rc == 0);

        /* BLE_HS_ENOENT means we deleted everything. */
        if (rc != BLE_HS_ENOENT) {
            return rc;
        }
    }

    return 0;
}
