From 0ec6287f5393a11c8644d93562ada5de9187db7b Mon Sep 17 00:00:00 2001 From: Denis Kenzior Date: Fri, 8 May 2009 14:49:56 -0700 Subject: Add AT chat library implementation --- gatchat/Makefile.am | 3 +- gatchat/gatchat.c | 1104 ++++++++++++++++++++++++++++++++++++++++++++++++++ gatchat/gatchat.h | 105 +++++ gatchat/gatresult.c | 377 +++++++++++++++++ gatchat/gatresult.h | 69 ++++ gatchat/ringbuffer.c | 199 +++++++++ gatchat/ringbuffer.h | 124 ++++++ 7 files changed, 1980 insertions(+), 1 deletion(-) create mode 100644 gatchat/gatchat.c create mode 100644 gatchat/gatchat.h create mode 100644 gatchat/gatresult.c create mode 100644 gatchat/gatresult.h create mode 100644 gatchat/ringbuffer.c create mode 100644 gatchat/ringbuffer.h diff --git a/gatchat/Makefile.am b/gatchat/Makefile.am index 56c17421..9f1da113 100644 --- a/gatchat/Makefile.am +++ b/gatchat/Makefile.am @@ -1,7 +1,8 @@ noinst_LTLIBRARIES = libgatchat.la -libgatchat_la_SOURCES = +libgatchat_la_SOURCES = gatchat.h gatchat.c gatresult.h gatresult.c \ + ringbuffer.h ringbuffer.c AM_CFLAGS = @GLIB_CFLAGS@ diff --git a/gatchat/gatchat.c b/gatchat/gatchat.c new file mode 100644 index 00000000..e54e5d93 --- /dev/null +++ b/gatchat/gatchat.c @@ -0,0 +1,1104 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "ringbuffer.h" +#include "gatresult.h" +#include "gatchat.h" + +/* #define WRITE_SCHEDULER_DEBUG 1 */ + +static void g_at_chat_wakeup_writer(GAtChat *chat); + +enum chat_state { + PARSER_STATE_IDLE = 0, + PARSER_STATE_INITIAL_CR, + PARSER_STATE_INITIAL_LF, + PARSER_STATE_RESPONSE, + PARSER_STATE_TERMINATOR_CR, + PARSER_STATE_RESPONSE_COMPLETE, + PARSER_STATE_PDU, + PARSER_STATE_PDU_CR, + PARSER_STATE_PDU_COMPLETE, + PARSER_STATE_PROMPT, + PARSER_STATE_PROMPT_COMPLETE +}; + +struct at_command { + char *cmd; + char **prefixes; + guint id; + GAtResultFunc callback; + gpointer user_data; + GDestroyNotify notify; +}; + +struct at_notify_node { + guint id; + GAtNotifyFunc callback; + gpointer user_data; + GDestroyNotify notify; +}; + +struct at_notify { + GSList *nodes; + gboolean pdu; +}; + +struct _GAtChat { + gint ref_count; /* Ref count */ + guint next_cmd_id; /* Next command id */ + guint next_notify_id; /* Next notify id */ + guint read_watch; /* GSource read id, 0 if none */ + guint write_watch; /* GSource write id, 0 if none */ + GIOChannel *channel; /* channel */ + GQueue *command_queue; /* Command queue */ + guint cmd_bytes_written; /* bytes written from cmd */ + GHashTable *notify_list; /* List of notification reg */ + GAtDisconnectFunc user_disconnect; /* user disconnect func */ + gpointer user_disconnect_data; /* user disconnect data */ + struct ring_buffer *buf; /* Current read buffer */ + guint read_so_far; /* Number of bytes processed */ + gboolean disconnecting; /* Whether we're disconnecting */ + enum chat_state state; /* Current chat state */ + int flags; + char *pdu_notify; /* Unsolicited Resp w/ PDU */ + GSList *response_lines; /* char * lines of the response */ + char *wakeup; /* command sent to wakeup modem */ + gdouble inactivity_time; /* Period of inactivity */ + guint wakeup_timeout; /* How long to wait for resp */ + GTimer *wakeup_timer; /* Keep track of elapsed time */ +}; + +static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b) +{ + const struct at_notify_node *node = a; + guint id = GPOINTER_TO_UINT(b); + + if (node->id < id) + return -1; + + if (node->id > id) + return 1; + + return 0; +} + +static void at_notify_node_destroy(struct at_notify_node *node) +{ + if (node->notify) + node->notify(node->user_data); + + g_free(node); +} + +static void at_notify_destroy(struct at_notify *notify) +{ + g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL); + g_free(notify); +} + +static gint at_command_compare_by_id(gconstpointer a, gconstpointer b) +{ + const struct at_command *command = a; + guint id = GPOINTER_TO_UINT(b); + + if (command->id < id) + return -1; + + if (command->id > id) + return 1; + + return 0; +} + +static struct at_command *at_command_create(const char *cmd, + const char **prefix_list, + GAtResultFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + struct at_command *c; + gsize len; + char **prefixes = NULL; + + if (prefix_list) { + int num_prefixes = 0; + int i; + + while (prefix_list[num_prefixes]) + num_prefixes += 1; + + prefixes = g_new(char *, num_prefixes + 1); + + for (i = 0; i < num_prefixes; i++) + prefixes[i] = strdup(prefix_list[i]); + + prefixes[num_prefixes] = NULL; + } + + c = g_try_new0(struct at_command, 1); + + if (!c) + return 0; + + len = strlen(cmd); + c->cmd = g_try_new(char, len + 2); + + if (!c->cmd) { + g_free(c); + return 0; + } + + memcpy(c->cmd, cmd, len); + + /* If we have embedded '\r' then this is a command expecting a prompt + * from the modem. Embed Ctrl-Z at the very end automatically + */ + if (strchr(cmd, '\r')) + c->cmd[len] = 26; + else + c->cmd[len] = '\r'; + + c->cmd[len+1] = '\0'; + + c->prefixes = prefixes; + c->callback = func; + c->user_data = user_data; + c->notify = notify; + + return c; +} + +static void at_command_destroy(struct at_command *cmd) +{ + if (cmd->notify) + cmd->notify(cmd->user_data); + + g_strfreev(cmd->prefixes); + g_free(cmd->cmd); + g_free(cmd); +} + +static void g_at_chat_cleanup(GAtChat *chat) +{ + struct at_command *c; + + ring_buffer_free(chat->buf); + chat->buf = NULL; + + /* Cleanup pending commands */ + while ((c = g_queue_pop_head(chat->command_queue))) + at_command_destroy(c); + + g_queue_free(chat->command_queue); + chat->command_queue = NULL; + + /* Cleanup any response lines we have pending */ + g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL); + g_slist_free(chat->response_lines); + chat->response_lines = NULL; + + /* Cleanup registered notifications */ + g_hash_table_destroy(chat->notify_list); + chat->notify_list = NULL; + + if (chat->pdu_notify) { + g_free(chat->pdu_notify); + chat->pdu_notify = NULL; + } + + if (chat->wakeup) { + g_free(chat->wakeup); + chat->wakeup = NULL; + } + + if (chat->wakeup_timer) { + g_timer_destroy(chat->wakeup_timer); + chat->wakeup_timer = 0; + } +} + +static void read_watcher_destroy_notify(GAtChat *chat) +{ + chat->read_watch = 0; + + if (chat->disconnecting) + return; + + chat->channel = NULL; + + g_at_chat_cleanup(chat); + + if (chat->user_disconnect) + chat->user_disconnect(chat->user_disconnect_data); +} + +static void write_watcher_destroy_notify(GAtChat *chat) +{ + chat->write_watch = 0; +} + +static void at_notify_call_callback(gpointer data, gpointer user_data) +{ + struct at_notify_node *node = data; + GAtResult *result = user_data; + + node->callback(result, node->user_data); +} + +static gboolean g_at_chat_match_notify(GAtChat *chat, char *line) +{ + GHashTableIter iter; + struct at_notify *notify; + char *prefix; + gpointer key, value; + gboolean ret = FALSE; + GAtResult result; + + g_hash_table_iter_init(&iter, chat->notify_list); + result.lines = 0; + result.final_or_pdu = 0; + + while (g_hash_table_iter_next(&iter, &key, &value)) { + prefix = key; + notify = value; + + if (!g_str_has_prefix(line, key)) + continue; + + if (notify->pdu) { + chat->pdu_notify = line; + chat->state = PARSER_STATE_PDU; + return TRUE; + } + + if (!result.lines) + result.lines = g_slist_prepend(NULL, line); + + g_slist_foreach(notify->nodes, at_notify_call_callback, + &result); + ret = TRUE; + } + + if (ret) { + g_slist_free(result.lines); + g_free(line); + chat->state = PARSER_STATE_IDLE; + } + + return ret; +} + +static void g_at_chat_finish_command(GAtChat *p, gboolean ok, + char *final) +{ + struct at_command *cmd = g_queue_pop_head(p->command_queue); + + /* Cannot happen, but lets be paranoid */ + if (!cmd) + return; + + if (cmd->callback) { + GAtResult result; + + p->response_lines = g_slist_reverse(p->response_lines); + + result.final_or_pdu = final; + result.lines = p->response_lines; + + cmd->callback(ok, &result, cmd->user_data); + } + + g_slist_foreach(p->response_lines, (GFunc)g_free, NULL); + g_slist_free(p->response_lines); + p->response_lines = NULL; + + g_free(final); + + at_command_destroy(cmd); + + p->cmd_bytes_written = 0; + + if (g_queue_peek_head(p->command_queue)) + g_at_chat_wakeup_writer(p); +} + +struct terminator_info { + const char *terminator; + int len; + gboolean success; +}; + +static struct terminator_info terminator_table[] = { + { "OK", -1, TRUE }, + { "ERROR", -1, FALSE }, + { "NO DIALTONE", -1, FALSE }, + { "BUSY", -1, FALSE }, + { "NO CARRIER", -1, FALSE }, + { "CONNECT", -1, TRUE }, + { "NO ANSWER", -1, FALSE }, + { "+CMS ERROR:", 11, FALSE }, + { "+CME ERROR:", 11, FALSE }, + { "+EXT ERROR:", 11, FALSE } +}; + +static gboolean g_at_chat_handle_command_response(GAtChat *p, + struct at_command *cmd, + char *line) +{ + int i; + int size = sizeof(terminator_table) / sizeof(struct terminator_info); + + p->state = PARSER_STATE_IDLE; + + for (i = 0; i < size; i++) { + struct terminator_info *info = &terminator_table[i]; + + if (info->len == -1 && !strcmp(line, info->terminator)) { + g_at_chat_finish_command(p, info->success, line); + return TRUE; + } + + if (info->len > 0 && + !strncmp(line, info->terminator, info->len)) { + g_at_chat_finish_command(p, info->success, line); + return TRUE; + } + } + + if (cmd->prefixes) { + int i; + + for (i = 0; cmd->prefixes[i]; i++) + if (g_str_has_prefix(line, cmd->prefixes[i])) + goto out; + + return FALSE; + } + +out: + p->response_lines = g_slist_prepend(p->response_lines, + line); + + return TRUE; +} + +static void have_line(GAtChat *p) +{ + /* We're not going to copy terminal */ + unsigned int len = p->read_so_far - 2; + char *str; + struct at_command *cmd; + + /* If we have preceding modify the len */ + if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0) + len -= 2; + + /* Make sure we have terminal null */ + str = g_try_new(char, len + 1); + + if (!str) { + ring_buffer_drain(p->buf, p->read_so_far); + return; + } + + if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0) + ring_buffer_drain(p->buf, 2); + ring_buffer_read(p->buf, str, len); + ring_buffer_drain(p->buf, 2); + + str[len] = '\0'; + + /* Check for echo, this should not happen, but lets be paranoid */ + if (!strncmp(str, "AT", 2) == TRUE) + goto done; + + cmd = g_queue_peek_head(p->command_queue); + + if (cmd && p->cmd_bytes_written == strlen(cmd->cmd) && + g_at_chat_handle_command_response(p, cmd, str)) + return; + + if (g_at_chat_match_notify(p, str) == TRUE) + return; + +done: + /* No matches & no commands active, ignore line */ + g_free(str); + p->state = PARSER_STATE_IDLE; +} + +static void have_pdu(GAtChat *p) +{ + unsigned int len = p->read_so_far - 2; + char *pdu; + GHashTableIter iter; + struct at_notify *notify; + char *prefix; + gpointer key, value; + GAtResult result; + + pdu = g_try_new(char, len + 1); + + if (!pdu) { + ring_buffer_drain(p->buf, p->read_so_far); + goto out; + } + + ring_buffer_read(p->buf, pdu, len); + ring_buffer_drain(p->buf, 2); + + pdu[len] = '\0'; + + result.lines = g_slist_prepend(NULL, p->pdu_notify); + result.final_or_pdu = pdu; + + g_hash_table_iter_init(&iter, p->notify_list); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + prefix = key; + notify = value; + + if (!g_str_has_prefix(p->pdu_notify, prefix)) + continue; + + if (!notify->pdu) + continue; + + g_slist_foreach(notify->nodes, at_notify_call_callback, + &result); + } + + g_slist_free(result.lines); + +out: + g_free(p->pdu_notify); + p->pdu_notify = NULL; + + if (pdu) + g_free(pdu); + + p->state = PARSER_STATE_IDLE; +} + +static inline void parse_char(GAtChat *chat, char byte) +{ + switch (chat->state) { + case PARSER_STATE_IDLE: + if (byte == '\r') + chat->state = PARSER_STATE_INITIAL_CR; + else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) { + if (byte == '>') + chat->state = PARSER_STATE_PROMPT; + else + chat->state = PARSER_STATE_RESPONSE; + } + break; + + case PARSER_STATE_INITIAL_CR: + if (byte == '\n') + chat->state = PARSER_STATE_INITIAL_LF; + else if (byte != '\r' && /* Echo & no ?! */ + (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF)) + chat->state = PARSER_STATE_RESPONSE; + else if (byte != '\r') + chat->state = PARSER_STATE_IDLE; + break; + + case PARSER_STATE_INITIAL_LF: + if (byte == '\r') + chat->state = PARSER_STATE_TERMINATOR_CR; + else if (byte == '>') + chat->state = PARSER_STATE_PROMPT; + else + chat->state = PARSER_STATE_RESPONSE; + break; + + case PARSER_STATE_RESPONSE: + if (byte == '\r') + chat->state = PARSER_STATE_TERMINATOR_CR; + break; + + case PARSER_STATE_TERMINATOR_CR: + if (byte == '\n') + chat->state = PARSER_STATE_RESPONSE_COMPLETE; + else + chat->state = PARSER_STATE_IDLE; + break; + + case PARSER_STATE_PDU: + if (byte == '\r') + chat->state = PARSER_STATE_PDU_CR; + break; + + case PARSER_STATE_PDU_CR: + if (byte == '\n') + chat->state = PARSER_STATE_PDU_COMPLETE; + break; + + case PARSER_STATE_PROMPT: + if (byte == ' ') + chat->state = PARSER_STATE_PROMPT_COMPLETE; + else + chat->state = PARSER_STATE_RESPONSE; + + case PARSER_STATE_RESPONSE_COMPLETE: + case PARSER_STATE_PDU_COMPLETE: + default: + /* This really shouldn't happen */ + assert(TRUE); + return; + } +} + +static void new_bytes(GAtChat *p) +{ + unsigned int len = ring_buffer_len(p->buf); + unsigned int wrap = ring_buffer_len_no_wrap(p->buf); + unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far); + + while (p->read_so_far < len) { + parse_char(p, *buf); + + buf += 1; + p->read_so_far += 1; + + if (p->read_so_far == wrap) { + buf = ring_buffer_read_ptr(p->buf, p->read_so_far); + wrap = len; + } + + if (p->state == PARSER_STATE_RESPONSE_COMPLETE) { + len -= p->read_so_far; + wrap -= p->read_so_far; + + have_line(p); + + p->read_so_far = 0; + } else if (p->state == PARSER_STATE_PDU_COMPLETE) { + len -= p->read_so_far; + wrap -= p->read_so_far; + + have_pdu(p); + + p->read_so_far = 0; + } else if (p->state == PARSER_STATE_INITIAL_CR) { + len -= p->read_so_far - 1; + wrap -= p->read_so_far - 1; + + ring_buffer_drain(p->buf, p->read_so_far - 1); + + p->read_so_far = 1; + } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) { + len -= p->read_so_far; + wrap -= p->read_so_far; + + g_at_chat_wakeup_writer(p); + + ring_buffer_drain(p->buf, p->read_so_far); + + p->read_so_far = 0; + } + } + + if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) { + ring_buffer_drain(p->buf, p->read_so_far); + p->read_so_far = 0; + } +} + +static gboolean received_data(GIOChannel *channel, GIOCondition cond, + gpointer data) +{ + unsigned char *buf; + GAtChat *chat = data; + GIOError err; + gsize rbytes; + gsize toread; + gsize total_read = 0; + + if (cond & G_IO_NVAL) + return FALSE; + + /* Regardless of condition, try to read all the data available */ + do { + rbytes = 0; + + toread = ring_buffer_avail_no_wrap(chat->buf); + + /* We're going to start overflowing the buffer + * this cannot happen under normal circumstances, so probably + * the channel is getting garbage, drop off + */ + if (toread == 0) { + if (chat->state == PARSER_STATE_RESPONSE) + return FALSE; + + err = G_IO_ERROR_AGAIN; + break; + } + + buf = ring_buffer_write_ptr(chat->buf); + + err = g_io_channel_read(channel, (char *) buf, toread, &rbytes); + + total_read += rbytes; + + if (rbytes > 0) + ring_buffer_write_advance(chat->buf, rbytes); + + } while (err == G_IO_ERROR_NONE && rbytes > 0); + + if (total_read > 0) + new_bytes(chat); + + if (cond & (G_IO_HUP | G_IO_ERR)) + return FALSE; + + if (err == G_IO_ERROR_NONE && rbytes == 0) + return FALSE; + + if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN) + return FALSE; + + return TRUE; +} + +static gboolean wakeup_no_response(gpointer user) +{ + GAtChat *chat = user; + + g_at_chat_finish_command(chat, FALSE, NULL); + + return FALSE; +} + +static gboolean can_write_data(GIOChannel *channel, GIOCondition cond, + gpointer data) +{ + GAtChat *chat = data; + struct at_command *cmd; + GIOError err; + gsize bytes_written; + gsize towrite; + gsize len; + char *cr; + gboolean wakeup_first = FALSE; +#ifdef WRITE_SCHEDULER_DEBUG + int limiter; +#endif + + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) + return FALSE; + + /* Grab the first command off the queue and write as + * much of it as we can + */ + cmd = g_queue_peek_head(chat->command_queue); + + /* For some reason command queue is empty, cancel write watcher */ + if (cmd == NULL) + return FALSE; + + len = strlen(cmd->cmd); + + /* For some reason write watcher fired, but we've already + * written the entire command out to the io channel, + * cancel write watcher + */ + if (chat->cmd_bytes_written >= len) + return FALSE; + + if (chat->wakeup) { + if (!chat->wakeup_timer) { + wakeup_first = TRUE; + chat->wakeup_timer = g_timer_new(); + + } else if (g_timer_elapsed(chat->wakeup_timer, NULL) > + chat->inactivity_time) + wakeup_first = TRUE; + } + + if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) { + cmd = at_command_create(chat->wakeup, NULL, NULL, NULL, NULL); + + if (!cmd) + return FALSE; + + g_queue_push_head(chat->command_queue, cmd); + + len = strlen(chat->wakeup); + + g_timeout_add(chat->wakeup_timeout, wakeup_no_response, + chat); + } + + towrite = len - chat->cmd_bytes_written; + + cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r'); + + if (cr) + towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1; + +#ifdef WRITE_SCHEDULER_DEBUG + limiter = towrite; + + if (limiter > 5) + limiter = 5; +#endif + + err = g_io_channel_write(chat->channel, + cmd->cmd + chat->cmd_bytes_written, +#ifdef WRITE_SCHEDULER_DEBUG + limiter, +#else + towrite, +#endif + &bytes_written); + + if (err != G_IO_ERROR_NONE) { + g_at_chat_shutdown(chat); + return FALSE; + } + + chat->cmd_bytes_written += bytes_written; + + if (bytes_written < towrite) + return TRUE; + + /* Full command submitted, update timer */ + if (chat->wakeup_timer) + g_timer_start(chat->wakeup_timer); + + return FALSE; +} + +static void g_at_chat_wakeup_writer(GAtChat *chat) +{ + if (chat->write_watch != 0) + return; + + chat->write_watch = g_io_add_watch_full(chat->channel, + G_PRIORITY_DEFAULT, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + can_write_data, chat, + (GDestroyNotify)write_watcher_destroy_notify); +} + +GAtChat *g_at_chat_new(GIOChannel *channel, int flags) +{ + GAtChat *chat; + GIOFlags io_flags; + + if (!channel) + return NULL; + + chat = g_try_new0(GAtChat, 1); + + if (!chat) + return chat; + + chat->next_cmd_id = 1; + chat->next_notify_id = 1; + chat->flags = flags; + + chat->buf = ring_buffer_new(4096); + + if (!chat->buf) + goto error; + + chat->command_queue = g_queue_new(); + + if (!chat->command_queue) + goto error; + + chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, (GDestroyNotify)at_notify_destroy); + + if (g_io_channel_set_encoding(channel, NULL, NULL) != + G_IO_STATUS_NORMAL) + goto error; + + io_flags = g_io_channel_get_flags(channel); + + io_flags |= G_IO_FLAG_NONBLOCK; + + if (g_io_channel_set_flags(channel, io_flags, NULL) != + G_IO_STATUS_NORMAL) + goto error; + + g_io_channel_set_close_on_unref(channel, TRUE); + + chat->channel = channel; + chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + received_data, chat, + (GDestroyNotify)read_watcher_destroy_notify); + + return chat; + +error: + if (chat->buf) + ring_buffer_free(chat->buf); + + if (chat->command_queue) + g_queue_free(chat->command_queue); + + if (chat->notify_list) + g_hash_table_destroy(chat->notify_list); + + g_free(chat); + return NULL; +} + +GAtChat *g_at_chat_ref(GAtChat *chat) +{ + if (chat == NULL) + return NULL; + + g_atomic_int_inc(&chat->ref_count); + + return chat; +} + +void g_at_chat_unref(GAtChat *chat) +{ + gboolean is_zero; + + if (chat == NULL) + return; + + is_zero = g_atomic_int_dec_and_test(&chat->ref_count); + + if (is_zero) { + g_at_chat_shutdown(chat); + + g_at_chat_cleanup(chat); + g_free(chat); + } +} + +gboolean g_at_chat_shutdown(GAtChat *chat) +{ + if (chat->channel == NULL) + return FALSE; + + chat->disconnecting = TRUE; + + if (chat->read_watch) + g_source_remove(chat->read_watch); + + if (chat->write_watch) + g_source_remove(chat->write_watch); + + return TRUE; +} + +gboolean g_at_chat_set_disconnect_function(GAtChat *chat, + GAtDisconnectFunc disconnect, gpointer user_data) +{ + if (chat == NULL) + return FALSE; + + chat->user_disconnect = disconnect; + chat->user_disconnect_data = user_data; + + return TRUE; +} + +guint g_at_chat_send(GAtChat *chat, const char *cmd, + const char **prefix_list, GAtResultFunc func, + gpointer user_data, GDestroyNotify notify) +{ + struct at_command *c; + + if (chat == NULL || chat->command_queue == NULL) + return 0; + + c = at_command_create(cmd, prefix_list, func, user_data, notify); + + if (!c) + return 0; + + c->id = chat->next_cmd_id++; + + g_queue_push_tail(chat->command_queue, c); + + if (g_queue_get_length(chat->command_queue) == 1) + g_at_chat_wakeup_writer(chat); + + return c->id; +} + +gboolean g_at_chat_cancel(GAtChat *chat, guint id) +{ + GList *l; + + if (chat == NULL || chat->command_queue == NULL) + return FALSE; + + l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id), + at_command_compare_by_id); + + if (!l) + return FALSE; + + if (l == g_queue_peek_head(chat->command_queue)) { + struct at_command *c = l->data; + + /* We can't actually remove it since it is most likely + * already in progress, just null out the callback + * so it won't be called + */ + c->callback = NULL; + } else { + at_command_destroy(l->data); + g_queue_remove(chat->command_queue, l->data); + } + + return TRUE; +} + +static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix, + gboolean pdu) +{ + struct at_notify *notify; + char *key; + + key = g_strdup(prefix); + + if (!key) + return 0; + + notify = g_try_new0(struct at_notify, 1); + + if (!notify) { + g_free(key); + return 0; + } + + notify->pdu = pdu; + + g_hash_table_insert(chat->notify_list, key, notify); + + return notify; +} + +guint g_at_chat_register(GAtChat *chat, const char *prefix, + GAtNotifyFunc func, gboolean expect_pdu, + gpointer user_data, + GDestroyNotify destroy_notify) +{ + struct at_notify *notify; + struct at_notify_node *node; + + if (chat == NULL || chat->notify_list == NULL) + return 0; + + if (func == NULL) + return 0; + + if (prefix == NULL || strlen(prefix) == 0) + return 0; + + notify = g_hash_table_lookup(chat->notify_list, prefix); + + if (!notify) + notify = at_notify_create(chat, prefix, expect_pdu); + + if (!notify || notify->pdu != expect_pdu) + return 0; + + node = g_try_new0(struct at_notify_node, 1); + + if (!node) + return 0; + + node->id = chat->next_notify_id++; + node->callback = func; + node->user_data = user_data; + node->notify = destroy_notify; + + notify->nodes = g_slist_prepend(notify->nodes, node); + + return node->id; +} + +gboolean g_at_chat_unregister(GAtChat *chat, guint id) +{ + GHashTableIter iter; + struct at_notify *notify; + char *prefix; + gpointer key, value; + GSList *l; + + if (chat == NULL || chat->notify_list == NULL) + return FALSE; + + g_hash_table_iter_init(&iter, chat->notify_list); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + prefix = key; + notify = value; + + l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id), + at_notify_node_compare_by_id); + + if (!l) + continue; + + at_notify_node_destroy(l->data); + notify->nodes = g_slist_remove(notify->nodes, l->data); + + if (notify->nodes == NULL) + g_hash_table_iter_remove(&iter); + + return TRUE; + } + + return TRUE; +} + +gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd, + unsigned int timeout, unsigned int msec) +{ + if (chat == NULL) + return FALSE; + + if (chat->wakeup) + g_free(chat->wakeup); + + chat->wakeup = g_strdup(cmd); + chat->inactivity_time = (gdouble)msec / 1000; + chat->wakeup_timeout = timeout; + + return TRUE; +} diff --git a/gatchat/gatchat.h b/gatchat/gatchat.h new file mode 100644 index 00000000..58ca9114 --- /dev/null +++ b/gatchat/gatchat.h @@ -0,0 +1,105 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GATCHAT_H +#define __GATCHAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "gatresult.h" + +struct _GAtChat; + +typedef struct _GAtChat GAtChat; + +typedef void (*GAtResultFunc)(gboolean success, GAtResult *result, + gpointer user_data); +typedef void (*GAtNotifyFunc)(GAtResult *result, gpointer user_data); +typedef void (*GAtDisconnectFunc)(gpointer user_data); + +enum _GAtChatFlags { + G_AT_CHAT_FLAG_NO_LEADING_CRLF = 1, /* Some emulators are broken */ +}; + +typedef enum _GAtChatFlags GAtChatFlags; + +GAtChat *g_at_chat_new(GIOChannel *channel, int flags); + +GAtChat *g_at_chat_ref(GAtChat *chat); +void g_at_chat_unref(GAtChat *chat); + +gboolean g_at_chat_shutdown(GAtChat *chat); + +gboolean g_at_chat_set_disconnect_function(GAtChat *chat, + GAtDisconnectFunc disconnect, gpointer user_data); + +/*! + * Queue an AT command for execution. The command contents are given + * in cmd. Once the command executes, the callback function given by + * func is called with user provided data in user_data. + * + * Returns an id of the queued command which can be canceled using + * g_at_chat_cancel. If an error occurred, an id of 0 is returned. + * + * This function can be used in three ways: + * - Send a simple command such as g_at_chat_send(p, "AT+CGMI?", ... + * + * - Send a compound command: g_at_chat_send(p, "AT+CMD1;+CMD2", ... + * + * - Send a command requiring a prompt. The command up to '\r' is sent + * after which time a '> ' prompt is expected from the modem. Further + * contents of the command are sent until a '\r' or end of string is + * encountered. If end of string is encountered, the Ctrl-Z character + * is sent automatically. There is no need to include the Ctrl-Z + * by the caller. + * + * The valid_resp field can be used to send an array of strings which will + * be accepted as a valid response for this command. This is treated as a + * simple prefix match. If a response line comes in from the modem and it + * does not match any of the prefixes in valid_resp, it is treated as an + * unsolicited notification. If valid_resp is NULL, then all response + * lines after command submission and final response line are treated as + * part of the command response. This can be used to get around broken + * modems which send unsolicited notifications during command processing. + */ +guint g_at_chat_send(GAtChat *chat, const char *cmd, + const char **valid_resp, GAtResultFunc func, + gpointer user_data, GDestroyNotify notify); + +gboolean g_at_chat_cancel(GAtChat *chat, guint id); + +guint g_at_chat_register(GAtChat *chat, const char *prefix, + GAtNotifyFunc func, gboolean expect_pdu, + gpointer user_data, GDestroyNotify notify); + +gboolean g_at_chat_unregister(GAtChat *chat, guint id); + +gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd, + guint timeout, guint msec); + + +#ifdef __cplusplus +} +#endif + +#endif /* __GATCHAT_H */ diff --git a/gatchat/gatresult.c b/gatchat/gatresult.c new file mode 100644 index 00000000..87457f1c --- /dev/null +++ b/gatchat/gatresult.c @@ -0,0 +1,377 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "gatresult.h" + +void g_at_result_iter_init(GAtResultIter *iter, GAtResult *result) +{ + iter->result = result; + iter->pre.next = result->lines; + iter->pre.data = NULL; + iter->l = &iter->pre; + iter->line_pos = 0; +} + +gboolean g_at_result_iter_next(GAtResultIter *iter, const char *prefix) +{ + char *line; + int prefix_len = prefix ? strlen(prefix) : 0; + + while ((iter->l = iter->l->next)) { + line = iter->l->data; + + if (prefix_len == 0) { + iter->line_pos = 0; + return TRUE; + } + + if (g_str_has_prefix(line, prefix) == FALSE) + continue; + + iter->line_pos = prefix_len; + + while (iter->line_pos < strlen(line) && + line[iter->line_pos] == ' ') + iter->line_pos += 1; + + return TRUE; + } + + return FALSE; +} + +const char *g_at_result_iter_raw_line(GAtResultIter *iter) +{ + const char *line; + + if (!iter) + return NULL; + + if (!iter->l) + return NULL; + + line = iter->l->data; + + line += iter->line_pos; + + return line; +} + +static inline int skip_to_next_field(const char *line, int pos, int len) +{ + if (pos < len && line[pos] == ',') + pos += 1; + + while (pos < len && line[pos] == ' ') + pos += 1; + + return pos; +} + +gboolean g_at_result_iter_next_string(GAtResultIter *iter, const char **str) +{ + unsigned int pos; + unsigned int end; + unsigned int len; + char *line; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + len = strlen(line); + + pos = iter->line_pos; + + /* Omitted string */ + if (line[pos] == ',') { + end = pos; + memset(iter->buf, 0, sizeof(iter->buf)); + goto out; + } + + if (line[pos++] != '"') + return FALSE; + + end = pos; + + while (end < len && line[end] != '"') + end += 1; + + if (line[end] != '"') + return FALSE; + + if (end - pos >= sizeof(iter->buf)) + return FALSE; + + strncpy(iter->buf, line+pos, end-pos); + memset(iter->buf + end - pos, 0, sizeof(iter->buf) - end + pos); + + /* Skip " */ + end += 1; + +out: + iter->line_pos = skip_to_next_field(line, end, len); + + if (str) + *str = iter->buf; + + return TRUE; +} + +gboolean g_at_result_iter_next_number(GAtResultIter *iter, gint *number) +{ + int pos; + int end; + int len; + int value = 0; + char *line; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + len = strlen(line); + + pos = iter->line_pos; + end = pos; + + while (line[end] >= '0' && line[end] <= '9') { + value = value * 10 + (int)(line[end] - '0'); + end += 1; + } + + if (pos == end) + return FALSE; + + iter->line_pos = skip_to_next_field(line, end, len); + + if (number) + *number = value; + + return TRUE; +} + +gboolean g_at_result_iter_next_range(GAtResultIter *iter, gint *min, gint *max) +{ + int pos; + int end; + int len; + int low = 0; + int high = 0; + char *line; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + len = strlen(line); + + pos = iter->line_pos; + + while (pos < len && line[pos] == ' ') + pos += 1; + + end = pos; + + while (line[end] >= '0' && line[end] <= '9') { + low = low * 10 + (int)(line[end] - '0'); + end += 1; + } + + if (pos == end) + return FALSE; + + if (line[end] == ',') { + high = low; + goto out; + } + + if (line[end] == '-') + pos = end = end + 1; + else + return FALSE; + + while (line[end] >= '0' && line[end] <= '9') { + high = high * 10 + (int)(line[end] - '0'); + end += 1; + } + + if (pos == end) + return FALSE; + +out: + iter->line_pos = skip_to_next_field(line, end, len); + + if (min) + *min = low; + + if (max) + *max = high; + + return TRUE; +} + +static gint skip_until(const char *line, int start, const char delim) +{ + int len = strlen(line); + int i = start; + + while (i < len) { + if (line[i] == delim) + return i; + + if (line[i] != '(') { + i += 1; + continue; + } + + i = skip_until(line, i+1, ')'); + + if (i < len) + i += 1; + } + + return i; +} + +gboolean g_at_result_iter_skip_next(GAtResultIter *iter) +{ + unsigned int skipped_to; + char *line; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + + skipped_to = skip_until(line, iter->line_pos, ','); + + if (skipped_to == iter->line_pos && line[skipped_to] != ',') + return FALSE; + + iter->line_pos = skip_to_next_field(line, skipped_to, strlen(line)); + + return TRUE; +} + +gboolean g_at_result_iter_open_list(GAtResultIter *iter) +{ + char *line; + unsigned int len; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + len = strlen(line); + + if (iter->line_pos >= len) + return FALSE; + + if (line[iter->line_pos] != '(') + return FALSE; + + iter->line_pos += 1; + + while (iter->line_pos < strlen(line) && + line[iter->line_pos] == ' ') + iter->line_pos += 1; + + return TRUE; +} + +gboolean g_at_result_iter_close_list(GAtResultIter *iter) +{ + char *line; + unsigned int len; + + if (!iter) + return FALSE; + + if (!iter->l) + return FALSE; + + line = iter->l->data; + len = strlen(line); + + if (iter->line_pos >= len) + return FALSE; + + if (line[iter->line_pos] != ')') + return FALSE; + + iter->line_pos += 1; + + iter->line_pos = skip_to_next_field(line, iter->line_pos, len); + + return TRUE; +} + +const char *g_at_result_final_response(GAtResult *result) +{ + if (!result) + return NULL; + + return result->final_or_pdu; +} + +const char *g_at_result_pdu(GAtResult *result) +{ + if (!result) + return NULL; + + return result->final_or_pdu; +} + +gint g_at_result_num_response_lines(GAtResult *result) +{ + if (!result) + return 0; + + if (!result->lines) + return 0; + + return g_slist_length(result->lines); +} diff --git a/gatchat/gatresult.h b/gatchat/gatresult.h new file mode 100644 index 00000000..8259e7cd --- /dev/null +++ b/gatchat/gatresult.h @@ -0,0 +1,69 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GATCHAT_RESULT_H +#define __GATCHAT_RESULT_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct _GAtResult { + GSList *lines; + char *final_or_pdu; +}; + +typedef struct _GAtResult GAtResult; + +struct _GAtResultIter { + GAtResult *result; + GSList *l; + char buf[2048]; + unsigned int line_pos; + GSList pre; +}; + +typedef struct _GAtResultIter GAtResultIter; + +void g_at_result_iter_init(GAtResultIter *iter, GAtResult *result); + +gboolean g_at_result_iter_next(GAtResultIter *iter, const char *prefix); +gboolean g_at_result_iter_open_list(GAtResultIter *iter); +gboolean g_at_result_iter_close_list(GAtResultIter *iter); + +gboolean g_at_result_iter_skip_next(GAtResultIter *iter); + +gboolean g_at_result_iter_next_range(GAtResultIter *iter, gint *min, gint *max); +gboolean g_at_result_iter_next_string(GAtResultIter *iter, const char **str); +gboolean g_at_result_iter_next_number(GAtResultIter *iter, gint *number); + +const char *g_at_result_iter_raw_line(GAtResultIter *iter); + +const char *g_at_result_final_response(GAtResult *result); +const char *g_at_result_pdu(GAtResult *result); + +gint g_at_result_num_response_lines(GAtResult *result); + +#ifdef __cplusplus +} +#endif + +#endif /* __GATCHAT_RESULT_H */ diff --git a/gatchat/ringbuffer.c b/gatchat/ringbuffer.c new file mode 100644 index 00000000..0b1860f4 --- /dev/null +++ b/gatchat/ringbuffer.c @@ -0,0 +1,199 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "ringbuffer.h" + +#define MAX_SIZE 262144 + +struct ring_buffer *ring_buffer_new(unsigned int size) +{ + unsigned int real_size = 1; + struct ring_buffer *buffer; + + /* Find the next power of two for size */ + while (real_size < size && real_size < MAX_SIZE) + real_size = real_size << 1; + + if (real_size > MAX_SIZE) + return NULL; + + buffer = g_new(struct ring_buffer, 1); + + if (!buffer) + return NULL; + + buffer->buffer = g_new(unsigned char, real_size); + + if (!buffer->buffer) { + g_free(buffer); + return NULL; + } + + buffer->size = real_size; + buffer->in = 0; + buffer->out = 0; + + return buffer; +} + +int ring_buffer_write(struct ring_buffer *buf, const void *data, + unsigned int len) +{ + unsigned int end; + unsigned int offset; + const unsigned char *d = data; /* Needed to satisfy non-gcc compilers */ + + /* Determine how much we can actually write */ + len = MIN(len, buf->size - buf->in + buf->out); + + /* Determine how much to write before wrapping */ + offset = buf->in % buf->size; + end = MIN(len, buf->size - offset); + memcpy(buf->buffer+offset, d, end); + + /* Now put the remainder on the beginning of the buffer */ + memcpy(buf->buffer, d + end, len - end); + + buf->in += len; + + return len; +} + +unsigned char *ring_buffer_write_ptr(struct ring_buffer *buf) +{ + return buf->buffer + buf->in % buf->size; +} + +int ring_buffer_avail_no_wrap(struct ring_buffer *buf) +{ + unsigned int offset = buf->in % buf->size; + unsigned int len = buf->size - buf->in + buf->out; + + return MIN(len, buf->size - offset); +} + +int ring_buffer_write_advance(struct ring_buffer *buf, unsigned int len) +{ + len = MIN(len, buf->size - buf->in + buf->out); + buf->in += len; + + return len; +} + +int ring_buffer_read(struct ring_buffer *buf, void *data, unsigned int len) +{ + unsigned int end; + unsigned int offset; + unsigned char *d = data; + + len = MIN(len, buf->in - buf->out); + + /* Grab data from buffer starting at offset until the end */ + offset = buf->out % buf->size; + end = MIN(len, buf->size - offset); + memcpy(d, buf->buffer + offset, end); + + /* Now grab remainder from the beginning */ + memcpy(d + end, buf->buffer, len - end); + + buf->out += len; + + if (buf->out == buf->in) + buf->out = buf->in = 0; + + return len; +} + +int ring_buffer_drain(struct ring_buffer *buf, unsigned int len) +{ + len = MIN(len, buf->in - buf->out); + + buf->out += len; + + if (buf->out == buf->in) + buf->out = buf->in = 0; + + return len; +} + +int ring_buffer_len_no_wrap(struct ring_buffer *buf) +{ + unsigned int offset = buf->out % buf->size; + unsigned int len = buf->in - buf->out; + + return MIN(len, buf->size - offset); +} + +unsigned char *ring_buffer_read_ptr(struct ring_buffer *buf, + unsigned int offset) +{ + return buf->buffer + (buf->out + offset) % buf->size; +} + +int ring_buffer_len(struct ring_buffer *buf) +{ + if (!buf) + return -1; + + return buf->in - buf->out; +} + +void ring_buffer_reset(struct ring_buffer *buf) +{ + if (!buf) + return; + + buf->in = 0; + buf->out = 0; +} + +int ring_buffer_avail(struct ring_buffer *buf) +{ + if (!buf) + return -1; + + return buf->size - buf->in + buf->out; +} + +int ring_buffer_capacity(struct ring_buffer *buf) +{ + if (!buf) + return -1; + + return buf->size; +} + +void ring_buffer_free(struct ring_buffer *buf) +{ + if (!buf) + return; + + g_free(buf->buffer); + g_free(buf); +} diff --git a/gatchat/ringbuffer.h b/gatchat/ringbuffer.h new file mode 100644 index 00000000..b77c428b --- /dev/null +++ b/gatchat/ringbuffer.h @@ -0,0 +1,124 @@ +/* + * + * AT chat library with GLib integration + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GATCHAT_RINGBUFFER_H +#define __GATCHAT_RINGBUFFER_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct ring_buffer { + unsigned char *buffer; + unsigned int size; + unsigned int in; + unsigned int out; +}; + +/*! + * Creates a new ring buffer with capacity size + */ +struct ring_buffer *ring_buffer_new(unsigned int size); + +/*! + * Frees the resources allocated for the ring buffer + */ +void ring_buffer_free(struct ring_buffer *buf); + +/*! + * Returns the capacity of the ring buffer + */ +int ring_buffer_capacity(struct ring_buffer *buf); + +/*! + * Resets the ring buffer, all data inside the buffer is lost + */ +void ring_buffer_reset(struct ring_buffer *buf); + +/*! + * Writes data of size len into the ring buffer buf. Returns -1 if the + * write failed or the number of bytes written + */ +int ring_buffer_write(struct ring_buffer *buf, const void *data, + unsigned int len); + +/*! + * Advances the write counter by len, this is meant to be used with + * the ring_buffer_write_ptr function. Returns the number of bytes + * actually advanced (the capacity of the buffer) + */ +int ring_buffer_write_advance(struct ring_buffer *buf, unsigned int len); + +/*! + * Returns the write pointer. Careful not to write past the end of the + * buffer. Use the ring_buffer_avail_no_wrap function, + * ring_buffer_write_advance. + */ +unsigned char *ring_buffer_write_ptr(struct ring_buffer *buf); + +/*! + * Returns the number of free bytes available in the buffer + */ +int ring_buffer_avail(struct ring_buffer *buf); + +/*! + * Returns the number of free bytes available in the buffer without wrapping + */ +int ring_buffer_avail_no_wrap(struct ring_buffer *buf); + +/*! + * Reads data from the ring buffer buf into memory region pointed to by data. + * A maximum of len bytes will be read. Returns -1 if the read failed or + * the number of bytes read + */ +int ring_buffer_read(struct ring_buffer *buf, void *data, + unsigned int len); + +/*! + * Returns the read pointer with read offset specified by offset. No bounds + * checking is performed. Be careful not to read past the end of the buffer. + * Use the ring_buffer_len_no_wrap function, and ring_buffer_drain. + */ +unsigned char *ring_buffer_read_ptr(struct ring_buffer *buf, + unsigned int offset); + +/*! + * Returns the number of bytes currently available to be read in the buffer + */ +int ring_buffer_len(struct ring_buffer *buf); + +/*! + * Returns the number of bytes currently available to be read in the buffer + * without wrapping. + */ +int ring_buffer_len_no_wrap(struct ring_buffer *buf); + +/*! + * Drains the ring buffer of len bytes. Returns the number of bytes the + * read counter was actually advanced. + */ +int ring_buffer_drain(struct ring_buffer *buf, unsigned int len); + +#ifdef __cplusplus +} +#endif + +#endif /* __GATCHAT_RINGBUFFER_H */ -- cgit v1.2.3