2013-04-26 20:05:33

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 0/8] Autopair

Hi!
All the comments made by Johan are addressed now. Removed the introduced
GLib dependency and replaced it with clock_gettime(). Removed the
bonding_req.capability field and compute it using the agent field.

Best regards,
Alex.

Alex Deymo (8):
core: Convert the pincode callback to an interable list.
plugins: Extend the pin code callback with the call number
core: Add support for retrying a bonding
core: retry bonding attempt until the iterator reaches the end.
core: Add device_get_class to the public interface.
autopair: Add the autopair plugin.
core: Expose the last bonding attempt timeout on retry
autopair: Try a fixed pincode for keyboards rejecting random codes

Makefile.plugins | 3 +
plugins/autopair.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++
plugins/wiimote.c | 7 ++-
src/adapter.c | 163 ++++++++++++++++++++++++++++++++++++++++++++--------
src/adapter.h | 10 +++-
src/device.c | 135 +++++++++++++++++++++++++++++++++++++++++++
src/device.h | 7 +++
7 files changed, 465 insertions(+), 25 deletions(-)
create mode 100644 plugins/autopair.c

--
1.8.2.1


2013-04-30 08:18:26

by Johan Hedberg

[permalink] [raw]
Subject: Re: [PATCH v4 6/8] autopair: Add the autopair plugin.

Hi Alex,

On Fri, Apr 26, 2013, Alex Deymo wrote:
> +static ssize_t autopair_pincb(struct btd_adapter *adapter,
> + struct btd_device *device,
> + char *pinbuf, gboolean *display,
> + uint32_t attempt)

Another forgotten uint32_t here as well.

Johan

2013-04-30 08:16:58

by Johan Hedberg

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] plugins: Extend the pin code callback with the call number

Hi Alex,

On Fri, Apr 26, 2013, Alex Deymo wrote:
> static ssize_t wii_pincb(struct btd_adapter *adapter, struct btd_device *device,
> - char *pinbuf, gboolean *display)
> + char *pinbuf, gboolean *display, uint32_t attempt)

You've still got uint32_t here.

Johan

2013-04-30 08:16:29

by Johan Hedberg

[permalink] [raw]
Subject: Re: [PATCH v4 1/8] core: Convert the pincode callback to an interable list.

Hi Alex,

