2014-10-02 19:29:48

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 00/11] shared/hfp: Add support for HFP HF

Following patches extends hfp API with HFP HF functionality.
HFP HF parser has been added and unit test for it.

TODO: add functions to parse AT response.

To consider: how strict we should be when it comes to parsing
AT responses. For example, at the moment, command +CCLC:<cr><lf>
will be recognized as +CCLC: eventhough correct response format
should be <cr><lf>+CCLC:<cr><lf>

Note: As discussed on IRC I did not try to generalize code.

Lukasz Rymanowski (11):
shared/hfp: Add support for HFP HF
shared/hfp: Add set_debug and close_on_unref API for HFP HF
shared/hfp: Add set disconnect handler and disconnect API to HFP HF
shared/hfp: Add register/unregister event for HFP HF
shared/hfp: Add HFP HF parser
shared/hfp: Add send AT command API for HFP HF
unit/test-hfp: Provide test_handler function via struct data
unit/test-hfp: Add init test for HFP HF
unit/test-hfp: Add send command tests for HFP HF
unit/test-hfp: Add tests for unsolicited results for HFP HF
unit/test-hfp: Add some robustness tests for HFP HF

src/shared/hfp.c | 618 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 28 +++
unit/test-hfp.c | 272 ++++++++++++++++++++++--
3 files changed, 899 insertions(+), 19 deletions(-)

--
1.8.4



2014-10-02 19:29:59

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 11/11] unit/test-hfp: Add some robustness tests for HFP HF

This patch adds folowing tests:
/hfp/test_hf_corrupted_1
/hfp/test_hf_corrupted_2
/hfp/test_hf_empty
/hfp/test_hf_unknown
---
unit/test-hfp.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 4d552fd..1702cb6 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -555,6 +555,25 @@ static void test_hf_unsolicited(gconstpointer data)
execute_context(context);
}

+static void test_hf_robustness(gconstpointer data)
+{
+ struct context *context = create_context(data);
+ bool ret;
+
+ context->hfp_hf = hfp_hf_new(context->fd_client);
+ g_assert(context->hfp_hf);
+
+ ret = hfp_hf_set_close_on_unref(context->hfp_hf, true);
+ g_assert(ret);
+
+ send_pdu(context);
+
+ hfp_hf_unref(context->hfp_hf);
+ context->hfp_hf = NULL;
+
+ execute_context(context);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -663,5 +682,26 @@ int main(int argc, char *argv[])
frg_pdu('\r'), frg_pdu('\n'),
data_end());

+ define_hf_test("/hfp/test_hf_corrupted_1", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ frg_pdu('\r', 'X', '\r', '\n'),
+ frg_pdu('+', 'C', 'L', 'C', 'C', ':', '1', '3', '0',
+ '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp/test_hf_corrupted_2", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp/test_hf_empty", test_hf_robustness, NULL, NULL,
+ raw_pdu('\r'), data_end());
+
+ define_hf_test("/hfp/test_hf_unknown", test_hf_robustness, NULL, NULL,
+ raw_pdu('\r', '\n', 'B', 'R', '\r', '\n'),
+ data_end());
+
return g_test_run();
}
--
1.8.4


2014-10-02 19:29:55

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 07/11] unit/test-hfp: Provide test_handler function via struct data

This patch allows us to use user defined test handler depends on needs.
Will use it in following patches which implements tests for HFP HF.
---
unit/test-hfp.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index a8801b0..4b3473b 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -52,6 +52,7 @@ struct test_data {
char *test_name;
struct test_pdu *pdu_list;
hfp_result_func_t result_func;
+ GIOFunc test_handler;
};

#define data(args...) ((const unsigned char[]) { args })
@@ -95,6 +96,7 @@ struct test_data {
data.result_func = result_function; \
memcpy(data.pdu_list, pdus, sizeof(pdus)); \
g_test_add_data_func(name, &data, function); \
+ data.test_handler = test_handler; \
} while (0)

static void context_quit(struct context *context)
@@ -158,6 +160,7 @@ static struct context *create_context(gconstpointer data)
struct context *context = g_new0(struct context, 1);
GIOChannel *channel;
int err, sv[2];
+ const struct test_data *d = data;

context->main_loop = g_main_loop_new(NULL, FALSE);
g_assert(context->main_loop);
@@ -173,7 +176,8 @@ static struct context *create_context(gconstpointer data)

context->watch_id = g_io_add_watch(channel,
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
- test_handler, context);
+ d->test_handler, context);
+
g_assert(context->watch_id > 0);

g_io_channel_unref(channel);
--
1.8.4


2014-10-02 19:29:58

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 10/11] unit/test-hfp: Add tests for unsolicited results for HFP HF

This patch adds three test case:
/hfp/test_unsolicited_1
/hfp/test_unsolicited_2
/hfp/test_unsolicited_3
---
unit/test-hfp.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 1926615..4d552fd 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -519,6 +519,42 @@ static void test_hf_send_command(gconstpointer data)
execute_context(context);
}

