summaryrefslogtreecommitdiff
path: root/src/adv_monitor.c
diff options
context:
space:
mode:
authorManish Mandlik <mmandlik@google.com>2020-10-30 17:52:48 -0700
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>2020-11-02 09:53:25 -0800
commit058ef5a662160281ae0fdbbc47565a7292c2312d (patch)
tree44c28c1ed451c760fc96a034372e370a93fb0c18 /src/adv_monitor.c
parentdc136054955b4b4fe3c62902358b7b993275a04b (diff)
downloadbluez-058ef5a662160281ae0fdbbc47565a7292c2312d.tar.gz
adv_monitor: Implement RSSI filtering and content matching
This implements the following logic for background scanning. - Implement RSSI tracking based on high/low RSSI thresholds and timers. - Create an entry point in adapter to start the matching of Adv based on all monitors and invoke the RSSI tracking for Adv reporting.
Diffstat (limited to 'src/adv_monitor.c')
-rw-r--r--src/adv_monitor.c443
1 files changed, 406 insertions, 37 deletions
diff --git a/src/adv_monitor.c b/src/adv_monitor.c
index e441a5566..9a04da6e1 100644
--- a/src/adv_monitor.c
+++ b/src/adv_monitor.c
@@ -26,9 +26,9 @@
#include "adapter.h"
#include "dbus-common.h"
+#include "device.h"
#include "log.h"
#include "src/error.h"
-#include "src/shared/ad.h"
#include "src/shared/mgmt.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
@@ -81,13 +81,6 @@ enum monitor_state {
MONITOR_STATE_HONORED, /* Accepted by kernel */
};
-struct pattern {
- uint8_t ad_type;
- uint8_t offset;
- uint8_t length;
- uint8_t value[BT_AD_MAX_DATA_LEN];
-};
-
struct adv_monitor {
struct adv_monitor_app *app;
GDBusProxy *proxy;
@@ -95,13 +88,34 @@ 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;
+ struct queue *patterns; /* List of bt_ad_pattern objects */
+};
+
+/* 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 found; /* State of the device - lost/found */
+ guint lost_timer; /* Timer to track if the device goes
+ * offline/out-of-range
+ */
};
struct app_match_data {
@@ -109,6 +123,20 @@ struct app_match_data {
const char *path;
};
+struct adv_content_filter_info {
+ struct bt_ad *ad;
+ struct queue *matched_monitors; /* List of matched monitors */
+};
+
+struct adv_rssi_filter_info {
+ struct btd_device *device;
+ int8_t rssi;
+};
+
+static void monitor_device_free(void *data);
+static void adv_monitor_filter_rssi(struct adv_monitor *monitor,
+ struct btd_device *device, int8_t rssi);
+
const struct adv_monitor_type {
enum monitor_type type;
const char *name;
@@ -131,10 +159,7 @@ static void app_reply_msg(struct adv_monitor_app *app, DBusMessage *reply)
/* Frees a pattern */
static void pattern_free(void *data)
{
- struct pattern *pattern = data;
-
- if (!pattern)
- return;
+ struct bt_ad_pattern *pattern = data;
free(pattern);
}
@@ -150,6 +175,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);
@@ -248,6 +276,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;
@@ -436,7 +465,7 @@ static bool parse_patterns(struct adv_monitor *monitor, const char *path)
int value_len;
uint8_t *value;
uint8_t offset, ad_type;
- struct pattern *pattern;
+ struct bt_ad_pattern *pattern;
DBusMessageIter struct_iter, value_iter;
dbus_message_iter_recurse(&array_iter, &struct_iter);
@@ -468,28 +497,10 @@ static bool parse_patterns(struct adv_monitor *monitor, const char *path)
dbus_message_iter_get_fixed_array(&value_iter, &value,
&value_len);
- // Verify the values
- if (offset > BT_AD_MAX_DATA_LEN - 1)
- goto failed;
-
- if ((ad_type > BT_AD_3D_INFO_DATA &&
- ad_type != BT_AD_MANUFACTURER_DATA) ||
- ad_type < BT_AD_FLAGS) {
- goto failed;
- }
-
- if (!value || value_len <= 0 || value_len > BT_AD_MAX_DATA_LEN)
- goto failed;
-
- pattern = new0(struct pattern, 1);
+ pattern = bt_ad_pattern_new(ad_type, offset, value_len, value);
if (!pattern)
goto failed;
- pattern->ad_type = ad_type;
- pattern->offset = offset;
- pattern->length = value_len;
- memcpy(pattern->value, value, pattern->length);
-
queue_push_tail(monitor->patterns, pattern);
dbus_message_iter_next(&array_iter);
@@ -923,3 +934,361 @@ void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager)
manager_destroy(manager);
}
+
+/* Processes the content matching based pattern(s) of a monitor */
+static void adv_match_per_monitor(void *data, void *user_data)
+{
+ struct adv_monitor *monitor = data;
+ struct adv_content_filter_info *info = user_data;
+
+ if (!monitor) {
+ error("Unexpected NULL adv_monitor object upon match");
+ return;
+ }
+
+ if (monitor->state != MONITOR_STATE_HONORED)
+ return;
+
+ if (monitor->type == MONITOR_TYPE_OR_PATTERNS &&
+ bt_ad_pattern_match(info->ad, monitor->patterns)) {
+ goto matched;
+ }
+
+ return;
+
+matched:
+ if (!info->matched_monitors)
+ info->matched_monitors = queue_new();
+
+ queue_push_tail(info->matched_monitors, monitor);
+}
+
+/* Processes the content matching for the monitor(s) of an app */
+static void adv_match_per_app(void *data, void *user_data)
+{
+ struct adv_monitor_app *app = data;
+
+ if (!app) {
+ error("Unexpected NULL adv_monitor_app object upon match");
+ return;
+ }
+
+ queue_foreach(app->monitors, adv_match_per_monitor, user_data);
+}
+
+/* Processes the content matching for every app without RSSI filtering and
+ * notifying monitors. The caller is responsible of releasing the memory of the
+ * list but not the ad data.
+ * Returns the list of monitors whose content match the ad data.
+ */
+struct queue *btd_adv_monitor_content_filter(
+ struct btd_adv_monitor_manager *manager,
+ struct bt_ad *ad)
+{
+ struct adv_content_filter_info info;
+
+ if (!manager || !ad)
+ return NULL;
+
+ info.ad = ad;
+ info.matched_monitors = NULL;
+
+ queue_foreach(manager->apps, adv_match_per_app, &info);
+
+ return info.matched_monitors;
+}
+
+/* Wraps adv_monitor_filter_rssi() to processes the content-matched monitor with
+ * RSSI filtering and notifies it on device found/lost event
+ */
+static void monitor_filter_rssi(void *data, void *user_data)
+{
+ struct adv_monitor *monitor = data;
+ struct adv_rssi_filter_info *info = user_data;
+
+ if (!monitor || !info)
+ return;
+
+ adv_monitor_filter_rssi(monitor, info->device, info->rssi);
+}
+
+/* Processes every content-matched monitor with RSSI filtering and notifies on
+ * device found/lost event. The caller is responsible of releasing the memory
+ * of matched_monitors list but not its data.
+ */
+void btd_adv_monitor_notify_monitors(struct btd_adv_monitor_manager *manager,
+ struct btd_device *device, int8_t rssi,
+ struct queue *matched_monitors)
+{
+ struct adv_rssi_filter_info info;
+
+ if (!manager || !device || !matched_monitors ||
+ queue_isempty(matched_monitors)) {
+ return;
+ }
+
+ info.device = device;
+ info.rssi = rssi;
+
+ queue_foreach(matched_monitors, monitor_filter_rssi, &info);
+}
+
+/* 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) {
+ error("Unexpected NULL adv_monitor_device object upon match");
+ 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) {
+ error("Unexpected NULL adv_monitor_device object upon free");
+ return;
+ }
+
+ if (dev->lost_timer) {
+ g_source_remove(dev->lost_timer);
+ dev->lost_timer = 0;
+ }
+
+ dev->monitor = NULL;
+ dev->device = NULL;
+
+ 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) {
+ error("Unexpected NULL adv_monitor object upon device remove");
+ 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) {
+ error("Unexpected NULL adv_monitor_app object upon device "
+ "remove");
+ 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 = new0(struct adv_monitor_device, 1);
+ 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->lost_timer = 0;
+
+ if (dev->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->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->lost_timer) {
+ g_source_remove(dev->lost_timer);
+ dev->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->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->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->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->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->found) {
+ dev->lost_timer =
+ g_timeout_add_seconds(monitor->low_rssi_timeout,
+ handle_device_lost_timeout, dev);
+ }
+}