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 2016/07/15 04:30:06 UTC

[9/9] incubator-mynewt-core git commit: blecent - Example app: NimBLE central.

blecent - Example app: NimBLE central.


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

Branch: refs/heads/develop
Commit: b236e39822877754d407c86caa174dab373d5396
Parents: 35d4eb8
Author: Christopher Collins <cc...@apache.org>
Authored: Thu Jul 14 21:24:00 2016 -0700
Committer: Christopher Collins <cc...@apache.org>
Committed: Thu Jul 14 21:29:49 2016 -0700

----------------------------------------------------------------------
 apps/blecent/pkg.yml       |  37 ++
 apps/blecent/src/blecent.h | 116 ++++++
 apps/blecent/src/main.c    | 590 ++++++++++++++++++++++++++++++
 apps/blecent/src/misc.c    | 220 ++++++++++++
 apps/blecent/src/peer.c    | 772 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1735 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/b236e398/apps/blecent/pkg.yml
----------------------------------------------------------------------
diff --git a/apps/blecent/pkg.yml b/apps/blecent/pkg.yml
new file mode 100644
index 0000000..b1710aa
--- /dev/null
+++ b/apps/blecent/pkg.yml
@@ -0,0 +1,37 @@
+# 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.
+#
+pkg.name: apps/blecent
+pkg.type: app
+pkg.description: Simple BLE central application.
+pkg.author: "Apache Mynewt <de...@mynewt.incubator.apache.org>"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+
+pkg.deps: 
+    - libs/os 
+    - sys/log
+    - net/nimble/controller
+    - net/nimble/host
+    - net/nimble/host/services/mandatory
+    - net/nimble/host/store/ram
+    - libs/console/full
+    - libs/baselibc
+
+pkg.cflags:
+    # DEBUG logging is a bit noisy; use INFO.
+    - "-DLOG_LEVEL=1"

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/b236e398/apps/blecent/src/blecent.h
----------------------------------------------------------------------
diff --git a/apps/blecent/src/blecent.h b/apps/blecent/src/blecent.h
new file mode 100644
index 0000000..c2a2aee
--- /dev/null
+++ b/apps/blecent/src/blecent.h
@@ -0,0 +1,116 @@
+/**
+ * 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.
+ */
+
+#ifndef H_BLECENT_
+#define H_BLECENT_
+
+#include "os/queue.h"
+#include "log/log.h"
+struct ble_hs_adv_fields;
+struct ble_gap_conn_desc;
+struct ble_hs_cfg;
+union ble_store_value;
+union ble_store_key;
+
+extern struct log blecent_log;
+
+/* blecent uses the first "peruser" log module. */
+#define BLECENT_LOG_MODULE  (LOG_MODULE_PERUSER + 0)
+
+/* Convenience macro for logging to the blecent module. */
+#define BLECENT_LOG(lvl, ...) \
+    LOG_ ## lvl(&blecent_log, BLECENT_LOG_MODULE, __VA_ARGS__)
+
+#define BLECENT_SVC_ALERT_UUID              0x1811
+#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID  0x2A47
+#define BLECENT_CHR_NEW_ALERT               0x2A46
+#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID  0x2A48
+#define BLECENT_CHR_UNR_ALERT_STAT_UUID     0x2A45
+#define BLECENT_CHR_ALERT_NOT_CTRL_PT       0x2A44
+
+/** GATT server. */
+void gatt_svr_register(void);
+void gatt_svr_init_cfg(struct ble_hs_cfg *cfg);
+
+
+/** Misc. */
+void print_bytes(const uint8_t *bytes, int len);
+char *addr_str(const void *addr);
+void print_uuid(const void *uuid128);
+void print_conn_desc(const struct ble_gap_conn_desc *desc);
+void print_adv_fields(const struct ble_hs_adv_fields *fields);
+
+/** Peer. */
+struct peer_dsc {
+    SLIST_ENTRY(peer_dsc) next;
+    struct ble_gatt_dsc dsc;
+};
+SLIST_HEAD(peer_dsc_list, peer_dsc);
+
+struct peer_chr {
+    SLIST_ENTRY(peer_chr) next;
+    struct ble_gatt_chr chr;
+
+    struct peer_dsc_list dscs;
+};
+SLIST_HEAD(peer_chr_list, peer_chr);
+
+struct peer_svc {
+    SLIST_ENTRY(peer_svc) next;
+    struct ble_gatt_svc svc;
+
+    struct peer_chr_list chrs;
+};
+SLIST_HEAD(peer_svc_list, peer_svc);
+
+struct peer;
+typedef void peer_disc_fn(const struct peer *peer, int status, void *arg);
+
+struct peer {
+    SLIST_ENTRY(peer) next;
+
+    uint16_t conn_handle;
+
+    /** List of discovered GATT services. */
+    struct peer_svc_list svcs;
+
+    /** Keeps track of where we are in the service discovery process. */
+    uint16_t disc_prev_chr_val;
+    struct peer_svc *cur_svc;
+
+    /** Callback that gets executed when service discovery completes. */
+    peer_disc_fn *disc_cb;
+    void *disc_cb_arg;
+};
+
+int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb,
+                  void *disc_cb_arg);
+const struct peer_dsc *
+peer_dsc_find_uuid(const struct peer *peer, const uint8_t *svc_uuid128,
+                   const uint8_t *chr_uuid128, const uint8_t *dsc_uuid128);
+const struct peer_chr *
+peer_chr_find_uuid(const struct peer *peer, const uint8_t *svc_uuid128,
+                   const uint8_t *chr_uuid128);
+const struct peer_svc *
+peer_svc_find_uuid(const struct peer *peer, const uint8_t *uuid128);
+int peer_delete(uint16_t conn_handle);
+int peer_add(uint16_t conn_handle);
+int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
+
+#endif

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/b236e398/apps/blecent/src/main.c
----------------------------------------------------------------------
diff --git a/apps/blecent/src/main.c b/apps/blecent/src/main.c
new file mode 100755
index 0000000..a86f11a
--- /dev/null
+++ b/apps/blecent/src/main.c
@@ -0,0 +1,590 @@
+/**
+ * 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 <assert.h>
+#include <string.h>
+#include "bsp/bsp.h"
+#include "os/os.h"
+#include "hal/hal_cputime.h"
+#include "console/console.h"
+
+/* BLE */
+#include "nimble/ble.h"
+#include "controller/ble_ll.h"
+#include "host/host_hci.h"
+#include "host/ble_hs.h"
+
+/* RAM persistence layer. */
+#include "store/ram/ble_store_ram.h"
+
+/* Mandatory services. */
+#include "services/mandatory/ble_svc_gap.h"
+#include "services/mandatory/ble_svc_gatt.h"
+
+/* Application-specified header. */
+#include "blecent.h"
+
+#define BSWAP16(x)  ((uint16_t)(((x) << 8) | (((x) & 0xff00) >> 8)))
+
+/** Mbuf settings. */
+#define MBUF_NUM_MBUFS      (12)
+#define MBUF_BUF_SIZE       OS_ALIGN(BLE_MBUF_PAYLOAD_SIZE, 4)
+#define MBUF_MEMBLOCK_SIZE  (MBUF_BUF_SIZE + BLE_MBUF_MEMBLOCK_OVERHEAD)
+#define MBUF_MEMPOOL_SIZE   OS_MEMPOOL_SIZE(MBUF_NUM_MBUFS, MBUF_MEMBLOCK_SIZE)
+
+static os_membuf_t blecent_mbuf_mpool_data[MBUF_MEMPOOL_SIZE];
+struct os_mbuf_pool blecent_mbuf_pool;
+struct os_mempool blecent_mbuf_mpool;
+
+/** Log data. */
+static struct log_handler blecent_log_console_handler;
+struct log blecent_log;
+
+/** Priority of the nimble host and controller tasks. */
+#define BLE_LL_TASK_PRI             (OS_TASK_PRI_HIGHEST)
+
+/** blecent task settings. */
+#define BLECENT_TASK_PRIO           1
+#define BLECENT_STACK_SIZE          (OS_STACK_ALIGN(336))
+
+struct os_eventq blecent_evq;
+struct os_task blecent_task;
+bssnz_t os_stack_t blecent_stack[BLECENT_STACK_SIZE];
+
+/** Our global device address (public) */
+uint8_t g_dev_addr[BLE_DEV_ADDR_LEN] = {0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c};
+
+/** Our random address (in case we need it) */
+uint8_t g_random_addr[BLE_DEV_ADDR_LEN];
+
+static int blecent_gap_event(struct ble_gap_event *event, void *arg);
+
+/**
+ * Application callback.  Called when the read of the ANS Supported New Alert
+ * Category characteristic has completed.
+ */
+static int
+blecent_on_read(uint16_t conn_handle,
+                const struct ble_gatt_error *error,
+                const struct ble_gatt_attr *attr,
+                void *arg)
+{
+    BLECENT_LOG(INFO, "Read complete; status=%d conn_handle=%d", error->status,
+                conn_handle);
+    if (error->status == 0) {
+        BLECENT_LOG(INFO, " attr_handle=%d value=", attr->handle);
+        print_bytes(attr->value, attr->value_len);
+    }
+    BLECENT_LOG(INFO, "\n");
+
+    return 0;
+}
+
+/**
+ * Application callback.  Called when the write to the ANS Alert Notification
+ * Control Point characteristic has completed.
+ */
+static int
+blecent_on_write(uint16_t conn_handle,
+                 const struct ble_gatt_error *error,
+                 const struct ble_gatt_attr *attr,
+                 void *arg)
+{
+    BLECENT_LOG(INFO, "Write complete; status=%d conn_handle=%d "
+                      "attr_handle=%d\n",
+                error->status, conn_handle, attr->handle);
+
+    return 0;
+}
+
+/**
+ * Application callback.  Called when the attempt to subscribe to notifications
+ * for the ANS Unread Alert Status characteristic has completed.
+ */
+static int
+blecent_on_subscribe(uint16_t conn_handle,
+                     const struct ble_gatt_error *error,
+                     const struct ble_gatt_attr *attr,
+                     void *arg)
+{
+    BLECENT_LOG(INFO, "Subscribe complete; status=%d conn_handle=%d "
+                      "attr_handle=%d\n",
+                error->status, conn_handle, attr->handle);
+
+    /* Now that notifications have been enabled, we can terminate the
+     * connection.
+     */
+    BLECENT_LOG(INFO, "Terminating the connection; conn_handle=%d\n",
+                conn_handle);
+    ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+
+    return 0;
+}
+
+/**
+ * Performs three concurrent GATT operations against the specified peer:
+ * 1. Reads the ANS Supported New Alert Category characteristic.
+ * 2. Writes the ANS Alert Notification Control Point characteristic.
+ * 3. Subscribes to notifications for the ANS Unread Alert Status
+ *    characteristic.
+ *
+ * If the peer does not support a required service, characteristic, or
+ * descriptor, then the peer lied when it claimed support for the alert
+ * notification service!  When this happens, or if a GATT procedure fails, this
+ * function immediately terminates the connection.
+ *
+ * When all three procedures have completed, the connection is terminated (this
+ * happens in the subscribe callback, not in this function).
+ */
+static void
+blecent_read_write_subscribe(const struct peer *peer)
+{
+    const struct peer_chr *chr;
+    const struct peer_dsc *dsc;
+    uint8_t value[2];
+    int rc;
+
+    /* Read the supported-new-alert-category characteristic. */
+    chr = peer_chr_find_uuid(peer,
+                             BLE_UUID16(BLECENT_SVC_ALERT_UUID),
+                             BLE_UUID16(BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID));
+    if (chr == NULL) {
+        BLECENT_LOG(ERROR, "Error: Peer doesn't support the Supported New "
+                           "Alert Category characteristic\n");
+        goto err;
+    }
+
+    rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
+                        blecent_on_read, NULL);
+    if (rc != 0) {
+        BLECENT_LOG(ERROR, "Error: Failed to read characteristic; rc=%d\n",
+                    rc);
+        goto err;
+    }
+
+    /* Write two bytes (99, 100) to the alert-notification-control-point
+     * characteristic.
+     */
+    chr = peer_chr_find_uuid(peer,
+                             BLE_UUID16(BLECENT_SVC_ALERT_UUID),
+                             BLE_UUID16(BLECENT_CHR_ALERT_NOT_CTRL_PT));
+    if (chr == NULL) {
+        BLECENT_LOG(ERROR, "Error: Peer doesn't support the Alert "
+                           "Notification Control Point characteristic\n");
+        goto err;
+    }
+
+    value[0] = 99;
+    value[1] = 100;
+    rc = ble_gattc_write(peer->conn_handle, chr->chr.val_handle, 
+                         value, sizeof value, blecent_on_write, NULL);
+    if (rc != 0) {
+        BLECENT_LOG(ERROR, "Error: Failed to write characteristic; rc=%d\n",
+                    rc);
+    }
+
+    /* Subscribe to notifications for the Unread Alert Status characteristic.
+     * A central enables notifications by writing two bytes (1, 0) to the
+     * characteristic's client-characteristic-configuration-descriptor (CCCD).
+     */
+    dsc = peer_dsc_find_uuid(peer,
+                             BLE_UUID16(BLECENT_SVC_ALERT_UUID),
+                             BLE_UUID16(BLECENT_CHR_UNR_ALERT_STAT_UUID),
+                             BLE_UUID16(BLE_GATT_DSC_CLT_CFG_UUID16));
+    if (dsc == NULL) {
+        BLECENT_LOG(ERROR, "Error: Peer lacks a CCCD for the Unread Alert "
+                           "Status characteristic\n");
+        goto err;
+    }
+
+    value[0] = 1;
+    value[1] = 0;
+    rc = ble_gattc_write(peer->conn_handle, dsc->dsc.handle,
+                         value, sizeof value, blecent_on_subscribe, NULL);
+    if (rc != 0) {
+        BLECENT_LOG(ERROR, "Error: Failed to subscribe to characteristic; "
+                           "rc=%d\n", rc);
+        goto err;
+    }
+
+    return;
+
+err:
+    /* Terminate the connection. */
+    ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+}
+
+/**
+ * Called when service discovery of the specified peer has completed.
+ */
+static void
+blecent_on_disc_complete(const struct peer *peer, int status, void *arg)
+{
+
+    if (status != 0) {
+        /* Service discovery failed.  Terminate the connection. */
+        BLECENT_LOG(ERROR, "Error: Service discovery failed; status=%d "
+                           "conn_handle=%d\n", status, peer->conn_handle);
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return;
+    }
+
+    /* Service discovery has completed successfully.  Now we have a complete
+     * list of services, characteristics, and descriptors that the peer
+     * supports.
+     */
+    BLECENT_LOG(ERROR, "Service discovery complete; status=%d "
+                       "conn_handle=%d\n", status, peer->conn_handle);
+
+    /* Now perform three concurrent GATT procedures against the peer: read,
+     * write, and subscribe to notifications.
+     */
+    blecent_read_write_subscribe(peer);
+}
+
+/**
+ * Initiates the GAP general discovery procedure.
+ */
+static void
+blecent_scan(void)
+{
+    struct ble_gap_disc_params disc_params;
+    int rc;
+
+    /* Tell the controller to filter duplicates; we don't want to process
+     * repeated advertisements from the same device.
+     */
+    disc_params.filter_duplicates = 1;
+
+    /**
+     * Perform a passive scan.  I.e., don't send follow-up scan requests to
+     * each advertiser.
+     */
+    disc_params.passive = 1;
+
+    /* Use defaults for the rest of the parameters. */
+    disc_params.itvl = 0;
+    disc_params.window = 0;
+    disc_params.filter_policy = 0;
+    disc_params.limited = 0;
+
+    rc = ble_gap_disc(BLE_ADDR_TYPE_PUBLIC, BLE_HS_FOREVER, &disc_params,
+                      blecent_gap_event, NULL);
+    if (rc != 0) {
+        BLECENT_LOG(ERROR, "Error initiating GAP discovery procedure; rc=%d\n",
+                    rc);
+    }
+}
+
+/**
+ * Indicates whether we should tre to connect to the sender of the specified
+ * advertisement.  The function returns a positive result if the device
+ * advertises connectability and support for the Alert Notification service.
+ */
+static int
+blecent_should_connect(const struct ble_gap_disc_desc *disc)
+{
+    int i;
+
+    /* The device has to be advertising connectability. */
+    if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND &&
+        disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) {
+
+        return 0;
+    }
+
+    /* The device has to advertise support for the Alert Notification
+     * service (0x1811).
+     */
+    for (i = 0; i < disc->fields->num_uuids16; i++) {
+        if (disc->fields->uuids16[i] == BLECENT_SVC_ALERT_UUID) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Connects to the sender of the specified advertisement of it looks
+ * interesting.  A device is "interesting" if it advertises connectability and
+ * support for the Alert Notification service.
+ */
+static void
+blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
+{
+    int rc;
+
+    /* Don't do anything if we don't care about this advertiser. */
+    if (!blecent_should_connect(disc)) {
+        return;
+    }
+
+    /* Scanning must be stopped before a connection can be initiated. */
+    rc = ble_gap_disc_cancel();
+    if (rc != 0) {
+        BLECENT_LOG(DEBUG, "Failed to cancel scan; rc=%d\n", rc);
+        return;
+    }
+
+    /* Try to connect the the advertiser.  Allow 30 seconds (30000 ms) for
+     * timeout.
+     */
+    rc = ble_gap_connect(BLE_ADDR_TYPE_PUBLIC, disc->addr_type, disc->addr,
+                         30000, NULL, blecent_gap_event, NULL);
+    if (rc != 0) {
+        BLECENT_LOG(ERROR, "Error: Failed to connect to device; addr_type=%d "
+                           "addr=%s\n", disc->addr_type, addr_str(disc->addr));
+        return;
+    }
+}
+
+/**
+ * The nimble host executes this callback when a GAP event occurs.  The
+ * application associates a GAP event callback with each connection that is
+ * established.  blecent uses the same callback for all connections.
+ *
+ * @param event                 The event being signalled.
+ * @param arg                   Application-specified argument; unused by
+ *                                  blecent.
+ *
+ * @return                      0 if the application successfully handled the
+ *                                  event; nonzero on failure.  The semantics
+ *                                  of the return code is specific to the
+ *                                  particular GAP event being signalled.
+ */
+static int
+blecent_gap_event(struct ble_gap_event *event, void *arg)
+{
+    int rc;
+
+    switch (event->type) {
+    case BLE_GAP_EVENT_DISC:
+        /* An advertisment report was received during GAP discovery. */
+        print_adv_fields(event->disc.fields);
+
+        /* Try to connect to the advertiser if it looks interesting. */
+        blecent_connect_if_interesting(&event->disc);
+        return 0;
+
+    case BLE_GAP_EVENT_CONNECT:
+        /* A new connection was established or a connection attempt failed. */
+        BLECENT_LOG(INFO, "connection %s; status=%d ",
+                    event->connect.status == 0 ? "established" : "failed",
+                    event->connect.status);
+        print_conn_desc(&event->connect.conn);
+        BLECENT_LOG(INFO, "\n");
+
+        if (event->connect.status != 0) {
+            /* Connection attempt failed; resume scanning. */
+            blecent_scan();
+        } else {
+            /* Connection successfully established. */
+
+            /* Remember peer. */
+            rc = peer_add(event->connect.conn.conn_handle);
+            if (rc != 0) {
+                BLECENT_LOG(ERROR, "Failed to add peer; rc=%d\n", rc);
+                return 0;
+            }
+
+            /* Perform service discovery. */
+            rc = peer_disc_all(event->connect.conn.conn_handle,
+                               blecent_on_disc_complete, NULL);
+            if (rc != 0) {
+                BLECENT_LOG(ERROR, "Failed to discover services; rc=%d\n", rc);
+                return 0;
+            }
+        }
+
+        return 0;
+
+    case BLE_GAP_EVENT_DISCONNECT:
+        /* Connection terminated. */
+        BLECENT_LOG(INFO, "disconnect; reason=%d ", event->disconnect.reason);
+        print_conn_desc(&event->disconnect.conn);
+        BLECENT_LOG(INFO, "\n");
+
+        /* Forget about peer. */
+        peer_delete(event->disconnect.conn.conn_handle);
+
+        /* Resume scanning. */
+        blecent_scan();
+        return 0;
+
+    case BLE_GAP_EVENT_ENC_CHANGE:
+        /* Encryption has been enabled or disabled for this connection. */
+        BLECENT_LOG(INFO, "encryption change event; status=%d ",
+                    event->enc_change.status);
+        print_conn_desc(&event->enc_change.conn);
+        BLECENT_LOG(INFO, "\n");
+        return 0;
+
+    default:
+        return 0;
+    }
+}
+
+/**
+ * Event loop for the main blecent task.
+ */
+static void
+blecent_task_handler(void *unused)
+{
+    struct os_event *ev;
+    struct os_callout_func *cf;
+    int rc;
+
+    /* Activate the host.  This causes the host to synchronize with the
+     * controller.
+     */
+    rc = ble_hs_start();
+    assert(rc == 0);
+
+    /* Begin scanning for a peripheral to connect to. */
+    blecent_scan();
+
+    while (1) {
+        ev = os_eventq_get(&blecent_evq);
+        switch (ev->ev_type) {
+        case OS_EVENT_T_TIMER:
+            cf = (struct os_callout_func *)ev;
+            assert(cf->cf_func);
+            cf->cf_func(CF_ARG(cf));
+            break;
+
+        default:
+            assert(0);
+            break;
+        }
+    }
+}
+
+/**
+ * main
+ *
+ * The main function for the project. This function initializes the os, calls
+ * init_tasks to initialize tasks (and possibly other objects), then starts the
+ * OS. We should not return from os start.
+ *
+ * @return int NOTE: this function should never return!
+ */
+int
+main(void)
+{
+    struct ble_hs_cfg cfg;
+    uint32_t seed;
+    int rc;
+    int i;
+
+    /* Initialize OS */
+    os_init();
+
+    /* Set cputime to count at 1 usec increments */
+    rc = cputime_init(1000000);
+    assert(rc == 0);
+
+    /* Seed random number generator with least significant bytes of device
+     * address.
+     */
+    seed = 0;
+    for (i = 0; i < 4; ++i) {
+        seed |= g_dev_addr[i];
+        seed <<= 8;
+    }
+    srand(seed);
+
+    /* Initialize msys mbufs. */
+    rc = os_mempool_init(&blecent_mbuf_mpool, MBUF_NUM_MBUFS,
+                         MBUF_MEMBLOCK_SIZE, blecent_mbuf_mpool_data,
+                         "blecent_mbuf_data");
+    assert(rc == 0);
+
+    rc = os_mbuf_pool_init(&blecent_mbuf_pool, &blecent_mbuf_mpool,
+                           MBUF_MEMBLOCK_SIZE, MBUF_NUM_MBUFS);
+    assert(rc == 0);
+
+    rc = os_msys_register(&blecent_mbuf_pool);
+    assert(rc == 0);
+
+    /* Initialize the console (for log output). */
+    rc = console_init(NULL);
+    assert(rc == 0);
+
+    /* Initialize the logging system. */
+    log_init();
+    log_console_handler_init(&blecent_log_console_handler);
+    log_register("blecent", &blecent_log, &blecent_log_console_handler);
+
+    os_task_init(&blecent_task, "blecent", blecent_task_handler,
+                 NULL, BLECENT_TASK_PRIO, OS_WAIT_FOREVER,
+                 blecent_stack, BLECENT_STACK_SIZE);
+
+    /* Initialize the eventq for the application task. */
+    os_eventq_init(&blecent_evq);
+
+    /* Initialize the BLE LL */
+    rc = ble_ll_init(BLE_LL_TASK_PRI, MBUF_NUM_MBUFS, BLE_MBUF_PAYLOAD_SIZE);
+    assert(rc == 0);
+
+    /* Configure the host. */
+    cfg = ble_hs_cfg_dflt;
+    cfg.max_hci_bufs = 3;
+    cfg.max_connections = 1;
+    cfg.max_gattc_procs = 5;
+    cfg.max_l2cap_chans = 3;
+    cfg.max_l2cap_sig_procs = 1;
+    cfg.sm_bonding = 1;
+    cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC;
+    cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC;
+    cfg.store_read_cb = ble_store_ram_read;
+    cfg.store_write_cb = ble_store_ram_write;
+
+    /* Populate config with the required GATT server settings. */
+    cfg.max_attrs = 0;
+    cfg.max_services = 0;
+    cfg.max_client_configs = 0;
+    ble_svc_gap_init(&cfg);
+    ble_svc_gatt_init(&cfg);
+
+    /* Initialize the BLE host. */
+    rc = ble_hs_init(&blecent_evq, &cfg);
+    assert(rc == 0);
+
+    rc = peer_init(cfg.max_connections, 64, 64, 64);
+    assert(rc == 0);
+
+    /* Register GATT attributes (services, characteristics, and
+     * descriptors).
+     */
+    ble_svc_gap_register();
+    ble_svc_gatt_register();
+
+    /* Set the default device name. */
+    rc = ble_svc_gap_device_name_set("nimble-blecent");
+    assert(rc == 0);
+
+    /* Start the OS */
+    os_start();
+
+    /* os start should never return. If it does, this should be an error */
+    assert(0);
+
+    return 0;
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/b236e398/apps/blecent/src/misc.c
----------------------------------------------------------------------
diff --git a/apps/blecent/src/misc.c b/apps/blecent/src/misc.c
new file mode 100644
index 0000000..8371b14
--- /dev/null
+++ b/apps/blecent/src/misc.c
@@ -0,0 +1,220 @@
+/**
+ * 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "blecent.h"
+
+/**
+ * Utility function to log an array of bytes.
+ */
+void
+print_bytes(const uint8_t *bytes, int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++) {
+        BLECENT_LOG(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
+    }
+}
+
+char *
+addr_str(const void *addr)
+{
+    static char buf[6 * 2 + 5 + 1];
+    const uint8_t *u8p;
+
+    u8p = addr;
+    sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
+            u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
+
+    return buf;
+}
+
+void
+print_uuid(const void *uuid128)
+{
+    uint16_t uuid16;
+    const uint8_t *u8p;
+
+    uuid16 = ble_uuid_128_to_16(uuid128);
+    if (uuid16 != 0) {
+        BLECENT_LOG(DEBUG, "0x%04x", uuid16);
+        return;
+    }
+
+    u8p = uuid128;
+
+    /* 00001101-0000-1000-8000-00805f9b34fb */
+    BLECENT_LOG(DEBUG, "%02x%02x%02x%02x-", u8p[15], u8p[14], u8p[13],
+                u8p[12]);
+    BLECENT_LOG(DEBUG, "%02x%02x-%02x%02x-", u8p[11], u8p[10], u8p[9], u8p[8]);
+    BLECENT_LOG(DEBUG, "%02x%02x%02x%02x%02x%02x%02x%02x",
+                   u8p[7], u8p[6], u8p[5], u8p[4],
+                   u8p[3], u8p[2], u8p[1], u8p[0]);
+}
+
+/**
+ * Logs information about a connection to the console.
+ */
+void
+print_conn_desc(const struct ble_gap_conn_desc *desc)
+{
+    BLECENT_LOG(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
+                desc->conn_handle, desc->our_ota_addr_type,
+                addr_str(desc->our_ota_addr));
+    BLECENT_LOG(DEBUG, "our_id_addr_type=%d our_id_addr=%s ",
+                desc->our_id_addr_type, addr_str(desc->our_id_addr));
+    BLECENT_LOG(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ",
+                desc->peer_ota_addr_type, addr_str(desc->peer_ota_addr));
+    BLECENT_LOG(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ",
+                desc->peer_id_addr_type, addr_str(desc->peer_id_addr));
+    BLECENT_LOG(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+                "encrypted=%d authenticated=%d bonded=%d\n",
+                desc->conn_itvl, desc->conn_latency,
+                desc->supervision_timeout,
+                desc->sec_state.encrypted,
+                desc->sec_state.authenticated,
+                desc->sec_state.bonded);
+}
+
+
+void
+print_adv_fields(const struct ble_hs_adv_fields *fields)
+{
+    char s[BLE_HCI_MAX_ADV_DATA_LEN];
+    const uint8_t *u8p;
+    int i;
+
+    if (fields->flags_is_present) {
+        BLECENT_LOG(DEBUG, "    flags=0x%02x\n", fields->flags);
+    }
+
+    if (fields->uuids16 != NULL) {
+        BLECENT_LOG(DEBUG, "    uuids16(%scomplete)=",
+                    fields->uuids16_is_complete ? "" : "in");
+        for (i = 0; i < fields->num_uuids16; i++) {
+            BLECENT_LOG(DEBUG, "0x%04x ", fields->uuids16[i]);
+        }
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->uuids32 != NULL) {
+        BLECENT_LOG(DEBUG, "    uuids32(%scomplete)=",
+                    fields->uuids32_is_complete ? "" : "in");
+        for (i = 0; i < fields->num_uuids32; i++) {
+            BLECENT_LOG(DEBUG, "0x%08x ", fields->uuids32[i]);
+        }
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->uuids128 != NULL) {
+        BLECENT_LOG(DEBUG, "    uuids128(%scomplete)=",
+                    fields->uuids128_is_complete ? "" : "in");
+        u8p = fields->uuids128;
+        for (i = 0; i < fields->num_uuids128; i++) {
+            print_uuid(u8p);
+            BLECENT_LOG(DEBUG, " ");
+            u8p += 16;
+        }
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->name != NULL) {
+        assert(fields->name_len < sizeof s - 1);
+        memcpy(s, fields->name, fields->name_len);
+        s[fields->name_len] = '\0';
+        BLECENT_LOG(DEBUG, "    name(%scomplete)=%s\n",
+                    fields->name_is_complete ? "" : "in", s);
+    }
+
+    if (fields->tx_pwr_lvl_is_present) {
+        BLECENT_LOG(DEBUG, "    tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
+    }
+
+    if (fields->device_class != NULL) {
+        BLECENT_LOG(DEBUG, "    device_class=");
+        print_bytes(fields->device_class, BLE_HS_ADV_DEVICE_CLASS_LEN);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->slave_itvl_range != NULL) {
+        BLECENT_LOG(DEBUG, "    slave_itvl_range=");
+        print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->svc_data_uuid16 != NULL) {
+        BLECENT_LOG(DEBUG, "    svc_data_uuid16=");
+        print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->public_tgt_addr != NULL) {
+        BLECENT_LOG(DEBUG, "    public_tgt_addr=");
+        u8p = fields->public_tgt_addr;
+        for (i = 0; i < fields->num_public_tgt_addrs; i++) {
+            BLECENT_LOG(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
+            u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
+        }
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->appearance_is_present) {
+        BLECENT_LOG(DEBUG, "    appearance=0x%04x\n", fields->appearance);
+    }
+
+    if (fields->adv_itvl_is_present) {
+        BLECENT_LOG(DEBUG, "    adv_itvl=0x%04x\n", fields->adv_itvl);
+    }
+
+    if (fields->le_addr != NULL) {
+        BLECENT_LOG(DEBUG, "    le_addr=%s\n", addr_str(fields->le_addr));
+    }
+
+    if (fields->le_role_is_present) {
+        BLECENT_LOG(DEBUG, "    le_role=0x%02x\n", fields->le_role);
+    }
+
+    if (fields->svc_data_uuid32 != NULL) {
+        BLECENT_LOG(DEBUG, "    svc_data_uuid32=");
+        print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->svc_data_uuid128 != NULL) {
+        BLECENT_LOG(DEBUG, "    svc_data_uuid128=");
+        print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->uri != NULL) {
+        BLECENT_LOG(DEBUG, "    uri=");
+        print_bytes(fields->uri, fields->uri_len);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+
+    if (fields->mfg_data != NULL) {
+        BLECENT_LOG(DEBUG, "    mfg_data=");
+        print_bytes(fields->mfg_data, fields->mfg_data_len);
+        BLECENT_LOG(DEBUG, "\n");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/b236e398/apps/blecent/src/peer.c
----------------------------------------------------------------------
diff --git a/apps/blecent/src/peer.c b/apps/blecent/src/peer.c
new file mode 100644
index 0000000..4520b1b
--- /dev/null
+++ b/apps/blecent/src/peer.c
@@ -0,0 +1,772 @@
+/**
+ * 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 <assert.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "blecent.h"
+
+static void *peer_svc_mem;
+static struct os_mempool peer_svc_pool;
+
+static void *peer_chr_mem;
+static struct os_mempool peer_chr_pool;
+
+static void *peer_dsc_mem;
+static struct os_mempool peer_dsc_pool;
+
+static void *peer_mem;
+static struct os_mempool peer_pool;
+static SLIST_HEAD(, peer) peers;
+
+static struct peer_svc *
+peer_svc_find_range(struct peer *peer, uint16_t attr_handle);
+static struct peer_svc *
+peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
+              struct peer_svc **out_prev);
+int
+peer_svc_is_empty(const struct peer_svc *svc);
+
+uint16_t
+chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr);
+int
+chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr);
+static struct peer_chr *
+peer_chr_find(const struct peer_svc *svc, uint16_t chr_def_handle,
+              struct peer_chr **out_prev);
+static void
+peer_disc_chrs(struct peer *peer);
+
+static int
+peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                uint16_t chr_def_handle, const struct ble_gatt_dsc *dsc,
+                void *arg);
+
+static struct peer *
+peer_find(uint16_t conn_handle)
+{
+    struct peer *peer;
+
+    SLIST_FOREACH(peer, &peers, next) {
+        if (peer->conn_handle == conn_handle) {
+            return peer;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+peer_full_disc_complete(struct peer *peer, int rc)
+{
+    peer->disc_prev_chr_val = 0;
+
+    /* Notify caller that discovery has completed. */
+    if (peer->disc_cb != NULL) {
+        peer->disc_cb(peer, rc, peer->disc_cb_arg);
+    }
+}
+
+static struct peer_dsc *
+peer_dsc_find_prev(const struct peer_chr *chr, uint16_t dsc_handle)
+{
+    struct peer_dsc *prev;
+    struct peer_dsc *dsc;
+
+    prev = NULL;
+    SLIST_FOREACH(dsc, &chr->dscs, next) {
+        if (dsc->dsc.handle >= dsc_handle) {
+            break;
+        }
+
+        prev = dsc;
+    }
+
+    return prev;
+}
+
+static struct peer_dsc *
+peer_dsc_find(const struct peer_chr *chr, uint16_t dsc_handle,
+              struct peer_dsc **out_prev)
+{
+    struct peer_dsc *prev;
+    struct peer_dsc *dsc;
+
+    prev = peer_dsc_find_prev(chr, dsc_handle);
+    if (prev == NULL) {
+        dsc = SLIST_FIRST(&chr->dscs);
+    } else {
+        dsc = SLIST_NEXT(prev, next);
+    }
+
+    if (dsc != NULL && dsc->dsc.handle != dsc_handle) {
+        dsc = NULL;
+    }
+
+    if (out_prev != NULL) {
+        *out_prev = prev;
+    }
+    return dsc;
+}
+
+static struct peer_dsc *
+peer_dsc_add(struct peer *peer, uint16_t chr_val_handle,
+             const struct ble_gatt_dsc *gatt_dsc)
+{
+    struct peer_dsc *prev;
+    struct peer_dsc *dsc;
+    struct peer_svc *svc;
+    struct peer_chr *chr;
+
+    svc = peer_svc_find_range(peer, chr_val_handle);
+    if (svc == NULL) {
+        /* Can't find service for discovered descriptor; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return NULL;
+    }
+
+    chr = peer_chr_find(svc, chr_val_handle, NULL);
+    if (chr == NULL) {
+        /* Can't find characteristic for discovered descriptor; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return NULL;
+    }
+
+    dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev);
+    if (dsc != NULL) {
+        /* Descriptor already discovered. */
+        return dsc;
+    }
+
+    dsc = os_memblock_get(&peer_dsc_pool);
+    if (dsc == NULL) {
+        /* Out of memory. */
+        return NULL;
+    }
+    memset(dsc, 0, sizeof *dsc);
+
+    dsc->dsc = *gatt_dsc;
+
+    if (prev == NULL) {
+        SLIST_INSERT_HEAD(&chr->dscs, dsc, next);
+    } else {
+        SLIST_NEXT(prev, next) = dsc;
+    }
+
+    return dsc;
+}
+
+static void
+peer_disc_dscs(struct peer *peer)
+{
+    struct peer_chr *chr;
+    struct peer_svc *svc;
+    int rc;
+
+    /* Search through the list of discovered characteristics for the first
+     * characteristic that contains undiscovered descriptors.  Then, discover
+     * all descriptors belonging to that characteristic.
+     */
+    SLIST_FOREACH(svc, &peer->svcs, next) {
+        SLIST_FOREACH(chr, &svc->chrs, next) {
+            if (!chr_is_empty(svc, chr) &&
+                SLIST_EMPTY(&chr->dscs) &&
+                peer->disc_prev_chr_val <= chr->chr.def_handle) {
+
+                rc = ble_gattc_disc_all_dscs(peer->conn_handle,
+                                             chr->chr.val_handle,
+                                             chr_end_handle(svc, chr),
+                                             peer_dsc_disced, peer);
+                if (rc != 0) {
+                    peer_full_disc_complete(peer, rc);
+                }
+
+                peer->disc_prev_chr_val = chr->chr.val_handle;
+                return;
+            }
+        }
+    }
+
+    /* All descriptors discovered. */
+    peer_full_disc_complete(peer, 0);
+}
+
+static int
+peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
+                void *arg)
+{
+    struct peer *peer;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+
+    switch (error->status) {
+    case 0:
+        peer_dsc_add(peer, chr_val_handle, dsc);
+        break;
+
+    case BLE_HS_EDONE:
+        if (peer->disc_prev_chr_val > 0) {
+            peer_disc_dscs(peer);
+        }
+        break;
+
+    default:
+        /* Error. */
+        break;
+    }
+
+    return 0;
+}
+
+uint16_t
+chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr)
+{
+    const struct peer_chr *next_chr;
+
+    next_chr = SLIST_NEXT(chr, next);
+    if (next_chr != NULL) {
+        return next_chr->chr.def_handle - 1;
+    } else {
+        return svc->svc.end_handle;
+    }
+}
+
+int
+chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr)
+{
+    return chr_end_handle(svc, chr) <= chr->chr.val_handle;
+}
+
+static struct peer_chr *
+peer_chr_find_prev(const struct peer_svc *svc, uint16_t chr_val_handle)
+{
+    struct peer_chr *prev;
+    struct peer_chr *chr;
+
+    prev = NULL;
+    SLIST_FOREACH(chr, &svc->chrs, next) {
+        if (chr->chr.val_handle >= chr_val_handle) {
+            break;
+        }
+
+        prev = chr;
+    }
+
+    return prev;
+}
+
+static struct peer_chr *
+peer_chr_find(const struct peer_svc *svc, uint16_t chr_val_handle,
+              struct peer_chr **out_prev)
+{
+    struct peer_chr *prev;
+    struct peer_chr *chr;
+
+    prev = peer_chr_find_prev(svc, chr_val_handle);
+    if (prev == NULL) {
+        chr = SLIST_FIRST(&svc->chrs);
+    } else {
+        chr = SLIST_NEXT(prev, next);
+    }
+
+    if (chr != NULL && chr->chr.val_handle != chr_val_handle) {
+        chr = NULL;
+    }
+
+    if (out_prev != NULL) {
+        *out_prev = prev;
+    }
+    return chr;
+}
+
+static void
+peer_chr_delete(struct peer_chr *chr)
+{
+    struct peer_dsc *dsc;
+
+    while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) {
+        SLIST_REMOVE_HEAD(&chr->dscs, next);
+        os_memblock_put(&peer_dsc_pool, dsc);
+    }
+
+    os_memblock_put(&peer_chr_pool, chr);
+}
+
+static struct peer_chr *
+peer_chr_add(struct peer *peer,  uint16_t svc_start_handle,
+             const struct ble_gatt_chr *gatt_chr)
+{
+    struct peer_chr *prev;
+    struct peer_chr *chr;
+    struct peer_svc *svc;
+
+    svc = peer_svc_find(peer, svc_start_handle, NULL);
+    if (svc == NULL) {
+        /* Can't find service for discovered characteristic; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return NULL;
+    }
+
+    chr = peer_chr_find(svc, gatt_chr->def_handle, &prev);
+    if (chr != NULL) {
+        /* Characteristic already discovered. */
+        return chr;
+    }
+
+    chr = os_memblock_get(&peer_chr_pool);
+    if (chr == NULL) {
+        /* Out of memory. */
+        return NULL;
+    }
+    memset(chr, 0, sizeof *chr);
+
+    chr->chr = *gatt_chr;
+
+    if (prev == NULL) {
+        SLIST_INSERT_HEAD(&svc->chrs, chr, next);
+    } else {
+        SLIST_NEXT(prev, next) = chr;
+    }
+
+    return chr;
+}
+
+static int
+peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                const struct ble_gatt_chr *chr, void *arg)
+{
+    struct peer *peer;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+
+    switch (error->status) {
+    case 0:
+        peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr);
+        break;
+
+    case BLE_HS_EDONE:
+        if (peer->disc_prev_chr_val > 0) {
+            peer_disc_chrs(peer);
+        }
+        break;
+
+    default:
+        /* Error. */
+        break;
+    }
+
+    return 0;
+}
+
+static void
+peer_disc_chrs(struct peer *peer)
+{
+    struct peer_svc *svc;
+    int rc;
+
+    /* Search through the list of discovered service for the first service that
+     * contains undiscovered characteristics.  Then, discover all
+     * characteristics belonging to that service.
+     */
+    SLIST_FOREACH(svc, &peer->svcs, next) {
+        if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) {
+            peer->cur_svc = svc;
+            rc = ble_gattc_disc_all_chrs(peer->conn_handle,
+                                         svc->svc.start_handle,
+                                         svc->svc.end_handle,
+                                         peer_chr_disced, peer);
+            if (rc != 0) {
+                peer_full_disc_complete(peer, rc);
+            }
+            return;
+        }
+    }
+
+    /* All characteristics discovered. */
+    peer_disc_dscs(peer);
+}
+
+int
+peer_svc_is_empty(const struct peer_svc *svc)
+{
+    return svc->svc.end_handle < svc->svc.start_handle;
+}
+
+static struct peer_svc *
+peer_svc_find_prev(struct peer *peer, uint16_t svc_start_handle)
+{
+    struct peer_svc *prev;
+    struct peer_svc *svc;
+
+    prev = NULL;
+    SLIST_FOREACH(svc, &peer->svcs, next) {
+        if (svc->svc.start_handle >= svc_start_handle) {
+            break;
+        }
+
+        prev = svc;
+    }
+
+    return prev;
+}
+
+static struct peer_svc *
+peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
+              struct peer_svc **out_prev)
+{
+    struct peer_svc *prev;
+    struct peer_svc *svc;
+
+    prev = peer_svc_find_prev(peer, svc_start_handle);
+    if (prev == NULL) {
+        svc = SLIST_FIRST(&peer->svcs);
+    } else {
+        svc = SLIST_NEXT(prev, next);
+    }
+
+    if (svc != NULL && svc->svc.start_handle != svc_start_handle) {
+        svc = NULL;
+    }
+
+    if (out_prev != NULL) {
+        *out_prev = prev;
+    }
+    return svc;
+}
+
+static struct peer_svc *
+peer_svc_find_range(struct peer *peer, uint16_t attr_handle)
+{
+    struct peer_svc *svc;
+
+    SLIST_FOREACH(svc, &peer->svcs, next) {
+        if (svc->svc.start_handle <= attr_handle &&
+            svc->svc.end_handle >= attr_handle) {
+
+            return svc;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_svc *
+peer_svc_find_uuid(const struct peer *peer, const uint8_t *uuid128)
+{
+    const struct peer_svc *svc;
+
+    SLIST_FOREACH(svc, &peer->svcs, next) {
+        if (memcmp(svc->svc.uuid128, uuid128, 16) == 0) {
+            return svc;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_chr *
+peer_chr_find_uuid(const struct peer *peer, const uint8_t *svc_uuid128,
+                   const uint8_t *chr_uuid128)
+{
+    const struct peer_svc *svc;
+    const struct peer_chr *chr;
+
+    svc = peer_svc_find_uuid(peer, svc_uuid128);
+    if (svc == NULL) {
+        return NULL;
+    }
+
+    SLIST_FOREACH(chr, &svc->chrs, next) {
+        if (memcmp(chr->chr.uuid128, chr_uuid128, 16) == 0) {
+            return chr;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_dsc *
+peer_dsc_find_uuid(const struct peer *peer, const uint8_t *svc_uuid128,
+                   const uint8_t *chr_uuid128, const uint8_t *dsc_uuid128)
+{
+    const struct peer_chr *chr;
+    const struct peer_dsc *dsc;
+
+    chr = peer_chr_find_uuid(peer, svc_uuid128, chr_uuid128);
+    if (chr == NULL) {
+        return NULL;
+    }
+
+    SLIST_FOREACH(dsc, &chr->dscs, next) {
+        if (memcmp(dsc->dsc.uuid128, dsc_uuid128, 16) == 0) {
+            return dsc;
+        }
+    }
+
+    return NULL;
+}
+
+static struct peer_svc *
+peer_svc_add(struct peer *peer, const struct ble_gatt_svc *gatt_svc)
+{
+    struct peer_svc *prev;
+    struct peer_svc *svc;
+
+    svc = peer_svc_find(peer, gatt_svc->start_handle, &prev);
+    if (svc != NULL) {
+        /* Service already discovered. */
+        return svc;
+    }
+
+    svc = os_memblock_get(&peer_svc_pool);
+    if (svc == NULL) {
+        /* Out of memory. */
+        return NULL;
+    }
+    memset(svc, 0, sizeof *svc);
+
+    svc->svc = *gatt_svc;
+    SLIST_INIT(&svc->chrs);
+
+    if (prev == NULL) {
+        SLIST_INSERT_HEAD(&peer->svcs, svc, next);
+    } else {
+        SLIST_INSERT_AFTER(prev, svc, next);
+    }
+
+    return svc;
+}
+
+static void
+peer_svc_delete(struct peer_svc *svc)
+{
+    struct peer_chr *chr;
+
+    while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) {
+        SLIST_REMOVE_HEAD(&svc->chrs, next);
+        peer_chr_delete(chr);
+    }
+
+    os_memblock_put(&peer_svc_pool, svc);
+}
+
+static int
+peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
+                const struct ble_gatt_svc *service, void *arg)
+{
+    struct peer *peer;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+
+    switch (error->status) {
+    case 0:
+        peer_svc_add(peer, service);
+        break;
+
+    case BLE_HS_EDONE:
+        if (peer->disc_prev_chr_val > 0) {
+            peer_disc_chrs(peer);
+        }
+        break;
+
+    default:
+        /* Error */
+        break;
+    }
+
+    return 0;
+}
+
+
+int
+peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg)
+{
+    struct peer_svc *svc;
+    struct peer *peer;
+    int rc;
+
+    peer = peer_find(conn_handle);
+    if (peer == NULL) {
+        return BLE_HS_ENOTCONN;
+    }
+
+    /* Undiscover everything first. */
+    while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
+        SLIST_REMOVE_HEAD(&peer->svcs, next);
+        peer_svc_delete(svc);
+    }
+
+    peer->disc_prev_chr_val = 1;
+    peer->disc_cb = disc_cb;
+    peer->disc_cb_arg = disc_cb_arg;
+
+    rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+int
+peer_delete(uint16_t conn_handle)
+{
+    struct peer *peer;
+    int rc;
+
+    peer = peer_find(conn_handle);
+    if (peer == NULL) {
+        return BLE_HS_ENOTCONN;
+    }
+
+    SLIST_REMOVE(&peers, peer, peer, next);
+
+    rc = os_memblock_put(&peer_pool, peer);
+    if (rc != 0) {
+        return BLE_HS_EOS;
+    }
+
+    return 0;
+}
+
+int
+peer_add(uint16_t conn_handle)
+{
+    struct peer *peer;
+
+    /* Make sure the connection handle is unique. */
+    peer = peer_find(conn_handle);
+    if (peer != NULL) {
+        return BLE_HS_EALREADY;
+    }
+
+    peer = os_memblock_get(&peer_pool);
+    if (peer == NULL) {
+        /* Out of memory. */
+        return BLE_HS_ENOMEM;
+    }
+
+    memset(peer, 0, sizeof *peer);
+    peer->conn_handle = conn_handle;
+
+    SLIST_INSERT_HEAD(&peers, peer, next);
+
+    return 0;
+}
+
+static void
+peer_free_mem(void)
+{
+    free(peer_mem);
+    peer_mem = NULL;
+
+    free(peer_svc_mem);
+    peer_svc_mem = NULL;
+
+    free(peer_chr_mem);
+    peer_chr_mem = NULL;
+
+    free(peer_dsc_mem);
+    peer_dsc_mem = NULL;
+}
+
+int
+peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs)
+{
+    int rc;
+
+    /* Free memory first in case this function gets called more than once. */
+    peer_free_mem();
+
+    peer_mem = malloc(
+        OS_MEMPOOL_BYTES(max_peers, sizeof (struct peer)));
+    if (peer_mem == NULL) {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_pool, max_peers,
+                         sizeof (struct peer), peer_mem,
+                         "peer_pool");
+    if (rc != 0) {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_svc_mem = malloc(
+        OS_MEMPOOL_BYTES(max_svcs, sizeof (struct peer_svc)));
+    if (peer_svc_mem == NULL) {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_svc_pool, max_svcs,
+                         sizeof (struct peer_svc), peer_svc_mem,
+                         "peer_svc_pool");
+    if (rc != 0) {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_chr_mem = malloc(
+        OS_MEMPOOL_BYTES(max_chrs, sizeof (struct peer_chr)));
+    if (peer_chr_mem == NULL) {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_chr_pool, max_chrs,
+                         sizeof (struct peer_chr), peer_chr_mem,
+                         "peer_chr_pool");
+    if (rc != 0) {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_dsc_mem = malloc(
+        OS_MEMPOOL_BYTES(max_dscs, sizeof (struct peer_dsc)));
+    if (peer_dsc_mem == NULL) {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_dsc_pool, max_dscs,
+                         sizeof (struct peer_dsc), peer_dsc_mem,
+                         "peer_dsc_pool");
+    if (rc != 0) {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    return 0;
+
+err:
+    peer_free_mem();
+    return rc;
+}