/*
 * 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 "nimble/nimble_opt.h"
#include "host/ble_sm.h"
#include "ble_hs_priv.h"
#include "ble_sm_priv.h"

#if NIMBLE_BLE_CONNECT
#if MYNEWT_VAL(BLE_SM_SC)

#define BLE_SM_SC_PASSKEY_BYTES     4
#define BLE_SM_SC_PASSKEY_BITS      20

static uint8_t ble_sm_sc_pub_key[64];
static uint8_t ble_sm_sc_priv_key[32];

/**
 * Whether our public-private key pair has been generated.  We generate it on
 * startup for now until we have a non-volatile storage mechanism.
 */
static uint8_t ble_sm_sc_keys_generated;

/**
 * Create some shortened names for the passkey actions so that the table is
 * easier to read.
 */
#define IOACT_NONE      BLE_SM_IOACT_NONE
#define IOACT_OOB       BLE_SM_IOACT_OOB
#define IOACT_INPUT     BLE_SM_IOACT_INPUT
#define IOACT_DISP      BLE_SM_IOACT_DISP
#define IOACT_NUMCMP    BLE_SM_IOACT_NUMCMP

/**
 * This table expresses the required initiator IO action.  Inputs are:
 *     o Responder IO capabilities (from pair response).
 *     o Initiator IO capabilities (from pair request).
 */
static const uint8_t ble_sm_sc_init_ioa[5 /*resp*/ ][5 /*init*/ ] =
{
      /* init */
/*r*/ {IOACT_NONE,    IOACT_NONE,   IOACT_INPUT, IOACT_NONE, IOACT_INPUT},
/*e*/ {IOACT_NONE,    IOACT_NUMCMP, IOACT_INPUT, IOACT_NONE, IOACT_NUMCMP},
/*s*/ {IOACT_DISP,    IOACT_DISP,   IOACT_INPUT, IOACT_NONE, IOACT_DISP},
/*p*/ {IOACT_NONE,    IOACT_NONE,   IOACT_NONE,  IOACT_NONE, IOACT_NONE},
      {IOACT_DISP,    IOACT_NUMCMP, IOACT_INPUT, IOACT_NONE, IOACT_NUMCMP},
};

/**
 * This table expresses the required responder IO action.  Inputs are:
 *     o Responder IO capabilities (from pair response).
 *     o Initiator IO capabilities (from pair request).
 */
static const uint8_t ble_sm_sc_resp_ioa[5 /*resp*/ ][5 /*init*/ ] =
{
      /* init */
/*r*/ {IOACT_NONE,    IOACT_NONE,   IOACT_DISP,  IOACT_NONE, IOACT_DISP},
/*e*/ {IOACT_NONE,    IOACT_NUMCMP, IOACT_DISP,  IOACT_NONE, IOACT_NUMCMP},
/*s*/ {IOACT_INPUT,   IOACT_INPUT,  IOACT_INPUT, IOACT_NONE, IOACT_INPUT},
/*p*/ {IOACT_NONE,    IOACT_NONE,   IOACT_NONE,  IOACT_NONE, IOACT_NONE},
      {IOACT_INPUT,   IOACT_NUMCMP, IOACT_DISP,  IOACT_NONE, IOACT_NUMCMP},
};

#if MYNEWT_VAL(BLE_HS_DEBUG)

static uint8_t ble_sm_dbg_sc_pub_key[64];
static uint8_t ble_sm_dbg_sc_priv_key[32];
static uint8_t ble_sm_dbg_sc_keys_set;

void
ble_sm_dbg_set_sc_keys(uint8_t *pubkey, uint8_t *privkey)
{
    memcpy(ble_sm_dbg_sc_pub_key, pubkey,
           sizeof ble_sm_dbg_sc_pub_key);
    memcpy(ble_sm_dbg_sc_priv_key, privkey,
           sizeof ble_sm_dbg_sc_priv_key);
    ble_sm_dbg_sc_keys_set = 1;
}

#endif