+static void hf_result_handler(struct hfp_hf_result *result,
+ void *user_data)
+{
+ struct context *context = user_data;
+
+ hfp_hf_disconnect(context->hfp_hf);
+}
+
+static void test_hf_unsolicited(gconstpointer data)
+{
+ struct context *context = create_context(data);
+ bool ret;
+
+ context->hfp_hf = hfp_hf_new(context->fd_client);
+ g_assert(context->hfp_hf);
+
+ ret = hfp_hf_set_close_on_unref(context->hfp_hf, true);
+ g_assert(ret);
+
+ if (context->data->hf_result_func) {
+ const struct test_pdu *pdu;
+
+ pdu = &context->data->pdu_list[context->pdu_offset++];
+
+ ret = hfp_hf_register(context->hfp_hf,
+ context->data->hf_result_func,
+ (char *)pdu->data, context,
+ NULL);
+ g_assert(ret);
+ }
+
+ send_pdu(context);
+
+ execute_context(context);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -604,5 +640,28 @@ int main(int argc, char *argv[])
raw_pdu('\r', '\n', 'O', 'k', '\r', '\n'),
data_end());

+ define_hf_test("/hfp/test_unsolicited_1", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ raw_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', '\r',
+ '\n'),
+ data_end());
+
+ define_hf_test("/hfp/test_unsolicited_2", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ raw_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ '3', '0', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp/test_unsolicited_3", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ frg_pdu('\r'), frg_pdu('\n'), frg_pdu('+'),
+ frg_pdu('C'), frg_pdu('L'), frg_pdu('C'), frg_pdu('C'),
+ frg_pdu(':'), frg_pdu('1'), frg_pdu('3'), frg_pdu('0'),
+ frg_pdu('\r'), frg_pdu('\n'),
+ data_end());
+
return g_test_run();
}
--
1.8.4


2014-10-02 19:29:56

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 08/11] unit/test-hfp: Add init test for HFP HF

This patch adds basic infrastruction for HFP HF test plus
init test.

It also moves send_pdu function in the file so it can be used by
test_hf_handler
---
unit/test-hfp.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 78 insertions(+), 18 deletions(-)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 4b3473b..ad26058 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -36,6 +36,7 @@ struct context {
int fd_server;
int fd_client;
struct hfp_gw *hfp;
+ struct hfp_hf *hfp_hf;
const struct test_data *data;
unsigned int pdu_offset;
};
@@ -52,6 +53,8 @@ struct test_data {
char *test_name;
struct test_pdu *pdu_list;
hfp_result_func_t result_func;
+ hfp_response_func_t response_func;
+ hfp_hf_result_func_t hf_result_func;
GIOFunc test_handler;
};

@@ -99,6 +102,22 @@ struct test_data {
data.test_handler = test_handler; \
} while (0)

+#define define_hf_test(name, function, result_func, response_function, \
+ args...)\
+ do { \
+ const struct test_pdu pdus[] = { \
+ args, { } \
+ }; \
+ static struct test_data data; \
+ data.test_name = g_strdup(name); \
+ data.pdu_list = g_malloc(sizeof(pdus)); \
+ data.hf_result_func = result_func; \
+ data.response_func = response_function; \
+ memcpy(data.pdu_list, pdus, sizeof(pdus)); \
+ g_test_add_data_func(name, &data, function); \
+ data.test_handler = test_hf_handler; \
+ } while (0)
+
static void context_quit(struct context *context)
{
g_main_loop_quit(context->main_loop);
@@ -128,6 +147,46 @@ static gboolean test_handler(GIOChannel *channel, GIOCondition cond,
return FALSE;
}

+static gboolean send_pdu(gpointer user_data)
+{
+ struct context *context = user_data;
+ const struct test_pdu *pdu;
+ ssize_t len;
+
+ pdu = &context->data->pdu_list[context->pdu_offset++];
+
+ if (pdu && !pdu->valid)
+ return FALSE;
+
+ len = write(context->fd_server, pdu->data, pdu->size);
+ g_assert_cmpint(len, ==, pdu->size);
+
+ pdu = &context->data->pdu_list[context->pdu_offset];
+ if (pdu->fragmented)
+ g_idle_add(send_pdu, context);
+
+ return FALSE;
+}
+
+static gboolean test_hf_handler(GIOChannel *channel, GIOCondition cond,
+ gpointer user_data)
+{
+ struct context *context = user_data;
+
+ if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
+ goto done;
+
+ send_pdu(context);
+
+ return TRUE;
+
+done:
+ context_quit(context);
+ context->watch_id = 0;
+
+ return FALSE;
+}
+
static void cmd_handler(const char *command, void *user_data)
{
struct context *context = user_data;
@@ -203,6 +262,9 @@ static void execute_context(struct context *context)
if (context->hfp)
hfp_gw_unref(context->hfp);

+ if (context->hfp_hf)
+ hfp_hf_unref(context->hfp_hf);
+
g_free(context);
}

@@ -275,24 +337,6 @@ static void test_register(gconstpointer data)
execute_context(context);
}

