/*
 * 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_uuid.h"
#include "ble_hs_test_util.h"

static struct ble_gatt_svc ble_gatt_find_s_test_svcs[256];
static int ble_gatt_find_s_test_num_svcs;
static int ble_gatt_find_s_test_proc_complete;

struct ble_gatt_find_s_test_entry {
    uint16_t inc_handle; /* 0 indicates no more entries. */
    uint16_t start_handle;
    uint16_t end_handle;
    const ble_uuid_t *uuid;
};

static void
ble_gatt_find_s_test_misc_init(void)
{
    ble_hs_test_util_init();
    ble_gatt_find_s_test_num_svcs = 0;
    ble_gatt_find_s_test_proc_complete = 0;
}

static int
ble_gatt_find_s_test_misc_cb(uint16_t conn_handle,
                             const struct ble_gatt_error *error,
                             const struct ble_gatt_svc *service,
                             void *arg)
{
    TEST_ASSERT(!ble_gatt_find_s_test_proc_complete);
    TEST_ASSERT(error != NULL);

    switch (error->status) {
    case 0:
        ble_gatt_find_s_test_svcs[ble_gatt_find_s_test_num_svcs++] = *service;
        break;

    case BLE_HS_EDONE:
        ble_gatt_find_s_test_proc_complete = 1;
        break;

    default:
        TEST_ASSERT(0);
        break;
    }

    return 0;
}

static void
ble_gatt_find_s_test_misc_verify_incs(
    struct ble_gatt_find_s_test_entry *entries)
{
    int i;

    for (i = 0; entries[i].inc_handle != 0; i++) {
        TEST_ASSERT(ble_gatt_find_s_test_svcs[i].start_handle ==
                    entries[i].start_handle);
        TEST_ASSERT(ble_gatt_find_s_test_svcs[i].end_handle ==
                    entries[i].end_handle);
        TEST_ASSERT(ble_uuid_cmp(&ble_gatt_find_s_test_svcs[i].uuid.u,
                                 entries[i].uuid) == 0);
    }

    TEST_ASSERT(i == ble_gatt_find_s_test_num_svcs);
    TEST_ASSERT(ble_gatt_find_s_test_proc_complete);
}

static int
ble_gatt_find_s_test_misc_rx_read_type(uint16_t conn_handle, uint16_t cid,
                                       struct ble_gatt_find_s_test_entry *entries)
{
    struct ble_att_read_type_rsp rsp;
    uint8_t buf[1024];
    int off;
    int rc;
    int i;

    memset(&rsp, 0, sizeof rsp);

    off = BLE_ATT_READ_TYPE_RSP_BASE_SZ;
    for (i = 0; entries[i].inc_handle != 0; i++) {
        if (rsp.batp_length == BLE_GATTS_INC_SVC_LEN_NO_UUID + 2) {
            break;
        }

        if (entries[i].uuid->type != BLE_UUID_TYPE_16) {
            if (rsp.batp_length != 0) {
                break;
            }
            rsp.batp_length = BLE_GATTS_INC_SVC_LEN_NO_UUID + 2;
        } else {
            rsp.batp_length = BLE_GATTS_INC_SVC_LEN_UUID + 2;
        }

        TEST_ASSERT_FATAL(off + rsp.batp_length <= sizeof buf);

        put_le16(buf + off, entries[i].inc_handle);
        off += 2;

        put_le16(buf + off, entries[i].start_handle);
        off += 2;

        put_le16(buf + off, entries[i].end_handle);
        off += 2;

        if (entries[i].uuid->type == BLE_UUID_TYPE_16) {
            put_le16(buf + off, ble_uuid_u16(entries[i].uuid));
            off += 2;
        }
    }

    if (i == 0) {
        ble_hs_test_util_rx_att_err_rsp(conn_handle, cid,
                                        BLE_ATT_OP_READ_TYPE_REQ,
                                        BLE_ATT_ERR_ATTR_NOT_FOUND, 0);
        return 0;
    }

    ble_att_read_type_rsp_write(buf + 0, BLE_ATT_READ_TYPE_RSP_BASE_SZ, &rsp);

    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, cid,
                                                buf, off);
    TEST_ASSERT(rc == 0);

    return i;
}

static void
ble_gatt_find_s_test_misc_rx_read(uint16_t conn_handle, uint16_t cid, const ble_uuid_t *uuid)
{
    uint8_t buf[17];
    int rc;

    TEST_ASSERT(uuid->type == BLE_UUID_TYPE_128);

    buf[0] = BLE_ATT_OP_READ_RSP;
    ble_uuid_flat(uuid, buf + 1);

    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn_handle, cid,
                                                buf, 17);
    TEST_ASSERT(rc == 0);
}

