Refactor GAtChat to accept user-provided parsers
authorDenis Kenzior <denkenz@gmail.com>
Tue, 4 Aug 2009 23:53:25 +0000 (18:53 -0500)
committerMarcel Holtmann <marcel@holtmann.org>
Fri, 7 Aug 2009 00:48:48 +0000 (17:48 -0700)
Intended for really broken modems.  A default 27.007 compliant
parser is provided.

gatchat/Makefile.am
gatchat/gatchat.c
gatchat/gatchat.h
gatchat/gatsyntax.c [new file with mode: 0644]
gatchat/gatsyntax.h [new file with mode: 0644]

index 9f1da11..8b2ed20 100644 (file)
@@ -2,7 +2,7 @@
 noinst_LTLIBRARIES = libgatchat.la
 
 libgatchat_la_SOURCES = gatchat.h gatchat.c gatresult.h gatresult.c \
-                                               ringbuffer.h ringbuffer.c
+                       ringbuffer.h ringbuffer.c gatsyntax.h gatsyntax.c
 
 AM_CFLAGS = @GLIB_CFLAGS@
 
index d06b500..8263ad7 100644 (file)
 #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_GUESS_MULTILINE_RESPONSE,
-       PARSER_STATE_MULTILINE_RESPONSE,
-       PARSER_STATE_MULTILINE_TERMINATOR_CR,
-       PARSER_STATE_MULTILINE_COMPLETE,
-       PARSER_STATE_PDU,
-       PARSER_STATE_PDU_CR,
-       PARSER_STATE_PDU_COMPLETE,
-       PARSER_STATE_PROMPT,
-       PARSER_STATE_PROMPT_COMPLETE
-};
+static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len);
 
 struct at_command {
        char *cmd;
@@ -95,14 +77,13 @@ struct _GAtChat {
        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 */
+       GAtSyntax *syntax;
 };
 
 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
@@ -254,6 +235,9 @@ static void g_at_chat_cleanup(GAtChat *chat)
                g_timer_destroy(chat->wakeup_timer);
                chat->wakeup_timer = 0;
        }
+
+       g_at_syntax_unref(chat->syntax);
+       chat->syntax = NULL;
 }
 
 static void read_watcher_destroy_notify(GAtChat *chat)
@@ -306,7 +290,10 @@ static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
 
                if (notify->pdu) {
                        chat->pdu_notify = line;
-                       chat->state = PARSER_STATE_PDU;
+
+                       if (chat->syntax->set_hint)
+                               chat->syntax->set_hint(chat->syntax,
+                                                       G_AT_SYNTAX_EXPECT_PDU);
                        return TRUE;
                }
 
@@ -321,7 +308,6 @@ static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
        if (ret) {
                g_slist_free(result.lines);
                g_free(line);
-               chat->state = PARSER_STATE_IDLE;
        }
 
        return ret;
@@ -387,8 +373,6 @@ static gboolean g_at_chat_handle_command_response(GAtChat *p,
        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];
 
@@ -415,8 +399,8 @@ static gboolean g_at_chat_handle_command_response(GAtChat *p,
        }
 
 out:
-       if (!(p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
-               p->state = PARSER_STATE_GUESS_MULTILINE_RESPONSE;
+       if (p->syntax->set_hint)
+               p->syntax->set_hint(p->syntax, G_AT_SYNTAX_EXPECT_MULTILINE);
 
        if (cmd->listing) {
                GAtResult result;
@@ -434,31 +418,13 @@ out:
        return TRUE;
 }
 
-static void have_line(GAtChat *p, gboolean strip_preceding)
+static void have_line(GAtChat *p, char *str)
 {
        /* 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 (strip_preceding)
-               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);
+       if (!str)
                return;
-       }
-
-       if (strip_preceding)
-               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)
@@ -488,30 +454,18 @@ static void have_line(GAtChat *p, gboolean strip_preceding)
 done:
        /* No matches & no commands active, ignore line */
        g_free(str);
-       p->state = PARSER_STATE_IDLE;
 }
 
-static void have_pdu(GAtChat *p)
+static void have_pdu(GAtChat *p, char *pdu)
 {
-       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);
+       if (!pdu)
                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;
@@ -540,97 +494,47 @@ out:
 
        if (pdu)
                g_free(pdu);
-
-       p->state = PARSER_STATE_IDLE;
 }
 
-static inline void parse_char(GAtChat *chat, char byte)
+static char *extract_line(GAtChat *p)
 {
-       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;
+       unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
+       unsigned int pos = 0;
+       unsigned char *buf = ring_buffer_read_ptr(p->buf, pos);
+       int strip_front = 0;
+       int line_length = 0;
+       char *line;
+
+       while (pos < p->read_so_far) {
+               if (*buf == '\r' || *buf == '\n')
+                       if (!line_length)
+                               strip_front += 1;
                        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;
+                               break;
                else
-                       chat->state = PARSER_STATE_RESPONSE;
-               break;
+                       line_length += 1;
 
-       case PARSER_STATE_RESPONSE:
-               if (byte == '\r')
-                       chat->state = PARSER_STATE_TERMINATOR_CR;
-               break;
+               buf += 1;
+               pos += 1;
 
-       case PARSER_STATE_TERMINATOR_CR:
-               if (byte == '\n')
-                       chat->state = PARSER_STATE_RESPONSE_COMPLETE;
-               else
-                       chat->state = PARSER_STATE_IDLE;
-               break;
+               if (pos == wrap)
+                       buf = ring_buffer_read_ptr(p->buf, pos);
+       }
 
-       case PARSER_STATE_GUESS_MULTILINE_RESPONSE:
-               if (byte == '\r')
-                       chat->state = PARSER_STATE_INITIAL_CR;
-               else
-                       chat->state = PARSER_STATE_MULTILINE_RESPONSE;
-               break;
-
-       case PARSER_STATE_MULTILINE_RESPONSE:
-               if (byte == '\r')
-                       chat->state = PARSER_STATE_MULTILINE_TERMINATOR_CR;
-               break;
-
-       case PARSER_STATE_MULTILINE_TERMINATOR_CR:
-               if (byte == '\n')
-                       chat->state = PARSER_STATE_MULTILINE_COMPLETE;
-               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;
-               break;
-
-       case PARSER_STATE_RESPONSE_COMPLETE:
-       case PARSER_STATE_PDU_COMPLETE:
-       case PARSER_STATE_MULTILINE_COMPLETE:
-       default:
-               /* This really shouldn't happen */
-               assert(FALSE);
-               return;
+       line = g_try_new(char, line_length + 1);
+
+       if (!line) {
+               ring_buffer_drain(p->buf, p->read_so_far);
+               return NULL;
        }
+
+       ring_buffer_drain(p->buf, strip_front);
+       ring_buffer_read(p->buf, line, line_length);
+       ring_buffer_drain(p->buf, p->read_so_far - strip_front - line_length);
+
+       line[line_length] = '\0';
+
+       return line;
 }
 
 static void new_bytes(GAtChat *p)
@@ -639,76 +543,45 @@ static void new_bytes(GAtChat *p)
        unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
        unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
 
+       GAtSyntaxResult result;
+
        while (p->read_so_far < len) {
-               parse_char(p, *buf);
+               gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
+               result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
 
-               buf += 1;
-               p->read_so_far += 1;
+               buf += rbytes;
+               p->read_so_far += rbytes;
 
                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) {
-                       gboolean strip_preceding;
-
-                       if (p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF)
-                               strip_preceding = FALSE;
-                       else
-                               strip_preceding = TRUE;
-
-                       len -= p->read_so_far;
-                       wrap -= p->read_so_far;
-
-                       have_line(p, strip_preceding);
-
-                       p->read_so_far = 0;
-               } else if (p->state == PARSER_STATE_MULTILINE_COMPLETE) {
-                       len -= p->read_so_far;
-                       wrap -= p->read_so_far;
-
-                       have_line(p, FALSE);
-
-                       p->read_so_far = 0;
-               } else if (p->state == PARSER_STATE_PDU_COMPLETE) {
-                       len -= p->read_so_far;
-                       wrap -= p->read_so_far;
-
-                       /* Some modems like the TI Calypso send a CMT style
-                        * notification with an extra CRLF thrown in
-                        */
-                       if ((p->flags & G_AT_CHAT_FLAG_EXTRA_PDU_CRLF) &&
-                                       p->read_so_far == 2) {
-                               p->state = PARSER_STATE_PDU;
-                               ring_buffer_drain(p->buf, p->read_so_far);
-                       } else
-                               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;
+               if (result == G_AT_SYNTAX_RESULT_UNSURE)
+                       continue;
 
-                       ring_buffer_drain(p->buf, p->read_so_far - 1);
+               switch (result) {
+               case G_AT_SYNTAX_RESULT_LINE:
+               case G_AT_SYNTAX_RESULT_MULTILINE:
+                       have_line(p, extract_line(p));
+                       break;
 
-                       p->read_so_far = 1;
-               } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
-                       len -= p->read_so_far;
-                       wrap -= p->read_so_far;
+               case G_AT_SYNTAX_RESULT_PDU:
+                       have_pdu(p, extract_line(p));
+                       break;
 
+               case G_AT_SYNTAX_RESULT_PROMPT:
                        g_at_chat_wakeup_writer(p);
-
                        ring_buffer_drain(p->buf, p->read_so_far);
+                       break;
 
-                       p->read_so_far = 0;
-
-                       p->state = PARSER_STATE_IDLE;
+               default:
+                       ring_buffer_drain(p->buf, p->read_so_far);
+                       break;
                }
-       }
 
