You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2015/12/01 02:38:19 UTC

[2/2] incubator-mynewt-larva git commit: Initial work for client-side ATT.

Initial work for client-side ATT.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/commit/61e167e6
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/tree/61e167e6
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/diff/61e167e6

Branch: refs/heads/master
Commit: 61e167e603383dcc3db2031fede4a5e7d9caff95
Parents: 8abbb7f
Author: Christopher Collins <cc...@gmail.com>
Authored: Mon Nov 30 17:37:40 2015 -0800
Committer: Christopher Collins <cc...@gmail.com>
Committed: Mon Nov 30 17:37:40 2015 -0800

----------------------------------------------------------------------
 net/nimble/host/include/host/ble_hs_test.h     |   3 +-
 net/nimble/host/src/ble_hs.c                   |  30 +-
 net/nimble/host/src/ble_hs_att.c               |  74 +-
 net/nimble/host/src/ble_hs_att.h               |  52 +-
 net/nimble/host/src/ble_hs_att_clt.c           | 263 ++++++
 net/nimble/host/src/ble_hs_att_cmd.c           | 103 +--
 net/nimble/host/src/ble_hs_att_cmd.h           |  13 +-
 net/nimble/host/src/ble_hs_att_svr.c           | 148 +---
 net/nimble/host/src/ble_hs_conn.c              |  12 +-
 net/nimble/host/src/ble_hs_conn.h              |   9 +-
 net/nimble/host/src/ble_hs_work.c              |  41 +-
 net/nimble/host/src/ble_hs_work.h              |   6 +-
 net/nimble/host/src/host_hci.c                 |   5 +-
 net/nimble/host/src/test/ble_gap_test.c        |  11 +-
 net/nimble/host/src/test/ble_hs_att_clt_test.c | 133 +++
 net/nimble/host/src/test/ble_hs_att_svr_test.c | 860 +++++++++++++++++++
 net/nimble/host/src/test/ble_hs_att_test.c     | 862 --------------------
 net/nimble/host/src/test/ble_hs_test.c         |   3 +-
 net/nimble/host/src/test/ble_hs_test_util.c    |  24 +-
 net/nimble/host/src/test/ble_hs_test_util.h    |   1 +
 20 files changed, 1573 insertions(+), 1080 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/include/host/ble_hs_test.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/include/host/ble_hs_test.h b/net/nimble/host/include/host/ble_hs_test.h
index d45a748..874d92d 100644
--- a/net/nimble/host/include/host/ble_hs_test.h
+++ b/net/nimble/host/include/host/ble_hs_test.h
@@ -22,7 +22,8 @@ struct os_mbuf;
 void ble_hs_test_pkt_txed(struct os_mbuf *om);
 
 int l2cap_test_all(void);
-int ble_hs_att_test_all(void);
+int ble_hs_att_svr_test_all(void);
+int ble_hs_att_clt_test_all(void);
 int ble_host_hci_test_all(void);
 int ble_hs_conn_test_all(void);
 int ble_gap_test_all(void);

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs.c b/net/nimble/host/src/ble_hs.c
index 0d80519..7c67b4c 100644
--- a/net/nimble/host/src/ble_hs.c
+++ b/net/nimble/host/src/ble_hs.c
@@ -104,7 +104,7 @@ ble_hs_process_rx_data_queue(void)
     struct ble_hs_pkt *pkt;
     struct tpq_elem *tpq_elem;
 
-    while ((tpq_elem = tpq_get(&ble_hs_tx_q)) != NULL) {
+    while ((tpq_elem = tpq_get(&ble_hs_rx_q)) != NULL) {
         pkt = (void *)tpq_elem;
         host_hci_data_rx(pkt->bhp_om);
 
@@ -112,11 +112,32 @@ ble_hs_process_rx_data_queue(void)
     }
 }
 
