MYNEWT-353 BLE Host - Timeout for ATT queued write

If a queued write is only partially received, and 30 seconds has passed
since the last prepare write was received, a timeout occurs.  When this
happens, the connection is terminated.


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/19814398
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/tree/19814398
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/diff/19814398

Branch: refs/heads/develop
Commit: 198143989112b3b55f1d50619c938e3b34fc63f9
Parents: defd62a
Author: Christopher Collins <ccoll...@apache.org>
Authored: Thu Dec 1 18:25:46 2016 -0800
Committer: Christopher Collins <ccoll...@apache.org>
Committed: Thu Dec 1 18:25:46 2016 -0800

----------------------------------------------------------------------
 net/nimble/host/src/ble_att_priv.h          |  4 +-
 net/nimble/host/src/ble_att_svr.c           | 40 ++++++++++++++++
 net/nimble/host/src/ble_gap.c               | 10 +++-
 net/nimble/host/src/ble_hs.c                |  2 +-
 net/nimble/host/src/ble_hs_conn.c           | 50 +++++++++++++++----
 net/nimble/host/src/ble_hs_priv.h           |  8 ++++
 net/nimble/host/src/ble_l2cap.c             |  6 ++-
 net/nimble/host/src/ble_l2cap_priv.h        |  2 -
 net/nimble/host/syscfg.yml                  | 13 +++++
 net/nimble/host/test/src/ble_att_svr_test.c | 61 +++++++++++++++++++++++-
 net/nimble/host/test/src/ble_l2cap_test.c   |  8 ++--
 11 files changed, 184 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_att_priv.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_att_priv.h 
b/net/nimble/host/src/ble_att_priv.h
index c10418a..5a0eb35 100644
--- a/net/nimble/host/src/ble_att_priv.h
+++ b/net/nimble/host/src/ble_att_priv.h
@@ -120,7 +120,7 @@ SLIST_HEAD(ble_att_prep_entry_list, ble_att_prep_entry);
 struct ble_att_svr_conn {
     /** This list is sorted by attribute handle ID. */
     struct ble_att_prep_entry_list basc_prep_list;
-    uint32_t basc_prep_write_rx_time;
+    os_time_t basc_prep_timeout_at;
 };
 
 /**
@@ -187,6 +187,8 @@ ble_att_svr_find_by_uuid(struct ble_att_svr_entry *start_at,
 uint16_t ble_att_svr_prev_handle(void);
 int ble_att_svr_rx_mtu(uint16_t conn_handle, struct os_mbuf **rxom);
 struct ble_att_svr_entry *ble_att_svr_find_by_handle(uint16_t handle_id);
+int32_t ble_att_svr_ticks_until_tmo(const struct ble_att_svr_conn *svr,
+                                    os_time_t now);
 int ble_att_svr_rx_find_info(uint16_t conn_handle, struct os_mbuf **rxom);
 int ble_att_svr_rx_find_type_value(uint16_t conn_handle,
                                    struct os_mbuf **rxom);

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_att_svr.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_att_svr.c 
b/net/nimble/host/src/ble_att_svr.c
index 46daf87..25aa341 100644
--- a/net/nimble/host/src/ble_att_svr.c
+++ b/net/nimble/host/src/ble_att_svr.c
@@ -291,6 +291,39 @@ ble_att_svr_check_perms(uint16_t conn_handle, int is_read,
 }
 
 /**
+ * Calculates the number of ticks until a queued write times out on the
+ * specified ATT server.  If this server is not in the process of receiving a
+ * queued write, then BLE_HS_FOREVER is returned.  If a timeout just occurred,
+ * 0 is returned.
+ *
+ * @param svr                   The ATT server to check.
+ * @param now                   The current OS time.
+ *
+ * @return                      The number of ticks until the current queued
+ *                                  write times out.  
+ */
+int32_t
+ble_att_svr_ticks_until_tmo(const struct ble_att_svr_conn *svr, os_time_t now)
+{
+#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO == 0
+    return BLE_HS_FOREVER;
+#endif
+
+    int32_t time_diff;
+
+    if (SLIST_EMPTY(&svr->basc_prep_list)) {
+        return BLE_HS_FOREVER;
+    }
+
+    time_diff = svr->basc_prep_timeout_at - now;
+    if (time_diff < 0) {
+        return 0;
+    }
+
+    return time_diff;
+}
+
+/**
  * Allocates an mbuf to be used for an ATT response.  If an mbuf cannot be
  * allocated, the received request mbuf is reused for the error response.
  */
