blob: 87fa824b35d8b7f103b132169359e1327d43711c [file]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2020 Google LLC
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus/gdbus.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
#include "adapter.h"
#include "btd.h"
#include "dbus-common.h"
#include "device.h"
#include "log.h"
#include "src/error.h"
#include "src/shared/mgmt.h"
#include "src/shared/queue.h"
#include "src/shared/timeout.h"
#include "src/shared/util.h"
#include "adv_monitor.h"
#define ADV_MONITOR_INTERFACE "org.bluez.AdvertisementMonitor1"
#define ADV_MONITOR_MGR_INTERFACE "org.bluez.AdvertisementMonitorManager1"
#define ADV_MONITOR_UNSET_RSSI 127 /* dBm */
#define ADV_MONITOR_MAX_RSSI 20 /* dBm */
#define ADV_MONITOR_MIN_RSSI -127 /* dBm */
#define ADV_MONITOR_UNSET_TIMEOUT 0 /* second */
#define ADV_MONITOR_MIN_TIMEOUT 1 /* second */
#define ADV_MONITOR_MAX_TIMEOUT 300 /* second */
#define ADV_MONITOR_DEFAULT_LOW_TIMEOUT 5 /* second */
#define ADV_MONITOR_DEFAULT_HIGH_TIMEOUT 10 /* second */
#define ADV_MONITOR_UNSET_SAMPLING_PERIOD 256 /* 100 ms */
#define ADV_MONITOR_MAX_SAMPLING_PERIOD 255 /* 100 ms */
struct btd_adv_monitor_manager {
struct btd_adapter *adapter;
struct mgmt *mgmt;
uint16_t adapter_id;
uint32_t supported_features; /* MGMT_ADV_MONITOR_FEATURE_MASK_* */
uint32_t enabled_features; /* MGMT_ADV_MONITOR_FEATURE_MASK_* */
uint16_t max_num_monitors;
uint8_t max_num_patterns;
struct queue *apps; /* apps who registered for Adv monitoring */
struct queue *merged_patterns;
};
struct adv_monitor_app {
struct btd_adv_monitor_manager *manager;
char *owner;
char *path;
DBusMessage *reg;
GDBusClient *client;
struct queue *monitors;
};
enum monitor_type {
MONITOR_TYPE_NONE,
MONITOR_TYPE_OR_PATTERNS,
};
enum monitor_state {
MONITOR_STATE_NEW, /* New but not yet init'ed with actual values */
MONITOR_STATE_FAILED, /* Failed to be init'ed */
MONITOR_STATE_INITED, /* Init'ed but not yet sent to kernel */
MONITOR_STATE_ACTIVE, /* Accepted by kernel */
MONITOR_STATE_REMOVED, /* Removed from kernel */
MONITOR_STATE_RELEASED, /* Dbus Object removed by app */
};
enum merged_pattern_state {
MERGED_PATTERN_STATE_ADDING, /* Adding pattern to kernel */
MERGED_PATTERN_STATE_REMOVING, /* Removing pattern from kernel */
MERGED_PATTERN_STATE_STABLE, /* Idle */
};
struct rssi_parameters {
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 */
uint16_t sampling_period; /* Merge packets in the same timeslot.
* Currently unimplemented in user
* space.
* Used only to pass data to kernel.
*/
};
struct adv_monitor {
struct adv_monitor_app *app;
GDBusProxy *proxy;
char *path;
enum monitor_state state; /* MONITOR_STATE_* */
struct rssi_parameters rssi; /* RSSI parameter for this monitor */
struct adv_monitor_merged_pattern *merged_pattern;
struct queue *devices; /* List of adv_monitor_device objects */
};
/* Some chipsets doesn't support multiple monitors with the same pattern.
* To solve that and to generally ease their task, we merge monitors with the
* same pattern, so those monitors will only be sent once to the kernel.
*/
struct adv_monitor_merged_pattern {
struct btd_adv_monitor_manager *manager;
uint16_t monitor_handle; /* Kernel Monitor Handle */
struct rssi_parameters rssi; /* Merged RSSI parameter for |monitors|,
* this will be sent to the kernel.
*/
struct queue *monitors; /* List of adv_monitor objects which
* have this pattern
*/
enum monitor_type type; /* MONITOR_TYPE_* */
struct queue *patterns; /* List of bt_ad_pattern objects */
enum merged_pattern_state current_state; /* MERGED_PATTERN_STATE_* */
enum merged_pattern_state next_state; /* MERGED_PATTERN_STATE_* */
};
/* 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 */
unsigned int lost_timer; /* Timer to track if the device goes
* offline/out-of-range
*/
};
struct app_match_data {
const char *owner;
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;
};
struct monitored_device_info {
uint16_t monitor_handle; /* Kernel Monitor Handle */
struct btd_device *device;
};
static void monitor_device_free(void *data);
static void adv_monitor_filter_rssi(struct adv_monitor *monitor,
struct btd_device *device, int8_t rssi);
static void merged_pattern_send_add(
struct adv_monitor_merged_pattern *merged_pattern);
static void merged_pattern_send_remove(
struct adv_monitor_merged_pattern *merged_pattern);
const struct adv_monitor_type {
enum monitor_type type;
const char *name;
} supported_types[] = {
{ MONITOR_TYPE_OR_PATTERNS, "or_patterns" },
{ },
};
static void rssi_unset(struct rssi_parameters *rssi)
{
rssi->high_rssi = ADV_MONITOR_UNSET_RSSI;
rssi->high_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT;
rssi->low_rssi = ADV_MONITOR_UNSET_RSSI;
rssi->low_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT;
rssi->sampling_period = ADV_MONITOR_UNSET_SAMPLING_PERIOD;
}
static bool rssi_is_unset(const struct rssi_parameters *rssi)
{
return rssi->high_rssi == ADV_MONITOR_UNSET_RSSI &&
rssi->low_rssi == ADV_MONITOR_UNSET_RSSI &&
rssi->high_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT &&
rssi->low_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT &&
rssi->sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD;
}
/* Replies to an app's D-Bus message and unref it */
static void app_reply_msg(struct adv_monitor_app *app, DBusMessage *reply)
{
if (!app || !app->reg || !reply)
return;
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(app->reg);
app->reg = NULL;
}
/* Frees a pattern */
static void pattern_free(void *data)
{
struct bt_ad_pattern *pattern = data;
free(pattern);
}
static void merged_pattern_free(void *data)
{
struct adv_monitor_merged_pattern *merged_pattern = data;
queue_destroy(merged_pattern->patterns, pattern_free);
queue_destroy(merged_pattern->monitors, NULL);
if (merged_pattern->manager)
queue_remove(merged_pattern->manager->merged_patterns,
merged_pattern);
free(merged_pattern);
}
/* Returns the smaller of the two integers |a| and |b| which is not equal to the
* |unset| value. If both are unset, return unset.
*/
static int get_smaller_not_unset(int a, int b, int unset)
{
if (a == unset)
return b;
if (b == unset)
return a;
return a < b ? a : b;
}
/* Merges two RSSI parameters, return the result. The result is chosen to be
* whichever is more lenient of the two inputs, so we can pass that to the
* kernel and still do additional filtering in the user space without loss of
* information while still receiving benefit from offloading some filtering to
* the hardware.
* It is allowed for |a|, |b|, and |merged| to point to the same object.
*/
static void merge_rssi(const struct rssi_parameters *a,
const struct rssi_parameters *b,
struct rssi_parameters *merged)
{
/* For low rssi, low_timeout, and high_rssi, choose the minimum of the
* two values. Filtering the higher values is done on userspace.
*/
merged->low_rssi = get_smaller_not_unset(a->low_rssi, b->low_rssi,
ADV_MONITOR_UNSET_RSSI);
merged->high_rssi = get_smaller_not_unset(a->high_rssi, b->high_rssi,
ADV_MONITOR_UNSET_RSSI);
merged->low_rssi_timeout = get_smaller_not_unset(a->low_rssi_timeout,
b->low_rssi_timeout,
ADV_MONITOR_UNSET_TIMEOUT);
/* High timeout doesn't matter for now, it will be zeroed when it is
* forwarded to kernel anyway.
*/
merged->high_rssi_timeout = 0;
/* Sampling period is not implemented yet in userspace. There is no
* good value if the two values are different, so just choose 0 for
* always reporting, to avoid missing packets.
*/
if (a->sampling_period != b->sampling_period)
merged->sampling_period = 0;
else
merged->sampling_period = a->sampling_period;
}
/* Two merged_pattern are considered equal if all the following are true:
* (1) both has the same monitor_type
* (2) both has exactly the same pattern in the same order
* Therefore, patterns A+B and B+A are considered different, as well as patterns
* A and A+A. This shouldn't cause any issue, but solving this issue is a
* potential improvement.
*/
static bool merged_pattern_is_equal(const void *data, const void *match_data)
{
const struct adv_monitor_merged_pattern *a = data;
const struct adv_monitor_merged_pattern *b = match_data;
const struct queue_entry *a_entry, *b_entry;
struct bt_ad_pattern *a_data, *b_data;
if (a->type != b->type)
return false;
if (queue_length(a->patterns) != queue_length(b->patterns))
return false;
a_entry = queue_get_entries(a->patterns);
b_entry = queue_get_entries(b->patterns);
while (a_entry) {
a_data = a_entry->data;
b_data = b_entry->data;
if (a_data->type != b_data->type ||
a_data->offset != b_data->offset ||
a_data->len != b_data->len ||
memcmp(a_data->data, b_data->data, a_data->len) != 0)
return false;
a_entry = a_entry->next;
b_entry = b_entry->next;
}
return true;
}
static char *get_merged_pattern_state_name(enum merged_pattern_state state)
{
switch (state) {
case MERGED_PATTERN_STATE_ADDING:
return "Adding";
case MERGED_PATTERN_STATE_REMOVING:
return "Removing";
case MERGED_PATTERN_STATE_STABLE:
return "Stable";
}
return NULL;
}
/* Adds a new merged pattern */
static void merged_pattern_add(
struct adv_monitor_merged_pattern *merged_pattern)
{
/* This is only called when no merged_pattern found. Therefore, the
* state must be stable.
*/
if (merged_pattern->current_state != MERGED_PATTERN_STATE_STABLE) {
btd_error(merged_pattern->manager->adapter_id,
"Add merged_pattern request when state is not stable");
return;
}
merged_pattern->current_state = MERGED_PATTERN_STATE_ADDING;
merged_pattern_send_add(merged_pattern);
DBG("Monitor state: %s -> %s",
get_merged_pattern_state_name(merged_pattern->current_state),
get_merged_pattern_state_name(merged_pattern->next_state));
}
/* Removes merged pattern, or queues for removal if busy */
static void merged_pattern_remove(
struct adv_monitor_merged_pattern *merged_pattern)
{
rssi_unset(&merged_pattern->rssi);
/* If we currently are removing, cancel subsequent ADD command if any */
if (merged_pattern->current_state == MERGED_PATTERN_STATE_REMOVING) {
merged_pattern->next_state = MERGED_PATTERN_STATE_STABLE;
goto print_state;
}
/* If stable, we can proceed with removal right away */
if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE) {
merged_pattern->current_state = MERGED_PATTERN_STATE_REMOVING;
merged_pattern_send_remove(merged_pattern);
} else {
/* otherwise queue the removal */
merged_pattern->next_state = MERGED_PATTERN_STATE_REMOVING;
}
print_state:
DBG("Monitor state: %s -> %s",
get_merged_pattern_state_name(merged_pattern->current_state),
get_merged_pattern_state_name(merged_pattern->next_state));
}
/* Replaces (removes and re-adds) merged pattern, or queues it if busy */
static void merged_pattern_replace(
struct adv_monitor_merged_pattern *merged_pattern,
const struct rssi_parameters *rssi)
{
/* If the RSSI are the same then nothing needs to be done, except on
* the case where pattern is being removed. In that case, we need to
* re-add the pattern.
* high_rssi_timeout is purposely left out in the comparison since
* the value is ignored upon submission to kernel.
*/
if (merged_pattern->rssi.high_rssi == rssi->high_rssi &&
merged_pattern->rssi.low_rssi == rssi->low_rssi &&
merged_pattern->rssi.low_rssi_timeout == rssi->low_rssi_timeout &&
merged_pattern->rssi.sampling_period == rssi->sampling_period &&
merged_pattern->current_state != MERGED_PATTERN_STATE_REMOVING &&
merged_pattern->next_state != MERGED_PATTERN_STATE_REMOVING)
return;
merged_pattern->rssi = *rssi;
/* If stable, we can proceed with replacement. */
if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE) {
/* Replacement is done by first removing, then re-adding */
merged_pattern->current_state = MERGED_PATTERN_STATE_REMOVING;
merged_pattern->next_state = MERGED_PATTERN_STATE_ADDING;
merged_pattern_send_remove(merged_pattern);
} else {
/* otherwise queue the replacement */
merged_pattern->next_state = MERGED_PATTERN_STATE_ADDING;
}
DBG("Monitor state: %s -> %s",
get_merged_pattern_state_name(merged_pattern->current_state),
get_merged_pattern_state_name(merged_pattern->next_state));
}
/* Current_state of merged_pattern is done, proceed to the next_state */
static void merged_pattern_process_next_step(
struct adv_monitor_merged_pattern *mp)
{
if (mp->current_state == MERGED_PATTERN_STATE_STABLE) {
btd_error(mp->manager->adapter_id,
"Merged pattern invalid current state");
return;
}
if (mp->current_state == MERGED_PATTERN_STATE_REMOVING) {
/* We might need to follow-up with re-adding the pattern */
if (mp->next_state == MERGED_PATTERN_STATE_ADDING) {
mp->current_state = MERGED_PATTERN_STATE_ADDING;
mp->next_state = MERGED_PATTERN_STATE_STABLE;
merged_pattern_send_add(mp);
goto print_state;
}
/* We should never end up with remove-remove sequence */
if (mp->next_state == MERGED_PATTERN_STATE_REMOVING)
btd_error(mp->manager->adapter_id,
"Merged pattern can't be removed again");
/* No more operations */
mp->current_state = MERGED_PATTERN_STATE_STABLE;
mp->next_state = MERGED_PATTERN_STATE_STABLE;
goto print_state;
}
/* current_state == MERGED_PATTERN_STATE_ADDING */
if (mp->next_state == MERGED_PATTERN_STATE_REMOVING) {
mp->current_state = MERGED_PATTERN_STATE_REMOVING;
mp->next_state = MERGED_PATTERN_STATE_STABLE;
merged_pattern_send_remove(mp);
goto print_state;
} else if (mp->next_state == MERGED_PATTERN_STATE_ADDING) {
/* To re-add a just added pattern, we need to remove it first */
mp->current_state = MERGED_PATTERN_STATE_REMOVING;
mp->next_state = MERGED_PATTERN_STATE_ADDING;
merged_pattern_send_remove(mp);
goto print_state;
}
/* No more operations */
mp->current_state = MERGED_PATTERN_STATE_STABLE;
mp->next_state = MERGED_PATTERN_STATE_STABLE;
print_state:
DBG("Monitor state: %s -> %s",
get_merged_pattern_state_name(mp->current_state),
get_merged_pattern_state_name(mp->next_state));
}
/* Frees a monitor object */
static void monitor_free(struct adv_monitor *monitor)
{
g_dbus_proxy_unref(monitor->proxy);
g_free(monitor->path);
queue_destroy(monitor->devices, monitor_device_free);
monitor->devices = NULL;
free(monitor);
}
/* Calls Release() method of the remote Adv Monitor */
static void monitor_release(struct adv_monitor *monitor)
{
/* Release() method on a monitor can be called when -
* 1. monitor initialization failed
* 2. app calls UnregisterMonitor and monitors held by app are released,
* it may or may not be activated at this point
* 3. monitor is removed by kernel
*/
if (monitor->state != MONITOR_STATE_FAILED &&
monitor->state != MONITOR_STATE_INITED &&
monitor->state != MONITOR_STATE_ACTIVE &&
monitor->state != MONITOR_STATE_REMOVED) {
return;
}
DBG("Calling Release() on Adv Monitor of owner %s at path %s",
monitor->app->owner, monitor->path);
g_dbus_proxy_method_call(monitor->proxy, "Release", NULL, NULL, NULL,
NULL);
}
/* Removes monitor from the merged_pattern. This would result in removing it
* from the kernel if there is only one such monitor with that pattern.
*/
static void monitor_remove(struct adv_monitor *monitor)
{
struct adv_monitor_app *app = monitor->app;
uint16_t adapter_id = app->manager->adapter_id;
struct adv_monitor_merged_pattern *merged_pattern;
const struct queue_entry *e;
struct rssi_parameters rssi;
/* Monitor from kernel can be removed when -
* 1. monitor object is deleted by app - may or may not be activated
* 2. app is destroyed and monitors held by app are marked as released
*/
if (monitor->state != MONITOR_STATE_INITED &&
monitor->state != MONITOR_STATE_ACTIVE &&
monitor->state != MONITOR_STATE_RELEASED) {
return;
}
monitor->state = MONITOR_STATE_REMOVED;
if (!monitor->merged_pattern) {
btd_error(adapter_id,
"Merged_pattern not found when removing monitor");
return;
}
merged_pattern = monitor->merged_pattern;
monitor->merged_pattern = NULL;
queue_remove(merged_pattern->monitors, monitor);
/* No more monitors - just remove the pattern entirely */
if (queue_length(merged_pattern->monitors) == 0) {
merged_pattern_remove(merged_pattern);
return;
}
/* Calculate the merge result of the RSSIs of the monitors with the
* same pattern, minus the monitor being removed.
*/
rssi_unset(&rssi);
for (e = queue_get_entries(merged_pattern->monitors); e; e = e->next) {
struct adv_monitor *m = e->data;
merge_rssi(&rssi, &m->rssi, &rssi);
}
merged_pattern_replace(merged_pattern, &rssi);
}
/* Destroys monitor object */
static void monitor_destroy(void *data)
{
struct adv_monitor *monitor = data;
if (!monitor)
return;
queue_remove(monitor->app->monitors, monitor);
monitor_release(monitor);
monitor_remove(monitor);
monitor_free(monitor);
}
/* Destroys an app object along with related D-Bus handlers */
static void app_destroy(void *data)
{
struct adv_monitor_app *app = data;
if (!app)
return;
DBG("Destroy Adv Monitor app %s at path %s", app->owner, app->path);
queue_destroy(app->monitors, monitor_destroy);
if (app->reg) {
app_reply_msg(app, btd_error_failed(app->reg,
"Adv Monitor app destroyed"));
}
if (app->client) {
g_dbus_client_set_disconnect_watch(app->client, NULL, NULL);
g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL,
NULL);
g_dbus_client_set_ready_watch(app->client, NULL, NULL);
g_dbus_client_unref(app->client);
}
g_free(app->owner);
g_free(app->path);
free(app);
}
/* Updates monitor state to 'released' */
static void monitor_state_released(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
if (!monitor || (monitor->state != MONITOR_STATE_INITED
&& monitor->state != MONITOR_STATE_ACTIVE))
return;
monitor->state = MONITOR_STATE_RELEASED;
}
/* Updates monitor state to 'active' */
static void monitor_state_active(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
if (!monitor || monitor->state != MONITOR_STATE_INITED)
return;
monitor->state = MONITOR_STATE_ACTIVE;
DBG("Calling Activate() on Adv Monitor of owner %s at path %s",
monitor->app->owner, monitor->path);
g_dbus_proxy_method_call(monitor->proxy, "Activate", NULL,
NULL, NULL, NULL);
}
/* Handles a D-Bus disconnection event of an app */
static void app_disconnect_cb(DBusConnection *conn, void *user_data)
{
struct adv_monitor_app *app = user_data;
if (!app) {
error("Unexpected NULL app object upon app disconnect");
return;
}
btd_info(app->manager->adapter_id,
"Adv Monitor app %s disconnected from D-Bus",
app->owner);
if (queue_remove(app->manager->apps, app)) {
queue_foreach(app->monitors, monitor_state_released, NULL);
app_destroy(app);
}
}
/* Handles the ready signal of Adv Monitor app */
static void app_ready_cb(GDBusClient *client, void *user_data)
{
struct adv_monitor_app *app = user_data;
uint16_t adapter_id = app->manager->adapter_id;
btd_info(adapter_id, "Path %s reserved for Adv Monitor app %s",
app->path, app->owner);
app_reply_msg(app, dbus_message_new_method_return(app->reg));
}
/* Allocates an Adv Monitor */
static struct adv_monitor *monitor_new(struct adv_monitor_app *app,
GDBusProxy *proxy)
{
struct adv_monitor *monitor;
if (!app || !proxy)
return NULL;
monitor = new0(struct adv_monitor, 1);
if (!monitor)
return NULL;
monitor->app = app;
monitor->proxy = g_dbus_proxy_ref(proxy);
monitor->path = g_strdup(g_dbus_proxy_get_path(proxy));
monitor->state = MONITOR_STATE_NEW;
rssi_unset(&monitor->rssi);
monitor->devices = queue_new();
return monitor;
}
/* Matches a monitor based on its D-Bus path */
static bool monitor_match(const void *a, const void *b)
{
const GDBusProxy *proxy = b;
const struct adv_monitor *monitor = a;
if (!proxy || !monitor)
return false;
if (g_strcmp0(g_dbus_proxy_get_path(proxy), monitor->path) != 0)
return false;
return true;
}
/* Retrieves Type from the remote Adv Monitor object, verifies the value and
* update the local Adv Monitor
*/
static bool parse_monitor_type(struct adv_monitor *monitor, const char *path)
{
DBusMessageIter iter;
const struct adv_monitor_type *t;
const char *type_str;
uint16_t adapter_id = monitor->app->manager->adapter_id;
if (!g_dbus_proxy_get_property(monitor->proxy, "Type", &iter)) {
btd_error(adapter_id,
"Failed to retrieve property Type from the "
"Adv Monitor at path %s", path);
return false;
}
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
goto failed;
dbus_message_iter_get_basic(&iter, &type_str);
for (t = supported_types; t->name; t++) {
if (strcmp(t->name, type_str) == 0) {
monitor->merged_pattern->type = t->type;
return true;
}
}
failed:
btd_error(adapter_id,
"Invalid argument of property Type of the Adv Monitor "
"at path %s", path);
return false;
}
/* Retrieves RSSI thresholds and timeouts from the remote Adv Monitor object,
* verifies the values and update the local Adv Monitor
*/
static bool parse_rssi_and_timeout(struct adv_monitor *monitor,
const char *path)
{
DBusMessageIter iter;
GDBusProxy *proxy = monitor->proxy;
int16_t h_rssi = ADV_MONITOR_UNSET_RSSI;
int16_t l_rssi = ADV_MONITOR_UNSET_RSSI;
uint16_t h_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT;
uint16_t l_rssi_timeout = ADV_MONITOR_UNSET_TIMEOUT;
uint16_t sampling_period = ADV_MONITOR_UNSET_SAMPLING_PERIOD;
uint16_t adapter_id = monitor->app->manager->adapter_id;
/* Extract RSSIHighThreshold */
if (g_dbus_proxy_get_property(proxy, "RSSIHighThreshold", &iter)) {
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16)
goto failed;
dbus_message_iter_get_basic(&iter, &h_rssi);
}
/* Extract RSSIHighTimeout */
if (g_dbus_proxy_get_property(proxy, "RSSIHighTimeout", &iter)) {
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16)
goto failed;
dbus_message_iter_get_basic(&iter, &h_rssi_timeout);
}
/* Extract RSSILowThreshold */
if (g_dbus_proxy_get_property(proxy, "RSSILowThreshold", &iter)) {
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16)
goto failed;
dbus_message_iter_get_basic(&iter, &l_rssi);
}
/* Extract RSSILowTimeout */
if (g_dbus_proxy_get_property(proxy, "RSSILowTimeout", &iter)) {
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16)
goto failed;
dbus_message_iter_get_basic(&iter, &l_rssi_timeout);
}
/* Extract RSSISamplingPeriod */
if (g_dbus_proxy_get_property(proxy, "RSSISamplingPeriod", &iter)) {
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16)
goto failed;
dbus_message_iter_get_basic(&iter, &sampling_period);
}
/* Verify the values of RSSIs and their timeouts. All fields should be
* either set to the unset values or are set within valid ranges.
* If the fields are only partially set, we would try our best to fill
* in with some sane values.
*/
if (h_rssi == ADV_MONITOR_UNSET_RSSI &&
l_rssi == ADV_MONITOR_UNSET_RSSI &&
h_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT &&
l_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT &&
sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD) {
goto done;
}
if (l_rssi == ADV_MONITOR_UNSET_RSSI)
l_rssi = ADV_MONITOR_MIN_RSSI;
if (h_rssi == ADV_MONITOR_UNSET_RSSI)
h_rssi = l_rssi;
if (l_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT)
l_rssi_timeout = ADV_MONITOR_DEFAULT_LOW_TIMEOUT;
if (h_rssi_timeout == ADV_MONITOR_UNSET_TIMEOUT)
h_rssi_timeout = ADV_MONITOR_DEFAULT_HIGH_TIMEOUT;
if (sampling_period == ADV_MONITOR_UNSET_SAMPLING_PERIOD)
sampling_period = btd_opts.advmon.rssi_sampling_period;
if (h_rssi < ADV_MONITOR_MIN_RSSI || h_rssi > ADV_MONITOR_MAX_RSSI ||
l_rssi < ADV_MONITOR_MIN_RSSI ||
l_rssi > ADV_MONITOR_MAX_RSSI || h_rssi < l_rssi) {
goto failed;
}
if (h_rssi_timeout < ADV_MONITOR_MIN_TIMEOUT ||
h_rssi_timeout > ADV_MONITOR_MAX_TIMEOUT ||
l_rssi_timeout < ADV_MONITOR_MIN_TIMEOUT ||
l_rssi_timeout > ADV_MONITOR_MAX_TIMEOUT) {
goto failed;
}
if (sampling_period > ADV_MONITOR_MAX_SAMPLING_PERIOD)
goto failed;
monitor->rssi.high_rssi = h_rssi;
monitor->rssi.low_rssi = l_rssi;
monitor->rssi.high_rssi_timeout = h_rssi_timeout;
monitor->rssi.low_rssi_timeout = l_rssi_timeout;
monitor->rssi.sampling_period = sampling_period;
done:
DBG("Adv Monitor at %s initiated with high RSSI threshold %d, high "
"RSSI threshold timeout %d, low RSSI threshold %d, low RSSI "
"threshold timeout %d, sampling period %d", path,
monitor->rssi.high_rssi, monitor->rssi.high_rssi_timeout,
monitor->rssi.low_rssi, monitor->rssi.low_rssi_timeout,
monitor->rssi.sampling_period);
monitor->merged_pattern->rssi = monitor->rssi;
return true;
failed:
btd_error(adapter_id,
"Invalid argument of RSSI thresholds and timeouts "
"of the Adv Monitor at path %s",
path);
return false;
}
/* Retrieves Patterns from the remote Adv Monitor object, verifies the values
* and update the local Adv Monitor
*/
static bool parse_patterns(struct adv_monitor *monitor, const char *path)
{
DBusMessageIter array, array_iter;
uint16_t adapter_id = monitor->app->manager->adapter_id;
if (!g_dbus_proxy_get_property(monitor->proxy, "Patterns", &array)) {
btd_error(adapter_id,
"Failed to retrieve property Patterns from the "
"Adv Monitor at path %s", path);
return false;
}
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&array) !=
DBUS_TYPE_STRUCT) {
goto failed;
}
monitor->merged_pattern->patterns = queue_new();
dbus_message_iter_recurse(&array, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) ==
DBUS_TYPE_STRUCT) {
int value_len;
uint8_t *value;
uint8_t offset, ad_type;
struct bt_ad_pattern *pattern;
DBusMessageIter struct_iter, value_iter;
dbus_message_iter_recurse(&array_iter, &struct_iter);
// Extract start position
if (dbus_message_iter_get_arg_type(&struct_iter) !=
DBUS_TYPE_BYTE) {
goto failed;
}
dbus_message_iter_get_basic(&struct_iter, &offset);
if (!dbus_message_iter_next(&struct_iter))
goto failed;
// Extract AD data type
if (dbus_message_iter_get_arg_type(&struct_iter) !=
DBUS_TYPE_BYTE) {
goto failed;
}
dbus_message_iter_get_basic(&struct_iter, &ad_type);
if (!dbus_message_iter_next(&struct_iter))
goto failed;
// Extract value of a pattern
if (dbus_message_iter_get_arg_type(&struct_iter) !=
DBUS_TYPE_ARRAY) {
goto failed;
}
dbus_message_iter_recurse(&struct_iter, &value_iter);
dbus_message_iter_get_fixed_array(&value_iter, &value,
&value_len);
pattern = bt_ad_pattern_new(ad_type, offset, value_len, value);
if (!pattern)
goto failed;
queue_push_tail(monitor->merged_pattern->patterns, pattern);
dbus_message_iter_next(&array_iter);
}
/* There must be at least one pattern. */
if (queue_isempty(monitor->merged_pattern->patterns))
goto failed;
return true;
failed:
btd_error(adapter_id, "Invalid argument of property Patterns of the "
"Adv Monitor at path %s", path);
return false;
}
/* Processes the content of the remote Adv Monitor */
static bool monitor_process(struct adv_monitor *monitor)
{
const char *path = g_dbus_proxy_get_path(monitor->proxy);
monitor->state = MONITOR_STATE_FAILED;
monitor->merged_pattern = malloc0(sizeof(*monitor->merged_pattern));
monitor->merged_pattern->current_state = MERGED_PATTERN_STATE_STABLE;
monitor->merged_pattern->next_state = MERGED_PATTERN_STATE_STABLE;
if (!parse_monitor_type(monitor, path))
goto fail;
if (!parse_rssi_and_timeout(monitor, path))
goto fail;
if (monitor->merged_pattern->type != MONITOR_TYPE_OR_PATTERNS ||
!parse_patterns(monitor, path))
goto fail;
monitor->state = MONITOR_STATE_INITED;
monitor->merged_pattern->monitors = queue_new();
queue_push_tail(monitor->merged_pattern->monitors, monitor);
return true;
fail:
merged_pattern_free(monitor->merged_pattern);
monitor->merged_pattern = NULL;
return false;
}
static void merged_pattern_destroy_monitors(
struct adv_monitor_merged_pattern *merged_pattern)
{
const struct queue_entry *e;
for (e = queue_get_entries(merged_pattern->monitors); e; e = e->next) {
struct adv_monitor *monitor = e->data;
monitor->merged_pattern = NULL;
monitor_destroy(monitor);
}
}
/* Handles the callback of Remove Adv Monitor command */
static void remove_adv_monitor_cb(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
const struct mgmt_rp_remove_adv_monitor *rp = param;
struct adv_monitor_merged_pattern *merged_pattern = user_data;
if (status != MGMT_STATUS_SUCCESS || !param) {
error("Failed to Remove Adv Monitor with status 0x%02x",
status);
goto fail;
}
if (length < sizeof(*rp)) {
error("Wrong size of Remove Adv Monitor response");
goto fail;
}
DBG("Adv monitor with handle:0x%04x removed from kernel",
le16_to_cpu(rp->monitor_handle));
merged_pattern_process_next_step(merged_pattern);
if (merged_pattern->current_state == MERGED_PATTERN_STATE_STABLE)
merged_pattern_free(merged_pattern);
return;
fail:
merged_pattern_destroy_monitors(merged_pattern);
merged_pattern_free(merged_pattern);
}
/* sends MGMT_OP_REMOVE_ADV_MONITOR */
static void merged_pattern_send_remove(
struct adv_monitor_merged_pattern *merged_pattern)
{
struct mgmt_cp_remove_adv_monitor cp;
struct btd_adv_monitor_manager *manager = merged_pattern->manager;
cp.monitor_handle = cpu_to_le16(merged_pattern->monitor_handle);
if (!mgmt_send(manager->mgmt, MGMT_OP_REMOVE_ADV_MONITOR,
manager->adapter_id, sizeof(cp), &cp,
remove_adv_monitor_cb, merged_pattern, NULL)) {
btd_error(merged_pattern->manager->adapter_id,
"Unable to send Remove Advt Monitor command");
}
}
/* Handles the callback of Add Adv Patterns Monitor command */
static void add_adv_patterns_monitor_cb(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
const struct mgmt_rp_add_adv_patterns_monitor *rp = param;
struct adv_monitor_merged_pattern *merged_pattern = user_data;
uint16_t adapter_id = merged_pattern->manager->adapter_id;
if (status != MGMT_STATUS_SUCCESS || !param) {
btd_error(adapter_id,
"Failed to Add Adv Patterns Monitor with status"
" 0x%02x", status);
goto fail;
}
if (length < sizeof(*rp)) {
btd_error(adapter_id, "Wrong size of Add Adv Patterns Monitor "
"response");
goto fail;
}
merged_pattern->monitor_handle = le16_to_cpu(rp->monitor_handle);
DBG("Adv monitor with handle:0x%04x added",
merged_pattern->monitor_handle);
merged_pattern_process_next_step(merged_pattern);
if (merged_pattern->current_state != MERGED_PATTERN_STATE_STABLE)
return;
queue_foreach(merged_pattern->monitors, monitor_state_active, NULL);
return;
fail:
merged_pattern_destroy_monitors(merged_pattern);
merged_pattern_free(merged_pattern);
}
/* sends MGMT_OP_ADD_ADV_PATTERNS_MONITOR */
static bool merged_pattern_send_add_pattern(
struct adv_monitor_merged_pattern *merged_pattern)
{
struct mgmt_cp_add_adv_monitor *cp = NULL;
uint8_t pattern_count, cp_len;
const struct queue_entry *e;
bool success = true;
pattern_count = queue_length(merged_pattern->patterns);
cp_len = sizeof(*cp) + pattern_count * sizeof(struct mgmt_adv_pattern);
cp = malloc0(cp_len);
if (!cp)
return false;
for (e = queue_get_entries(merged_pattern->patterns); e; e = e->next) {
struct bt_ad_pattern *pattern = e->data;
memcpy(&cp->patterns[cp->pattern_count++], pattern,
sizeof(*pattern));
}
if (!mgmt_send(merged_pattern->manager->mgmt,
MGMT_OP_ADD_ADV_PATTERNS_MONITOR,
merged_pattern->manager->adapter_id, cp_len, cp,
add_adv_patterns_monitor_cb, merged_pattern, NULL)) {
error("Unable to send Add Adv Patterns Monitor command");
success = false;
}
free(cp);
return success;
}
/* sends MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI */
static bool merged_pattern_send_add_pattern_rssi(
struct adv_monitor_merged_pattern *merged_pattern)
{
struct mgmt_cp_add_adv_patterns_monitor_rssi *cp = NULL;
uint8_t pattern_count, cp_len;
const struct queue_entry *e;
bool success = true;
pattern_count = queue_length(merged_pattern->patterns);
cp_len = sizeof(*cp) + pattern_count * sizeof(struct mgmt_adv_pattern);
cp = malloc0(cp_len);
if (!cp)
return false;
cp->rssi.high_threshold = merged_pattern->rssi.high_rssi;
/* High threshold timeout is unsupported in kernel. Value must be 0. */
cp->rssi.high_threshold_timeout = 0;
cp->rssi.low_threshold = merged_pattern->rssi.low_rssi;
cp->rssi.low_threshold_timeout =
htobs(merged_pattern->rssi.low_rssi_timeout);
cp->rssi.sampling_period = merged_pattern->rssi.sampling_period;
for (e = queue_get_entries(merged_pattern->patterns); e; e = e->next) {
struct bt_ad_pattern *pattern = e->data;
memcpy(&cp->patterns[cp->pattern_count++], pattern,
sizeof(*pattern));
}
if (!mgmt_send(merged_pattern->manager->mgmt,
MGMT_OP_ADD_ADV_PATTERNS_MONITOR_RSSI,
merged_pattern->manager->adapter_id, cp_len, cp,
add_adv_patterns_monitor_cb, merged_pattern, NULL)) {
error("Unable to send Add Adv Patterns Monitor RSSI command");
success = false;
}
free(cp);
return success;
}
/* Sends mgmt command to kernel for adding monitor */
static void merged_pattern_send_add(
struct adv_monitor_merged_pattern *merged_pattern)
{
if (rssi_is_unset(&merged_pattern->rssi))
merged_pattern_send_add_pattern(merged_pattern);
else
merged_pattern_send_add_pattern_rssi(merged_pattern);
}
/* Handles an Adv Monitor D-Bus proxy added event */
static void monitor_proxy_added_cb(GDBusProxy *proxy, void *user_data)
{
struct adv_monitor *monitor;
struct adv_monitor_app *app = user_data;
struct adv_monitor_merged_pattern *existing_pattern;
uint16_t adapter_id = app->manager->adapter_id;
const char *path = g_dbus_proxy_get_path(proxy);
const char *iface = g_dbus_proxy_get_interface(proxy);
struct rssi_parameters rssi;
if (strcmp(iface, ADV_MONITOR_INTERFACE) != 0 ||
!g_str_has_prefix(path, app->path)) {
return;
}
if (queue_find(app->monitors, monitor_match, proxy)) {
btd_error(adapter_id,
"Adv Monitor proxy already exists with path %s",
path);
return;
}
monitor = monitor_new(app, proxy);
if (!monitor) {
btd_error(adapter_id,
"Failed to allocate an Adv Monitor for the "
"object at %s", path);
return;
}
if (!monitor_process(monitor)) {
monitor_destroy(monitor);
DBG("Adv Monitor at path %s released due to invalid content",
path);
return;
}
queue_push_tail(app->monitors, monitor);
existing_pattern = queue_find(monitor->app->manager->merged_patterns,
merged_pattern_is_equal,
monitor->merged_pattern);
if (!existing_pattern) {
monitor->merged_pattern->manager = monitor->app->manager;
queue_push_tail(monitor->app->manager->merged_patterns,
monitor->merged_pattern);
merged_pattern_add(monitor->merged_pattern);
} else {
/* Since there is a matching pattern, abandon the one we have */
merged_pattern_free(monitor->merged_pattern);
monitor->merged_pattern = existing_pattern;
queue_push_tail(existing_pattern->monitors, monitor);
merge_rssi(&existing_pattern->rssi, &monitor->rssi, &rssi);
merged_pattern_replace(existing_pattern, &rssi);
/* Stable means request is not forwarded to kernel */
if (existing_pattern->current_state ==
MERGED_PATTERN_STATE_STABLE)
monitor_state_active(monitor, NULL);
}
DBG("Adv Monitor allocated for the object at path %s", path);
}
/* Handles the removal of an Adv Monitor D-Bus proxy */
static void monitor_proxy_removed_cb(GDBusProxy *proxy, void *user_data)
{
struct adv_monitor *monitor;
struct adv_monitor_app *app = user_data;
monitor = queue_find(app->monitors, monitor_match, proxy);
if (!monitor)
return;
DBG("Adv Monitor removed in state %02x with path %s", monitor->state,
monitor->path);
monitor_state_released(monitor, NULL);
monitor_destroy(monitor);
}
/* Creates an app object, initiates it and sets D-Bus event handlers */
static struct adv_monitor_app *app_create(DBusConnection *conn,
DBusMessage *msg, const char *sender,
const char *path,
struct btd_adv_monitor_manager *manager)
{
struct adv_monitor_app *app;
if (!path || !sender || !manager)
return NULL;
app = new0(struct adv_monitor_app, 1);
if (!app)
return NULL;
app->owner = g_strdup(sender);
app->path = g_strdup(path);
app->manager = manager;
app->reg = NULL;
app->client = g_dbus_client_new_full(conn, sender, path, path);
if (!app->client) {
app_destroy(app);
return NULL;
}
app->monitors = queue_new();
app->reg = dbus_message_ref(msg);
g_dbus_client_set_disconnect_watch(app->client, app_disconnect_cb, app);
/* Note that any property changes on a monitor object would not affect
* the content of the corresponding monitor.
*/
g_dbus_client_set_proxy_handlers(app->client, monitor_proxy_added_cb,
monitor_proxy_removed_cb, NULL,
app);
g_dbus_client_set_ready_watch(app->client, app_ready_cb, app);
return app;
}
/* Matches an app based on its owner and path */
static bool app_match(const void *a, const void *b)
{
const struct adv_monitor_app *app = a;
const struct app_match_data *match = b;
if (match->owner && strcmp(app->owner, match->owner))
return false;
if (match->path && strcmp(app->path, match->path))
return false;
return true;
}
/* Handles a RegisterMonitor D-Bus call */
static DBusMessage *register_monitor(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
DBusMessageIter args;
struct app_match_data match;
struct adv_monitor_app *app;
struct btd_adv_monitor_manager *manager = user_data;
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &match.path);
if (!strlen(match.path) || !g_str_has_prefix(match.path, "/"))
return btd_error_invalid_args(msg);
match.owner = dbus_message_get_sender(msg);
if (queue_find(manager->apps, app_match, &match))
return btd_error_already_exists(msg);
app = app_create(conn, msg, match.owner, match.path, manager);
if (!app) {
btd_error(manager->adapter_id,
"Failed to reserve %s for Adv Monitor app %s",
match.path, match.owner);
return btd_error_failed(msg,
"Failed to create Adv Monitor app");
}
queue_push_tail(manager->apps, app);
return NULL;
}
/* Handles UnregisterMonitor D-Bus call */
static DBusMessage *unregister_monitor(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
DBusMessageIter args;
struct app_match_data match;
struct adv_monitor_app *app;
struct btd_adv_monitor_manager *manager = user_data;
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &match.path);
if (!strlen(match.path) || !g_str_has_prefix(match.path, "/"))
return btd_error_invalid_args(msg);
match.owner = dbus_message_get_sender(msg);
app = queue_find(manager->apps, app_match, &match);
if (!app)
return btd_error_does_not_exist(msg);
queue_remove(manager->apps, app);
app_destroy(app);
btd_info(manager->adapter_id,
"Path %s removed along with Adv Monitor app %s",
match.path, match.owner);
return dbus_message_new_method_return(msg);
}
static const GDBusMethodTable adv_monitor_methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("RegisterMonitor",
GDBUS_ARGS({ "application", "o" }),
NULL, register_monitor) },
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("UnregisterMonitor",
GDBUS_ARGS({ "application", "o" }),
NULL, unregister_monitor) },
{ }
};
/* Gets SupportedMonitorTypes property */
static gboolean get_supported_monitor_types(const GDBusPropertyTable *property,
DBusMessageIter *iter,
void *data)
{
DBusMessageIter entry;
const struct adv_monitor_type *t;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING,
&entry);
for (t = supported_types; t->name; t++) {
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
&t->name);
}
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
const struct adv_monitor_feature {
uint32_t mask;
const char *name;
} supported_features[] = {
{ MGMT_ADV_MONITOR_FEATURE_MASK_OR_PATTERNS, "controller-patterns" },
{ }
};
/* Gets SupportedFeatures property */
static gboolean get_supported_features(const GDBusPropertyTable *property,
DBusMessageIter *iter,
void *data)
{
DBusMessageIter entry;
const struct adv_monitor_feature *f;
struct btd_adv_monitor_manager *manager = data;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING,
&entry);
for (f = supported_features; f->name; f++) {
if (manager->supported_features & f->mask) {
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
&f->name);
}
}
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
static const GDBusPropertyTable adv_monitor_properties[] = {
{"SupportedMonitorTypes", "as", get_supported_monitor_types, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL},
{"SupportedFeatures", "as", get_supported_features, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL},
{ }
};
/* Updates monitor state to 'removed' */
static void monitor_state_removed(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
if (!monitor || (monitor->state != MONITOR_STATE_INITED
&& monitor->state != MONITOR_STATE_ACTIVE))
return;
monitor->state = MONITOR_STATE_REMOVED;
monitor->merged_pattern = NULL;
}
/* Remove the matched merged_pattern and remove the monitors */
static void remove_merged_pattern(void *data, void *user_data)
{
struct adv_monitor_merged_pattern *merged_pattern = data;
uint16_t *handle = user_data;
if (!handle)
return;
/* handle = 0 indicates kernel has removed all monitors */
if (handle != 0 && *handle != merged_pattern->monitor_handle)
return;
DBG("Adv monitor with handle:0x%04x removed by kernel",
merged_pattern->monitor_handle);
queue_foreach(merged_pattern->monitors, monitor_state_removed, NULL);
queue_destroy(merged_pattern->monitors, monitor_destroy);
merged_pattern_free(merged_pattern);
}
/* Processes Adv Monitor removed event from kernel */
static void adv_monitor_removed_callback(uint16_t index, uint16_t length,
const void *param, void *user_data)
{
struct btd_adv_monitor_manager *manager = user_data;
const struct mgmt_ev_adv_monitor_removed *ev = param;
uint16_t handle = ev->monitor_handle;
const uint16_t adapter_id = manager->adapter_id;
if (length < sizeof(*ev)) {
btd_error(adapter_id,
"Wrong size of Adv Monitor Removed event");
return;
}
/* Traverse the merged_patterns to find matching pattern */
queue_foreach(manager->merged_patterns, remove_merged_pattern, &handle);
DBG("Adv Monitor removed event with handle 0x%04x processed",
ev->monitor_handle);
}
/* 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);
}
/* Invokes DeviceFound on the matched monitor */
static void notify_device_found_per_monitor(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
struct monitored_device_info *info = user_data;
if (monitor->merged_pattern->monitor_handle == info->monitor_handle) {
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,
info->device, NULL);
}
}
/* Checks all monitors for match in the app to invoke DeviceFound */
static void notify_device_found_per_app(void *data, void *user_data)
{
struct adv_monitor_app *app = data;
queue_foreach(app->monitors, notify_device_found_per_monitor,
user_data);
}
/* Processes Adv Monitor Device Found event from kernel */
static void adv_monitor_device_found_callback(uint16_t index, uint16_t length,
const void *param,
void *user_data)
{
const struct mgmt_ev_adv_monitor_device_found *ev = param;
struct btd_adv_monitor_manager *manager = user_data;
const uint16_t adapter_id = manager->adapter_id;
struct btd_adapter *adapter = manager->adapter;
uint16_t handle = le16_to_cpu(ev->monitor_handle);
struct monitored_device_info info;
const uint8_t *ad_data = NULL;
uint16_t ad_data_len;
uint32_t flags;
char addr[18];
if (length < sizeof(*ev)) {
btd_error(adapter_id,
"Too short Adv Monitor Device Found event");
return;
}
ad_data_len = btohs(ev->ad_data_len);
if (length != sizeof(*ev) + ad_data_len) {
btd_error(adapter_id,
"Wrong size of Adv Monitor Device Found event");
return;
}
if (ad_data_len > 0)
ad_data = ev->ad_data;
flags = le32_to_cpu(ev->flags);
ba2str(&ev->addr.bdaddr, addr);
DBG("hci%u addr %s, rssi %d flags 0x%04x ad_data_len %u",
index, addr, ev->rssi, flags, ad_data_len);
btd_adapter_device_found(adapter, &ev->addr.bdaddr,
ev->addr.type, ev->rssi, flags, ad_data,
ad_data_len, true);
if (handle) {
DBG("Adv Monitor with handle 0x%04x started tracking "
"the device %s", handle, addr);
info.device = btd_adapter_find_device(adapter, &ev->addr.bdaddr,
ev->addr.type);
if (!info.device) {
btd_error(adapter_id, "Device object not found for %s",
addr);
return;
}
/* Check for matched monitor in all apps */
info.monitor_handle = handle;
queue_foreach(manager->apps, notify_device_found_per_app,
&info);
}
}
/* Invokes DeviceLost on the matched monitor */
static void notify_device_lost_per_monitor(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
struct monitored_device_info *info = user_data;
if (monitor->merged_pattern->monitor_handle == info->monitor_handle) {
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,
info->device, NULL);
}
}
/* Checks all monitors for match in the app to invoke DeviceLost */
static void notify_device_lost_per_app(void *data, void *user_data)
{
struct adv_monitor_app *app = data;
queue_foreach(app->monitors, notify_device_lost_per_monitor,
user_data);
}
/* Processes Adv Monitor Device Lost event from kernel */
static void adv_monitor_device_lost_callback(uint16_t index, uint16_t length,
const void *param,
void *user_data)
{
struct btd_adv_monitor_manager *manager = user_data;
const struct mgmt_ev_adv_monitor_device_lost *ev = param;
uint16_t handle = le16_to_cpu(ev->monitor_handle);
const uint16_t adapter_id = manager->adapter_id;
struct btd_adapter *adapter = manager->adapter;
struct monitored_device_info info;
char addr[18];
if (length < sizeof(*ev)) {
btd_error(adapter_id,
"Wrong size of Adv Monitor Device Lost event");
return;
}
ba2str(&ev->addr.bdaddr, addr);
DBG("Adv Monitor with handle 0x%04x stopped tracking the device %s",
handle, addr);
info.device = btd_adapter_find_device(adapter, &ev->addr.bdaddr,
ev->addr.type);
if (!info.device) {
btd_error(adapter_id, "Device object not found for %s", addr);
return;
}
/* Check for matched monitor in all apps */
info.monitor_handle = handle;
queue_foreach(manager->apps, notify_device_lost_per_app, &info);
}
/* Allocates a manager object */
static struct btd_adv_monitor_manager *manager_new(
struct btd_adapter *adapter,
struct mgmt *mgmt)
{
struct btd_adv_monitor_manager *manager;
if (!adapter || !mgmt)
return NULL;
manager = new0(struct btd_adv_monitor_manager, 1);
if (!manager)
return NULL;
manager->adapter = adapter;
manager->mgmt = mgmt_ref(mgmt);
manager->adapter_id = btd_adapter_get_index(adapter);
manager->apps = queue_new();
manager->merged_patterns = queue_new();
mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_REMOVED,
manager->adapter_id, adv_monitor_removed_callback,
manager, NULL);
mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_DEVICE_FOUND,
manager->adapter_id, adv_monitor_device_found_callback,
manager, NULL);
mgmt_register(manager->mgmt, MGMT_EV_ADV_MONITOR_DEVICE_LOST,
manager->adapter_id, adv_monitor_device_lost_callback,
manager, NULL);
return manager;
}
/* Frees a manager object */
static void manager_free(struct btd_adv_monitor_manager *manager)
{
mgmt_unref(manager->mgmt);
queue_destroy(manager->apps, app_destroy);
queue_destroy(manager->merged_patterns, merged_pattern_free);
free(manager);
}
/* Destroys a manager object and unregisters its D-Bus interface */
static void manager_destroy(struct btd_adv_monitor_manager *manager)
{
if (!manager)
return;
g_dbus_unregister_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
ADV_MONITOR_MGR_INTERFACE);
manager_free(manager);
}
/* Initiates manager's members based on the return of
* MGMT_OP_READ_ADV_MONITOR_FEATURES
*/
static void read_adv_monitor_features_cb(uint8_t status, uint16_t length,
const void *param,
void *user_data)
{
const struct mgmt_rp_read_adv_monitor_features *rp = param;
struct btd_adv_monitor_manager *manager = user_data;
if (status != MGMT_STATUS_SUCCESS || !param) {
btd_error(manager->adapter_id,
"Failed to Read Adv Monitor Features with "
"status 0x%02x", status);
return;
}
if (length < sizeof(*rp)) {
btd_error(manager->adapter_id,
"Wrong size of Read Adv Monitor Features "
"response");
return;
}
manager->supported_features = le32_to_cpu(rp->supported_features);
manager->enabled_features = le32_to_cpu(rp->enabled_features);
manager->max_num_monitors = le16_to_cpu(rp->max_num_handles);
manager->max_num_patterns = rp->max_num_patterns;
btd_info(manager->adapter_id, "Adv Monitor Manager created with "
"supported features:0x%08x, enabled features:0x%08x, "
"max number of supported monitors:%d, "
"max number of supported patterns:%d",
manager->supported_features, manager->enabled_features,
manager->max_num_monitors, manager->max_num_patterns);
}
/* Creates a manager and registers its D-Bus interface */
struct btd_adv_monitor_manager *btd_adv_monitor_manager_create(
struct btd_adapter *adapter,
struct mgmt *mgmt)
{
struct btd_adv_monitor_manager *manager;
manager = manager_new(adapter, mgmt);
if (!manager)
return NULL;
if (!g_dbus_register_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
ADV_MONITOR_MGR_INTERFACE,
adv_monitor_methods, NULL,
adv_monitor_properties, manager,
NULL)) {
btd_error(manager->adapter_id,
"Failed to register "
ADV_MONITOR_MGR_INTERFACE);
manager_free(manager);
return NULL;
}
if (!mgmt_send(manager->mgmt, MGMT_OP_READ_ADV_MONITOR_FEATURES,
manager->adapter_id, 0, NULL,
read_adv_monitor_features_cb, manager, NULL)) {
btd_error(manager->adapter_id,
"Failed to send Read Adv Monitor Features");
manager_destroy(manager);
return NULL;
}
return manager;
}
/* Destroys a manager and unregisters its D-Bus interface */
void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager)
{
if (!manager)
return;
btd_info(manager->adapter_id, "Destroy Adv Monitor Manager");
manager_destroy(manager);
}
bool btd_adv_monitor_offload_enabled(struct btd_adv_monitor_manager *manager)
{
if (!manager)
return false;
return !!(manager->enabled_features &
MGMT_ADV_MONITOR_FEATURE_MASK_OR_PATTERNS);
}
/* 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;
struct queue *patterns;
if (!monitor) {
error("Unexpected NULL adv_monitor object upon match");
return;
}
if (monitor->state != MONITOR_STATE_ACTIVE)
return;
if (!monitor->merged_pattern)
return;
patterns = monitor->merged_pattern->patterns;
if (monitor->merged_pattern->type == MONITOR_TYPE_OR_PATTERNS &&
bt_ad_pattern_match(info->ad, 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) {
timeout_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;
}
/* Handles a situation where the device goes offline/out-of-range */
static bool handle_device_lost_timeout(gpointer user_data)
{
struct adv_monitor_device *dev = user_data;
struct adv_monitor *monitor = dev->monitor;
DBG("Device Lost timeout triggered for device %p. Calling DeviceLost() "
"on Adv Monitor of owner %s at path %s", dev->device,
monitor->app->owner, monitor->path);
g_dbus_proxy_method_call(monitor->proxy, "DeviceLost",
report_device_state_setup,
NULL, dev->device, NULL);
dev->lost_timer = 0;
dev->found = false;
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 (rssi_is_unset(&monitor->rssi)) {
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) {
timeout_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->rssi.high_rssi_timeout) {
dev->high_rssi_first_seen = 0;
}
if (difftime(curr_time, dev->last_seen) >
monitor->rssi.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->rssi.high_rssi) {
if (dev->high_rssi_first_seen) {
if (difftime(curr_time, dev->high_rssi_first_seen) >=
monitor->rssi.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->rssi.low_rssi) {
if (dev->low_rssi_first_seen) {
if (difftime(curr_time, dev->low_rssi_first_seen) >=
monitor->rssi.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 =
timeout_add_seconds(monitor->rssi.low_rssi_timeout,
handle_device_lost_timeout, dev,
NULL);
}
}
/* Clears running DeviceLost timer for a given device */
static void clear_device_lost_timer(void *data, void *user_data)
{
struct adv_monitor_device *dev = data;
struct adv_monitor *monitor = NULL;
if (dev->lost_timer) {
timeout_remove(dev->lost_timer);
dev->lost_timer = 0;
monitor = dev->monitor;
DBG("Calling DeviceLost() for device %p on Adv Monitor "
"of owner %s at path %s", dev->device,
monitor->app->owner, monitor->path);
g_dbus_proxy_method_call(monitor->proxy, "DeviceLost",
report_device_state_setup,
NULL, dev->device, NULL);
}
}
/* Clears running DeviceLost timers from each monitor */
static void clear_lost_timers_from_monitor(void *data, void *user_data)
{
struct adv_monitor *monitor = data;
queue_foreach(monitor->devices, clear_device_lost_timer, NULL);
}
/* Clears running DeviceLost timers from each app */
static void clear_lost_timers_from_app(void *data, void *user_data)
{
struct adv_monitor_app *app = data;
queue_foreach(app->monitors, clear_lost_timers_from_monitor, NULL);
}
/* Handles bt power down scenario */
void btd_adv_monitor_power_down(struct btd_adv_monitor_manager *manager)
{
if (!manager) {
error("Unexpected NULL btd_adv_monitor_manager object upon "
"power down");
return;
}
/* Clear any running DeviceLost timers in case of power down */
queue_foreach(manager->apps, clear_lost_timers_from_app, NULL);
}