Discussion:
[PATCH v5 00/11] shared/hfp: Add support for HFP HF
Lukasz Rymanowski
2014-10-24 11:59:24 UTC
Permalink
Following patches extends hfp API with HFP HF functionality.
HFP HF parser has been added and unit test for it.

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.

v2:
* minor self review fixes
* response callback on send command, contains now result (OK/ERROR) and
data from unsolicited response if available.

v3:
* Fix some memory leaks found on self review

v4:
* Fallback to approach from v1 in context of response callback for AT command.
Bassically, if AT+X has +X and OK response, response callback contains only OK or
ERROR code (including CME which will be added in following patches). To get +X
response, user need to use hfp_hf_register() API. It is done mostly to keep hfp.c
simple. With this approach we do not have to cache all +X in hfp.c before calling
response callback.

v5:
* Szymon comments taken into account
* Support to handle wrapped commands in ringbuffer. Note that for testing I used
modified buffer size in hfp but eventually unit test will be done for that.

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 | 649 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 30 +++
unit/test-hfp.c | 283 ++++++++++++++++++++++--
3 files changed, 943 insertions(+), 19 deletions(-)
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:31 UTC
Permalink
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
Lukasz Rymanowski
2014-10-24 11:59:35 UTC
Permalink
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 ab695d7..24ea402 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -563,6 +563,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);
@@ -674,5 +693,26 @@ int main(int argc, char *argv[])
frg_pdu('\n'),
data_end());

+ define_hf_test("/hfp_hf/test_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'),
+ frg_pdu(',', '0', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp_hf/test_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_hf/test_empty", test_hf_robustness, NULL, NULL,
+ raw_pdu('\r'), data_end());
+
+ define_hf_test("/hfp_hf/test_unknown", test_hf_robustness, NULL, NULL,
+ raw_pdu('\r', '\n', 'B', 'R', '\r', '\n'),
+ data_end());
+
return g_test_run();
}
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:34 UTC
Permalink
This patch adds three test case:
/hfp_hf/test_unsolicited_1
/hfp_hf/test_unsolicited_2
/hfp_hf/test_unsolicited_3
---
unit/test-hfp.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 3c25691..ab695d7 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -527,6 +527,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);
@@ -614,5 +650,29 @@ int main(int argc, char *argv[])
frg_pdu('\r', '\n', 'O', 'k', '\r', '\n'),
data_end());

+ define_hf_test("/hfp_hf/test_unsolicited_1", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C'),
+ frg_pdu('C', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp_hf/test_unsolicited_2", test_hf_unsolicited,
+ hf_result_handler, NULL,
+ raw_pdu('+', 'C', 'L', 'C', 'C', '\0'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1'),
+ frg_pdu(',', '3', ',', '0', '\r', '\n'),
+ data_end());
+
+ define_hf_test("/hfp_hf/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(','), frg_pdu('3'),
+ frg_pdu(','), frg_pdu('0'), frg_pdu('\r'),
+ frg_pdu('\n'),
+ data_end());
+
return g_test_run();
}
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:32 UTC
Permalink
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 | 103 ++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 85 insertions(+), 18 deletions(-)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 4b3473b..1a8548e 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,52 @@ 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;
+ gchar buf[60];
+ gsize bytes_read;
+ GError *error = NULL;
+
+ if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
+ goto done;
+
+ /* dummy read */
+ g_io_channel_read_chars(channel, buf, 60, &bytes_read, &error);
+
+ 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 +268,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 +343,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 +454,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 +537,8 @@ int main(int argc, char *argv[])
raw_pdu('\r'),
data_end());

+ define_hf_test("/hfp_hf/test_init", test_hf_init, NULL, NULL,
+ data_end());
+
return g_test_run();
}
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:33 UTC
Permalink
This patch adds following tests:
/hfp_hf/test_send_command_1
/hfp_hf/test_send_command_2
---
unit/test-hfp.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)

diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 1a8548e..3c25691 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
@@ -468,6 +468,65 @@ static void test_hf_init(gconstpointer data)
execute_context(context);
}