-       if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
-               ring_buffer_drain(p->buf, p->read_so_far);
+               len -= p->read_so_far;
+               wrap -= p->read_so_far;
                p->read_so_far = 0;
        }
 }
@@ -736,13 +609,8 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond,
                 * 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;
-               }
+               if (toread == 0)
+                       return FALSE;
 
                buf = ring_buffer_write_ptr(chat->buf);
 
@@ -900,7 +768,7 @@ static void g_at_chat_wakeup_writer(GAtChat *chat)
                                (GDestroyNotify)write_watcher_destroy_notify);
 }
 
-GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
+GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax)
 {
        GAtChat *chat;
        GIOFlags io_flags;
@@ -908,6 +776,9 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
        if (!channel)
                return NULL;
 
+       if (!syntax)
+               return NULL;
+
        chat = g_try_new0(GAtChat, 1);
 
        if (!chat)
@@ -916,7 +787,6 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
        chat->ref_count = 1;
        chat->next_cmd_id = 1;
        chat->next_notify_id = 1;
-       chat->flags = flags;
 
        chat->buf = ring_buffer_new(4096);
 
@@ -951,6 +821,8 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
                                received_data, chat,
                                (GDestroyNotify)read_watcher_destroy_notify);
 
+       chat->syntax = g_at_syntax_ref(syntax);
+
        return chat;
 
 error:
@@ -967,22 +839,6 @@ error:
        return NULL;
 }
 
-int g_at_chat_get_flags(GAtChat *chat)
-{
-       if (chat == NULL)
-               return 0;
-
-       return chat->flags;
-}
-
-void g_at_chat_set_flags(GAtChat *chat, int flags)
-{
-       if (chat == NULL)
-               return;
-
-       chat->flags = flags;
-}
-
 static int open_device(const char *device)
 {
        struct termios ti;
@@ -1003,7 +859,7 @@ static int open_device(const char *device)
        return fd;
 }
 
-GAtChat *g_at_chat_new_from_tty(const char *device, int flags)
+GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax)
 {
        GIOChannel *channel;
        int fd;
@@ -1018,7 +874,7 @@ GAtChat *g_at_chat_new_from_tty(const char *device, int flags)
                return NULL;
        }
 
-       return g_at_chat_new(channel, flags);
+       return g_at_chat_new(channel, syntax);
 }
 
 GAtChat *g_at_chat_ref(GAtChat *chat)
index 969f6f4..d0a59b8 100644 (file)
@@ -27,6 +27,7 @@ extern "C" {
 #endif
 
 #include "gatresult.h"
+#include "gatsyntax.h"
 
 struct _GAtChat;
 
@@ -44,11 +45,8 @@ enum _GAtChatFlags {
 
 typedef enum _GAtChatFlags GAtChatFlags;
 
-GAtChat *g_at_chat_new(GIOChannel *channel, int flags);
-GAtChat *g_at_chat_new_from_tty(const char *device, int flags);
-
-int g_at_chat_get_flags(GAtChat *chat);
-void g_at_chat_set_flags(GAtChat *chat, int flags);
+GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax);
+GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax);
 
 GAtChat *g_at_chat_ref(GAtChat *chat);
 void g_at_chat_unref(GAtChat *chat);