On Fri, Apr 26, 2013, Alex Deymo wrote:
> The current pincode callback list on the adapter keeps track of all the
> pincode callbacks registered by a plugin for that adapter and calls each
> one until one provides a pincode for the current bonding. This mechanism
> forgets about what happened with previous bonding attempts and pushes the
> status track to the plugin side.
>
> This patch creates an iterator struct (struct pincb_iter) that keeps track
> of the last function called and the number of times called. This will
> allow to provide more information about the bonding status to the pincode
> callback.
> ---
> src/adapter.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++--------
> src/adapter.h | 4 ++++
> src/device.c | 14 ++++++++++++++
> src/device.h | 1 +
> 4 files changed, 65 insertions(+), 8 deletions(-)
>
> diff --git a/src/adapter.c b/src/adapter.c
> index 6255da6..f343178 100644
> --- a/src/adapter.c
> +++ b/src/adapter.c
> @@ -121,6 +121,12 @@ struct service_auth {
> struct agent *agent; /* NULL for queued auths */
> };
>
> +struct pincb_iter {

What concerns me a bit about this is that you're now introducing the
first symbols exported by adapter.{c.h} that are not name spaced with
btd_adapter* or adapter*. Would it make sense to keep this name spacing
even for the iterator?

> + unsigned int attempt; /* numer of times it() was called */

Just use int and not unsigned int. It's what we typically use for
iterator variables.

Johan

2013-04-27 09:31:08

by David Herrmann

[permalink] [raw]
Subject: Re: [PATCH v4 6/8] autopair: Add the autopair plugin.

Hi Alex

On Fri, Apr 26, 2013 at 10:05 PM, Alex Deymo <[email protected]> wrote:
> The autopair plugin tries standard pincodes for different devices with dumb
> pincodes. It also generates a random 6 digit pincode for keyboards that
> support any pincode but fallbacks to the agent call in case the random
> generated pincode didn't work.
> ---
> Makefile.plugins | 3 ++
> plugins/autopair.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 153 insertions(+)
> create mode 100644 plugins/autopair.c
>
> diff --git a/Makefile.plugins b/Makefile.plugins
> index 44e6eca..3efe849 100644
> --- a/Makefile.plugins
> +++ b/Makefile.plugins
> @@ -5,6 +5,9 @@ builtin_sources += plugins/hostname.c
> builtin_modules += wiimote
> builtin_sources += plugins/wiimote.c
>
> +builtin_modules += autopair
> +builtin_sources += plugins/autopair.c
> +
> if MAINTAINER_MODE
> builtin_modules += gatt_example
> builtin_sources += plugins/gatt-example.c
> diff --git a/plugins/autopair.c b/plugins/autopair.c
> new file mode 100644
> index 0000000..2b7a5ee
> --- /dev/null
> +++ b/plugins/autopair.c
> @@ -0,0 +1,150 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2012 Google Inc.
> + *
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdbool.h>
> +#include <stdlib.h>
> +
> +#include <bluetooth/bluetooth.h>
> +#include <glib.h>
> +
> +#include "plugin.h"
> +#include "adapter.h"
> +#include "device.h"
> +#include "log.h"
> +#include "storage.h"
> +
> +/*
> + * Plugin to handle automatic pairing of devices with reduced user
> + * interaction, including implementing the recommendation of the HID spec
> + * for keyboard devices.
> + *
> + * The plugin works by intercepting the PIN request for devices; if the
> + * device is a keyboard a random six-digit numeric PIN is generated and
> + * returned, flagged for displaying using DisplayPinCode.
> + *
> + */
> +
> +static ssize_t autopair_pincb(struct btd_adapter *adapter,
> + struct btd_device *device,
> + char *pinbuf, gboolean *display,
> + uint32_t attempt)
> +{
> + char addr[18];
> + char pinstr[7];
> + uint32_t class;
> +
> + ba2str(device_get_address(device), addr);
> +
> + class = btd_device_get_class(device);
> +
> + DBG("device %s 0x%x", addr, class);
> +
> + /* This is a class-based pincode guesser. Ignore devices with an unknown
> + * class. */
> + if (class == 0)
> + return 0;
> +
> + switch ((class & 0x1f00) >> 8) {
> + case 0x04: /* Audio/Video */
> + switch ((class & 0xfc) >> 2) {
> + case 0x01: /* Wearable Headset Device */
> + case 0x02: /* Hands-free Device */
> + case 0x06: /* Headphones */
> + case 0x07: /* Portable Audio */
> + case 0x0a: /* HiFi Audio Device */
> + if (attempt > 1)
> + return 0;
> + memcpy(pinbuf, "0000", 4);
> + return 4;
> + break;
> + }
> + break;
> + case 0x05: /* Peripheral */
> + switch ((class & 0xc0) >> 6) {
> + case 0x01: /* Keyboard */
> + case 0x03: /* Combo keyboard/pointing device */
> + if (attempt > 1)
> + return 0;
> + snprintf(pinstr, sizeof(pinstr), "%06d",
> + rand() % 1000000);
> + *display = TRUE;
> + memcpy(pinbuf, pinstr, 6);
> + return 6;
> + break;
> + case 0x02: /* Pointing device */
> + if (attempt > 1)
> + return 0;
> + memcpy(pinbuf, "0000", 4);
> + return 4;
> + break;
> + }
> + break;
> + }
> +
> + return 0;
> +}
> +
> +
> +static int autopair_probe(struct btd_adapter *adapter)
> +{
> + btd_adapter_register_pin_cb(adapter, autopair_pincb);
> +
> + return 0;
> +}
> +
> +static void autopair_remove(struct btd_adapter *adapter)
> +{
> + btd_adapter_unregister_pin_cb(adapter, autopair_pincb);
> +}
> +
> +static struct btd_adapter_driver autopair_driver = {
> + .name = "autopair",
> + .probe = autopair_probe,
> + .remove = autopair_remove,
> +};
> +
> +static int autopair_init(void)
> +{
> + /* Initialize the random seed from /dev/urandom */
> + unsigned int seed = time(NULL);
> + FILE *f = fopen("/dev/urandom", "rb");
> + if (f != NULL) {
> + fread(&seed, sizeof(seed), 1, f);
> + fclose(f);
> + }
> + srand(seed);
> +
> + return btd_register_adapter_driver(&autopair_driver);
> +}
> +
> +static void autopair_exit(void)
> +{
> + btd_unregister_adapter_driver(&autopair_driver);
> +}
> +
> +BLUETOOTH_PLUGIN_DEFINE(autopair, VERSION,
> + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, autopair_init, autopair_exit)

Would you mind choosing a lower priority than default? Or how do you
intend other plugins like plugins/wiimote.c to work in combination
with this? I have seen a lot of 3rd party Wii-Remote based devices
which report nearly random "class" entries. So if the autopair
callback is called _before_ the wiimote callback, pairing will fail.

I have two solutions for this: Integrate wiimote pairing into this
plugin or changing the plugin priorities so the autopair callback is
registered _after_ other pin-callbacks (or reversed, I forgot the
order in which pincbs are called).

I'm ok with both.
Regards
David

2013-04-26 20:05:41

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 8/8] autopair: Try a fixed pincode for keyboards rejecting random codes