int
ble_sm_sc_io_action(struct ble_sm_proc *proc, uint8_t *action)
{
    struct ble_sm_pair_cmd *pair_req, *pair_rsp;

    pair_req = (struct ble_sm_pair_cmd *) &proc->pair_req[1];
    pair_rsp = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1];

    if (pair_req->oob_data_flag == BLE_SM_PAIR_OOB_YES ||
        pair_rsp->oob_data_flag == BLE_SM_PAIR_OOB_YES) {
        *action = BLE_SM_IOACT_OOB_SC;
    } else if (!(pair_req->authreq & BLE_SM_PAIR_AUTHREQ_MITM) &&
               !(pair_rsp->authreq & BLE_SM_PAIR_AUTHREQ_MITM)) {

        *action = BLE_SM_IOACT_NONE;
    } else if (pair_req->io_cap >= BLE_SM_IO_CAP_RESERVED ||
               pair_rsp->io_cap >= BLE_SM_IO_CAP_RESERVED) {
        *action = BLE_SM_IOACT_NONE;
    } else if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        *action = ble_sm_sc_init_ioa[pair_rsp->io_cap][pair_req->io_cap];
    } else {
        *action = ble_sm_sc_resp_ioa[pair_rsp->io_cap][pair_req->io_cap];
    }

    switch (*action) {
    case BLE_SM_IOACT_NONE:
        proc->pair_alg = BLE_SM_PAIR_ALG_JW;
        break;

    case BLE_SM_IOACT_OOB_SC:
        proc->pair_alg = BLE_SM_PAIR_ALG_OOB;
        proc->flags |= BLE_SM_PROC_F_AUTHENTICATED;
        break;

    case BLE_SM_IOACT_INPUT:
    case BLE_SM_IOACT_DISP:
        proc->pair_alg = BLE_SM_PAIR_ALG_PASSKEY;
        proc->flags |= BLE_SM_PROC_F_AUTHENTICATED;
        break;

    case BLE_SM_IOACT_NUMCMP:
        proc->pair_alg = BLE_SM_PAIR_ALG_NUMCMP;
        proc->flags |= BLE_SM_PROC_F_AUTHENTICATED;
        break;

    default:
        BLE_HS_DBG_ASSERT(0);
        return BLE_HS_EINVAL;
    }

    return 0;
}

