/*
 * 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 <stddef.h>
#include <errno.h>
#include "ble_hs_priv.h"

#if NIMBLE_BLE_CONNECT
static uint16_t ble_att_preferred_mtu_val;

/** Dispatch table for incoming ATT requests.  Sorted by op code. */
typedef int ble_att_rx_fn(uint16_t conn_handle, struct os_mbuf **om);
struct ble_att_rx_dispatch_entry {
    uint8_t bde_op;
    ble_att_rx_fn *bde_fn;
};

/** Dispatch table for incoming ATT commands.  Must be ordered by op code. */
static const struct ble_att_rx_dispatch_entry ble_att_rx_dispatch[] = {
    { BLE_ATT_OP_ERROR_RSP,            ble_att_clt_rx_error },
    { BLE_ATT_OP_MTU_REQ,              ble_att_svr_rx_mtu },
    { BLE_ATT_OP_MTU_RSP,              ble_att_clt_rx_mtu },
    { BLE_ATT_OP_FIND_INFO_REQ,        ble_att_svr_rx_find_info },
    { BLE_ATT_OP_FIND_INFO_RSP,        ble_att_clt_rx_find_info },
    { BLE_ATT_OP_FIND_TYPE_VALUE_REQ,  ble_att_svr_rx_find_type_value },
    { BLE_ATT_OP_FIND_TYPE_VALUE_RSP,  ble_att_clt_rx_find_type_value },
    { BLE_ATT_OP_READ_TYPE_REQ,        ble_att_svr_rx_read_type },
    { BLE_ATT_OP_READ_TYPE_RSP,        ble_att_clt_rx_read_type },
    { BLE_ATT_OP_READ_REQ,             ble_att_svr_rx_read },
    { BLE_ATT_OP_READ_RSP,             ble_att_clt_rx_read },
    { BLE_ATT_OP_READ_BLOB_REQ,        ble_att_svr_rx_read_blob },
    { BLE_ATT_OP_READ_BLOB_RSP,        ble_att_clt_rx_read_blob },
    { BLE_ATT_OP_READ_MULT_REQ,        ble_att_svr_rx_read_mult },
    { BLE_ATT_OP_READ_MULT_RSP,        ble_att_clt_rx_read_mult },
    { BLE_ATT_OP_READ_GROUP_TYPE_REQ,  ble_att_svr_rx_read_group_type },
    { BLE_ATT_OP_READ_GROUP_TYPE_RSP,  ble_att_clt_rx_read_group_type },
    { BLE_ATT_OP_WRITE_REQ,            ble_att_svr_rx_write },
    { BLE_ATT_OP_WRITE_RSP,            ble_att_clt_rx_write },
    { BLE_ATT_OP_PREP_WRITE_REQ,       ble_att_svr_rx_prep_write },
    { BLE_ATT_OP_PREP_WRITE_RSP,       ble_att_clt_rx_prep_write },
    { BLE_ATT_OP_EXEC_WRITE_REQ,       ble_att_svr_rx_exec_write },
    { BLE_ATT_OP_EXEC_WRITE_RSP,       ble_att_clt_rx_exec_write },
    { BLE_ATT_OP_NOTIFY_REQ,           ble_att_svr_rx_notify },
    { BLE_ATT_OP_INDICATE_REQ,         ble_att_svr_rx_indicate },
    { BLE_ATT_OP_INDICATE_RSP,         ble_att_clt_rx_indicate },
    { BLE_ATT_OP_WRITE_CMD,            ble_att_svr_rx_write_no_rsp },
};

#define BLE_ATT_RX_DISPATCH_SZ \
    (sizeof ble_att_rx_dispatch / sizeof ble_att_rx_dispatch[0])

