diff --git a/src/lib/common/sol-bus.c b/src/lib/common/sol-bus.c index 7dd6e61f8..9e9b518d4 100644 --- a/src/lib/common/sol-bus.c +++ b/src/lib/common/sol-bus.c @@ -947,3 +947,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..951d1fc60 100644 --- a/src/lib/common/sol-bus.h +++ b/src/lib/common/sol-bus.h @@ -272,3 +272,50 @@ 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, in this case, 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 Represents the final entry in a dictionary parser. + */ +#define SOL_BUS_DICT_ENTRY_FINAL { .name = NULL } + +/** + * @brief Helper to parse D-Bus dictionaries + * + * @see #sol_bus_dict_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); diff --git a/src/lib/comms/include/sol-bluetooth.h b/src/lib/comms/include/sol-bluetooth.h index 394d1d749..305aaba56 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. * @@ -327,6 +336,212 @@ 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 (*cb)(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, + * for that 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. + * + * @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, void *data); + +/** + * @brief Unregisters the agent for the system + * + * @param agent The agent to be unregistered + * @return 0 on sucess, -errno otherwise + */ +int sol_bt_unregister_agent(const struct sol_bt_agent *agent); + +/** + * @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_reply_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_reply_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_reply_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_reply_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_reply_pairing_confirm(struct sol_bt_conn *conn); + +/** + * @brief Replies to a request to the user to enter a pincode + * + * 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_reply_pincode_entry(struct sol_bt_conn *conn, const char *pin); + /** * @} */ diff --git a/src/lib/comms/include/sol-gatt.h b/src/lib/comms/include/sol-gatt.h index 2d3f7feab..ad4f7db5d 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.c b/src/lib/comms/sol-bluetooth-impl-bluez.c index a866e60ff..bd36496f2 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); } @@ -913,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) @@ -922,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) @@ -929,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; @@ -977,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); @@ -1187,6 +1238,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 +1640,613 @@ sol_bt_stop_scan(struct sol_bt_scan_pending *scan) return 0; } +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) { + 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; +} + +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); + + 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 = get_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); + + 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 = get_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; + struct sol_bt_conn *conn; + 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); + + /* 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); + + 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 = get_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 = get_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, 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; + + SOL_NULL_CHECK(agent, -EINVAL); + + if (ctx->agent) + return -EALREADY; + + 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; +} + +SOL_API int +sol_bt_unregister_agent(const struct sol_bt_agent *agent) +{ + 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; + int r; + + SOL_NULL_CHECK(agent, -EINVAL); + + if (!ctx->agent || ctx->agent != agent) + return -EINVAL; + + 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; +} + +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_reply_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_reply_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_reply_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_reply_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_reply_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..893a98b44 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 { @@ -74,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; @@ -97,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-bluetooth-impl-none.c b/src/lib/comms/sol-bluetooth-impl-none.c index 68e09dda9..98feb5ad2 100644 --- a/src/lib/comms/sol-bluetooth-impl-none.c +++ b/src/lib/comms/sol-bluetooth-impl-none.c @@ -78,3 +78,70 @@ 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, void *data) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_unregister_agent(const struct sol_bt_agent *agent) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_reply_passkey_entry(struct sol_bt_conn *conn, uint32_t passkey) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_reply_cancel(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_reply_passkey_confirm(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_reply_pairing_confirm(struct sol_bt_conn *conn) +{ + return -ENOSYS; +} + +SOL_API int +sol_bt_agent_reply_pincode_entry(struct sol_bt_conn *conn, const char *pin) +{ + return -ENOSYS; +} diff --git a/src/lib/comms/sol-gatt-impl-bluez.c b/src/lib/comms/sol-gatt-impl-bluez.c index ae0069a8e..3a0c4a7ef 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 @@ -80,6 +81,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) { @@ -136,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) @@ -147,52 +159,77 @@ 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); } return r; } +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 }, + SOL_BUS_DICT_ENTRY_FINAL + }; + int r; + + *offset = 0; + *path = NULL; + + r = sol_bus_parse_dict(m, dict); + SOL_INT_CHECK(r, < 0, r); + + 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)) @@ -216,7 +253,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: @@ -227,6 +275,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: @@ -240,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) { @@ -494,9 +550,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, }; @@ -508,9 +564,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, }; @@ -777,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; } @@ -870,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; } @@ -907,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; } diff --git a/src/lib/comms/sol-gatt-impl-none.c b/src/lib/comms/sol-gatt-impl-none.c index 2899b81c4..2b412ef8d 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; } @@ -96,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; +} 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/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) diff --git a/src/samples/bluetooth/simple-pair.c b/src/samples/bluetooth/simple-pair.c new file mode 100644 index 000000000..135360e23 --- /dev/null +++ b/src/samples/bluetooth/simple-pair.c @@ -0,0 +1,184 @@ +/* + * 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-log.h" +#include "sol-mainloop.h" +#include "sol-bluetooth.h" +#include "sol-gatt.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); + + SOL_DBG("Paired with %s", 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); + + SOL_INF("Connected to device %.*s", (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); + + SOL_INF("Disconnected from device %.*s", (int)str.used, (char *)str.data); + + auth_conn = NULL; +} + +static bool +timeout_cb(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 +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); + + SOL_INF("device %.*s in range %s", (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, timeout_cb, NULL); + } +} + +static void +pairing_confirm(void *data, struct sol_bt_conn *conn) +{ + SOL_DBG("conn %p auth_conn %p", conn, auth_conn); + + if (auth_conn != conn) { + sol_bt_agent_reply_cancel(conn); + return; + } + + sol_bt_agent_reply_pairing_confirm(conn); +} + +static struct sol_bt_agent agent = { + .pairing_confirm = pairing_confirm, +}; + +static void +enabled(void *data, bool powered) +{ + int r; + + if (!powered) + return; + + SOL_INF("Bluetooth Adapter enabled"); + + scan = sol_bt_start_scan(SOL_BT_TRANSPORT_ALL, found_device, NULL); + SOL_NULL_CHECK(scan); + + r = sol_bt_register_agent(&agent, NULL); + if (r < 0) { + SOL_WRN("r (%d) < 0", r); + sol_quit_with_code(r); + } +} + +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_unregister_agent(&agent); +} + +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(enabled, NULL); + if (!session) { + SOL_WRN("Couldn't create a Bluetooth session"); + sol_quit_with_code(-ENOMEM); + } +} + +SOL_MAIN_DEFAULT(startup, shutdown);