This patch makes the autopair plugin try a fixed "0000" pincode for
keyboards that reject the pincode too fast (less than 500ms). This
too short delay rejecting the pincode means that the user didn't
have time to type the random pincode in the bluetooth keyboard and
was the keyboard who actually rejected it.
---
plugins/autopair.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/plugins/autopair.c b/plugins/autopair.c
index 2b7a5ee..c669fcb 100644
--- a/plugins/autopair.c
+++ b/plugins/autopair.c
@@ -87,8 +87,23 @@ static ssize_t autopair_pincb(struct btd_adapter *adapter,
switch ((class & 0xc0) >> 6) {
case 0x01: /* Keyboard */
case 0x03: /* Combo keyboard/pointing device */
- if (attempt > 1)
+ /* For keyboards rejecting the first random code in less
+ * than 500ms, try a fixed code. */
+ if (attempt > 1 &&
+ device_bonding_last_duration(device) < 500) {
+ /* Don't try more than one dumb code */
+ if (attempt > 2)
+ return 0;
+ /* Try "0000" as the code for the second
+ * attempt. */
+ memcpy(pinbuf, "0000", 4);
+ return 4;
+ }
+
+ /* Never try more than 3 random pincodes. */
+ if (attempt >= 4)
return 0;
+
snprintf(pinstr, sizeof(pinstr), "%06d",
rand() % 1000000);
*display = TRUE;
--
1.8.2.1

2013-04-26 20:05:40

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 7/8] core: Expose the last bonding attempt timeout on retry

One of the interesting values for pincode plugins is the timeout of a
bonding attempt. For keyboards supporting any random pincode as long as
it is also typed in the keyboard, the timeout until it fails is in the
order of the seconds to allow the user type the pincode on the bluetooth
keyboard. In the case of a dumb keyboard accepting only a fixed pincode
the timeout before the keyboard fails is in the order of a fraction of
a second.

This patch computes the elapsed time between the pairing started or the
pin code was sent to the device and the device returns with an error.
This measured duration is exposed in milliseconds to the plugins when
retrying the bonding attempt.
---
src/adapter.c | 19 ++++++++++++++++++-
src/device.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/device.h | 2 ++
3 files changed, 77 insertions(+), 1 deletion(-)

diff --git a/src/adapter.c b/src/adapter.c
index 3250e13..b1a358e 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -4570,10 +4570,22 @@ int btd_adapter_remove_bonding(struct btd_adapter *adapter,
return -EIO;
}

+static void pincode_reply_complete(uint8_t status, uint16_t length,
+ const void *param, void *user_data)
+{
+ struct btd_device *device = user_data;
+
+ /* If the MGMT_OP_PIN_CODE_REPLY command is acknowledged, move the
+ * starting time to that point. This give a better sense of time
+ * evaluating the pincode. */
+ device_bonding_restart_timer(device);
+}
+
int btd_adapter_pincode_reply(struct btd_adapter *adapter,
const bdaddr_t *bdaddr,
const char *pin, size_t pin_len)
{
+ struct btd_device *device;
unsigned int id;
char addr[18];

@@ -4602,9 +4614,14 @@ int btd_adapter_pincode_reply(struct btd_adapter *adapter,
cp.pin_len = pin_len;
memcpy(cp.pin_code, pin, pin_len);

+ /* Since a pincode was requested, update the starting time to
+ * the point where the pincode is provided. */
+ device = adapter_find_device(adapter, bdaddr);
+ device_bonding_restart_timer(device);
+
id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_REPLY,
adapter->dev_id, sizeof(cp), &cp,
- NULL, NULL, NULL);
+ pincode_reply_complete, device, NULL);
}

if (id == 0)
diff --git a/src/device.c b/src/device.c
index 8b8efc2..6705a38 100644
--- a/src/device.c
+++ b/src/device.c
@@ -35,6 +35,7 @@
#include <sys/ioctl.h>
#include <errno.h>
#include <dirent.h>
+#include <time.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
@@ -90,6 +91,8 @@ struct bonding_req {
struct pincb_iter *cb_iter;
uint8_t status;
guint retry_timer;
+ struct timespec attempt_start_time;
+ long last_attempt_duration_ms;
};