static int
ble_sm_gen_pub_priv(uint8_t *pub, uint8_t *priv)
{
    int rc;

#if MYNEWT_VAL(BLE_HS_DEBUG)
    if (ble_sm_dbg_sc_keys_set) {
        ble_sm_dbg_sc_keys_set = 0;
        memcpy(pub, ble_sm_dbg_sc_pub_key, sizeof ble_sm_dbg_sc_pub_key);
        memcpy(priv, ble_sm_dbg_sc_priv_key, sizeof ble_sm_dbg_sc_priv_key);
        return 0;
    }
#endif

    rc = ble_sm_alg_gen_key_pair(pub, priv);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

static int
ble_sm_sc_ensure_keys_generated(void)
{
    int rc;

    if (!ble_sm_sc_keys_generated) {
        rc = ble_sm_gen_pub_priv(ble_sm_sc_pub_key, ble_sm_sc_priv_key);
        if (rc != 0) {
            return rc;
        }

        ble_sm_sc_keys_generated = 1;
    }

    BLE_HS_LOG(DEBUG, "our pubkey=");
    ble_hs_log_flat_buf(&ble_sm_sc_pub_key, 64);
    BLE_HS_LOG(DEBUG, "\n");
    BLE_HS_LOG(DEBUG, "our privkey=");
    ble_hs_log_flat_buf(&ble_sm_sc_priv_key, 32);
    BLE_HS_LOG(DEBUG, "\n");

    return 0;
}

/* Initiator does not send a confirm when pairing algorithm is any of:
 *     o just works
 *     o numeric comparison
 * (vol. 3, part H, 2.3.5.6.2)
 */
static int
ble_sm_sc_initiator_txes_confirm(struct ble_sm_proc *proc)
{
    BLE_HS_DBG_ASSERT(proc->flags & BLE_SM_PROC_F_SC);

    return proc->pair_alg != BLE_SM_PAIR_ALG_JW &&
           proc->pair_alg != BLE_SM_PAIR_ALG_NUMCMP;
}

/* Responder does not verify the initiator's random number when pairing
 * algorithm is any of:
 *     o just works
 *     o numeric comparison
 * (vol. 3, part H, 2.3.5.6.2)
 */
static int
ble_sm_sc_responder_verifies_random(struct ble_sm_proc *proc)
{
    BLE_HS_DBG_ASSERT(proc->flags & BLE_SM_PROC_F_SC);

    return proc->pair_alg != BLE_SM_PAIR_ALG_JW &&
           proc->pair_alg != BLE_SM_PAIR_ALG_NUMCMP;
}

/**
 * Generates the Ri byte used in the confirm message.  On success, the byte is
 * written to the supplied procedure object.
 */
static int
ble_sm_sc_gen_ri(struct ble_sm_proc *proc)
{
    int byte;
    int bit;

    switch (proc->pair_alg) {
    case BLE_SM_PAIR_ALG_JW:
    case BLE_SM_PAIR_ALG_NUMCMP:
    case BLE_SM_PAIR_ALG_OOB:
        proc->ri = 0;
        return 0;

    case BLE_SM_PAIR_ALG_PASSKEY:
        BLE_HS_DBG_ASSERT(proc->passkey_bits_exchanged <
                          BLE_SM_SC_PASSKEY_BITS);

        byte = proc->passkey_bits_exchanged / 8;
        bit = proc->passkey_bits_exchanged % 8;
        proc->ri = 0x80 | !!(proc->tk[byte] & (1 << bit));

        proc->passkey_bits_exchanged++;

        return 0;

    default:
        BLE_HS_DBG_ASSERT(0);
        return BLE_HS_EUNKNOWN;
    }
}

void
ble_sm_sc_oob_confirm(struct ble_sm_proc *proc, struct ble_sm_result *res)
{
    int err;
    bool match;
    uint8_t c[16];

    /* Authentication stage 1: Step 5 */
    if (proc->oob_data_remote) {
        err = ble_sm_alg_f4(proc->pub_key_peer.x, proc->pub_key_peer.x,
                            proc->oob_data_remote->r, 0, c);
        if (err) {
            res->sm_err = BLE_SM_ERR_UNSPECIFIED;
            res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_UNSPECIFIED);
            res->enc_cb = 1;
            return;
        }

        match = (memcmp(c, proc->oob_data_remote->c, sizeof(c)) == 0);
        if (!match) {
            /* Random number mismatch. */
            res->sm_err = BLE_SM_ERR_CONFIRM_MISMATCH;
            res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_CONFIRM_MISMATCH);
            res->enc_cb = 1;
            return;
        }
    }

    if ((proc->flags & BLE_SM_PROC_F_INITIATOR) ||
        (proc->flags & BLE_SM_PROC_F_ADVANCE_ON_IO)) {
        /* If is initiator or was waiting on
         * IO then execute step 6: send Random
         */
        res->execute = 1;
    }
}