-static gboolean send_pdu(gpointer user_data)
-{
- struct context *context = user_data;
- const struct test_pdu *pdu;
- ssize_t len;
-
- pdu = &context->data->pdu_list[context->pdu_offset++];
-
- len = write(context->fd_server, pdu->data, pdu->size);
- g_assert_cmpint(len, ==, pdu->size);
-
- pdu = &context->data->pdu_list[context->pdu_offset];
- if (pdu->fragmented)
- g_idle_add(send_pdu, context);
-
- return FALSE;
-}
-
static void test_fragmented(gconstpointer data)
{
struct context *context = create_context(data);
@@ -404,6 +448,20 @@ static void check_string_2(struct hfp_gw_result *result,
hfp_gw_send_result(context->hfp, HFP_RESULT_ERROR);
}

+static void test_hf_init(gconstpointer data)
+{
+ struct context *context = create_context(data);
+
+ context->hfp_hf = hfp_hf_new(context->fd_client);
+ g_assert(context->hfp_hf);
+ g_assert(hfp_hf_set_close_on_unref(context->hfp_hf, true));
+
+ hfp_hf_unref(context->hfp_hf);
+ context->hfp_hf = NULL;
+
+ execute_context(context);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -473,5 +531,7 @@ int main(int argc, char *argv[])
raw_pdu('\r'),
data_end());

+ define_hf_test("/hfp/test_init", test_hf_init, NULL, NULL, data_end());
+
return g_test_run();
}
--
1.8.4


2014-10-02 19:29:57

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 09/11] unit/test-hfp: Add send command tests for HFP HF

This patch adds following tests:
/hfp/test_send_command_1
/hfp/test_send_command_2
---
unit/test-hfp.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index ad26058..1926615 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -118,6 +118,9 @@ struct test_data {
data.test_handler = test_hf_handler; \
} while (0)

+
+static bool received_unsolicited = false;
+
static void context_quit(struct context *context)
{
g_main_loop_quit(context->main_loop);
@@ -462,6 +465,60 @@ static void test_hf_init(gconstpointer data)
execute_context(context);
}

+static void hf_unsolicited_resp_cb(struct hfp_hf_result *result,
+ void *user_data)
+{
+ received_unsolicited = true;
+}
+
+static void hf_response_with_unsolicited_cb(const char *prefix,
+ enum hfp_result res,
+ void *user_data)
+{
+ struct context *context = user_data;
+
+ g_assert_cmpstr(prefix, ==, "AT+BRSF");
+ g_assert(received_unsolicited);
+ received_unsolicited = false;
+
+ hfp_hf_disconnect(context->hfp_hf);
+}
+
+static void hf_response_cb(const char *prefix, enum hfp_result res,
+ void *user_data)
+{
+ struct context *context = user_data;
+
+ g_assert_cmpstr(prefix, ==, "AT+BRSF");
+
+ hfp_hf_disconnect(context->hfp_hf);
+}
+
+static void test_hf_send_command(gconstpointer data)
+{
+ struct context *context = create_context(data);
+ const struct test_pdu *pdu;
+ bool ret;
+
+ context->hfp_hf = hfp_hf_new(context->fd_client);
+ g_assert(context->hfp_hf);
+
+ pdu = &context->data->pdu_list[context->pdu_offset++];
+
+ ret = hfp_hf_set_close_on_unref(context->hfp_hf, true);
+ g_assert(ret);
+
+ if (context->data->response_func) {
+ ret = hfp_hf_send_command(context->hfp_hf,
+ context->data->response_func,
+ context->data->hf_result_func,
+ context, (char *)pdu->data);
+ g_assert(ret);
+ }
+
+ execute_context(context);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -532,6 +589,20 @@ int main(int argc, char *argv[])
data_end());

define_hf_test("/hfp/test_init", test_hf_init, NULL, NULL, data_end());
+ define_hf_test("/hfp/test_send_command_1", test_hf_send_command, NULL,
+ hf_response_cb,
+ raw_pdu('A', 'T', '+', 'B', 'R', 'S', 'F', '\0'),
+ raw_pdu('\r', '\n', 'O', 'k', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp/test_send_command_2", test_hf_send_command,
+ hf_unsolicited_resp_cb,
+ hf_response_with_unsolicited_cb,
+ raw_pdu('A', 'T', '+', 'B', 'R', 'S', 'F', '\0'),
+ raw_pdu('\r', '\n', '+', 'B', 'R', 'S', 'F', '\r',
+ '\n'),
+ raw_pdu('\r', '\n', 'O', 'k', '\r', '\n'),
+ data_end());

return g_test_run();
}
--
1.8.4


2014-10-02 19:29:53

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 05/11] shared/hfp: Add HFP HF parser

