@@ -37,14 +37,23 @@
#include "dbus-common.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"
#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_TIMER 0 /* second */
+#define ADV_MONITOR_MIN_TIMER 1 /* second */
+#define ADV_MONITOR_MAX_TIMER 300 /* second */
+
struct btd_adv_monitor_manager {
struct btd_adapter *adapter;
struct mgmt *mgmt;
@@ -65,6 +74,43 @@ struct adv_monitor_app {
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_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;
+ char *path;
+
+ 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 */
+
+ enum monitor_type type; /* MONITOR_TYPE_* */
+ struct queue *patterns;
};
struct app_match_data {
@@ -72,6 +118,14 @@ struct app_match_data {
const char *path;
};
+const struct adv_monitor_type {
+ enum monitor_type type;
+ const char *name;
+} supported_types[] = {
+ { MONITOR_TYPE_OR_PATTERNS, "or_patterns" },
+ { },
+};
+
/* Replies to an app's D-Bus message and unref it */
static void app_reply_msg(struct adv_monitor_app *app, DBusMessage *reply)
{
@@ -83,6 +137,48 @@ static void app_reply_msg(struct adv_monitor_app *app, DBusMessage *reply)
app->reg = NULL;
}
+/* Frees a pattern */
+static void pattern_free(void *data)
+{
+ struct pattern *pattern = data;
+
+ if (!pattern)
+ return;
+
+ free(pattern);
+}
+
+/* Frees a monitor object */
+static void monitor_free(void *data)
+{
+ struct adv_monitor *monitor = data;
+
+ if (!monitor)
+ return;
+
+ g_dbus_proxy_unref(monitor->proxy);
+ g_free(monitor->path);
+
+ queue_destroy(monitor->patterns, pattern_free);
+
+ free(monitor);
+}
+
+/* Calls Release() method of the remote Adv Monitor */
+static void monitor_release(void *data, void *user_data)
+{
+ struct adv_monitor *monitor = data;
+
+ if (!monitor)
+ 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);
+}
+
/* Destroys an app object along with related D-Bus handlers */
static void app_destroy(void *data)
{
@@ -93,6 +189,9 @@ static void app_destroy(void *data)
DBG("Destroy Adv Monitor app %s at path %s", app->owner, app->path);
+ queue_foreach(app->monitors, monitor_release, NULL);
+ queue_destroy(app->monitors, monitor_free);
+
if (app->reg) {
app_reply_msg(app, btd_error_failed(app->reg,
"Adv Monitor app destroyed"));
@@ -135,6 +234,371 @@ static void app_ready_cb(GDBusClient *client, void *user_data)
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;
+
+ monitor->high_rssi = ADV_MONITOR_UNSET_RSSI;
+ monitor->high_rssi_timeout = ADV_MONITOR_UNSET_TIMER;
+ monitor->low_rssi = ADV_MONITOR_UNSET_RSSI;
+ monitor->low_rssi_timeout = ADV_MONITOR_UNSET_TIMER;
+
+ monitor->type = MONITOR_TYPE_NONE;
+ monitor->patterns = NULL;
+
+ 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->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 RSSIThresholdsAndTimers 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 prop_struct, iter;
+ int16_t h_rssi, l_rssi;
+ uint16_t h_rssi_timer, l_rssi_timer;
+ uint16_t adapter_id = monitor->app->manager->adapter_id;
+
+ /* Property RSSIThresholdsAndTimers is optional */
+ if (!g_dbus_proxy_get_property(monitor->proxy,
+ "RSSIThresholdsAndTimers",
+ &prop_struct)) {
+ DBG("Adv Monitor at path %s provides no RSSI thresholds and "
+ "timeouts", path);
+ return true;
+ }
+
+ if (dbus_message_iter_get_arg_type(&prop_struct) != DBUS_TYPE_STRUCT)
+ goto failed;
+
+ dbus_message_iter_recurse(&prop_struct, &iter);
+
+ /* Extract HighRSSIThreshold */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16)
+ goto failed;
+ dbus_message_iter_get_basic(&iter, &h_rssi);
+ if (!dbus_message_iter_next(&iter))
+ goto failed;
+
+ /* Extract HighRSSIThresholdTimer */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16)
+ goto failed;
+ dbus_message_iter_get_basic(&iter, &h_rssi_timer);
+ if (!dbus_message_iter_next(&iter))
+ goto failed;
+
+ /* Extract LowRSSIThreshold */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT16)
+ goto failed;
+ dbus_message_iter_get_basic(&iter, &l_rssi);
+ if (!dbus_message_iter_next(&iter))
+ goto failed;
+
+ /* Extract LowRSSIThresholdTimer */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16)
+ goto failed;
+ dbus_message_iter_get_basic(&iter, &l_rssi_timer);
+
+ /* Verify the values of RSSIs and their timers. For simplicity, we
+ * enforce the all-or-none rule to these fields. In other words, either
+ * all are set to the unset values or all are set within valid ranges.
+ */
+ if (h_rssi == ADV_MONITOR_UNSET_RSSI &&
+ l_rssi == ADV_MONITOR_UNSET_RSSI &&
+ h_rssi_timer == ADV_MONITOR_UNSET_TIMER &&
+ l_rssi_timer == ADV_MONITOR_UNSET_TIMER) {
+ goto done;
+ }
+
+ 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_timer < ADV_MONITOR_MIN_TIMER ||
+ h_rssi_timer > ADV_MONITOR_MAX_TIMER ||
+ l_rssi_timer < ADV_MONITOR_MIN_TIMER ||
+ l_rssi_timer > ADV_MONITOR_MAX_TIMER) {
+ goto failed;
+ }
+
+ monitor->high_rssi = h_rssi;
+ monitor->low_rssi = l_rssi;
+ monitor->high_rssi_timeout = h_rssi_timer;
+ monitor->low_rssi_timeout = l_rssi_timer;
+
+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", path, monitor->high_rssi,
+ monitor->high_rssi_timeout, monitor->low_rssi,
+ monitor->low_rssi_timeout);
+
+ return true;
+
+failed:
+ 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;
+
+ btd_error(adapter_id, "Invalid argument of property "
+ "RSSIThresholdsAndTimers 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;
+ }
+
+ monitor->patterns = queue_new();
+
+ if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&array) !=
+ DBUS_TYPE_STRUCT) {
+ goto failed;
+ }
+
+ 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 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);
+
+ // 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);
+ 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);
+ }
+
+ /* There must be at least one pattern. */
+ if (queue_isempty(monitor->patterns))
+ goto failed;
+
+ return true;
+
+failed:
+ queue_destroy(monitor->patterns, pattern_free);
+ monitor->patterns = NULL;
+
+ 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,
+ struct adv_monitor_app *app)
+{
+ const char *path = g_dbus_proxy_get_path(monitor->proxy);
+
+ monitor->state = MONITOR_STATE_FAILED;
+
+ if (!parse_monitor_type(monitor, path))
+ goto done;
+
+ if (!parse_rssi_and_timeout(monitor, path))
+ goto done;
+
+ if (monitor->type == MONITOR_TYPE_OR_PATTERNS &&
+ parse_patterns(monitor, path)) {
+ monitor->state = MONITOR_STATE_INITED;
+ }
+
+done:
+ return monitor->state != MONITOR_STATE_FAILED;
+}
+
+/* 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;
+ 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);
+
+ 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, app)) {
+ monitor_release(monitor, NULL);
+ monitor_free(monitor);
+ DBG("Adv Monitor at path %s released due to invalid content",
+ path);
+ return;
+ }
+
+ queue_push_tail(app->monitors, monitor);
+
+ 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_remove_if(app->monitors, monitor_match, proxy);
+ if (monitor) {
+ DBG("Adv Monitor removed for the object at path %s",
+ monitor->path);
+
+ /* The object was gone, so we don't need to call Release() */
+ monitor_free(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,
@@ -161,8 +625,17 @@ static struct adv_monitor_app *app_create(DBusConnection *conn,
return NULL;
}
+ app->monitors = queue_new();
+
g_dbus_client_set_disconnect_watch(app->client, app_disconnect_cb, app);
- g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL);
+
+ /* 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);
app->reg = dbus_message_ref(msg);
@@ -269,18 +742,6 @@ static const GDBusMethodTable adv_monitor_methods[] = {
{ }
};
-enum monitor_type {
- MONITOR_TYPE_OR_PATTERNS,
-};
-
-const struct adv_monitor_type {
- enum monitor_type type;
- const char *name;
-} supported_types[] = {
- { MONITOR_TYPE_OR_PATTERNS, "or_patterns" },
- { },
-};
-
/* Gets SupportedMonitorTypes property */
static gboolean get_supported_monitor_types(const GDBusPropertyTable *property,
DBusMessageIter *iter,