@@ -2377,6 +2410,13 @@ ble_att_svr_insert_prep_entry(uint16_t conn_handle,
         SLIST_INSERT_AFTER(prep_prev, prep_entry, bape_next);
     }
 
+#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO != 0
+    conn->bhc_att_svr.basc_prep_timeout_at =
+        os_time_get() + BLE_HS_ATT_SVR_QUEUED_WRITE_TMO;
+
+    ble_hs_timer_resched();
+#endif
+
     return 0;
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_gap.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_gap.c b/net/nimble/host/src/ble_gap.c
index 863fc5d..fcfdc57 100644
--- a/net/nimble/host/src/ble_gap.c
+++ b/net/nimble/host/src/ble_gap.c
@@ -2549,17 +2549,24 @@ int
 ble_gap_terminate(uint16_t conn_handle, uint8_t hci_reason)
 {
     uint8_t buf[BLE_HCI_CMD_HDR_LEN + BLE_HCI_DISCONNECT_CMD_LEN];
+    struct ble_hs_conn *conn;
     int rc;
 
     STATS_INC(ble_gap_stats, terminate);
 
     ble_hs_lock();
 
-    if (!ble_hs_conn_exists(conn_handle)) {
+    conn = ble_hs_conn_find(conn_handle);
+    if (conn == NULL) {
         rc = BLE_HS_ENOTCONN;
         goto done;
     }
 
+    if (conn->bhc_flags & BLE_HS_CONN_F_TERMINATING) {
+        rc = BLE_HS_EALREADY;
+        goto done;
+    }
+
     BLE_HS_LOG(INFO, "GAP procedure initiated: terminate connection; "
                      "conn_handle=%d hci_reason=%d\n",
                conn_handle, hci_reason);
@@ -2571,6 +2578,7 @@ ble_gap_terminate(uint16_t conn_handle, uint8_t 
hci_reason)
         goto done;
     }
 
+    conn->bhc_flags |= BLE_HS_CONN_F_TERMINATING;
     rc = 0;
 
 done:

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/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 c5799ee..0d35a88 100644
--- a/net/nimble/host/src/ble_hs.c
+++ b/net/nimble/host/src/ble_hs.c
@@ -405,7 +405,7 @@ ble_hs_enqueue_hci_event(uint8_t *hci_evt)
         ble_hci_trans_buf_free(hci_evt);
     } else {
         ev->ev_queued = 0;
-        ev->ev_cb = ble_hs_event_rx_hci_ev,
+        ev->ev_cb = ble_hs_event_rx_hci_ev;
         ev->ev_arg = hci_evt;
         os_eventq_put(ble_hs_evq_get(), ev);
     }

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/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 9a6517e..e92f9e5 100644
--- a/net/nimble/host/src/ble_hs_conn.c
+++ b/net/nimble/host/src/ble_hs_conn.c
@@ -381,6 +381,13 @@ ble_hs_conn_addrs(const struct ble_hs_conn *conn,
 int32_t
 ble_hs_conn_timer(void)
 {
+    /* If there are no timeouts configured, then there is nothing to check. */
+#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) == 0 && \
+    BLE_HS_ATT_SVR_QUEUED_WRITE_TMO == 0
+     
+    return BLE_HS_FOREVER;
+#endif
+
     struct ble_hs_conn *conn;
     os_time_t now;
     int32_t next_exp_in;
@@ -400,20 +407,44 @@ ble_hs_conn_timer(void)
      * 2. Otherwise, determine when the next timeout will occur.
      */
     SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
-        /* Check each connection's rx fragment timer.  If too much time passes
-         * after a partial packet is received, the connection is terminated.
-         */
-        if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING) &&
-            conn->bhc_rx_chan != NULL) {
-
-            time_diff = conn->bhc_rx_timeout - now;
+        if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) {
+
+#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
+            /* Check each connection's rx fragment timer.  If too much time
+             * passes after a partial packet is received, the connection is
+             * terminated.
+             */
+            if (conn->bhc_rx_chan != NULL) {
+                time_diff = conn->bhc_rx_timeout - now;
+
+                if (time_diff <= 0) {
+                    /* ACL reassembly has timed out.  Remember the connection
+                     * handle so it can be terminated after the mutex is
+                     * unlocked.
+                     */
+                    conn_handle = conn->bhc_handle;
+                    break;
+                }
+
+                /* Determine if this connection is the soonest to time out. */
+                if (time_diff < next_exp_in) {
+                    next_exp_in = time_diff;
+                }
+            }
+#endif
 
+#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
+            /* Check each connection's rx queued write timer.  If too much
+             * time passes after a prep write is received, the queue is
+             * cleared.
+             */
+            time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
             if (time_diff <= 0) {
                 /* ACL reassembly has timed out.  Remember the connection
-                 * handle so it can be terminated after the mutex is unlocked.
+                 * handle so it can be terminated after the mutex is
+                 * unlocked.
                  */
                 conn_handle = conn->bhc_handle;
-                conn->bhc_flags |= BLE_HS_CONN_F_TERMINATING;
                 break;
             }
 
@@ -421,6 +452,7 @@ ble_hs_conn_timer(void)
             if (time_diff < next_exp_in) {
                 next_exp_in = time_diff;
             }
+#endif
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_hs_priv.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_hs_priv.h 
b/net/nimble/host/src/ble_hs_priv.h
index 905386b..2cc8b40 100644
--- a/net/nimble/host/src/ble_hs_priv.h
+++ b/net/nimble/host/src/ble_hs_priv.h
@@ -22,6 +22,7 @@
 
 #include <assert.h>
 #include <inttypes.h>
+#include "os/os_time.h"
 #include "ble_att_cmd_priv.h"
 #include "ble_att_priv.h"
 #include "ble_gap_priv.h"
@@ -64,6 +65,13 @@ struct os_event;
 #define BLE_HS_MAX_CONNECTIONS 0
 #endif
 
+#if !MYNEWT_VAL(BLE_ATT_SVR_QUEUED_WRITE)
+#define BLE_HS_ATT_SVR_QUEUED_WRITE_TMO 0
+#else
+#define BLE_HS_ATT_SVR_QUEUED_WRITE_TMO \
+    MYNEWT_VAL(BLE_ATT_SVR_QUEUED_WRITE_TMO)
+#endif
+
 STATS_SECT_START(ble_hs_stats)
     STATS_SECT_ENTRY(conn_create)
     STATS_SECT_ENTRY(conn_delete)

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_l2cap.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_l2cap.c b/net/nimble/host/src/ble_l2cap.c
index aa37784..7f66b57 100644
--- a/net/nimble/host/src/ble_l2cap.c
+++ b/net/nimble/host/src/ble_l2cap.c
@@ -180,8 +180,12 @@ ble_l2cap_rx_payload(struct ble_hs_conn *conn, struct 
ble_l2cap_chan *chan,
         rc = 0;
     } else {
         /* More fragments remain. */
-        conn->bhc_rx_timeout = os_time_get() + BLE_L2CAP_REASSEM_TIMEOUT;
+#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
+        conn->bhc_rx_timeout =
+            os_time_get() + MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT);
+
         ble_hs_timer_resched();
+#endif
         rc = BLE_HS_EAGAIN;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/src/ble_l2cap_priv.h
----------------------------------------------------------------------
diff --git a/net/nimble/host/src/ble_l2cap_priv.h 
b/net/nimble/host/src/ble_l2cap_priv.h
index 2087e81..79d58a7 100644
--- a/net/nimble/host/src/ble_l2cap_priv.h
+++ b/net/nimble/host/src/ble_l2cap_priv.h
@@ -29,8 +29,6 @@
 extern "C" {
 #endif
 
-#define BLE_L2CAP_REASSEM_TIMEOUT       (30 * OS_TICKS_PER_SEC)
-
 struct ble_hs_conn;
 struct hci_data_hdr;
 

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/syscfg.yml
----------------------------------------------------------------------
diff --git a/net/nimble/host/syscfg.yml b/net/nimble/host/syscfg.yml
index c4bbca9..361b4b5 100644
--- a/net/nimble/host/syscfg.yml
+++ b/net/nimble/host/syscfg.yml
@@ -42,6 +42,12 @@ syscfg.defs:
     BLE_L2CAP_SIG_MAX_PROCS:
         description: 'TBD'
         value: 1
+    BLE_L2CAP_RX_FRAG_TIMEOUT:
+        description: >
+            Expiry time for incoming data packets (ms).  If this much time
+            passes since the previous fragment was received, the connection is
+            terminated.  A value of 0 means no timeout.
+        value: 30000
 
     # Security manager settings.
     BLE_SM:
@@ -204,6 +210,13 @@ syscfg.defs:
             sends a partial write.
         value: 64
 
+    BLE_ATT_SVR_QUEUED_WRITE_TMO:
+        description: >
+            Expiry time for incoming ATT queued writes (ms).  If this much
+            time passes since the previous prepared write was received, the
+            connection is terminated.  A value of 0 means no timeout.
+        value: 30000
+
     # Privacy options.
     BLE_RPA_TIMEOUT:
         description: 'TBD'

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/test/src/ble_att_svr_test.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/test/src/ble_att_svr_test.c 
b/net/nimble/host/test/src/ble_att_svr_test.c
index 5de166b..4a1ea61 100644
--- a/net/nimble/host/test/src/ble_att_svr_test.c
+++ b/net/nimble/host/test/src/ble_att_svr_test.c
@@ -1763,6 +1763,65 @@ TEST_CASE(ble_att_svr_test_notify)
 
 }
 
+TEST_CASE(ble_att_svr_test_prep_write_tmo)
+{
+    int32_t ticks_from_now;
+    uint16_t conn_handle;
+    int rc;
+    int i;
+
+    static uint8_t data[1024];
+
+    conn_handle = ble_att_svr_test_misc_init(205);
+
+    /* Initialize some attribute data. */
+    for (i = 0; i < sizeof data; i++) {
+        data[i] = i;
+    }
+
+    /* Register a writable attribute. */
+    ble_att_svr_test_misc_register_uuid16(0x1234, HA_FLAG_PERM_RW, 1,
+                                          ble_att_svr_test_misc_attr_fn_w_1);
+
+    /* Ensure timer is not set. */
+    ticks_from_now = ble_hs_conn_timer();
+    TEST_ASSERT_FATAL(ticks_from_now == BLE_HS_FOREVER);
+
+    /* Receive a prepare write request. */
+    ble_att_svr_test_misc_prep_write(conn_handle, 1, 0, data, 7, 0);
+
+    /* Ensure timer will expire in 30 seconds. */
+    ticks_from_now = ble_hs_conn_timer();
+    TEST_ASSERT(ticks_from_now == BLE_HS_ATT_SVR_QUEUED_WRITE_TMO);
+
+    /* Almost let the timer expire. */
+    os_time_advance(BLE_HS_ATT_SVR_QUEUED_WRITE_TMO - 1);
+    ticks_from_now = ble_hs_conn_timer();
+    TEST_ASSERT(ticks_from_now == 1);
+
+    /* Receive a second prepare write request. */
+    ble_att_svr_test_misc_prep_write(conn_handle, 1, 7, data + 7, 10, 0);
+
+    /* Ensure timer got reset. */
+    ticks_from_now = ble_hs_conn_timer();
+    TEST_ASSERT(ticks_from_now == BLE_HS_ATT_SVR_QUEUED_WRITE_TMO);
+
+    /* Allow the timer to expire. */
+    ble_hs_test_util_set_ack_disconnect(0);
+    os_time_advance(BLE_HS_ATT_SVR_QUEUED_WRITE_TMO);
+    ticks_from_now = ble_hs_conn_timer();
+    TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);
+
+    /* Ensure connection was terminated. */
+    ble_hs_test_util_verify_tx_disconnect(2, BLE_ERR_REM_USER_CONN_TERM);
+
+    /* Free connection.  This is needed so that the prep write mbufs get
+     * freed and no mbuf leak gets reported.
+     */
+    rc = ble_hs_atomic_conn_delete(conn_handle);
+    TEST_ASSERT_FATAL(rc == 0);
+}
+
 TEST_CASE(ble_att_svr_test_indicate)
 {
     uint16_t conn_handle;
@@ -1783,7 +1842,6 @@ TEST_CASE(ble_att_svr_test_indicate)
     /* Attribute handle of 0. */
     ble_att_svr_test_misc_verify_indicate(conn_handle, 0,
                                           (uint8_t[]) { 1, 2, 3 }, 3, 0);
-
 }
 
 TEST_CASE(ble_att_svr_test_oom)