+static bool unsolicited_resp = false;
+
+static void hf_unsolicited_resp_cb(struct hfp_hf_result *result,
+ void *user_data) {
+ unsolicited_resp = true;
+}
+
+static void hf_response_with_data(enum hfp_result res,
+ void *user_data)
+{
+ struct context *context = user_data;
+
+ g_assert(unsolicited_resp);
+ unsolicited_resp = false;
+
+ hfp_hf_disconnect(context->hfp_hf);
+}
+
+static void hf_response_cb(enum hfp_result res, void *user_data)
+{
+ struct context *context = user_data;
+
+ 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);
+
+ ret = hfp_hf_set_close_on_unref(context->hfp_hf, true);
+ g_assert(ret);
+
+ if (context->data->response_func) {
+ if (context->data->hf_result_func) {
+ pdu = &context->data->pdu_list[context->pdu_offset++];
+
+ ret = hfp_hf_register(context->hfp_hf,
+ context->data->hf_result_func,
+ (char *)pdu->data,
+ NULL, NULL);
+ g_assert(ret);
+ }
+
+ pdu = &context->data->pdu_list[context->pdu_offset++];
+
+ ret = hfp_hf_send_command(context->hfp_hf,
+ context->data->response_func,
+ context, (char *)pdu->data);
+ g_assert(ret);
+ }
+
+ execute_context(context);
+}
+
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
@@ -539,6 +598,21 @@ int main(int argc, char *argv[])

define_hf_test("/hfp_hf/test_init", test_hf_init, NULL, NULL,
data_end());
+ define_hf_test("/hfp_hf/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_hf/test_send_command_2", test_hf_send_command,
+ hf_unsolicited_resp_cb,
+ hf_response_with_data,
+ raw_pdu('+', 'B', 'R', 'S', 'F', '\0'),
+ raw_pdu('A', 'T', '+', 'B', 'R', 'S', 'F', '\0'),
+ frg_pdu('\r', '\n', '+', 'B', 'R', 'S', 'F', '\r',
+ '\n'),
+ frg_pdu('\r', '\n', 'O', 'k', '\r', '\n'),
+ data_end());

return g_test_run();
}
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:30 UTC
Permalink
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 | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 6 +++
2 files changed, 168 insertions(+)

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

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

hfp_debug_func_t debug_callback;
@@ -101,6 +104,13 @@ struct hfp_hf_result {
unsigned int offset;
};

