-/*
- *
- * AT chat 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 <assert.h>
-#include <ctype.h>
-#include <errno.h>
-
-#include <glib.h>
-
-#include "ringbuffer.h"
-#include "gatchat.h"
-
-/* #define WRITE_SCHEDULER_DEBUG 1 */
-
-static const char *none_prefix[] = { NULL };
-
-static void g_at_chat_wakeup_writer(GAtChat *chat);
-
-struct at_command {
- char *cmd;
- char **prefixes;
- gboolean expect_pdu;
- guint id;
- GAtResultFunc callback;
- GAtNotifyFunc listing;
- 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 */
- gboolean use_write_watch; /* watch usage for non blocking */
- 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 */
- guint max_read_attempts; /* max number of read attempts */
- GAtDebugFunc debugf; /* debugging output function */
- gpointer debug_data; /* Data to pass to debug func */
- char *pdu_notify; /* Unsolicited Resp w/ PDU */
- GSList *response_lines; /* char * lines of the response */
- char *wakeup; /* command sent to wakeup modem */
- gint timeout_source;
- 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;
- gboolean destroyed; /* Re-entrancy guard */
- GSList *terminator_list; /* Non-standard terminator */
-};
-
-struct terminator_info {
- char *terminator;
- int len;
- gboolean success;
-};
-
-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,
- gboolean expect_pdu,
- GAtNotifyFunc listing,
- GAtResultFunc func,
- gpointer user_data,
- GDestroyNotify notify,
- gboolean wakeup)
-{
- 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 (wakeup == FALSE) {
- if (strchr(cmd, '\r'))
- c->cmd[len] = 26;
- else
- c->cmd[len] = '\r';
-
- len += 1;
- }
-
- c->cmd[len] = '\0';
-
- c->expect_pdu = expect_pdu;
- c->prefixes = prefixes;
- c->callback = func;
- c->listing = listing;
- 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 free_terminator(struct terminator_info *info)
-{
- g_free(info->terminator);
- info->terminator = NULL;
- g_free(info);
- info = NULL;
-}
-
-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;
- }
-
- if (chat->timeout_source) {
- g_source_remove(chat->timeout_source);
- chat->timeout_source = 0;
- }
-
- g_at_syntax_unref(chat->syntax);
- chat->syntax = NULL;
-
- chat->channel = NULL;
-
- if (chat->terminator_list) {
- g_slist_foreach(chat->terminator_list,
- (GFunc)free_terminator, NULL);
- g_slist_free(chat->terminator_list);
- chat->terminator_list = NULL;
- }
-}
-
-static void read_watcher_destroy_notify(GAtChat *chat)
-{
- g_at_chat_cleanup(chat);
- chat->read_watch = 0;
-
- if (chat->user_disconnect)
- chat->user_disconnect(chat->user_disconnect_data);
-
- if (chat->destroyed)
- g_free(chat);
-}
-
-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;
-
- if (chat->syntax->set_hint)
- chat->syntax->set_hint(chat->syntax,
- G_AT_SYNTAX_EXPECT_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);
- }
-
- 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);
- GSList *response_lines;
-
- /* Cannot happen, but lets be paranoid */
- if (!cmd)
- return;
-
- p->cmd_bytes_written = 0;
-
- if (g_queue_peek_head(p->command_queue))
- g_at_chat_wakeup_writer(p);
-
- response_lines = p->response_lines;
- p->response_lines = NULL;
-
- if (cmd->callback) {
- GAtResult result;
-
- response_lines = g_slist_reverse(response_lines);
-
- result.final_or_pdu = final;
- result.lines = response_lines;
-
- cmd->callback(ok, &result, cmd->user_data);
- }
-
- g_slist_foreach(response_lines, (GFunc)g_free, NULL);
- g_slist_free(response_lines);
-
- g_free(final);
- at_command_destroy(cmd);
-}
-
-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 }
-};
-
-void g_at_chat_add_terminator(GAtChat *chat, char *terminator,
- int len, gboolean success)
-{
- struct terminator_info *info = g_new0(struct terminator_info, 1);
- info->terminator = g_strdup(terminator);
- info->len = len;
- info->success = success;
- chat->terminator_list = g_slist_prepend(chat->terminator_list, info);
-}
-
-static gboolean check_terminator(struct terminator_info *info, char *line)
-{
- if (info->len == -1 && !strcmp(line, info->terminator))
- return TRUE;
-
- if (info->len > 0 && !strncmp(line, info->terminator, info->len))
- return TRUE;
-
- return 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);
- int hint;
- GSList *l;
-
- for (i = 0; i < size; i++) {
- struct terminator_info *info = &terminator_table[i];
- if (check_terminator(info, line)) {
- g_at_chat_finish_command(p, info->success, line);
- return TRUE;
- }
- }
-
- for (l = p->terminator_list; l; l = l->next) {
- struct terminator_info *info = l->data;
- if (check_terminator(info, line)) {
- 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:
- if (cmd->listing && cmd->expect_pdu)
- hint = G_AT_SYNTAX_EXPECT_PDU;
- else
- hint = G_AT_SYNTAX_EXPECT_MULTILINE;
-
- if (p->syntax->set_hint)
- p->syntax->set_hint(p->syntax, hint);
-
- if (cmd->listing && cmd->expect_pdu) {
- p->pdu_notify = line;
- return TRUE;
- }
-
- if (cmd->listing) {
- GAtResult result;
-
- result.lines = g_slist_prepend(NULL, line);
- result.final_or_pdu = NULL;
-
- cmd->listing(&result, cmd->user_data);
-
- g_slist_free(result.lines);
- g_free(line);
- } else
- p->response_lines = g_slist_prepend(p->response_lines, line);
-
- return TRUE;
-}
-
-static void have_line(GAtChat *p, char *str)
-{
- /* We're not going to copy terminal <CR><LF> */
- struct at_command *cmd;
-
- if (!str)
- return;
-
- /* 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 > 0) {
- char c = cmd->cmd[p->cmd_bytes_written - 1];
-
- /* We check that we have submitted a terminator, in which case
- * a command might have failed or completed successfully
- *
- * In the generic case, \r is at the end of the command, so we
- * know the entire command has been submitted. In the case of
- * commands like CMGS, every \r or Ctrl-Z might result in a
- * final response from the modem, so we check this as well.
- */
- if ((c == '\r' || c == 26) &&
- 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);
-}
-
-static void have_notify_pdu(GAtChat *p, char *pdu, GAtResult *result)
-{
- GHashTableIter iter;
- struct at_notify *notify;
- char *prefix;
- gpointer key, value;
-
- 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);
- }
-}
-
-static void have_pdu(GAtChat *p, char *pdu)
-{
- struct at_command *cmd;
- GAtResult result;
- gboolean listing_pdu = FALSE;
-
- if (!pdu)
- goto error;
-
- result.lines = g_slist_prepend(NULL, p->pdu_notify);
- result.final_or_pdu = pdu;
-
- cmd = g_queue_peek_head(p->command_queue);
-
- if (cmd && cmd->expect_pdu && p->cmd_bytes_written > 0) {
- char c = cmd->cmd[p->cmd_bytes_written - 1];
-
- if (c == '\r')
- listing_pdu = TRUE;
- }
-
- if (listing_pdu) {
- cmd->listing(&result, cmd->user_data);
-
- if (p->syntax->set_hint)
- p->syntax->set_hint(p->syntax,
- G_AT_SYNTAX_EXPECT_MULTILINE);
- } else
- have_notify_pdu(p, pdu, &result);
-
- g_slist_free(result.lines);
-
-error:
- g_free(p->pdu_notify);
- p->pdu_notify = NULL;
-
- if (pdu)
- g_free(pdu);
-}
-
-static char *extract_line(GAtChat *p)
-{
- 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
- break;
- else
- line_length += 1;
-
- buf += 1;
- pos += 1;
-
- if (pos == wrap)
- buf = ring_buffer_read_ptr(p->buf, pos);
- }
-
- 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)
-{
- 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);
-
- GAtSyntaxResult result;
-
- g_at_chat_ref(p);
-
- while (p->channel && (p->read_so_far < len)) {
- gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
- result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
-
- 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 (result == G_AT_SYNTAX_RESULT_UNSURE)
- continue;
-
- switch (result) {
- case G_AT_SYNTAX_RESULT_LINE:
- case G_AT_SYNTAX_RESULT_MULTILINE:
- have_line(p, extract_line(p));
- break;
-
- 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;
-
- default:
- ring_buffer_drain(p->buf, p->read_so_far);
- break;
- }
-
- len -= p->read_so_far;
- wrap -= p->read_so_far;
- p->read_so_far = 0;
- }
-
- /* We're overflowing the buffer, shutdown the socket */
- if (p->buf && ring_buffer_avail(p->buf) == 0)
- g_source_remove(p->read_watch);
-
- g_at_chat_unref(p);
-}
-
-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;
- guint read_count = 0;
-
- if (cond & G_IO_NVAL)
- return FALSE;
-
- /* Regardless of condition, try to read all the data available */
- do {
- toread = ring_buffer_avail_no_wrap(chat->buf);
-
- if (toread == 0)
- break;
-
- rbytes = 0;
- buf = ring_buffer_write_ptr(chat->buf);
-
- err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
- g_at_util_debug_chat(TRUE, (char *)buf, rbytes,
- chat->debugf, chat->debug_data);
-
- read_count++;
-
- total_read += rbytes;
-
- if (rbytes > 0)
- ring_buffer_write_advance(chat->buf, rbytes);
-
- } while (err == G_IO_ERROR_NONE && rbytes > 0 &&
- read_count < chat->max_read_attempts);
-
- if (total_read > 0)
- new_bytes(chat);
-
- if (cond & (G_IO_HUP | G_IO_ERR))
- return FALSE;
-
- if (read_count > 0 && rbytes == 0 && err != G_IO_ERROR_AGAIN)
- return FALSE;
-
- return TRUE;
-}
-
-static void wakeup_cb(gboolean ok, GAtResult *result, gpointer user_data)
-{
- GAtChat *chat = user_data;
-
- if (ok == FALSE)
- return;
-
- if (chat->debugf)
- chat->debugf("Finally woke up the modem\n", chat->debug_data);
-
- g_source_remove(chat->timeout_source);
- chat->timeout_source = 0;
-}
-
-static gboolean wakeup_no_response(gpointer user)
-{
- GAtChat *chat = user;
- struct at_command *cmd = g_queue_peek_head(chat->command_queue);
-
- if (chat->debugf)
- chat->debugf("Wakeup got no response\n", chat->debug_data);
-
- g_at_chat_finish_command(chat, FALSE, NULL);
- cmd = at_command_create(chat->wakeup, none_prefix, FALSE,
- NULL, wakeup_cb, chat, NULL, TRUE);
-
- if (!cmd) {
- chat->timeout_source = 0;
- return FALSE;
- }
-
- g_queue_push_head(chat->command_queue, cmd);
-
- return TRUE;
-}
-
-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, none_prefix, FALSE,
- NULL, wakeup_cb, chat, NULL, TRUE);
-
- if (!cmd)
- return FALSE;
-
- g_queue_push_head(chat->command_queue, cmd);
-
- len = strlen(chat->wakeup);
-
- chat->timeout_source = 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_source_remove(chat->read_watch);
- return FALSE;
- }
-
- g_at_util_debug_chat(FALSE, cmd->cmd + chat->cmd_bytes_written,
- bytes_written, chat->debugf, chat->debug_data);
- 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;
-
- if (chat->use_write_watch == TRUE) {
- 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);
- } else {
- while (can_write_data(chat->channel, G_IO_OUT, chat) == TRUE);
- write_watcher_destroy_notify(chat);
- }
-}
-
-static GAtChat *create_chat(GIOChannel *channel, GIOFlags flags,
- GAtSyntax *syntax)
-{
- GAtChat *chat;
-
- if (!channel)
- return NULL;
-
- if (!syntax)
- return NULL;
-
- chat = g_try_new0(GAtChat, 1);
-
- if (!chat)
- return chat;
-
- chat->ref_count = 1;
- chat->next_cmd_id = 1;
- chat->next_notify_id = 1;
- chat->debugf = NULL;
-
- if (flags & G_IO_FLAG_NONBLOCK) {
- chat->use_write_watch = TRUE;
- chat->max_read_attempts = 3;
- } else {
- chat->use_write_watch = FALSE;
- chat->max_read_attempts = 1;
- }
-
- 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_at_util_setup_io(channel, flags))
- goto error;
-
- 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);
-
- chat->syntax = g_at_syntax_ref(syntax);
-
- 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_new(GIOChannel *channel, GAtSyntax *syntax)
-{
- return create_chat(channel, G_IO_FLAG_NONBLOCK, syntax);
-}
-
-GAtChat *g_at_chat_new_blocking(GIOChannel *channel, GAtSyntax *syntax)
-{
- return create_chat(channel, 0, syntax);
-}
-
-GIOChannel *g_at_chat_get_channel(GAtChat *chat)
-{
- if (chat == NULL)
- return NULL;
-
- return chat->channel;
-}
-
-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 == FALSE)
- return;
-
- g_at_chat_shutdown(chat);
-
- /* glib delays the destruction of the watcher until it exits, this
- * means we can't free the data just yet, even though we've been
- * destroyed already. We have to wait until the read_watcher
- * destroy function gets called
- */
- if (chat->read_watch != 0)
- chat->destroyed = TRUE;
- else
- g_free(chat);
-}
-
-gboolean g_at_chat_shutdown(GAtChat *chat)
-{
- if (chat->channel == NULL)
- return FALSE;
-
- /* Don't trigger user disconnect on shutdown */
- chat->user_disconnect = NULL;
- chat->user_disconnect_data = NULL;
-
- 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_syntax(GAtChat *chat, GAtSyntax *syntax)
-{
- if (chat == NULL)
- return FALSE;
-
- g_at_syntax_unref(chat->syntax);
-
- chat->syntax = g_at_syntax_ref(syntax);
-
- 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;
-}
-
-gboolean g_at_chat_set_debug(GAtChat *chat, GAtDebugFunc func, gpointer user)
-{
- if (chat == NULL)
- return FALSE;
-
- chat->debugf = func;
- chat->debug_data = user;
-
- return TRUE;
-}
-
-static guint send_common(GAtChat *chat, const char *cmd,
- const char **prefix_list,
- gboolean expect_pdu,
- GAtNotifyFunc listing, 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, expect_pdu, listing, func,
- user_data, notify, FALSE);
-
- 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;
-}
-
-guint g_at_chat_send(GAtChat *chat, const char *cmd,
- const char **prefix_list, GAtResultFunc func,
- gpointer user_data, GDestroyNotify notify)
-{
- return send_common(chat, cmd, prefix_list, FALSE, NULL, func,
- user_data, notify);
-}
-
-guint g_at_chat_send_listing(GAtChat *chat, const char *cmd,
- const char **prefix_list,
- GAtNotifyFunc listing, GAtResultFunc func,
- gpointer user_data, GDestroyNotify notify)
-{
- if (listing == NULL)
- return 0;
-
- return send_common(chat, cmd, prefix_list, FALSE, listing, func,
- user_data, notify);
-}
-
-guint g_at_chat_send_pdu_listing(GAtChat *chat, const char *cmd,
- const char **prefix_list,
- GAtNotifyFunc listing, GAtResultFunc func,
- gpointer user_data, GDestroyNotify notify)
-{
- if (listing == NULL)
- return 0;
-
- return send_common(chat, cmd, prefix_list, TRUE, listing, func,
- user_data, notify);
-}
-
-gboolean g_at_chat_cancel(GAtChat *chat, guint id)
-{
- GList *l;
-
- if (chat == NULL || chat->command_queue == NULL)
- return FALSE;
-
- /* We use id 0 for wakeup commands */
- if (id == 0)
- 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) &&
- chat->cmd_bytes_written > 0) {
- 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;
-}
-
-gboolean g_at_chat_cancel_all(GAtChat *chat)
-{
- int n = 0;
- struct at_command *c;
-
- if (chat == NULL || chat->command_queue == NULL)
- return FALSE;
-
- while ((c = g_queue_peek_nth(chat->command_queue, n)) != NULL) {
- if (c->id == 0) {
- n += 1;
- continue;
- }
-
- if (n == 0 && chat->cmd_bytes_written > 0) {
- c->callback = NULL;
- n += 1;
- continue;
- }
-
- at_command_destroy(c);
- g_queue_remove(chat->command_queue, c);
- }
-
- 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_unregister_all(GAtChat *chat)
-{
- 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;
-
- for (l = notify->nodes; l; l = l->next)
- at_notify_node_destroy(l->data);
-
- g_slist_free(notify->nodes);
- notify->nodes = NULL;
- g_hash_table_iter_remove(&iter);
- }
-
- 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;
-}