2016-03-30 21:01:28

by Łukasz Rymanowski

[permalink] [raw]
Subject: [PATCH v4 0/3] shared/gatt: Couple fixes and improvements

I've back to work on making Android gatt to make use of shared/gatt.
Here is couple of fixes I've done to shared code when doing this work.

Android gatt still needs testing but will be available sooner or later

v2:
- fix test-gatt for long write seesion testes
- prepare test-gatt test to support other changes
- do aggregation of prep writes for long write session
- support reliable nested long write
- start to look into characteristic extended prop for reliable session

v3:
- rebase
- remove patch adding notification type - that one needs to be reworked

v4:
- Fix according to Luiz comment

Łukasz Rymanowski (3):
shared/gatt-server: Add support for long write
shared/gatt-db: Add API to get extended prop
shared/gatt-server: Check for ext. charact. prop. on reliable session

src/shared/gatt-db.c | 62 +++++++++++++++++++++++
src/shared/gatt-db.h | 4 ++
src/shared/gatt-server.c | 127 +++++++++++++++++++++++++++++++++++++++++------
unit/test-gatt.c | 14 ++++++
4 files changed, 192 insertions(+), 15 deletions(-)

--
2.5.0



2016-03-31 13:51:26

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [PATCH v4 1/3] shared/gatt-server: Add support for long write

Hi Łukasz,

