summaryrefslogtreecommitdiffstats
path: root/gatchat
diff options
context:
space:
mode:
authorZhenhua Zhang <zhenhua.zhang@intel.com>2010-01-18 22:31:18 +0800
committerDenis Kenzior <denkenz@gmail.com>2010-02-01 17:59:27 -0600
commitdf7b1b30dc17827a1597f4753b8132df5607356f (patch)
treebaf2f11bf90afc981877e15b70e31507bc954000 /gatchat
parent105fccdcaf4c941a64f92ab7ca4508c15c34eba6 (diff)
downloadofono-df7b1b30dc17827a1597f4753b8132df5607356f.tar.bz2
Add GAtServer basic parsing support
It's the basic skeleton of GAtServer, including new/shutdown, ref/ unref, received_data/parse_buffer and set_discuss/set_debug. GAtServer is to emulate the server side of AT conversation. It complies with V.250 and 27.007 spec to accept AT command like ATV1, ATE0 and extended command like AT+CLCC. Upper layer could create customize server to expose TTY, tcp or unix socket to client side application.
Diffstat (limited to 'gatchat')
-rw-r--r--gatchat/gatserver.c486
-rw-r--r--gatchat/gatserver.h65
2 files changed, 551 insertions, 0 deletions
diff --git a/gatchat/gatserver.c b/gatchat/gatserver.c
new file mode 100644
index 00000000..4f9ca7db
--- /dev/null
+++ b/gatchat/gatserver.c
@@ -0,0 +1,486 @@
+/*
+ *
+ * AT server library with GLib integration
+ *
+ * Copyright (C) 2008-2010 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 <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "ringbuffer.h"
+#include "gatserver.h"
+
+struct result_codes {
+ const char *v1;
+ unsigned int v0;
+};
+
+/* V.250 Table 1/V.250 Result codes */
+static struct result_codes at_server_result_codes[] = {
+ { "OK", 0, },
+ { "CONNECT", 1, },
+ { "RING", 2, },
+ { "NO CARRIER", 3, },
+ { "ERROR", 4, },
+ { "NO DIALTONE", 5, },
+ { "BUSY", 6, },
+ { "NO ANSWER", 7, },
+ { "CONNECT", 8, },
+ { NULL },
+};
+
+/* Basic command setting for V.250 */
+struct v250_settings {
+ char s3; /* set by S3=<val> */
+ char s4; /* set by S4=<val> */
+ char s5; /* set by S5=<val> */
+ gboolean echo; /* set by E<val> */
+ gboolean quiet; /* set by Q<val> */
+ gboolean is_v1; /* set by V<val>, v0 or v1 */
+ unsigned int res_format; /* set by X<val> */
+ unsigned int c109; /* set by &C<val> */
+ unsigned int c108; /* set by &D<val> */
+};
+
+struct _GAtServer {
+ gint ref_count; /* Ref count */
+ struct v250_settings v250; /* V.250 command setting */
+ GIOChannel *server_io; /* Server IO */
+ int server_watch; /* Watch for server IO */
+ guint read_so_far; /* Number of bytes processed */
+ GAtDisconnectFunc user_disconnect; /* User disconnect func */
+ gpointer user_disconnect_data; /* User disconnect data */
+ GAtDebugFunc debugf; /* Debugging output function */
+ gpointer debug_data; /* Data to pass to debug func */
+ struct ring_buffer *buf; /* Current read buffer */
+};
+
+static int at_server_parse(GAtServer *server, char *buf);
+
+static void g_at_server_send_result_code(GAtServer *server, int error)
+{
+ struct v250_settings v250 = server->v250;
+ char buf[1024];
+ char text[1024];
+ char t = v250.s3;
+ char r = v250.s4;
+ struct result_codes c;
+ gsize wbuf;
+
+ memset(buf, 0, sizeof(buf));
+ memset(text, 0, sizeof(text));
+
+ if (v250.quiet)
+ return;
+
+ c = at_server_result_codes[error];
+
+ if (v250.is_v1)
+ sprintf(text, "%s", c.v1);
+ else
+ sprintf(text, "%d", c.v0);
+
+ if (v250.is_v1)
+ sprintf(buf, "%c%c%s%c%c", t, r, text, t, r);
+ else
+ sprintf(buf, "%s%c", text, t);
+
+ g_at_util_debug_chat(server->debugf, FALSE, buf, strlen(buf),
+ server->debug_data);
+
+ g_io_channel_write(server->server_io, (char *) buf, strlen(buf),
+ &wbuf);
+}
+
+static gsize skip_space(const char *buf, gsize pos)
+{
+ gsize i = pos;
+ char c = buf[i];
+
+ while (c == ' ')
+ c = buf[++i];
+
+ return i;
+}
+
+static inline gboolean is_at_command_prefix(const char c)
+{
+ if (c == '&')
+ return FALSE;
+
+ return g_ascii_ispunct(c);
+}
+
+static int parse_at_command(GAtServer *server, char *buf)
+{
+ int res = G_AT_SERVER_RESULT_ERROR;
+
+ return res;
+}
+
+static int parse_v250_settings(GAtServer *server, char *buf)
+{
+ int res = G_AT_SERVER_RESULT_ERROR;
+
+ return res;
+}
+
+static int at_server_parse(GAtServer *server, char *buf)
+{
+ int res = G_AT_SERVER_RESULT_ERROR;
+ gsize i = 0;
+ char c;
+
+ /* skip space after "AT" or previous command */
+ i = skip_space(buf, i);
+
+ c = buf[i];
+ /* skip semicolon */
+ if (c == ';')
+ c = buf[++i];
+
+ if (is_at_command_prefix(c) || c == 'A' || c == 'D' || c == 'H')
+ res = parse_at_command(server, buf + i);
+ else if (g_ascii_isalpha(c) || c == '&')
+ res = parse_v250_settings(server, buf + i);
+ else if (c == '\0')
+ res = G_AT_SERVER_RESULT_OK;
+
+ return res;
+}
+
+static void parse_buffer(GAtServer *server, char *buf)
+{
+ int res = G_AT_SERVER_RESULT_ERROR;
+ gsize i = 0;
+
+ if (!buf)
+ return;
+
+ g_at_util_debug_chat(server->debugf, TRUE, (char *) buf,
+ strlen(buf), server->debug_data);
+
+ /* skip header space */
+ buf += skip_space(buf, i);
+
+ /* Make sure the command line prefix is "AT" or "at" */
+ if (g_str_has_prefix(buf, "AT") ||
+ g_str_has_prefix(buf, "at"))
+ res = at_server_parse(server, (char *) buf + 2);
+
+ g_at_server_send_result_code(server, res);
+
+ /* We're overflowing the buffer, shutdown the socket */
+ if (server->buf && ring_buffer_avail(server->buf) == 0)
+ g_at_server_shutdown(server);
+
+ if (buf)
+ g_free(buf);
+}
+
+static char *extract_line(GAtServer *p, unsigned int *unread)
+{
+ unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
+ unsigned int pos = 0;
+ unsigned char *buf = ring_buffer_read_ptr(p->buf, pos);
+ char s3 = p->v250.s3;
+ char s4 = p->v250.s4;
+ char *line;
+
+ int strip_front = 0;
+ int strip_tail = 0;
+ int line_length = 0;
+
+ while (pos < p->read_so_far) {
+ if (*buf == s3 || *buf == s4)
+ if (!line_length)
+ strip_front += 1;
+ else
+ break;
+ else
+ line_length += 1;
+
+ buf += 1;
+ pos += 1;
+
+ if (pos == wrap)
+ buf = ring_buffer_read_ptr(p->buf, pos);
+ }
+
+ if (!line_length) {
+ ring_buffer_drain(p->buf, strip_front);
+ return NULL;
+ }
+
+ 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);
+
+ line[line_length] = '\0';
+
+ while (pos < p->read_so_far) {
+ if (*buf == s3 || *buf == s4)
+ strip_tail += 1;
+ else
+ break;
+
+ buf += 1;
+ pos += 1;
+
+ if (pos == wrap)
+ buf = ring_buffer_read_ptr(p->buf, pos);
+ }
+
+ ring_buffer_drain(p->buf, strip_tail);
+
+ *unread = p->read_so_far - strip_front - line_length - strip_tail;
+
+ return line;
+}
+
+static void new_bytes(GAtServer *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);
+ char s3 = p->v250.s3;
+
+ while (p->read_so_far < len) {
+ gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
+ unsigned char *s3_pos = memchr(buf, s3, rbytes);
+ char *line = NULL;
+ unsigned int unread = 0;
+
+ p->read_so_far += rbytes;
+
+ if (s3_pos)
+ line = extract_line(p, &unread);
+
+ buf += rbytes - unread;
+ p->read_so_far -= unread;
+
+ if (p->read_so_far == wrap) {
+ buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
+ wrap = len;
+ }
+
+ if (s3_pos) {
+ parse_buffer(p, line);
+
+ len -= p->read_so_far;
+ wrap -= p->read_so_far;
+ p->read_so_far = 0;
+ }
+ }
+}
+
+static gboolean received_data(GIOChannel *channel, GIOCondition cond,
+ gpointer data)
+{
+ unsigned char *buf;
+ GAtServer *server = data;
+ GIOError err;
+ gsize rbytes;
+ gsize toread;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ do {
+ toread = ring_buffer_avail_no_wrap(server->buf);
+
+ if (toread == 0)
+ break;
+
+ rbytes = 0;
+ buf = ring_buffer_write_ptr(server->buf);
+
+ err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
+ g_at_util_debug_chat(server->debugf, TRUE, (char *)buf, rbytes,
+ server->debug_data);
+
+ if (rbytes > 0) {
+ ring_buffer_write_advance(server->buf, rbytes);
+
+ new_bytes(server);
+ }
+
+ } while (err == G_IO_ERROR_NONE && rbytes > 0);
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ return FALSE;
+
+ if (rbytes == 0 && err != G_IO_ERROR_AGAIN)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void server_watcher_destroy_notify(GAtServer *server)
+{
+ server->server_watch = 0;
+
+ ring_buffer_free(server->buf);
+ server->buf = NULL;
+
+ server->server_io = NULL;
+
+ if (server->user_disconnect)
+ server->user_disconnect(server->user_disconnect_data);
+}
+
+static void v250_settings_create(struct v250_settings *v250)
+{
+ v250->s3 = '\r';
+ v250->s4 = '\n';
+ v250->s5 = '\b';
+ v250->echo = TRUE;
+ v250->quiet = FALSE;
+ v250->is_v1 = TRUE;
+ v250->res_format = 0;
+ v250->c109 = 1;
+ v250->c108 = 0;
+}
+
+GAtServer *g_at_server_new(GIOChannel *io)
+{
+ GAtServer *server;
+
+ if (!io)
+ return NULL;
+
+ server = g_try_new0(GAtServer, 1);
+ if (!server)
+ return NULL;
+
+ server->ref_count = 1;
+ v250_settings_create(&server->v250);
+ server->server_io = io;
+ server->read_so_far = 0;
+ server->user_disconnect = NULL;
+ server->user_disconnect_data = NULL;
+ server->debugf = NULL;
+ server->debug_data = NULL;
+ server->buf = ring_buffer_new(4096);
+
+ if (!server->buf)
+ goto error;
+
+ if (!g_at_util_setup_io(server->server_io))
+ goto error;
+
+ server->server_watch = g_io_add_watch_full(io,
+ G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ received_data, server,
+ (GDestroyNotify)server_watcher_destroy_notify);
+
+ return server;
+
+error:
+ if (server->buf)
+ ring_buffer_free(server->buf);
+
+ if (server)
+ g_free(server);
+
+ return NULL;
+}
+
+GAtServer *g_at_server_ref(GAtServer *server)
+{
+ if (server == NULL)
+ return NULL;
+
+ g_atomic_int_inc(&server->ref_count);
+
+ return server;
+}
+
+void g_at_server_unref(GAtServer *server)
+{
+ gboolean is_zero;
+
+ if (server == NULL)
+ return;
+
+ is_zero = g_atomic_int_dec_and_test(&server->ref_count);
+
+ if (is_zero == FALSE)
+ return;
+
+ g_at_server_shutdown(server);
+}
+
+gboolean g_at_server_shutdown(GAtServer *server)
+{
+ if (!server)
+ return FALSE;
+
+ /* Don't trigger user disconnect on shutdown */
+ server->user_disconnect = NULL;
+ server->user_disconnect_data = NULL;
+
+ if (server->server_watch) {
+ g_source_remove(server->server_watch);
+ server->server_watch = 0;
+ }
+
+ g_free(server);
+ server = NULL;
+
+ return TRUE;
+}
+
+gboolean g_at_server_set_disconnect_function(GAtServer *server,
+ GAtDisconnectFunc disconnect,
+ gpointer user)
+{
+ if (server == NULL)
+ return FALSE;
+
+ server->user_disconnect = disconnect;
+ server->user_disconnect_data = user;
+
+ return TRUE;
+}
+
+gboolean g_at_server_set_debug(GAtServer *server, GAtDebugFunc func,
+ gpointer user)
+{
+ if (server == NULL)
+ return FALSE;
+
+ server->debugf = func;
+ server->debug_data = user;
+
+ return TRUE;
+}
diff --git a/gatchat/gatserver.h b/gatchat/gatserver.h
new file mode 100644
index 00000000..0785f274
--- /dev/null
+++ b/gatchat/gatserver.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * AT Server library with GLib integration
+ *
+ * Copyright (C) 2008-2010 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 __GATSERVER_H
+#define __GATSERVER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gatutil.h"
+
+struct _GAtServer;
+
+typedef struct _GAtServer GAtServer;
+
+/* V.250 Table 1/V.250 Result codes */
+enum _GAtServerResultCodes {
+ G_AT_SERVER_RESULT_OK = 0,
+ G_AT_SERVER_RESULT_CONNECT,
+ G_AT_SERVER_RESULT_RING,
+ G_AT_SERVER_RESULT_NO_CARRIER,
+ G_AT_SERVER_RESULT_ERROR,
+ G_AT_SERVER_RESULT_NO_DIALTONE,
+ G_AT_SERVER_RESULT_BUSY,
+ G_AT_SERVER_RESULT_NO_ANSWER,
+ G_AT_SERVER_RESULT_CONNECT_EXT,
+};
+
+GAtServer *g_at_server_new(GIOChannel *io);
+
+GAtServer *g_at_server_ref(GAtServer *server);
+void g_at_server_unref(GAtServer *server);
+gboolean g_at_server_shutdown(GAtServer *server);
+
+gboolean g_at_server_set_disconnect_function(GAtServer *server,
+ GAtDisconnectFunc disconnect,
+ gpointer user_data);
+gboolean g_at_server_set_debug(GAtServer *server,
+ GAtDebugFunc func,
+ gpointer user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GATSERVER_H */