typedef enum {
@@ -1442,12 +1445,56 @@ static struct bonding_req *bonding_request_new(DBusMessage *msg,

bonding->cb_iter = pincb_iter_new(device->adapter);

+ /* Marks the bonding start time for the first attempt on request
+ * construction. The following attempts will be updated on
+ * device_bonding_retry. */
+ clock_gettime(CLOCK_MONOTONIC, &bonding->attempt_start_time);
+
if (agent)
bonding->agent = agent_ref(agent);

return bonding;
}

+void device_bonding_restart_timer(struct btd_device *device)
+{
+ if (!device || !device->bonding)
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &device->bonding->attempt_start_time);
+}
+
+static void bonding_request_stop_timer(struct bonding_req *bonding)
+{
+ struct timespec current;
+
+ clock_gettime(CLOCK_MONOTONIC, &current);
+
+ /* Compute the time difference in ms. */
+ bonding->last_attempt_duration_ms =
+ (current.tv_sec - bonding->attempt_start_time.tv_sec) * 1000L +
+ (current.tv_nsec - bonding->attempt_start_time.tv_nsec)
+ / 1000000L;
+}
+
+/* Returns the duration of the last bonding attempt in milliseconds. The
+ * duration is measured starting from the latest of the following three
+ * events and finishing when the Command complete event is received for the
+ * authentication request:
+ * - MGMT_OP_PAIR_DEVICE is sent,
+ * - MGMT_OP_PIN_CODE_REPLY is sent and
+ * - Command complete event is received for the sent MGMT_OP_PIN_CODE_REPLY.
+ */
+long device_bonding_last_duration(struct btd_device *device)
+{
+ struct bonding_req *bonding = device->bonding;
+
+ if (!bonding)
+ return 0;
+
+ return bonding->last_attempt_duration_ms;
+}
+
static void create_bond_req_exit(DBusConnection *conn, void *user_data)
{
struct btd_device *device = user_data;
@@ -3774,6 +3821,12 @@ static gboolean device_bonding_retry(gpointer data)
DBG("retrying bonding");
bonding->retry_timer = 0;

+ /* Restart the bonding timer to the begining of the pairing. If not
+ * pincode request/reply occurs during this retry,
+ * device_bonding_last_duration() will return a consistent value from
+ * this point. */
+ device_bonding_restart_timer(device);
+
if (bonding->agent)
io_cap = agent_get_io_capability(bonding->agent);
else
@@ -3798,6 +3851,10 @@ int device_bonding_attempt_retry(struct btd_device *device)
if (!bonding)
return -EINVAL;

+ /* Mark the end of a bonding attempt to compute the delta for the
+ * retry. */
+ bonding_request_stop_timer(bonding);
+
if (pincb_iter_end(bonding->cb_iter))
return -EINVAL;

diff --git a/src/device.h b/src/device.h
index bc5c84b..5015d5b 100644
--- a/src/device.h
+++ b/src/device.h
@@ -81,6 +81,8 @@ void device_bonding_attempt_failed(struct btd_device *device, uint8_t status);
void device_bonding_failed(struct btd_device *device, uint8_t status);
struct pincb_iter *device_bonding_iter(struct btd_device *device);
int device_bonding_attempt_retry(struct btd_device *device);
+long device_bonding_last_duration(struct btd_device *device);
+void device_bonding_restart_timer(struct btd_device *device);
int device_request_pincode(struct btd_device *device, gboolean secure);
int device_request_passkey(struct btd_device *device);
int device_confirm_passkey(struct btd_device *device, uint32_t passkey,
--
1.8.2.1

2013-04-26 20:05:39

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 6/8] autopair: Add the autopair plugin.

The autopair plugin tries standard pincodes for different devices with dumb
pincodes. It also generates a random 6 digit pincode for keyboards that
support any pincode but fallbacks to the agent call in case the random
generated pincode didn't work.
---
Makefile.plugins | 3 ++
plugins/autopair.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 153 insertions(+)
create mode 100644 plugins/autopair.c

diff --git a/Makefile.plugins b/Makefile.plugins
index 44e6eca..3efe849 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -5,6 +5,9 @@ builtin_sources += plugins/hostname.c
builtin_modules += wiimote
builtin_sources += plugins/wiimote.c

+builtin_modules += autopair
+builtin_sources += plugins/autopair.c
+
if MAINTAINER_MODE
builtin_modules += gatt_example
builtin_sources += plugins/gatt-example.c
diff --git a/plugins/autopair.c b/plugins/autopair.c
new file mode 100644
index 0000000..2b7a5ee
--- /dev/null
+++ b/plugins/autopair.c
@@ -0,0 +1,150 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Google Inc.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <glib.h>
+
+#include "plugin.h"
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "storage.h"
+
+/*
+ * Plugin to handle automatic pairing of devices with reduced user
+ * interaction, including implementing the recommendation of the HID spec
+ * for keyboard devices.
+ *
+ * The plugin works by intercepting the PIN request for devices; if the
+ * device is a keyboard a random six-digit numeric PIN is generated and
+ * returned, flagged for displaying using DisplayPinCode.
+ *
+ */
+
+static ssize_t autopair_pincb(struct btd_adapter *adapter,
+ struct btd_device *device,
+ char *pinbuf, gboolean *display,
+ uint32_t attempt)
+{
+ char addr[18];
+ char pinstr[7];
+ uint32_t class;
+
+ ba2str(device_get_address(device), addr);
+
+ class = btd_device_get_class(device);
+
+ DBG("device %s 0x%x", addr, class);
+
+ /* This is a class-based pincode guesser. Ignore devices with an unknown
+ * class. */
+ if (class == 0)
+ return 0;
+
+ switch ((class & 0x1f00) >> 8) {
+ case 0x04: /* Audio/Video */
+ switch ((class & 0xfc) >> 2) {
+ case 0x01: /* Wearable Headset Device */
+ case 0x02: /* Hands-free Device */
+ case 0x06: /* Headphones */
+ case 0x07: /* Portable Audio */
+ case 0x0a: /* HiFi Audio Device */
+ if (attempt > 1)
+ return 0;
+ memcpy(pinbuf, "0000", 4);
+ return 4;
+ break;
+ }
+ break;
+ case 0x05: /* Peripheral */
+ switch ((class & 0xc0) >> 6) {
+ case 0x01: /* Keyboard */
+ case 0x03: /* Combo keyboard/pointing device */
+ if (attempt > 1)
+ return 0;
+ snprintf(pinstr, sizeof(pinstr), "%06d",
+ rand() % 1000000);
+ *display = TRUE;
+ memcpy(pinbuf, pinstr, 6);
+ return 6;
+ break;
+ case 0x02: /* Pointing device */
+ if (attempt > 1)
+ return 0;
+ memcpy(pinbuf, "0000", 4);
+ return 4;
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+static int autopair_probe(struct btd_adapter *adapter)
+{
+ btd_adapter_register_pin_cb(adapter, autopair_pincb);
+
+ return 0;
+}
+
+static void autopair_remove(struct btd_adapter *adapter)
+{
+ btd_adapter_unregister_pin_cb(adapter, autopair_pincb);
+}
+
+static struct btd_adapter_driver autopair_driver = {
+ .name = "autopair",
+ .probe = autopair_probe,
+ .remove = autopair_remove,
+};
+
+static int autopair_init(void)
+{
+ /* Initialize the random seed from /dev/urandom */
+ unsigned int seed = time(NULL);
+ FILE *f = fopen("/dev/urandom", "rb");
+ if (f != NULL) {
+ fread(&seed, sizeof(seed), 1, f);
+ fclose(f);
+ }
+ srand(seed);
+
+ return btd_register_adapter_driver(&autopair_driver);
+}
+
+static void autopair_exit(void)
+{
+ btd_unregister_adapter_driver(&autopair_driver);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(autopair, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, autopair_init, autopair_exit)
--
1.8.2.1

2013-04-26 20:05:37

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 4/8] core: retry bonding attempt until the iterator reaches the end.

This patch splits the bonding process in an interative process consisting
of one or more "bonding attempts". The user/agent starts a new "bonding"
that may involve several rounds of "bonding attempts" with the device
before it gets back to the user/agent.

Some functions were split in two parts to reflect this change. When a
bonding attempt fails with an authentication error, a new bonding attempt
is initiated (after a short delay) to retry with the next function (or
next call to the same function) in the pincode callback list. This
effectively allows a plugin try different pincodes for the same device
during the same bonding.
---
src/adapter.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++---------
src/adapter.h | 3 ++
src/device.c | 47 +++++++++++++++++++++++++++++++
src/device.h | 2 ++
4 files changed, 127 insertions(+), 13 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 3007086..3250e13 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -4933,6 +4933,42 @@ static void bonding_complete(struct btd_adapter *adapter,
check_oob_bonding_complete(adapter, bdaddr, status);
}

+/* bonding_attempt_complete() handles the end of a "bonding attempt" checking if
+ * it should begin a new attempt or complete the bonding.
+ */
+static void bonding_attempt_complete(struct btd_adapter *adapter,
+ const bdaddr_t *bdaddr,
+ uint8_t addr_type, uint8_t status)
+{
+ struct btd_device *device;
+ char addr[18];
+
+ ba2str(bdaddr, addr);
+ DBG("hci%u bdaddr %s type %u status 0x%x", adapter->dev_id, addr,
+ addr_type, status);
+
+ if (status == 0)
+ device = adapter_get_device(adapter, bdaddr, addr_type);
+ else
+ device = adapter_find_device(adapter, bdaddr);
+
+ if (status == MGMT_STATUS_AUTH_FAILED) {
+ /* On faliure, issue a bonding_retry if possible. */
+ if (device != NULL) {
+ if (device_bonding_attempt_retry(device) == 0)
+ return;
+ }
+ }
+
+ /* Ignore disconnects during retry. */
+ if (status == MGMT_STATUS_DISCONNECTED &&
+ device && device_is_retrying(device))
+ return;
+
+ /* In any other case, finish the bonding. */
+ bonding_complete(adapter, bdaddr, addr_type, status);
+}
+
struct pair_device_data {
struct btd_adapter *adapter;
bdaddr_t bdaddr;
@@ -4986,7 +5022,7 @@ static void pair_device_complete(uint8_t status, uint16_t length,
error("Pair device failed: %s (0x%02x)",
mgmt_errstr(status), status);

- bonding_complete(adapter, &data->bdaddr,
+ bonding_attempt_complete(adapter, &data->bdaddr,
data->addr_type, status);
return;
}
@@ -4996,17 +5032,13 @@ static void pair_device_complete(uint8_t status, uint16_t length,
return;
}

- bonding_complete(adapter, &rp->addr.bdaddr, rp->addr.type, status);
+ bonding_attempt_complete(adapter, &rp->addr.bdaddr, rp->addr.type,
+ status);
}

int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
uint8_t addr_type, uint8_t io_cap)
{
- struct mgmt_cp_pair_device cp;
- char addr[18];
- struct pair_device_data *data;
- unsigned int id;
-
if (adapter->pair_device_id > 0) {
error("Unable pair since another pairing is in progress");
return -EBUSY;
@@ -5014,6 +5046,18 @@ int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,

suspend_discovery(adapter);

+ return adapter_bonding_attempt(adapter, bdaddr, addr_type, io_cap);
+}
+
+/* Starts a new bonding attempt in a fresh new bonding_req or a retried one. */
+int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
+ uint8_t addr_type, uint8_t io_cap)
+{
+ struct mgmt_cp_pair_device cp;
+ char addr[18];
+ struct pair_device_data *data;
+ unsigned int id;
+
ba2str(bdaddr, addr);
DBG("hci%u bdaddr %s type %d io_cap 0x%02x",
adapter->dev_id, addr, addr_type, io_cap);
@@ -5066,7 +5110,7 @@ static void dev_disconnected(struct btd_adapter *adapter,
if (device)
adapter_remove_connection(adapter, device);

- bonding_complete(adapter, &addr->bdaddr, addr->type,
+ bonding_attempt_complete(adapter, &addr->bdaddr, addr->type,
MGMT_STATUS_DISCONNECTED);
}

@@ -5120,7 +5164,8 @@ static void auth_failed_callback(uint16_t index, uint16_t length,
return;
}

- bonding_complete(adapter, &ev->addr.bdaddr, ev->addr.type, ev->status);
+ bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type,
+ ev->status);
}

static void store_link_key(struct btd_adapter *adapter,
@@ -5720,14 +5765,31 @@ static void connect_failed_callback(uint16_t index, uint16_t length,

device = adapter_find_device(adapter, &ev->addr.bdaddr);
if (device) {
+ /* If the device is in a bonding process cancel any auth request
+ * sent to the agent before proceeding, but keep the bonding
+ * request structure. */
if (device_is_bonding(device, NULL))
- device_bonding_failed(device, ev->status);
- if (device_is_temporary(device))
- adapter_remove_device(adapter, device, TRUE);
+ device_cancel_authentication(device, FALSE);
}

/* In the case of security mode 3 devices */
- bonding_complete(adapter, &ev->addr.bdaddr, ev->addr.type, ev->status);
+ bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type,
+ ev->status);
+
+ /* If the device is scheduled to retry the bonding wait until the retry
+ * happens. In other case, proceed with cancel the bondig.
+ */
+ if (device && device_is_bonding(device, NULL)
+ && !device_is_retrying(device)) {
+ device_cancel_authentication(device, TRUE);
+ device_bonding_failed(device, ev->status);
+ }
+
+ /* In the case the bonding was canceled or did exists, remove the device
+ * when it is temporary. */
+ if (device && !device_is_bonding(device, NULL)
+ && device_is_temporary(device))
+ adapter_remove_device(adapter, device, TRUE);
}

static void unpaired_callback(uint16_t index, uint16_t length,
diff --git a/src/adapter.h b/src/adapter.h
index 26cea57..e579ca1 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -178,6 +178,9 @@ int btd_adapter_passkey_reply(struct btd_adapter *adapter,
int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
uint8_t addr_type, uint8_t io_cap);

+int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
+ uint8_t addr_type, uint8_t io_cap);
+
int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
uint8_t addr_type);

diff --git a/src/device.c b/src/device.c
index d9639b9..f260d47 100644
--- a/src/device.c
+++ b/src/device.c
@@ -3755,6 +3755,53 @@ gboolean device_is_bonding(struct btd_device *device, const char *sender)
return g_str_equal(sender, dbus_message_get_sender(bonding->msg));
}

+static gboolean device_bonding_retry(gpointer data)
+{
+ struct btd_device *device = data;
+ struct btd_adapter *adapter = device_get_adapter(device);
+ struct bonding_req *bonding = device->bonding;
+ uint8_t io_cap;
+ int err;
+
+ if (!bonding)
+ return FALSE;
+
+ DBG("retrying bonding");
+ bonding->retry_timer = 0;
+
+ if (bonding->agent)
+ io_cap = agent_get_io_capability(bonding->agent);
+ else
+ io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT;
+
+ err = adapter_bonding_attempt(adapter, &device->bdaddr,
+ device->bdaddr_type, io_cap);
+ if (err < 0)
+ device_bonding_complete(device, bonding->status);
+
+ return FALSE;
+}
+
+int device_bonding_attempt_retry(struct btd_device *device)
+{
+ struct bonding_req *bonding = device->bonding;
+
+ /* Ignore other failure events while retrying */
+ if (device_is_retrying(device))
+ return 0;
+
+ if (!bonding)
+ return -EINVAL;
+
+ if (pincb_iter_end(bonding->cb_iter))
+ return -EINVAL;
+
+ DBG("scheduling retry");
+ bonding->retry_timer = g_timeout_add(3000,
+ device_bonding_retry, device);
+ return 0;
+}
+
void device_bonding_failed(struct btd_device *device, uint8_t status)
{
struct bonding_req *bonding = device->bonding;
diff --git a/src/device.h b/src/device.h
index fec214b..d19793c 100644
--- a/src/device.h
+++ b/src/device.h
@@ -76,8 +76,10 @@ gboolean device_is_connected(struct btd_device *device);
bool device_is_retrying(struct btd_device *device);
void device_bonding_complete(struct btd_device *device, uint8_t status);
gboolean device_is_bonding(struct btd_device *device, const char *sender);
+void device_bonding_attempt_failed(struct btd_device *device, uint8_t status);
void device_bonding_failed(struct btd_device *device, uint8_t status);
struct pincb_iter *device_bonding_iter(struct btd_device *device);
+int device_bonding_attempt_retry(struct btd_device *device);
int device_request_pincode(struct btd_device *device, gboolean secure);
int device_request_passkey(struct btd_device *device);
int device_confirm_passkey(struct btd_device *device, uint32_t passkey,
--
1.8.2.1

2013-04-26 20:05:38

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 5/8] core: Add device_get_class to the public interface.

Exports the device class to plugins.
---
src/device.c | 5 +++++
src/device.h | 1 +
2 files changed, 6 insertions(+)

diff --git a/src/device.c b/src/device.c
index f260d47..8b8efc2 100644
--- a/src/device.c
+++ b/src/device.c
@@ -2149,6 +2149,11 @@ void device_set_class(struct btd_device *device, uint32_t class)
DEVICE_INTERFACE, "Class");
}

+uint32_t btd_device_get_class(struct btd_device *device)
+{
+ return device->class;
+}
+
uint16_t btd_device_get_vendor(struct btd_device *device)
{
return device->vendor;
diff --git a/src/device.h b/src/device.h
index d19793c..bc5c84b 100644
--- a/src/device.h
+++ b/src/device.h
@@ -37,6 +37,7 @@ void device_set_name(struct btd_device *device, const char *name);
void device_get_name(struct btd_device *device, char *name, size_t len);
bool device_name_known(struct btd_device *device);
void device_set_class(struct btd_device *device, uint32_t class);
+uint32_t btd_device_get_class(struct btd_device *device);
uint16_t btd_device_get_vendor(struct btd_device *device);
uint16_t btd_device_get_vendor_src(struct btd_device *device);
uint16_t btd_device_get_product(struct btd_device *device);
--
1.8.2.1


2013-04-26 20:05:36

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 3/8] core: Add support for retrying a bonding

In order to retry a bonding we need a timer that will perform the
retry, we need to stash the status of the bonding request so we
can use it again. In the case of a retrying bonding attempt we
need to not tear down the temporary D-Bus device object on the
adapter.
---
src/adapter.c | 2 +-
src/device.c | 12 ++++++++++++
src/device.h | 1 +
3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/adapter.c b/src/adapter.c
index 4b20a9e..3007086 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -4253,7 +4253,7 @@ static void adapter_remove_connection(struct btd_adapter *adapter,
if (device_is_authenticating(device))
device_cancel_authentication(device, TRUE);

- if (device_is_temporary(device)) {
+ if (device_is_temporary(device) && !device_is_retrying(device)) {
const char *path = device_get_path(device);

DBG("Removing temporary device %s", path);
diff --git a/src/device.c b/src/device.c
index d523d31..d9639b9 100644
--- a/src/device.c
+++ b/src/device.c
@@ -88,6 +88,8 @@ struct bonding_req {
struct btd_device *device;
struct agent *agent;
struct pincb_iter *cb_iter;
+ uint8_t status;
+ guint retry_timer;
};

typedef enum {
@@ -1574,6 +1576,9 @@ static void bonding_request_free(struct bonding_req *bonding)
bonding->agent = NULL;
}

+ if (bonding->retry_timer)
+ g_source_remove(bonding->retry_timer);
+
if (bonding->device)
bonding->device->bonding = NULL;

@@ -3610,6 +3615,13 @@ static void device_auth_req_free(struct btd_device *device)
device->authr = NULL;
}

+bool device_is_retrying(struct btd_device *device)
+{
+ struct bonding_req *bonding = device->bonding;
+
+ return bonding && bonding->retry_timer > 0;
+}
+
void device_bonding_complete(struct btd_device *device, uint8_t status)
{
struct bonding_req *bonding = device->bonding;
diff --git a/src/device.h b/src/device.h
index e62728d..fec214b 100644
--- a/src/device.h
+++ b/src/device.h
@@ -73,6 +73,7 @@ void device_set_bonded(struct btd_device *device, gboolean bonded);
void device_set_legacy(struct btd_device *device, bool legacy);
void device_set_rssi(struct btd_device *device, int8_t rssi);
gboolean device_is_connected(struct btd_device *device);
+bool device_is_retrying(struct btd_device *device);
void device_bonding_complete(struct btd_device *device, uint8_t status);
gboolean device_is_bonding(struct btd_device *device, const char *sender);
void device_bonding_failed(struct btd_device *device, uint8_t status);
--
1.8.2.1


2013-04-26 20:05:35

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 2/8] plugins: Extend the pin code callback with the call number

The plugin's pin code callback doesn't know about the pairing process. It
just provides a pin code based on the information provided to this function.
Although limited state could be added through other new callbacks, this fix
achieves this by providing more information to the callback itself. The
new argument "attempt" states the pin callback attempt of the particular
plugin for the current pairing of the device. This allows a plugin to try
different pincodes for the same device in the same pairing process.

To signal that the plugin doesn't provide any pin code for the provided device
the current implementation returns 0 (an empty pin code). Analogously, with
this fix, a plugin should return 0 when it doesn't have any other pin code to
provide for the given device.
---
plugins/wiimote.c | 7 ++++++-
src/adapter.c | 2 +-
src/adapter.h | 3 ++-
3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/plugins/wiimote.c b/plugins/wiimote.c
index 90d6d7b..a1e7974 100644
--- a/plugins/wiimote.c
+++ b/plugins/wiimote.c
@@ -70,12 +70,17 @@ static const char *wii_names[] = {
};

static ssize_t wii_pincb(struct btd_adapter *adapter, struct btd_device *device,
- char *pinbuf, gboolean *display)
+ char *pinbuf, gboolean *display, uint32_t attempt)
{
uint16_t vendor, product;
char addr[18], name[25];
unsigned int i;

+ /* Only try the pin code once per device. If it's not correct then it's
+ * an unknown device. */
+ if (attempt > 1)
+ return 0;
+
ba2str(device_get_address(device), addr);

vendor = btd_device_get_vendor(device);
diff --git a/src/adapter.c b/src/adapter.c
index f343178..4b20a9e 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -4806,7 +4806,7 @@ static ssize_t pincb_iter_next(struct pincb_iter *iter,

while (iter->it != NULL) {
cb = iter->it->data;
- ret = cb(adapter, device, pin_buf, display);
+ ret = cb(adapter, device, pin_buf, display, iter->attempt);
iter->attempt++;
if (ret > 0)
return ret;
diff --git a/src/adapter.h b/src/adapter.h
index cb776bf..26cea57 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -132,7 +132,8 @@ int btd_cancel_authorization(guint id);
int btd_adapter_restore_powered(struct btd_adapter *adapter);

typedef ssize_t (*btd_adapter_pin_cb_t) (struct btd_adapter *adapter,
- struct btd_device *dev, char *out, gboolean *display);
+ struct btd_device *dev, char *out, gboolean *display,
+ uint32_t attempt);
void btd_adapter_register_pin_cb(struct btd_adapter *adapter,
btd_adapter_pin_cb_t cb);
void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter,
--
1.8.2.1


2013-04-26 20:05:34

by Alex Deymo

[permalink] [raw]
Subject: [PATCH v4 1/8] core: Convert the pincode callback to an interable list.

The current pincode callback list on the adapter keeps track of all the
pincode callbacks registered by a plugin for that adapter and calls each
one until one provides a pincode for the current bonding. This mechanism
forgets about what happened with previous bonding attempts and pushes the
status track to the plugin side.

This patch creates an iterator struct (struct pincb_iter) that keeps track
of the last function called and the number of times called. This will
allow to provide more information about the bonding status to the pincode
callback.
---
src/adapter.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++--------
src/adapter.h | 4 ++++
src/device.c | 14 ++++++++++++++
src/device.h | 1 +
4 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 6255da6..f343178 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -121,6 +121,12 @@ struct service_auth {
struct agent *agent; /* NULL for queued auths */
};

+struct pincb_iter {
+ GSList *it; /* current callback function */
+ unsigned int attempt; /* numer of times it() was called */
+ /* When the iterator reaches the end, it is NULL and attempt is 0 */
+};
+
struct btd_adapter {
int ref_count;

@@ -4769,22 +4775,47 @@ static void user_passkey_notify_callback(uint16_t index, uint16_t length,
error("device_notify_passkey: %s", strerror(-err));
}

-static ssize_t adapter_get_pin(struct btd_adapter *adapter,
- struct btd_device *dev, char *pin_buf,
+struct pincb_iter *pincb_iter_new(struct btd_adapter *adapter)
+{
+ struct pincb_iter *iter = g_new0(struct pincb_iter, 1);
+
+ iter->it = adapter->pin_callbacks;
+ iter->attempt = 1;
+
+ return iter;
+}
+
+void pincb_iter_free(struct pincb_iter *iter)
+{
+ g_free(iter);
+}
+
+bool pincb_iter_end(struct pincb_iter *iter)
+{
+ return iter->it == NULL && iter->attempt == 0;
+}
+
+static ssize_t pincb_iter_next(struct pincb_iter *iter,
+ struct btd_adapter *adapter,
+ struct btd_device *device,
+ char *pin_buf,
gboolean *display)
{
btd_adapter_pin_cb_t cb;
ssize_t ret;
- GSList *l;

- for (l = adapter->pin_callbacks; l != NULL; l = g_slist_next(l)) {
- cb = l->data;
- ret = cb(adapter, dev, pin_buf, display);
+ while (iter->it != NULL) {
+ cb = iter->it->data;
+ ret = cb(adapter, device, pin_buf, display);
+ iter->attempt++;
if (ret > 0)
return ret;
+ iter->attempt = 1;
+ iter->it = g_slist_next(iter->it);
}
+ iter->attempt = 0;

- return -1;
+ return 0;
}

static void pin_code_request_callback(uint16_t index, uint16_t length,
@@ -4798,6 +4829,7 @@ static void pin_code_request_callback(uint16_t index, uint16_t length,
ssize_t pinlen;
char addr[18];
int err;
+ struct pincb_iter *iter;

if (length < sizeof(*ev)) {
error("Too small PIN code request event");
@@ -4815,7 +4847,13 @@ static void pin_code_request_callback(uint16_t index, uint16_t length,
}

memset(pin, 0, sizeof(pin));
- pinlen = adapter_get_pin(adapter, device, pin, &display);
+
+ iter = device_bonding_iter(device);
+ if (iter == NULL)
+ pinlen = 0;
+ else
+ pinlen = pincb_iter_next(iter, adapter, device, pin, &display);
+
if (pinlen > 0 && (!ev->secure || pinlen == 16)) {
if (display && device_is_bonding(device, NULL)) {
err = device_notify_pincode(device, ev->secure, pin);
diff --git a/src/adapter.h b/src/adapter.h
index 8d23a64..cb776bf 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -138,6 +138,10 @@ void btd_adapter_register_pin_cb(struct btd_adapter *adapter,
void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter,
btd_adapter_pin_cb_t cb);

+struct pincb_iter *pincb_iter_new(struct btd_adapter *adapter);
+void pincb_iter_free(struct pincb_iter *iter);
+bool pincb_iter_end(struct pincb_iter *iter);
+
/* If TRUE, enables fast connectabe, i.e. reduces page scan interval and changes
* type. If FALSE, disables fast connectable, i.e. sets page scan interval and
* type to default values. Valid for both connectable and discoverable modes. */
diff --git a/src/device.c b/src/device.c
index dd137ed..d523d31 100644
--- a/src/device.c
+++ b/src/device.c
@@ -87,6 +87,7 @@ struct bonding_req {
guint listener_id;
struct btd_device *device;
struct agent *agent;
+ struct pincb_iter *cb_iter;
};

typedef enum {
@@ -1437,6 +1438,8 @@ static struct bonding_req *bonding_request_new(DBusMessage *msg,

bonding->msg = dbus_message_ref(msg);

+ bonding->cb_iter = pincb_iter_new(device->adapter);
+
if (agent)
bonding->agent = agent_ref(agent);

@@ -1562,6 +1565,9 @@ static void bonding_request_free(struct bonding_req *bonding)
if (bonding->msg)
dbus_message_unref(bonding->msg);

+ if (bonding->cb_iter)
+ g_free(bonding->cb_iter);
+
if (bonding->agent) {
agent_cancel(bonding->agent);
agent_unref(bonding->agent);
@@ -3756,6 +3762,14 @@ void device_bonding_failed(struct btd_device *device, uint8_t status)
bonding_request_free(bonding);
}

+struct pincb_iter *device_bonding_iter(struct btd_device *device)
+{
+ if (device->bonding == NULL)
+ return NULL;
+
+ return device->bonding->cb_iter;
+}
+
static void pincode_cb(struct agent *agent, DBusError *err, const char *pin,
void *data)
{
diff --git a/src/device.h b/src/device.h
index f467fa6..e62728d 100644
--- a/src/device.h
+++ b/src/device.h
@@ -76,6 +76,7 @@ gboolean device_is_connected(struct btd_device *device);
void device_bonding_complete(struct btd_device *device, uint8_t status);
gboolean device_is_bonding(struct btd_device *device, const char *sender);
void device_bonding_failed(struct btd_device *device, uint8_t status);
+struct pincb_iter *device_bonding_iter(struct btd_device *device);
int device_request_pincode(struct btd_device *device, gboolean secure);
int device_request_passkey(struct btd_device *device);
int device_confirm_passkey(struct btd_device *device, uint32_t passkey,
--
1.8.2.1

2013-05-07 04:20:11

by Alex Deymo

[permalink] [raw]
Subject: Re: [PATCH v4 1/8] core: Convert the pincode callback to an interable list.

Hi Johan,

On Tue, Apr 30, 2013 at 1:16 AM, Johan Hedberg <[email protected]> wrote:
> On Fri, Apr 26, 2013, Alex Deymo wrote:
>> +struct pincb_iter {
>
> What concerns me a bit about this is that you're now introducing the
> first symbols exported by adapter.{c.h} that are not name spaced with
> btd_adapter* or adapter*. Would it make sense to keep this name spacing
> even for the iterator?
Yes, I'll change the name of those functions to btd_adapter_pincb... .
After all, those are for iterating the adapter's pincode callback list
and not /any/ pincode callback list.

>> + unsigned int attempt; /* numer of times it() was called */
>
> Just use int and not unsigned int. It's what we typically use for
> iterator variables.
This "attempt" member is not exactly an iterator variable, but a
member of an iterator struct. The iterator variable will be of type
struct pincb_iter *. Also, the attempt member will only take
non-negative values, so the unsigned version looks more appropriate in
that case.

Excuse me for not iterate this patch before. I'll try to send an
update tomorrow including a fix for the bonding retry with a Bluetooth
2.1+ discoverable but not pairable device.
Best regards,
Alex.