/*
 * 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 <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "nimble/porting/nimble/include/os/os_mempool.h"
#include "nimble/nimble/include/nimble/ble.h"
#include "nimble/nimble/host/include/host/ble_uuid.h"
#include "ble_hs_priv.h"

#if NIMBLE_BLE_CONNECT
/*****************************************************************************
 * $error response                                                           *
 *****************************************************************************/

int
ble_att_clt_rx_error(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
    struct ble_att_error_rsp *rsp;
    int rc;

    rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp));
    if (rc != 0) {
        return rc;
    }

    rsp = (struct ble_att_error_rsp *)(*rxom)->om_data;

    ble_gattc_rx_err(conn_handle, cid, le16toh(rsp->baep_handle),
                     le16toh(rsp->baep_error_code));

    return 0;
}

/*****************************************************************************
 * $mtu exchange                                                             *
 *****************************************************************************/

int
ble_att_clt_tx_mtu(uint16_t conn_handle, uint16_t mtu)
{
    struct ble_att_mtu_cmd *req;
    struct ble_l2cap_chan *chan;
    struct ble_hs_conn *conn;
    struct os_mbuf *txom;
    int rc;

    if (mtu < BLE_ATT_MTU_DFLT) {
        return BLE_HS_EINVAL;
    }

    ble_hs_lock();

    rc = ble_att_conn_chan_find(conn_handle, BLE_L2CAP_CID_ATT, &conn, &chan);
    if (rc != 0) {
        rc = BLE_HS_ENOTCONN;
    } else if (chan->flags & BLE_L2CAP_CHAN_F_TXED_MTU) {
        rc = BLE_HS_EALREADY;
    } else {
        rc = 0;
    }
    ble_hs_unlock();

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

    req = ble_att_cmd_get(BLE_ATT_OP_MTU_REQ, sizeof(*req), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->bamc_mtu = htole16(mtu);

    rc = ble_att_tx(conn_handle, BLE_L2CAP_CID_ATT, txom);
    if (rc != 0) {
        return rc;
    }

    ble_hs_lock();

    rc = ble_att_conn_chan_find(conn_handle, BLE_L2CAP_CID_ATT, &conn, &chan);
    if (rc == 0) {
        chan->flags |= BLE_L2CAP_CHAN_F_TXED_MTU;
    }

    ble_hs_unlock();

    return rc;
}

int
ble_att_clt_rx_mtu(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
    struct ble_att_mtu_cmd *cmd;
    struct ble_l2cap_chan *chan;
    uint16_t mtu;
    int rc;

    mtu = 0;

#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0
    if (cid != BLE_L2CAP_CID_ATT) {
        /*FIXME reject ?*/
        assert(0);
    }
#endif

    rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*cmd));
    if (rc == 0) {
        cmd = (struct ble_att_mtu_cmd *)(*rxom)->om_data;

        ble_hs_lock();

        rc = ble_att_conn_chan_find(conn_handle, cid, NULL, &chan);
        if (rc == 0) {
            ble_att_set_peer_mtu(chan, le16toh(cmd->bamc_mtu));
            mtu = ble_att_chan_mtu(chan);
        }

        ble_hs_unlock();

        if (rc == 0) {
            ble_gap_mtu_event(conn_handle, BLE_L2CAP_CID_ATT, mtu);
        }
    }

    ble_gattc_rx_mtu(conn_handle, BLE_L2CAP_CID_ATT, rc, mtu);
    return rc;
}

/*****************************************************************************
 * $find information                                                         *
 *****************************************************************************/

int
ble_att_clt_tx_find_info(uint16_t conn_handle, uint16_t cid, uint16_t start_handle,
                         uint16_t end_handle)
{
#if !NIMBLE_BLE_ATT_CLT_FIND_INFO
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_find_info_req *req;
    struct os_mbuf *txom;

    if (start_handle == 0 || start_handle > end_handle) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_FIND_INFO_REQ, sizeof(*req), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->bafq_start_handle = htole16(start_handle);
    req->bafq_end_handle = htole16(end_handle);

    return ble_att_tx(conn_handle, cid, txom);
}

static int
ble_att_clt_parse_find_info_entry(struct os_mbuf **rxom, uint8_t rsp_format,
                                  struct ble_att_find_info_idata *idata)
{
    int entry_len;
    int rc;