On Thu, Mar 31, 2016 at 12:01 AM, Łukasz Rymanowski
<[email protected]> wrote:
> With this patch long write and nested long write reliable is supported.
> GATT server is responsible now to do aggregation of prep write data
> for long write session.
> Note: We consider long write as the consequtive prepare writes with
> continues offsets.
>
> E.g. 1
>
> prep_write: handle 1, offset 0, value_len 10
> prep_write: handle 1, offset 10, value_len 10
> prep_write: handle 2, offset 0, value_len 10
> prep_write: handle 2, offset 10, value_len 10
>
> Will result with following calles to app:
>
> exec_write: handle 1: offset 0, value_len 20
> exec_write: handle 2: offset 0, value_len 20
>
> E.g. 2
>
> prep_write: handle 1, offset 0, value_len 10
> prep_write: handle 1, offset 2, value_len 5
> prep_write: handle 2, offset 0, value_len 10
> prep_write: handle 2, offset 4, value_len 5
>
> Will result with following calles to app:
>
> exec_write: handle 1: offset 0, value_len 10
> exec_write: handle 1: offset 2, value_len 5
> exec_write: handle 2: offset 0, value_len 10
> exec_write: handle 2: offset 4, value_len 5
>
> E.g. 3
> prep_write: handle 1, offset 0, value_len 10
> prep_write: handle 1, offset 5, value_len 5
> prep_write: handle 1, offset 10, value_len 6
>
> will result with following calles to app:
>
> exec_write: handle 1, offset 0, value 10
> exec_write: handle 1, offset 5, value 11
> ---
> src/shared/gatt-server.c | 79 +++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 65 insertions(+), 14 deletions(-)
>
> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
> index c41273a..ae079b1 100644
> --- a/src/shared/gatt-server.c
> +++ b/src/shared/gatt-server.c
> @@ -1088,6 +1088,56 @@ error:
> bt_att_send_error_rsp(server->att, opcode, 0, ecode);
> }
>
> +static bool create_and_store_prep_data(struct bt_gatt_server *server,
> + uint16_t handle, uint16_t offset,
> + uint16_t length, uint8_t *value)
> +{
> + struct prep_write_data *prep_data;
> +
> + prep_data = new0(struct prep_write_data, 1);
> + prep_data->length = length;
> + if (prep_data->length) {
> + prep_data->value = malloc(prep_data->length);
> + if (!prep_data->value) {
> + return false;
> + }
> +
> + memcpy(prep_data->value, value, prep_data->length);
> + }
> +
> + prep_data->server = server;
> + prep_data->handle = handle;
> + prep_data->offset = offset;
> +
> + queue_push_tail(server->prep_queue, prep_data);
> +
> + return true;
> +}
> +
> +static bool make_aggregation_of_long_write_data(struct bt_gatt_server *server,
> + struct prep_write_data *prep_data,
> + uint16_t handle, uint16_t length,
> + uint8_t *value)
> +{
> + uint8_t *buf;
> + uint16_t new_len;
> +
> + new_len = prep_data->length + length;
> +
> + buf = malloc(new_len);
> + if (!buf)
> + return false;
> +
> + memcpy(buf, prep_data->value, prep_data->length);
> + memcpy(buf + prep_data->length, value, length);

I think we could use realloc here instead of copying like this.

> +
> + free(prep_data->value);
> + prep_data->value = buf;
> + prep_data->length = new_len;
> +
> + return true;
> +}
> +
> static void prep_write_cb(uint8_t opcode, const void *pdu,
> uint16_t length, void *user_data)
> {
> @@ -1097,6 +1147,7 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
> uint16_t offset;
> struct gatt_db_attribute *attr;
> uint8_t ecode;
> + bool success;
>
> if (length < 4) {
> ecode = BT_ATT_ERROR_INVALID_PDU;
> @@ -1126,22 +1177,22 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
> if (ecode)
> goto error;
>
> - prep_data = new0(struct prep_write_data, 1);
> - prep_data->length = length - 4;
> - if (prep_data->length) {
> - prep_data->value = malloc(prep_data->length);
> - if (!prep_data->value) {
> - ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
> - goto error;
> - }
> - }
> + /* Now lets check if prep write is a continuation of long write */
> + prep_data = queue_peek_tail(server->prep_queue);
>
> - prep_data->server = server;
> - prep_data->handle = handle;
> - prep_data->offset = offset;
> - memcpy(prep_data->value, pdu + 4, prep_data->length);
> + if (prep_data && (prep_data->handle == handle) &&
> + (offset == prep_data->length + prep_data->offset))
> + success = make_aggregation_of_long_write_data(server, prep_data,
> + handle, length - 4,
> + &((uint8_t *) pdu)[4]);
> + else
> + success = create_and_store_prep_data(server, handle, offset,
> + length - 4, &((uint8_t *) pdu)[4]);

I would prefer if this would actually be done in a separate function,
store_prep_data which should be able to detect if the data shall be
appended or if a new item should be created.

>
> - queue_push_tail(server->prep_queue, prep_data);
> + if (!success) {
> + ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
> + goto error;
> + }
>
> bt_att_send(server->att, BT_ATT_OP_PREP_WRITE_RSP, pdu, length, NULL,
> NULL, NULL);
> --
> 2.5.0
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html



--
Luiz Augusto von Dentz

2016-03-30 21:01:31

by Łukasz Rymanowski

[permalink] [raw]
Subject: [PATCH v4 3/3] shared/gatt-server: Check for ext. charact. prop. on reliable session

With this patch we make sure that reliable session is done on
characteristics which does support it.
---
src/shared/gatt-server.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index ae079b1..8b2e884 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -72,6 +72,8 @@ struct prep_write_data {
uint16_t handle;
uint16_t offset;
uint16_t length;
+
+ bool reliable_supported;
};

static void prep_write_data_destroy(void *user_data)
@@ -1088,6 +1090,22 @@ error:
bt_att_send_error_rsp(server->att, opcode, 0, ecode);
}

+static bool is_reliable_supported(const struct bt_gatt_server *server,
+ uint16_t handle)
+{
+ struct gatt_db_attribute *attr;
+ uint8_t ext_prop;
+
+ attr = gatt_db_get_attribute(server->db, handle);
+ if (!attr)
+ return false;
+
+ if (!gatt_db_attribute_get_characteristic_extended_prop(attr, &ext_prop))
+ return false;
+
+ return (ext_prop & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE);
+}
+
static bool create_and_store_prep_data(struct bt_gatt_server *server,
uint16_t handle, uint16_t offset,
uint16_t length, uint8_t *value)
@@ -1109,6 +1127,12 @@ static bool create_and_store_prep_data(struct bt_gatt_server *server,
prep_data->handle = handle;
prep_data->offset = offset;

+ /*
+ * Handle is the value handle. We need characteristic declariation
+ * handle which in BlueZ is handle_value -1
+ */
+ prep_data->reliable_supported = is_reliable_supported(server,
+ handle - 1);
queue_push_tail(server->prep_queue, prep_data);

return true;
@@ -1262,6 +1286,14 @@ error:
ehandle, err);
}

