/*
 * 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 <errno.h>
#include "testutil/testutil.h"
#include "nimble/ble.h"
#include "ble_hs_test.h"
#include "host/ble_gatt.h"
#include "ble_hs_test_util.h"

#define BLE_GATT_WRITE_TEST_MAX_ATTRS   128

static int ble_gatt_write_test_cb_called;

static uint8_t ble_gatt_write_test_attr_value[BLE_ATT_ATTR_MAX_LEN];
static struct ble_gatt_error ble_gatt_write_test_error;

static struct ble_hs_test_util_flat_attr
ble_gatt_write_test_attrs[BLE_GATT_WRITE_TEST_MAX_ATTRS];
static int ble_gatt_write_test_num_attrs;

static void
ble_gatt_write_test_init(void)
{
    int i;

    ble_hs_test_util_init();
    ble_gatt_write_test_cb_called = 0;
    ble_gatt_write_test_num_attrs = 0;

    for (i = 0; i < sizeof ble_gatt_write_test_attr_value; i++) {
        ble_gatt_write_test_attr_value[i] = i;
    }
}

static int
ble_gatt_write_test_cb_good(uint16_t conn_handle,
                            const struct ble_gatt_error *error,
                            struct ble_gatt_attr *attr, void *arg)
{
    int *attr_len;

    attr_len = arg;

    TEST_ASSERT(error != NULL);
    TEST_ASSERT(conn_handle == 2);

    ble_gatt_write_test_error = *error;

    if (attr_len != NULL) {
        TEST_ASSERT(error->status == 0);
        TEST_ASSERT(attr->handle == 100);
    }

    ble_gatt_write_test_cb_called = 1;

    return 0;
}

static void
ble_gatt_write_test_rx_rsp(uint16_t conn_handle)
{
    uint8_t op;
    int rc;

    op = BLE_ATT_OP_WRITE_RSP;
    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, BLE_L2CAP_CID_ATT,
                                                &op, 1);
    TEST_ASSERT(rc == 0);
}

static void
ble_gatt_write_test_rx_prep_rsp(uint16_t conn_handle, uint16_t cid,
                                uint16_t attr_handle, uint16_t offset,
                                const void *attr_data, uint16_t attr_data_len)
{
    struct ble_att_prep_write_cmd rsp;
    uint8_t buf[512];
    int rc;

    rsp.bapc_handle = attr_handle;
    rsp.bapc_offset = offset;
    ble_att_prep_write_rsp_write(buf, sizeof buf, &rsp);

    memcpy(buf + BLE_ATT_PREP_WRITE_CMD_BASE_SZ, attr_data, attr_data_len);

    rc = ble_hs_test_util_l2cap_rx_payload_flat(
        conn_handle, cid, buf,
        BLE_ATT_PREP_WRITE_CMD_BASE_SZ + attr_data_len);
    TEST_ASSERT(rc == 0);
}

static void
ble_gatt_write_test_rx_exec_rsp(uint16_t conn_handle, uint16_t cid)
{
    uint8_t op;
    int rc;

    op = BLE_ATT_OP_EXEC_WRITE_RSP;
    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, cid,
                                                &op, 1);
    TEST_ASSERT(rc == 0);
}

static void
ble_gatt_write_test_misc_long_good(int attr_len)
{
    uint16_t mtu;
    int off;
    int len;
    int rc;

    ble_gatt_write_test_init();

    ble_hs_test_util_create_conn(2, ((uint8_t[]) {2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    mtu = ble_att_mtu_by_cid(2, BLE_L2CAP_CID_ATT);

    rc = ble_hs_test_util_gatt_write_long_flat(
        2, 100, ble_gatt_write_test_attr_value, attr_len,
        ble_gatt_write_test_cb_good, &attr_len);
    TEST_ASSERT(rc == 0);

    off = 0;
    while (off < attr_len) {
        len = mtu - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
        if (off + len > attr_len) {
            len = attr_len - off;
        }

        /* Send the pending ATT Prep Write Command. */
        ble_hs_test_util_verify_tx_prep_write(
            100, off, ble_gatt_write_test_attr_value + off, len);

        /* Receive Prep Write response. */
        ble_gatt_write_test_rx_prep_rsp(
            2, BLE_L2CAP_CID_ATT, 100, off, ble_gatt_write_test_attr_value + off, len);

        /* Verify callback hasn't gotten called. */
        TEST_ASSERT(!ble_gatt_write_test_cb_called);

        off += len;
    }

    /* Verify execute write request sent. */
    ble_hs_test_util_verify_tx_exec_write(BLE_ATT_EXEC_WRITE_F_EXECUTE);

    /* Receive Exec Write response. */
    ble_gatt_write_test_rx_exec_rsp(2, BLE_L2CAP_CID_ATT);

    /* Verify callback got called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
}

typedef void ble_gatt_write_test_long_fail_fn(uint16_t conn_handle, uint16_t cid,
                                              int off, int len);

static void
ble_gatt_write_test_misc_long_bad(int attr_len,
                                  ble_gatt_write_test_long_fail_fn *cb)
{
    uint16_t mtu;
    int fail_now;
    int off;
    int len;
    int rc;

    ble_gatt_write_test_init();

    ble_hs_test_util_create_conn(2, ((uint8_t[]) {2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    mtu = ble_att_mtu_by_cid(2, BLE_L2CAP_CID_ATT);

    rc = ble_hs_test_util_gatt_write_long_flat(
        2, 100, ble_gatt_write_test_attr_value, attr_len,
        ble_gatt_write_test_cb_good, NULL);
    TEST_ASSERT(rc == 0);

    fail_now = 0;
    off = 0;
    while (off < attr_len) {
        len = mtu - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
        if (off + len > attr_len) {
            len = attr_len - off;
        }

        /* Send the pending ATT Prep Write Command. */
        ble_hs_test_util_verify_tx_prep_write(
            100, off, ble_gatt_write_test_attr_value + off, len);

        /* Receive Prep Write response. */
        len = BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
        if (off + len >= attr_len) {
            len = attr_len - off;
            fail_now = 1;
        }
        if (!fail_now) {
            ble_gatt_write_test_rx_prep_rsp(
                2, BLE_L2CAP_CID_ATT, 100, off, ble_gatt_write_test_attr_value + off, len);
        } else {
            cb(2, BLE_L2CAP_CID_ATT, off, len);
            break;
        }

        /* Verify callback hasn't gotten called. */
        TEST_ASSERT(!ble_gatt_write_test_cb_called);

        off += len;
    }

    /* Verify callback was called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
    TEST_ASSERT(ble_gatt_write_test_error.status == BLE_HS_EBADDATA);
    TEST_ASSERT(ble_gatt_write_test_error.att_handle == 0);
}

static void
ble_gatt_write_test_misc_long_fail_handle(uint16_t conn_handle, uint16_t cid,
                                          int off, int len)
{
    ble_gatt_write_test_rx_prep_rsp(
        conn_handle, cid, 99, off, ble_gatt_write_test_attr_value + off,
        len);
}

static void
ble_gatt_write_test_misc_long_fail_offset(uint16_t conn_handle, uint16_t cid,
                                          int off, int len)
{
    ble_gatt_write_test_rx_prep_rsp(
        conn_handle, cid, 100, off + 1, ble_gatt_write_test_attr_value + off,
        len);
}

static void
ble_gatt_write_test_misc_long_fail_value(uint16_t conn_handle, uint16_t cid,
                                         int off, int len)
{
    ble_gatt_write_test_rx_prep_rsp(
        conn_handle, cid, 100, off, ble_gatt_write_test_attr_value + off + 1,
        len);
}

static void
ble_gatt_write_test_misc_long_fail_length(uint16_t conn_handle, uint16_t cid,
                                          int off, int len)
{
    ble_gatt_write_test_rx_prep_rsp(
        conn_handle, cid, 100, off, ble_gatt_write_test_attr_value + off,
        len - 1);
}

static int
ble_gatt_write_test_reliable_cb_good(uint16_t conn_handle,
                                     const struct ble_gatt_error *error,
                                     struct ble_gatt_attr *attrs,
                                     uint8_t num_attrs, void *arg)
{
    int i;

    TEST_ASSERT_FATAL(num_attrs <= BLE_GATT_WRITE_TEST_MAX_ATTRS);

    TEST_ASSERT(conn_handle == 2);

    ble_gatt_write_test_num_attrs = num_attrs;
    for (i = 0; i < num_attrs; i++) {
        ble_hs_test_util_attr_to_flat(ble_gatt_write_test_attrs + i,
                                      attrs + i);
    }

    ble_gatt_write_test_cb_called = 1;

    return 0;
}

static void
ble_gatt_write_test_misc_reliable_good(
    struct ble_hs_test_util_flat_attr *flat_attrs)
{
    const struct ble_hs_test_util_flat_attr *attr;
    struct ble_gatt_attr attrs[16];
    uint16_t mtu;
    int num_attrs;
    int attr_idx;
    int len;
    int off;
    int rc;
    int i;

    ble_gatt_write_test_init();

    for (num_attrs = 0; flat_attrs[num_attrs].handle != 0; num_attrs++) {
        TEST_ASSERT_FATAL(num_attrs < sizeof attrs / sizeof attrs[0]);
        ble_hs_test_util_attr_from_flat(attrs + num_attrs,
                                        flat_attrs + num_attrs);
    }

    ble_hs_test_util_create_conn(2, ((uint8_t[]) {2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    mtu = ble_att_mtu_by_cid(2, BLE_L2CAP_CID_ATT);

    rc = ble_gattc_write_reliable(2, attrs, num_attrs,
                                  ble_gatt_write_test_reliable_cb_good, NULL);
    TEST_ASSERT(rc == 0);

    attr_idx = 0;
    off = 0;
    while (attr_idx < num_attrs) {
        attr = flat_attrs + attr_idx;

        len = mtu - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
        if (off + len > attr->value_len) {
            len = attr->value_len - off;
        }

        /* Send the pending ATT Prep Write Command. */
        ble_hs_test_util_verify_tx_prep_write(attr->handle, off,
                                              attr->value + off, len);

        /* Receive Prep Write response. */
        ble_gatt_write_test_rx_prep_rsp(2, BLE_L2CAP_CID_ATT, attr->handle, off,
                                        attr->value + off, len);

        /* Verify callback hasn't gotten called. */
        TEST_ASSERT(!ble_gatt_write_test_cb_called);

        off += len;
        if (off >= attr->value_len) {
            attr_idx++;
            off = 0;
        }
    }

    /* Verify execute write request sent. */
    ble_hs_test_util_verify_tx_exec_write(BLE_ATT_EXEC_WRITE_F_EXECUTE);

    /* Receive Exec Write response. */
    ble_gatt_write_test_rx_exec_rsp(2, BLE_L2CAP_CID_ATT);

    /* Verify callback got called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
    TEST_ASSERT(ble_gatt_write_test_num_attrs == num_attrs);
    for (i = 0; i < num_attrs; i++) {
        rc = ble_hs_test_util_flat_attr_cmp(
            ble_gatt_write_test_attrs + i, flat_attrs + i);
        TEST_ASSERT(rc == 0);
    }
}

TEST_CASE_SELF(ble_gatt_write_test_no_rsp)
{
    int attr_len;
    int rc;

    ble_gatt_write_test_init();

    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    attr_len = 4;
    rc = ble_hs_test_util_gatt_write_no_rsp_flat(
        2, 100, ble_gatt_write_test_attr_value, attr_len);
    TEST_ASSERT(rc == 0);

    /* Send the pending ATT Write Command. */

    /* No response expected; verify callback not called. */
    TEST_ASSERT(!ble_gatt_write_test_cb_called);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_rsp)
{
    int attr_len;

    ble_gatt_write_test_init();

    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    attr_len = 4;
    ble_hs_test_util_gatt_write_flat(2, 100, ble_gatt_write_test_attr_value,
                                     attr_len, ble_gatt_write_test_cb_good,
                                     &attr_len);

    /* Send the pending ATT Write Command. */

    /* Response not received yet; verify callback not called. */
    TEST_ASSERT(!ble_gatt_write_test_cb_called);

    /* Receive write response. */
    ble_gatt_write_test_rx_rsp(2);

    /* Verify callback got called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_good)
{
    /*** 1 prep write req/rsp. */
    ble_gatt_write_test_misc_long_good(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ);

    /*** 2 prep write reqs/rsps. */
    ble_gatt_write_test_misc_long_good(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ + 1);

    /*** Maximum reqs/rsps. */
    ble_gatt_write_test_misc_long_good(BLE_ATT_ATTR_MAX_LEN);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_bad_handle)
{
    /*** 1 prep write req/rsp. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ,
        ble_gatt_write_test_misc_long_fail_handle);

    /*** 2 prep write reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ + 1,
        ble_gatt_write_test_misc_long_fail_handle);

    /*** Maximum reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_ATTR_MAX_LEN,
        ble_gatt_write_test_misc_long_fail_handle);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_bad_offset)
{
    /*** 1 prep write req/rsp. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ,
        ble_gatt_write_test_misc_long_fail_offset);

    /*** 2 prep write reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ + 1,
        ble_gatt_write_test_misc_long_fail_offset);

    /*** Maximum reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_ATTR_MAX_LEN,
        ble_gatt_write_test_misc_long_fail_offset);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_bad_value)
{
    /*** 1 prep write req/rsp. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ,
        ble_gatt_write_test_misc_long_fail_value);

    /*** 2 prep write reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ + 1,
        ble_gatt_write_test_misc_long_fail_value);

    /*** Maximum reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_ATTR_MAX_LEN,
        ble_gatt_write_test_misc_long_fail_value);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_bad_length)
{
    /*** 1 prep write req/rsp. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ,
        ble_gatt_write_test_misc_long_fail_length);

    /*** 2 prep write reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ + 1,
        ble_gatt_write_test_misc_long_fail_length);

    /*** Maximum reqs/rsps. */
    ble_gatt_write_test_misc_long_bad(
        BLE_ATT_ATTR_MAX_LEN,
        ble_gatt_write_test_misc_long_fail_length);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_reliable_good)
{
    /*** 1 attribute. */
    ble_gatt_write_test_misc_reliable_good(
        ((struct ble_hs_test_util_flat_attr[]) { {
            .handle = 100,
            .value_len = 2,
            .value = { 1, 2 },
        }, {
            0
        } }));

    /*** 2 attributes. */
    ble_gatt_write_test_misc_reliable_good(
        ((struct ble_hs_test_util_flat_attr[]) { {
            .handle = 100,
            .value_len = 2,
            .value = { 1,2 },
        }, {
            .handle = 113,
            .value_len = 6,
            .value = { 5,6,7,8,9,10 },
        }, {
            0
        } }));

    /*** 3 attributes. */
    ble_gatt_write_test_misc_reliable_good(
        ((struct ble_hs_test_util_flat_attr[]) { {
            .handle = 100,
            .value_len = 2,
            .value = { 1,2 },
        }, {
            .handle = 113,
            .value_len = 6,
            .value = { 5,6,7,8,9,10 },
        }, {
            .handle = 144,
            .value_len = 1,
            .value = { 0xff },
        }, {
            0
        } }));

    /*** Long attributes. */
    ble_gatt_write_test_misc_reliable_good(
        ((struct ble_hs_test_util_flat_attr[]) { {
            .handle = 100,
            .value_len = 20,
            .value = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 },
        }, {
            .handle = 144,
            .value_len = 20,
            .value = { 11,12,13,14,15,16,17,18,19,110,
                       111,112,113,114,115,116,117,118,119,120 },
        }, {
            0
        } }));

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_queue_full)
{
    int off;
    int len;
    int rc;
    int i;

    ble_gatt_write_test_init();

    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    rc = ble_hs_test_util_gatt_write_long_flat(
        2, 100, ble_gatt_write_test_attr_value, 128,
        ble_gatt_write_test_cb_good, NULL);
    TEST_ASSERT(rc == 0);

    off = 0;
    for (i = 0; i < 2; i++) {
        /* Verify prep write request was sent. */
        TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() != NULL);

        /* Receive Prep Write response. */
        len = BLE_ATT_MTU_DFLT - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;
        ble_gatt_write_test_rx_prep_rsp(
            2, BLE_L2CAP_CID_ATT, 100, off, ble_gatt_write_test_attr_value + off, len);

        /* Verify callback hasn't gotten called. */
        TEST_ASSERT(!ble_gatt_write_test_cb_called);

        off += len;
    }

    /* Verify prep write request was sent. */
    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue() != NULL);

    /* Receive queue full error. */
    ble_hs_test_util_rx_att_err_rsp(2, BLE_L2CAP_CID_ATT,
                                    BLE_ATT_OP_PREP_WRITE_REQ,
                                    BLE_ATT_ERR_PREPARE_QUEUE_FULL, 100);

    /* Verify callback was called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
    TEST_ASSERT(ble_gatt_write_test_error.status ==
                BLE_HS_ATT_ERR(BLE_ATT_ERR_PREPARE_QUEUE_FULL));
    TEST_ASSERT(ble_gatt_write_test_error.att_handle == 100);

    /* Verify clear queue command got sent. */
    ble_hs_test_util_verify_tx_exec_write(BLE_ATT_EXEC_WRITE_F_CANCEL);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_long_oom)
{
    static const struct ble_hs_test_util_flat_attr attr = {
        .handle = 34,
        .value = {
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20,
        },
        .value_len = 20,
    };

    struct os_mbuf *oms;
    int32_t ticks_until;
    int chunk_sz;
    int off;
    int rc;

    ble_gatt_write_test_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    /* Initiate a write long procedure. */
    off = 0;
    rc = ble_hs_test_util_gatt_write_long_flat(
        2, attr.handle, attr.value, attr.value_len,
        ble_gatt_write_test_cb_good, NULL);
    TEST_ASSERT_FATAL(rc == 0);

    chunk_sz = ble_att_mtu_by_cid(2, BLE_L2CAP_CID_ATT) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;

    ble_hs_test_util_verify_tx_prep_write(attr.handle, off,
                                          attr.value + off, chunk_sz);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_write_test_rx_prep_rsp(2, BLE_L2CAP_CID_ATT, attr.handle, off, attr.value + off,
                                    chunk_sz);
    off += chunk_sz;

    /* Ensure no follow-up request got sent.  It should not have gotten sent
     * due to mbuf exhaustion.
     */
    ble_hs_test_util_prev_tx_queue_clear();
    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue_pullup() == NULL);

    /* Verify that we will resume the stalled GATT procedure in one second. */
    ticks_until = ble_gattc_timer();
    TEST_ASSERT(ticks_until == os_time_ms_to_ticks32(MYNEWT_VAL(BLE_GATT_RESUME_RATE)));

    /* Verify the procedure proceeds after mbufs become available. */
    rc = os_mbuf_free_chain(oms);
    TEST_ASSERT_FATAL(rc == 0);
    os_time_advance(ticks_until);
    ble_gattc_timer();

    chunk_sz = attr.value_len - off;
    ble_hs_test_util_verify_tx_prep_write(attr.handle, off,
                                          attr.value + off, chunk_sz);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_write_test_rx_prep_rsp(
        2, BLE_L2CAP_CID_ATT, attr.handle, off, attr.value + off, chunk_sz);
    off += chunk_sz;

    /* Ensure no follow-up request got sent.  It should not have gotten sent
     * due to mbuf exhaustion.
     */
    ble_hs_test_util_prev_tx_queue_clear();
    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue_pullup() == NULL);

    /* Verify that we will resume the stalled GATT procedure in one second. */
    ticks_until = ble_gattc_timer();
    TEST_ASSERT(ticks_until == os_time_ms_to_ticks32(MYNEWT_VAL(BLE_GATT_RESUME_RATE)));

    /* Verify that procedure completes when mbufs are available. */
    rc = os_mbuf_free_chain(oms);
    TEST_ASSERT_FATAL(rc == 0);
    os_time_advance(ticks_until);
    ble_gattc_timer();

    /* Verify execute write request sent. */
    ble_hs_test_util_verify_tx_exec_write(BLE_ATT_EXEC_WRITE_F_EXECUTE);

    /* Receive Exec Write response. */
    ble_gatt_write_test_rx_exec_rsp(2, BLE_L2CAP_CID_ATT);

    /* Verify callback got called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
    TEST_ASSERT(!ble_gattc_any_jobs());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_write_test_reliable_oom)
{
    static const struct ble_hs_test_util_flat_attr attr = {
        .handle = 34,
        .value = {
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20,
        },
        .value_len = 20,
    };

    struct ble_gatt_attr mattr;
    struct os_mbuf *oms;
    int32_t ticks_until;
    int chunk_sz;
    int off;
    int rc;

    ble_gatt_write_test_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);

    /* Initiate a write reliable procedure. */
    ble_hs_test_util_attr_from_flat(&mattr, &attr);

    off = 0;
    rc = ble_gattc_write_reliable(2, &mattr, 1,
                                  ble_gatt_write_test_reliable_cb_good, NULL);
    TEST_ASSERT_FATAL(rc == 0);

    chunk_sz = ble_att_mtu_by_cid(2, BLE_L2CAP_CID_ATT) - BLE_ATT_PREP_WRITE_CMD_BASE_SZ;

    ble_hs_test_util_verify_tx_prep_write(attr.handle, off,
                                          attr.value + off, chunk_sz);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_write_test_rx_prep_rsp(2, BLE_L2CAP_CID_ATT, attr.handle, off, attr.value + off,
                                    chunk_sz);
    off += chunk_sz;

    /* Ensure no follow-up request got sent.  It should not have gotten sent
     * due to mbuf exhaustion.
     */
    ble_hs_test_util_prev_tx_queue_clear();
    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue_pullup() == NULL);

    /* Verify that we will resume the stalled GATT procedure in one second. */
    ticks_until = ble_gattc_timer();
    TEST_ASSERT(ticks_until == os_time_ms_to_ticks32(MYNEWT_VAL(BLE_GATT_RESUME_RATE)));

    /* Verify the procedure proceeds after mbufs become available. */
    rc = os_mbuf_free_chain(oms);
    TEST_ASSERT_FATAL(rc == 0);
    os_time_advance(ticks_until);
    ble_gattc_timer();

    chunk_sz = attr.value_len - off;
    ble_hs_test_util_verify_tx_prep_write(attr.handle, off,
                                          attr.value + off, chunk_sz);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_write_test_rx_prep_rsp(
        2, BLE_L2CAP_CID_ATT, attr.handle, off, attr.value + off, chunk_sz);
    off += chunk_sz;

    /* Ensure no follow-up request got sent.  It should not have gotten sent
     * due to mbuf exhaustion.
     */
    ble_hs_test_util_prev_tx_queue_clear();
    TEST_ASSERT(ble_hs_test_util_prev_tx_dequeue_pullup() == NULL);

    /* Verify that we will resume the stalled GATT procedure in one second. */
    ticks_until = ble_gattc_timer();
    TEST_ASSERT(ticks_until == os_time_ms_to_ticks32(MYNEWT_VAL(BLE_GATT_RESUME_RATE)));

    /* Verify that procedure completes when mbufs are available. */
    rc = os_mbuf_free_chain(oms);
    TEST_ASSERT_FATAL(rc == 0);
    os_time_advance(ticks_until);
    ble_gattc_timer();

    /* Verify execute write request sent. */
    ble_hs_test_util_verify_tx_exec_write(BLE_ATT_EXEC_WRITE_F_EXECUTE);

    /* Receive Exec Write response. */
    ble_gatt_write_test_rx_exec_rsp(2, BLE_L2CAP_CID_ATT);

    /* Verify callback got called. */
    TEST_ASSERT(ble_gatt_write_test_cb_called);
    TEST_ASSERT(!ble_gattc_any_jobs());

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gatt_write_test_suite)
{
    ble_gatt_write_test_no_rsp();
    ble_gatt_write_test_rsp();
    ble_gatt_write_test_long_good();
    ble_gatt_write_test_long_bad_handle();
    ble_gatt_write_test_long_bad_offset();
    ble_gatt_write_test_long_bad_value();
    ble_gatt_write_test_long_bad_length();
    ble_gatt_write_test_long_queue_full();
    ble_gatt_write_test_reliable_good();
    ble_gatt_write_test_long_oom();
    ble_gatt_write_test_reliable_oom();
}