    switch (rsp_format) {
    case BLE_ATT_FIND_INFO_RSP_FORMAT_16BIT:
        entry_len = 2 + 2;
        break;

    case BLE_ATT_FIND_INFO_RSP_FORMAT_128BIT:
        entry_len = 2 + 16;
        break;

    default:
        return BLE_HS_EBADDATA;
    }

    rc = ble_hs_mbuf_pullup_base(rxom, entry_len);
    if (rc != 0) {
        return rc;
    }

    idata->attr_handle = get_le16((*rxom)->om_data);

    switch (rsp_format) {
    case BLE_ATT_FIND_INFO_RSP_FORMAT_16BIT:
        rc = ble_uuid_init_from_att_mbuf(&idata->uuid, *rxom, 2, 2);
        if (rc != 0) {
            return BLE_HS_EBADDATA;
        }
        break;

    case BLE_ATT_FIND_INFO_RSP_FORMAT_128BIT:
        rc = ble_uuid_init_from_att_mbuf(&idata->uuid, *rxom, 2, 16);
        if (rc != 0) {
            return BLE_HS_EBADDATA;
        }
        break;

    default:
        BLE_HS_DBG_ASSERT(0);
        break;
    }

    os_mbuf_adj(*rxom, entry_len);
    return 0;
}

int
ble_att_clt_rx_find_info(uint16_t conn_handle, uint16_t cid, struct os_mbuf **om)
{
#if !NIMBLE_BLE_ATT_CLT_FIND_INFO
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_find_info_idata idata;
    struct ble_att_find_info_rsp *rsp;
    int rc;

    rc = ble_hs_mbuf_pullup_base(om, sizeof(*rsp));
    if (rc != 0) {
        goto done;
    }

    rsp = (struct ble_att_find_info_rsp *)(*om)->om_data;

    /* Strip the response base from the front of the mbuf. */
    os_mbuf_adj((*om), sizeof(*rsp));

    while (OS_MBUF_PKTLEN(*om) > 0) {
        rc = ble_att_clt_parse_find_info_entry(om, rsp->bafp_format, &idata);
        if (rc != 0) {
            goto done;
        }

        /* Hand find-info entry to GATT. */
        ble_gattc_rx_find_info_idata(conn_handle, cid, &idata);
    }

    rc = 0;

done:
    /* Notify GATT that response processing is done. */
    ble_gattc_rx_find_info_complete(conn_handle, cid, rc);
    return rc;
}

/*****************************************************************************
 * $find by type value                                                       *
 *****************************************************************************/

/*
 * TODO consider this to accept UUID instead of value, it is used only for this
 * anyway
 */
int
ble_att_clt_tx_find_type_value(uint16_t conn_handle, uint16_t cid,
                               uint16_t start_handle, uint16_t end_handle,
                               uint16_t attribute_type,
                               const void *attribute_value, int value_len)
{
#if !NIMBLE_BLE_ATT_CLT_FIND_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_find_type_value_req *req;
    struct os_mbuf *txom;

    if (start_handle == 0 || start_handle > end_handle) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_FIND_TYPE_VALUE_REQ, sizeof(*req) + value_len,
                          &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->bavq_start_handle = htole16(start_handle);
    req->bavq_end_handle = htole16(end_handle);
    req->bavq_attr_type = htole16(attribute_type);
    memcpy(req->bavq_value, attribute_value, value_len);

    return ble_att_tx(conn_handle, cid, txom);
}

static int
ble_att_clt_parse_find_type_value_hinfo(
    struct os_mbuf **om, struct ble_att_find_type_value_hinfo *dst)
{
    struct ble_att_handle_group *group;
    int rc;

    rc = ble_hs_mbuf_pullup_base(om, sizeof(*group));
    if (rc != 0) {
        return BLE_HS_EBADDATA;
    }

    group = (struct ble_att_handle_group *)(*om)->om_data;

    dst->attr_handle = le16toh(group->attr_handle);
    dst->group_end_handle = le16toh(group->group_end_handle);

    os_mbuf_adj((*om), sizeof(*group));

    return 0;
}

