diff --git a/Makefile.am b/Makefile.am index a07b8170..3b1f2734 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,7 +53,8 @@ gatchat_sources = gatchat/gatchat.h gatchat/gatchat.c \ gatchat/gsm0710.h gatchat/gsm0710.c \ gatchat/gattty.h gatchat/gattty.c \ gatchat/gatutil.h gatchat/gatutil.c \ - gatchat/gat.h + gatchat/gat.h \ + gatchat/gatserver.h gatchat/gatserver.c udev_files = plugins/ofono.rules 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 +#endif + +#include +#include +#include + +#include + +#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= */ + char s4; /* set by S4= */ + char s5; /* set by S5= */ + gboolean echo; /* set by E */ + gboolean quiet; /* set by Q */ + gboolean is_v1; /* set by V, v0 or v1 */ + unsigned int res_format; /* set by X */ + unsigned int c109; /* set by &C */ + unsigned int c108; /* set by &D */ +}; + +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 */