This patch adds parser for AT responses and unsolicited results.
---
src/shared/hfp.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 131 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index b1cf08e..d267698 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -868,6 +868,128 @@ static void destroy_event_handler(void *data)
free(handler);
}

+static void hf_skip_whitespace(struct hfp_hf_result *result)
+{
+ while (result->data[result->offset] == ' ')
+ result->offset++;
+}
+
+static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data)
+{
+ struct event_handler *handler;
+ const char *separators = ";:\0";
+ struct hfp_hf_result result_data;
+ char lookup_prefix[18];
+ uint8_t pref_len = 0;
+ const char *prefix;
+ int i;
+
+ result_data.offset = 0;
+ result_data.data = data;
+
+ hf_skip_whitespace(&result_data);
+
+ if (strlen(data + result_data.offset) < 2)
+ return;
+
+ prefix = data + result_data.offset;
+
+ pref_len = strcspn(prefix, separators);
+ if (pref_len > 17 || pref_len < 2)
+ return;
+
+ for (i = 0; i < pref_len; i++)
+ lookup_prefix[i] = toupper(prefix[i]);
+
+ lookup_prefix[pref_len] = '\0';
+ result_data.offset += pref_len;
+
+ handler = queue_find(hfp->event_handlers, match_handler_event_prefix,
+ lookup_prefix);
+
+ if (!handler)
+ return;
+
+ handler->callback(&result_data, handler->user_data);
+
+}
+
+static char *find_cr_lf(char *str, size_t len)
+{
+ char *ptr;
+ int count;
+ int offset;
+
+ offset = 0;
+
+ ptr = memchr(str, '\r', len);
+ while (ptr) {
+ /*
+ * Check if there is more data after '\r'. If so check for
+ * '\n'
+ */
+ count = ptr-str;
+ if ((count < (int) (len - 1)) && *(ptr + 1) == '\n')
+ return ptr;
+
+ /* There is only '\r'? Let's try to find next one */
+ offset += count + 1;
+
+ if (offset >= (int)len)
+ return NULL;
+
+ ptr = memchr(str + offset, '\r', len - offset);
+ }
+
+ return NULL;
+}
+
+static void hf_process_input(struct hfp_hf *hfp)
+{
+ char *str, *ptr;
+ size_t len, count, offset;
+
+ str = ringbuf_peek(hfp->read_buf, 0, &len);
+ if (!str)
+ return;
+
+ offset = 0;
+
+ ptr = find_cr_lf(str, len);
+ while (ptr) {
+ count = ptr - (str + offset);
+ if (count == 0) {
+ /* 2 is for <cr><lf> */
+ offset += 2;
+ } else {
+ *ptr = '\0';
+ hf_call_prefix_handler(hfp, str + offset);
+ offset += count + 2;
+ }
+
+ if (offset >= len)
+ break;
+
+ ptr = find_cr_lf(str + offset, len - offset);
+ }
+
+ ringbuf_drain(hfp->read_buf, offset < len ? offset : len);
+}
+
+static bool hf_can_read_data(struct io *io, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ ssize_t bytes_read;
+
+ bytes_read = ringbuf_read(hfp->read_buf, hfp->fd);
+ if (bytes_read < 0)
+ return false;
+
+ hf_process_input(hfp);
+
+ return true;
+}
+
struct hfp_hf *hfp_hf_new(int fd)
{
struct hfp_hf *hfp;
@@ -912,6 +1034,15 @@ struct hfp_hf *hfp_hf_new(int fd)
return NULL;
}

+ if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp,
+ read_watch_destroy)) {
+ queue_destroy(hfp->event_handlers,
+ destroy_event_handler);
+ io_destroy(hfp->io);
+ ringbuf_free(hfp->write_buf);
+ ringbuf_free(hfp->read_buf);
+ }
+
return hfp_hf_ref(hfp);
}

--
1.8.4


2014-10-02 19:29:54

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 06/11] shared/hfp: Add send AT command API for HFP HF