STATS_SECT_DECL(ble_att_stats) ble_att_stats;
STATS_NAME_START(ble_att_stats)
    STATS_NAME(ble_att_stats, error_rsp_rx)
    STATS_NAME(ble_att_stats, error_rsp_tx)
    STATS_NAME(ble_att_stats, mtu_req_rx)
    STATS_NAME(ble_att_stats, mtu_req_tx)
    STATS_NAME(ble_att_stats, mtu_rsp_rx)
    STATS_NAME(ble_att_stats, mtu_rsp_tx)
    STATS_NAME(ble_att_stats, find_info_req_rx)
    STATS_NAME(ble_att_stats, find_info_req_tx)
    STATS_NAME(ble_att_stats, find_info_rsp_rx)
    STATS_NAME(ble_att_stats, find_info_rsp_tx)
    STATS_NAME(ble_att_stats, find_type_value_req_rx)
    STATS_NAME(ble_att_stats, find_type_value_req_tx)
    STATS_NAME(ble_att_stats, find_type_value_rsp_rx)
    STATS_NAME(ble_att_stats, find_type_value_rsp_tx)
    STATS_NAME(ble_att_stats, read_type_req_rx)
    STATS_NAME(ble_att_stats, read_type_req_tx)
    STATS_NAME(ble_att_stats, read_type_rsp_rx)
    STATS_NAME(ble_att_stats, read_type_rsp_tx)
    STATS_NAME(ble_att_stats, read_req_rx)
    STATS_NAME(ble_att_stats, read_req_tx)
    STATS_NAME(ble_att_stats, read_rsp_rx)
    STATS_NAME(ble_att_stats, read_rsp_tx)
    STATS_NAME(ble_att_stats, read_blob_req_rx)
    STATS_NAME(ble_att_stats, read_blob_req_tx)
    STATS_NAME(ble_att_stats, read_blob_rsp_rx)
    STATS_NAME(ble_att_stats, read_blob_rsp_tx)
    STATS_NAME(ble_att_stats, read_mult_req_rx)
    STATS_NAME(ble_att_stats, read_mult_req_tx)
    STATS_NAME(ble_att_stats, read_mult_rsp_rx)
    STATS_NAME(ble_att_stats, read_mult_rsp_tx)
    STATS_NAME(ble_att_stats, read_group_type_req_rx)
    STATS_NAME(ble_att_stats, read_group_type_req_tx)
    STATS_NAME(ble_att_stats, read_group_type_rsp_rx)
    STATS_NAME(ble_att_stats, read_group_type_rsp_tx)
    STATS_NAME(ble_att_stats, write_req_rx)
    STATS_NAME(ble_att_stats, write_req_tx)
    STATS_NAME(ble_att_stats, write_rsp_rx)
    STATS_NAME(ble_att_stats, write_rsp_tx)
    STATS_NAME(ble_att_stats, prep_write_req_rx)
    STATS_NAME(ble_att_stats, prep_write_req_tx)
    STATS_NAME(ble_att_stats, prep_write_rsp_rx)
    STATS_NAME(ble_att_stats, prep_write_rsp_tx)
    STATS_NAME(ble_att_stats, exec_write_req_rx)
    STATS_NAME(ble_att_stats, exec_write_req_tx)
    STATS_NAME(ble_att_stats, exec_write_rsp_rx)
    STATS_NAME(ble_att_stats, exec_write_rsp_tx)
    STATS_NAME(ble_att_stats, notify_req_rx)
    STATS_NAME(ble_att_stats, notify_req_tx)
    STATS_NAME(ble_att_stats, indicate_req_rx)
    STATS_NAME(ble_att_stats, indicate_req_tx)
    STATS_NAME(ble_att_stats, indicate_rsp_rx)
    STATS_NAME(ble_att_stats, indicate_rsp_tx)
    STATS_NAME(ble_att_stats, write_cmd_rx)
    STATS_NAME(ble_att_stats, write_cmd_tx)
STATS_NAME_END(ble_att_stats)

static const struct ble_att_rx_dispatch_entry *
ble_att_rx_dispatch_entry_find(uint8_t op)
{
    const struct ble_att_rx_dispatch_entry *entry;
    int i;

    for (i = 0; i < BLE_ATT_RX_DISPATCH_SZ; i++) {
        entry = ble_att_rx_dispatch + i;
        if (entry->bde_op == op) {
            return entry;
        }

        if (entry->bde_op > op) {
            break;
        }
    }

    return NULL;
}

int
ble_att_conn_chan_find(uint16_t conn_handle, struct ble_hs_conn **out_conn,
                       struct ble_l2cap_chan **out_chan)
{
    return ble_hs_misc_conn_chan_find(conn_handle, BLE_L2CAP_CID_ATT,
                                      out_conn, out_chan);
}