+static bool find_no_reliable_characteristic(const void *data,
+ const void *match_data)
+{
+ const struct prep_write_data *prep_data = data;
+
+ return !prep_data->reliable_supported;
+}
+
static void exec_write_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -1269,6 +1301,7 @@ static void exec_write_cb(uint8_t opcode, const void *pdu,
uint8_t flags;
uint8_t ecode;
bool write;
+ uint16_t ehandle = 0;

if (length != 1) {
ecode = BT_ATT_ERROR_INVALID_PDU;
@@ -1297,6 +1330,19 @@ static void exec_write_cb(uint8_t opcode, const void *pdu,
return;
}

+ /* If there is more than one prep request, we are in reliable session */
+ if (queue_length(server->prep_queue) > 1) {
+ struct prep_write_data *prep_data;
+
+ prep_data = queue_find(server->prep_queue,
+ find_no_reliable_characteristic, NULL);
+ if (prep_data) {
+ ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+ ehandle = prep_data->handle;
+ goto error;
+ }
+ }
+
exec_next_prep_write(server, 0, 0);

return;
@@ -1304,7 +1350,7 @@ static void exec_write_cb(uint8_t opcode, const void *pdu,
error:
queue_remove_all(server->prep_queue, NULL, NULL,
prep_write_data_destroy);
- bt_att_send_error_rsp(server->att, opcode, 0, ecode);
+ bt_att_send_error_rsp(server->att, opcode, ehandle, ecode);
}

static void exchange_mtu_cb(uint8_t opcode, const void *pdu,
--
2.5.0


2016-03-30 21:01:30

by Łukasz Rymanowski

[permalink] [raw]
Subject: [PATCH v4 2/3] shared/gatt-db: Add API to get extended prop

This patch adds way to get extended properties from
characteristic extended property descriptor
---
src/shared/gatt-db.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/gatt-db.h | 4 ++++
unit/test-gatt.c | 14 ++++++++++++
3 files changed, 80 insertions(+)

diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
index cc49458..1a1704b 100644
--- a/src/shared/gatt-db.c
+++ b/src/shared/gatt-db.c
@@ -52,6 +52,8 @@ static const bt_uuid_t characteristic_uuid = { .type = BT_UUID16,
.value.u16 = GATT_CHARAC_UUID };
static const bt_uuid_t included_service_uuid = { .type = BT_UUID16,
.value.u16 = GATT_INCLUDE_UUID };
+static const bt_uuid_t ext_desc_uuid = { .type = BT_UUID16,
+ .value.u16 = GATT_CHARAC_EXT_PROPER_UUID };

struct gatt_db {
int ref_count;
@@ -1456,6 +1458,66 @@ bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib,
return le_to_uuid(decl->value, decl->value_len, uuid);
}

+struct ext_prop_data {
+ bool present;
+ uint8_t prop;
+};
+
+static void set_ext_prop_data(struct gatt_db_attribute *attrib,
+ int err, const uint8_t *value,
+ size_t length, void *user_data)
+{
+ struct ext_prop_data *ext_prop_data = user_data;
+
+ if (err || (length != sizeof(uint8_t)))
+ return;
+
+ ext_prop_data->prop = value[0];
+}
+
+static void check_reliable_supported(struct gatt_db_attribute *attrib,
+ void *user_data)
+{
+ struct ext_prop_data *ext_prop_data = user_data;
+
+ if (ext_prop_data->present)
+ return;
+
+ if (bt_uuid_cmp(&ext_desc_uuid, &attrib->uuid))
+ return;
+
+ ext_prop_data->present = true;
+ ext_prop_data->prop = gatt_db_attribute_read(attrib, 0,
+ BT_ATT_OP_READ_REQ, NULL,
+ set_ext_prop_data, user_data);
+}
+
+bool gatt_db_attribute_get_characteristic_extended_prop(
+ const struct gatt_db_attribute *attrib,
+ uint8_t *ext_prop)
+{
+ struct ext_prop_data ext_prop_data;
+
+ if (!attrib)
+ return false;
+
+ if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid))
+ return false;
+
+ memset(&ext_prop_data, 0, sizeof(ext_prop_data));
+
+ /*
+ * Cast needed for foreach function. We do not change attrib during
+ * this call
+ */
+ gatt_db_service_foreach_desc((struct gatt_db_attribute *) attrib,
+ check_reliable_supported,
+ &ext_prop_data);
+
+ *ext_prop = ext_prop_data.prop;
+ return ext_prop_data.present;
+}
+
bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib,
uint16_t *handle,
uint16_t *value_handle,
diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
index 96cceb9..0cb713e 100644
--- a/src/shared/gatt-db.h
+++ b/src/shared/gatt-db.h
@@ -195,6 +195,10 @@ bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib,
bool *primary,
bt_uuid_t *uuid);

