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
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
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
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
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
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
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
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
---
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
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
---
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
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