void
ble_att_inc_tx_stat(uint8_t att_op)
{
    switch (att_op) {
    case BLE_ATT_OP_ERROR_RSP:
        STATS_INC(ble_att_stats, error_rsp_tx);
        break;

    case BLE_ATT_OP_MTU_REQ:
        STATS_INC(ble_att_stats, mtu_req_tx);
        break;

    case BLE_ATT_OP_MTU_RSP:
        STATS_INC(ble_att_stats, mtu_rsp_tx);
        break;

    case BLE_ATT_OP_FIND_INFO_REQ:
        STATS_INC(ble_att_stats, find_info_req_tx);
        break;

    case BLE_ATT_OP_FIND_INFO_RSP:
        STATS_INC(ble_att_stats, find_info_rsp_tx);
        break;

    case BLE_ATT_OP_FIND_TYPE_VALUE_REQ:
        STATS_INC(ble_att_stats, find_type_value_req_tx);
        break;

    case BLE_ATT_OP_FIND_TYPE_VALUE_RSP:
        STATS_INC(ble_att_stats, find_type_value_rsp_tx);
        break;

    case BLE_ATT_OP_READ_TYPE_REQ:
        STATS_INC(ble_att_stats, read_type_req_tx);
        break;

    case BLE_ATT_OP_READ_TYPE_RSP:
        STATS_INC(ble_att_stats, read_type_rsp_tx);
        break;

    case BLE_ATT_OP_READ_REQ:
        STATS_INC(ble_att_stats, read_req_tx);
        break;

    case BLE_ATT_OP_READ_RSP:
        STATS_INC(ble_att_stats, read_rsp_tx);
        break;

    case BLE_ATT_OP_READ_BLOB_REQ:
        STATS_INC(ble_att_stats, read_blob_req_tx);
        break;

    case BLE_ATT_OP_READ_BLOB_RSP:
        STATS_INC(ble_att_stats, read_blob_rsp_tx);
        break;

    case BLE_ATT_OP_READ_MULT_REQ:
        STATS_INC(ble_att_stats, read_mult_req_tx);
        break;

    case BLE_ATT_OP_READ_MULT_RSP:
        STATS_INC(ble_att_stats, read_mult_rsp_tx);
        break;

    case BLE_ATT_OP_READ_GROUP_TYPE_REQ:
        STATS_INC(ble_att_stats, read_group_type_req_tx);
        break;

    case BLE_ATT_OP_READ_GROUP_TYPE_RSP:
        STATS_INC(ble_att_stats, read_group_type_rsp_tx);
        break;

    case BLE_ATT_OP_WRITE_REQ:
        STATS_INC(ble_att_stats, write_req_tx);
        break;

    case BLE_ATT_OP_WRITE_RSP:
        STATS_INC(ble_att_stats, write_rsp_tx);
        break;

    case BLE_ATT_OP_PREP_WRITE_REQ:
        STATS_INC(ble_att_stats, prep_write_req_tx);
        break;

    case BLE_ATT_OP_PREP_WRITE_RSP:
        STATS_INC(ble_att_stats, prep_write_rsp_tx);
        break;

    case BLE_ATT_OP_EXEC_WRITE_REQ:
        STATS_INC(ble_att_stats, exec_write_req_tx);
        break;

    case BLE_ATT_OP_EXEC_WRITE_RSP:
        STATS_INC(ble_att_stats, exec_write_rsp_tx);
        break;

    case BLE_ATT_OP_NOTIFY_REQ:
        STATS_INC(ble_att_stats, notify_req_tx);
        break;

    case BLE_ATT_OP_INDICATE_REQ:
        STATS_INC(ble_att_stats, indicate_req_tx);
        break;

    case BLE_ATT_OP_INDICATE_RSP:
        STATS_INC(ble_att_stats, indicate_rsp_tx);
        break;

    case BLE_ATT_OP_WRITE_CMD:
        STATS_INC(ble_att_stats, write_cmd_tx);
        break;

    default:
        break;
    }
}