void
ble_sm_sc_confirm_exec(struct ble_sm_proc *proc, struct ble_sm_result *res)
{
    struct ble_sm_pair_confirm *cmd;
    struct os_mbuf *txom;
    int rc;

    rc = ble_sm_sc_gen_ri(proc);
    if (rc != 0) {
        res->app_status = rc;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_CONFIRM, sizeof(*cmd), &txom);
    if (cmd == NULL) {
        rc = BLE_HS_ENOMEM;
        res->app_status = rc;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    rc = ble_sm_alg_f4(ble_sm_sc_pub_key, proc->pub_key_peer.x,
                       ble_sm_our_pair_rand(proc), proc->ri, cmd->value);
    if (rc != 0) {
        os_mbuf_free_chain(txom);
        res->app_status = rc;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    rc = ble_sm_tx(proc->conn_handle, txom);
    if (rc != 0) {
        res->app_status = rc;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    if (!(proc->flags & BLE_SM_PROC_F_INITIATOR)) {
        proc->state = BLE_SM_PROC_STATE_RANDOM;
    }
}

static void
ble_sm_sc_gen_numcmp(struct ble_sm_proc *proc, struct ble_sm_result *res)
{
    uint8_t *pka;
    uint8_t *pkb;

    if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        pka = ble_sm_sc_pub_key;
        pkb = proc->pub_key_peer.x;
    } else {
        pka = proc->pub_key_peer.x;
        pkb = ble_sm_sc_pub_key;
    }
    res->app_status = ble_sm_alg_g2(pka, pkb, proc->randm, proc->rands,
                                    &res->passkey_params.numcmp);
    if (res->app_status != 0) {
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        res->enc_cb = 1;
    }
}

/**
 * Advances the supplied procedure object to the next state after it has
 * completed the random state.
 */
static int
ble_sm_sc_random_advance(struct ble_sm_proc *proc)
{
    int rc;

    if (proc->pair_alg != BLE_SM_PAIR_ALG_PASSKEY ||
        proc->passkey_bits_exchanged >= BLE_SM_SC_PASSKEY_BITS) {

        proc->state = BLE_SM_PROC_STATE_DHKEY_CHECK;
    } else {
        proc->state = BLE_SM_PROC_STATE_CONFIRM;
        rc = ble_sm_gen_pair_rand(ble_sm_our_pair_rand(proc));
        if (rc != 0) {
            return rc;
        }
    }

    return 0;
}

void
ble_sm_sc_random_exec(struct ble_sm_proc *proc, struct ble_sm_result *res)
{
    struct ble_sm_pair_random *cmd;
    struct os_mbuf *txom;
    uint8_t ioact;
    int rc;

    cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_RANDOM, sizeof(*cmd), &txom);
    if (cmd == NULL) {
        rc = BLE_HS_ENOMEM;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    memcpy(cmd->value, ble_sm_our_pair_rand(proc), 16);

    rc = ble_sm_tx(proc->conn_handle, txom);
    if (rc != 0) {
        res->app_status = rc;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    if (!(proc->flags & BLE_SM_PROC_F_INITIATOR)) {
        rc = ble_sm_sc_random_advance(proc);
        if (rc != 0) {
            res->app_status = rc;
            res->enc_cb = 1;
            res->sm_err = BLE_SM_ERR_UNSPECIFIED;
            return;
        }

        rc = ble_sm_sc_io_action(proc, &ioact);
        BLE_HS_DBG_ASSERT(rc == 0);

        if (ble_sm_ioact_state(ioact) == proc->state &&
            !(proc->flags & BLE_SM_PROC_F_IO_INJECTED)) {

            res->passkey_params.action = ioact;
            BLE_HS_DBG_ASSERT(ioact == BLE_SM_IOACT_NUMCMP);
            ble_sm_sc_gen_numcmp(proc, res);
        }
    }
}

void
ble_sm_sc_random_rx(struct ble_sm_proc *proc, struct ble_sm_result *res)
{
    uint8_t confirm_val[16];
    uint8_t ia[6];
    uint8_t ra[6];
    uint8_t ioact;
    uint8_t iat;
    uint8_t rat;
    int rc;

    if (proc->pair_alg != BLE_SM_PAIR_ALG_OOB && (
        proc->flags & BLE_SM_PROC_F_INITIATOR ||
        ble_sm_sc_responder_verifies_random(proc))) {

        BLE_HS_LOG(DEBUG, "tk=");
        ble_hs_log_flat_buf(proc->tk, 16);
        BLE_HS_LOG(DEBUG, "\n");

        rc = ble_sm_alg_f4(proc->pub_key_peer.x, ble_sm_sc_pub_key,
                           ble_sm_peer_pair_rand(proc), proc->ri,
                           confirm_val);
        if (rc != 0) {
            res->app_status = rc;
            res->sm_err = BLE_SM_ERR_UNSPECIFIED;
            res->enc_cb = 1;
            return;
        }

        if (memcmp(proc->confirm_peer, confirm_val, 16) != 0) {
            /* Random number mismatch. */
            res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_CONFIRM_MISMATCH);
            res->sm_err = BLE_SM_ERR_CONFIRM_MISMATCH;
            res->enc_cb = 1;
            return;
        }
    }

    /* Calculate the mac key and ltk. */
    ble_sm_ia_ra(proc, &iat, ia, &rat, ra);
    rc = ble_sm_alg_f5(proc->dhkey, proc->randm, proc->rands,
                       iat, ia, rat, ra, proc->mackey, proc->ltk);
    if (rc != 0) {
        res->app_status = rc;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        res->enc_cb = 1;
        return;
    }

    /* Ensure proper key size */
    memset(proc->ltk + proc->key_size, 0, sizeof proc->ltk - proc->key_size);

    /* Ensure the ltk gets persisted when the pairing procedure succeeds. */
    memcpy(proc->our_keys.ltk, proc->ltk, sizeof proc->our_keys.ltk);
    proc->our_keys.ltk_valid = 1;
    proc->our_keys.ediv = 0;
    proc->our_keys.rand_val = 0;
    proc->our_keys.ediv_rand_valid = 1;
    proc->our_keys.key_size = proc->key_size;

    memcpy(proc->peer_keys.ltk, proc->ltk, sizeof proc->peer_keys.ltk);
    proc->peer_keys.ltk_valid = 1;
    proc->peer_keys.ediv = 0;
    proc->peer_keys.rand_val = 0;
    proc->peer_keys.ediv_rand_valid = 1;
    proc->peer_keys.key_size = proc->key_size;

    if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        ble_sm_sc_random_advance(proc);

        rc = ble_sm_sc_io_action(proc, &ioact);
        if (rc != 0) {
            BLE_HS_DBG_ASSERT(0);
        }

        if (ble_sm_ioact_state(ioact) == proc->state &&
            !(proc->flags & BLE_SM_PROC_F_IO_INJECTED)) {

            res->passkey_params.action = ioact;
            BLE_HS_DBG_ASSERT(ioact == BLE_SM_IOACT_NUMCMP);
            ble_sm_sc_gen_numcmp(proc, res);
        } else {
            res->execute = 1;
        }
    } else {
        if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB &&
            !(proc->flags & BLE_SM_PROC_F_IO_INJECTED)) {
            proc->flags |= BLE_SM_PROC_F_ADVANCE_ON_IO;
        } else {
            res->execute = 1;
        }
    }
}