int
ble_att_clt_rx_find_type_value(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_FIND_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_find_type_value_hinfo hinfo;
    int rc;

    /* Parse the Handles-Information-List field, passing each entry to GATT. */
    rc = 0;
    while (OS_MBUF_PKTLEN(*rxom) > 0) {
        rc = ble_att_clt_parse_find_type_value_hinfo(rxom, &hinfo);
        if (rc != 0) {
            break;
        }

        ble_gattc_rx_find_type_value_hinfo(conn_handle, cid, &hinfo);
    }

    /* Notify GATT client that the full response has been parsed. */
    ble_gattc_rx_find_type_value_complete(conn_handle, cid, rc);

    return 0;
}

/*****************************************************************************
 * $read by type                                                             *
 *****************************************************************************/

int
ble_att_clt_tx_read_type(uint16_t conn_handle, uint16_t cid, uint16_t start_handle,
                         uint16_t end_handle, const ble_uuid_t *uuid)
{
#if !NIMBLE_BLE_ATT_CLT_READ_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_type_req *req;
    struct os_mbuf *txom;

    if (start_handle == 0 || start_handle > end_handle) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_READ_TYPE_REQ,
                          sizeof(*req) + ble_uuid_length(uuid), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->batq_start_handle = htole16(start_handle);
    req->batq_end_handle = htole16(end_handle);

    ble_uuid_flat(uuid, req->uuid);

    return ble_att_tx(conn_handle, cid, txom);
}

int
ble_att_clt_rx_read_type(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_type_adata adata;
    struct ble_att_attr_data_list *data;
    struct ble_att_read_type_rsp *rsp;
    uint8_t data_len;
    int rc;

    rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp));
    if (rc != 0) {
        goto done;
    }

    rsp = (struct ble_att_read_type_rsp *)(*rxom)->om_data;

    data_len = rsp->batp_length;

    /* Strip the response base from the front of the mbuf. */
    os_mbuf_adj(*rxom, sizeof(*rsp));

    if (data_len < sizeof(*data)) {
        rc = BLE_HS_EBADDATA;
        goto done;
    }

    /* Parse the Attribute Data List field, passing each entry to the GATT. */
    while (OS_MBUF_PKTLEN(*rxom) > 0) {
        rc = ble_hs_mbuf_pullup_base(rxom, data_len);
        if (rc != 0) {
            break;
        }

        data = (struct ble_att_attr_data_list *)(*rxom)->om_data;

        adata.att_handle = le16toh(data->handle);
        adata.value_len = data_len - sizeof(*data);
        adata.value = data->value;

        rc = ble_gattc_rx_read_type_adata(conn_handle, cid, &adata);
        if (rc != 0) {
            /* We should not call complete callback if this returned an error
             * since proc is not added to the proc list.
             */
            return 0;
        }
        os_mbuf_adj(*rxom, data_len);
    }

done:
    /* Notify GATT that the response is done being parsed. */
    ble_gattc_rx_read_type_complete(conn_handle, cid, rc);

    return rc;

}

/*****************************************************************************
 * $read                                                                     *
 *****************************************************************************/