This patch adds handling send and response of AT command.
Note that we always wait for AT command response before sending next
command, however user can fill hfp_hf with more than one command.
All the commands are queued and send one by one.
---
src/shared/hfp.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 6 ++
2 files changed, 173 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index d267698..9ac508d 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -70,6 +70,10 @@ struct hfp_hf {
struct ringbuf *read_buf;
struct ringbuf *write_buf;

+ bool writer_active;
+ struct queue *cmd_queue;
+ bool command_in_progress;
+
struct queue *event_handlers;

hfp_debug_func_t debug_callback;
@@ -96,6 +100,13 @@ struct hfp_gw_result {
unsigned int offset;
};

+struct cmd_response {
+ char *prefix;
+ hfp_response_func_t resp_cb;
+ hfp_hf_result_func_t unsolicited_cb;
+ void *user_data;
+};
+
struct hfp_hf_result {
const char *data;
unsigned int offset;
@@ -868,12 +879,64 @@ static void destroy_event_handler(void *data)
free(handler);
}

+static bool hf_can_write_data(struct io *io, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ ssize_t bytes_written;
+
+ bytes_written = ringbuf_write(hfp->write_buf, hfp->fd);
+ if (bytes_written < 0)
+ return false;
+
+ if (ringbuf_len(hfp->write_buf) > 0)
+ return true;
+
+ return false;
+}
+
+static void hf_write_watch_destroy(void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ hfp->writer_active = false;
+}
+
static void hf_skip_whitespace(struct hfp_hf_result *result)
{
while (result->data[result->offset] == ' ')
result->offset++;
}

+static bool is_response(const char *msg, enum hfp_result *result)
+{
+ if (strcmp(msg, "OK") == 0) {
+ *result = HFP_RESULT_OK;
+ return true;
+ }
+
+ if (strcmp(msg, "ERROR") == 0) {
+ *result = HFP_RESULT_ERROR;
+ return true;
+ }
+
+ return false;
+}
+
+static void hf_wakeup_writer(struct hfp_hf *hfp)
+{
+ if (hfp->writer_active)
+ return;
+
+ if (!ringbuf_len(hfp->write_buf))
+ return;
+
+ if (!io_set_write_handler(hfp->io, hf_can_write_data,
+ hfp, hf_write_watch_destroy))
+ return;
+
+ hfp->writer_active = true;
+}
+
static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data)
{
struct event_handler *handler;
@@ -904,6 +967,42 @@ static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data)
lookup_prefix[pref_len] = '\0';
result_data.offset += pref_len;

+ if (hfp->command_in_progress) {
+ struct cmd_response *cmd;
+ enum hfp_result result;
+
+ cmd = queue_peek_head(hfp->cmd_queue);
+ if (!cmd)
+ return;
+
+ if (is_response(lookup_prefix, &result)) {
+ cmd->resp_cb(cmd->prefix, result, cmd->user_data);
+
+ queue_remove(hfp->cmd_queue, cmd);
+ free(cmd);
+
+ if (!queue_isempty(hfp->cmd_queue)) {
+ hf_wakeup_writer(hfp);
+ return;
+ }
+
+ hfp->command_in_progress = false;
+
+ return;
+ }
+ /*
+ * Check if unsolicited result is the response for ongoing
+ * command. If not we try to find registered handler for it
+ * later.
+ */
+ if (strcmp(lookup_prefix, &cmd->prefix[2]) == 0 &&
+ cmd->unsolicited_cb) {
+ cmd->unsolicited_cb(&result_data, cmd->user_data);
+
+ return;
+ }
+ }
+
handler = queue_find(hfp->event_handlers, match_handler_event_prefix,
lookup_prefix);

@@ -1034,6 +1133,19 @@ struct hfp_hf *hfp_hf_new(int fd)
return NULL;
}

+ hfp->cmd_queue = queue_new();
+ if (!hfp->cmd_queue) {
+ io_destroy(hfp->io);
+ ringbuf_free(hfp->write_buf);
+ ringbuf_free(hfp->read_buf);
+ queue_destroy(hfp->event_handlers, NULL);
+ free(hfp);
+ return NULL;
+ }
+
+ hfp->writer_active = false;
+ hfp->command_in_progress = false;
+
if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp,
read_watch_destroy)) {
queue_destroy(hfp->event_handlers,
@@ -1144,6 +1256,61 @@ bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close)
return true;
}

+bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb,
+ hfp_hf_result_func_t unsolicited_cb,
+ void *user_data,
+ const char *format, ...)
+{
+ va_list ap;
+ char *fmt;
+ int len;
+ const char *separators = ";?=\0";
+ uint8_t prefix_len;
+ struct cmd_response *cmd;
+
+ if (!hfp || !format || !resp_cb)
+ return false;
+
+ if (asprintf(&fmt, "%s\r", format) < 0)
+ return false;
+
+ cmd = new0(struct cmd_response, 1);
+ if (!cmd)
+ return false;
+
+ va_start(ap, format);
+ len = ringbuf_vprintf(hfp->write_buf, fmt, ap);
+ va_end(ap);
+
+ free(fmt);
+
+ if (len < 0) {
+ free(cmd);
+ return false;
+ }
+
+ prefix_len = strcspn(format, separators);
+ cmd->prefix = strndup(format, prefix_len);
+ cmd->resp_cb = resp_cb;
+ cmd->unsolicited_cb = unsolicited_cb;
+ cmd->user_data = user_data;
+
+ if (!queue_push_tail(hfp->cmd_queue, cmd)) {
+ ringbuf_drain(hfp->write_buf, len);
+ free(cmd);
+ return false;
+ }
+
+ if (hfp->command_in_progress)
+ return true;
+
+ hfp->command_in_progress = true;
+
+ hf_wakeup_writer(hfp);
+
+ return true;
+}
+
bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback,
const char *prefix,
void *user_data,
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 85037b1..6f2e17c 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -83,6 +83,9 @@ typedef void (*hfp_command_func_t)(const char *command, void *user_data);