+struct cmd_response {
+ hfp_response_func_t resp_cb;
+ struct hfp_hf_result *response;
+ char *resp_data;
+ void *user_data;
+};
+
struct event_handler {
char *prefix;
void *user_data;
@@ -865,17 +875,95 @@ 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 *prefix, enum hfp_result *result)
+{
+ if (strcmp(prefix, "OK") == 0) {
+ *result = HFP_RESULT_OK;
+ return true;
+ }
+
+ if (strcmp(prefix, "ERROR") == 0) {
+ *result = HFP_RESULT_ERROR;
+ return true;
+ }
+
+ if (strcmp(prefix, "NO CARRIER") == 0) {
+ *result = HFP_RESULT_NO_CARRIER;
+ return true;
+ }
+
+ if (strcmp(prefix, "NO ANSWER") == 0) {
+ *result = HFP_RESULT_NO_ANSWER;
+ return true;
+ }
+
+ if (strcmp(prefix, "BUSY") == 0) {
+ *result = HFP_RESULT_BUSY;
+ return true;
+ }
+
+ if (strcmp(prefix, "DELAYED") == 0) {
+ *result = HFP_RESULT_DELAYED;
+ return true;
+ }
+
+ if (strcmp(prefix, "BLACKLISTED") == 0) {
+ *result = HFP_RESULT_BLACKLISTED;
+ 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;
const char *separators = ";:\0";
struct hfp_hf_result result_data;
+ enum hfp_result result;
char lookup_prefix[18];
uint8_t pref_len = 0;
const char *prefix;
@@ -901,6 +989,22 @@ static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data)
lookup_prefix[pref_len] = '\0';
result_data.offset += pref_len + 1;

+ if (is_response(lookup_prefix, &result)) {
+ struct cmd_response *cmd;
+
+ cmd = queue_peek_head(hfp->cmd_queue);
+ if (!cmd)
+ return;
+
+ cmd->resp_cb(result, cmd->user_data);
+
+ queue_remove(hfp->cmd_queue, cmd);
+ free(cmd);
+
+ hf_wakeup_writer(hfp);
+ return;
+ }
+
handler = queue_find(hfp->event_handlers, match_handler_event_prefix,
lookup_prefix);
if (!handler)
@@ -1073,6 +1177,18 @@ 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;
+
if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp,
read_watch_destroy)) {
queue_destroy(hfp->event_handlers,
@@ -1126,6 +1242,9 @@ void hfp_hf_unref(struct hfp_hf *hfp)
queue_destroy(hfp->event_handlers, destroy_event_handler);
hfp->event_handlers = NULL;

+ queue_destroy(hfp->cmd_queue, free);
+ hfp->cmd_queue = NULL;
+
if (!hfp->in_disconnect) {
free(hfp);
return;
@@ -1185,6 +1304,49 @@ 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,
+ void *user_data, const char *format, ...)
+{
+ va_list ap;
+ char *fmt;
+ int 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;
+ }
+
+ cmd->resp_cb = resp_cb;
+ cmd->user_data = user_data;
+
+ if (!queue_push_tail(hfp->cmd_queue, cmd)) {
+ ringbuf_drain(hfp->write_buf, len);
+ free(cmd);
+ return false;
+ }
+
+ 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 3860e25..1467c62 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -32,6 +32,8 @@ enum hfp_result {
HFP_RESULT_NO_DIALTONE = 6,
HFP_RESULT_BUSY = 7,
HFP_RESULT_NO_ANSWER = 8,
+ HFP_RESULT_DELAYED = 9,
+ HFP_RESULT_BLACKLISTED = 10,
};

enum hfp_error {
@@ -130,6 +132,8 @@ struct hfp_hf_result;
typedef void (*hfp_hf_result_func_t)(struct hfp_hf_result *result,
void *user_data);

+typedef void (*hfp_response_func_t)(enum hfp_result result, void *user_data);
+
struct hfp_hf;

struct hfp_hf *hfp_hf_new(int fd);
@@ -148,3 +152,5 @@ 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,
+ void *user_data, const char *format, ...);
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:29 UTC
Permalink
This patch adds parser for AT responses and unsolicited results.
---
src/shared/hfp.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index 84c0db1..37a18d9 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -865,6 +865,170 @@ 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 + 1;
+
+ 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;
+ size_t count, 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 < (len - 1)) && *(ptr + 1) == '\n')
+ return ptr;
+
+ /* There is only '\r'? Let's try to find next one */
+ offset += count + 1;
+
+ if (offset >= len)
+ return NULL;
+
+ ptr = memchr(str + offset, '\r', len - offset);
+ }
+
+ return NULL;
+}
+
+static void hf_process_input(struct hfp_hf *hfp)
+{
+ char *str, *ptr, *str2, *tmp;
+ size_t len, count, offset, len2;
+ bool free_tmp = false;
+
+ 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;
+ }
+
+ ptr = find_cr_lf(str + offset, len - offset);
+ }
+
+ /*
+ * Just check if there is no wrapped data in ring buffer.
+ * Should not happen too often
+ */
+ if (len == ringbuf_len(hfp->read_buf))
+ goto done;
+
+ /* If we are here second time for some reason, that is wrong */
+ if (free_tmp)
+ goto done;
+
+ str2 = ringbuf_peek(hfp->read_buf, len, &len2);
+ if (!str2)
+ goto done;
+
+ ptr = find_cr_lf(str2, len2);
+ if (!ptr) {
+ /* Might happen that we wrap between \r and \n */
+ ptr = memchr(str2, '\n', len2);
+ if (!ptr)
+ goto done;
+ }
+
+ count = ptr - str2;
+
+ if (count) {
+ *ptr = '\0';
+
+ tmp = malloc(len + count);
+
+ /* "str" here is not a string so we need to use memcpy */
+ memcpy(tmp, str, len);
+ memcpy(tmp + len, str2, count);
+
+ free_tmp = true;
+ } else {
+ str[len-1] = '\0';
+ tmp = str;
+ }
+
+ hf_call_prefix_handler(hfp, tmp);
+ offset += count;
+
+done:
+ ringbuf_drain(hfp->read_buf, offset);
+
+ if (free_tmp)
+ free(tmp);
+}
+
+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;
@@ -909,6 +1073,17 @@ 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);
+ free(hfp);
+ return NULL;
+ }
+
return hfp_hf_ref(hfp);
}
--
1.8.4
Lukasz Rymanowski
2014-10-24 11:59:28 UTC
Permalink
---
src/shared/hfp.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/hfp.h | 9 +++++
2 files changed, 109 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index b7855ed..84c0db1 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,29 @@ 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 (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 +900,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 +948,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 +1010,57 @@ 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;
+
+ /* Cast to void as queue_remove needs that */
+ handler = queue_remove_if(hfp->event_handlers,
+ match_handler_event_prefix,
+ (void *) 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 d98d14b..3860e25 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -125,6 +125,11 @@ 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_result;
+
+typedef void (*hfp_hf_result_func_t)(struct hfp_hf_result *result,
+ void *user_data);
+
struct hfp_hf;

struct hfp_hf *hfp_hf_new(int fd);
@@ -139,3 +144,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
Lukasz Rymanowski
2014-10-24 11:59:25 UTC
Permalink
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 | 7 +++++
2 files changed, 99 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..0b57e2e 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -124,3 +124,10 @@ 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;
+
+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
Lukasz Rymanowski
2014-10-24 11:59:26 UTC
Permalink
---
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 0b57e2e..21d205b 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -131,3 +131,6 @@ 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
Lukasz Rymanowski
2014-10-24 11:59:27 UTC
Permalink
---
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 21d205b..d98d14b 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -134,3 +134,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
Loading...