int
ble_att_clt_tx_read(uint16_t conn_handle, uint16_t cid, uint16_t handle)
{
#if !NIMBLE_BLE_ATT_CLT_READ
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_req *req;
    struct os_mbuf *txom;
    int rc;

    if (handle == 0) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_READ_REQ, sizeof(*req), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->barq_handle = htole16(handle);

    rc = ble_att_tx(conn_handle, cid, txom);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_att_clt_rx_read(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ
    return BLE_HS_ENOTSUP;
#endif

    /* Pass the Attribute Value field to GATT. */
    ble_gattc_rx_read_rsp(conn_handle, cid, 0, rxom);
    return 0;
}

/*****************************************************************************
 * $read blob                                                                *
 *****************************************************************************/

int
ble_att_clt_tx_read_blob(uint16_t conn_handle, uint16_t cid, uint16_t handle, uint16_t offset)
{
#if !NIMBLE_BLE_ATT_CLT_READ_BLOB
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_blob_req *req;
    struct os_mbuf *txom;
    int rc;

    if (handle == 0) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_READ_BLOB_REQ, sizeof(*req), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->babq_handle = htole16(handle);
    req->babq_offset = htole16(offset);

    rc = ble_att_tx(conn_handle, cid, txom);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_att_clt_rx_read_blob(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ_BLOB
    return BLE_HS_ENOTSUP;
#endif

    /* Pass the Attribute Value field to GATT. */
    ble_gattc_rx_read_blob_rsp(conn_handle, cid, 0, rxom);
    return 0;
}

/*****************************************************************************
 * $read multiple                                                            *
 *****************************************************************************/
int
ble_att_clt_tx_read_mult(uint16_t conn_handle, uint16_t cid, const uint16_t *handles,
                         int num_handles, bool variable)
{
#if !NIMBLE_BLE_ATT_CLT_READ_MULT
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_mult_req *req;
    struct os_mbuf *txom;
    int i;
    uint8_t op;

    if (num_handles < 1) {
        return BLE_HS_EINVAL;
    }

    op = variable ? BLE_ATT_OP_READ_MULT_VAR_REQ : BLE_ATT_OP_READ_MULT_REQ;

    req = ble_att_cmd_get(op,
                          sizeof(req->handles[0]) * num_handles,
                          &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    for(i = 0; i < num_handles; i++) {
        req->handles[i] = htole16(handles[i]);
    }

    return ble_att_tx(conn_handle, cid, txom);
}

int
ble_att_clt_rx_read_mult(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ_MULT
    return BLE_HS_ENOTSUP;
#endif

    /* Pass the Attribute Value field to GATT. */
    ble_gattc_rx_read_mult_rsp(conn_handle, cid, 0, rxom, false);
    return 0;
}

int
ble_att_clt_rx_read_mult_var(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ_MULT_VAR
    return BLE_HS_ENOTSUP;
#endif

    /* Pass the Attribute Value field to GATT. */
    ble_gattc_rx_read_mult_rsp(conn_handle, cid, 0, rxom, true);
    return 0;
}

/*****************************************************************************
 * $read by group type                                                       *
 *****************************************************************************/

int
ble_att_clt_tx_read_group_type(uint16_t conn_handle, uint16_t cid,
                               uint16_t start_handle, uint16_t end_handle,
                               const ble_uuid_t *uuid)
{
#if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_group_type_req *req;
    struct os_mbuf *txom;

    if (start_handle == 0 || start_handle > end_handle) {
        return BLE_HS_EINVAL;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_READ_GROUP_TYPE_REQ,
                          sizeof(*req) + ble_uuid_length(uuid), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->bagq_start_handle = htole16(start_handle);
    req->bagq_end_handle = htole16(end_handle);
    ble_uuid_flat(uuid, req->uuid);

    return ble_att_tx(conn_handle, cid, txom);
}

static int
ble_att_clt_parse_read_group_type_adata(
    struct os_mbuf **om, int data_len,
    struct ble_att_read_group_type_adata *adata)
{
    int rc;

    if (data_len < BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ + 1) {
        return BLE_HS_EMSGSIZE;
    }

    rc = ble_hs_mbuf_pullup_base(om, data_len);
    if (rc != 0) {
        return rc;
    }

    adata->att_handle = get_le16((*om)->om_data + 0);
    adata->end_group_handle = get_le16((*om)->om_data + 2);
    adata->value_len = data_len - BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ;
    adata->value = (*om)->om_data + BLE_ATT_READ_GROUP_TYPE_ADATA_BASE_SZ;

    return 0;
}

int
ble_att_clt_rx_read_group_type(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_read_group_type_adata adata;
    struct ble_att_read_group_type_rsp *rsp;
    uint8_t len;
    int rc;

    rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp));
    if (rc != 0) {
        goto done;
    }

    rsp = (struct ble_att_read_group_type_rsp *)(*rxom)->om_data;

    len = rsp->bagp_length;

    /* Strip the base from the front of the response. */
    os_mbuf_adj(*rxom, sizeof(*rsp));

    /* Parse the Attribute Data List field, passing each entry to GATT. */
    while (OS_MBUF_PKTLEN(*rxom) > 0) {
        rc = ble_att_clt_parse_read_group_type_adata(rxom, len, &adata);
        if (rc != 0) {
            goto done;
        }

        ble_gattc_rx_read_group_type_adata(conn_handle, cid, &adata);
        os_mbuf_adj(*rxom, len);
    }

done:
    /* Notify GATT that the response is done being parsed. */
    ble_gattc_rx_read_group_type_complete(conn_handle, cid, rc);
    return rc;
}

/*****************************************************************************
 * $write                                                                    *
 *****************************************************************************/

int
ble_att_clt_tx_write_req(uint16_t conn_handle, uint16_t cid, uint16_t handle,
                         struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_WRITE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_write_req *req;
    struct os_mbuf *txom2;

    req = ble_att_cmd_get(BLE_ATT_OP_WRITE_REQ, sizeof(*req), &txom2);
    if (req == NULL) {
        os_mbuf_free_chain(txom);
        return BLE_HS_ENOMEM;
    }

    req->bawq_handle = htole16(handle);
    os_mbuf_concat(txom2, txom);

    return ble_att_tx(conn_handle, cid, txom2);
}

int
ble_att_clt_tx_write_cmd(uint16_t conn_handle, uint16_t cid,
                         uint16_t handle, struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_WRITE_NO_RSP
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_write_cmd *cmd;
    struct os_mbuf *txom2;

#if MYNEWT_VAL(BLE_HS_DEBUG)
    uint8_t b;
    int rc;
    int i;

    BLE_HS_LOG(DEBUG, "ble_att_clt_tx_write_cmd(): ");
    for (i = 0; i < OS_MBUF_PKTLEN(txom); i++) {
        if (i != 0) {
            BLE_HS_LOG(DEBUG, ":");
        }
        rc = os_mbuf_copydata(txom, i, 1, &b);
        assert(rc == 0);
        BLE_HS_LOG(DEBUG, "0x%02x", b);
    }
#endif

    cmd = ble_att_cmd_get(BLE_ATT_OP_WRITE_CMD, sizeof(*cmd), &txom2);
    if (cmd == NULL) {
        os_mbuf_free_chain(txom);
        return BLE_HS_ENOMEM;
    }

    cmd->handle = htole16(handle);
    os_mbuf_concat(txom2, txom);

    return ble_att_tx(conn_handle, cid, txom2);
}

int
ble_att_clt_rx_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_WRITE
    return BLE_HS_ENOTSUP;
#endif

    /* No payload. */
    ble_gattc_rx_write_rsp(conn_handle, cid);
    return 0;
}

/*****************************************************************************
 * $prepare write request                                                    *
 *****************************************************************************/

int
ble_att_clt_tx_prep_write(uint16_t conn_handle, uint16_t cid, uint16_t handle,
                          uint16_t offset, struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_PREP_WRITE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_prep_write_cmd *req;
    struct os_mbuf *txom2;
    int rc;

    if (handle == 0) {
        rc = BLE_HS_EINVAL;
        goto err;
    }

    if (offset + OS_MBUF_PKTLEN(txom) > BLE_ATT_ATTR_MAX_LEN) {
        rc = BLE_HS_EINVAL;
        goto err;
    }

    if (OS_MBUF_PKTLEN(txom) >
        ble_att_mtu_by_cid(conn_handle, cid) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ) {
        rc = BLE_HS_EINVAL;
        goto err;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_PREP_WRITE_REQ, sizeof(*req), &txom2);
    if (req == NULL) {
        rc = BLE_HS_ENOMEM;
        goto err;
    }

    req->bapc_handle = htole16(handle);
    req->bapc_offset = htole16(offset);
    os_mbuf_concat(txom2, txom);

    return ble_att_tx(conn_handle, cid, txom2);

err:
    os_mbuf_free_chain(txom);
    return rc;
}

int
ble_att_clt_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_PREP_WRITE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_prep_write_cmd *rsp;
    uint16_t handle, offset;
    int rc;

    /* Initialize some values in case of early error. */
    handle = 0;
    offset = 0;

    rc = ble_hs_mbuf_pullup_base(rxom, sizeof(*rsp));
    if (rc != 0) {
        goto done;
    }

    rsp = (struct ble_att_prep_write_cmd *)(*rxom)->om_data;

    handle = le16toh(rsp->bapc_handle);
    offset = le16toh(rsp->bapc_offset);

    /* Strip the base from the front of the response. */
    os_mbuf_adj(*rxom, sizeof(*rsp));

done:
    /* Notify GATT client that the full response has been parsed. */
    ble_gattc_rx_prep_write_rsp(conn_handle, cid, rc, handle, offset, rxom);
    return rc;
}

