// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Google LLC * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/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. * Currenly 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 purposedly 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; bool confirm_name; bool legacy; bool not_connectable; bool name_resolve_failed; 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 = btohl(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); confirm_name = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); not_connectable = (flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); name_resolve_failed = (flags & MGMT_DEV_FOUND_NAME_REQUEST_FAILED); btd_adapter_update_found_device(adapter, &ev->addr.bdaddr, ev->addr.type, ev->rssi, confirm_name, legacy, not_connectable, name_resolve_failed, 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); }