+static int
+ble_hs_read_hci_buf_size(void)
+{
+    struct ble_hs_work_entry *entry;
+
+    entry = ble_hs_work_entry_alloc();
+    if (entry == NULL) {
+        return ENOMEM;
+    }
+
+    entry->bwe_type = BLE_HS_WORK_TYPE_READ_HCI_BUF_SIZE;
+
+    ble_hs_work_enqueue(entry);
+
+    return 0;
+}
+
 void
 ble_hs_task_handler(void *arg)
 {
     struct os_event *ev;
     struct os_callout_func *cf;
+    int rc;
+
+    rc = ble_hs_read_hci_buf_size();
+    assert(rc == 0);
 
     while (1) {
         ev = os_eventq_get(&ble_hs_evq);
@@ -151,7 +172,7 @@ ble_hs_task_handler(void *arg)
         /* If a work event is not already in progress and there is another
          * event pending, begin processing it.
          */
-        if (!ble_hs_work_busy) {
+        if (ble_hs_work_cur_entry == NULL) {
             ble_hs_work_process_next();
         }
     }
@@ -295,6 +316,11 @@ ble_hs_init(uint8_t prio)
         goto err;
     }
 
+    rc = ble_hs_att_clt_init();
+    if (rc != 0) {
+        goto err;
+    }
+
     rc = ble_gap_conn_init();
     if (rc != 0) {
         goto err;

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att.c b/net/nimble/host/src/ble_hs_att.c
index 839861d..0aaef1f 100644
--- a/net/nimble/host/src/ble_hs_att.c
+++ b/net/nimble/host/src/ble_hs_att.c
@@ -15,8 +15,78 @@
  */
 
 #include <stddef.h>
-#include "ble_hs_att.h"
+#include <errno.h>
 #include "ble_l2cap.h"
+#include "ble_hs_att_cmd.h"
+#include "ble_hs_att.h"
+
+/** Dispatch table for incoming ATT requests.  Sorted by op code. */
+typedef int ble_hs_att_rx_fn(struct ble_hs_conn *conn,
+                                 struct ble_l2cap_chan *chan,
+                                 struct os_mbuf *om);
+struct ble_hs_att_rx_dispatch_entry {
+    uint8_t bde_op;
+    ble_hs_att_rx_fn *bde_fn;
+};
+
+static struct ble_hs_att_rx_dispatch_entry ble_hs_att_rx_dispatch[] = {
+    { BLE_HS_ATT_OP_MTU_REQ,              ble_hs_att_svr_rx_mtu },
+    { BLE_HS_ATT_OP_FIND_INFO_REQ,        ble_hs_att_svr_rx_find_info },
+    { BLE_HS_ATT_OP_FIND_INFO_RSP,        ble_hs_att_clt_rx_find_info },
+    { BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ,  ble_hs_att_svr_rx_find_type_value },
+    { BLE_HS_ATT_OP_READ_TYPE_REQ,        ble_hs_att_svr_rx_read_type },
+    { BLE_HS_ATT_OP_READ_REQ,             ble_hs_att_svr_rx_read },
+    { BLE_HS_ATT_OP_WRITE_REQ,            ble_hs_att_svr_rx_write },
+};
+
+#define BLE_HS_ATT_RX_DISPATCH_SZ \
+    (sizeof ble_hs_att_rx_dispatch / sizeof ble_hs_att_rx_dispatch[0])
+
+static struct ble_hs_att_rx_dispatch_entry *
+ble_hs_att_rx_dispatch_entry_find(uint8_t op)
+{
+    struct ble_hs_att_rx_dispatch_entry *entry;
+    int i;
+
+    for (i = 0; i < BLE_HS_ATT_RX_DISPATCH_SZ; i++) {
+        entry = ble_hs_att_rx_dispatch + i;
+        if (entry->bde_op == op) {
+            return entry;
+        }
+
+        if (entry->bde_op > op) {
+            break;
+        }
+    }
+
+    return NULL;
+}
+
+static int
+ble_hs_att_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
+              struct os_mbuf *om)
+{
+    struct ble_hs_att_rx_dispatch_entry *entry;
+    uint8_t op;
+    int rc;
+
+    rc = os_mbuf_copydata(om, 0, 1, &op);
+    if (rc != 0) {
+        return EMSGSIZE;
+    }
+
+    entry = ble_hs_att_rx_dispatch_entry_find(op);
+    if (entry == NULL) {
+        return EINVAL;
+    }
+
+    rc = entry->bde_fn(conn, chan, om);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
 
 struct ble_l2cap_chan *
 ble_hs_att_create_chan(void)
@@ -31,7 +101,7 @@ ble_hs_att_create_chan(void)
     chan->blc_cid = BLE_L2CAP_CID_ATT;
     chan->blc_my_mtu = BLE_HS_ATT_MTU_DFLT;
     chan->blc_default_mtu = BLE_HS_ATT_MTU_DFLT;
-    chan->blc_rx_fn = ble_hs_att_svr_rx;
+    chan->blc_rx_fn = ble_hs_att_rx;
 
     return chan;
 }

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att.h b/net/nimble/host/src/ble_hs_att.h
index ea1466f..d76f4d1 100644
--- a/net/nimble/host/src/ble_hs_att.h
+++ b/net/nimble/host/src/ble_hs_att.h
@@ -22,6 +22,7 @@
 struct os_mbuf;
 struct ble_hs_conn;
 struct ble_l2cap_chan;
+struct ble_hs_att_find_info_req;
 
 #define BLE_HS_ATT_MTU_DFLT         23  /* Also the minimum. */
 #define BLE_HS_ATT_MTU_MAX          256 /* XXX: I'm making this up! */
@@ -97,11 +98,56 @@ struct ble_hs_att_svr_entry {
 #define HA_OPCODE_COMMAND_FLAG (1 << 6) 
 #define HA_OPCODE_AUTH_SIG_FLAG (1 << 7) 
 
+struct ble_hs_att_clt_entry {
+    SLIST_ENTRY(ble_hs_att_clt_entry) bhac_next;
+    uint16_t bhac_handle_id;
+    uint8_t bhac_uuid[16];
+};
+
+SLIST_HEAD(ble_hs_att_clt_entry_list, ble_hs_att_clt_entry);
+
+/*** @gen */
+struct ble_l2cap_chan *ble_hs_att_create_chan(void);
+
+/*** @svr */
 int ble_hs_att_svr_register(uint8_t *uuid, uint8_t flags, uint16_t *handle_id,
                             ble_hs_att_svr_handle_func *fn);
-int ble_hs_att_svr_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
-                      struct os_mbuf *om);
-struct ble_l2cap_chan *ble_hs_att_create_chan(void);
+
+
+int ble_hs_att_svr_rx_mtu(struct ble_hs_conn *conn,
+                          struct ble_l2cap_chan *chan,
+                          struct os_mbuf *om);
+int ble_hs_att_svr_rx_find_info(struct ble_hs_conn *conn,
+                                struct ble_l2cap_chan *chan,
+                                struct os_mbuf *om);
+int ble_hs_att_svr_rx_find_type_value(struct ble_hs_conn *conn,
+                                      struct ble_l2cap_chan *chan,
+                                      struct os_mbuf *om);
+int ble_hs_att_svr_rx_read_type(struct ble_hs_conn *conn,
+                                struct ble_l2cap_chan *chan,
+                                struct os_mbuf *om);
+int ble_hs_att_svr_rx_read(struct ble_hs_conn *conn,
+                           struct ble_l2cap_chan *chan,
+                           struct os_mbuf *om);
+int ble_hs_att_svr_rx_write(struct ble_hs_conn *conn,
+                            struct ble_l2cap_chan *chan,
+                            struct os_mbuf *om);
 int ble_hs_att_svr_init(void);
 
+/*** @cnt */
+void ble_hs_att_clt_entry_list_free(struct ble_hs_att_clt_entry_list *list);
+int ble_hs_att_clt_entry_insert(struct ble_hs_conn *conn, uint16_t handle_id,
+                                uint8_t *uuid);
+uint16_t ble_hs_att_clt_find_entry_uuid128(struct ble_hs_conn *conn,
+                                           void *uuid128);
+uint16_t ble_hs_att_clt_find_entry_uuid16(struct ble_hs_conn *conn,
+                                          uint16_t uuid16);
+int ble_hs_att_clt_tx_find_info(struct ble_hs_conn *conn,
+                                struct ble_l2cap_chan *chan,
+                                struct ble_hs_att_find_info_req *req);
+int ble_hs_att_clt_rx_find_info(struct ble_hs_conn *conn,
+                                struct ble_l2cap_chan *chan,
+                                struct os_mbuf *om);
+int ble_hs_att_clt_init(void);
+
 #endif

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att_clt.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att_clt.c b/net/nimble/host/src/ble_hs_att_clt.c
new file mode 100644
index 0000000..28a2d8d
--- /dev/null
+++ b/net/nimble/host/src/ble_hs_att_clt.c
@@ -0,0 +1,263 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include "os/os_mempool.h"
+#include "nimble/ble.h"
+#include "host/ble_hs.h"
+#include "ble_hs_uuid.h"
+#include "ble_hs_conn.h"
+#include "ble_hs_att_cmd.h"
+#include "ble_hs_att.h"
+
+#define BLE_HS_ATT_CLT_NUM_ENTRIES  128
+static void *ble_hs_att_clt_entry_mem;
+static struct os_mempool ble_hs_att_clt_entry_pool;
+
+static struct ble_hs_att_clt_entry *
+ble_hs_att_clt_entry_alloc(void)
+{
+    struct ble_hs_att_clt_entry *entry;
+
+    entry = os_memblock_get(&ble_hs_att_clt_entry_pool);
+    if (entry != NULL) {
+        memset(entry, 0, sizeof *entry);
+    }
+
+    return entry;
+}
+
+static void
+ble_hs_att_clt_entry_free(struct ble_hs_att_clt_entry *entry)
+{
+    int rc;
+
+    rc = os_memblock_put(&ble_hs_att_clt_entry_pool, entry);
+    assert(rc == 0);
+}
+
+void
+ble_hs_att_clt_entry_list_free(struct ble_hs_att_clt_entry_list *list)
+{
+    struct ble_hs_att_clt_entry *entry;
+
+    while ((entry = SLIST_FIRST(list)) != NULL) {
+        SLIST_REMOVE_HEAD(list, bhac_next);
+        ble_hs_att_clt_entry_free(entry);
+    }
+}
+
+int
+ble_hs_att_clt_entry_insert(struct ble_hs_conn *conn, uint16_t handle_id,
+                            uint8_t *uuid)
+{
+    struct ble_hs_att_clt_entry *entry;
+    struct ble_hs_att_clt_entry *prev;
+    struct ble_hs_att_clt_entry *cur;
+
+    /* XXX: Probably need to lock a semaphore here. */
+
+    entry = ble_hs_att_clt_entry_alloc();
+    if (entry == NULL) {
+        return ENOMEM;
+    }
+
+    entry->bhac_handle_id = handle_id;
+    memcpy(entry->bhac_uuid, uuid, sizeof entry->bhac_uuid);
+
+    prev = NULL;
+    SLIST_FOREACH(cur, &conn->bhc_att_clt_list, bhac_next) {
+        if (cur->bhac_handle_id == handle_id) {
+            return EEXIST;
+        }
+        if (cur->bhac_handle_id > handle_id) {
+            break;
+        }
+
+        prev = cur;
+    }
+
+    if (prev == NULL) {
+        SLIST_INSERT_HEAD(&conn->bhc_att_clt_list, entry, bhac_next);
+    } else {
+        SLIST_INSERT_AFTER(prev, entry, bhac_next);
+    }
+
+    return 0;
+}
+
+uint16_t
+ble_hs_att_clt_find_entry_uuid128(struct ble_hs_conn *conn, void *uuid128)
+{
+    struct ble_hs_att_clt_entry *entry;
+    int rc;
+
+    SLIST_FOREACH(entry, &conn->bhc_att_clt_list, bhac_next) {
+        rc = memcmp(entry->bhac_uuid, uuid128, 16);
+        if (rc == 0) {
+            return entry->bhac_handle_id;
+        }
+    }
+
+    return 0;
+}
+
+uint16_t
+ble_hs_att_clt_find_entry_uuid16(struct ble_hs_conn *conn, uint16_t uuid16)
+{
+    uint8_t uuid128[16];
+    uint16_t handle_id;
+    int rc;
+
+    rc = ble_hs_uuid_from_16bit(uuid16, uuid128);
+    if (rc != 0) {
+        return 0;
+    }
+
+    handle_id = ble_hs_att_clt_find_entry_uuid128(conn, uuid128);
+    return handle_id;
+}
+
+int
+ble_hs_att_clt_tx_find_info(struct ble_hs_conn *conn,
+                            struct ble_l2cap_chan *chan,
+                            struct ble_hs_att_find_info_req *req)
+{
+    struct os_mbuf *txom;
+    void *buf;
+    int rc;
+
+    txom = NULL;
+
+    if (req->bhafq_start_handle == 0 ||
+        req->bhafq_start_handle > req->bhafq_end_handle) {
+
+        rc = EINVAL;
+        goto err;
+    }
+
+    txom = os_mbuf_get_pkthdr(&ble_hs_mbuf_pool, 0);
+    if (txom == NULL) {
+        rc = ENOMEM;
+        goto err;
+    }
+
+    buf = os_mbuf_extend(txom, BLE_HS_ATT_FIND_INFO_REQ_SZ);
+    if (buf == NULL) {
+        rc = ENOMEM;
+        goto err;
+    }
+
+    rc = ble_hs_att_find_info_req_write(buf, BLE_HS_ATT_FIND_INFO_REQ_SZ, req);
+    if (rc != 0) {
+        goto err;
+    }
+
+    rc = ble_l2cap_tx(chan, txom);
+    txom = NULL;
+    if (rc != 0) {
+        goto err;
+    }
+
+    rc = 0;
+
+err:
+    os_mbuf_free_chain(txom);
+    return rc;
+}
+
+int
+ble_hs_att_clt_rx_find_info(struct ble_hs_conn *conn,
+                            struct ble_l2cap_chan *chan,
+                            struct os_mbuf *om)
+{
+    struct ble_hs_att_find_info_rsp rsp;
+    uint16_t handle_id;
+    uint16_t uuid16;
+    uint8_t uuid128[16];
+    int off;
+    int rc;
+
+    /* XXX: Pull up om */
+
+    rc = ble_hs_att_find_info_rsp_parse(om->om_data, om->om_len, &rsp);
+    if (rc != 0) {
+        return rc;
+    }
+
+    off = BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ;
+    while (off < OS_MBUF_PKTHDR(om)->omp_len) {
+        rc = os_mbuf_copydata(om, off, 2, &handle_id);
+        if (rc != 0) {
+            return EINVAL;
+        }
+        off += 2;
+        handle_id = le16toh(&handle_id);
+
+        switch (rsp.bhafp_format) {
+        case BLE_HS_ATT_FIND_INFO_RSP_FORMAT_16BIT:
+            rc = os_mbuf_copydata(om, off, 2, &uuid16);
+            if (rc != 0) {
+                return EINVAL;
+            }
+            off += 2;
+            uuid16 = le16toh(&uuid16);
+
+            rc = ble_hs_uuid_from_16bit(uuid16, uuid128);
+            if (rc != 0) {
+                return EINVAL;
+            }
+            break;
+
+        case BLE_HS_ATT_FIND_INFO_RSP_FORMAT_128BIT:
+            rc = os_mbuf_copydata(om, off, 16, &uuid128);
+            if (rc != 0) {
+                return EINVAL;
+            }
+            off += 16;
+            break;
+
+        default:
+            return EINVAL;
+        }
+
+        rc = ble_hs_att_clt_entry_insert(conn, handle_id, uuid128);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+int
+ble_hs_att_clt_init(void)
+{
+    int rc;
+
+    free(ble_hs_att_clt_entry_mem);
+    ble_hs_att_clt_entry_mem = malloc(
+        OS_MEMPOOL_BYTES(BLE_HS_ATT_CLT_NUM_ENTRIES,
+                         sizeof (struct ble_hs_att_clt_entry)));
+    if (ble_hs_att_clt_entry_mem == NULL) {
+        rc = ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&ble_hs_att_clt_entry_pool,
+                         BLE_HS_ATT_CLT_NUM_ENTRIES,
+                         sizeof (struct ble_hs_att_clt_entry),
+                         ble_hs_att_clt_entry_mem,
+                         "ble_hs_att_clt_entry_pool");
+    if (rc != 0) {
+        goto err;
+    }
+
+    return 0;
+
+err:
+    free(ble_hs_att_clt_entry_mem);
+    ble_hs_att_clt_entry_mem = NULL;
+
+    return rc;
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att_cmd.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att_cmd.c b/net/nimble/host/src/ble_hs_att_cmd.c
index 0a41d3e..11200ee 100644
--- a/net/nimble/host/src/ble_hs_att_cmd.c
+++ b/net/nimble/host/src/ble_hs_att_cmd.c
@@ -32,15 +32,14 @@ ble_hs_att_error_rsp_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    rsp->bhaep_op = u8ptr[0];
+    if (u8ptr[0] != BLE_HS_ATT_OP_ERROR_RSP) {
+        return EINVAL;
+    }
+
     rsp->bhaep_req_op = u8ptr[1];
     rsp->bhaep_handle = le16toh(u8ptr + 2);
     rsp->bhaep_error_code = u8ptr[4];
 
-    if (rsp->bhaep_op != BLE_HS_ATT_OP_ERROR_RSP) {
-        return EINVAL;
-    }
-
     return 0;
 }
 
@@ -56,7 +55,7 @@ ble_hs_att_error_rsp_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = rsp->bhaep_op;
+    u8ptr[0] = BLE_HS_ATT_OP_ERROR_RSP;
     u8ptr[1] = rsp->bhaep_req_op;
     htole16(u8ptr + 2, rsp->bhaep_handle);
     u8ptr[4] = rsp->bhaep_error_code;
@@ -76,20 +75,37 @@ ble_hs_att_mtu_cmd_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    cmd->bhamc_op = u8ptr[0];
+    if (u8ptr[0] != BLE_HS_ATT_OP_MTU_REQ &&
+        u8ptr[0] != BLE_HS_ATT_OP_MTU_RSP) {
+
+        return EINVAL;
+    }
+
     cmd->bhamc_mtu = le16toh(u8ptr + 1);
 
-    if (cmd->bhamc_op != BLE_HS_ATT_OP_MTU_REQ &&
-        cmd->bhamc_op != BLE_HS_ATT_OP_MTU_RSP) {
+    return 0;
+}
 
-        return EINVAL;
+int
+ble_hs_att_mtu_req_write(void *payload, int len,
+                         struct ble_hs_att_mtu_cmd *cmd)
+{
+    uint8_t *u8ptr;
+
+    if (len < BLE_HS_ATT_MTU_CMD_SZ) {
+        return EMSGSIZE;
     }
 
+    u8ptr = payload;
+
+    u8ptr[0] = BLE_HS_ATT_OP_MTU_REQ;
+    htole16(u8ptr + 1, cmd->bhamc_mtu);
+
     return 0;
 }
 
 int
-ble_hs_att_mtu_cmd_write(void *payload, int len,
+ble_hs_att_mtu_rsp_write(void *payload, int len,
                          struct ble_hs_att_mtu_cmd *cmd)
 {
     uint8_t *u8ptr;
@@ -100,7 +116,7 @@ ble_hs_att_mtu_cmd_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = cmd->bhamc_op;
+    u8ptr[0] = BLE_HS_ATT_OP_MTU_RSP;
     htole16(u8ptr + 1, cmd->bhamc_mtu);
 
     return 0;
@@ -118,14 +134,13 @@ ble_hs_att_find_info_req_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    req->bhafq_op = u8ptr[0];
-    req->bhafq_start_handle = le16toh(u8ptr + 1);
-    req->bhafq_end_handle = le16toh(u8ptr + 3);
-
-    if (req->bhafq_op != BLE_HS_ATT_OP_FIND_INFO_REQ) {
+    if (u8ptr[0] != BLE_HS_ATT_OP_FIND_INFO_REQ) {
         return EINVAL;
     }
 
+    req->bhafq_start_handle = le16toh(u8ptr + 1);
+    req->bhafq_end_handle = le16toh(u8ptr + 3);
+
     return 0;
 }
 
@@ -141,7 +156,7 @@ ble_hs_att_find_info_req_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = req->bhafq_op;
+    u8ptr[0] = BLE_HS_ATT_OP_FIND_INFO_REQ;
     htole16(u8ptr + 1, req->bhafq_start_handle);
     htole16(u8ptr + 3, req->bhafq_end_handle);
 
@@ -160,13 +175,12 @@ ble_hs_att_find_info_rsp_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    rsp->bhafp_op = u8ptr[0];
-    rsp->bhafp_format = u8ptr[1];
-
-    if (rsp->bhafp_op != BLE_HS_ATT_OP_FIND_INFO_RSP) {
+    if (u8ptr[0] != BLE_HS_ATT_OP_FIND_INFO_RSP) {
         return EINVAL;
     }
 
+    rsp->bhafp_format = u8ptr[1];
+
     return 0;
 }
 
@@ -182,7 +196,7 @@ ble_hs_att_find_info_rsp_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = rsp->bhafp_op;
+    u8ptr[0] = BLE_HS_ATT_OP_FIND_INFO_RSP;
     u8ptr[1] = rsp->bhafp_format;
 
     return 0;
@@ -200,15 +214,14 @@ ble_hs_att_find_type_value_req_parse(
 
     u8ptr = payload;
 
-    req->bhavq_op = u8ptr[0];
+    if (u8ptr[0] != BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ) {
+        return EINVAL;
+    }
+
     req->bhavq_start_handle = le16toh(u8ptr + 1);
     req->bhavq_end_handle = le16toh(u8ptr + 3);
     req->bhavq_attr_type = le16toh(u8ptr + 5);
 
-    if (req->bhavq_op != BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ) {
-        return EINVAL;
-    }
-
     return 0;
 }
 
@@ -224,7 +237,7 @@ ble_hs_att_find_type_value_req_write(
 
     u8ptr = payload;
 
-    u8ptr[0] = req->bhavq_op;
+    u8ptr[0] = BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ;
     htole16(u8ptr + 1, req->bhavq_start_handle);
     htole16(u8ptr + 3, req->bhavq_end_handle);
     htole16(u8ptr + 5, req->bhavq_attr_type);
@@ -244,14 +257,13 @@ ble_hs_att_read_type_req_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    req->bhatq_op = u8ptr[0];
-    req->bhatq_start_handle = le16toh(u8ptr + 1);
-    req->bhatq_end_handle = le16toh(u8ptr + 3);
-
-    if (req->bhatq_op != BLE_HS_ATT_OP_READ_TYPE_REQ) {
+    if (u8ptr[0] != BLE_HS_ATT_OP_READ_TYPE_REQ) {
         return EINVAL;
     }
 
+    req->bhatq_start_handle = le16toh(u8ptr + 1);
+    req->bhatq_end_handle = le16toh(u8ptr + 3);
+
     return 0;
 }
 
@@ -267,7 +279,7 @@ ble_hs_att_read_type_req_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = req->bhatq_op;
+    u8ptr[0] = BLE_HS_ATT_OP_READ_TYPE_REQ;
     htole16(u8ptr + 1, req->bhatq_start_handle);
     htole16(u8ptr + 3, req->bhatq_end_handle);
 
@@ -286,7 +298,6 @@ ble_hs_att_read_type_rsp_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    rsp->bhatp_op = u8ptr[0];
     rsp->bhatp_len = u8ptr[1];
 
     return 0;
@@ -304,7 +315,7 @@ ble_hs_att_read_type_rsp_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = rsp->bhatp_op;
+    u8ptr[0] = BLE_HS_ATT_OP_READ_TYPE_RSP;
     u8ptr[1] = rsp->bhatp_len;
 
     return 0;
@@ -322,13 +333,12 @@ ble_hs_att_read_req_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    req->bharq_op = u8ptr[0];
-    req->bharq_handle = le16toh(u8ptr + 1);
-
-    if (req->bharq_op != BLE_HS_ATT_OP_READ_REQ) {
+    if (u8ptr[0] != BLE_HS_ATT_OP_READ_REQ) {
         return EINVAL;
     }
 
+    req->bharq_handle = le16toh(u8ptr + 1);
+
     return 0;
 }
 
@@ -344,7 +354,7 @@ ble_hs_att_read_req_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = req->bharq_op;
+    u8ptr[0] = BLE_HS_ATT_OP_READ_REQ;
     htole16(u8ptr + 1, req->bharq_handle);
 
     return 0;
@@ -362,13 +372,12 @@ ble_hs_att_write_req_parse(void *payload, int len,
 
     u8ptr = payload;
 
-    req->bhawq_op = u8ptr[0];
-    req->bhawq_handle = le16toh(u8ptr + 1);
-
-    if (req->bhawq_op != BLE_HS_ATT_OP_WRITE_REQ) {
+    if (u8ptr[0] != BLE_HS_ATT_OP_WRITE_REQ) {
         return EINVAL;
     }
 
+    req->bhawq_handle = le16toh(u8ptr + 1);
+
     return 0;
 }
 
@@ -384,7 +393,7 @@ ble_hs_att_write_req_write(void *payload, int len,
 
     u8ptr = payload;
 
-    u8ptr[0] = req->bhawq_op;
+    u8ptr[0] = BLE_HS_ATT_OP_WRITE_REQ;
     htole16(u8ptr + 1, req->bhawq_handle);
 
     return 0;

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att_cmd.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att_cmd.h b/net/nimble/host/src/ble_hs_att_cmd.h
index b27a5fb..3362187 100644
--- a/net/nimble/host/src/ble_hs_att_cmd.h
+++ b/net/nimble/host/src/ble_hs_att_cmd.h
@@ -41,7 +41,6 @@ struct ble_l2cap_chan;
  */
 #define BLE_HS_ATT_ERROR_RSP_SZ             5
 struct ble_hs_att_error_rsp {
-    uint8_t bhaep_op;
     uint8_t bhaep_req_op;
     uint16_t bhaep_handle;
     uint8_t bhaep_error_code;
@@ -55,7 +54,6 @@ struct ble_hs_att_error_rsp {
  */
 #define BLE_HS_ATT_MTU_CMD_SZ               3
 struct ble_hs_att_mtu_cmd {
-    uint8_t bhamc_op;
     uint16_t bhamc_mtu;
 };
 
@@ -68,7 +66,6 @@ struct ble_hs_att_mtu_cmd {
  */
 #define BLE_HS_ATT_FIND_INFO_REQ_SZ         5
 struct ble_hs_att_find_info_req {
-    uint8_t bhafq_op;
     uint16_t bhafq_start_handle;
     uint16_t bhafq_end_handle;
 };
@@ -82,7 +79,6 @@ struct ble_hs_att_find_info_req {
  */
 #define BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ     2
 struct ble_hs_att_find_info_rsp {
-    uint8_t bhafp_op;
     uint8_t bhafp_format;
     /* Followed by information data. */
 };
@@ -101,7 +97,6 @@ struct ble_hs_att_find_info_rsp {
  */
 #define BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ   7
 struct ble_hs_att_find_type_value_req {
-    uint8_t bhavq_op;
     uint16_t bhavq_start_handle;
     uint16_t bhavq_end_handle;
     uint16_t bhavq_attr_type;
@@ -127,7 +122,6 @@ struct ble_hs_att_find_type_value_req {
 #define BLE_HS_ATT_READ_TYPE_REQ_SZ_16      7
 #define BLE_HS_ATT_READ_TYPE_REQ_SZ_128     21
 struct ble_hs_att_read_type_req {
-    uint8_t bhatq_op;
     uint16_t bhatq_start_handle;
     uint16_t bhatq_end_handle;
 };
@@ -141,7 +135,6 @@ struct ble_hs_att_read_type_req {
  */
 #define BLE_HS_ATT_READ_TYPE_RSP_MIN_SZ     2
 struct ble_hs_att_read_type_rsp {
-    uint8_t bhatp_op;
     uint8_t bhatp_len;
 };
 
@@ -153,7 +146,6 @@ struct ble_hs_att_read_type_rsp {
  */
 #define BLE_HS_ATT_READ_REQ_SZ              3
 struct ble_hs_att_read_req {
-    uint8_t bharq_op;
     uint16_t bharq_handle;
 };
 
@@ -174,7 +166,6 @@ struct ble_hs_att_read_req {
  */
 #define BLE_HS_ATT_WRITE_REQ_MIN_SZ         3
 struct ble_hs_att_write_req {
-    uint8_t bhawq_op;
     uint16_t bhawq_handle;
 };
 
@@ -186,7 +177,9 @@ int ble_hs_att_error_rsp_write(void *payload, int len,
                                struct ble_hs_att_error_rsp *rsp);
 int ble_hs_att_mtu_cmd_parse(void *payload, int len,
                              struct ble_hs_att_mtu_cmd *cmd);
-int ble_hs_att_mtu_cmd_write(void *payload, int len,
+int ble_hs_att_mtu_req_write(void *payload, int len,
+                             struct ble_hs_att_mtu_cmd *cmd);
+int ble_hs_att_mtu_rsp_write(void *payload, int len,
                              struct ble_hs_att_mtu_cmd *cmd);
 int ble_hs_att_find_info_req_parse(void *payload, int len,
                                    struct ble_hs_att_find_info_req *req);

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_att_svr.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_att_svr.c b/net/nimble/host/src/ble_hs_att_svr.c
index b915110..a03b8cd 100644
--- a/net/nimble/host/src/ble_hs_att_svr.c
+++ b/net/nimble/host/src/ble_hs_att_svr.c
@@ -26,52 +26,12 @@
 #include "ble_hs_att_cmd.h"
 #include "ble_hs_att.h"
 
-typedef int ble_hs_att_svr_rx_fn(struct ble_hs_conn *conn,
-                                 struct ble_l2cap_chan *chan,
-                                 struct os_mbuf *om);
-struct ble_hs_att_svr_rx_dispatch_entry {
-    uint8_t bde_op;
-    ble_hs_att_svr_rx_fn *bde_fn;
-};
-
-/** Dispatch table for incoming ATT requests.  Sorted by op code. */
-static int ble_hs_att_svr_rx_mtu(struct ble_hs_conn *conn,
-                                 struct ble_l2cap_chan *chan,
-                                 struct os_mbuf *om);
-static int ble_hs_att_svr_rx_find_info(struct ble_hs_conn *conn,
-                                       struct ble_l2cap_chan *chan,
-                                       struct os_mbuf *om);
-static int ble_hs_att_svr_rx_find_type_value(struct ble_hs_conn *conn,
-                                             struct ble_l2cap_chan *chan,
-                                             struct os_mbuf *om);
-static int ble_hs_att_svr_rx_read_type(struct ble_hs_conn *conn,
-                                       struct ble_l2cap_chan *chan,
-                                       struct os_mbuf *om);
-static int ble_hs_att_svr_rx_read(struct ble_hs_conn *conn,
-                                  struct ble_l2cap_chan *chan,
-                                  struct os_mbuf *om);
-static int ble_hs_att_svr_rx_write(struct ble_hs_conn *conn,
-                                   struct ble_l2cap_chan *chan,
-                                   struct os_mbuf *om);
-
-static struct ble_hs_att_svr_rx_dispatch_entry ble_hs_att_svr_rx_dispatch[] = {
-    { BLE_HS_ATT_OP_MTU_REQ,              ble_hs_att_svr_rx_mtu },
-    { BLE_HS_ATT_OP_FIND_INFO_REQ,        ble_hs_att_svr_rx_find_info },
-    { BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ,  ble_hs_att_svr_rx_find_type_value },
-    { BLE_HS_ATT_OP_READ_TYPE_REQ,        ble_hs_att_svr_rx_read_type },
-    { BLE_HS_ATT_OP_READ_REQ,             ble_hs_att_svr_rx_read },
-    { BLE_HS_ATT_OP_WRITE_REQ,            ble_hs_att_svr_rx_write },
-};
-
-#define BLE_HS_ATT_RX_DISPATCH_SZ \
-    (sizeof ble_hs_att_svr_rx_dispatch / sizeof ble_hs_att_svr_rx_dispatch[0])
-
 static STAILQ_HEAD(, ble_hs_att_svr_entry) ble_hs_att_svr_list;
 static uint16_t ble_hs_att_svr_id;
 
 static struct os_mutex ble_hs_att_svr_list_mutex;
 
-#define BLE_HS_ATT_NUM_ENTRIES          1024
+#define BLE_HS_ATT_SVR_NUM_ENTRIES          32
 static void *ble_hs_att_svr_entry_mem;
 static struct os_mempool ble_hs_att_svr_entry_pool;
 
@@ -304,26 +264,6 @@ ble_hs_att_svr_find_by_uuid(uint8_t *uuid,
     }
 }
 
-static struct ble_hs_att_svr_rx_dispatch_entry *
-ble_hs_att_svr_rx_dispatch_entry_find(uint8_t op)
-{
-    struct ble_hs_att_svr_rx_dispatch_entry *entry;
-    int i;
-
-    for (i = 0; i < BLE_HS_ATT_RX_DISPATCH_SZ; i++) {
-        entry = ble_hs_att_svr_rx_dispatch + i;
-        if (entry->bde_op == op) {
-            return entry;
-        }
-
-        if (entry->bde_op > op) {
-            break;
-        }
-    }
-
-    return NULL;
-}
-
 static int
 ble_hs_att_svr_tx_error_rsp(struct ble_l2cap_chan *chan, uint8_t req_op,
                             uint16_t handle, uint8_t error_code)
@@ -345,7 +285,6 @@ ble_hs_att_svr_tx_error_rsp(struct ble_l2cap_chan *chan, uint8_t req_op,
         goto err;
     }
 
-    rsp.bhaep_op = BLE_HS_ATT_OP_ERROR_RSP;
     rsp.bhaep_req_op = req_op;
     rsp.bhaep_handle = handle;
     rsp.bhaep_error_code = error_code;
@@ -368,7 +307,7 @@ err:
 }
 
 static int
-ble_hs_att_svr_tx_mtu_cmd(struct ble_l2cap_chan *chan, uint8_t op,
+ble_hs_att_svr_tx_mtu_rsp(struct ble_l2cap_chan *chan, uint8_t op,
                           uint16_t mtu)
 {
     struct ble_hs_att_mtu_cmd cmd;
@@ -392,10 +331,9 @@ ble_hs_att_svr_tx_mtu_cmd(struct ble_l2cap_chan *chan, uint8_t op,
         goto err;
     }
 
-    cmd.bhamc_op = op;
     cmd.bhamc_mtu = mtu;
 
-    rc = ble_hs_att_mtu_cmd_write(dst, BLE_HS_ATT_MTU_CMD_SZ, &cmd);
+    rc = ble_hs_att_mtu_rsp_write(dst, BLE_HS_ATT_MTU_CMD_SZ, &cmd);
     assert(rc == 0);
 
     rc = ble_l2cap_tx(chan, txom);
@@ -413,7 +351,7 @@ err:
     return rc;
 }
 
-static int
+int
 ble_hs_att_svr_rx_mtu(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
                       struct os_mbuf *om)
 {
@@ -421,12 +359,6 @@ ble_hs_att_svr_rx_mtu(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
     uint8_t buf[BLE_HS_ATT_MTU_CMD_SZ];
     int rc;
 
-    /* We should only receive this command as a server. */
-    if (conn->bhc_flags & BLE_HS_CONN_F_CLIENT) {
-        /* XXX: Unspecified what to do in this case. */
-        return EINVAL;
-    }
-
     rc = os_mbuf_copydata(om, 0, sizeof buf, buf);
     assert(rc == 0);
 
@@ -439,7 +371,7 @@ ble_hs_att_svr_rx_mtu(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
 
     chan->blc_peer_mtu = cmd.bhamc_mtu;
 
-    rc = ble_hs_att_svr_tx_mtu_cmd(chan, BLE_HS_ATT_OP_MTU_RSP,
+    rc = ble_hs_att_svr_tx_mtu_rsp(chan, BLE_HS_ATT_OP_MTU_RSP,
                                    chan->blc_my_mtu);
     if (rc != 0) {
         return rc;
@@ -567,7 +499,7 @@ done:
     }
 }
 
-static int
+int
 ble_hs_att_svr_rx_find_info(struct ble_hs_conn *conn,
                             struct ble_l2cap_chan *chan,
                             struct os_mbuf *rxom)
@@ -611,7 +543,6 @@ ble_hs_att_svr_rx_find_info(struct ble_hs_conn *conn,
     /* Write the response base at the start of the buffer.  The format field is
      * unknown at this point; it will be filled in later.
      */
-    rsp.bhafp_op = BLE_HS_ATT_OP_FIND_INFO_RSP;
     rc = ble_hs_att_find_info_rsp_write(buf, BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ,
                                         &rsp);
     assert(rc == 0);
@@ -860,28 +791,22 @@ done:
     }
 }
 
-static int
+int
 ble_hs_att_svr_rx_find_type_value(struct ble_hs_conn *conn,
                                   struct ble_l2cap_chan *chan,
                                   struct os_mbuf *rxom)
 {
     struct ble_hs_att_find_type_value_req req;
     struct os_mbuf *txom;
-    uint8_t buf[max(BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ,
-                    BLE_HS_ATT_FIND_TYPE_VALUE_RSP_MIN_SZ)];
+    uint8_t *buf;
     int rc;
 
     txom = NULL;
 
-    rc = os_mbuf_copydata(rxom, 0, BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ, buf);
-    if (rc != 0) {
-        req.bhavq_start_handle = 0;
-        rc = BLE_HS_ATT_ERR_INVALID_PDU;
-        goto err;
-    }
+    /* XXX: Pull up rx_om. */
 
-    rc = ble_hs_att_find_type_value_req_parse(
-        buf, BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ, &req);
+    rc = ble_hs_att_find_type_value_req_parse(rxom->om_data, rxom->om_len,
+                                              &req);
     assert(rc == 0);
 
     /* Tx error response if start handle is greater than end handle or is equal
@@ -901,13 +826,12 @@ ble_hs_att_svr_rx_find_type_value(struct ble_hs_conn *conn,
     }
 
     /* Write the response base at the start of the buffer. */
-    buf[0] = BLE_HS_ATT_OP_FIND_TYPE_VALUE_RSP;
-    rc = os_mbuf_append(txom, buf,
-                        BLE_HS_ATT_FIND_TYPE_VALUE_RSP_MIN_SZ);
-    if (rc != 0) {
+    buf = os_mbuf_extend(txom, BLE_HS_ATT_FIND_TYPE_VALUE_RSP_MIN_SZ);
+    if (buf == NULL) {
         rc = BLE_HS_ATT_ERR_INSUFFICIENT_RES;
         goto err;
     }
+    buf[0] = BLE_HS_ATT_OP_FIND_TYPE_VALUE_RSP;
 
     /* Write the variable length Information Data field. */
     rc = ble_hs_att_svr_fill_type_value(&req, rxom, txom,
@@ -1024,13 +948,13 @@ ble_hs_att_svr_tx_read_type_rsp(struct ble_hs_conn *conn,
     }
 
     /* Fill the response base. */
-    rsp.bhatp_op = BLE_HS_ATT_OP_READ_TYPE_RSP;
     rsp.bhatp_len = prev_attr_len;
     rc = ble_hs_att_read_type_rsp_write(txom->om_data, txom->om_len, &rsp);
     assert(rc == 0);
 
     /* Transmit the response. */
     rc = ble_l2cap_tx(chan, txom);
+    txom = NULL;
     if (rc != 0) {
         rc = BLE_HS_ATT_ERR_UNLIKELY;
         goto err;
@@ -1045,7 +969,7 @@ err:
     return rc;
 }
 
-static int
+int
 ble_hs_att_svr_rx_read_type(struct ble_hs_conn *conn,
                             struct ble_l2cap_chan *chan, struct os_mbuf *rx_om)
 {
@@ -1140,7 +1064,7 @@ err:
     return rc;
 }
 
-static int
+int
 ble_hs_att_svr_rx_read(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
                        struct os_mbuf *om)
 {
@@ -1227,7 +1151,7 @@ err:
     return rc;
 }
 
-static int
+int
 ble_hs_att_svr_rx_write(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
                         struct os_mbuf *om)
 {
@@ -1273,37 +1197,11 @@ ble_hs_att_svr_rx_write(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
 
 send_err:
     ble_hs_att_svr_tx_error_rsp(chan, BLE_HS_ATT_OP_WRITE_REQ,
-                            req.bhawq_handle, rc);
+                                req.bhawq_handle, rc);
     return rc;
 }
 
 int
-ble_hs_att_svr_rx(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan,
-                  struct os_mbuf *om)
-{
-    struct ble_hs_att_svr_rx_dispatch_entry *entry;
-    uint8_t op;
-    int rc;
-
-    rc = os_mbuf_copydata(om, 0, 1, &op);
-    if (rc != 0) {
-        return EMSGSIZE;
-    }
-
-    entry = ble_hs_att_svr_rx_dispatch_entry_find(op);
-    if (entry == NULL) {
-        return EINVAL;
-    }
-
-    rc = entry->bde_fn(conn, chan, om);
-    if (rc != 0) {
-        return rc;
-    }
-
-    return 0;
-}
-
-int
 ble_hs_att_svr_init(void)
 {
     int rc;
@@ -1317,16 +1215,18 @@ ble_hs_att_svr_init(void)
 
     free(ble_hs_att_svr_entry_mem);
     ble_hs_att_svr_entry_mem = malloc(
-        OS_MEMPOOL_BYTES(BLE_HS_ATT_NUM_ENTRIES,
+        OS_MEMPOOL_BYTES(BLE_HS_ATT_SVR_NUM_ENTRIES,
                          sizeof (struct ble_hs_att_svr_entry)));
     if (ble_hs_att_svr_entry_mem == NULL) {
         rc = ENOMEM;
         goto err;
     }
 
-    rc = os_mempool_init(&ble_hs_att_svr_entry_pool, BLE_HS_ATT_NUM_ENTRIES,
+    rc = os_mempool_init(&ble_hs_att_svr_entry_pool,
+                         BLE_HS_ATT_SVR_NUM_ENTRIES,
                          sizeof (struct ble_hs_att_svr_entry),
-                         ble_hs_att_svr_entry_mem, "ble_hs_att_svr_entry_pool");
+                         ble_hs_att_svr_entry_mem,
+                         "ble_hs_att_svr_entry_pool");
     if (rc != 0) {
         goto err;
     }

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_conn.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_conn.c b/net/nimble/host/src/ble_hs_conn.c
index 49a4f75..64b44a7 100644
--- a/net/nimble/host/src/ble_hs_conn.c
+++ b/net/nimble/host/src/ble_hs_conn.c
@@ -57,6 +57,8 @@ ble_hs_conn_alloc(void)
     }
     SLIST_INSERT_HEAD(&conn->bhc_channels, chan, blc_next);
 
+    SLIST_INIT(&conn->bhc_att_clt_list);
+
     return conn;
 
 err:
@@ -83,6 +85,8 @@ ble_hs_conn_free(struct ble_hs_conn *conn)
 
     /* XXX: Free contents of rx and tx queues. */
 
+    ble_hs_att_clt_entry_list_free(&conn->bhc_att_clt_list);
+
     rc = os_memblock_put(&ble_hs_conn_pool, conn);
     assert(rc == 0);
 }
@@ -138,14 +142,6 @@ ble_hs_conn_chan_find(struct ble_hs_conn *conn, uint16_t cid)
     return NULL;
 }
 
-int
-ble_hs_conn_tx(struct ble_hs_conn *conn, struct os_mbuf *om)
-{
-    //struct ble_hs_conn_pkt *pkt;
-
-    return 0;
-}
-
 static void
 ble_hs_conn_free_mem(void)
 {

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_conn.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_conn.h b/net/nimble/host/src/ble_hs_conn.h
index ceb10c3..776dbe8 100644
--- a/net/nimble/host/src/ble_hs_conn.h
+++ b/net/nimble/host/src/ble_hs_conn.h
@@ -19,21 +19,20 @@
 
 #include "os/queue.h"
 #include "ble_l2cap.h"
+#include "ble_hs_att.h"
 struct hci_le_conn_complete;
 struct hci_create_conn;
 struct ble_l2cap_chan;
 
-typedef uint8_t ble_hs_conn_flags;
-#define BLE_HS_CONN_F_CLIENT        0x01
-
 struct ble_hs_conn {
     SLIST_ENTRY(ble_hs_conn) bhc_next;
     uint16_t bhc_handle;
-    uint8_t bhc_addr[BLE_DEV_ADDR_LEN];
+    uint8_t bhc_addr[6];
 
     struct ble_l2cap_chan_list bhc_channels;
 
-    ble_hs_conn_flags bhc_flags;
+    /** Mapping of peer's ATT attributes to handle IDs. */
+    struct ble_hs_att_clt_entry_list bhc_att_clt_list;
 };
 
 struct ble_hs_conn *ble_hs_conn_alloc(void);

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_work.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_work.c b/net/nimble/host/src/ble_hs_work.c
index ed54027..545056b 100644
--- a/net/nimble/host/src/ble_hs_work.c
+++ b/net/nimble/host/src/ble_hs_work.c
@@ -18,13 +18,14 @@
 #include <assert.h>
 #include <errno.h>
 #include "os/os.h"
+#include "host/host_hci.h"
 #include "host/ble_hs.h"
 #include "ble_gap_conn.h"
 #include "ble_hs_work.h"
 
 #define BLE_HS_WORK_NUM_ENTRIES     16
 
-uint8_t ble_hs_work_busy;
+struct ble_hs_work_entry *ble_hs_work_cur_entry;
 
 static void *ble_hs_work_entry_mem;
 static struct os_mempool ble_hs_work_entry_pool;
@@ -52,7 +53,7 @@ ble_hs_work_process_next(void)
     struct ble_hs_work_entry *entry;
     int rc;
 
-    assert(!ble_hs_work_busy);
+    assert(ble_hs_work_cur_entry == NULL);
 
     entry = STAILQ_FIRST(&ble_hs_work_queue);
     if (entry == NULL) {
@@ -61,6 +62,8 @@ ble_hs_work_process_next(void)
 
     STAILQ_REMOVE_HEAD(&ble_hs_work_queue, bwe_next);
 
+    ble_hs_work_cur_entry = entry;
+
     switch (entry->bwe_type) {
     case BLE_HS_WORK_TYPE_DIRECT_CONNECT:
         rc = ble_gap_conn_direct_connect(
@@ -74,24 +77,44 @@ ble_hs_work_process_next(void)
             entry->bwe_direct_advertise.bwda_peer_addr);
         break;
 
+    case BLE_HS_WORK_TYPE_READ_HCI_BUF_SIZE:
+        rc = host_hci_read_buf_size();
+        break;
+
     default:
         rc = -1;
         assert(0);
         break;
     }
 
-    if (rc == 0) {
-        ble_hs_work_busy = 1;
+    if (rc != 0) {
+        os_memblock_put(&ble_hs_work_entry_pool, entry);
+        ble_hs_work_cur_entry = NULL;
     }
-
-    os_memblock_put(&ble_hs_work_entry_pool, entry);
 }
 
 void
 ble_hs_work_done(void)
 {
-    assert(ble_hs_work_busy || !g_os_started);
-    ble_hs_work_busy = 0;
+    assert(ble_hs_work_cur_entry != NULL || !g_os_started);
+
+    if (ble_hs_work_cur_entry != NULL) {
+        os_memblock_put(&ble_hs_work_entry_pool, ble_hs_work_cur_entry);
+        ble_hs_work_cur_entry = NULL;
+    }
+}
+
+int
+ble_hs_work_done_if(int work_type)
+{
+    if (ble_hs_work_cur_entry != NULL &&
+        ble_hs_work_cur_entry->bwe_type == work_type) {
+
+        ble_hs_work_done();
+        return 0;
+    } else {
+        return 1;
+    }
 }
 
 int
@@ -116,5 +139,7 @@ ble_hs_work_init(void)
 
     STAILQ_INIT(&ble_hs_work_queue);
 
+    ble_hs_work_cur_entry = NULL;
+
     return 0;
 }

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/ble_hs_work.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_work.h b/net/nimble/host/src/ble_hs_work.h
index d5bdb05..b187c98 100644
--- a/net/nimble/host/src/ble_hs_work.h
+++ b/net/nimble/host/src/ble_hs_work.h
@@ -22,7 +22,8 @@
 
 #define BLE_HS_WORK_TYPE_DIRECT_CONNECT     0
 #define BLE_HS_WORK_TYPE_DIRECT_ADVERTISE   1
-#define BLE_HS_WORK_TYPE_MAX                2
+#define BLE_HS_WORK_TYPE_READ_HCI_BUF_SIZE  2
+#define BLE_HS_WORK_TYPE_MAX                3
 
 struct ble_hs_work_direct_connect {
     uint8_t bwdc_peer_addr[8];
@@ -48,8 +49,9 @@ struct ble_hs_work_entry *ble_hs_work_entry_alloc(void);
 void ble_hs_work_enqueue(struct ble_hs_work_entry *entry);
 void ble_hs_work_process_next(void);
 void ble_hs_work_done(void);
+int ble_hs_work_done_if(int work_type);
 int ble_hs_work_init(void);
 
-extern uint8_t ble_hs_work_busy;
+extern struct ble_hs_work_entry *ble_hs_work_cur_entry;
 
 #endif

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/host_hci.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/host_hci.c b/net/nimble/host/src/host_hci.c
index 2b33b02..b49c22e 100644
--- a/net/nimble/host/src/host_hci.c
+++ b/net/nimble/host/src/host_hci.c
@@ -26,6 +26,7 @@
 #include "host/ble_hs.h"
 #include "host_dbg.h"
 #include "ble_hs_conn.h"
+#include "ble_hs_work.h"
 #include "ble_l2cap.h"
 #include "ble_hs_ack.h"
 #include "ble_gap_conn.h"
@@ -173,7 +174,7 @@ host_hci_rx_cmd_complete(uint8_t event_code, uint8_t *data, int len)
 
     num_pkts = data[2];
     opcode = le16toh(data + 3);
-    params = data + 4;
+    params = data + 5;
 
     /* XXX: Process num_pkts field. */
     (void)num_pkts;
@@ -329,6 +330,8 @@ host_hci_rx_read_buf_size_ack(struct ble_hs_ack *ack, void *arg)
 
     host_hci_buffer_sz = pktlen;
     host_hci_max_pkts = max_pkts;
+
+    ble_hs_work_done_if(BLE_HS_WORK_TYPE_READ_HCI_BUF_SIZE);
 }
 
 int

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/test/ble_gap_test.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/test/ble_gap_test.c b/net/nimble/host/src/test/ble_gap_test.c
index c66d77f..863bbd3 100644
--- a/net/nimble/host/src/test/ble_gap_test.c
+++ b/net/nimble/host/src/test/ble_gap_test.c
@@ -75,6 +75,11 @@ ble_gap_test_task_handler(void *arg)
     int cb_called;
     int rc;
 
+    /* Receive the HCI buffer size acknowledgement.  We sent the corresponding
+     * request when the host task was started.
+     */
+    ble_hs_test_util_rx_hci_buf_size_ack(0xffff);
+
     /* Set the connect callback so we can verify that it gets called with the
      * proper arguments.
      */
@@ -84,18 +89,18 @@ ble_gap_test_task_handler(void *arg)
     /* Make sure there are no created connections and no connections in
      * progress.
      */
-    TEST_ASSERT(!ble_hs_work_busy);
+    TEST_ASSERT(ble_hs_work_cur_entry == NULL);
     TEST_ASSERT(ble_hs_conn_first() == NULL);
 
     /* Initiate a direct connection. */
     ble_gap_direct_connection_establishment(0, addr);
-    TEST_ASSERT(ble_hs_work_busy);
+    TEST_ASSERT(ble_hs_work_cur_entry != NULL);
     TEST_ASSERT(ble_hs_conn_first() == NULL);
     TEST_ASSERT(!cb_called);
 
     /* Receive an ack for the HCI create-connection command. */
     ble_gap_test_misc_rx_ack(BLE_HCI_OCF_LE_CREATE_CONN, 0);
-    TEST_ASSERT(!ble_hs_work_busy);
+    TEST_ASSERT(ble_hs_work_cur_entry == NULL);
     TEST_ASSERT(ble_hs_conn_first() == NULL);
     TEST_ASSERT(!cb_called);
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/test/ble_hs_att_clt_test.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/test/ble_hs_att_clt_test.c b/net/nimble/host/src/test/ble_hs_att_clt_test.c
new file mode 100644
index 0000000..00c6239
--- /dev/null
+++ b/net/nimble/host/src/test/ble_hs_att_clt_test.c
@@ -0,0 +1,133 @@
+#include <string.h>
+#include <errno.h>
+#include "testutil/testutil.h"
+#include "nimble/ble.h"
+#include "host/ble_hs_test.h"
+#include "ble_hs_conn.h"
+#include "ble_hs_att.h"
+#include "ble_hs_att_cmd.h"
+#include "ble_hs_test_util.h"
+
+static void
+ble_hs_att_clt_test_misc_init(struct ble_hs_conn **conn,
+                              struct ble_l2cap_chan **att_chan)
+{
+    ble_hs_test_util_init();
+
+    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}));
+    *conn = ble_hs_conn_find(2);
+    TEST_ASSERT_FATAL(*conn != NULL);
+
+    *att_chan = ble_hs_conn_chan_find(*conn, BLE_L2CAP_CID_ATT);
+    TEST_ASSERT_FATAL(*att_chan != NULL);
+}
+
+TEST_CASE(ble_hs_att_clt_test_tx_find_info)
+{
+    struct ble_hs_att_find_info_req req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    int rc;
+
+    ble_hs_att_clt_test_misc_init(&conn, &chan);
+
+    /*** Success. */
+    req.bhafq_start_handle = 1;
+    req.bhafq_end_handle = 0xffff;
+    rc = ble_hs_att_clt_tx_find_info(conn, chan, &req);
+    TEST_ASSERT(rc == 0);
+
+    /*** Error: start handle of 0. */
+    req.bhafq_start_handle = 0;
+    req.bhafq_end_handle = 0xffff;
+    rc = ble_hs_att_clt_tx_find_info(conn, chan, &req);
+    TEST_ASSERT(rc == EINVAL);
+
+    /*** Error: start handle greater than end handle. */
+    req.bhafq_start_handle = 500;
+    req.bhafq_end_handle = 499;
+    rc = ble_hs_att_clt_tx_find_info(conn, chan, &req);
+    TEST_ASSERT(rc == EINVAL);
+
+    /*** Success; start and end handles equal. */
+    req.bhafq_start_handle = 500;
+    req.bhafq_end_handle = 500;
+    rc = ble_hs_att_clt_tx_find_info(conn, chan, &req);
+    TEST_ASSERT(rc == 0);
+}
+
+TEST_CASE(ble_hs_att_clt_test_rx_find_info)
+{
+    struct ble_hs_att_find_info_rsp rsp;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint16_t handle_id;
+    uint8_t buf[1024];
+    uint8_t uuid128_1[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
+    int off;
+    int rc;
+
+    ble_hs_att_clt_test_misc_init(&conn, &chan);
+
+    /*** One 128-bit UUID. */
+    /* Ensure attribute mapping is not initially present. */
+    handle_id = ble_hs_att_clt_find_entry_uuid128(conn, uuid128_1);
+    TEST_ASSERT_FATAL(handle_id == 0);
+
+    /* Receive response with attribute mapping. */
+    off = 0;
+    rsp.bhafp_format = BLE_HS_ATT_FIND_INFO_RSP_FORMAT_128BIT;
+    rc = ble_hs_att_find_info_rsp_write(buf + off, sizeof buf - off, &rsp);
+    TEST_ASSERT(rc == 0);
+    off += BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ;
+
+    htole16(buf + off, 1);
+    off += 2;
+
+    memcpy(buf + off, uuid128_1, 16);
+    off += 16;
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, off);
+    TEST_ASSERT(rc == 0);
+
+    handle_id = ble_hs_att_clt_find_entry_uuid128(conn, uuid128_1);
+    TEST_ASSERT_FATAL(handle_id == 1);
+
+    /*** One 16-bit UUID. */
+    /* Ensure attribute mapping is not initially present. */
+    handle_id = ble_hs_att_clt_find_entry_uuid16(conn, 0x000f);
+    TEST_ASSERT_FATAL(handle_id == 0);
+
+    /* Receive response with attribute mapping. */
+    off = 0;
+    rsp.bhafp_format = BLE_HS_ATT_FIND_INFO_RSP_FORMAT_16BIT;
+    rc = ble_hs_att_find_info_rsp_write(buf + off, sizeof buf - off, &rsp);
+    TEST_ASSERT(rc == 0);
+    off += BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ;
+
+    htole16(buf + off, 2);
+    off += 2;
+
+    htole16(buf + off, 0x000f);
+    off += 2;
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, off);
+    TEST_ASSERT(rc == 0);
+
+    handle_id = ble_hs_att_clt_find_entry_uuid16(conn, 0x000f);
+    TEST_ASSERT_FATAL(handle_id == 2);
+}
+
+TEST_SUITE(ble_hs_att_clt_suite)
+{
+    ble_hs_att_clt_test_tx_find_info();
+    ble_hs_att_clt_test_rx_find_info();
+}
+
+int
+ble_hs_att_clt_test_all(void)
+{
+    ble_hs_att_clt_suite();
+
+    return tu_any_failed;
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-larva/blob/61e167e6/net/nimble/host/src/test/ble_hs_att_svr_test.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/test/ble_hs_att_svr_test.c b/net/nimble/host/src/test/ble_hs_att_svr_test.c
new file mode 100644
index 0000000..4cd7045
--- /dev/null
+++ b/net/nimble/host/src/test/ble_hs_att_svr_test.c
@@ -0,0 +1,860 @@
+/**
+ * Copyright (c) 2015 Runtime Inc.
+ *
+ * Licensed 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 <string.h>
+#include "nimble/hci_common.h"
+#include "host/ble_hs.h"
+#include "host/ble_hs_test.h"
+#include "testutil/testutil.h"
+#include "ble_l2cap.h"
+#include "ble_hs_test_util.h"
+#include "ble_hs_conn.h"
+#include "ble_hs_att.h"
+#include "ble_hs_att_cmd.h"
+
+static uint8_t *ble_hs_att_svr_test_attr_r_1;
+static int ble_hs_att_svr_test_attr_r_1_len;
+static uint8_t *ble_hs_att_svr_test_attr_r_2;
+static int ble_hs_att_svr_test_attr_r_2_len;
+
+static uint8_t ble_hs_att_svr_test_attr_w_1[1024];
+static int ble_hs_att_svr_test_attr_w_1_len;
+
+static void
+ble_hs_att_svr_test_misc_init(struct ble_hs_conn **conn,
+                              struct ble_l2cap_chan **att_chan)
+{
+    ble_hs_test_util_init();
+
+    ble_hs_test_util_create_conn(2, ((uint8_t[]){2,3,4,5,6,7,8,9}));
+    *conn = ble_hs_conn_find(2);
+    TEST_ASSERT_FATAL(*conn != NULL);
+
+    *att_chan = ble_hs_conn_chan_find(*conn, BLE_L2CAP_CID_ATT);
+    TEST_ASSERT_FATAL(*att_chan != NULL);
+}
+
+static int
+ble_hs_att_svr_test_misc_attr_fn_r_1(struct ble_hs_att_svr_entry *entry,
+                                     uint8_t op,
+                                     union ble_hs_att_svr_handle_arg *arg)
+{
+    switch (op) {
+    case BLE_HS_ATT_OP_READ_REQ:
+        arg->aha_read.attr_data = ble_hs_att_svr_test_attr_r_1;
+        arg->aha_read.attr_len = ble_hs_att_svr_test_attr_r_1_len;
+        return 0;
+
+    default:
+        return -1;
+    }
+}
+
+static int
+ble_hs_att_svr_test_misc_attr_fn_r_2(struct ble_hs_att_svr_entry *entry,
+                                     uint8_t op,
+                                     union ble_hs_att_svr_handle_arg *arg)
+{
+    switch (op) {
+    case BLE_HS_ATT_OP_READ_REQ:
+        arg->aha_read.attr_data = ble_hs_att_svr_test_attr_r_2;
+        arg->aha_read.attr_len = ble_hs_att_svr_test_attr_r_2_len;
+        return 0;
+
+    default:
+        return -1;
+    }
+}
+
+static int
+ble_hs_att_svr_test_misc_attr_fn_w_1(struct ble_hs_att_svr_entry *entry,
+                                     uint8_t op,
+                                     union ble_hs_att_svr_handle_arg *arg)
+{
+    struct os_mbuf_pkthdr *omp;
+    int rc;
+
+    switch (op) {
+    case BLE_HS_ATT_OP_WRITE_REQ:
+        omp = OS_MBUF_PKTHDR(arg->aha_write.om);
+        rc = os_mbuf_copydata(arg->aha_write.om, 0, arg->aha_write.attr_len,
+                              ble_hs_att_svr_test_attr_w_1);
+        TEST_ASSERT(rc == 0);
+        ble_hs_att_svr_test_attr_w_1_len = arg->aha_write.attr_len;
+        return 0;
+
+    default:
+        return -1;
+    }
+
+    (void)omp;
+}
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_err_rsp(struct ble_l2cap_chan *chan,
+                                           uint8_t req_op, uint16_t handle,
+                                           uint8_t error_code)
+{
+    struct ble_hs_att_error_rsp rsp;
+    uint8_t buf[BLE_HS_ATT_ERROR_RSP_SZ];
+    int rc;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, 0, sizeof buf, buf);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_att_error_rsp_parse(buf, sizeof buf, &rsp);
+    TEST_ASSERT(rc == 0);
+
+    TEST_ASSERT(rsp.bhaep_req_op == req_op);
+    TEST_ASSERT(rsp.bhaep_handle == handle);
+    TEST_ASSERT(rsp.bhaep_error_code == error_code);
+
+    /* Remove the error response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx,
+                BLE_HS_ATT_ERROR_RSP_SZ);
+}
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_read_rsp(struct ble_l2cap_chan *chan,
+                                            uint8_t *attr_data, int attr_len)
+{
+    uint8_t u8;
+    int rc;
+    int i;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, 0, 1, &u8);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(u8 == BLE_HS_ATT_OP_READ_RSP);
+
+    for (i = 0; i < attr_len; i++) {
+        rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, i + 1, 1, &u8);
+        TEST_ASSERT(rc == 0);
+        TEST_ASSERT(u8 == attr_data[i]);
+    }
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, i + 1, 1, &u8);
+    TEST_ASSERT(rc != 0);
+
+    /* Remove the read response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx, attr_len + 1);
+}
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_write_rsp(struct ble_l2cap_chan *chan)
+{
+    uint8_t u8;
+    int rc;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, 0, 1, &u8);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(u8 == BLE_HS_ATT_OP_WRITE_RSP);
+
+    /* Remove the write response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx,
+                BLE_HS_ATT_WRITE_RSP_SZ);
+}
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_mtu_rsp(struct ble_l2cap_chan *chan)
+{
+    struct ble_hs_att_mtu_cmd rsp;
+    uint8_t buf[BLE_HS_ATT_MTU_CMD_SZ];
+    int rc;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, 0, sizeof buf, buf);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_att_mtu_cmd_parse(buf, sizeof buf, &rsp);
+    TEST_ASSERT(rc == 0);
+
+    TEST_ASSERT(rsp.bhamc_mtu == chan->blc_my_mtu);
+
+    /* Remove the write response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx,
+                BLE_HS_ATT_MTU_CMD_SZ);
+}
+
+struct ble_hs_att_svr_test_info_entry {
+    uint16_t handle;        /* 0 on last entry */
+    uint16_t uuid16;        /* 0 if not present. */
+    uint8_t uuid128[16];
+};
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_find_info_rsp(
+    struct ble_l2cap_chan *chan,
+    struct ble_hs_att_svr_test_info_entry *entries)
+{
+    struct ble_hs_att_svr_test_info_entry *entry;
+    struct ble_hs_att_find_info_rsp rsp;
+    uint16_t handle;
+    uint16_t uuid16;
+    uint8_t buf[BLE_HS_ATT_FIND_INFO_RSP_MIN_SZ];
+    uint8_t uuid128[16];
+    int off;
+    int rc;
+
+    off = 0;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, sizeof buf, buf);
+    TEST_ASSERT(rc == 0);
+    off += sizeof buf;
+
+    rc = ble_hs_att_find_info_rsp_parse(buf, sizeof buf, &rsp);
+    TEST_ASSERT(rc == 0);
+
+    for (entry = entries; entry->handle != 0; entry++) {
+        rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 2, &handle);
+        TEST_ASSERT(rc == 0);
+        off += 2;
+
+        handle = le16toh((void *)&handle);
+        TEST_ASSERT(handle == entry->handle);
+
+        if (entry->uuid16 != 0) {
+            TEST_ASSERT(rsp.bhafp_format ==
+                        BLE_HS_ATT_FIND_INFO_RSP_FORMAT_16BIT);
+            rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 2, &uuid16);
+            TEST_ASSERT(rc == 0);
+            off += 2;
+
+            uuid16 = le16toh((void *)&uuid16);
+            TEST_ASSERT(uuid16 == entry->uuid16);
+        } else {
+            TEST_ASSERT(rsp.bhafp_format ==
+                        BLE_HS_ATT_FIND_INFO_RSP_FORMAT_128BIT);
+            rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 16, uuid128);
+            TEST_ASSERT(rc == 0);
+            off += 16;
+
+            TEST_ASSERT(memcmp(uuid128, entry->uuid128, 16) == 0);
+        }
+    }
+
+    /* Ensure there is no extra data in the response. */
+    TEST_ASSERT(off == OS_MBUF_PKTHDR(ble_hs_test_util_prev_tx)->omp_len);
+
+    /* Remove the response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx, off);
+}
+
+struct ble_hs_att_svr_test_type_value_entry {
+    uint16_t first;        /* 0 on last entry */
+    uint16_t last;
+};
+
+static void
+ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(
+    struct ble_l2cap_chan *chan,
+    struct ble_hs_att_svr_test_type_value_entry *entries)
+{
+    struct ble_hs_att_svr_test_type_value_entry *entry;
+    uint16_t u16;
+    uint8_t op;
+    int off;
+    int rc;
+
+    off = 0;
+
+    rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 1, &op);
+    TEST_ASSERT(rc == 0);
+    off += 1;
+
+    TEST_ASSERT(op == BLE_HS_ATT_OP_FIND_TYPE_VALUE_RSP);
+
+    for (entry = entries; entry->first != 0; entry++) {
+        rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 2, &u16);
+        TEST_ASSERT(rc == 0);
+        htole16(&u16, u16);
+        TEST_ASSERT(u16 == entry->first);
+        off += 2;
+
+        rc = os_mbuf_copydata(ble_hs_test_util_prev_tx, off, 2, &u16);
+        TEST_ASSERT(rc == 0);
+        htole16(&u16, u16);
+        TEST_ASSERT(u16 == entry->last);
+        off += 2;
+    }
+
+    /* Ensure there is no extra data in the response. */
+    TEST_ASSERT(off == OS_MBUF_PKTHDR(ble_hs_test_util_prev_tx)->omp_len);
+
+    /* Remove the response from the buffer. */
+    os_mbuf_adj(ble_hs_test_util_prev_tx, off);
+}
+
+static void
+ble_hs_att_svr_test_misc_mtu_exchange(uint16_t my_mtu, uint16_t peer_sent,
+                                      uint16_t peer_actual, uint16_t chan_mtu)
+{
+    struct ble_hs_att_mtu_cmd req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint8_t buf[BLE_HS_ATT_MTU_CMD_SZ];
+    int rc;
+
+    ble_hs_att_svr_test_misc_init(&conn, &chan);
+
+    chan->blc_my_mtu = my_mtu;
+
+    req.bhamc_mtu = peer_sent;
+    rc = ble_hs_att_mtu_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    TEST_ASSERT(chan->blc_peer_mtu == peer_actual);
+
+    ble_hs_att_svr_test_misc_verify_tx_mtu_rsp(chan);
+
+    TEST_ASSERT(ble_l2cap_chan_mtu(chan) == chan_mtu);
+}
+
+TEST_CASE(ble_hs_att_svr_test_mtu)
+{
+    /*** MTU too low; should pretend peer sent default value instead. */
+    ble_hs_att_svr_test_misc_mtu_exchange(BLE_HS_ATT_MTU_DFLT, 5,
+                                          BLE_HS_ATT_MTU_DFLT,
+                                          BLE_HS_ATT_MTU_DFLT);
+
+    /*** MTUs equal. */
+    ble_hs_att_svr_test_misc_mtu_exchange(50, 50, 50, 50);
+
+    /*** Peer's higher than mine. */
+    ble_hs_att_svr_test_misc_mtu_exchange(50, 100, 100, 50);
+
+    /*** Mine higher than peer's. */
+    ble_hs_att_svr_test_misc_mtu_exchange(100, 50, 50, 50);
+}
+
+TEST_CASE(ble_hs_att_svr_test_read)
+{
+    struct ble_hs_att_read_req req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint8_t buf[BLE_HS_ATT_READ_REQ_SZ];
+    uint8_t uuid[16] = {0};
+    int rc;
+
+    ble_hs_att_svr_test_misc_init(&conn, &chan);
+
+    /*** Nonexistent attribute. */
+    req.bharq_handle = 0;
+    rc = ble_hs_att_read_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(chan, BLE_HS_ATT_OP_READ_REQ, 0,
+                                               BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** Successful read. */
+    ble_hs_att_svr_test_attr_r_1 = (uint8_t[]){0,1,2,3,4,5,6,7};
+    ble_hs_att_svr_test_attr_r_1_len = 8;
+    rc = ble_hs_att_svr_register(uuid, 0, &req.bharq_handle,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_att_read_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_read_rsp(
+        chan, ble_hs_att_svr_test_attr_r_1, ble_hs_att_svr_test_attr_r_1_len);
+
+    /*** Partial read. */
+    ble_hs_att_svr_test_attr_r_1 =
+        (uint8_t[]){0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,
+                    22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39};
+    ble_hs_att_svr_test_attr_r_1_len = 40;
+
+    rc = ble_hs_att_read_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_read_rsp(chan,
+                                                ble_hs_att_svr_test_attr_r_1,
+                                                BLE_HS_ATT_MTU_DFLT - 1);
+}
+
+TEST_CASE(ble_hs_att_svr_test_write)
+{
+    struct ble_hs_att_write_req req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint8_t buf[BLE_HS_ATT_READ_REQ_SZ + 8];
+    uint8_t uuid[16] = {0};
+    int rc;
+
+    ble_hs_att_svr_test_misc_init(&conn, &chan);
+
+    /*** Nonexistent attribute. */
+    req.bhawq_handle = 0;
+    rc = ble_hs_att_write_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+    memcpy(buf + BLE_HS_ATT_READ_REQ_SZ, ((uint8_t[]){0,1,2,3,4,5,6,7}), 8);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_WRITE_REQ, 0, BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** Successful write. */
+    rc = ble_hs_att_svr_register(uuid, 0, &req.bhawq_handle,
+                                 ble_hs_att_svr_test_misc_attr_fn_w_1);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_att_write_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+    memcpy(buf + BLE_HS_ATT_WRITE_REQ_MIN_SZ,
+           ((uint8_t[]){0,1,2,3,4,5,6,7}), 8);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_write_rsp(chan);
+}
+
+TEST_CASE(ble_hs_att_svr_test_find_info)
+{
+    struct ble_hs_att_find_info_req req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint16_t handle1;
+    uint16_t handle2;
+    uint16_t handle3;
+    uint8_t buf[BLE_HS_ATT_FIND_INFO_REQ_SZ];
+    uint8_t uuid1[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
+    uint8_t uuid2[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
+    uint8_t uuid3[16] = {
+        0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x10, 0x00,
+        0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+    };
+    int rc;
+
+    ble_hs_att_svr_test_misc_init(&conn, &chan);
+
+    /* Increase the MTU to 128 bytes to allow testing of long responses. */
+    chan->blc_my_mtu = 128;
+    chan->blc_peer_mtu = 128;
+    chan->blc_flags |= BLE_L2CAP_CHAN_F_TXED_MTU;
+
+    /*** Start handle of 0. */
+    req.bhafq_start_handle = 0;
+    req.bhafq_end_handle = 0;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_INFO_REQ, 0, BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** Start handle > end handle. */
+    req.bhafq_start_handle = 101;
+    req.bhafq_end_handle = 100;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_INFO_REQ, 101, BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** No attributes. */
+    req.bhafq_start_handle = 200;
+    req.bhafq_end_handle = 300;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_INFO_REQ, 200, BLE_HS_ATT_ERR_ATTR_NOT_FOUND);
+
+    /*** Range too late. */
+    rc = ble_hs_att_svr_register(uuid1, 0, &handle1,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhafq_start_handle = 200;
+    req.bhafq_end_handle = 300;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_INFO_REQ, 200, BLE_HS_ATT_ERR_ATTR_NOT_FOUND);
+
+    /*** One 128-bit entry. */
+    req.bhafq_start_handle = handle1;
+    req.bhafq_end_handle = handle1;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_info_rsp(chan,
+        ((struct ble_hs_att_svr_test_info_entry[]) { {
+            .handle = handle1,
+            .uuid128 = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
+        }, {
+            .handle = 0,
+        } }));
+
+    /*** Two 128-bit entries. */
+    rc = ble_hs_att_svr_register(uuid2, 0,
+                                 &handle2,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhafq_start_handle = handle1;
+    req.bhafq_end_handle = handle2;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_info_rsp(chan,
+        ((struct ble_hs_att_svr_test_info_entry[]) { {
+            .handle = handle1,
+            .uuid128 = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
+        }, {
+            .handle = handle2,
+            .uuid128 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16},
+        }, {
+            .handle = 0,
+        } }));
+
+    /*** Two 128-bit entries; 16-bit entry doesn't get sent. */
+    rc = ble_hs_att_svr_register(uuid3, 0,
+                                 &handle3,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhafq_start_handle = handle1;
+    req.bhafq_end_handle = handle3;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_info_rsp(chan,
+        ((struct ble_hs_att_svr_test_info_entry[]) { {
+            .handle = handle1,
+            .uuid128 = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
+        }, {
+            .handle = handle2,
+            .uuid128 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16},
+        }, {
+            .handle = 0,
+        } }));
+
+    /*** Remaining 16-bit entry requested. */
+    req.bhafq_start_handle = handle3;
+    req.bhafq_end_handle = handle3;
+
+    rc = ble_hs_att_find_info_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_info_rsp(chan,
+        ((struct ble_hs_att_svr_test_info_entry[]) { {
+            .handle = handle3,
+            .uuid16 = 0x000f,
+        }, {
+            .handle = 0,
+        } }));
+}
+
+TEST_CASE(ble_hs_att_svr_test_find_type_value)
+{
+    struct ble_hs_att_find_type_value_req req;
+    struct ble_l2cap_chan *chan;
+    struct ble_hs_conn *conn;
+    uint8_t buf[BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ + 2];
+    uint16_t handle1;
+    uint16_t handle2;
+    uint16_t handle3;
+    uint16_t handle4;
+    uint16_t handle5;
+    uint8_t uuid1[16] = {
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00,
+        0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+    };
+    uint8_t uuid2[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
+    uint8_t uuid3[16] = {
+        0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00,
+        0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+    };
+    int rc;
+
+    ble_hs_att_svr_test_misc_init(&conn, &chan);
+
+    /* Increase the MTU to 128 bytes to allow testing of long responses. */
+    chan->blc_my_mtu = 128;
+    chan->blc_peer_mtu = 128;
+    chan->blc_flags |= BLE_L2CAP_CHAN_F_TXED_MTU;
+
+    /* One-time write of the attribute value at the end of the request. */
+    ble_hs_att_svr_test_attr_r_1 = (uint8_t[]){0x99, 0x99};
+    ble_hs_att_svr_test_attr_r_1_len = 2;
+    memcpy(buf + BLE_HS_ATT_FIND_TYPE_VALUE_REQ_MIN_SZ,
+           ble_hs_att_svr_test_attr_r_1,
+           ble_hs_att_svr_test_attr_r_1_len);
+
+    /*** Start handle of 0. */
+    req.bhavq_start_handle = 0;
+    req.bhavq_end_handle = 0;
+    req.bhavq_attr_type = 0x0001;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ, 0,
+        BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** Start handle > end handle. */
+    req.bhavq_start_handle = 101;
+    req.bhavq_end_handle = 100;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ, 101,
+        BLE_HS_ATT_ERR_INVALID_HANDLE);
+
+    /*** No attributes. */
+    req.bhavq_start_handle = 200;
+    req.bhavq_end_handle = 300;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ, 200,
+        BLE_HS_ATT_ERR_ATTR_NOT_FOUND);
+
+    /*** Range too late. */
+    rc = ble_hs_att_svr_register(uuid1, 0, &handle1,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhavq_start_handle = 200;
+    req.bhavq_end_handle = 300;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc != 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_err_rsp(
+        chan, BLE_HS_ATT_OP_FIND_TYPE_VALUE_REQ, 200,
+        BLE_HS_ATT_ERR_ATTR_NOT_FOUND);
+
+    /*** One entry, one attribute. */
+    req.bhavq_start_handle = handle1;
+    req.bhavq_end_handle = handle1;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(chan,
+        ((struct ble_hs_att_svr_test_type_value_entry[]) { {
+            .first = handle1,
+            .last = handle1,
+        }, {
+            .first = 0,
+        } }));
+
+    /*** One entry, two attributes. */
+    rc = ble_hs_att_svr_register(uuid1, 0, &handle2,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhavq_start_handle = handle1;
+    req.bhavq_end_handle = handle2;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(chan,
+        ((struct ble_hs_att_svr_test_type_value_entry[]) { {
+            .first = handle1,
+            .last = handle2,
+        }, {
+            .first = 0,
+        } }));
+
+    /*** Entry 1: two attributes; entry 2: one attribute. */
+    rc = ble_hs_att_svr_register(uuid2, 0, &handle3,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_2);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_att_svr_register(uuid1, 0, &handle4,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+    TEST_ASSERT(rc == 0);
+
+    req.bhavq_start_handle = 0x0001;
+    req.bhavq_end_handle = 0xffff;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(chan,
+        ((struct ble_hs_att_svr_test_type_value_entry[]) { {
+            .first = handle1,
+            .last = handle2,
+        }, {
+            .first = handle4,
+            .last = handle4,
+        }, {
+            .first = 0,
+        } }));
+
+    /*** Ensure attribute with wrong value is not included. */
+    ble_hs_att_svr_test_attr_r_2 = (uint8_t[]){0x00, 0x00};
+    ble_hs_att_svr_test_attr_r_2_len = 2;
+
+    req.bhavq_start_handle = 0x0001;
+    req.bhavq_end_handle = 0xffff;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(chan,
+        ((struct ble_hs_att_svr_test_type_value_entry[]) { {
+            .first = handle1,
+            .last = handle2,
+        }, {
+            .first = handle4,
+            .last = handle4,
+        }, {
+            .first = 0,
+        } }));
+
+    /*** Ensure attribute with wrong type is not included. */
+    rc = ble_hs_att_svr_register(uuid3, 0, &handle5,
+                                 ble_hs_att_svr_test_misc_attr_fn_r_1);
+
+    req.bhavq_start_handle = 0x0001;
+    req.bhavq_end_handle = 0xffff;
+
+    rc = ble_hs_att_find_type_value_req_write(buf, sizeof buf, &req);
+    TEST_ASSERT(rc == 0);
+
+    rc = ble_hs_test_util_l2cap_rx_payload_flat(conn, chan, buf, sizeof buf);
+    TEST_ASSERT(rc == 0);
+    ble_hs_process_tx_data_queue();
+
+    ble_hs_att_svr_test_misc_verify_tx_find_type_value_rsp(chan,
+        ((struct ble_hs_att_svr_test_type_value_entry[]) { {
+            .first = handle1,
+            .last = handle2,
+        }, {
+            .first = handle4,
+            .last = handle4,
+        }, {
+            .first = 0,
+        } }));
+}
+
+TEST_SUITE(ble_hs_att_svr_suite)
+{
+    ble_hs_att_svr_test_mtu();
+    ble_hs_att_svr_test_read();
+    ble_hs_att_svr_test_write();
+    ble_hs_att_svr_test_find_info();
+    ble_hs_att_svr_test_find_type_value();
+}
+
+int
+ble_hs_att_svr_test_all(void)
+{
+    ble_hs_att_svr_suite();
+
+    return tu_any_failed;
+}