/*****************************************************************************
 * $execute write request                                                    *
 *****************************************************************************/

int
ble_att_clt_tx_exec_write(uint16_t conn_handle, uint16_t cid, uint8_t flags)
{
#if !NIMBLE_BLE_ATT_CLT_EXEC_WRITE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_exec_write_req *req;
    struct os_mbuf *txom;
    int rc;

    req = ble_att_cmd_get(BLE_ATT_OP_EXEC_WRITE_REQ, sizeof(*req), &txom);
    if (req == NULL) {
        return BLE_HS_ENOMEM;
    }

    req->baeq_flags = flags;

    rc = ble_att_tx(conn_handle, cid, txom);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_att_clt_rx_exec_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_EXEC_WRITE
    return BLE_HS_ENOTSUP;
#endif

    ble_gattc_rx_exec_write_rsp(conn_handle, cid, 0);
    return 0;
}

/*****************************************************************************
 * $handle value notification                                                *
 *****************************************************************************/

int
ble_att_clt_tx_notify(uint16_t conn_handle, uint16_t handle,
                      struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_NOTIFY
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_notify_req *req;
    struct os_mbuf *txom2;
    uint16_t cid;
    int rc;

    if (handle == 0) {
        rc = BLE_HS_EINVAL;
        goto err;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_NOTIFY_REQ, sizeof(*req), &txom2);
    if (req == NULL) {
        rc = BLE_HS_ENOMEM;
        goto err;
    }

    req->banq_handle = htole16(handle);
    os_mbuf_concat(txom2, txom);

    cid = ble_eatt_get_available_chan_cid(conn_handle, BLE_GATT_OP_DUMMY);
    rc = ble_att_tx(conn_handle, cid, txom2);
    ble_eatt_release_chan(conn_handle, BLE_GATT_OP_DUMMY);
    return rc;

err:
    os_mbuf_free_chain(txom);
    return rc;
}