void
ble_sm_sc_public_key_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
                          void *arg)
{
    struct ble_sm_public_key *cmd;
    struct os_mbuf *txom;
    uint8_t ioact;
    int rc;

    res->app_status = ble_sm_sc_ensure_keys_generated();
    if (res->app_status != 0) {
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_PUBLIC_KEY, sizeof(*cmd), &txom);
    if (!cmd) {
        res->app_status = BLE_HS_ENOMEM;
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    memcpy(cmd->x, ble_sm_sc_pub_key + 0, 32);
    memcpy(cmd->y, ble_sm_sc_pub_key + 32, 32);

    res->app_status = ble_sm_tx(proc->conn_handle, txom);
    if (res->app_status != 0) {
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    if (!(proc->flags & BLE_SM_PROC_F_INITIATOR)) {
        if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB) {
            proc->state = BLE_SM_PROC_STATE_RANDOM;
        } else {
            proc->state = BLE_SM_PROC_STATE_CONFIRM;
        }

        rc = ble_sm_sc_io_action(proc, &ioact);
        if (rc != 0) {
            BLE_HS_DBG_ASSERT(0);
        }

        if (ble_sm_ioact_state(ioact) == proc->state) {
            res->passkey_params.action = ioact;
        }

        if (ble_sm_proc_can_advance(proc) &&
            !ble_sm_sc_initiator_txes_confirm(proc)) {

            res->execute = 1;
        }
    }
}

void
ble_sm_sc_public_key_rx(uint16_t conn_handle, struct os_mbuf **om,
                        struct ble_sm_result *res)
{
    struct ble_sm_public_key *cmd;
    struct ble_sm_proc *proc;
    uint8_t ioact;
    int rc;

    res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd));
    if (res->app_status != 0) {
        res->enc_cb = 1;
        return;
    }

    res->app_status = ble_sm_sc_ensure_keys_generated();
    if (res->app_status != 0) {
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    cmd = (struct ble_sm_public_key *)(*om)->om_data;
    /* Check if the X component of peer public key is same as X
     * component of our generated public key. Return fail if they match. */
    if (memcmp(cmd->x, ble_sm_sc_pub_key, 32) == 0) {
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_AUTHREQ;
        return;
    }

    ble_hs_lock();
    proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_PUBLIC_KEY, -1,
                            NULL);
    if (proc == NULL) {
        /**
         * Unexpected public key information received
         * Recommended action: Ignore
         */
        res->app_status = BLE_HS_ENOENT;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        res->out_of_order = 1;
    } else {
        memcpy(&proc->pub_key_peer, cmd, sizeof(*cmd));
        rc = ble_sm_alg_gen_dhkey(proc->pub_key_peer.x,
                                  proc->pub_key_peer.y,
                                  ble_sm_sc_priv_key,
                                  proc->dhkey);
        if (rc != 0) {
            res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_DHKEY);
            res->sm_err = BLE_SM_ERR_DHKEY;
            res->enc_cb = 1;
        } else {
            if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
                if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB) {
                    proc->state = BLE_SM_PROC_STATE_RANDOM;
                } else {
                    proc->state = BLE_SM_PROC_STATE_CONFIRM;
                }

                rc = ble_sm_sc_io_action(proc, &ioact);
                if (rc != 0) {
                        BLE_HS_DBG_ASSERT(0);
                }

                if (ble_sm_ioact_state(ioact) == proc->state) {
                    res->passkey_params.action = ioact;
                }

                if (ble_sm_proc_can_advance(proc) &&
                    ble_sm_sc_initiator_txes_confirm(proc)) {

                    res->execute = 1;
                }
            } else {
                res->execute = 1;
            }
        }
    }
    ble_hs_unlock();
}