typedef void (*hfp_disconnect_func_t)(void *user_data);

+typedef void (*hfp_response_func_t)(const char *prefix,
+ enum hfp_result result,
+ void *user_data);
struct hfp_gw;
struct hfp_hf;

@@ -146,3 +149,6 @@ bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback,
const char *prefix, void *user_data,
hfp_destroy_func_t destroy);
bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix);
+bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb,
+ hfp_hf_result_func_t unsolicited_cb,
+ void *user_data, const char *format, ...);
--
1.8.4


2014-10-02 19:29:51

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 03/11] shared/hfp: Add set disconnect handler and disconnect API to HFP HF

---
src/shared/hfp.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 5 +++++
2 files changed, 68 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index ad2daa2..b7855ed 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -74,6 +74,10 @@ struct hfp_hf {
hfp_destroy_func_t debug_destroy;
void *debug_data;

+ hfp_disconnect_func_t disconnect_callback;
+ hfp_destroy_func_t disconnect_destroy;
+ void *disconnect_data;
+
bool in_disconnect;
bool destroyed;
};
@@ -956,3 +960,62 @@ bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close)

return true;
}
+
+static void hf_disconnect_watch_destroy(void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ if (hfp->disconnect_destroy)
+ hfp->disconnect_destroy(hfp->disconnect_data);
+
+ if (hfp->destroyed)
+ free(hfp);
+}
+
+static bool hf_io_disconnected(struct io *io, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ hfp->in_disconnect = true;
+
+ if (hfp->disconnect_callback)
+ hfp->disconnect_callback(hfp->disconnect_data);
+
+ hfp->in_disconnect = false;
+
+ return false;
+}
+
+bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp,
+ hfp_disconnect_func_t callback,
+ void *user_data,
+ hfp_destroy_func_t destroy)
+{
+ if (!hfp)
+ return false;
+
+ if (hfp->disconnect_destroy)
+ hfp->disconnect_destroy(hfp->disconnect_data);
+
+ if (!io_set_disconnect_handler(hfp->io, hf_io_disconnected, hfp,
+ hf_disconnect_watch_destroy)) {
+ hfp->disconnect_callback = NULL;
+ hfp->disconnect_destroy = NULL;
+ hfp->disconnect_data = NULL;
+ return false;
+ }
+
+ hfp->disconnect_callback = callback;
+ hfp->disconnect_destroy = destroy;
+ hfp->disconnect_data = user_data;
+
+ return true;
+}
+
+bool hfp_hf_disconnect(struct hfp_hf *hfp)
+{
+ if (!hfp)
+ return false;
+
+ return io_shutdown(hfp->io);
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index ae67ac2..a9a169b 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -133,3 +133,8 @@ void hfp_hf_unref(struct hfp_hf *hfp);
bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback,
void *user_data, hfp_destroy_func_t destroy);
bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close);
+bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp,
+ hfp_disconnect_func_t callback,
+ void *user_data,
+ hfp_destroy_func_t destroy);
+bool hfp_hf_disconnect(struct hfp_hf *hfp);
--
1.8.4


2014-10-02 19:29:52

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 04/11] shared/hfp: Add register/unregister event for HFP HF

This patch adds API which allows to register/unregister for unsolicited
responses.
---
src/shared/hfp.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 8 +++++
2 files changed, 116 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index b7855ed..b1cf08e 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -70,6 +70,8 @@ struct hfp_hf {
struct ringbuf *read_buf;
struct ringbuf *write_buf;

+ struct queue *event_handlers;
+
hfp_debug_func_t debug_callback;
hfp_destroy_func_t debug_destroy;
void *debug_data;
@@ -94,6 +96,18 @@ struct hfp_gw_result {
unsigned int offset;
};

+struct hfp_hf_result {
+ const char *data;
+ unsigned int offset;
+};
+
+struct event_handler {
+ char *prefix;
+ void *user_data;
+ hfp_destroy_func_t destroy;
+ hfp_hf_result_func_t callback;
+};
+
static void destroy_cmd_handler(void *data)
{
struct cmd_handler *handler = data;
@@ -828,6 +842,32 @@ bool hfp_gw_disconnect(struct hfp_gw *hfp)
return io_shutdown(hfp->io);
}

+static bool match_handler_event_prefix(const void *a, const void *b)
+{
+ const struct event_handler *handler = a;
+ const char *prefix = b;
+
+ if (strlen(handler->prefix) != strlen(prefix))
+ return false;
+
+ if (memcmp(handler->prefix, prefix, strlen(prefix)))
+ return false;
+
+ return true;
+}
+
+static void destroy_event_handler(void *data)
+{
+ struct event_handler *handler = data;
+
+ if (handler->destroy)
+ handler->destroy(handler->user_data);
+
+ free(handler->prefix);
+
+ free(handler);
+}
+
struct hfp_hf *hfp_hf_new(int fd)
{
struct hfp_hf *hfp;
@@ -863,6 +903,15 @@ struct hfp_hf *hfp_hf_new(int fd)
return NULL;
}

+ hfp->event_handlers = queue_new();
+ if (!hfp->event_handlers) {
+ io_destroy(hfp->io);
+ ringbuf_free(hfp->write_buf);
+ ringbuf_free(hfp->read_buf);
+ free(hfp);
+ return NULL;
+ }
+
return hfp_hf_ref(hfp);
}

