Add AT chat library implementation
authorDenis Kenzior <denis.kenzior@intel.com>
Fri, 8 May 2009 21:49:56 +0000 (14:49 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Fri, 8 May 2009 21:50:21 +0000 (14:50 -0700)
gatchat/Makefile.am
gatchat/gatchat.c [new file with mode: 0644]
gatchat/gatchat.h [new file with mode: 0644]
gatchat/gatresult.c [new file with mode: 0644]
gatchat/gatresult.h [new file with mode: 0644]
gatchat/ringbuffer.c [new file with mode: 0644]
gatchat/ringbuffer.h [new file with mode: 0644]

index 56c1742..9f1da11 100644 (file)
@@ -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 (file)
index 0000000..e54e5d9
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <glib.h>
+
+#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 <CR><LF> */
+       unsigned int len = p->read_so_far - 2;
+       char *str;
+       struct at_command *cmd;
+
+       /* If we have preceding <CR><LF> 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 <CR><LF>?! */
+                       (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 (file)
index 0000000..58ca911
--- /dev/null
@@ -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 (file)
index 0000000..87457f1
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+
+#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 (file)
index 0000000..8259e7c
--- /dev/null
@@ -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 (file)
index 0000000..0b1860f
--- /dev/null
@@ -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 <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+
+#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 (file)
index 0000000..b77c428
--- /dev/null
@@ -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 */