From 35e1b9abe2efa867752e0a65abca1bd88580c149 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Fri, 27 May 2016 17:15:22 -0300 Subject: [PATCH 01/13] sol-bus: Ignore not mapped interfaces and paths In case there's no known handler for a given (interface, path) pair, it's should not be considered an error. Signed-off-by: Vinicius Costa Gomes --- src/lib/common/sol-bus.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/common/sol-bus.c b/src/lib/common/sol-bus.c index 7dd6e61f8..cd0b119af 100644 --- a/src/lib/common/sol-bus.c +++ b/src/lib/common/sol-bus.c @@ -498,7 +498,10 @@ _match_properties_changed(sd_bus_message *m, void *userdata, SOL_INT_CHECK(r, < 0, r); t = find_property_table(client, iface, path); - SOL_NULL_CHECK(t, -ENOENT); + if (!t) { + SOL_DBG("Property table not found for iface %s path %s", iface, path); + return 0; + } /* Ignore PropertiesChanged signals until the GetAll() method returns */ if (t->getall_slot) From 5e7c915ade77c7b78a92bf00f272f27c97b87819 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Tue, 17 May 2016 15:32:24 -0300 Subject: [PATCH 02/13] sol-gatt: Update the none implementation The sol-gatt API has changed, so we must keep the none implementation up to date so it at least compiles. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-gatt-impl-none.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/comms/sol-gatt-impl-none.c b/src/lib/comms/sol-gatt-impl-none.c index 2899b81c4..081000176 100644 --- a/src/lib/comms/sol-gatt-impl-none.c +++ b/src/lib/comms/sol-gatt-impl-none.c @@ -70,17 +70,18 @@ sol_gatt_discover(struct sol_bt_conn *conn, enum sol_gatt_attr_type type, } SOL_API int -sol_gatt_subscribe(struct sol_bt_conn *conn, struct sol_gatt_attr *attr, - bool (*cb)(void *user_data, struct sol_gatt_attr *attr, const struct sol_buffer *buffer), +sol_gatt_subscribe(struct sol_bt_conn *conn, const struct sol_gatt_attr *attr, + bool (*cb)(void *user_data, const struct sol_gatt_attr *attr, + const struct sol_buffer *buffer), const void *user_data) { return -ENOSYS; } SOL_API int -sol_gatt_unsubscribe(struct sol_bt_conn *conn, struct sol_gatt_attr *attr, - bool (*cb)(void *user_data, struct sol_gatt_attr *attr, - const struct sol_buffer *buffer)) +sol_gatt_unsubscribe(bool (*cb)(void *user_data, const struct sol_gatt_attr *attr, + const struct sol_buffer *buffer), + const void *user_data) { return -ENOSYS; } From fff2a2e24ce94b7e74a1fa51a35644a9ad8d29cb Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 12 May 2016 15:57:49 -0300 Subject: [PATCH 03/13] sol-bluetooth: Introduce the agent API The agent will be used when request user input, necessary mostly when pairing. The API is heavily based on the Zephyr's API, which maps nicely to the BlueZ D-Bus API. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/include/sol-bluetooth.h | 208 ++++++++++++++++++++++++ src/lib/comms/sol-bluetooth-impl-none.c | 61 +++++++ 2 files changed, 269 insertions(+) diff --git a/src/lib/comms/include/sol-bluetooth.h b/src/lib/comms/include/sol-bluetooth.h index 6e9b2dc9e..e1ba2c561 100644 --- a/src/lib/comms/include/sol-bluetooth.h +++ b/src/lib/comms/include/sol-bluetooth.h @@ -154,6 +154,15 @@ void sol_bt_conn_unref(struct sol_bt_conn *conn); const struct sol_network_link_addr *sol_bt_conn_get_addr( const struct sol_bt_conn *conn); +/** + * @brief Returns the device info associated with a connection. + * + * @param conn The reference to a connection. + * + * @return Information about the device connected + */ +const struct sol_bt_device_info *sol_bt_conn_get_device_info(const struct sol_bt_conn *conn); + /** * @brief Attempts to establish a connection with a remote device. * @@ -335,6 +344,205 @@ struct sol_bt_scan_pending *sol_bt_start_scan( */ int sol_bt_stop_scan(struct sol_bt_scan_pending *handle); +/** + * @brief Initiates a pairing procedure with an device + * + * The callback will not be called if sol_bt_conn_pair_cancel() is called + * and returns successfully. + * + * @param conn Connection with the device to pair + * @param cb Callback to be called when the pairing finishes. + * + * @return 0 on success, -errno otherwise. + */ +int sol_bt_conn_pair(struct sol_bt_conn *conn, + void (*on_pair)(void *user_data, bool success, struct sol_bt_conn *conn), + void *user_data); + +/** + * @brief Cancels a pairing attempt + * + * @param conn Connection in which the pairing was initiated + * + * @return 0 on success, -errno otherwise. + */ +int sol_bt_conn_pair_cancel(struct sol_bt_conn *conn); + +/** + * @brief Forgets a device, removing any stored security key + * + * Removes any security key saved in permanent stoorage associated with + * a device. + * + * @param addr Address of the device to be removed + * + * @return 0 on success, -errno otherwise. + */ +int sol_bt_forget_device(const struct sol_network_link_addr *addr); + +/** + * @brief Represents an Bluetooth agent + * + * The agent is used when user input is necessary, when pairing, for example, we + * may request a passkey to be displayed to the user. + * + * When pairing with devices, the input and output capabilities are taken into account, + * the callbacks not set to NULL are used to determine the input/output capabilities + * of the system. + */ +struct sol_bt_agent { + /** + * @brief Called when a pairing procedure needs to display a passkey. + * + * Indicates that the @a passkey should be displayed to the user. + * + * @param data User data + * @param conn Connection being authenticated + * @param passkey The passkey that needs to be displayed + */ + void (*passkey_display)(void *data, struct sol_bt_conn *conn, uint32_t passkey); + + /** + * @brief Called when a pairing procedure needs a passkey to be input. + * + * Indicates that the user needs to input a passkey. sol_bt_agent_reply_passkey_entry() + * should be called with the input passkey. + * + * @param data User data + * @param conn Connection being authenticated + */ + void (*passkey_entry)(void *data, struct sol_bt_conn *conn); + + /** + * @brief Called when a pairing procedure needs a passkey to be confirmed. + * + * Indicates that the user needs confirm a passkey. sol_bt_agent_reply_passkey_confirm() + * should be called with the input passkey, sol_bt_agent_reply_cancel() should be called + * otherwise. + * + * @param data User data + * @param conn Connection being authenticated + * @param passkey The passkey that needs to be confirmed + */ + void (*passkey_confirm)(void *data, struct sol_bt_conn *conn, uint32_t passkey); + + /** + * @brief Called when the pairing procedure is cancelled by the other party. + * + * @param data User data + * @param conn Connection being authenticated + */ + void (*cancel)(void *data, struct sol_bt_conn *conn); + + /** + * @brief Called when a pairing attempt needs to be confirmed. + * + * Indicates that the user needs to confirm a pairing attempt. + * sol_bt_agent_reply_pairing_confirm() should be called if the pairing is + * confirmed, sol_bt_agent_reply_cancel() otherwise. + * + * @param data User data + * @param conn Connection being authenticated + */ + void (*pairing_confirm)(void *data, struct sol_bt_conn *conn); + + /** + * @brief Called when a pairing procedure needs a pincode to be entered. + * + * Indicates that the user needs to input a pincode. sol_bt_agent_reply_pincode_entry() + * should be called with the input pincode, sol_bt_agent_reply_cancel() otherwise. + * + * This is only used when pairing with legacy Bluetooth devices. + * + * @param data User data + * @param conn Connection being authenticated + * @param highsec Informs that the pincode needs to be 16 digits long. + * + */ + void (*pincode_entry)(void *data, struct sol_bt_conn *conn, bool highsec); +}; + +/** + * @brief Registers an agent for the system + * + * It is only possible to have one agent at a time. Pass NULL to unregister the + * current agent. + * + * @param agent The agent to be registered + * @param data The user data to be passed to each agent callback + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_register_agent(const struct sol_bt_agent *agent, const void *data); + +/** + * @brief Replies to a request to the user to enter a passkey + * + * This should be called after passkey_entry() is called with the passkey + * entered by the user. + * + * @param conn The connection to be authenticated + * @param passkey The passkey entered by the user + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_passkey_entry(struct sol_bt_conn *conn, uint32_t passkey); + +/** + * @brief Informs that the passkey was displayed to the user + * + * This should be called after passkey_display() is called to inform that + * it is no longer displayed. + * + * @param conn The connection to be authenticated + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_passkey_display(struct sol_bt_conn *conn); + +/** + * @brief Cancels an attempt to authenticate a connection + * + * Rejects the pairing the attempt. + * + * @param conn The connection to be authenticated + * @param passkey The passkey entered by the user + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_cancel(struct sol_bt_conn *conn); + +/** + * @brief Confirms that the same passkey is display in both devices + * + * This should be called after passkey_confirm() is called with the passkey + * to be displayed and confirmed + * + * @param conn The connection to be authenticated + * @param passkey The passkey entered by the user + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_passkey_confirm(struct sol_bt_conn *conn); + +/** + * @brief Confirms the pairing attempt + * + * This should be called after pairing_confirm() indicates a pairing attempt. + * + * @param conn The connection to be authenticated + * @param passkey The passkey entered by the user + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_pairing_confirm(struct sol_bt_conn *conn); + +/** + * @brief Replies to a request to the user to enter a pincode + * + * This should be called after pincode_entry() is called with the pincode + * entered by the user. + * + * @param conn The connection to be authenticated + * @param passkey The passkey entered by the user + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_agent_finish_pincode_entry(struct sol_bt_conn *conn, const char *pin); + /** * @} */ diff --git a/src/lib/comms/sol-bluetooth-impl-none.c b/src/lib/comms/sol-bluetooth-impl-none.c index 68e09dda9..4568ea3f7 100644 --- a/src/lib/comms/sol-bluetooth-impl-none.c +++ b/src/lib/comms/sol-bluetooth-impl-none.c @@ -78,3 +78,64 @@ sol_bt_conn_get_addr(const struct sol_bt_conn *conn) return NULL; } +SOL_API const struct sol_bt_device_info * +sol_bt_conn_get_device_info(const struct sol_bt_conn *conn) +{ + return NULL; +} + +SOL_API int +sol_bt_conn_pair(struct sol_bt_conn *conn, + void (*cb)(void *user_data, bool success, struct sol_bt_conn *conn), + void *user_data) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_conn_pair_cancel(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_forget_device(const struct sol_network_link_addr *addr) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_register_agent(const struct sol_bt_agent *agent, const void *data) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_finish_passkey_entry(struct sol_bt_conn *conn, uint32_t passkey) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_finish_cancel(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_finish_passkey_confirm(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_finish_pairing_confirm(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_finish_pincode_entry(struct sol_bt_conn *conn, const char *pin) +{ + return -ENOSYS; +} From a049837ca9898810337f556fbdb3a37c471fa696 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Fri, 20 May 2016 10:37:26 -0300 Subject: [PATCH 04/13] sol-bluetooth: Implement the agent API for BlueZ The agent will allow to user input to be handled so pairing procedures and the user can properly authorize and provide input. In Bluetooth, depending on the input/output capabilities of the device the pairing may use different procedures with different security characteristics, the choice of the input/output capabilities with the agent API depend on which of the agent callbacks are implemented. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-bluetooth-impl-bluez.c | 590 +++++++++++++++++++++++ src/lib/comms/sol-bluetooth-impl-bluez.h | 9 + 2 files changed, 599 insertions(+) diff --git a/src/lib/comms/sol-bluetooth-impl-bluez.c b/src/lib/comms/sol-bluetooth-impl-bluez.c index 6ca4d5156..6495b9bb0 100644 --- a/src/lib/comms/sol-bluetooth-impl-bluez.c +++ b/src/lib/comms/sol-bluetooth-impl-bluez.c @@ -29,6 +29,8 @@ #include "sol-bluetooth-impl-bluez.h" +#define AGENT_PATH "/org/solettaproject/agent" + struct sol_bt_scan_pending { sd_bus_slot *slot; void (*callback)(void *user_data, const struct sol_bt_device_info *device); @@ -294,6 +296,23 @@ destroy_device(struct device_info *device) free(device); } +static void +destroy_pairing(struct context *ctx, bool success) +{ + if (ctx->pair_cb) + ctx->pair_cb((void *)ctx->pair_user_data, success, ctx->auth_conn); + + ctx->pair_cb = NULL; + + if (ctx->auth_conn) + sol_bt_conn_unref(ctx->auth_conn); + + ctx->auth_conn = NULL; + + ctx->pair_slot = sd_bus_slot_unref(ctx->pair_slot); + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); +} + static struct device_info * find_device_by_path(struct context *ctx, const char *path) { @@ -781,6 +800,9 @@ device_property_changed(void *data, const char *path, uint64_t mask) if (mask & (1 << DEVICE_PROPERTY_CONNECTED)) trigger_bt_conn(ctx, d, info->connected); + if (mask & (1 << DEVICE_PROPERTY_PAIRED)) + destroy_pairing(ctx, info->paired); + if (info->connected && d->resolved) { SOL_PTR_VECTOR_FOREACH_REVERSE_IDX (&d->pending_discoveries, disc, idx) { trigger_gatt_discover(disc); @@ -856,6 +878,9 @@ device_removed(void *data, const char *path) if (conn->d != d) continue; + if (ctx->auth_conn == conn) + destroy_pairing(ctx, false); + destroy_conn(conn); sol_ptr_vector_del(&ctx->conns, idx); } @@ -1187,6 +1212,14 @@ sol_bt_conn_get_addr(const struct sol_bt_conn *conn) return &conn->d->info.addr; } +SOL_API const struct sol_bt_device_info * +sol_bt_conn_get_device_info(const struct sol_bt_conn *conn) +{ + SOL_NULL_CHECK(conn, NULL); + + return &conn->d->info; +} + static void bluez_service_connected(void *data, const char *unique) { @@ -1581,6 +1614,563 @@ sol_bt_stop_scan(struct sol_bt_scan_pending *scan) return 0; } +static struct sol_bt_conn * +find_conn_by_path(struct context *ctx, const char *path) +{ + struct sol_bt_conn *conn; + uint16_t idx; + + SOL_PTR_VECTOR_FOREACH_IDX (&ctx->conns, conn, idx) { + const struct device_info *d = conn->d; + + /* FIXME: need to think about multiple connections */ + if (streq(d->path, path)) + return conn; + } + + return NULL; +} + +static int +agent_release(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + + ctx->register_slot = sd_bus_slot_unref(ctx->register_slot); + ctx->agent_slot = sd_bus_slot_unref(ctx->agent_slot); + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + + ctx->agent = NULL; + ctx->agent_data = NULL; + + return 0; +} + +static int +reject_request(sd_bus_message *m) +{ + sd_bus_message *reply; + int r; + + r = sd_bus_message_new_method_errorf(m, &reply, "org.bluez.Error.Rejected", + "Request was rejected"); + SOL_INT_CHECK(r, < 0, r); + + r = sd_bus_send(NULL, reply, NULL); + + sd_bus_message_unref(reply); + + SOL_INT_CHECK(r, < 0, r); + + return r; +} + +static int +agent_request_pin_code(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + const struct sol_bt_agent *agent = ctx->agent; + const char *path; + int r; + + SOL_NULL_CHECK_GOTO(agent, rejected); + SOL_NULL_CHECK_GOTO(agent->pincode_entry, rejected); + + r = sd_bus_message_read_basic(m, 'o', &path); + SOL_INT_CHECK_GOTO(r, < 0, rejected); + + ctx->auth_conn = find_conn_by_path(ctx, path); + SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); + + ctx->agent_msg = sd_bus_message_ref(m); + + agent->pincode_entry((void *)ctx->agent_data, ctx->auth_conn, false); + + return 0; + +rejected: + return reject_request(m);; +} + +static int +agent_display_pin_code(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + /* + * FIXME: there's no equivalent for this on Zephyr, and it only makes + * sense for legacy keyboards, need to think + */ + return reject_request(m); +} + +static int +agent_request_passkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + const struct sol_bt_agent *agent = ctx->agent; + const char *path; + int r; + + SOL_NULL_CHECK_GOTO(agent, rejected); + SOL_NULL_CHECK_GOTO(agent->passkey_entry, rejected); + + r = sd_bus_message_read_basic(m, 'o', &path); + SOL_INT_CHECK_GOTO(r, < 0, rejected); + + ctx->auth_conn = find_conn_by_path(ctx, path); + SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); + + ctx->agent_msg = sd_bus_message_ref(m); + + agent->passkey_entry((void *)ctx->agent_data, ctx->auth_conn); + + return 0; + +rejected: + return reject_request(m);; +} + +static int +agent_display_passkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + const struct sol_bt_agent *agent = ctx->agent; + const char *path; + uint32_t passkey; + uint16_t entered; + int r; + + SOL_NULL_CHECK_GOTO(agent, rejected); + SOL_NULL_CHECK_GOTO(agent->passkey_display, rejected); + + r = sd_bus_message_read(m, "ouq", &path, &passkey, &entered); + SOL_INT_CHECK_GOTO(r, < 0, rejected); + + ctx->auth_conn = find_conn_by_path(ctx, path); + SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); + + ctx->agent_msg = sd_bus_message_ref(m); + + agent->passkey_display((void *)ctx->agent_data, ctx->auth_conn, passkey); + + return 0; + +rejected: + return reject_request(m);; +} + +static int +agent_request_confirmation(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + const struct sol_bt_agent *agent = ctx->agent; + const char *path; + uint32_t passkey; + int r; + + SOL_NULL_CHECK_GOTO(agent, rejected); + SOL_NULL_CHECK_GOTO(agent->passkey_confirm, rejected); + + r = sd_bus_message_read(m, "ou", &path, &passkey); + SOL_INT_CHECK_GOTO(r, < 0, rejected); + + ctx->auth_conn = find_conn_by_path(ctx, path); + SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); + + ctx->agent_msg = sd_bus_message_ref(m); + + agent->passkey_confirm((void *)ctx->agent_data, ctx->auth_conn, passkey); + + return 0; + +rejected: + return reject_request(m);; +} + +static int +agent_request_authorization(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + const struct sol_bt_agent *agent = ctx->agent; + const char *path; + int r; + + SOL_NULL_CHECK_GOTO(agent, rejected); + SOL_NULL_CHECK_GOTO(agent->pairing_confirm, rejected); + + r = sd_bus_message_read(m, "o", &path); + SOL_INT_CHECK_GOTO(r, < 0, rejected); + + ctx->auth_conn = find_conn_by_path(ctx, path); + SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); + + ctx->agent_msg = sd_bus_message_ref(m); + + agent->pairing_confirm((void *)ctx->agent_data, ctx->auth_conn); + + return 0; + +rejected: + return reject_request(m); +} + +static int +agent_authorize_service(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + /* FIXME: always authorize services? */ + return sd_bus_reply_method_return(m, NULL); +} + +static int +agent_cancel(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + + if (ctx->auth_conn) + sol_bt_conn_unref(ctx->auth_conn); + + ctx->auth_conn = NULL; + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + + return 0; +} + +static const sd_bus_vtable agent_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Release", NULL, NULL, + agent_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RequestPinCode", "o", NULL, + agent_request_pin_code, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("DisplayPinCode", "os", NULL, + agent_display_pin_code, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RequestPasskey", "o", NULL, + agent_request_passkey, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("DisplayPasskey", "ouq", NULL, + agent_display_passkey, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RequestConfirmation", "ou", NULL, + agent_request_confirmation, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RequestAuthorization", "o", NULL, + agent_request_authorization, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("AuthorizeService", "os", NULL, + agent_authorize_service, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Cancel", NULL, NULL, + agent_cancel, SD_BUS_VTABLE_UNPRIVILEGED | SD_BUS_VTABLE_METHOD_NO_REPLY), + SD_BUS_VTABLE_END, +}; + +static const char * +agent_to_capabities(const struct sol_bt_agent *agent) +{ + if (agent->passkey_entry && agent->passkey_display) + return "KeyboardDisplay"; + + if (agent->passkey_confirm && agent->passkey_display) + return "DisplayYesNo"; + + if (agent->passkey_entry) + return "KeyboardOnly"; + + if (agent->passkey_confirm) + return "DisplayOnly"; + + return "NoInputNoOutput"; +} + +static int +register_agent_reply(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + sd_bus *bus = sol_bus_client_get_bus(ctx->bluez); + const char *service = sol_bus_client_get_service(ctx->bluez); + const char *path = AGENT_PATH; + int r; + + ctx->register_slot = sd_bus_slot_unref(ctx->register_slot); + + if (sol_bus_log_callback(reply, userdata, ret_error)) + return -EINVAL; + + /* Don't really care if doesn't get to be the default agent */ + r = sd_bus_call_method_async(bus, NULL, service, "/org/bluez", + "org.bluez.AgentManager1", "RequestDefaultAgent", sol_bus_log_callback, NULL, + "o", path); + SOL_INT_CHECK(r, < 0, r); + + return 0; +} + +SOL_API int +sol_bt_register_agent(const struct sol_bt_agent *agent, const void *data) +{ + struct context *ctx = &context; + sd_bus *bus = sol_bus_client_get_bus(ctx->bluez); + const char *service = sol_bus_client_get_service(ctx->bluez); + const char *path = AGENT_PATH; + const char *capabilities; + int r; + + if (!ctx->agent) + SOL_NULL_CHECK(agent, -EINVAL); + + if (ctx->agent && agent) + return -EEXIST; + + if (!agent) { + /* Removing the current agent */ + ctx->register_slot = sd_bus_slot_unref(ctx->register_slot); + ctx->agent_slot = sd_bus_slot_unref(ctx->agent_slot); + ctx->agent = NULL; + ctx->agent_data = NULL; + + destroy_pairing(ctx, false); + + r = sd_bus_call_method_async(bus, NULL, service, "/org/bluez", + "org.bluez.AgentManager1", "UnregisterAgent", sol_bus_log_callback, NULL, + "o", &path); + SOL_INT_CHECK(r, < 0, r); + + return 0; + } + + r = sd_bus_add_object_vtable(bus, &ctx->agent_slot, path, "org.bluez.Agent1", + agent_vtable, ctx); + SOL_INT_CHECK(r, < 0, -ENOMEM); + + ctx->agent = agent; + ctx->agent_data = data; + + capabilities = agent_to_capabities(agent); + + r = sd_bus_call_method_async(bus, &ctx->register_slot, service, "/org/bluez", + "org.bluez.AgentManager1", "RegisterAgent", register_agent_reply, ctx, + "os", path, capabilities); + SOL_INT_CHECK_GOTO(r, < 0, error_call); + + return 0; + +error_call: + ctx->agent_slot = sd_bus_slot_unref(ctx->agent_slot); + ctx->agent = NULL; + + return r; +} + +static int +conn_pair_reply(sd_bus_message *reply, void *userdata, + sd_bus_error *ret_error) +{ + struct context *ctx = userdata; + int r; + + r = sol_bus_log_callback(reply, userdata, ret_error); + + /* if we didn't receive an error, the pairing succeded */ + destroy_pairing(ctx, r == 0); + + return r; +} + +SOL_API int +sol_bt_conn_pair(struct sol_bt_conn *conn, + void (*cb)(void *user_data, bool success, struct sol_bt_conn *conn), + void *user_data) +{ + struct context *ctx = &context; + sd_bus *bus = sol_bus_client_get_bus(ctx->bluez); + const char *service = sol_bus_client_get_service(ctx->bluez); + struct device_info *d = conn->d; + int r; + + if (ctx->auth_conn) + return -EALREADY; + + if (ctx->pair_slot) + return -EINVAL; + + if (d->info.paired) + return -EALREADY; + + r = sd_bus_call_method_async(bus, &ctx->pair_slot, service, d->path, + "org.bluez.Device1", "Pair", conn_pair_reply, ctx, NULL); + SOL_INT_CHECK(r, < 0, r); + + ctx->pair_cb = cb; + ctx->pair_user_data = user_data; + ctx->auth_conn = sol_bt_conn_ref(conn); + + return 0; +} + +SOL_API int +sol_bt_conn_pair_cancel(struct sol_bt_conn *conn) +{ + struct context *ctx = &context; + + SOL_NULL_CHECK(ctx->auth_conn, -EINVAL); + SOL_NULL_CHECK(ctx->pair_slot, -EINVAL); + + if (ctx->auth_conn != conn) + return -EINVAL; + + ctx->pair_slot = sd_bus_slot_unref(ctx->pair_slot); + + if (ctx->auth_conn) + sol_bt_conn_unref(ctx->auth_conn); + + ctx->auth_conn = NULL; + + return 0; +} + +SOL_API int +sol_bt_forget_device(const struct sol_network_link_addr *addr) +{ + struct context *ctx = &context; + sd_bus *bus = sol_bus_client_get_bus(ctx->bluez); + const char *service = sol_bus_client_get_service(ctx->bluez); + struct device_info *d; + int r; + + SOL_NULL_CHECK(addr, -EINVAL); + + d = find_device_by_addr(ctx, addr); + SOL_NULL_CHECK(d, -ENOENT); + + r = sd_bus_call_method_async(bus, NULL, service, ctx->adapter_path, + "org.bluez.Adapter1", "RemoveDevice", sol_bus_log_callback, NULL, + "o", d->path); + SOL_INT_CHECK(r, < 0, r); + + return 0; +} + +SOL_API int +sol_bt_agent_finish_passkey_entry(struct sol_bt_conn *conn, uint32_t passkey) +{ + struct context *ctx = &context; + const struct sol_bt_agent *agent = ctx->agent; + int r; + + SOL_NULL_CHECK(agent, -EINVAL); + SOL_NULL_CHECK(ctx->agent_msg, -EINVAL); + + if (ctx->auth_conn != conn) { + SOL_WRN("The connection is not the one being authenticated"); + return -EINVAL; + } + + r = sd_bus_reply_method_return(ctx->agent_msg, "u", &passkey); + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + + return r; +} + +SOL_API int +sol_bt_agent_finish_cancel(struct sol_bt_conn *conn) +{ + struct context *ctx = &context; + sd_bus_message *reply; + int r; + + SOL_NULL_CHECK(conn, -EINVAL); + + if (ctx->auth_conn != conn) { + SOL_WRN("The connection is not the one being authenticated"); + return -EINVAL; + } + + if (ctx->auth_conn) + sol_bt_conn_unref(ctx->auth_conn); + ctx->auth_conn = NULL; + + r = sd_bus_message_new_method_errorf(ctx->agent_msg, &reply, "org.bluez.Error.Canceled", + "Request was canceled"); + SOL_INT_CHECK(r, < 0, r); + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + + r = sd_bus_send(NULL, reply, NULL); + + sd_bus_message_unref(reply); + + SOL_INT_CHECK(r, < 0, r); + + return r; +} + +SOL_API int +sol_bt_agent_finish_passkey_confirm(struct sol_bt_conn *conn) +{ + struct context *ctx = &context; + const struct sol_bt_agent *agent = ctx->agent; + int r; + + SOL_NULL_CHECK(agent, -EINVAL); + SOL_NULL_CHECK(ctx->agent_msg, -EINVAL); + + if (ctx->auth_conn != conn) { + SOL_WRN("The connection is not the one being authenticated"); + return -EINVAL; + } + + r = sd_bus_reply_method_return(ctx->agent_msg, NULL); + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + sol_bt_conn_unref(ctx->auth_conn); + ctx->auth_conn = NULL; + + return r; +} + +SOL_API int +sol_bt_agent_finish_pairing_confirm(struct sol_bt_conn *conn) +{ + struct context *ctx = &context; + const struct sol_bt_agent *agent = ctx->agent; + int r; + + SOL_NULL_CHECK(agent, -EINVAL); + SOL_NULL_CHECK(ctx->agent_msg, -EINVAL); + + if (ctx->auth_conn != conn) { + SOL_WRN("The connection is not the one being authenticated"); + return -EINVAL; + } + + r = sd_bus_reply_method_return(ctx->agent_msg, NULL); + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + sol_bt_conn_unref(ctx->auth_conn); + ctx->auth_conn = NULL; + + return r; +} + +SOL_API int +sol_bt_agent_finish_pincode_entry(struct sol_bt_conn *conn, const char *pin) +{ + struct context *ctx = &context; + const struct sol_bt_agent *agent = ctx->agent; + int r; + + SOL_NULL_CHECK(agent, -EINVAL); + SOL_NULL_CHECK(ctx->agent_msg, -EINVAL); + + if (ctx->auth_conn != conn) { + SOL_WRN("The connection is not the one being authenticated"); + return -EINVAL; + } + + r = sd_bus_reply_method_return(ctx->agent_msg, "s", &pin); + + ctx->agent_msg = sd_bus_message_unref(ctx->agent_msg); + sol_bt_conn_unref(ctx->auth_conn); + ctx->auth_conn = NULL; + + return r; +} + static uint16_t find_subscription_by_attr(const struct sol_gatt_attr *attr) { diff --git a/src/lib/comms/sol-bluetooth-impl-bluez.h b/src/lib/comms/sol-bluetooth-impl-bluez.h index c1f59c521..50414087f 100644 --- a/src/lib/comms/sol-bluetooth-impl-bluez.h +++ b/src/lib/comms/sol-bluetooth-impl-bluez.h @@ -32,6 +32,15 @@ struct context { struct sol_ptr_vector conns; enum adapter_state original_state; enum adapter_state current_state; + const struct sol_bt_agent *agent; + const void *agent_data; + struct sol_bt_conn *auth_conn; + sd_bus_slot *register_slot; + sd_bus_slot *agent_slot; + sd_bus_slot *pair_slot; + void (*pair_cb)(void *user_data, bool success, struct sol_bt_conn *conn); + void *pair_user_data; + sd_bus_message *agent_msg; }; struct sol_bt_conn { From 9afd7d370542347265cf6c255449ee9696c3f98b Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Tue, 17 May 2016 15:37:29 -0300 Subject: [PATCH 05/13] samples/bluetooth: Add a simple-pair sample This sample tries pairing with the device (if provided) implementing only the pairing_confirm() callback, which means that the capabilties would be equivalent to a device which implements the "DisplayYesNo" capability. Signed-off-by: Vinicius Costa Gomes --- src/samples/bluetooth/Makefile | 4 +- src/samples/bluetooth/simple-pair.c | 183 ++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/samples/bluetooth/simple-pair.c diff --git a/src/samples/bluetooth/Makefile b/src/samples/bluetooth/Makefile index 4a8f5c873..fde1e6517 100644 --- a/src/samples/bluetooth/Makefile +++ b/src/samples/bluetooth/Makefile @@ -1,7 +1,9 @@ -sample-$(BLUETOOTH_SAMPLES) += connect-paired heartbeat browse +sample-$(BLUETOOTH_SAMPLES) += connect-paired heartbeat browse simple-pair sample-connect-paired-$(BLUETOOTH_SAMPLES) := connect-paired.c sample-heartbeat-$(BLUETOOTH_SAMPLES) := heartbeat.c sample-browse-$(BLUETOOTH_SAMPLES) := browse.c + +sample-simple-pair-$(BLUETOOTH_SAMPLES) := simple-pair.c diff --git a/src/samples/bluetooth/simple-pair.c b/src/samples/bluetooth/simple-pair.c new file mode 100644 index 000000000..928855c55 --- /dev/null +++ b/src/samples/bluetooth/simple-pair.c @@ -0,0 +1,183 @@ +/* + * This file is part of the Soletta Project + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "soletta.h" +#include "sol-bluetooth.h" +#include "sol-gatt.h" +#include "sol-log.h" +#include "sol-mainloop.h" +#include "sol-util.h" + +static struct sol_bt_scan_pending *scan; +static struct sol_bt_session *session; + +static struct sol_bt_conn *auth_conn; +static struct sol_network_link_addr pair_addr; + +static struct sol_timeout *timeout; + +static void +on_error(void *user_data, int error) +{ + SOL_DBG("error %d", error); + + auth_conn = NULL; +} + +static void +paired_callback(void *user_data, bool success, struct sol_bt_conn *conn) +{ + const struct sol_bt_device_info *info = sol_bt_conn_get_device_info(conn); + + printf("Paired with %s\n", info->name); +} + +static bool +on_connect(void *user_data, struct sol_bt_conn *conn) +{ + SOL_BUFFER_DECLARE_STATIC(str, SOL_BLUETOOTH_ADDR_STRLEN); + int r; + + sol_network_link_addr_to_str(sol_bt_conn_get_addr(conn), &str); + + printf("Connected to device %.*s\n", (int)str.used, (char *)str.data); + + r = sol_bt_conn_pair(conn, paired_callback, NULL); + SOL_INT_CHECK(r, < 0, false); + + return true; +} + +static void +on_disconnect(void *user_data, struct sol_bt_conn *conn) +{ + SOL_BUFFER_DECLARE_STATIC(str, SOL_BLUETOOTH_ADDR_STRLEN); + + sol_network_link_addr_to_str(sol_bt_conn_get_addr(conn), &str); + + printf("Disconnected from device %.*s\n", (int)str.used, (char *)str.data); + + auth_conn = NULL; +} + +static bool +on_timeout(void *data) +{ + auth_conn = sol_bt_connect(&pair_addr, on_connect, on_disconnect, + on_error, NULL); + SOL_NULL_CHECK(auth_conn, false); + + return false; +} + +static void +on_found_device(void *user_data, const struct sol_bt_device_info *device) +{ + SOL_BUFFER_DECLARE_STATIC(str, SOL_BLUETOOTH_ADDR_STRLEN); + const char *addr; + + addr = sol_network_link_addr_to_str(&device->addr, &str); + SOL_NULL_CHECK(addr); + + printf("device %.*s in range %s\n", (int)str.used, (char *)str.data, + device->in_range ? "yes" : "no"); + + if (pair_addr.family != SOL_NETWORK_FAMILY_UNSPEC) { + if (!sol_network_link_addr_eq(&pair_addr, &device->addr)) + return; + + sol_bt_stop_scan(scan); + scan = NULL; + + timeout = sol_timeout_add(500, on_timeout, NULL); + } +} + +static void +pairing_confirm(void *data, struct sol_bt_conn *conn) +{ + if (auth_conn != conn) { + sol_bt_agent_finish_cancel(conn); + return; + } + + sol_bt_agent_finish_pairing_confirm(conn); +} + +static struct sol_bt_agent agent = { + .pairing_confirm = pairing_confirm, +}; + +static void +on_enabled(void *data, bool powered) +{ + int r; + + if (!powered) + return; + + SOL_INF("Bluetooth Adapter enabled"); + + scan = sol_bt_start_scan(SOL_BT_TRANSPORT_ALL, on_found_device, NULL); + SOL_NULL_CHECK(scan); + + r = sol_bt_register_agent(&agent, NULL); + if (r < 0) { + SOL_WRN("%s (%d) < 0", sol_util_strerrora(-r), r); + sol_quit_with_code(EXIT_FAILURE); + } +} + +static void +shutdown(void) +{ + if (auth_conn) + sol_bt_conn_unref(auth_conn); + + if (scan) + sol_bt_stop_scan(scan); + + if (session) + sol_bt_disable(session); + + sol_bt_register_agent(NULL, NULL); +} + +static void +startup(void) +{ + if (sol_argc() > 1) { + const struct sol_network_link_addr *addr; + + addr = sol_network_link_addr_from_str(&pair_addr, sol_argv()[1]); + SOL_NULL_CHECK(addr); + } + + session = sol_bt_enable(on_enabled, NULL); + if (!session) { + SOL_WRN("Couldn't create a Bluetooth session"); + sol_quit_with_code(EXIT_FAILURE); + } +} + +SOL_MAIN_DEFAULT(startup, shutdown); From eaa22022be772b53743ac25c3c03ccbfb67a7726 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 19 May 2016 18:01:37 -0300 Subject: [PATCH 06/13] samples/bluetooth: Print the device name when browsing Signed-off-by: Vinicius Costa Gomes --- src/samples/bluetooth/browse.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/samples/bluetooth/browse.c b/src/samples/bluetooth/browse.c index a66e1dfa5..112a2be06 100644 --- a/src/samples/bluetooth/browse.c +++ b/src/samples/bluetooth/browse.c @@ -91,11 +91,12 @@ static bool on_connect(void *user_data, struct sol_bt_conn *conn) { SOL_BUFFER_DECLARE_STATIC(str, SOL_BLUETOOTH_ADDR_STRLEN); + const struct sol_bt_device_info *info = sol_bt_conn_get_device_info(conn); int r; sol_network_link_addr_to_str(sol_bt_conn_get_addr(conn), &str); - SOL_INF("Connected to device %.*s", (int)str.used, (char *)str.data); + SOL_INF("Connected to device '%s' (%.*s)", info->name, (int)str.used, (char *)str.data); r = sol_gatt_discover(conn, 0, NULL, NULL, print_attr, NULL); SOL_INT_CHECK(r, < 0, false); @@ -107,10 +108,11 @@ static void on_disconnect(void *user_data, struct sol_bt_conn *conn) { SOL_BUFFER_DECLARE_STATIC(str, SOL_BLUETOOTH_ADDR_STRLEN); + const struct sol_bt_device_info *info = sol_bt_conn_get_device_info(conn); sol_network_link_addr_to_str(sol_bt_conn_get_addr(conn), &str); - SOL_INF("Disconnected from device %.*s", (int)str.used, (char *)str.data); + SOL_INF("Disconnected from device '%s' (%.*s)", info->name, (int)str.used, (char *)str.data); browse_conn = NULL; } @@ -134,7 +136,7 @@ found_device(void *user_data, const struct sol_bt_device_info *device) addr = sol_network_link_addr_to_str(&device->addr, &str); SOL_NULL_CHECK(addr); - SOL_INF("device %.*s in range %s", (int)str.used, (char *)str.data, + SOL_INF("device '%s' (%.*s) in range %s", device->name, (int)str.used, (char *)str.data, device->in_range ? "yes" : "no"); if (!device->in_range) From 6c1d2d5c8529685db89fe5c9b8aa07d8efc087b4 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 19 May 2016 18:03:12 -0300 Subject: [PATCH 07/13] sol-gatt: Add sol_gatt_pending_get_conn() This allows the connection in which a GATT operation is happening to be retrieved by the pending handle. This may be useful when the value of characteristic is different depending on the device accessing it, for example. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/include/sol-gatt.h | 10 ++++++++++ src/lib/comms/sol-bluetooth-impl-bluez.h | 3 +++ src/lib/comms/sol-gatt-impl-bluez.c | 8 ++++++++ src/lib/comms/sol-gatt-impl-none.c | 6 ++++++ 4 files changed, 27 insertions(+) diff --git a/src/lib/comms/include/sol-gatt.h b/src/lib/comms/include/sol-gatt.h index da28bf0cc..82edfe429 100644 --- a/src/lib/comms/include/sol-gatt.h +++ b/src/lib/comms/include/sol-gatt.h @@ -141,6 +141,16 @@ struct sol_gatt_pending; const struct sol_gatt_attr *sol_gatt_pending_get_attr( const struct sol_gatt_pending *op); +/** + * @brief Returns the connection referenced by a pending operation + * + * @param op The pending operation + * + * @return reference to a connection + */ +struct sol_bt_conn *sol_gatt_pending_get_conn( + const struct sol_gatt_pending *op); + /** * @brief Representation of a GATT Attribute */ diff --git a/src/lib/comms/sol-bluetooth-impl-bluez.h b/src/lib/comms/sol-bluetooth-impl-bluez.h index 50414087f..893a98b44 100644 --- a/src/lib/comms/sol-bluetooth-impl-bluez.h +++ b/src/lib/comms/sol-bluetooth-impl-bluez.h @@ -83,6 +83,7 @@ enum pending_type { struct sol_gatt_pending { const struct sol_gatt_attr *attr; + struct sol_bt_conn *conn; sd_bus_message *m; sd_bus_slot *slot; enum pending_type type; @@ -106,3 +107,5 @@ uint16_t dbus_string_array_to_flags(enum sol_gatt_attr_type type, sd_bus_message void trigger_gatt_discover(struct pending_discovery *disc); void destroy_pending_discovery(struct pending_discovery *disc); + +struct sol_bt_conn *get_conn_by_path(struct context *ctx, const char *path); diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index ae0069a8e..6a9031138 100644 --- a/src/lib/comms/sol-gatt-impl-bluez.c +++ b/src/lib/comms/sol-gatt-impl-bluez.c @@ -80,6 +80,14 @@ sol_gatt_pending_get_attr(const struct sol_gatt_pending *op) return op->attr; } +SOL_API struct sol_bt_conn * +sol_gatt_pending_get_conn(const struct sol_gatt_pending *op) +{ + SOL_NULL_CHECK(op, NULL); + + return op->conn; +} + static void destroy_pending(struct sol_gatt_pending *op) { diff --git a/src/lib/comms/sol-gatt-impl-none.c b/src/lib/comms/sol-gatt-impl-none.c index 081000176..2b412ef8d 100644 --- a/src/lib/comms/sol-gatt-impl-none.c +++ b/src/lib/comms/sol-gatt-impl-none.c @@ -97,3 +97,9 @@ sol_gatt_notify(struct sol_bt_conn *conn, const struct sol_gatt_attr *attr) { return -ENOSYS; } + +SOL_API struct sol_bt_conn * +sol_gatt_pending_get_conn(const struct sol_gatt_pending *op) +{ + return NULL; +} From 2d135fbce10892ae1be5aa24db907d87d67bc178 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 19 May 2016 18:09:10 -0300 Subject: [PATCH 08/13] sol-bluetooth: Fix receiving pairing attempts When receiving a pairing attempt, it's possible that there aren't any 'sol_bt_conn' objects associated yet, in that case, the connection object must be created. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-bluetooth-impl-bluez.c | 55 ++++++++++++++++++++---- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/src/lib/comms/sol-bluetooth-impl-bluez.c b/src/lib/comms/sol-bluetooth-impl-bluez.c index 6495b9bb0..21dbef33d 100644 --- a/src/lib/comms/sol-bluetooth-impl-bluez.c +++ b/src/lib/comms/sol-bluetooth-impl-bluez.c @@ -1614,20 +1614,41 @@ sol_bt_stop_scan(struct sol_bt_scan_pending *scan) return 0; } -static struct sol_bt_conn * -find_conn_by_path(struct context *ctx, const char *path) +struct sol_bt_conn * +get_conn_by_path(struct context *ctx, const char *path) { struct sol_bt_conn *conn; + struct device_info *d; uint16_t idx; + int r; SOL_PTR_VECTOR_FOREACH_IDX (&ctx->conns, conn, idx) { - const struct device_info *d = conn->d; + d = conn->d; /* FIXME: need to think about multiple connections */ if (streq(d->path, path)) return conn; } + d = find_device_by_path(ctx, path); + SOL_NULL_CHECK(d, NULL); + + if (!d->info.connected) + return NULL; + + conn = calloc(1, sizeof(*conn)); + SOL_NULL_CHECK(conn, NULL); + + conn->d = d; + conn->ref = 1; + + r = sol_ptr_vector_append(&ctx->conns, conn); + SOL_INT_CHECK_GOTO(r, < 0, error_append); + + return conn; + +error_append: + free(conn); return NULL; } @@ -1676,10 +1697,15 @@ agent_request_pin_code(sd_bus_message *m, void *userdata, sd_bus_error *ret_erro SOL_NULL_CHECK_GOTO(agent, rejected); SOL_NULL_CHECK_GOTO(agent->pincode_entry, rejected); + if (ctx->auth_conn) { + SOL_WRN("Pairing procedure already in progress"); + goto rejected; + } + r = sd_bus_message_read_basic(m, 'o', &path); SOL_INT_CHECK_GOTO(r, < 0, rejected); - ctx->auth_conn = find_conn_by_path(ctx, path); + ctx->auth_conn = get_conn_by_path(ctx, path); SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); ctx->agent_msg = sd_bus_message_ref(m); @@ -1713,10 +1739,15 @@ agent_request_passkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error SOL_NULL_CHECK_GOTO(agent, rejected); SOL_NULL_CHECK_GOTO(agent->passkey_entry, rejected); + if (ctx->auth_conn) { + SOL_WRN("Pairing procedure already in progress"); + goto rejected; + } + r = sd_bus_message_read_basic(m, 'o', &path); SOL_INT_CHECK_GOTO(r, < 0, rejected); - ctx->auth_conn = find_conn_by_path(ctx, path); + ctx->auth_conn = get_conn_by_path(ctx, path); SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); ctx->agent_msg = sd_bus_message_ref(m); @@ -1734,6 +1765,7 @@ agent_display_passkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error { struct context *ctx = userdata; const struct sol_bt_agent *agent = ctx->agent; + struct sol_bt_conn *conn; const char *path; uint32_t passkey; uint16_t entered; @@ -1745,7 +1777,14 @@ agent_display_passkey(sd_bus_message *m, void *userdata, sd_bus_error *ret_error r = sd_bus_message_read(m, "ouq", &path, &passkey, &entered); SOL_INT_CHECK_GOTO(r, < 0, rejected); - ctx->auth_conn = find_conn_by_path(ctx, path); + /* The request for displaying the passkey may be called multiple times */ + conn = get_conn_by_path(ctx, path); + if (ctx->auth_conn != conn) { + SOL_WRN("Pairing procedure already in progress"); + goto rejected; + } + + ctx->auth_conn = conn; SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); ctx->agent_msg = sd_bus_message_ref(m); @@ -1773,7 +1812,7 @@ agent_request_confirmation(sd_bus_message *m, void *userdata, sd_bus_error *ret_ r = sd_bus_message_read(m, "ou", &path, &passkey); SOL_INT_CHECK_GOTO(r, < 0, rejected); - ctx->auth_conn = find_conn_by_path(ctx, path); + ctx->auth_conn = get_conn_by_path(ctx, path); SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); ctx->agent_msg = sd_bus_message_ref(m); @@ -1800,7 +1839,7 @@ agent_request_authorization(sd_bus_message *m, void *userdata, sd_bus_error *ret r = sd_bus_message_read(m, "o", &path); SOL_INT_CHECK_GOTO(r, < 0, rejected); - ctx->auth_conn = find_conn_by_path(ctx, path); + ctx->auth_conn = get_conn_by_path(ctx, path); SOL_NULL_CHECK_GOTO(ctx->auth_conn, rejected); ctx->agent_msg = sd_bus_message_ref(m); From aa2b46a56c09df082d6e81d63960a6dbee4ba987 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Thu, 19 May 2016 18:11:44 -0300 Subject: [PATCH 09/13] sol-gatt: Update to the new BlueZ API Since commit "93b64d9ca8a2bb6 doc/gatt-api: Add options dictionary to ReadValue/WriteValue" BlueZ passes a dictionary to its ReadValue()/WriteValue() operations, informing the device which is making the operation and the offset of the operation. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-bluetooth-impl-bluez.c | 30 ++++++++- src/lib/comms/sol-gatt-impl-bluez.c | 80 ++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/lib/comms/sol-bluetooth-impl-bluez.c b/src/lib/comms/sol-bluetooth-impl-bluez.c index 21dbef33d..d8d5b6e02 100644 --- a/src/lib/comms/sol-bluetooth-impl-bluez.c +++ b/src/lib/comms/sol-bluetooth-impl-bluez.c @@ -938,6 +938,20 @@ remote_attr_read_reply(sd_bus_message *m, void *userdata, sd_bus_error *ret_erro return r; } +static int +append_empty_dict(sd_bus_message *m) +{ + int r; + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + SOL_INT_CHECK(r, < 0, r); + + r = sd_bus_message_close_container(m); + SOL_INT_CHECK(r, < 0, r); + + return r; +} + static int remote_attr_read(struct sol_gatt_pending *op, uint16_t offset) @@ -947,6 +961,7 @@ remote_attr_read(struct sol_gatt_pending *op, const char *service = sol_bus_client_get_service(ctx->bluez); const struct sol_gatt_attr *attr = op->attr; const char *interface, *path = attr->_priv; + sd_bus_message *m; int r; if (attr->type == SOL_GATT_ATTR_TYPE_DESCRIPTOR) @@ -954,8 +969,15 @@ remote_attr_read(struct sol_gatt_pending *op, else interface = "org.bluez.GattCharacteristic1"; - r = sd_bus_call_method_async(bus, &op->slot, service, path, - interface, "ReadValue", remote_attr_read_reply, op, NULL); + r = sd_bus_message_new_method_call(bus, &m, service, path, + interface, "ReadValue"); + SOL_INT_CHECK(r, < 0, r); + + /* FIXME: add support for specifying the offset */ + r = append_empty_dict(m); + SOL_INT_CHECK(r, < 0, r); + + r = sd_bus_call_async(bus, &op->slot, m, remote_attr_read_reply, op, 0); SOL_INT_CHECK(r, < 0, r); return 0; @@ -1002,6 +1024,10 @@ remote_attr_write(struct sol_gatt_pending *op, r = sd_bus_message_append_array(m, 'y', buf->data, buf->used); SOL_INT_CHECK_GOTO(r, < 0, done); + /* FIXME: add support for specifying the offset */ + r = append_empty_dict(m); + SOL_INT_CHECK_GOTO(r, < 0, done); + r = sd_bus_call_async(bus, &op->slot, m, remote_attr_write_reply, op, 0); SOL_INT_CHECK_GOTO(r, < 0, done); diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index 6a9031138..ad03fddfc 100644 --- a/src/lib/comms/sol-gatt-impl-bluez.c +++ b/src/lib/comms/sol-gatt-impl-bluez.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -192,15 +193,65 @@ sol_gatt_pending_reply(struct sol_gatt_pending *pending, int error, return r; } +static int +parse_operation_dict(sd_bus_message *m, uint16_t *offset, const char **path) +{ + int r; + + *offset = 0; + *path = NULL; + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + SOL_INT_CHECK(r, < 0, r); + + do { + const char *member; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv"); + if (r <= 0) { + r = 0; + break; + } + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); + SOL_INT_CHECK_GOTO(r, < 0, end); + + if (streq(member, "offset")) { + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "q"); + SOL_INT_CHECK_GOTO(r, < 0, end); + + r = sd_bus_message_read_basic(m, 'q', offset); + SOL_INT_CHECK_GOTO(r, < 0, end); + + } else if (streq(member, "device")) { + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "o"); + SOL_INT_CHECK_GOTO(r, < 0, end); + + r = sd_bus_message_read_basic(m, 'o', path); + SOL_INT_CHECK_GOTO(r, < 0, end); + } else { + r = sd_bus_message_skip(m, "v"); + SOL_INT_CHECK_GOTO(r, < 0, end); + } + } while (1); + +end: + return r; + +} + static int attr_method(enum pending_type type, sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + struct context *ctx = bluetooth_get_context(); struct sol_gatt_attr *attr = userdata; sd_bus_message *reply = NULL; struct sol_gatt_pending *pending; const void *data; size_t len; struct sol_buffer buf; + uint16_t offset; + const char *path; int r = -EINVAL; if (sol_bus_log_callback(m, userdata, ret_error)) @@ -224,7 +275,18 @@ attr_method(enum pending_type type, sd_bus_message *m, void *userdata, sd_bus_er switch (type) { case PENDING_READ: - r = attr->read(pending, 0); + r = parse_operation_dict(m, &offset, &path); + SOL_INT_CHECK_GOTO(r, < 0, error_op); + + /* + * FIXME: need to decide if this is an error if it fails. Right now, + * any BlueZ build earlier than commit: + * 93b64d9ca8a2bb6 doc/gatt-api: Add options dictionary to ReadValue/WriteValue + * would fail. + */ + pending->conn = get_conn_by_path(ctx, path); + + r = attr->read(pending, offset); SOL_INT_CHECK_GOTO(r, < 0, error_op); break; case PENDING_WRITE: @@ -235,6 +297,14 @@ attr_method(enum pending_type type, sd_bus_message *m, void *userdata, sd_bus_er SOL_BUFFER_FLAGS_MEMORY_NOT_OWNED | SOL_BUFFER_FLAGS_NO_NUL_BYTE); buf.used = len; + r = parse_operation_dict(m, &offset, &path); + SOL_INT_CHECK_GOTO(r, < 0, error_op); + + /* + * See note above. + */ + pending->conn = get_conn_by_path(ctx, path); + r = attr->write(pending, &buf, 0); SOL_INT_CHECK_GOTO(r, < 0, error_op); default: @@ -502,9 +572,9 @@ static const sd_bus_vtable characteristic_vtable[] = { SD_BUS_PROPERTY("Service", "o", chr_prop_get_service, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Value", "ay", cached_prop_value, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Flags", "as", chr_prop_get_flags, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_METHOD("ReadValue", NULL, "ay", + SD_BUS_METHOD("ReadValue", "a{sv}", "ay", attr_read_value, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("WriteValue", "ay", NULL, + SD_BUS_METHOD("WriteValue", "aya{sv}", NULL, attr_write_value, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; @@ -516,9 +586,9 @@ static const sd_bus_vtable descriptor_vtable[] = { desc_prop_get_characteristic, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Value", "ay", cached_prop_value, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Flags", "as", desc_prop_get_flags, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_METHOD("ReadValue", NULL, "ay", + SD_BUS_METHOD("ReadValue", "a{sv}", "ay", attr_read_value, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("WriteValue", "ay", NULL, + SD_BUS_METHOD("WriteValue", "aya{sv}", NULL, attr_write_value, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; From f7161663f054b9da0ed1ec1974d74671c6d24d11 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Fri, 20 May 2016 13:32:25 -0300 Subject: [PATCH 10/13] sol-bus: Add a helper to parse dictionaries In D-Bus, there isn't the concept of dictionaries, only dictionary entries and arrays, and using them is pretty common, so it makes sense to provide a helper to parse dictionaries. Signed-off-by: Vinicius Costa Gomes --- src/lib/common/sol-bus.c | 64 ++++++++++++++++++++++++++++++++++++++++ src/lib/common/sol-bus.h | 41 +++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/src/lib/common/sol-bus.c b/src/lib/common/sol-bus.c index cd0b119af..62b86abe0 100644 --- a/src/lib/common/sol-bus.c +++ b/src/lib/common/sol-bus.c @@ -950,3 +950,67 @@ sol_bus_log_callback(sd_bus_message *reply, void *userdata, return -1; } + +SOL_API int +sol_bus_parse_dict(sd_bus_message *m, const struct sol_bus_dict_entry *dict) +{ + int r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + SOL_INT_CHECK(r, < 0, r); + + do { + const struct sol_bus_dict_entry *entry; + const char *member, *contents; + char type; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv"); + if (r <= 0) { + r = 0; + break; + } + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); + SOL_INT_CHECK_GOTO(r, < 0, end); + + r = sd_bus_message_peek_type(m, &type, &contents); + SOL_INT_CHECK_GOTO(r, < 0, end); + + for (entry = dict; entry && entry->name; entry++) { + if (streq(entry->name, member)) + break; + } + + if (entry->name) { + size_t len; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + SOL_INT_CHECK_GOTO(r, < 0, end); + + len = strlen(contents); + + if (entry->type == 'v' && entry->parse_variant) + r = entry->parse_variant(m, entry->value); + else if (len == 1 && entry->type == contents[0]) + r = sd_bus_message_read_basic(m, entry->type, entry->value); + else { + SOL_WRN("Invalid type in message '%s', expecting '%c'", + contents, entry->type); + r = -EINVAL; + } + SOL_INT_CHECK_GOTO(r, < 0, end); + + r = sd_bus_message_exit_container(m); + SOL_INT_CHECK_GOTO(r, < 0, end); + } else { + r = sd_bus_message_skip(m, "v"); + SOL_INT_CHECK_GOTO(r, < 0, end); + } + + r = sd_bus_message_exit_container(m); + SOL_INT_CHECK_GOTO(r, < 0, end); + } while (1); + +end: + return r; +} diff --git a/src/lib/common/sol-bus.h b/src/lib/common/sol-bus.h index 922829b81..9ed829d34 100644 --- a/src/lib/common/sol-bus.h +++ b/src/lib/common/sol-bus.h @@ -272,3 +272,44 @@ int sol_bus_remove_interfaces_watch(struct sol_bus_client *client, */ int sol_bus_log_callback(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); + +/** + * @brief Represents an entry in a D-Bus dictionary + * + * A common idiom in D-Bus is having an array of type 'a{sv}', representing + * a dictionary, this struct and sol_bus_parse_dict() help parsing those + * data structures. + */ +struct sol_bus_dict_entry { + /** + * @brief The name of the dictionary entry + */ + const char *name; + /** + * @brief The type of value of the property in the dictionary + * + * It can be any of the basic types, or 'v' (SD_BUS_TYPE_VARIANT), in + * case the type is not basic, the function parse_variant() will be called. + */ + char type; + /** + * @brief In which place to store the parsed value + */ + void *value; + /** + * @brief Function to be called in case the value being parsed is not basic. + */ + int (*parse_variant)(sd_bus_message *m, void *value); +}; + +/** + * @brief Helper to parse D-Bus dictionaries + * + * @see #sol_bus_dict_entry, the @a dict array must be terminated by an empty entry. + * + * @param m D-Bus message containing the D-Bus dictionary array + * @param dict Dictionary representation, informing how to parse the dictionary. + * + * @return 0 on success, -errno otherwise. + */ +int sol_bus_parse_dict(sd_bus_message *m, const struct sol_bus_dict_entry *dict); From ca66fe5e4832a833f80cf1434e34c465427dc892 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Fri, 20 May 2016 13:33:03 -0300 Subject: [PATCH 11/13] sol-gatt: Use sol_bus_parse_dict() to parse methods arguments Now that sol_bus_parse_dict() is available, we can make use of it. Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-gatt-impl-bluez.c | 40 +++++------------------------ 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index ad03fddfc..ef74f3d99 100644 --- a/src/lib/comms/sol-gatt-impl-bluez.c +++ b/src/lib/comms/sol-gatt-impl-bluez.c @@ -196,48 +196,20 @@ sol_gatt_pending_reply(struct sol_gatt_pending *pending, int error, static int parse_operation_dict(sd_bus_message *m, uint16_t *offset, const char **path) { + const struct sol_bus_dict_entry dict[] = { + { .name = "offset", .type = 'q', .value = offset }, + { .name = "device", .type = 'o', .value = path }, + { }, + }; int r; *offset = 0; *path = NULL; - r = sd_bus_message_enter_container(m, 'a', "{sv}"); + r = sol_bus_parse_dict(m, dict); SOL_INT_CHECK(r, < 0, r); - do { - const char *member; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv"); - if (r <= 0) { - r = 0; - break; - } - - r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); - SOL_INT_CHECK_GOTO(r, < 0, end); - - if (streq(member, "offset")) { - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "q"); - SOL_INT_CHECK_GOTO(r, < 0, end); - - r = sd_bus_message_read_basic(m, 'q', offset); - SOL_INT_CHECK_GOTO(r, < 0, end); - - } else if (streq(member, "device")) { - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "o"); - SOL_INT_CHECK_GOTO(r, < 0, end); - - r = sd_bus_message_read_basic(m, 'o', path); - SOL_INT_CHECK_GOTO(r, < 0, end); - } else { - r = sd_bus_message_skip(m, "v"); - SOL_INT_CHECK_GOTO(r, < 0, end); - } - } while (1); - -end: return r; - } static int From fd5679d500871a65e015ea7f8c5605112ef57c50 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Tue, 24 May 2016 16:49:21 -0300 Subject: [PATCH 12/13] sol-gatt: Fix keeping pending callbacks around There was a problem that some cases of GATT pending callback were being kept around for more time than was necessary. For that to work it was also needed to pay more attention to the lifetime of the buffer passed to sol_gatt_pending_reply(). Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-gatt-impl-bluez.c | 42 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index ef74f3d99..45b7e82ed 100644 --- a/src/lib/comms/sol-gatt-impl-bluez.c +++ b/src/lib/comms/sol-gatt-impl-bluez.c @@ -145,6 +145,9 @@ sol_gatt_pending_reply(struct sol_gatt_pending *pending, int error, case PENDING_NOTIFY: r = -EINVAL; pending->buf = buf; + /* 'buf' if going to be destroyed when the notification is sent */ + buf = NULL; + SOL_NULL_CHECK_GOTO(pending->buf, done); if (attr->type == SOL_GATT_ATTR_TYPE_DESCRIPTOR) @@ -156,37 +159,40 @@ sol_gatt_pending_reply(struct sol_gatt_pending *pending, int error, attr->_priv, interface, "Value", NULL); SOL_INT_CHECK_GOTO(r, < 0, done); break; + case PENDING_REMOTE_READ: pending->read((void *)pending->user_data, true, pending->attr, buf); pending->read = NULL; + sol_ptr_vector_remove(&pending_ops, pending); destroy_pending(pending); - break; + /* Called the pending callback, nothing more to do. */ + return 0; case PENDING_REMOTE_WRITE: pending->write((void *)pending->user_data, true, pending->attr); pending->write = NULL; + sol_ptr_vector_remove(&pending_ops, pending); destroy_pending(pending); - break; - } - - if (!reply) + /* Called the pending callback, nothing more to do. */ return 0; + } - r = sd_bus_send(NULL, reply, NULL); - sd_bus_message_unref(reply); - SOL_INT_CHECK_GOTO(r, < 0, done); +done: + if (buf) + sol_buffer_fini(buf); - return 0; + if (pending->m && !reply) { + if (r) + r = sd_bus_message_new_method_errno(pending->m, &reply, r, NULL); + else + r = sd_bus_message_new_method_return(pending->m, &reply); -done: - if (r && pending->m) { - r = sd_bus_message_new_method_errno(pending->m, &reply, r, NULL); SOL_INT_CHECK(r, < 0, r); + } + if (reply) { r = sd_bus_send(NULL, reply, NULL); - sd_bus_message_unref(reply); - SOL_INT_CHECK(r, < 0, r); } @@ -290,7 +296,7 @@ attr_method(enum pending_type type, sd_bus_message *m, void *userdata, sd_bus_er sol_ptr_vector_del_last(&pending_ops); error_append: - free(pending); + destroy_pending(pending); error: if (r < 0) { @@ -827,7 +833,7 @@ prepare_update(enum pending_type type, const struct sol_gatt_attr *attr) sol_ptr_vector_del_last(&pending_ops); error_append: - free(pending); + destroy_pending(pending); return r; } @@ -920,7 +926,7 @@ sol_gatt_read_attr(struct sol_bt_conn *conn, struct sol_gatt_attr *attr, sol_ptr_vector_del_last(&pending_ops); error_append: - free(pending); + destroy_pending(pending); return r; } @@ -957,6 +963,6 @@ sol_gatt_write_attr(struct sol_bt_conn *conn, struct sol_gatt_attr *attr, sol_ptr_vector_del_last(&pending_ops); error_append: - free(pending); + destroy_pending(pending); return r; } From 33958dce67a5ea989fd9979c034612fd9b7b95d8 Mon Sep 17 00:00:00 2001 From: Vinicius Costa Gomes Date: Fri, 27 May 2016 17:38:55 -0300 Subject: [PATCH 13/13] sol-gatt: Fix possible use of unitialized variable warning Signed-off-by: Vinicius Costa Gomes --- src/lib/comms/sol-gatt-impl-bluez.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index 45b7e82ed..e28b19a57 100644 --- a/src/lib/comms/sol-gatt-impl-bluez.c +++ b/src/lib/comms/sol-gatt-impl-bluez.c @@ -115,7 +115,7 @@ sol_gatt_pending_reply(struct sol_gatt_pending *pending, int error, struct context *ctx = bluetooth_get_context(); const struct sol_gatt_attr *attr; const char *interface; - int r; + int r = 0; SOL_NULL_CHECK(pending, -EINVAL);