Skip to content
This repository has been archived by the owner on Jun 27, 2019. It is now read-only.

Add the Bluetooth agent API (for BlueZ) v2 #2074

Closed
wants to merge 11 commits into from
64 changes: 64 additions & 0 deletions src/lib/common/sol-bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
47 changes: 47 additions & 0 deletions src/lib/common/sol-bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, but I'm trying to unify these things with sol-memdesc. Do you think we should try it here, or wouldn't help?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will take a look at sol-memdesc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this level, I don't see much advantage, what I can imagine being useful is something at the level of sd_bus_message, i.e. a D-Bus message representation based on sol-memdesc.

Am I not considering something?


/**
* @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);
215 changes: 215 additions & 0 deletions src/lib/comms/include/sol-bluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor, but const void *data and also state if agent is copied or not, if not mention the memory must be kept alive during the agent lifetime (ie: no stack!)

Also, why not use the pattern "null to unregister"? we use it elsewhere. In this case to prevent mistakes, only accept a non-NULL agent if none was set. See https://github.com/solettaproject/soletta/blob/master/src/lib/io/sol-ipm.c#L235

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed both issues.


/**
* @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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if reply is the best term. Maye dismiss or even better finish, since it may fit better all cases (reply_cancel() is kinda confusing)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like finish, let's see how it looks.


/**
* @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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pin code as a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice this will only be used when pairing with legacy keyboards, when you type the same thing in the devices being paired, some keyboards allow you to type anything, not just numbers...


/**
* @}
*/
Expand Down
10 changes: 10 additions & 0 deletions src/lib/comms/include/sol-gatt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Loading