| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <ctype.h> |
| |
| #include "src/shared/util.h" |
| #include "src/shared/ringbuf.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/io.h" |
| #include "src/shared/hfp.h" |
| |
| struct hfp_gw { |
| int ref_count; |
| int fd; |
| bool close_on_unref; |
| struct io *io; |
| struct ringbuf *read_buf; |
| struct ringbuf *write_buf; |
| struct queue *cmd_handlers; |
| bool writer_active; |
| bool result_pending; |
| hfp_command_func_t command_callback; |
| hfp_destroy_func_t command_destroy; |
| void *command_data; |
| hfp_debug_func_t debug_callback; |
| 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; |
| }; |
| |
| struct hfp_hf { |
| int ref_count; |
| int fd; |
| bool close_on_unref; |
| struct io *io; |
| 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; |
| 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; |
| }; |
| |
| struct cmd_handler { |
| char *prefix; |
| void *user_data; |
| hfp_destroy_func_t destroy; |
| hfp_result_func_t callback; |
| }; |
| |
| struct hfp_context { |
| const char *data; |
| unsigned int offset; |
| }; |
| |
| struct cmd_response { |
| hfp_response_func_t resp_cb; |
| struct hfp_context *response; |
| char *resp_data; |
| void *user_data; |
| }; |
| |
| 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; |
| |
| if (handler->destroy) |
| handler->destroy(handler->user_data); |
| |
| free(handler->prefix); |
| |
| free(handler); |
| } |
| |
| static bool match_handler_prefix(const void *a, const void *b) |
| { |
| const struct cmd_handler *handler = a; |
| const char *prefix = b; |
| |
| if (strcmp(handler->prefix, prefix) != 0) |
| return false; |
| |
| return true; |
| } |
| |
| static void write_watch_destroy(void *user_data) |
| { |
| struct hfp_gw *hfp = user_data; |
| |
| hfp->writer_active = false; |
| } |
| |
| static bool can_write_data(struct io *io, void *user_data) |
| { |
| struct hfp_gw *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 wakeup_writer(struct hfp_gw *hfp) |
| { |
| if (hfp->writer_active) |
| return; |
| |
| if (!ringbuf_len(hfp->write_buf)) |
| return; |
| |
| if (!io_set_write_handler(hfp->io, can_write_data, |
| hfp, write_watch_destroy)) |
| return; |
| |
| hfp->writer_active = true; |
| } |
| |
| static void skip_whitespace(struct hfp_context *context) |
| { |
| while (context->data[context->offset] == ' ') |
| context->offset++; |
| } |
| |
| static void handle_unknown_at_command(struct hfp_gw *hfp, |
| const char *data) |
| { |
| if (hfp->command_callback) { |
| hfp->result_pending = true; |
| hfp->command_callback(data, hfp->command_data); |
| } else { |
| hfp_gw_send_result(hfp, HFP_RESULT_ERROR); |
| } |
| } |
| |
| static bool handle_at_command(struct hfp_gw *hfp, const char *data) |
| { |
| struct cmd_handler *handler; |
| const char *separators = ";?=\0"; |
| struct hfp_context context; |
| enum hfp_gw_cmd_type type; |
| char lookup_prefix[18]; |
| uint8_t pref_len = 0; |
| const char *prefix; |
| int i; |
| |
| context.offset = 0; |
| context.data = data; |
| |
| skip_whitespace(&context); |
| |
| if (strlen(data + context.offset) < 3) |
| return false; |
| |
| if (strncmp(data + context.offset, "AT", 2)) |
| if (strncmp(data + context.offset, "at", 2)) |
| return false; |
| |
| context.offset += 2; |
| prefix = data + context.offset; |
| |
| if (isalpha(prefix[0])) { |
| lookup_prefix[pref_len++] = toupper(prefix[0]); |
| } else { |
| pref_len = strcspn(prefix, separators); |
| if (pref_len > 17 || pref_len < 2) |
| return false; |
| |
| for (i = 0; i < pref_len; i++) |
| lookup_prefix[i] = toupper(prefix[i]); |
| } |
| |
| lookup_prefix[pref_len] = '\0'; |
| context.offset += pref_len; |
| |
| if (lookup_prefix[0] == 'D') { |
| type = HFP_GW_CMD_TYPE_SET; |
| goto done; |
| } |
| |
| if (data[context.offset] == '=') { |
| context.offset++; |
| if (data[context.offset] == '?') { |
| context.offset++; |
| type = HFP_GW_CMD_TYPE_TEST; |
| } else { |
| type = HFP_GW_CMD_TYPE_SET; |
| } |
| goto done; |
| } |
| |
| if (data[context.offset] == '?') { |
| context.offset++; |
| type = HFP_GW_CMD_TYPE_READ; |
| goto done; |
| } |
| |
| type = HFP_GW_CMD_TYPE_COMMAND; |
| |
| done: |
| |
| handler = queue_find(hfp->cmd_handlers, match_handler_prefix, |
| lookup_prefix); |
| if (!handler) { |
| handle_unknown_at_command(hfp, data); |
| return true; |
| } |
| |
| hfp->result_pending = true; |
| handler->callback(&context, type, handler->user_data); |
| |
| return true; |
| } |
| |
| static void next_field(struct hfp_context *context) |
| { |
| if (context->data[context->offset] == ',') |
| context->offset++; |
| } |
| |
| bool hfp_context_get_number_default(struct hfp_context *context, |
| unsigned int *val, |
| unsigned int default_val) |
| { |
| skip_whitespace(context); |
| |
| if (context->data[context->offset] == ',') { |
| if (val) |
| *val = default_val; |
| |
| context->offset++; |
| return true; |
| } |
| |
| return hfp_context_get_number(context, val); |
| } |
| |
| bool hfp_context_get_number(struct hfp_context *context, |
| unsigned int *val) |
| { |
| unsigned int i; |
| int tmp = 0; |
| |
| skip_whitespace(context); |
| |
| i = context->offset; |
| |
| while (context->data[i] >= '0' && context->data[i] <= '9') |
| tmp = tmp * 10 + context->data[i++] - '0'; |
| |
| if (i == context->offset) |
| return false; |
| |
| if (val) |
| *val = tmp; |
| context->offset = i; |
| |
| skip_whitespace(context); |
| next_field(context); |
| |
| return true; |
| } |
| |
| bool hfp_context_open_container(struct hfp_context *context) |
| { |
| skip_whitespace(context); |
| |
| /* The list shall be preceded by a left parenthesis "(") */ |
| if (context->data[context->offset] != '(') |
| return false; |
| |
| context->offset++; |
| |
| return true; |
| } |
| |
| bool hfp_context_close_container(struct hfp_context *context) |
| { |
| skip_whitespace(context); |
| |
| /* The list shall be followed by a right parenthesis (")" V250 5.7.3.1*/ |
| if (context->data[context->offset] != ')') |
| return false; |
| |
| context->offset++; |
| |
| next_field(context); |
| |
| return true; |
| } |
| |
| bool hfp_context_get_string(struct hfp_context *context, char *buf, |
| uint8_t len) |
| { |
| int i = 0; |
| const char *data = context->data; |
| unsigned int offset; |
| |
| skip_whitespace(context); |
| |
| if (data[context->offset] != '"') |
| return false; |
| |
| offset = context->offset; |
| offset++; |
| |
| while (data[offset] != '\0' && data[offset] != '"') { |
| if (i == len) |
| return false; |
| |
| buf[i++] = data[offset]; |
| offset++; |
| } |
| |
| if (i == len) |
| return false; |
| |
| buf[i] = '\0'; |
| |
| if (data[offset] == '"') |
| offset++; |
| else |
| return false; |
| |
| context->offset = offset; |
| |
| skip_whitespace(context); |
| next_field(context); |
| |
| return true; |
| } |
| |
| bool hfp_context_get_unquoted_string(struct hfp_context *context, |
| char *buf, uint8_t len) |
| { |
| const char *data = context->data; |
| unsigned int offset; |
| int i = 0; |
| char c; |
| |
| skip_whitespace(context); |
| |
| c = data[context->offset]; |
| if (c == '"' || c == ')' || c == '(') |
| return false; |
| |
| offset = context->offset; |
| |
| while (data[offset] != '\0' && data[offset] != ',' && |
| data[offset] != ')') { |
| if (i == len) |
| return false; |
| |
| buf[i++] = data[offset]; |
| offset++; |
| } |
| |
| if (i == len) |
| return false; |
| |
| buf[i] = '\0'; |
| |
| context->offset = offset; |
| |
| next_field(context); |
| |
| return true; |
| } |
| |
| bool hfp_context_has_next(struct hfp_context *context) |
| { |
| return context->data[context->offset] != '\0'; |
| } |
| |
| void hfp_context_skip_field(struct hfp_context *context) |
| { |
| const char *data = context->data; |
| unsigned int offset = context->offset; |
| |
| while (data[offset] != '\0' && data[offset] != ',') |
| offset++; |
| |
| context->offset = offset; |
| next_field(context); |
| } |
| |
| bool hfp_context_get_range(struct hfp_context *context, uint32_t *min, |
| uint32_t *max) |
| { |
| uint32_t l, h; |
| uint32_t start; |
| |
| start = context->offset; |
| |
| if (!hfp_context_get_number(context, &l)) |
| goto failed; |
| |
| if (context->data[context->offset] != '-') |
| goto failed; |
| |
| context->offset++; |
| |
| if (!hfp_context_get_number(context, &h)) |
| goto failed; |
| |
| *min = l; |
| *max = h; |
| |
| next_field(context); |
| |
| return true; |
| |
| failed: |
| context->offset = start; |
| return false; |
| } |
| |
| static void process_input(struct hfp_gw *hfp) |
| { |
| char *str, *ptr; |
| size_t len, count; |
| bool free_ptr = false; |
| bool read_again; |
| |
| do { |
| str = ringbuf_peek(hfp->read_buf, 0, &len); |
| if (!str) |
| return; |
| |
| ptr = memchr(str, '\r', len); |
| if (!ptr) { |
| char *str2; |
| size_t len2; |
| |
| /* |
| * If there is no more data in ringbuffer, |
| * it's just an incomplete command. |
| */ |
| if (len == ringbuf_len(hfp->read_buf)) |
| return; |
| |
| str2 = ringbuf_peek(hfp->read_buf, len, &len2); |
| if (!str2) |
| return; |
| |
| ptr = memchr(str2, '\r', len2); |
| if (!ptr) |
| return; |
| |
| *ptr = '\0'; |
| |
| count = len2 + len; |
| ptr = malloc(count); |
| if (!ptr) |
| return; |
| |
| memcpy(ptr, str, len); |
| memcpy(ptr + len, str2, len2); |
| |
| free_ptr = true; |
| str = ptr; |
| } else { |
| count = ptr - str; |
| *ptr = '\0'; |
| } |
| |
| if (!handle_at_command(hfp, str)) |
| /* |
| * Command is not handled that means that was some |
| * trash. Let's skip that and keep reading from ring |
| * buffer. |
| */ |
| read_again = true; |
| else |
| /* |
| * Command has been handled. If we are waiting for a |
| * result from upper layer, we can stop reading. If we |
| * already reply i.e. ERROR on unknown command, then we |
| * can keep reading ring buffer. Actually ring buffer |
| * should be empty but lets just look there. |
| */ |
| read_again = !hfp->result_pending; |
| |
| ringbuf_drain(hfp->read_buf, count + 1); |
| |
| if (free_ptr) |
| free(ptr); |
| |
| } while (read_again); |
| } |
| |
| static void read_watch_destroy(void *user_data) |
| { |
| } |
| |
| static bool can_read_data(struct io *io, void *user_data) |
| { |
| struct hfp_gw *hfp = user_data; |
| ssize_t bytes_read; |
| |
| bytes_read = ringbuf_read(hfp->read_buf, hfp->fd); |
| if (bytes_read < 0) |
| return false; |
| |
| if (hfp->result_pending) |
| return true; |
| |
| process_input(hfp); |
| |
| return true; |
| } |
| |
| struct hfp_gw *hfp_gw_new(int fd) |
| { |
| struct hfp_gw *hfp; |
| |
| if (fd < 0) |
| return NULL; |
| |
| hfp = new0(struct hfp_gw, 1); |
| 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; |
| } |
| |
| hfp->cmd_handlers = queue_new(); |
| |
| if (!io_set_read_handler(hfp->io, can_read_data, hfp, |
| read_watch_destroy)) { |
| queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); |
| io_destroy(hfp->io); |
| ringbuf_free(hfp->write_buf); |
| ringbuf_free(hfp->read_buf); |
| free(hfp); |
| return NULL; |
| } |
| |
| hfp->writer_active = false; |
| hfp->result_pending = false; |
| |
| return hfp_gw_ref(hfp); |
| } |
| |
| struct hfp_gw *hfp_gw_ref(struct hfp_gw *hfp) |
| { |
| if (!hfp) |
| return NULL; |
| |
| __sync_fetch_and_add(&hfp->ref_count, 1); |
| |
| return hfp; |
| } |
| |
| void hfp_gw_unref(struct hfp_gw *hfp) |
| { |
| if (!hfp) |
| return; |
| |
| if (__sync_sub_and_fetch(&hfp->ref_count, 1)) |
| return; |
| |
| hfp_gw_set_command_handler(hfp, NULL, NULL, NULL); |
| |
| 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); |
| |
| hfp_gw_set_debug(hfp, NULL, NULL, NULL); |
| |
| ringbuf_free(hfp->read_buf); |
| hfp->read_buf = NULL; |
| |
| ringbuf_free(hfp->write_buf); |
| hfp->write_buf = NULL; |
| |
| queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); |
| hfp->cmd_handlers = NULL; |
| |
| if (!hfp->in_disconnect) { |
| free(hfp); |
| return; |
| } |
| |
| hfp->destroyed = true; |
| } |
| |
| static void read_tracing(const void *buf, size_t count, void *user_data) |
| { |
| struct hfp_gw *hfp = user_data; |
| |
| util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data); |
| } |
| |
| static void write_tracing(const void *buf, size_t count, void *user_data) |
| { |
| struct hfp_gw *hfp = user_data; |
| |
| util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data); |
| } |
| |
| bool hfp_gw_set_debug(struct hfp_gw *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, read_tracing, hfp); |
| ringbuf_set_input_tracing(hfp->write_buf, 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_gw_set_close_on_unref(struct hfp_gw *hfp, bool do_close) |
| { |
| if (!hfp) |
| return false; |
| |
| hfp->close_on_unref = do_close; |
| |
| return true; |
| } |
| |
| bool hfp_gw_send_result(struct hfp_gw *hfp, enum hfp_result result) |
| { |
| const char *str; |
| |
| if (!hfp) |
| return false; |
| |
| switch (result) { |
| case HFP_RESULT_OK: |
| str = "OK"; |
| break; |
| case HFP_RESULT_ERROR: |
| str = "ERROR"; |
| break; |
| case HFP_RESULT_RING: |
| case HFP_RESULT_NO_CARRIER: |
| case HFP_RESULT_BUSY: |
| case HFP_RESULT_NO_ANSWER: |
| case HFP_RESULT_DELAYED: |
| case HFP_RESULT_BLACKLISTED: |
| case HFP_RESULT_CME_ERROR: |
| case HFP_RESULT_NO_DIALTONE: |
| case HFP_RESULT_CONNECT: |
| default: |
| return false; |
| } |
| |
| if (ringbuf_printf(hfp->write_buf, "\r\n%s\r\n", str) < 0) |
| return false; |
| |
| wakeup_writer(hfp); |
| |
| /* |
| * There might be already something to read in the ring buffer. |
| * If so, let's read it. |
| */ |
| if (hfp->result_pending) { |
| hfp->result_pending = false; |
| process_input(hfp); |
| } |
| |
| return true; |
| } |
| |
| bool hfp_gw_send_error(struct hfp_gw *hfp, enum hfp_error error) |
| { |
| if (!hfp) |
| return false; |
| |
| if (ringbuf_printf(hfp->write_buf, "\r\n+CME ERROR: %u\r\n", error) < 0) |
| return false; |
| |
| wakeup_writer(hfp); |
| |
| /* |
| * There might be already something to read in the ring buffer. |
| * If so, let's read it. |
| */ |
| if (hfp->result_pending) { |
| hfp->result_pending = false; |
| process_input(hfp); |
| } |
| |
| return true; |
| } |
| |
| bool hfp_gw_send_info(struct hfp_gw *hfp, const char *format, ...) |
| { |
| va_list ap; |
| char *fmt; |
| int len; |
| |
| if (!hfp || !format) |
| return false; |
| |
| if (asprintf(&fmt, "\r\n%s\r\n", format) < 0) |
| return false; |
| |
| va_start(ap, format); |
| len = ringbuf_vprintf(hfp->write_buf, fmt, ap); |
| va_end(ap); |
| |
| free(fmt); |
| |
| if (len < 0) |
| return false; |
| |
| if (hfp->result_pending) |
| return true; |
| |
| wakeup_writer(hfp); |
| |
| return true; |
| } |
| |
| bool hfp_gw_set_command_handler(struct hfp_gw *hfp, |
| hfp_command_func_t callback, |
| void *user_data, hfp_destroy_func_t destroy) |
| { |
| if (!hfp) |
| return false; |
| |
| if (hfp->command_destroy) |
| hfp->command_destroy(hfp->command_data); |
| |
| hfp->command_callback = callback; |
| hfp->command_destroy = destroy; |
| hfp->command_data = user_data; |
| |
| return true; |
| } |
| |
| bool hfp_gw_register(struct hfp_gw *hfp, hfp_result_func_t callback, |
| const char *prefix, |
| void *user_data, |
| hfp_destroy_func_t destroy) |
| { |
| struct cmd_handler *handler; |
| |
| handler = new0(struct cmd_handler, 1); |
| handler->callback = callback; |
| handler->user_data = user_data; |
| |
| handler->prefix = strdup(prefix); |
| if (!handler->prefix) { |
| free(handler); |
| return false; |
| } |
| |
| if (queue_find(hfp->cmd_handlers, match_handler_prefix, |
| handler->prefix)) { |
| destroy_cmd_handler(handler); |
| return false; |
| } |
| |
| handler->destroy = destroy; |
| |
| return queue_push_tail(hfp->cmd_handlers, handler); |
| } |
| |
| bool hfp_gw_unregister(struct hfp_gw *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->cmd_handlers, match_handler_prefix, |
| lookup_prefix); |
| free(lookup_prefix); |
| |
| if (!handler) |
| return false; |
| |
| destroy_cmd_handler(handler); |
| |
| return true; |
| } |
| |
| static void disconnect_watch_destroy(void *user_data) |
| { |
| struct hfp_gw *hfp = user_data; |
| |
| if (hfp->disconnect_destroy) |
| hfp->disconnect_destroy(hfp->disconnect_data); |
| |
| if (hfp->destroyed) |
| free(hfp); |
| } |
| |
| static bool io_disconnected(struct io *io, void *user_data) |
| { |
| struct hfp_gw *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_gw_set_disconnect_handler(struct hfp_gw *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, io_disconnected, hfp, |
| 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_gw_disconnect(struct hfp_gw *hfp) |
| { |
| if (!hfp) |
| return false; |
| |
| 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 (strcmp(handler->prefix, prefix) != 0) |
| 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); |
| } |
| |
| 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_context *context) |
| { |
| while (context->data[context->offset] == ' ') |
| context->offset++; |
| } |
| |
| static bool is_response(const char *prefix, enum hfp_result *result, |
| enum hfp_error *cme_err, |
| struct hfp_context *context) |
| { |
| if (strcmp(prefix, "OK") == 0) { |
| *result = HFP_RESULT_OK; |
| /* |
| * Set cme_err to 0 as this is not valid when result is not |
| * CME ERROR |
| */ |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "ERROR") == 0) { |
| *result = HFP_RESULT_ERROR; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "NO CARRIER") == 0) { |
| *result = HFP_RESULT_NO_CARRIER; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "NO ANSWER") == 0) { |
| *result = HFP_RESULT_NO_ANSWER; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "BUSY") == 0) { |
| *result = HFP_RESULT_BUSY; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "DELAYED") == 0) { |
| *result = HFP_RESULT_DELAYED; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "BLACKLISTED") == 0) { |
| *result = HFP_RESULT_BLACKLISTED; |
| *cme_err = 0; |
| return true; |
| } |
| |
| if (strcmp(prefix, "+CME ERROR") == 0) { |
| uint32_t val; |
| |
| *result = HFP_RESULT_CME_ERROR; |
| |
| if (hfp_context_get_number(context, &val) && |
| val <= HFP_ERROR_NETWORK_NOT_ALLOWED) |
| *cme_err = val; |
| else |
| *cme_err = HFP_ERROR_AG_FAILURE; |
| |
| 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_context context; |
| enum hfp_result result; |
| enum hfp_error cme_err; |
| char lookup_prefix[18]; |
| uint8_t pref_len = 0; |
| const char *prefix; |
| int i; |
| |
| context.offset = 0; |
| context.data = data; |
| |
| hf_skip_whitespace(&context); |
| |
| if (strlen(data + context.offset) < 2) |
| return; |
| |
| prefix = data + context.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'; |
| context.offset += pref_len + 1; |
| |
| if (is_response(lookup_prefix, &result, &cme_err, &context)) { |
| struct cmd_response *cmd; |
| |
| cmd = queue_peek_head(hfp->cmd_queue); |
| if (!cmd) |
| return; |
| |
| cmd->resp_cb(result, cme_err, 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) |
| return; |
| |
| handler->callback(&context, 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; |
| |
| 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); |
| if (!tmp) |
| goto done; |
| |
| /* "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; |
| |
| if (fd < 0) |
| return NULL; |
| |
| hfp = new0(struct hfp_hf, 1); |
| 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; |
| } |
| |
| hfp->event_handlers = queue_new(); |
| hfp->cmd_queue = queue_new(); |
| hfp->writer_active = false; |
| |
| 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); |
| } |
| |
| 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); |
| |
| hfp_hf_set_debug(hfp, NULL, NULL, NULL); |
| |
| ringbuf_free(hfp->read_buf); |
| hfp->read_buf = NULL; |
| |
| ringbuf_free(hfp->write_buf); |
| hfp->write_buf = NULL; |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| |
| 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, |
| hfp_destroy_func_t destroy) |
| { |
| struct event_handler *handler; |
| |
| if (!callback) |
| return false; |
| |
| handler = new0(struct event_handler, 1); |
| 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; |
| |
| 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); |
| } |