2010-04-27 16:07:53 +00:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* 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 "gatio.h"
|
|
|
|
#include "gatutil.h"
|
|
|
|
|
|
|
|
struct _GAtIO {
|
|
|
|
gint ref_count; /* Ref count */
|
|
|
|
guint read_watch; /* GSource read id, 0 if no */
|
2010-04-28 03:43:25 +00:00
|
|
|
guint write_watch; /* GSource write id, 0 if no */
|
2010-04-27 16:07:53 +00:00
|
|
|
GIOChannel *channel; /* comms channel */
|
|
|
|
GAtDisconnectFunc user_disconnect; /* user disconnect func */
|
|
|
|
gpointer user_disconnect_data; /* user disconnect data */
|
|
|
|
struct ring_buffer *buf; /* Current read buffer */
|
|
|
|
guint max_read_attempts; /* max reads / select */
|
|
|
|
GAtIOReadFunc read_handler; /* Read callback */
|
|
|
|
gpointer read_data; /* Read callback userdata */
|
2010-04-28 03:43:25 +00:00
|
|
|
gboolean use_write_watch; /* Use write select */
|
|
|
|
GAtIOWriteFunc write_handler; /* Write callback */
|
|
|
|
gpointer write_data; /* Write callback userdata */
|
2010-04-27 16:07:53 +00:00
|
|
|
GAtDebugFunc debugf; /* debugging output function */
|
|
|
|
gpointer debug_data; /* Data to pass to debug func */
|
|
|
|
gboolean destroyed; /* Re-entrancy guard */
|
|
|
|
};
|
|
|
|
|
|
|
|
static void read_watcher_destroy_notify(gpointer user_data)
|
|
|
|
{
|
|
|
|
GAtIO *io = user_data;
|
|
|
|
|
|
|
|
ring_buffer_free(io->buf);
|
|
|
|
io->buf = NULL;
|
|
|
|
|
2010-04-28 03:43:25 +00:00
|
|
|
io->debugf = NULL;
|
|
|
|
io->debug_data = NULL;
|
|
|
|
|
2010-04-27 16:07:53 +00:00
|
|
|
io->read_watch = 0;
|
2010-04-28 03:43:25 +00:00
|
|
|
io->read_handler = NULL;
|
|
|
|
io->read_data = NULL;
|
|
|
|
|
|
|
|
io->channel = NULL;
|
2010-04-27 16:07:53 +00:00
|
|
|
|
|
|
|
if (io->destroyed)
|
|
|
|
g_free(io);
|
2010-04-27 19:48:11 +00:00
|
|
|
else if (io->user_disconnect)
|
|
|
|
io->user_disconnect(io->user_disconnect_data);
|
2010-04-27 16:07:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean received_data(GIOChannel *channel, GIOCondition cond,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
unsigned char *buf;
|
|
|
|
GAtIO *io = 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(io->buf);
|
|
|
|
|
|
|
|
if (toread == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
rbytes = 0;
|
|
|
|
buf = ring_buffer_write_ptr(io->buf, 0);
|
|
|
|
|
|
|
|
err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
|
|
|
|
g_at_util_debug_chat(TRUE, (char *)buf, rbytes,
|
|
|
|
io->debugf, io->debug_data);
|
|
|
|
|
|
|
|
read_count++;
|
|
|
|
|
|
|
|
total_read += rbytes;
|
|
|
|
|
|
|
|
if (rbytes > 0)
|
|
|
|
ring_buffer_write_advance(io->buf, rbytes);
|
|
|
|
|
|
|
|
} while (err == G_IO_ERROR_NONE && rbytes > 0 &&
|
|
|
|
read_count < io->max_read_attempts);
|
|
|
|
|
|
|
|
if (total_read > 0 && io->read_handler)
|
|
|
|
io->read_handler(io->buf, io->read_data);
|
|
|
|
|
|
|
|
if (cond & (G_IO_HUP | G_IO_ERR))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (read_count > 0 && rbytes == 0 && err != G_IO_ERROR_AGAIN)
|
|
|
|
return FALSE;
|
|
|
|
|
2010-04-27 19:46:54 +00:00
|
|
|
/* We're overflowing the buffer, shutdown the socket */
|
|
|
|
if (ring_buffer_avail(io->buf) == 0)
|
|
|
|
return FALSE;
|
|
|
|
|
2010-04-27 16:07:53 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2010-04-28 03:43:25 +00:00
|
|
|
gsize g_at_io_write(GAtIO *io, const gchar *data, gsize count)
|
|
|
|
{
|
|
|
|
GIOError err;
|
|
|
|
gsize bytes_written;
|
|
|
|
|
|
|
|
err = g_io_channel_write(io->channel, data, count, &bytes_written);
|
|
|
|
|
|
|
|
if (err != G_IO_ERROR_NONE) {
|
|
|
|
g_source_remove(io->read_watch);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_at_util_debug_chat(FALSE, data, bytes_written,
|
|
|
|
io->debugf, io->debug_data);
|
|
|
|
|
|
|
|
return bytes_written;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void write_watcher_destroy_notify(gpointer user_data)
|
|
|
|
{
|
|
|
|
GAtIO *io = user_data;
|
|
|
|
|
|
|
|
io->write_watch = 0;
|
|
|
|
io->write_handler = NULL;
|
|
|
|
io->write_data = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
GAtIO *io = data;
|
|
|
|
|
|
|
|
if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (io->write_handler == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return io->write_handler(io->write_data);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-04-27 16:07:53 +00:00
|
|
|
static GAtIO *create_io(GIOChannel *channel, GIOFlags flags)
|
|
|
|
{
|
|
|
|
GAtIO *io;
|
|
|
|
|
|
|
|
if (!channel)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
io = g_try_new0(GAtIO, 1);
|
|
|
|
if (!io)
|
|
|
|
return io;
|
|
|
|
|
|
|
|
io->ref_count = 1;
|
|
|
|
io->debugf = NULL;
|
|
|
|
|
2010-04-28 03:43:25 +00:00
|
|
|
if (flags & G_IO_FLAG_NONBLOCK) {
|
2010-04-27 16:07:53 +00:00
|
|
|
io->max_read_attempts = 3;
|
2010-04-28 03:43:25 +00:00
|
|
|
io->use_write_watch = TRUE;
|
|
|
|
} else {
|
2010-04-27 16:07:53 +00:00
|
|
|
io->max_read_attempts = 1;
|
2010-04-28 03:43:25 +00:00
|
|
|
io->use_write_watch = FALSE;
|
|
|
|
}
|
2010-04-27 16:07:53 +00:00
|
|
|
|
|
|
|
io->buf = ring_buffer_new(4096);
|
|
|
|
|
|
|
|
if (!io->buf)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (!g_at_util_setup_io(channel, flags))
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
io->channel = channel;
|
|
|
|
io->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, io,
|
|
|
|
read_watcher_destroy_notify);
|
|
|
|
|
|
|
|
return io;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (io->buf)
|
|
|
|
ring_buffer_free(io->buf);
|
|
|
|
|
|
|
|
g_free(io);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
GAtIO *g_at_io_new(GIOChannel *channel)
|
|
|
|
{
|
|
|
|
return create_io(channel, G_IO_FLAG_NONBLOCK);
|
|
|
|
}
|
|
|
|
|
|
|
|
GAtIO *g_at_io_new_blocking(GIOChannel *channel)
|
|
|
|
{
|
|
|
|
return create_io(channel, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
GIOChannel *g_at_io_get_channel(GAtIO *io)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return io->channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean g_at_io_set_read_handler(GAtIO *io, GAtIOReadFunc read_handler,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
io->read_handler = read_handler;
|
|
|
|
io->read_data = user_data;
|
|
|
|
|
|
|
|
if (read_handler && ring_buffer_len(io->buf) > 0)
|
|
|
|
read_handler(io->buf, user_data);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2010-04-28 03:43:25 +00:00
|
|
|
static gboolean call_blocking_read(gpointer user_data)
|
|
|
|
{
|
|
|
|
GAtIO *io = user_data;
|
|
|
|
|
|
|
|
while (can_write_data(io->channel, G_IO_OUT, io) == TRUE);
|
|
|
|
write_watcher_destroy_notify(io);
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean g_at_io_set_write_handler(GAtIO *io, GAtIOWriteFunc write_handler,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (io->write_watch > 0) {
|
|
|
|
if (write_handler == NULL) {
|
|
|
|
g_source_remove(io->write_watch);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (write_handler == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
io->write_handler = write_handler;
|
|
|
|
io->write_data = user_data;
|
|
|
|
|
|
|
|
if (io->use_write_watch == TRUE)
|
|
|
|
io->write_watch = g_io_add_watch_full(io->channel,
|
|
|
|
G_PRIORITY_DEFAULT,
|
|
|
|
G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
|
|
|
|
can_write_data, io,
|
|
|
|
write_watcher_destroy_notify);
|
|
|
|
else
|
|
|
|
io->write_watch = g_idle_add(call_blocking_read, io);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2010-04-27 16:07:53 +00:00
|
|
|
GAtIO *g_at_io_ref(GAtIO *io)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
g_atomic_int_inc(&io->ref_count);
|
|
|
|
|
|
|
|
return io;
|
|
|
|
}
|
|
|
|
|
2010-04-27 21:26:54 +00:00
|
|
|
static gboolean io_shutdown(GAtIO *io)
|
|
|
|
{
|
|
|
|
/* Don't trigger user disconnect on shutdown */
|
|
|
|
io->user_disconnect = NULL;
|
|
|
|
io->user_disconnect_data = NULL;
|
|
|
|
|
|
|
|
if (io->read_watch > 0)
|
|
|
|
g_source_remove(io->read_watch);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2010-04-27 16:07:53 +00:00
|
|
|
void g_at_io_unref(GAtIO *io)
|
|
|
|
{
|
|
|
|
gboolean is_zero;
|
|
|
|
|
|
|
|
if (io == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
is_zero = g_atomic_int_dec_and_test(&io->ref_count);
|
|
|
|
|
|
|
|
if (is_zero == FALSE)
|
|
|
|
return;
|
|
|
|
|
2010-04-27 21:26:54 +00:00
|
|
|
io_shutdown(io);
|
2010-04-27 16:07:53 +00:00
|
|
|
|
|
|
|
/* 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
|
|
|
|
*/
|
2010-04-27 19:47:38 +00:00
|
|
|
if (io->read_watch > 0)
|
2010-04-27 16:07:53 +00:00
|
|
|
io->destroyed = TRUE;
|
|
|
|
else
|
|
|
|
g_free(io);
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean g_at_io_set_disconnect_function(GAtIO *io,
|
|
|
|
GAtDisconnectFunc disconnect, gpointer user_data)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
io->user_disconnect = disconnect;
|
|
|
|
io->user_disconnect_data = user_data;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean g_at_io_set_debug(GAtIO *io, GAtDebugFunc func, gpointer user_data)
|
|
|
|
{
|
|
|
|
if (io == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
io->debugf = func;
|
|
|
|
io->debug_data = user_data;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|