static void
ble_sm_sc_dhkey_addrs(struct ble_sm_proc *proc, ble_addr_t *our_addr,
                      ble_addr_t *peer_addr)
{
    struct ble_hs_conn_addrs addrs;
    struct ble_hs_conn *conn;

    conn = ble_hs_conn_find_assert(proc->conn_handle);

    ble_hs_conn_addrs(conn, &addrs);

    *our_addr = addrs.our_ota_addr;
    *peer_addr = addrs.peer_ota_addr;
}

void
ble_sm_sc_dhkey_check_exec(struct ble_sm_proc *proc, struct ble_sm_result *res,
                           void *arg)
{
    struct ble_sm_dhkey_check *cmd;
    ble_addr_t our_addr;
    ble_addr_t peer_addr;
    struct os_mbuf *txom;
    uint8_t *iocap;
    int rc;

    if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        struct ble_sm_pair_cmd *pair_req;

        pair_req  = (struct ble_sm_pair_cmd *) &proc->pair_req[1];
        iocap = &pair_req->io_cap;
    } else {
        struct ble_sm_pair_cmd *pair_rsp;

        pair_rsp  = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1];
        iocap = &pair_rsp->io_cap;
    }

    if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB) {
        if (proc->oob_data_remote) {
            memcpy(proc->tk, proc->oob_data_remote->r, 16);
        } else {
            memset(proc->tk, 0, 16);
        }
    }

    ble_sm_sc_dhkey_addrs(proc, &our_addr, &peer_addr);

    cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_DHKEY_CHECK, sizeof(*cmd), &txom);
    if (!cmd) {
        rc = BLE_HS_ENOMEM;
        goto err;
    }

    rc = ble_sm_alg_f6(proc->mackey, ble_sm_our_pair_rand(proc),
                       ble_sm_peer_pair_rand(proc), proc->tk, iocap,
                       our_addr.type, our_addr.val, peer_addr.type,
                       peer_addr.val, cmd->value);
    if (rc != 0) {
        os_mbuf_free_chain(txom);
        goto err;
    }

    rc = ble_sm_tx(proc->conn_handle, txom);
    if (rc != 0) {
        goto err;
    }

    if (!(proc->flags & BLE_SM_PROC_F_INITIATOR)) {
        proc->state = BLE_SM_PROC_STATE_LTK_START;
    }

    return;

err:
    res->app_status = rc;
    res->enc_cb = 1;
    res->sm_err = BLE_SM_ERR_UNSPECIFIED;
}

static void
ble_sm_dhkey_check_process(struct ble_sm_proc *proc,
                           struct ble_sm_dhkey_check *cmd,
                           struct ble_sm_result *res)
{
    uint8_t exp_value[16];
    ble_addr_t our_addr;
    ble_addr_t peer_addr;
    uint8_t *iocap;
    uint8_t ioact;
    int rc;

    if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        struct ble_sm_pair_cmd *pair_rsp;