static void
ble_att_inc_rx_stat(uint8_t att_op)
{
    switch (att_op) {
    case BLE_ATT_OP_ERROR_RSP:
        STATS_INC(ble_att_stats, error_rsp_rx);
        break;

    case BLE_ATT_OP_MTU_REQ:
        STATS_INC(ble_att_stats, mtu_req_rx);
        break;

    case BLE_ATT_OP_MTU_RSP:
        STATS_INC(ble_att_stats, mtu_rsp_rx);
        break;

    case BLE_ATT_OP_FIND_INFO_REQ:
        STATS_INC(ble_att_stats, find_info_req_rx);
        break;

    case BLE_ATT_OP_FIND_INFO_RSP:
        STATS_INC(ble_att_stats, find_info_rsp_rx);
        break;

    case BLE_ATT_OP_FIND_TYPE_VALUE_REQ:
        STATS_INC(ble_att_stats, find_type_value_req_rx);
        break;

    case BLE_ATT_OP_FIND_TYPE_VALUE_RSP:
        STATS_INC(ble_att_stats, find_type_value_rsp_rx);
        break;

    case BLE_ATT_OP_READ_TYPE_REQ:
        STATS_INC(ble_att_stats, read_type_req_rx);
        break;

    case BLE_ATT_OP_READ_TYPE_RSP:
        STATS_INC(ble_att_stats, read_type_rsp_rx);
        break;

    case BLE_ATT_OP_READ_REQ:
        STATS_INC(ble_att_stats, read_req_rx);
        break;

    case BLE_ATT_OP_READ_RSP:
        STATS_INC(ble_att_stats, read_rsp_rx);
        break;

    case BLE_ATT_OP_READ_BLOB_REQ:
        STATS_INC(ble_att_stats, read_blob_req_rx);
        break;

    case BLE_ATT_OP_READ_BLOB_RSP:
        STATS_INC(ble_att_stats, read_blob_rsp_rx);
        break;

    case BLE_ATT_OP_READ_MULT_REQ:
        STATS_INC(ble_att_stats, read_mult_req_rx);
        break;

    case BLE_ATT_OP_READ_MULT_RSP:
        STATS_INC(ble_att_stats, read_mult_rsp_rx);
        break;

    case BLE_ATT_OP_READ_GROUP_TYPE_REQ:
        STATS_INC(ble_att_stats, read_group_type_req_rx);
        break;

    case BLE_ATT_OP_READ_GROUP_TYPE_RSP:
        STATS_INC(ble_att_stats, read_group_type_rsp_rx);
        break;

    case BLE_ATT_OP_WRITE_REQ:
        STATS_INC(ble_att_stats, write_req_rx);
        break;

    case BLE_ATT_OP_WRITE_RSP:
        STATS_INC(ble_att_stats, write_rsp_rx);
        break;

    case BLE_ATT_OP_PREP_WRITE_REQ:
        STATS_INC(ble_att_stats, prep_write_req_rx);
        break;

    case BLE_ATT_OP_PREP_WRITE_RSP:
        STATS_INC(ble_att_stats, prep_write_rsp_rx);
        break;

    case BLE_ATT_OP_EXEC_WRITE_REQ:
        STATS_INC(ble_att_stats, exec_write_req_rx);
        break;

    case BLE_ATT_OP_EXEC_WRITE_RSP:
        STATS_INC(ble_att_stats, exec_write_rsp_rx);
        break;

    case BLE_ATT_OP_NOTIFY_REQ:
        STATS_INC(ble_att_stats, notify_req_rx);
        break;

    case BLE_ATT_OP_INDICATE_REQ:
        STATS_INC(ble_att_stats, indicate_req_rx);
        break;

    case BLE_ATT_OP_INDICATE_RSP:
        STATS_INC(ble_att_stats, indicate_rsp_rx);
        break;

    case BLE_ATT_OP_WRITE_CMD:
        STATS_INC(ble_att_stats, write_cmd_rx);
        break;

    default:
        break;
    }
}

void
ble_att_truncate_to_mtu(const struct ble_l2cap_chan *att_chan,
                        struct os_mbuf *txom)
{
    int32_t extra_len;
    uint16_t mtu;

    mtu = ble_att_chan_mtu(att_chan);
    extra_len = OS_MBUF_PKTLEN(txom) - mtu;
    if (extra_len > 0) {
        os_mbuf_adj(txom, -extra_len);
    }
}

uint16_t
ble_att_mtu(uint16_t conn_handle)
{
    struct ble_l2cap_chan *chan;
    struct ble_hs_conn *conn;
    uint16_t mtu;
    int rc;

    ble_hs_lock();

    rc = ble_att_conn_chan_find(conn_handle, &conn, &chan);
    if (rc == 0) {
        mtu = ble_att_chan_mtu(chan);
    } else {
        mtu = 0;
    }

    ble_hs_unlock();

    return mtu;
}

void
ble_att_set_peer_mtu(struct ble_l2cap_chan *chan, uint16_t peer_mtu)
{
    if (peer_mtu < BLE_ATT_MTU_DFLT) {
        peer_mtu = BLE_ATT_MTU_DFLT;
    }

    chan->peer_mtu = peer_mtu;
}

