All of lore.kernel.org
 help / color / mirror / Atom feed
* [BlueZ PATCH v4 1/8] adv_monitor: Implement RSSI Filter logic for background scanning
@ 2020-09-17 21:25 Miao-chen Chou
  2020-09-17 21:25 ` [BlueZ PATCH v4 2/8] adv_monitor: Implement unit tests for RSSI Filter Miao-chen Chou
                   ` (7 more replies)
  0 siblings, 8 replies; 11+ messages in thread
From: Miao-chen Chou @ 2020-09-17 21:25 UTC (permalink / raw)
  To: Bluetooth Kernel Mailing List
  Cc: Manish Mandlik, Alain Michaud, chromeos-bluetooth-upstreaming,
	Luiz Augusto von Dentz, Marcel Holtmann, Howard Chung,
	Manish Mandlik, Abhishek Pandit-Subedi, Miao-chen Chou

From: Manish Mandlik <mmandlik@google.com>

This patch implements the RSSI Filter logic for background scanning.

This was unit-tested by running tests in unit/test-adv-monitor.c

Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Reviewed-by: Alain Michaud <alainm@chromium.org>
Reviewed-by: Miao-chen Chou <mcchou@chromium.org>
Reviewed-by: Howard Chung <howardchung@google.com>
---

(no changes since v3)

Changes in v3:
- Fix commit message

 doc/advertisement-monitor-api.txt |   5 +
 src/adapter.c                     |   1 +
 src/adv_monitor.c                 | 286 +++++++++++++++++++++++++++++-
 src/adv_monitor.h                 |   4 +
 4 files changed, 292 insertions(+), 4 deletions(-)

diff --git a/doc/advertisement-monitor-api.txt b/doc/advertisement-monitor-api.txt
index e09b6fd25..92c8ffc38 100644
--- a/doc/advertisement-monitor-api.txt
+++ b/doc/advertisement-monitor-api.txt
@@ -70,6 +70,11 @@ Properties	string Type [read-only]
 			dBm indicates unset. The valid range of a timer is 1 to
 			300 seconds while 0 indicates unset.
 
+			If the peer device advertising interval is greater than the
+			HighRSSIThresholdTimer, the device will never be found. Similarly,
+			if it is greater than LowRSSIThresholdTimer, the device will be
+			considered as lost. Consider configuring these values accordingly.
+
 		array{(uint8, uint8, array{byte})} Patterns [read-only, optional]
 
 			If Type is set to 0x01, this must exist and has at least
