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.
This commit is contained in:
Manish Mandlik 2020-10-30 17:52:48 -07:00 committed by Luiz Augusto von Dentz
parent dc13605495
commit 058ef5a662
4 changed files with 467 additions and 44 deletions

View 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

View File

@ -1214,6 +1214,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);
@ -6566,10 +6567,28 @@ static void update_found_devices(struct btd_adapter *adapter,
const uint8_t *data, uint8_t data_len)
{
struct btd_device *dev;
struct bt_ad *ad = NULL;
struct eir_data eir_data;
bool name_known, discoverable;
char addr[18];
bool duplicate = false;
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;
memset(&eir_data, 0, sizeof(eir_data));
eir_parse(&eir_data, data, data_len);
@ -6615,18 +6634,22 @@ 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
*/
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;
}
/* 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)))) {
eir_data_free(&eir_data);
return;
}
@ -6683,6 +6706,14 @@ static void update_found_devices(struct btd_adapter *adapter,
eir_data_free(&eir_data);
/* 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;
}
/*
* Only if at least one client has requested discovery, maintain
* list of found devices and name confirming for legacy devices.

View File

@ -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);
}
}

View File

@ -11,13 +11,31 @@
#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 */