/*****************************************************************************
 * $handle value indication                                                  *
 *****************************************************************************/

int
ble_att_clt_tx_indicate(uint16_t conn_handle, uint16_t cid,
                        uint16_t handle, struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_INDICATE
    return BLE_HS_ENOTSUP;
#endif

    struct ble_att_indicate_req *req;
    struct os_mbuf *txom2;
    int rc;

    if (handle == 0) {
        rc = BLE_HS_EINVAL;
        goto err;
    }

    req = ble_att_cmd_get(BLE_ATT_OP_INDICATE_REQ, sizeof(*req), &txom2);
    if (req == NULL) {
        rc = BLE_HS_ENOMEM;
        goto err;
    }

    req->baiq_handle = htole16(handle);
    os_mbuf_concat(txom2, txom);

    return ble_att_tx(conn_handle, cid, txom2);

err:
    os_mbuf_free_chain(txom);
    return rc;
}

int
ble_att_clt_rx_indicate(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom)
{
#if !NIMBLE_BLE_ATT_CLT_INDICATE
    return BLE_HS_ENOTSUP;
#endif

    /* No payload. */
    ble_gatts_rx_indicate_rsp(conn_handle, cid);
    return 0;
}

#endif

/*****************************************************************************
 * $multiple handle value notification                                       *
 *****************************************************************************/

int
ble_att_clt_tx_notify_mult(uint16_t conn_handle, struct os_mbuf *txom)
{
#if !NIMBLE_BLE_ATT_CLT_NOTIFY_MULT
    return BLE_HS_ENOTSUP;
#endif

    struct os_mbuf *txom2;
    uint16_t cid;
    int rc;

    if (ble_att_cmd_get(BLE_ATT_OP_NOTIFY_MULTI_REQ, 0, &txom2) == NULL) {
        rc = BLE_HS_ENOMEM;
        goto err;
    }

    os_mbuf_concat(txom2, txom);

    cid = ble_eatt_get_available_chan_cid(conn_handle, BLE_GATT_OP_DUMMY);
    rc = ble_att_tx(conn_handle, cid, txom2);
    if (cid != BLE_L2CAP_CID_ATT) {
        ble_eatt_release_chan(conn_handle, BLE_GATT_OP_DUMMY);
    }

err:
    return rc;
}