        pair_rsp  = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1];
        iocap = &pair_rsp->io_cap;

        if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB) {
            if (pair_rsp->oob_data_flag) {
                memcpy(proc->tk, proc->oob_data_local->r, 16);
            } else {
                memset(proc->tk, 0, 16);
            }
        }
    } else {
        struct ble_sm_pair_cmd *pair_req;

        pair_req  = (struct ble_sm_pair_cmd *) &proc->pair_req[1];
        iocap = &pair_req->io_cap;

        if (proc->pair_alg == BLE_SM_PAIR_ALG_OOB) {
            if (pair_req->oob_data_flag) {
                memcpy(proc->tk, proc->oob_data_local->r, 16);
            } else {
                memset(proc->tk, 0, 16);
            }
        }
    }

    ble_sm_sc_dhkey_addrs(proc, &our_addr, &peer_addr);
    BLE_HS_LOG(DEBUG, "tk=");
    ble_hs_log_flat_buf(proc->tk, 16);
    BLE_HS_LOG(DEBUG, "\n");

    res->app_status = ble_sm_alg_f6(proc->mackey,
                                    ble_sm_peer_pair_rand(proc),
                                    ble_sm_our_pair_rand(proc),
                                    proc->tk, iocap,
                                    peer_addr.type, peer_addr.val,
                                    our_addr.type, our_addr.val,
                                    exp_value);
    if (res->app_status != 0) {
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        res->enc_cb = 1;
        return;
    }

    if (memcmp(cmd->value, exp_value, 16) != 0) {
        /* Random number mismatch. */
        res->sm_err = BLE_SM_ERR_DHKEY;
        res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_DHKEY);
        res->enc_cb = 1;
        return;
    }

    rc = ble_sm_sc_io_action(proc, &ioact);
    if (rc != 0) {
        BLE_HS_DBG_ASSERT(0);
    }

    if (ble_sm_ioact_state(ioact) == proc->state) {
        proc->flags |= BLE_SM_PROC_F_ADVANCE_ON_IO;
    }

    if (ble_sm_proc_can_advance(proc)) {
        if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
            proc->state = BLE_SM_PROC_STATE_ENC_START;
        }

        res->execute = 1;
    }
}

void
ble_sm_sc_dhkey_check_rx(uint16_t conn_handle, struct os_mbuf **om,
                         struct ble_sm_result *res)
{
    struct ble_sm_dhkey_check *cmd;
    struct ble_sm_proc *proc;

    res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd));
    if (res->app_status != 0) {
        res->enc_cb = 1;
        res->sm_err = BLE_SM_ERR_UNSPECIFIED;
        return;
    }

    cmd = (struct ble_sm_dhkey_check *)(*om)->om_data;

    ble_hs_lock();
    proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_DHKEY_CHECK, -1,
                            NULL);
    if (proc == NULL) {
        /**
         * Unexpected DHKey check values received
         * Recommended action: Ignore
         */
        res->app_status = BLE_HS_ENOENT;
        res->out_of_order = 1;
    } else {
        ble_sm_dhkey_check_process(proc, cmd, res);
    }
    ble_hs_unlock();
}

bool
ble_sm_sc_oob_data_check(struct ble_sm_proc *proc,
                         bool oob_data_local_present,
                         bool oob_data_remote_present)
{
    struct ble_sm_pair_cmd *pair_req;
    struct ble_sm_pair_cmd *pair_rsp;
    bool req_oob_present;
    bool rsp_oob_present;

    pair_req = (struct ble_sm_pair_cmd *) &proc->pair_req[1];
    pair_rsp = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1];
    req_oob_present = pair_req->oob_data_flag == BLE_SM_PAIR_OOB_YES;
    rsp_oob_present = pair_rsp->oob_data_flag == BLE_SM_PAIR_OOB_YES;

    if (proc->flags & BLE_SM_PROC_F_INITIATOR) {
        return req_oob_present == oob_data_remote_present;
    } else {
        return rsp_oob_present == oob_data_remote_present;
    }
}

int
ble_sm_sc_oob_generate_data(struct ble_sm_sc_oob_data *oob_data)
{
    int rc;

#if !MYNEWT_VAL(BLE_SM_SC)
    return BLE_HS_ENOTSUP;
#endif

    rc = ble_sm_sc_ensure_keys_generated();
    if (rc) {
        return rc;
    }

    rc = ble_hs_hci_util_rand(oob_data->r, 16);
    if (rc) {
        return rc;
    }

    rc = ble_sm_alg_f4(ble_sm_sc_pub_key, ble_sm_sc_pub_key, oob_data->r, 0,
                       oob_data->c);
    if (rc) {
        return rc;
    }

    return 0;
}

void
ble_sm_sc_init(void)
{
    ble_sm_alg_ecc_init();
    ble_sm_sc_keys_generated = 0;
}

#endif  /* MYNEWT_VAL(BLE_SM_SC) */
#endif
