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/sol-bluetooth-impl-bluez.c b/src/lib/comms/sol-bluetooth-impl-bluez.c index a866e60ff..85052037f 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,574 @@ 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, 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 -ENOSYS; +} + +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..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 { 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-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; } 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..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);