+bool gatt_db_attribute_get_characteristic_extended_prop(
+ const struct gatt_db_attribute *attrib,
+ uint8_t *ext_prop);
+
bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib,
uint16_t *handle,
uint16_t *value_handle,
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 0912348..85e4d75 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -4434,5 +4434,19 @@ int main(int argc, char *argv[])
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff),
raw_pdu(0x01, 0x16, 0x04, 0x00, 0x03));

+ define_test_server("/robustness/no-reliable-characteristic", test_server,
+ ts_large_db_1, NULL,
+ raw_pdu(0x03, 0x00, 0x02),
+ raw_pdu(0x16, 0x82, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x17, 0x82, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x16, 0x25, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x17, 0x25, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x16, 0x82, 0x00, 0x03, 0x00, 0x04, 0x05, 0x06),
+ raw_pdu(0x17, 0x82, 0x00, 0x03, 0x00, 0x04, 0x05, 0x06),
+ raw_pdu(0x16, 0x25, 0x00, 0x03, 0x00, 0x04, 0x05, 0x06),
+ raw_pdu(0x17, 0x25, 0x00, 0x03, 0x00, 0x04, 0x05, 0x06),
+ raw_pdu(0x18, 0x01),
+ raw_pdu(0x01, 0x18, 0x25, 0x00, 0x06));
+
return tester_run();
}
--
2.5.0


2016-03-30 21:01:29

by Łukasz Rymanowski

[permalink] [raw]
Subject: [PATCH v4 1/3] shared/gatt-server: Add support for long write

With this patch long write and nested long write reliable is supported.
GATT server is responsible now to do aggregation of prep write data
for long write session.
Note: We consider long write as the consequtive prepare writes with
continues offsets.

E.g. 1

prep_write: handle 1, offset 0, value_len 10
prep_write: handle 1, offset 10, value_len 10
prep_write: handle 2, offset 0, value_len 10
prep_write: handle 2, offset 10, value_len 10

Will result with following calles to app:

exec_write: handle 1: offset 0, value_len 20
exec_write: handle 2: offset 0, value_len 20

E.g. 2

prep_write: handle 1, offset 0, value_len 10
prep_write: handle 1, offset 2, value_len 5
prep_write: handle 2, offset 0, value_len 10
prep_write: handle 2, offset 4, value_len 5

Will result with following calles to app:

exec_write: handle 1: offset 0, value_len 10
exec_write: handle 1: offset 2, value_len 5
exec_write: handle 2: offset 0, value_len 10
exec_write: handle 2: offset 4, value_len 5

E.g. 3
prep_write: handle 1, offset 0, value_len 10
prep_write: handle 1, offset 5, value_len 5
prep_write: handle 1, offset 10, value_len 6

will result with following calles to app:

exec_write: handle 1, offset 0, value 10
exec_write: handle 1, offset 5, value 11
---
src/shared/gatt-server.c | 79 +++++++++++++++++++++++++++++++++++++++---------
1 file changed, 65 insertions(+), 14 deletions(-)

diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c
index c41273a..ae079b1 100644
--- a/src/shared/gatt-server.c
+++ b/src/shared/gatt-server.c
@@ -1088,6 +1088,56 @@ error:
bt_att_send_error_rsp(server->att, opcode, 0, ecode);
}

+static bool create_and_store_prep_data(struct bt_gatt_server *server,
+ uint16_t handle, uint16_t offset,
+ uint16_t length, uint8_t *value)
+{
+ struct prep_write_data *prep_data;
+
+ prep_data = new0(struct prep_write_data, 1);
+ prep_data->length = length;
+ if (prep_data->length) {
+ prep_data->value = malloc(prep_data->length);
+ if (!prep_data->value) {
+ return false;
+ }
+
+ memcpy(prep_data->value, value, prep_data->length);
+ }
+
+ prep_data->server = server;
+ prep_data->handle = handle;
+ prep_data->offset = offset;
+
+ queue_push_tail(server->prep_queue, prep_data);
+
+ return true;
+}
+
+static bool make_aggregation_of_long_write_data(struct bt_gatt_server *server,
+ struct prep_write_data *prep_data,
+ uint16_t handle, uint16_t length,
+ uint8_t *value)
+{
+ uint8_t *buf;
+ uint16_t new_len;
+
+ new_len = prep_data->length + length;
+
+ buf = malloc(new_len);
+ if (!buf)
+ return false;
+
+ memcpy(buf, prep_data->value, prep_data->length);
+ memcpy(buf + prep_data->length, value, length);
+
+ free(prep_data->value);
+ prep_data->value = buf;
+ prep_data->length = new_len;
+
+ return true;
+}
+
static void prep_write_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
@@ -1097,6 +1147,7 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
uint16_t offset;
struct gatt_db_attribute *attr;
uint8_t ecode;
+ bool success;

if (length < 4) {
ecode = BT_ATT_ERROR_INVALID_PDU;
@@ -1126,22 +1177,22 @@ static void prep_write_cb(uint8_t opcode, const void *pdu,
if (ecode)
goto error;

- prep_data = new0(struct prep_write_data, 1);
- prep_data->length = length - 4;
- if (prep_data->length) {
- prep_data->value = malloc(prep_data->length);
- if (!prep_data->value) {
- ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
- goto error;
- }
- }
+ /* Now lets check if prep write is a continuation of long write */
+ prep_data = queue_peek_tail(server->prep_queue);

- prep_data->server = server;
- prep_data->handle = handle;
- prep_data->offset = offset;
- memcpy(prep_data->value, pdu + 4, prep_data->length);
+ if (prep_data && (prep_data->handle == handle) &&
+ (offset == prep_data->length + prep_data->offset))
+ success = make_aggregation_of_long_write_data(server, prep_data,
+ handle, length - 4,
+ &((uint8_t *) pdu)[4]);
+ else
+ success = create_and_store_prep_data(server, handle, offset,
+ length - 4, &((uint8_t *) pdu)[4]);

- queue_push_tail(server->prep_queue, prep_data);
+ if (!success) {
+ ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
+ goto error;
+ }

bt_att_send(server->att, BT_ATT_OP_PREP_WRITE_RSP, pdu, length, NULL,
NULL, NULL);
--
2.5.0