static void
ble_gatt_find_s_test_misc_verify_tx_read_type(uint16_t start_handle,
                                              uint16_t end_handle)
{
    struct ble_att_read_type_req req;
    struct os_mbuf *om;
    uint16_t uuid16;

    om = ble_hs_test_util_prev_tx_dequeue_pullup();
    TEST_ASSERT_FATAL(om != NULL);

    ble_att_read_type_req_parse(om->om_data, om->om_len, &req);

    TEST_ASSERT(req.batq_start_handle == start_handle);
    TEST_ASSERT(req.batq_end_handle == end_handle);
    TEST_ASSERT(om->om_len == BLE_ATT_READ_TYPE_REQ_BASE_SZ + 2);
    uuid16 = get_le16(om->om_data + BLE_ATT_READ_TYPE_REQ_BASE_SZ);
    TEST_ASSERT(uuid16 == BLE_ATT_UUID_INCLUDE);
}

static void
ble_gatt_find_s_test_misc_verify_tx_read(uint16_t handle)
{
    struct ble_att_read_req req;
    struct os_mbuf *om;

    om = ble_hs_test_util_prev_tx_dequeue_pullup();
    TEST_ASSERT_FATAL(om != NULL);

    ble_att_read_req_parse(om->om_data, om->om_len, &req);

    TEST_ASSERT(req.barq_handle == handle);
    TEST_ASSERT(om->om_len == BLE_ATT_READ_REQ_SZ);
}

static void
ble_gatt_find_s_test_misc_find_inc(uint16_t conn_handle, uint16_t cid,
                                   uint16_t start_handle, uint16_t end_handle,
                                   struct ble_gatt_find_s_test_entry *entries)
{
    struct ble_gatt_svc service;
    int cur_start;
    int num_found;
    int idx;
    int rc;
    int i;

    rc = ble_gattc_find_inc_svcs(conn_handle, start_handle, end_handle,
                                 ble_gatt_find_s_test_misc_cb, &service);
    TEST_ASSERT(rc == 0);

    cur_start = start_handle;
    idx = 0;
    while (1) {
        ble_gatt_find_s_test_misc_verify_tx_read_type(cur_start, end_handle);
        num_found = ble_gatt_find_s_test_misc_rx_read_type(conn_handle, cid,
                                                           entries + idx);
        if (num_found == 0) {
            break;
        }

        if (entries[idx].uuid->type == BLE_UUID_TYPE_128) {
            TEST_ASSERT(num_found == 1);
            ble_gatt_find_s_test_misc_verify_tx_read(
                entries[idx].start_handle);
            ble_gatt_find_s_test_misc_rx_read(conn_handle, cid,
                                              entries[idx].uuid);
        }

        idx += num_found;
        cur_start = entries[idx - 1].inc_handle + 1;
    }
    TEST_ASSERT(idx == ble_gatt_find_s_test_num_svcs);
    TEST_ASSERT(ble_gatt_find_s_test_proc_complete);

    for (i = 0; i < ble_gatt_find_s_test_num_svcs; i++) {
        TEST_ASSERT(ble_gatt_find_s_test_svcs[i].start_handle ==
                    entries[i].start_handle);
        TEST_ASSERT(ble_gatt_find_s_test_svcs[i].end_handle ==
                    entries[i].end_handle);
        TEST_ASSERT(ble_uuid_cmp(&ble_gatt_find_s_test_svcs[i].uuid.u,
                                 entries[i].uuid) == 0);
    }
}