diff --git a/src/adapter.c b/src/adapter.c
index b2bd8b3f1..415d6e06b 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1227,6 +1227,7 @@ void btd_adapter_remove_device(struct btd_adapter *adapter,
 	adapter->connect_list = g_slist_remove(adapter->connect_list, dev);
 
 	adapter->devices = g_slist_remove(adapter->devices, dev);
+	btd_adv_monitor_device_remove(adapter->adv_monitor_manager, dev);
 
 	adapter->discovery_found = g_slist_remove(adapter->discovery_found,
 									dev);
diff --git a/src/adv_monitor.c b/src/adv_monitor.c
index 737da1c90..7baa5317f 100644
--- a/src/adv_monitor.c
+++ b/src/adv_monitor.c
@@ -35,6 +35,7 @@
 
 #include "adapter.h"
 #include "dbus-common.h"
+#include "device.h"
 #include "log.h"
 #include "src/error.h"
 #include "src/shared/ad.h"
@@ -44,6 +45,8 @@
 
 #include "adv_monitor.h"
 
+static void monitor_device_free(void *data);
+
 #define ADV_MONITOR_INTERFACE		"org.bluez.AdvertisementMonitor1"
 #define ADV_MONITOR_MGR_INTERFACE	"org.bluez.AdvertisementMonitorManager1"
 
@@ -104,15 +107,36 @@ struct adv_monitor {
 
 	enum monitor_state state;	/* MONITOR_STATE_* */
 
-	int8_t high_rssi;		/* high RSSI threshold */
-	uint16_t high_rssi_timeout;	/* high RSSI threshold timeout */
-	int8_t low_rssi;		/* low RSSI threshold */
-	uint16_t low_rssi_timeout;	/* low RSSI threshold timeout */
+	int8_t high_rssi;		/* High RSSI threshold */
+	uint16_t high_rssi_timeout;	/* High RSSI threshold timeout */
+	int8_t low_rssi;		/* Low RSSI threshold */
+	uint16_t low_rssi_timeout;	/* Low RSSI threshold timeout */
+	struct queue *devices;		/* List of adv_monitor_device objects */
 
 	enum monitor_type type;		/* MONITOR_TYPE_* */
 	struct queue *patterns;
 };
 
+/* Some data like last_seen, timer/timeout values need to be maintained
+ * per device. struct adv_monitor_device maintains such data.
+ */
+struct adv_monitor_device {
+	struct adv_monitor *monitor;
+	struct btd_device *device;
+
+	time_t high_rssi_first_seen;	/* Start time when RSSI climbs above
+					 * the high RSSI threshold
+					 */
+	time_t low_rssi_first_seen;	/* Start time when RSSI drops below
+					 * the low RSSI threshold
+					 */
+	time_t last_seen;		/* Time when last Adv was received */
+	bool device_found;		/* State of the device - lost/found */
+	guint device_lost_timer;	/* Timer to track if the device goes
+					 * offline/out-of-range
+					 */
+};
+
 struct app_match_data {
 	const char *owner;
 	const char *path;
@@ -159,6 +183,9 @@ static void monitor_free(void *data)
 	g_dbus_proxy_unref(monitor->proxy);
 	g_free(monitor->path);
 
+	queue_destroy(monitor->devices, monitor_device_free);
+	monitor->devices = NULL;
+
 	queue_destroy(monitor->patterns, pattern_free);
 
 	free(monitor);
@@ -257,6 +284,7 @@ static struct adv_monitor *monitor_new(struct adv_monitor_app *app,
 	monitor->high_rssi_timeout = ADV_MONITOR_UNSET_TIMER;
 	monitor->low_rssi = ADV_MONITOR_UNSET_RSSI;
 	monitor->low_rssi_timeout = ADV_MONITOR_UNSET_TIMER;
+	monitor->devices = queue_new();
 
 	monitor->type = MONITOR_TYPE_NONE;
 	monitor->patterns = NULL;
@@ -932,3 +960,253 @@ void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager)
 
 	manager_destroy(manager);
 }
+
+/* Matches a device based on btd_device object */
+static bool monitor_device_match(const void *a, const void *b)
+{
+	const struct adv_monitor_device *dev = a;
+	const struct btd_device *device = b;
+
+	if (!dev)
+		return false;
+
+	if (dev->device != device)
+		return false;
+
+	return true;
+}
+
+/* Frees a monitor device object */
+static void monitor_device_free(void *data)
+{
+	struct adv_monitor_device *dev = data;
+
+	if (!dev)
+		return;
+
+	if (dev->device_lost_timer) {
+		g_source_remove(dev->device_lost_timer);
+		dev->device_lost_timer = 0;
+	}
+
+	dev->monitor = NULL;
+	dev->device = NULL;
+
+	g_free(dev);
+}
+
+/* Removes a device from monitor->devices list */
+static void remove_device_from_monitor(void *data, void *user_data)
+{
+	struct adv_monitor *monitor = data;
+	struct btd_device *device = user_data;
+	struct adv_monitor_device *dev = NULL;
+
+	if (!monitor)
+		return;
+
+	dev = queue_remove_if(monitor->devices, monitor_device_match, device);
+	if (dev) {
+		DBG("Device removed from the Adv Monitor at path %s",
+		    monitor->path);
+		monitor_device_free(dev);
+	}
+}
+
+/* Removes a device from every monitor in an app */
+static void remove_device_from_app(void *data, void *user_data)
+{
+	struct adv_monitor_app *app = data;
+	struct btd_device *device = user_data;
+
+	if (!app)
+		return;
+
+	queue_foreach(app->monitors, remove_device_from_monitor, device);
+}
+
+/* Removes a device from every monitor in all apps */
+void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager,
+				   struct btd_device *device)
+{
+	if (!manager || !device)
+		return;
+
+	queue_foreach(manager->apps, remove_device_from_app, device);
+}
+
+/* Creates a device object to track the per-device information */
+static struct adv_monitor_device *monitor_device_create(
+			struct adv_monitor *monitor,
+			struct btd_device *device)
+{
+	struct adv_monitor_device *dev = NULL;
+
+	dev = g_try_malloc0(sizeof(struct adv_monitor_device));
+	if (!dev)
+		return NULL;
+
+	dev->monitor = monitor;
+	dev->device = device;
+
+	queue_push_tail(monitor->devices, dev);
+
+	return dev;
+}
+
+/* Includes found/lost device's object path into the dbus message */
+static void report_device_state_setup(DBusMessageIter *iter, void *user_data)
+{
+	const char *path = device_get_path(user_data);
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+}
+
+/* Handles a situation where the device goes offline/out-of-range */
+static gboolean handle_device_lost_timeout(gpointer user_data)
+{
+	struct adv_monitor_device *dev = user_data;
+	struct adv_monitor *monitor = dev->monitor;
+	time_t curr_time = time(NULL);
+
+	DBG("Device Lost timeout triggered for device %p "
+	    "for the Adv Monitor at path %s", dev->device, monitor->path);
+
+	dev->device_lost_timer = 0;
+
+	if (dev->device_found && dev->last_seen) {
+		/* We were tracking for the Low RSSI filter. Check if there is
+		 * any Adv received after the timeout function is invoked.
+		 * If not, report the Device Lost event.
+		 */
+		if (difftime(curr_time, dev->last_seen) >=
+		    monitor->low_rssi_timeout) {
+			dev->device_found = false;
+
+			DBG("Calling DeviceLost() on Adv Monitor of owner %s "
+			    "at path %s", monitor->app->owner, monitor->path);
+
+			g_dbus_proxy_method_call(monitor->proxy, "DeviceLost",
+						 report_device_state_setup,
+						 NULL, dev->device, NULL);
+		}
+	}
+
+	return FALSE;
+}
+
+/* Filters an Adv based on its RSSI value */
+static void adv_monitor_filter_rssi(struct adv_monitor *monitor,
+				    struct btd_device *device, int8_t rssi)
+{
+	struct adv_monitor_device *dev = NULL;
+	time_t curr_time = time(NULL);
+	uint16_t adapter_id = monitor->app->manager->adapter_id;
+
+	/* If the RSSI thresholds and timeouts are not specified, report the
+	 * DeviceFound() event without tracking for the RSSI as the Adv has
+	 * already matched the pattern filter.
+	 */
+	if (monitor->high_rssi == ADV_MONITOR_UNSET_RSSI &&
+		monitor->low_rssi == ADV_MONITOR_UNSET_RSSI &&
+		monitor->high_rssi_timeout == ADV_MONITOR_UNSET_TIMER &&
+		monitor->low_rssi_timeout == ADV_MONITOR_UNSET_TIMER) {
+		DBG("Calling DeviceFound() on Adv Monitor of owner %s "
+		    "at path %s", monitor->app->owner, monitor->path);
+
+		g_dbus_proxy_method_call(monitor->proxy, "DeviceFound",
+					 report_device_state_setup, NULL,
+					 device, NULL);
+
+		return;
+	}
+
+	dev = queue_find(monitor->devices, monitor_device_match, device);
+	if (!dev)
+		dev = monitor_device_create(monitor, device);
+	if (!dev) {
+		btd_error(adapter_id, "Failed to create Adv Monitor "
+				      "device object.");
+		return;
+	}
+
+	if (dev->device_lost_timer) {
+		g_source_remove(dev->device_lost_timer);
+		dev->device_lost_timer = 0;
+	}
+
+	/* Reset the timings of found/lost if a device has been offline for
+	 * longer than the high/low timeouts.
+	 */
+	if (dev->last_seen) {
+		if (difftime(curr_time, dev->last_seen) >
+		    monitor->high_rssi_timeout) {
+			dev->high_rssi_first_seen = 0;
+		}
+
+		if (difftime(curr_time, dev->last_seen) >
+		    monitor->low_rssi_timeout) {
+			dev->low_rssi_first_seen = 0;
+		}
+	}
+	dev->last_seen = curr_time;
+
+	/* Check for the found devices (if the device is not already found) */
+	if (!dev->device_found && rssi > monitor->high_rssi) {
+		if (dev->high_rssi_first_seen) {
+			if (difftime(curr_time, dev->high_rssi_first_seen) >=
+			    monitor->high_rssi_timeout) {
+				dev->device_found = true;
+
+				DBG("Calling DeviceFound() on Adv Monitor "
+				    "of owner %s at path %s",
+				    monitor->app->owner, monitor->path);
+
+				g_dbus_proxy_method_call(
+					monitor->proxy, "DeviceFound",
+					report_device_state_setup, NULL,
+					dev->device, NULL);
+			}
+		} else {
+			dev->high_rssi_first_seen = curr_time;
+		}
+	} else {
+		dev->high_rssi_first_seen = 0;
+	}
+
+	/* Check for the lost devices (only if the device is already found, as
+	 * it doesn't make any sense to report the Device Lost event if the
+	 * device is not found yet)
+	 */
+	if (dev->device_found && rssi < monitor->low_rssi) {
+		if (dev->low_rssi_first_seen) {
+			if (difftime(curr_time, dev->low_rssi_first_seen) >=
+			    monitor->low_rssi_timeout) {
+				dev->device_found = false;
+
+				DBG("Calling DeviceLost() on Adv Monitor "
+				    "of owner %s at path %s",
+				    monitor->app->owner, monitor->path);
+
+				g_dbus_proxy_method_call(
+					monitor->proxy, "DeviceLost",
+					report_device_state_setup, NULL,
+					dev->device, NULL);
+			}
+		} else {
+			dev->low_rssi_first_seen = curr_time;
+		}
+	} else {
+		dev->low_rssi_first_seen = 0;
+	}
+
+	/* Setup a timer to track if the device goes offline/out-of-range, only
+	 * if we are tracking for the Low RSSI Threshold. If we are tracking
+	 * the High RSSI Threshold, nothing needs to be done.
+	 */
+	if (dev->device_found) {
+		dev->device_lost_timer =
+			g_timeout_add_seconds(monitor->low_rssi_timeout,
+					      handle_device_lost_timeout, dev);
+	}
+}
diff --git a/src/adv_monitor.h b/src/adv_monitor.h
index 69ea348f8..14508e7d1 100644
--- a/src/adv_monitor.h
+++ b/src/adv_monitor.h
@@ -21,6 +21,7 @@
 #define __ADV_MONITOR_H
 
 struct mgmt;
