Repository: incubator-mynewt-core Updated Branches: refs/heads/develop 5c5a987ef -> 0d402e54a
MYNEWT-341 BLE Host - Handle missing ACL fragments If a packet is only partially received, and 30 seconds has passed since the last fragment 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/0d402e54 Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/tree/0d402e54 Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/diff/0d402e54 Branch: refs/heads/develop Commit: 0d402e54aa6d0fa24f31eebac15038b1d00b969b Parents: 5c5a987 Author: Christopher Collins <ccoll...@apache.org> Authored: Thu Dec 1 16:35:59 2016 -0800 Committer: Christopher Collins <ccoll...@apache.org> Committed: Thu Dec 1 16:35:59 2016 -0800 ---------------------------------------------------------------------- net/nimble/host/src/ble_hs.c | 9 ++-- net/nimble/host/src/ble_hs_conn.c | 61 ++++++++++++++++++++++++ net/nimble/host/src/ble_hs_conn_priv.h | 3 ++ net/nimble/host/src/ble_l2cap.c | 2 + net/nimble/host/src/ble_l2cap_priv.h | 2 + net/nimble/host/test/src/ble_hs_conn_test.c | 6 ++- net/nimble/host/test/src/ble_l2cap_test.c | 46 ++++++++++++++++++ 7 files changed, 124 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/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 b55dc2c..c5799ee 100644 --- a/net/nimble/host/src/ble_hs.c +++ b/net/nimble/host/src/ble_hs.c @@ -314,10 +314,13 @@ ble_hs_timer_exp(struct os_event *ev) ticks_until_next = ble_sm_timer(); ble_hs_timer_sched(ticks_until_next); + + ticks_until_next = ble_hs_conn_timer(); + ble_hs_timer_sched(ticks_until_next); } static void -ble_hs_timer_timer_reset(uint32_t ticks) +ble_hs_timer_reset(uint32_t ticks) { int rc; @@ -341,7 +344,7 @@ ble_hs_timer_sched(int32_t ticks_from_now) if (!os_callout_queued(&ble_hs_timer_timer) || OS_TIME_TICK_LT(abs_time, ble_hs_timer_timer.c_ticks)) { - ble_hs_timer_timer_reset(ticks_from_now); + ble_hs_timer_reset(ticks_from_now); } } @@ -351,7 +354,7 @@ ble_hs_timer_resched(void) /* Reschedule the timer to run immediately. The timer callback will query * each module for an up-to-date expiration time. */ - ble_hs_timer_timer_reset(0); + ble_hs_timer_reset(0); } static void http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/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 59c3ae5..9a6517e 100644 --- a/net/nimble/host/src/ble_hs_conn.c +++ b/net/nimble/host/src/ble_hs_conn.c @@ -378,6 +378,67 @@ ble_hs_conn_addrs(const struct ble_hs_conn *conn, } } +int32_t +ble_hs_conn_timer(void) +{ + struct ble_hs_conn *conn; + os_time_t now; + int32_t next_exp_in; + int32_t time_diff; + uint16_t conn_handle; + + conn_handle = BLE_HS_CONN_HANDLE_NONE; + next_exp_in = BLE_HS_FOREVER; + now = os_time_get(); + + ble_hs_lock(); + + /* This loop performs one of two tasks: + * 1. Determine if any connections need to be terminated due to timeout. + * If so, break out of the loop and terminate the connection. This + * function will need to be executed again. + * 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 (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; + conn->bhc_flags |= BLE_HS_CONN_F_TERMINATING; + break; + } + + /* Determine if this connection is the soonest to time out. */ + if (time_diff < next_exp_in) { + next_exp_in = time_diff; + } + } + } + + ble_hs_unlock(); + + /* If a connection has timed out, terminate it. We need to recursively + * call this function again to determine when the next timeout is. This + * is a tail-recursive call, so it should be optimized to execute in the + * same stack frame. + */ + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); + return ble_hs_conn_timer(); + } + + return next_exp_in; +} + int ble_hs_conn_init(void) { http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/net/nimble/host/src/ble_hs_conn_priv.h ---------------------------------------------------------------------- diff --git a/net/nimble/host/src/ble_hs_conn_priv.h b/net/nimble/host/src/ble_hs_conn_priv.h index 92e6f37..3dfa5cf 100644 --- a/net/nimble/host/src/ble_hs_conn_priv.h +++ b/net/nimble/host/src/ble_hs_conn_priv.h @@ -35,6 +35,7 @@ struct ble_l2cap_chan; typedef uint8_t ble_hs_conn_flags_t; #define BLE_HS_CONN_F_MASTER 0x01 +#define BLE_HS_CONN_F_TERMINATING 0x02 struct ble_hs_conn { SLIST_ENTRY(ble_hs_conn) bhc_next; @@ -54,6 +55,7 @@ struct ble_hs_conn { struct ble_l2cap_chan_list bhc_channels; struct ble_l2cap_chan *bhc_rx_chan; /* Channel rxing current packet. */ + uint32_t bhc_rx_timeout; uint16_t bhc_outstanding_pkts; struct ble_att_svr_conn bhc_att_svr; @@ -93,6 +95,7 @@ int ble_hs_conn_chan_insert(struct ble_hs_conn *conn, struct ble_l2cap_chan *chan); void ble_hs_conn_addrs(const struct ble_hs_conn *conn, struct ble_hs_conn_addrs *addrs); +int32_t ble_hs_conn_timer(void); int ble_hs_conn_init(void); http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/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 66701b1..aa37784 100644 --- a/net/nimble/host/src/ble_l2cap.c +++ b/net/nimble/host/src/ble_l2cap.c @@ -180,6 +180,8 @@ 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; + ble_hs_timer_resched(); rc = BLE_HS_EAGAIN; } http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/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 79d58a7..2087e81 100644 --- a/net/nimble/host/src/ble_l2cap_priv.h +++ b/net/nimble/host/src/ble_l2cap_priv.h @@ -29,6 +29,8 @@ 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/0d402e54/net/nimble/host/test/src/ble_hs_conn_test.c ---------------------------------------------------------------------- diff --git a/net/nimble/host/test/src/ble_hs_conn_test.c b/net/nimble/host/test/src/ble_hs_conn_test.c index 4ba92d2..a5fdba7 100644 --- a/net/nimble/host/test/src/ble_hs_conn_test.c +++ b/net/nimble/host/test/src/ble_hs_conn_test.c @@ -167,8 +167,10 @@ TEST_CASE(ble_hs_conn_test_undirect_connectable_success) adv_params = ble_hs_test_util_adv_params; adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; - rc = ble_hs_test_util_adv_start(BLE_ADDR_TYPE_PUBLIC, BLE_ADDR_TYPE_PUBLIC, - addr, &adv_params, BLE_HS_FOREVER, + rc = ble_hs_test_util_adv_start(BLE_ADDR_TYPE_PUBLIC, + BLE_ADDR_TYPE_PUBLIC, + addr, &adv_params, + BLE_HS_FOREVER, NULL, NULL, 0, 0); TEST_ASSERT(rc == 0); http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/0d402e54/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 7be148c..55d6afe 100644 --- a/net/nimble/host/test/src/ble_l2cap_test.c +++ b/net/nimble/host/test/src/ble_l2cap_test.c @@ -352,6 +352,51 @@ TEST_CASE(ble_l2cap_test_case_frag_channels) ble_hs_unlock(); } +TEST_CASE(ble_l2cap_test_case_frag_timeout) +{ + int32_t ticks_from_now; + int rc; + + ble_l2cap_test_util_init(); + + ble_l2cap_test_util_create_conn(2, ((uint8_t[]){1,2,3,4,5,6}), + NULL, NULL); + + /* Ensure timer is not set. */ + ticks_from_now = ble_hs_conn_timer(); + TEST_ASSERT_FATAL(ticks_from_now == BLE_HS_FOREVER); + + /* Receive the first fragment of a multipart ACL data packet. */ + rc = ble_l2cap_test_util_rx_first_frag(2, 14, BLE_L2CAP_TEST_CID, 30); + TEST_ASSERT_FATAL(rc == 0); + + /* Ensure timer will expire in 30 seconds. */ + ticks_from_now = ble_hs_conn_timer(); + TEST_ASSERT(ticks_from_now == BLE_L2CAP_REASSEM_TIMEOUT); + + /* Almost let the timer expire. */ + os_time_advance(BLE_L2CAP_REASSEM_TIMEOUT - 1); + ticks_from_now = ble_hs_conn_timer(); + TEST_ASSERT(ticks_from_now == 1); + + /* Receive a second fragment. */ + rc = ble_l2cap_test_util_rx_next_frag(2, 14); + TEST_ASSERT_FATAL(rc == 0); + + /* Ensure timer got reset. */ + ticks_from_now = ble_hs_conn_timer(); + TEST_ASSERT(ticks_from_now == BLE_L2CAP_REASSEM_TIMEOUT); + + /* Allow the timer to expire. */ + ble_hs_test_util_set_ack_disconnect(0); + os_time_advance(BLE_L2CAP_REASSEM_TIMEOUT); + 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); +} + /***************************************************************************** * $unsolicited response * *****************************************************************************/ @@ -569,6 +614,7 @@ TEST_SUITE(ble_l2cap_test_suite) ble_l2cap_test_case_frag_single(); ble_l2cap_test_case_frag_multiple(); ble_l2cap_test_case_frag_channels(); + ble_l2cap_test_case_frag_timeout(); ble_l2cap_test_case_sig_unsol_rsp(); ble_l2cap_test_case_sig_update_accept(); ble_l2cap_test_case_sig_update_reject();