diff --git a/gatchat/gatsyntax.c b/gatchat/gatsyntax.c
new file mode 100644 (file)
index 0000000..7c5aadc
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *
+ *  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 <glib.h>
+
+#include "gatsyntax.h"
+
+enum GSMV1_STATE_ {
+       GSMV1_STATE_IDLE = 0,
+       GSMV1_STATE_INITIAL_CR,
+       GSMV1_STATE_INITIAL_LF,
+       GSMV1_STATE_RESPONSE,
+       GSMV1_STATE_TERMINATOR_CR,
+       GSMV1_STATE_GUESS_MULTILINE_RESPONSE,
+       GSMV1_STATE_MULTILINE_RESPONSE,
+       GSMV1_STATE_MULTILINE_TERMINATOR_CR,
+       GSMV1_STATE_PDU,
+       GSMV1_STATE_PDU_CR,
+       GSMV1_STATE_PROMPT,
+       GSMV1_STATE_GARBAGE,
+       GSMV1_STATE_GARBAGE_CHECK_LF,
+};
+
+static void gsmv1_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint)
+{
+       switch (hint) {
+       case G_AT_SYNTAX_EXPECT_PDU:
+               syntax->state = GSMV1_STATE_PDU;
+               break;
+       case G_AT_SYNTAX_EXPECT_MULTILINE:
+               syntax->state = GSMV1_STATE_GUESS_MULTILINE_RESPONSE;
+               break;
+       default:
+               break;
+       };
+}
+
+static GAtSyntaxResult gsmv1_feed(GAtSyntax *syntax,
+                                       const char *bytes, gsize *len)
+{
+       gsize i = 0;
+       GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE;
+
+       while (i < *len) {
+               char byte = bytes[i];
+
+               switch (syntax->state) {
+               case GSMV1_STATE_IDLE:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_INITIAL_CR;
+                       else
+                               syntax->state = GSMV1_STATE_GARBAGE;
+                       break;
+
+               case GSMV1_STATE_INITIAL_CR:
+                       if (byte == '\n')
+                               syntax->state = GSMV1_STATE_INITIAL_LF;
+                       else
+                               syntax->state = GSMV1_STATE_GARBAGE;
+                       break;
+
+               case GSMV1_STATE_INITIAL_LF:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_TERMINATOR_CR;
+                       else if (byte == '>')
+                               syntax->state = GSMV1_STATE_PROMPT;
+                       else
+                               syntax->state = GSMV1_STATE_RESPONSE;
+                       break;
+
+               case GSMV1_STATE_RESPONSE:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_TERMINATOR_CR;
+                       break;
+
+               case GSMV1_STATE_TERMINATOR_CR:
+                       syntax->state = GSMV1_STATE_IDLE;
+
+                       if (byte == '\n') {
+                               i += 1;
+                               res = G_AT_SYNTAX_RESULT_LINE;
+                       } else
+                               res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
+
+                       goto out;
+
+               case GSMV1_STATE_GUESS_MULTILINE_RESPONSE:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_INITIAL_CR;
+                       else
+                               syntax->state = GSMV1_STATE_MULTILINE_RESPONSE;
+                       break;
+
+               case GSMV1_STATE_MULTILINE_RESPONSE:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_MULTILINE_TERMINATOR_CR;
+                       break;
+
+               case GSMV1_STATE_MULTILINE_TERMINATOR_CR:
+                       syntax->state = GSMV1_STATE_IDLE;
+
+                       if (byte == '\n') {
+                               i += 1;
+                               res = G_AT_SYNTAX_RESULT_MULTILINE;
+                       } else
+                               res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
+
+                       goto out;
+
+               case GSMV1_STATE_PDU:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_PDU_CR;
+                       break;
+
+               case GSMV1_STATE_PDU_CR:
+                       syntax->state = GSMV1_STATE_IDLE;
+
+                       if (byte == '\n') {
+                               i += 1;
+                               res = G_AT_SYNTAX_RESULT_PDU;
+                       } else
+                               res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
+
+                       goto out;
+
+               case GSMV1_STATE_PROMPT:
+                       if (byte == ' ') {
+                               syntax->state = GSMV1_STATE_IDLE;
+                               i += 1;
+                               res = G_AT_SYNTAX_RESULT_PROMPT;
+                               goto out;
+                       }
+
+                       syntax->state = GSMV1_STATE_RESPONSE;
+                       return G_AT_SYNTAX_RESULT_UNSURE;
+
+               case GSMV1_STATE_GARBAGE:
+                       if (byte == '\r')
+                               syntax->state = GSMV1_STATE_GARBAGE_CHECK_LF;
+                       break;
+
+               case GSMV1_STATE_GARBAGE_CHECK_LF:
+                       syntax->state = GSMV1_STATE_IDLE;
+                       res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
+
+                       if (byte == '\n')
+                               i += 1;
+
+                       goto out;
+
+               default:
+                       break;
+               };
+
+               i += 1;
+       }
+
+out:
+       *len = i;
+       return res;
+}
+
+GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed,
+                                       GAtSyntaxSetHintFunc hint,
+                                       int initial_state)
+{
+       GAtSyntax *syntax;
+
+       syntax = g_new0(GAtSyntax, 1);
+
+       syntax->feed = feed;
+       syntax->set_hint = hint;
+       syntax->state = initial_state;
+       syntax->ref_count = 1;
+
+       return syntax;
+}
+
+
+GAtSyntax *g_at_syntax_new_gsmv1()
+{
+       return g_at_syntax_new_full(gsmv1_feed, gsmv1_hint, GSMV1_STATE_IDLE);
+}
+
+GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax)
+{
+       if (syntax == NULL)
+               return NULL;
+
+       g_atomic_int_inc(&syntax->ref_count);
+
+       return syntax;
+}
+
+void g_at_syntax_unref(GAtSyntax *syntax)
+{
+       gboolean is_zero;
+
+       if (syntax == NULL)
+               return;
+
+       is_zero = g_atomic_int_dec_and_test(&syntax->ref_count);
+
+       if (is_zero)
+               g_free(syntax);
+}
diff --git a/gatchat/gatsyntax.h b/gatchat/gatsyntax.h
new file mode 100644 (file)
index 0000000..57edead
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *
+ *  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 __GATSYNTAX_H
+#define __GATSYNTAX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum _GAtSyntaxExpectHint {
+       G_AT_SYNTAX_EXPECT_PDU,
+       G_AT_SYNTAX_EXPECT_MULTILINE,
+       G_AT_SYNTAX_EXPECT_PROMPT
+};
+
+typedef enum _GAtSyntaxExpectHint GAtSyntaxExpectHint;
+
+enum _GAtSyntaxResult {
+       G_AT_SYNTAX_RESULT_UNRECOGNIZED,
+       G_AT_SYNTAX_RESULT_UNSURE,
+       G_AT_SYNTAX_RESULT_LINE,
+       G_AT_SYNTAX_RESULT_MULTILINE,
+       G_AT_SYNTAX_RESULT_PDU,
+       G_AT_SYNTAX_RESULT_PROMPT,
+};
+
+typedef enum _GAtSyntaxResult GAtSyntaxResult;
+
+typedef struct _GAtSyntax GAtSyntax;
+
+typedef void (*GAtSyntaxSetHintFunc)(GAtSyntax *syntax,
+                                       GAtSyntaxExpectHint hint);
+typedef GAtSyntaxResult (*GAtSyntaxFeedFunc)(GAtSyntax *syntax,
+                                               const char *bytes, gsize *len);
+
+struct _GAtSyntax {
+       gint ref_count;
+       int state;
+       GAtSyntaxSetHintFunc set_hint;
+       GAtSyntaxFeedFunc feed;
+};
+
+
+GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed,
+                                       GAtSyntaxSetHintFunc hint,
+                                       int initial_state);
+GAtSyntax *g_at_syntax_new_gsmv1();
+
+GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax);
+void g_at_syntax_unref(GAtSyntax *syntax);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GATSYNTAX_H */