@@ -902,6 +951,9 @@ void hfp_hf_unref(struct hfp_hf *hfp)
ringbuf_free(hfp->write_buf);
hfp->write_buf = NULL;

+ queue_destroy(hfp->event_handlers, destroy_event_handler);
+ hfp->event_handlers = NULL;
+
if (!hfp->in_disconnect) {
free(hfp);
return;
@@ -961,6 +1013,62 @@ bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close)
return true;
}

+bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback,
+ const char *prefix,
+ void *user_data,
+ hfp_destroy_func_t destroy)
+{
+ struct event_handler *handler;
+
+ if (!callback)
+ return false;
+
+ handler = new0(struct event_handler, 1);
+ if (!handler)
+ return false;
+
+ handler->callback = callback;
+ handler->user_data = user_data;
+
+ handler->prefix = strdup(prefix);
+ if (!handler->prefix) {
+ free(handler);
+ return false;
+ }
+
+ if (queue_find(hfp->event_handlers, match_handler_event_prefix,
+ handler->prefix)) {
+ destroy_event_handler(handler);
+ return false;
+ }
+
+ handler->destroy = destroy;
+
+ return queue_push_tail(hfp->event_handlers, handler);
+}
+
+bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix)
+{
+ struct cmd_handler *handler;
+ char *lookup_prefix;
+
+ lookup_prefix = strdup(prefix);
+ if (!lookup_prefix)
+ return false;
+
+ handler = queue_remove_if(hfp->event_handlers,
+ match_handler_event_prefix,
+ lookup_prefix);
+ free(lookup_prefix);
+
+ if (!handler)
+ return false;
+
+ destroy_event_handler(handler);
+
+ return true;
+}
+
static void hf_disconnect_watch_destroy(void *user_data)
{
struct hfp_hf *hfp = user_data;
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index a9a169b..85037b1 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -68,10 +68,14 @@ enum hfp_gw_cmd_type {
};

struct hfp_gw_result;
+struct hfp_hf_result;

typedef void (*hfp_result_func_t)(struct hfp_gw_result *result,
enum hfp_gw_cmd_type type, void *user_data);

+typedef void (*hfp_hf_result_func_t)(struct hfp_hf_result *result,
+ void *user_data);
+
typedef void (*hfp_destroy_func_t)(void *user_data);
typedef void (*hfp_debug_func_t)(const char *str, void *user_data);

@@ -138,3 +142,7 @@ bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp,
void *user_data,
hfp_destroy_func_t destroy);
bool hfp_hf_disconnect(struct hfp_hf *hfp);
+bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback,
+ const char *prefix, void *user_data,
+ hfp_destroy_func_t destroy);
+bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix);
--
1.8.4


2014-10-02 19:29:50

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 02/11] shared/hfp: Add set_debug and close_on_unref API for HFP HF

---
src/shared/hfp.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 3 +++
2 files changed, 60 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index dbd049a..ad2daa2 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -70,6 +70,10 @@ struct hfp_hf {
struct ringbuf *read_buf;
struct ringbuf *write_buf;

+ hfp_debug_func_t debug_callback;
+ hfp_destroy_func_t debug_destroy;
+ void *debug_data;
+
bool in_disconnect;
bool destroyed;
};
@@ -886,6 +890,8 @@ void hfp_hf_unref(struct hfp_hf *hfp)
if (hfp->close_on_unref)
close(hfp->fd);

+ hfp_hf_set_debug(hfp, NULL, NULL, NULL);
+
ringbuf_free(hfp->read_buf);
hfp->read_buf = NULL;

@@ -899,3 +905,54 @@ void hfp_hf_unref(struct hfp_hf *hfp)