@@ -1996,6 +2054,7 @@ TEST_SUITE(ble_att_svr_suite)
     ble_att_svr_test_read_type();
     ble_att_svr_test_read_group_type();
     ble_att_svr_test_prep_write();
+    ble_att_svr_test_prep_write_tmo();
     ble_att_svr_test_notify();
     ble_att_svr_test_indicate();
     ble_att_svr_test_oom();

http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/19814398/net/nimble/host/test/src/ble_l2cap_test.c
----------------------------------------------------------------------
diff --git a/net/nimble/host/test/src/ble_l2cap_test.c 
b/net/nimble/host/test/src/ble_l2cap_test.c
index 55d6afe..9858660 100644
--- a/net/nimble/host/test/src/ble_l2cap_test.c
+++ b/net/nimble/host/test/src/ble_l2cap_test.c
@@ -372,10 +372,10 @@ TEST_CASE(ble_l2cap_test_case_frag_timeout)
 
     /* Ensure timer will expire in 30 seconds. */
     ticks_from_now = ble_hs_conn_timer();
-    TEST_ASSERT(ticks_from_now == BLE_L2CAP_REASSEM_TIMEOUT);
+    TEST_ASSERT(ticks_from_now == MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT));
 
     /* Almost let the timer expire. */
-    os_time_advance(BLE_L2CAP_REASSEM_TIMEOUT - 1);
+    os_time_advance(MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) - 1);
     ticks_from_now = ble_hs_conn_timer();
     TEST_ASSERT(ticks_from_now == 1);
 
@@ -385,11 +385,11 @@ TEST_CASE(ble_l2cap_test_case_frag_timeout)
 
     /* Ensure timer got reset. */
     ticks_from_now = ble_hs_conn_timer();
-    TEST_ASSERT(ticks_from_now == BLE_L2CAP_REASSEM_TIMEOUT);
+    TEST_ASSERT(ticks_from_now == MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT));
 
     /* Allow the timer to expire. */
     ble_hs_test_util_set_ack_disconnect(0);
-    os_time_advance(BLE_L2CAP_REASSEM_TIMEOUT);
+    os_time_advance(MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT));
     ticks_from_now = ble_hs_conn_timer();
     TEST_ASSERT(ticks_from_now == BLE_HS_FOREVER);
 

Reply via email to