395 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *
 | 
						|
 *  AT chat library with GLib integration
 | 
						|
 *
 | 
						|
 *  Copyright (C) 2008-2011  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 */
 | 
						|
	guint write_watch;			/* GSource write id, 0 if no */
 | 
						|
	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 */
 | 
						|
	gboolean use_write_watch;		/* Use write select */
 | 
						|
	GAtIOWriteFunc write_handler;		/* Write callback */
 | 
						|
	gpointer write_data;			/* Write callback userdata */
 | 
						|
	GAtDebugFunc debugf;			/* debugging output function */
 | 
						|
	gpointer debug_data;			/* Data to pass to debug func */
 | 
						|
	GAtDisconnectFunc write_done_func;	/* tx empty notifier */
 | 
						|
	gpointer write_done_data;		/* tx empty data */
 | 
						|
	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;
 | 
						|
 | 
						|
	io->debugf = NULL;
 | 
						|
	io->debug_data = NULL;
 | 
						|
 | 
						|
	io->read_watch = 0;
 | 
						|
	io->read_handler = NULL;
 | 
						|
	io->read_data = NULL;
 | 
						|
 | 
						|
	io->channel = NULL;
 | 
						|
 | 
						|
	if (io->destroyed)
 | 
						|
		g_free(io);
 | 
						|
	else if (io->user_disconnect)
 | 
						|
		io->user_disconnect(io->user_disconnect_data);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean received_data(GIOChannel *channel, GIOCondition cond,
 | 
						|
				gpointer data)
 | 
						|
{
 | 
						|
	unsigned char *buf;
 | 
						|
	GAtIO *io = data;
 | 
						|
	GIOStatus status;
 | 
						|
	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);
 | 
						|
 | 
						|
		status = g_io_channel_read_chars(channel, (char *) buf,
 | 
						|
							toread, &rbytes, NULL);
 | 
						|
		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 (status == G_IO_STATUS_NORMAL && 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 && status != G_IO_STATUS_AGAIN)
 | 
						|
		return FALSE;
 | 
						|
 | 
						|
	/* We're overflowing the buffer, shutdown the socket */
 | 
						|
	if (ring_buffer_avail(io->buf) == 0)
 | 
						|
		return FALSE;
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
gsize g_at_io_write(GAtIO *io, const gchar *data, gsize count)
 | 
						|
{
 | 
						|
	GIOStatus status;
 | 
						|
	gsize bytes_written;
 | 
						|
 | 
						|
	status = g_io_channel_write_chars(io->channel, data,
 | 
						|
						count, &bytes_written, NULL);
 | 
						|
 | 
						|
	if (status != G_IO_STATUS_NORMAL) {
 | 
						|
		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;
 | 
						|
 | 
						|
	if (io->write_done_func) {
 | 
						|
		io->write_done_func(io->write_done_data);
 | 
						|
		io->write_done_func = NULL;
 | 
						|
		io->write_done_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);
 | 
						|
}
 | 
						|
 | 
						|
static GAtIO *create_io(GIOChannel *channel, GIOFlags flags)
 | 
						|
{
 | 
						|
	GAtIO *io;
 | 
						|
 | 
						|
	if (channel == NULL)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	io = g_try_new0(GAtIO, 1);
 | 
						|
	if (io == NULL)
 | 
						|
		return io;
 | 
						|
 | 
						|
	io->ref_count = 1;
 | 
						|
	io->debugf = NULL;
 | 
						|
 | 
						|
	if (flags & G_IO_FLAG_NONBLOCK) {
 | 
						|
		io->max_read_attempts = 3;
 | 
						|
		io->use_write_watch = TRUE;
 | 
						|
	} else {
 | 
						|
		io->max_read_attempts = 1;
 | 
						|
		io->use_write_watch = FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	io->buf = ring_buffer_new(8192);
 | 
						|
 | 
						|
	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;
 | 
						|
}
 | 
						|
 | 
						|
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_HIGH,
 | 
						|
				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;
 | 
						|
}
 | 
						|
 | 
						|
GAtIO *g_at_io_ref(GAtIO *io)
 | 
						|
{
 | 
						|
	if (io == NULL)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	g_atomic_int_inc(&io->ref_count);
 | 
						|
 | 
						|
	return io;
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
	if (io->write_watch > 0)
 | 
						|
		g_source_remove(io->write_watch);
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
	io_shutdown(io);
 | 
						|
 | 
						|
	/* 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 (io->read_watch > 0)
 | 
						|
		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;
 | 
						|
}
 | 
						|
 | 
						|
void g_at_io_set_write_done(GAtIO *io, GAtDisconnectFunc func,
 | 
						|
				gpointer user_data)
 | 
						|
{
 | 
						|
	if (io == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	io->write_done_func = func;
 | 
						|
	io->write_done_data = user_data;
 | 
						|
}
 | 
						|
 | 
						|
void g_at_io_drain_ring_buffer(GAtIO *io, guint len)
 | 
						|
{
 | 
						|
	ring_buffer_drain(io->buf, len);
 | 
						|
}
 |