+struct btd_device;
 struct btd_adapter;
 struct btd_adv_monitor_manager;
 
@@ -29,4 +30,7 @@ struct btd_adv_monitor_manager *btd_adv_monitor_manager_create(
 						struct mgmt *mgmt);
 void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager);
 
+void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager,
+				   struct btd_device *device);
+
 #endif /* __ADV_MONITOR_H */
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 11+ messages in thread
* [BlueZ PATCH v4 2/8] adv_monitor: Implement unit tests for RSSI Filter
@ 2020-09-17  7:10 Miao-chen Chou
  2020-09-17  7:29 ` [BlueZ,v4,2/8] " bluez.test.bot
  0 siblings, 1 reply; 11+ messages in thread
From: Miao-chen Chou @ 2020-09-17  7:10 UTC (permalink / raw)
  To: Bluetooth Kernel Mailing List
  Cc: Marcel Holtmann, Alain Michaud, Howard Chung,
	chromeos-bluetooth-upstreaming, Luiz Augusto von Dentz,
	Manish Mandlik, Manish Mandlik, Miao-chen Chou

From: Manish Mandlik <mmandlik@google.com>

This patch implements unit tests for the background scanning RSSI
Filtering logic.

Verified all tests PASS by running tests in unit/test-adv-monitor.c

Reviewed-by: Alain Michaud <alainm@chromium.org>
Reviewed-by: Miao-chen Chou <mcchou@chromium.org>
---

Changes in v4:
- Fix commit message

Changes in v3:
- Fix commit message

Changes in v2:
- Cast test data to void *

 Makefile.am             |   9 +
 doc/test-coverage.txt   |   3 +-
 src/adv_monitor.c       |  79 ++++++++
 src/adv_monitor.h       |  10 +
 unit/test-adv-monitor.c | 391 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 491 insertions(+), 1 deletion(-)
 create mode 100644 unit/test-adv-monitor.c

diff --git a/Makefile.am b/Makefile.am
index 22b4fa30c..6918f02b0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -527,6 +527,15 @@ unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \
 			src/libshared-glib.la \
 			$(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt
 
+unit_tests += unit/test-adv-monitor
+
+unit_test_adv_monitor_SOURCES = unit/test-adv-monitor.c \
+				src/adv_monitor.h src/adv_monitor.c \
+				src/device.h src/device.c \
+				src/log.h src/log.c
+unit_test_adv_monitor_LDADD = gdbus/libgdbus-internal.la \
+				src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS)
+
 if MIDI
 unit_tests += unit/test-midi
 unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST
diff --git a/doc/test-coverage.txt b/doc/test-coverage.txt
index 741492a3e..5296983e6 100644
--- a/doc/test-coverage.txt
+++ b/doc/test-coverage.txt
@@ -30,8 +30,9 @@ test-gobex-transfer	  36	OBEX transfer handling
 test-gdbus-client	  13	D-Bus client handling
 test-gatt		 180	GATT qualification test cases
 test-hog		   6	HID Over GATT qualification test cases
+test-adv-monitor	   5	Advertisement Monitor test cases
 			-----
-			 761
+			 766
 
 
 Automated end-to-end testing
diff --git a/src/adv_monitor.c b/src/adv_monitor.c
index 7baa5317f..046f5953f 100644
--- a/src/adv_monitor.c
+++ b/src/adv_monitor.c
@@ -1210,3 +1210,82 @@ static void adv_monitor_filter_rssi(struct adv_monitor *monitor,
 					      handle_device_lost_timeout, dev);
 	}
 }
+
+/* Creates the dummy adv_monitor object for unit tests */
+void *btd_adv_monitor_rssi_test_setup(int8_t high_rssi, uint16_t high_timeout,
+				      int8_t low_rssi, uint16_t low_timeout)
+{
+	struct adv_monitor *test_monitor = NULL;
+
+	test_monitor = g_new0(struct adv_monitor, 1);
+	if (!test_monitor)
+		return NULL;
+
+	test_monitor->app = g_new0(struct adv_monitor_app, 1);
+	if (!test_monitor->app)
+		goto app_failed;
+
+	test_monitor->app->manager = g_new0(struct btd_adv_monitor_manager, 1);
+	if (!test_monitor->app->manager)
+		goto manager_failed;
+
+	test_monitor->high_rssi = high_rssi;
+	test_monitor->high_rssi_timeout = high_timeout;
+	test_monitor->low_rssi = low_rssi;
+	test_monitor->low_rssi_timeout = low_timeout;
+	test_monitor->devices = queue_new();
+
+	return test_monitor;
+
+manager_failed:
+	g_free(test_monitor->app);
+
+app_failed:
+	g_free(test_monitor);
+
+	return NULL;
+}
+
+/* Cleanup after unit test is done */
+void btd_adv_monitor_rssi_test_teardown(void *monitor_obj)
+{
+	struct adv_monitor *monitor = monitor_obj;
+
+	if (!monitor)
+		return;
+
+	queue_destroy(monitor->devices, monitor_device_free);
+	g_free(monitor);
+}
+
+/* Returns the current state of device - found/lost, used in unit tests */
+bool btd_adv_monitor_test_device_state(void *monitor_obj, void *device_obj)
+{
+	struct adv_monitor *monitor = monitor_obj;
+	struct btd_device *device = device_obj;
+	struct adv_monitor_device *dev = NULL;
+
+	if (!monitor || !device)
+		return false;
+
+	dev = queue_find(monitor->devices, monitor_device_match, device);
+	if (!dev)
+		return false;
+
+	return dev->device_found;
+}
+
+/* Helper function for the RSSI Filter unit tests */
+bool btd_adv_monitor_test_rssi(void *monitor_obj, void *device_obj,
+			       int8_t adv_rssi)
+{
+	struct adv_monitor *monitor = monitor_obj;
+	struct btd_device *device = device_obj;
+
+	if (!monitor || !device)
+		return false;
+
+	adv_monitor_filter_rssi(monitor, device, adv_rssi);
+
+	return btd_adv_monitor_test_device_state(monitor, device);
+}
diff --git a/src/adv_monitor.h b/src/adv_monitor.h
index 14508e7d1..351e7f9aa 100644
--- a/src/adv_monitor.h
+++ b/src/adv_monitor.h
@@ -33,4 +33,14 @@ void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager);
 void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager,
 				   struct btd_device *device);
 
+/* Following functions are the helper functions used for RSSI Filter unit tests
+ * defined in unit/test-adv-monitor.c
+ */
+void *btd_adv_monitor_rssi_test_setup(int8_t high_rssi, uint16_t high_timeout,
+				      int8_t low_rssi, uint16_t low_timeout);
+void btd_adv_monitor_rssi_test_teardown(void *monitor_obj);
+bool btd_adv_monitor_test_device_state(void *monitor_obj, void *device_obj);
+bool btd_adv_monitor_test_rssi(void *monitor_obj, void *device_obj,
+			       int8_t adv_rssi);
+
 #endif /* __ADV_MONITOR_H */
diff --git a/unit/test-adv-monitor.c b/unit/test-adv-monitor.c
new file mode 100644
index 000000000..970be84b0
--- /dev/null
+++ b/unit/test-adv-monitor.c
@@ -0,0 +1,391 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020 Google LLC
+ *
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <glib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "src/log.h"
+#include "src/shared/tester.h"
+
+#include "src/adv_monitor.h"
+
+#define define_test(name, type, data, setup_fn, test_fn, teardown_fn)	\
+	do {								\
+		static struct test_data test;				\
+		test.test_type = type;					\
+		test.test_name = g_strdup(name);			\
+		if (type == TEST_RSSI_FILTER) {				\
+			test.rssi_filter_test_data = (void *)&data;	\
+			test.rssi_filter_test_data->test_info = &test;	\
+		}							\
+		tester_add(name, &test, setup_fn, test_fn, teardown_fn);\
+	} while (0)
+
+#define ADV_INTERVAL		1	/* Advertisement interval in seconds */
+#define OUT_OF_RANGE		-128
+#define END_OF_RSSI_TEST	{0}
+
+#define RSSI_TEST_DONE(test_step)	\
+	(!test_step.adv_rssi && !test_step.duration && !test_step.result)
+
+#define DUMMY_BTD_DEVICE_OBJ	((void *) 0xF00)
+
+enum test_type {
+	TEST_RSSI_FILTER = 0,
+	TEST_CONTENT_FILTER,
+};
+
+enum result {
+	RESULT_DEVICE_NOT_FOUND = false,	/* Initial state of a device */
+	RESULT_DEVICE_FOUND = true,		/* Device state when the
+						 * Content/RSSI Filter match
+						 */
+	RESULT_DEVICE_LOST = false,		/* Device state when the Low
+						 * RSSI Filter match or if it
+						 * goes offline/out-of-range
+						 */
+};
+
+struct rssi_filter_test {
+	void *adv_monitor_obj;			/* struct adv_monitor object */
+	void *btd_device_obj;			/* struct btd_device object */
+	struct test_data *test_info;
+
+	const struct {
+		int8_t high_rssi_threshold;	/* High RSSI threshold */
+		uint16_t high_rssi_timeout;	/* High RSSI threshold timeout*/
+		int8_t low_rssi_threshold;	/* Low RSSI threshold */
+		uint16_t low_rssi_timeout;	/* Low RSSI threshold timeout */
+	} rssi_filter;
+
+	time_t start_time;		/* Start time of the test */
+	uint16_t resume_step;		/* Store the current sub-step of the
+					 * test before suspending that test
+					 */
+	guint out_of_range_timer;	/* Timer to simulate device offline */
+
+	const struct {
+		int8_t adv_rssi;	/* Advertisement RSSI */
+		uint16_t duration;	/* Advertisement duration in seconds */
+		enum result result;	/* Device state after every step */
+	} test_steps[];
+};
+
+/* Parent data structure to hold the test data and information,
+ * used by tester_* functions and callbacks.
+ */
+struct test_data {
+	enum test_type test_type;
+	char *test_name;
+
+	union {
+		struct rssi_filter_test *rssi_filter_test_data;
+	};
+};
+
+/* RSSI Filter Test 1:
+ * - The Device Lost event should NOT get triggered even if the Adv RSSI is
+ *   lower than LowRSSIThresh for more than LowRSSITimeout before finding
+ *   the device first.
+ * - Similarly, the Device Found event should NOT get triggered if the Adv RSSI
+ *   is greater than LowRSSIThresh but lower than HighRSSIThresh.
+ */
+static struct rssi_filter_test rssi_data_1 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-70, 6, RESULT_DEVICE_NOT_FOUND},
+		{-50, 6, RESULT_DEVICE_NOT_FOUND},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 2:
+ * - The Device Found event should get triggered when the Adv RSSI is higher
+ *   than HighRSSIThresh for more than HighRSSITimeout.
+ * - Once the device is found, the Device Lost event should NOT get triggered
+ *   if the Adv RSSI drops below HighRSSIThresh but it is not lower than
+ *   LowRSSIThresh.
+ * - When the Adv RSSI drops below LowRSSIThresh for more than LowRSSITimeout,
+ *   the Device Lost event should get triggered.
+ */
+static struct rssi_filter_test rssi_data_2 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-30, 6, RESULT_DEVICE_FOUND},
+		{-50, 6, RESULT_DEVICE_FOUND},
+		{-70, 6, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 3:
+ * - The Device Found event should get triggered only when the Adv RSSI is
+ *   higher than HighRSSIThresh for more than HighRSSITimeout.
+ * - If the Adv RSSI drops below HighRSSIThresh, timer should reset and start
+ *   counting once the Adv RSSI is above HighRSSIThresh.
+ * - Similarly, when tracking the Low RSSI, timer should reset when the Adv RSSI
+ *   goes above LowRSSIThresh. The Device Lost event should get triggered only
+ *   when the Adv RSSI is lower than LowRSSIThresh for more than LowRSSITimeout.
+ */
+static struct rssi_filter_test rssi_data_3 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-30, 2, RESULT_DEVICE_NOT_FOUND},
+		{-50, 6, RESULT_DEVICE_NOT_FOUND},
+		{-30, 4, RESULT_DEVICE_NOT_FOUND},
+		{-30, 2, RESULT_DEVICE_FOUND},
+		{-70, 2, RESULT_DEVICE_FOUND},
+		{-50, 6, RESULT_DEVICE_FOUND},
+		{-70, 4, RESULT_DEVICE_FOUND},
+		{-70, 2, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 4:
+ * - While tracking the High RSSI, timer should reset if the device goes
+ *   offline/out-of-range for more than HighRSSITimeout.
+ * - Once the device is found, if the device goes offline/out-of-range for
+ *   more than LowRSSITimeout, the Device Lost event should get triggered.
+ */
+static struct rssi_filter_test rssi_data_4 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{         -30, 2, RESULT_DEVICE_NOT_FOUND},
+		{OUT_OF_RANGE, 6, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 4, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 2, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 6, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 5:
+ * - The Device Found event should get triggered only once even if the Adv RSSI
+ *   stays higher than HighRSSIThresh for a longer period of time.
+ * - Once the device is found, while tracking the Low RSSI, timer should reset
+ *   when the Adv RSSI goes above LowRSSIThresh.
+ * - The timer should NOT reset if the device goes offline/out-of-range for
+ *   a very short period of time and comes back online/in-range before
+ *   the timeouts.
+ */
+static struct rssi_filter_test rssi_data_5 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{         -30, 2, RESULT_DEVICE_NOT_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 2, RESULT_DEVICE_FOUND},
+		{         -30, 3, RESULT_DEVICE_FOUND},
+		{         -30, 3, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_FOUND},
+		{         -50, 6, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* Initialize the data required for RSSI Filter test */
+static void setup_rssi_filter_test(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+
+	test->adv_monitor_obj = btd_adv_monitor_rssi_test_setup(
+					test->rssi_filter.high_rssi_threshold,
+					test->rssi_filter.high_rssi_timeout,
+					test->rssi_filter.low_rssi_threshold,
+					test->rssi_filter.low_rssi_timeout);
+
+	/* The RSSI Filter logic uses btd_device object only as a key in the
+	 * adv_monitor->devices list, it is never dereferenced nor used to
+	 * perform any operations related to btd_device. So we can use any
+	 * dummy address for unit testing.
+	 */
+	test->btd_device_obj = DUMMY_BTD_DEVICE_OBJ;
+
+	tester_setup_complete();
+}
+
+/* Cleanup after the RSSI Filter test is done */
+static void teardown_rssi_filter_test(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+
+	btd_adv_monitor_rssi_test_teardown(test->adv_monitor_obj);
+
+	tester_teardown_complete();
+}
+
+/* Execute the sub-steps of RSSI Filter test */
+static gboolean test_rssi_filter(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+	time_t start_time = time(NULL);
+	bool ret = false;
+
+	uint16_t i = 0;
+	uint16_t j = 0;
+
+	/* If this is not the beginning of test, return to the sub-step
+	 * before that test was suspended
+	 */
+	if (test->resume_step) {
+		start_time = test->start_time;
+		i = test->resume_step;
+
+		/* Clear the test resume timer */
+		g_source_remove(test->out_of_range_timer);
+		test->out_of_range_timer = 0;
+
+		/* Check state of the device - found/lost, while device was
+		 * offline/out-of-range
+		 */
+		ret = btd_adv_monitor_test_device_state(test->adv_monitor_obj,
+							test->btd_device_obj);
+		tester_debug("%s: [t=%.0lf, step=%d] Test resume, "
+			     "device_found = %s",
+			     test->test_info->test_name,
+			     difftime(time(NULL), start_time), i,
+			     ret ? "true" : "false");
+		g_assert(ret == test->test_steps[i].result);
+
+		i++;
+	}
+
+	while (!RSSI_TEST_DONE(test->test_steps[i])) {
+		if (test->test_steps[i].adv_rssi == OUT_OF_RANGE) {
+			/* Simulate device offline/out-of-range by suspending
+			 * the test.
+			 *
+			 * Note: All tester_* functions run sequentially by
+			 * adding a next function to the main loop using
+			 * g_idle_add(). If a timeout function is added using
+			 * g_timeout_add_*(), it doesn't really get invoked as
+			 * soon as the timer expires. Instead, it is invoked
+			 * once the current function returns and the timer has
+			 * expired. So, to give handle_device_lost_timeout()
+			 * function a chance to run at the correct time, we
+			 * must save the current state and exit from this
+			 * function while we simulate the device offline. We can
+			 * come back later to continue with the remaining steps.
+			 */
+			test->resume_step = i;
+			test->start_time = start_time;
+			test->out_of_range_timer = g_timeout_add_seconds(
+						   test->test_steps[i].duration,
+						   test_rssi_filter, data);
+
+			/* Check the device state before suspending the test */
+			ret = btd_adv_monitor_test_device_state(
+							test->adv_monitor_obj,
+							test->btd_device_obj);
+			tester_debug("%s: [t=%.0lf, step=%d] Test suspend, "
+				     "device_found = %s",
+				     test->test_info->test_name,
+				     difftime(time(NULL), start_time), i,
+				     ret ? "true" : "false");
+			return FALSE;
+		}
+
+		for (j = 0; j < test->test_steps[i].duration; j++) {
+			ret = btd_adv_monitor_test_rssi(
+						test->adv_monitor_obj,
+						test->btd_device_obj,
+						test->test_steps[i].adv_rssi);
+			tester_debug("%s: [t=%.0lf, step=%d] Test "
+				     "advertisement RSSI %d, device_found = %s",
+				     test->test_info->test_name,
+				     difftime(time(NULL), start_time), i,
+				     test->test_steps[i].adv_rssi,
+				     ret ? "true" : "false");
+
+			/* Sleep for a second to simulate receiving
+			 * advertisement once every second
+			 */
+			sleep(ADV_INTERVAL);
+		}
+		g_assert(ret == test->test_steps[i].result);
+
+		i++;
+	}
+
+	tester_debug("%s: [t=%.0lf] Test done", test->test_info->test_name,
+		     difftime(time(NULL), start_time));
+
+	tester_test_passed();
+
+	return FALSE;
+}
+
+/* Handler function to prepare for a test */
+static void setup_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		setup_rssi_filter_test(test->rssi_filter_test_data);
+}
+
+/* Handler function to cleanup after the test is done */
+static void teardown_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		teardown_rssi_filter_test(test->rssi_filter_test_data);
+}
+
+/* Handler function to execute a test with the given data set */
+static void test_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		test_rssi_filter(test->rssi_filter_test_data);
+}
+
+int main(int argc, char *argv[])
+{
+	tester_init(&argc, &argv);
+
+	__btd_log_init("*", 0);
+
+	define_test("/advmon/rssi/1", TEST_RSSI_FILTER, rssi_data_1,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/2", TEST_RSSI_FILTER, rssi_data_2,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/3", TEST_RSSI_FILTER, rssi_data_3,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/4", TEST_RSSI_FILTER, rssi_data_4,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/5", TEST_RSSI_FILTER, rssi_data_5,
+		    setup_handler, test_handler, teardown_handler);
+
+	return tester_run();
+}
-- 
2.26.2


^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-09-17 21:51 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-09-17 21:25 [BlueZ PATCH v4 1/8] adv_monitor: Implement RSSI Filter logic for background scanning Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 2/8] adv_monitor: Implement unit tests for RSSI Filter Miao-chen Chou
2020-09-17 21:49   ` [BlueZ,v4,2/8] " bluez.test.bot
2020-09-17 21:25 ` [BlueZ PATCH v4 3/8] adv_monitor: Implement Adv matching based on stored monitors Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 4/8] adv_monitor: Implement unit tests for content filter Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 5/8] adapter: Clear all Adv monitors upon bring-up Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 6/8] adv_monitor: Implement Add Adv Patterns Monitor cmd handler Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 7/8] adv_monitor: Fix return type of RegisterMonitor() method Miao-chen Chou
2020-09-17 21:25 ` [BlueZ PATCH v4 8/8] adv_monitor: Issue Remove Adv Monitor mgmt call Miao-chen Chou
2020-09-17 21:51 ` [BlueZ,v4,1/8] adv_monitor: Implement RSSI Filter logic for background scanning bluez.test.bot
  -- strict thread matches above, loose matches on Subject: below --
2020-09-17  7:10 [BlueZ PATCH v4 2/8] adv_monitor: Implement unit tests for RSSI Filter Miao-chen Chou
2020-09-17  7:29 ` [BlueZ,v4,2/8] " bluez.test.bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.