uint16_t
ble_att_chan_mtu(const struct ble_l2cap_chan *chan)
{
    uint16_t mtu;

    /* If either side has not exchanged MTU size, use the default.  Otherwise,
     * use the lesser of the two exchanged values.
     */
    if (!(ble_l2cap_is_mtu_req_sent(chan)) ||
        chan->peer_mtu == 0) {

        mtu = BLE_ATT_MTU_DFLT;
    } else {
        mtu = min(chan->my_mtu, chan->peer_mtu);
    }

    BLE_HS_DBG_ASSERT(mtu >= BLE_ATT_MTU_DFLT);

    return mtu;
}

static void
ble_att_rx_handle_unknown_request(uint8_t op, uint16_t conn_handle,
                                  struct os_mbuf **om)
{
    /* If this is command (bit6 is set to 1), do nothing */
    if (op & 0x40) {
        return;
    }

    os_mbuf_adj(*om, OS_MBUF_PKTLEN(*om));
    ble_att_svr_tx_error_rsp(conn_handle, *om, op, 0,
                             BLE_ATT_ERR_REQ_NOT_SUPPORTED);

    *om = NULL;
}

static int
ble_att_rx(struct ble_l2cap_chan *chan)
{
    const struct ble_att_rx_dispatch_entry *entry;
    uint8_t op;
    uint16_t conn_handle;
    struct os_mbuf **om;
    int rc;

    conn_handle = ble_l2cap_get_conn_handle(chan);
    if (conn_handle == BLE_HS_CONN_HANDLE_NONE) {
        return BLE_HS_ENOTCONN;
    }

    om = &chan->rx_buf;
    BLE_HS_DBG_ASSERT(*om != NULL);

    rc = os_mbuf_copydata(*om, 0, 1, &op);
    if (rc != 0) {
        return BLE_HS_EMSGSIZE;
    }

    entry = ble_att_rx_dispatch_entry_find(op);
    if (entry == NULL) {
        ble_att_rx_handle_unknown_request(op, conn_handle, om);
        return BLE_HS_ENOTSUP;
    }

    ble_att_inc_rx_stat(op);

    /* Strip L2CAP ATT header from the front of the mbuf. */
    os_mbuf_adj(*om, 1);

    rc = entry->bde_fn(conn_handle, om);
    if (rc != 0) {
        if (rc == BLE_HS_ENOTSUP) {
            ble_att_rx_handle_unknown_request(op, conn_handle, om);
        }
        return rc;
    }

    return 0;
}

uint16_t
ble_att_preferred_mtu(void)
{
    return ble_att_preferred_mtu_val;
}

int
ble_att_set_preferred_mtu(uint16_t mtu)
{
    struct ble_l2cap_chan *chan;
    struct ble_hs_conn *conn;
    int i;

    if (mtu < BLE_ATT_MTU_DFLT) {
        return BLE_HS_EINVAL;
    }
    if (mtu > BLE_ATT_MTU_MAX) {
        return BLE_HS_EINVAL;
    }

    ble_att_preferred_mtu_val = mtu;

    /* Set my_mtu for established connections that haven't exchanged. */
    ble_hs_lock();

    i = 0;
    while ((conn = ble_hs_conn_find_by_idx(i)) != NULL) {
        chan = ble_hs_conn_chan_find_by_scid(conn, BLE_L2CAP_CID_ATT);
        BLE_HS_DBG_ASSERT(chan != NULL);

        if (!(chan->flags & BLE_L2CAP_CHAN_F_TXED_MTU)) {
            chan->my_mtu = mtu;
        }

        i++;
    }

    ble_hs_unlock();

    return 0;
}

struct ble_l2cap_chan *
ble_att_create_chan(uint16_t conn_handle)
{
    struct ble_l2cap_chan *chan;

    chan = ble_l2cap_chan_alloc(conn_handle);
    if (chan == NULL) {
        return NULL;
    }

    chan->scid = BLE_L2CAP_CID_ATT;
    chan->dcid = BLE_L2CAP_CID_ATT;
    chan->my_mtu = ble_att_preferred_mtu_val;
    chan->rx_fn = ble_att_rx;

    return chan;
}

int
ble_att_init(void)
{
    int rc;

    ble_att_preferred_mtu_val = MYNEWT_VAL(BLE_ATT_PREFERRED_MTU);

    rc = stats_init_and_reg(
        STATS_HDR(ble_att_stats), STATS_SIZE_INIT_PARMS(ble_att_stats,
        STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_att_stats), "ble_att");
    if (rc != 0) {
        return BLE_HS_EOS;
    }

    return 0;
}

#endif