TEST_CASE_SELF(ble_gatt_find_s_test_1)
{
    /* Two 16-bit UUID services; one response. */
    ble_gatt_find_s_test_misc_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    ble_gatt_find_s_test_misc_find_inc(2, BLE_L2CAP_CID_ATT, 5, 10,
        ((struct ble_gatt_find_s_test_entry[]) { {
            .inc_handle = 6,
            .start_handle = 35,
            .end_handle = 49,
            .uuid = BLE_UUID16_DECLARE(0x5155),
        }, {
            .inc_handle = 9,
            .start_handle = 543,
            .end_handle = 870,
            .uuid = BLE_UUID16_DECLARE(0x1122),
        }, {
            0,
        } })
    );

    /* One 128-bit UUID service; two responses. */
    ble_gatt_find_s_test_misc_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    ble_gatt_find_s_test_misc_find_inc(2, BLE_L2CAP_CID_ATT, 34, 100,
        ((struct ble_gatt_find_s_test_entry[]) { {
            .inc_handle = 36,
            .start_handle = 403,
            .end_handle = 859,
            .uuid = BLE_UUID128_DECLARE(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16),
        }, {
            0,
        } })
    );

    /* Two 128-bit UUID service; four responses. */
    ble_gatt_find_s_test_misc_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    ble_gatt_find_s_test_misc_find_inc(2, BLE_L2CAP_CID_ATT, 34, 100,
        ((struct ble_gatt_find_s_test_entry[]) { {
            .inc_handle = 36,
            .start_handle = 403,
            .end_handle = 859,
            .uuid = BLE_UUID128_DECLARE(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16),
        }, {
            .inc_handle = 39,
            .start_handle = 900,
            .end_handle = 932,
            .uuid = BLE_UUID128_DECLARE(2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17),
        }, {
            0,
        } })
    );

    /* Two 16-bit UUID; three 128-bit UUID; seven responses. */
    ble_gatt_find_s_test_misc_init();
    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}),
                                 NULL, NULL);
    ble_gatt_find_s_test_misc_find_inc(2, BLE_L2CAP_CID_ATT, 1, 100,
        ((struct ble_gatt_find_s_test_entry[]) { {
            .inc_handle = 36,
            .start_handle = 403,
            .end_handle = 859,
            .uuid = BLE_UUID128_DECLARE(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16),
        }, {
            .inc_handle = 37,
            .start_handle = 35,
            .end_handle = 49,
            .uuid = BLE_UUID16_DECLARE(0x5155),
        }, {
            .inc_handle = 38,
            .start_handle = 543,
            .end_handle = 870,
            .uuid = BLE_UUID16_DECLARE(0x1122),
        }, {
            .inc_handle = 39,
            .start_handle = 900,
            .end_handle = 932,
            .uuid = BLE_UUID128_DECLARE(2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17),
        }, {
            .inc_handle = 40,
            .start_handle = 940,
            .end_handle = 950,
            .uuid = BLE_UUID128_DECLARE(3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18),
        }, {
            0,
        } })
    );

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_CASE_SELF(ble_gatt_find_s_test_oom)
{

    struct ble_gatt_find_s_test_entry incs[] = {
        {
            .inc_handle = 21,
            .start_handle = 800,
            .end_handle = 899,
            .uuid = BLE_UUID128_DECLARE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15),
        },
        {
            .inc_handle = 22,
            .start_handle = 900,
            .end_handle = 999,
            .uuid = BLE_UUID128_DECLARE(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16),
        },
        { 0 }
    };

    struct os_mbuf *oms;
    int32_t ticks_until;
    int rc;

    ble_gatt_find_s_test_misc_init();

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

    /* Initiate a discover all characteristics procedure. */
    rc = ble_gattc_find_inc_svcs(1, 20, 30,
                                 ble_gatt_find_s_test_misc_cb, NULL);
    TEST_ASSERT_FATAL(rc == 0);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_find_s_test_misc_rx_read_type(1, BLE_L2CAP_CID_ATT, incs);

    /* 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 succeeds after mbufs become available. */
    rc = os_mbuf_free_chain(oms);
    TEST_ASSERT_FATAL(rc == 0);
    os_time_advance(ticks_until);
    ble_gattc_timer();

    /* We can't cause a memory exhaustion error on the follow up request.  The
     * GATT client frees the read response immediately before sending the
     * follow-up request, so there is always an mbuf available.
     */
    /* XXX: Find a way to test this. */
    ble_gatt_find_s_test_misc_rx_read(1, BLE_L2CAP_CID_ATT, incs[0].uuid);

    /* Exhaust the msys pool.  Leave one mbuf for the forthcoming response. */
    oms = ble_hs_test_util_mbuf_alloc_all_but(1);
    ble_gatt_find_s_test_misc_rx_read_type(1, BLE_L2CAP_CID_ATT, incs + 1);

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

    ble_gatt_find_s_test_misc_rx_read(1, BLE_L2CAP_CID_ATT, incs[1].uuid);

    ble_hs_test_util_rx_att_err_rsp(1, BLE_L2CAP_CID_ATT,
                                    BLE_ATT_OP_READ_TYPE_REQ,
                                    BLE_ATT_ERR_ATTR_NOT_FOUND,
                                    1);

    ble_gatt_find_s_test_misc_verify_incs(incs);

    ble_hs_test_util_assert_mbufs_freed(NULL);
}

TEST_SUITE(ble_gatt_find_s_test_suite)
{
    ble_gatt_find_s_test_1();
    ble_gatt_find_s_test_oom();
}
