adv_monitor: Implement RSSI filtering and content matching
authorManish Mandlik <mmandlik@google.com>
Sat, 31 Oct 2020 00:52:48 +0000 (17:52 -0700)
committerAyush Garg <ayush.garg@samsung.com>
Fri, 11 Mar 2022 13:38:33 +0000 (19:08 +0530)
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.

Signed-off-by: Anuj Jain <anuj01.jain@samsung.com>
Signed-off-by: Ayush Garg <ayush.garg@samsung.com>
doc/advertisement-monitor-api.txt
src/adapter.c
src/adv_monitor.c
src/adv_monitor.h

index e09b6fd..92c8ffc 100644 (file)
@@ -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
index ec41630..f1e0e8d 100644 (file)
@@ -1722,7 +1722,9 @@ 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);
-
+#ifndef TIZEN_FEATURE_BLUEZ_MODIFY
+       btd_adv_monitor_device_remove(adapter->adv_monitor_manager, dev);
+#endif
        adapter->discovery_found = g_slist_remove(adapter->discovery_found,
                                                                        dev);
 
@@ -11685,6 +11687,9 @@ static void update_found_devices(struct btd_adapter *adapter,
 #endif
 {
        struct btd_device *dev;
+#ifndef TIZEN_FEATURE_BLUEZ_MODIFY
+       struct bt_ad *ad = NULL;
+#endif
        struct eir_data eir_data;
 #ifndef TIZEN_FEATURE_BLUEZ_MODIFY
        bool name_known, discoverable;
@@ -11696,7 +11701,25 @@ static void update_found_devices(struct btd_adapter *adapter,
 #endif
        char addr[18];
        bool duplicate = false;
+#ifndef TIZEN_FEATURE_BLUEZ_MODIFY
+       struct queue *matched_monitors = NULL;
+
+       if (bdaddr_type != BDADDR_BREDR)
+               ad = bt_ad_new_with_data(data_len, data);
 
+       /* During the background scanning, update the device only when the data
+        * match at least one Adv monitor
+        */
+       if (ad) {
+               matched_monitors = btd_adv_monitor_content_filter(
+                                       adapter->adv_monitor_manager, ad);
+               bt_ad_unref(ad);
+               ad = NULL;
+       }
+
+       if (!adapter->discovering && !matched_monitors)
+               return;
+#endif
        memset(&eir_data, 0, sizeof(eir_data));
        eir_parse(&eir_data, data, data_len);
        ba2str(bdaddr, addr);
@@ -11782,8 +11805,8 @@ static void update_found_devices(struct btd_adapter *adapter,
                device_store_cached_name(dev, eir_data.name);
 
        /*
-        * Only skip devices that are not connected, are temporary and there
-        * is no active discovery session ongoing.
+        * Only skip devices that are not connected, are temporary, and there
+        * is no active discovery session ongoing and no matched Adv monitors
         */
 #ifdef TIZEN_FEATURE_BLUEZ_MODIFY
        if (device_is_temporary(dev) && adapter->discovery_list == NULL &&
@@ -11796,8 +11819,9 @@ static void update_found_devices(struct btd_adapter *adapter,
        device_set_last_addr_type(dev, bdaddr_type);
        device_set_ipsp_connected(dev, FALSE, NULL);
 #else
-       if (!btd_device_is_connected(dev) && (device_is_temporary(dev) &&
-                                                !adapter->discovery_list)) {
+       if (!btd_device_is_connected(dev) &&
+               (device_is_temporary(dev) && !adapter->discovery_list) &&
+               !matched_monitors) {
                eir_data_free(&eir_data);
                return;
        }
@@ -11808,9 +11832,12 @@ static void update_found_devices(struct btd_adapter *adapter,
        if (adapter->filtered_discovery &&
            !is_filter_match(adapter->discovery_list, &eir_data, rssi)) {
 #else
-       /* Don't continue if not discoverable or if filter don't match */
-       if (!discoverable || (adapter->filtered_discovery &&
-           !is_filter_match(adapter->discovery_list, &eir_data, rssi))) {
+       /* If there is no matched Adv monitors, don't continue if not
+        * discoverable or if active discovery filter don't match.
+        */
+       if (!matched_monitors && (!discoverable ||
+               (adapter->filtered_discovery && !is_filter_match(
+                               adapter->discovery_list, &eir_data, rssi)))) {
 #endif
                eir_data_free(&eir_data);
                return;
@@ -11897,6 +11924,15 @@ static void update_found_devices(struct btd_adapter *adapter,
 
        eir_data_free(&eir_data);
 
+#ifndef TIZEN_FEATURE_BLUEZ_MODIFY
+       /* After the device is updated, notify the matched Adv monitors */
+       if (matched_monitors) {
+               btd_adv_monitor_notify_monitors(adapter->adv_monitor_manager,
+                                               dev, rssi, matched_monitors);
+               queue_destroy(matched_monitors, NULL);
+               matched_monitors = NULL;
+       }
+#endif
        /*
         * Only if at least one client has requested discovery, maintain
         * list of found devices and name confirming for legacy devices.
index fe3d469..3931ca3 100644 (file)
@@ -25,9 +25,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"
@@ -80,13 +80,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;
@@ -94,13 +87,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 {
@@ -108,6 +122,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;
@@ -130,10 +158,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);
 }
@@ -149,6 +174,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);
@@ -247,6 +275,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;
@@ -435,7 +464,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);
@@ -467,28 +496,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);
@@ -922,3 +933,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);
+       }
+}
index 5cb3722..b5a143c 100644 (file)
 #ifndef __ADV_MONITOR_H
 #define __ADV_MONITOR_H
 
+#include <glib.h>
+
+#include "src/shared/ad.h"
+
 struct mgmt;
+struct queue;
+struct btd_device;
 struct btd_adapter;
 struct btd_adv_monitor_manager;
+struct btd_adv_monitor_pattern;
 
 struct btd_adv_monitor_manager *btd_adv_monitor_manager_create(
                                                struct btd_adapter *adapter,
                                                struct mgmt *mgmt);
 void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager);
 
+struct queue *btd_adv_monitor_content_filter(
+                               struct btd_adv_monitor_manager *manager,
+                               struct bt_ad *ad);
+
+void btd_adv_monitor_notify_monitors(struct btd_adv_monitor_manager *manager,
+                                       struct btd_device *device, int8_t rssi,
+                                       struct queue *matched_monitors);
+
+void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager,
+                                  struct btd_device *device);
+
+
 #endif /* __ADV_MONITOR_H */