hfp->destroyed = true;
}
+
+static void hf_read_tracing(const void *buf, size_t count,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data);
+}
+
+static void hf_write_tracing(const void *buf, size_t count,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data);
+}
+
+bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback,
+ void *user_data, hfp_destroy_func_t destroy)
+{
+ if (!hfp)
+ return false;
+
+ if (hfp->debug_destroy)
+ hfp->debug_destroy(hfp->debug_data);
+
+ hfp->debug_callback = callback;
+ hfp->debug_destroy = destroy;
+ hfp->debug_data = user_data;
+
+ if (hfp->debug_callback) {
+ ringbuf_set_input_tracing(hfp->read_buf, hf_read_tracing, hfp);
+ ringbuf_set_input_tracing(hfp->write_buf, hf_write_tracing,
+ hfp);
+ } else {
+ ringbuf_set_input_tracing(hfp->read_buf, NULL, NULL);
+ ringbuf_set_input_tracing(hfp->write_buf, NULL, NULL);
+ }
+
+ return true;
+}
+
+bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close)
+{
+ if (!hfp)
+ return false;
+
+ hfp->close_on_unref = do_close;
+
+ return true;
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 50d9c4b..ae67ac2 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -130,3 +130,6 @@ bool hfp_gw_result_has_next(struct hfp_gw_result *result);
struct hfp_hf *hfp_hf_new(int fd);
struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp);
void hfp_hf_unref(struct hfp_hf *hfp);
+bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback,
+ void *user_data, hfp_destroy_func_t destroy);
+bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close);
--
1.8.4


2014-10-02 19:29:49

by Lukasz Rymanowski

[permalink] [raw]
Subject: [PATCH 01/11] shared/hfp: Add support for HFP HF

This patch add struct hfp_hf plus fuctions to create an instance ref and
unref. This code based on existing hfp_gw
---
src/shared/hfp.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 6 ++++
2 files changed, 98 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index efc981f..dbd049a 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -62,6 +62,18 @@ struct hfp_gw {
bool destroyed;
};

+struct hfp_hf {
+ int ref_count;
+ int fd;
+ bool close_on_unref;
+ struct io *io;
+ struct ringbuf *read_buf;
+ struct ringbuf *write_buf;
+
+ bool in_disconnect;
+ bool destroyed;
+};
+
struct cmd_handler {
char *prefix;
void *user_data;
@@ -807,3 +819,83 @@ bool hfp_gw_disconnect(struct hfp_gw *hfp)

return io_shutdown(hfp->io);
}
+
+struct hfp_hf *hfp_hf_new(int fd)
+{
+ struct hfp_hf *hfp;
+
+ if (fd < 0)
+ return NULL;
+
+ hfp = new0(struct hfp_hf, 1);
+ if (!hfp)
+ return NULL;
+
+ hfp->fd = fd;
+ hfp->close_on_unref = false;
+
+ hfp->read_buf = ringbuf_new(4096);
+ if (!hfp->read_buf) {
+ free(hfp);
+ return NULL;
+ }
+
+ hfp->write_buf = ringbuf_new(4096);
+ if (!hfp->write_buf) {
+ ringbuf_free(hfp->read_buf);
+ free(hfp);
+ return NULL;
+ }
+
+ hfp->io = io_new(fd);
+ if (!hfp->io) {
+ ringbuf_free(hfp->write_buf);
+ ringbuf_free(hfp->read_buf);
+ free(hfp);
+ return NULL;
+ }
+
+ return hfp_hf_ref(hfp);
+}
+
+struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp)
+{
+ if (!hfp)
+ return NULL;
+
+ __sync_fetch_and_add(&hfp->ref_count, 1);
+
+ return hfp;
+}
+
+void hfp_hf_unref(struct hfp_hf *hfp)
+{
+ if (!hfp)
+ return;
+
+ if (__sync_sub_and_fetch(&hfp->ref_count, 1))
+ return;
+
+ io_set_write_handler(hfp->io, NULL, NULL, NULL);
+ io_set_read_handler(hfp->io, NULL, NULL, NULL);
+ io_set_disconnect_handler(hfp->io, NULL, NULL, NULL);
+
+ io_destroy(hfp->io);
+ hfp->io = NULL;
+
+ if (hfp->close_on_unref)
+ close(hfp->fd);
+
+ ringbuf_free(hfp->read_buf);
+ hfp->read_buf = NULL;
+
+ ringbuf_free(hfp->write_buf);
+ hfp->write_buf = NULL;
+
+ if (!hfp->in_disconnect) {
+ free(hfp);
+ return;
+ }
+
+ hfp->destroyed = true;
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 743db65..50d9c4b 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -76,9 +76,11 @@ typedef void (*hfp_destroy_func_t)(void *user_data);
typedef void (*hfp_debug_func_t)(const char *str, void *user_data);

typedef void (*hfp_command_func_t)(const char *command, void *user_data);
+
typedef void (*hfp_disconnect_func_t)(void *user_data);

struct hfp_gw;
+struct hfp_hf;

struct hfp_gw *hfp_gw_new(int fd);

@@ -124,3 +126,7 @@ bool hfp_gw_result_get_string(struct hfp_gw_result *result, char *buf,
bool hfp_gw_result_get_unquoted_string(struct hfp_gw_result *result, char *buf,
uint8_t len);
bool hfp_gw_result_has_next(struct hfp_gw_result *result);
+
+struct hfp_hf *hfp_hf_new(int fd);
+struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp);
+void hfp_hf_unref(struct hfp_hf *hfp);
--
1.8.4