/*
 *
 *  AT chat library with GLib integration
 *
 *  Copyright (C) 2008-2011  Intel Corporation. All rights reserved.
 *  Copyright (C) 2009  Trolltech ASA.
 *
 *  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 <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <alloca.h>

#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wcast-function-type"

#include <glib.h>

#include "ringbuffer.h"
#include "gatmux.h"
#include "gsm0710.h"

static const char *cmux_prefix[] = { "+CMUX:", NULL };
static const char *none_prefix[] = { NULL };

typedef struct _GAtMuxChannel GAtMuxChannel;
typedef struct _GAtMuxWatch GAtMuxWatch;
typedef void (*GAtMuxWriteFrame)(GAtMux *mux, guint8 dlc, guint8 control,
				const guint8 *data, int len);

/* While 63 channels are theoretically possible, channel 62 and 63 is reserved
 * by 27.010 for use as the beginning of frame and end of frame flags.
 * Refer to Section 5.6 in 27.007
 */
#define MAX_CHANNELS 61
#define BITMAP_SIZE 8
#define MUX_CHANNEL_BUFFER_SIZE 4096
#define MUX_BUFFER_SIZE 4096

struct _GAtMuxChannel
{
	GIOChannel channel;
	GAtMux *mux;
	GIOCondition condition;
	struct ring_buffer *buffer;
	GSList *sources;
	gboolean throttled;
	guint dlc;
};

struct _GAtMuxWatch
{
	GSource source;
	GIOChannel *channel;
	GIOCondition condition;
};

struct _GAtMux {
	gint ref_count;				/* Ref count */
	guint read_watch;			/* GSource read id, 0 if none */
	guint write_watch;			/* GSource write id, 0 if none */
	GIOChannel *channel;			/* main serial channel */
	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 */
	GAtMuxChannel *dlcs[MAX_CHANNELS];	/* DLCs opened by the MUX */
	guint8 newdata[BITMAP_SIZE];		/* Channels that got new data */
	const GAtMuxDriver *driver;		/* Driver functions */
	void *driver_data;			/* Driver data */
	char buf[MUX_BUFFER_SIZE];		/* Buffer on the main mux */
	int buf_used;				/* Bytes of buf being used */
	gboolean shutdown;
};

struct mux_setup_data {
	GAtChat *chat;
	GAtMuxSetupFunc func;
	gpointer user;
	GDestroyNotify destroy;
	guint mode;
	guint frame_size;
};

static inline void debug(GAtMux *mux, const char *format, ...)
{
	char str[256];
	va_list ap;

	if (mux->debugf == NULL)
		return;

	va_start(ap, format);

	if (vsnprintf(str, sizeof(str), format, ap) > 0)
		mux->debugf(str, mux->debug_data);

	va_end(ap);
}

static void dispatch_sources(GAtMuxChannel *channel, GIOCondition condition)
{
	GSList *c;
	GSList *p;
	GSList *refs;

	/*
	 * Don't reference destroyed sources, they may have zero reference
	 * count if this function is invoked from the source's finalize
	 * callback, in which case incrementing and then decrementing
	 * the count would result in double free (first when we decrement
	 * the reference count and then when we return from the finalize
	 * callback).
	 */

	p = NULL;
	refs = NULL;

	for (c = channel->sources; c; c = c->next) {
		GSource *s = c->data;

		if (!g_source_is_destroyed(s)) {
			GSList *l = g_slist_append(NULL, g_source_ref(s));

			if (p)
				p->next = l;
			else
				refs = l;

			p = l;
		}
	}

	/*
	 * Keep the references to all sources for the duration of the loop.
	 * Callbacks may add and remove the sources, i.e. channel->sources
	 * may keep changing during the loop.
	 */

	for (c = refs; c; c = c->next) {
		GAtMuxWatch *w = c->data;
		GSource *s = &w->source;

		if (g_source_is_destroyed(s))
			continue;

		debug(channel->mux, "checking source: %p", s);

		if (condition & w->condition) {
			gpointer user_data = NULL;
			GSourceFunc callback = NULL;
			GSourceCallbackFuncs *cb_funcs = s->callback_funcs;
			gpointer cb_data = s->callback_data;
			gboolean destroy;

			debug(channel->mux, "dispatching source: %p", s);

			if (cb_funcs) {
				cb_funcs->ref(cb_data);
				cb_funcs->get(cb_data, s, &callback,
								&user_data);
			}

			destroy = !s->source_funcs->dispatch(s, callback,
								user_data);

			if (cb_funcs)
				cb_funcs->unref(cb_data);

			if (destroy) {
				debug(channel->mux, "removing source: %p", s);
				g_source_destroy(s);
			}
		}
	}

	/*
	 * Remove destroyed sources from channel->sources. During this
	 * loop we are not invoking any callbacks, so the consistency is
	 * guaranteed.
	 */

	p = NULL;
	c = channel->sources;

	while (c) {
		GSList *n = c->next;
		GSource *s = c->data;

		if (g_source_is_destroyed(s)) {
			if (p)
				p->next = n;
			else
				channel->sources = n;

			g_slist_free_1(c);
		} else {
			p = c;
		}

		c = n;
	}

	/* Release temporary references */
	g_slist_free_full(refs, (GDestroyNotify) g_source_unref);
}

static gboolean received_data(GIOChannel *channel, GIOCondition cond,
							gpointer data)
{
	GAtMux *mux = data;
	int i;
	GIOStatus status;
	gsize bytes_read;
	gboolean buffer_full = FALSE;

	if (cond & G_IO_NVAL)
		return FALSE;

	debug(mux, "received data");

	bytes_read = 0;
	status = g_io_channel_read_chars(mux->channel, mux->buf + mux->buf_used,
					sizeof(mux->buf) - mux->buf_used,
					&bytes_read, NULL);

	mux->buf_used += bytes_read;

	if (bytes_read > 0 && mux->driver->feed_data) {
		int nread;

		memset(mux->newdata, 0, BITMAP_SIZE);

		nread = mux->driver->feed_data(mux, mux->buf, mux->buf_used);
		mux->buf_used -= nread;

		if (mux->buf_used > 0)
			memmove(mux->buf, mux->buf + nread, mux->buf_used);

		g_at_mux_ref(mux);

		for (i = 1; i <= MAX_CHANNELS; i++) {
			int offset = i / 8;
			int bit = i % 8;

			if (!(mux->newdata[offset] & (1 << bit)))
				continue;

			debug(mux, "dispatching sources for channel: %p",
				mux->dlcs[i-1]);

			dispatch_sources(mux->dlcs[i-1], G_IO_IN);
		}

		buffer_full = mux->buf_used == sizeof(mux->buf);

		g_at_mux_unref(mux);
	}

	if (cond & (G_IO_HUP | G_IO_ERR))
		return FALSE;

	if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN)
		return FALSE;

	if (buffer_full)
		return FALSE;

	return TRUE;
}

static void write_watcher_destroy_notify(gpointer user_data)
{
	GAtMux *mux = user_data;

	mux->write_watch = 0;
}

static gboolean can_write_data(GIOChannel *chan, GIOCondition cond,
				gpointer data)
{
	GAtMux *mux = data;
	int dlc;

	if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
		return FALSE;

	debug(mux, "can write data");

	for (dlc = 0; dlc < MAX_CHANNELS; dlc += 1) {
		GAtMuxChannel *channel = mux->dlcs[dlc];

		if (channel == NULL)
			continue;

		debug(mux, "checking channel for write: %p", channel);

		if (channel->throttled)
			continue;

		debug(mux, "dispatching write sources: %p", channel);

		dispatch_sources(channel, G_IO_OUT);
	}

	for (dlc = 0; dlc < MAX_CHANNELS; dlc += 1) {
		GAtMuxChannel *channel = mux->dlcs[dlc];
		GSList *l;
		GAtMuxWatch *source;

		if (channel == NULL)
			continue;

		if (channel->throttled)
			continue;

		for (l = channel->sources; l; l = l->next) {
			source = l->data;

			if (source->condition & G_IO_OUT)
				return TRUE;
		}
	}

	return FALSE;
}

static void wakeup_writer(GAtMux *mux)
{
	if (mux->write_watch != 0)
		return;

	debug(mux, "waking up writer");

	mux->write_watch = g_io_add_watch_full(mux->channel,
				G_PRIORITY_DEFAULT,
				G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
				can_write_data, mux,
				write_watcher_destroy_notify);
}

int g_at_mux_raw_write(GAtMux *mux, const void *data, int towrite)
{
	gssize count = towrite;
	gsize bytes_written;

	g_io_channel_write_chars(mux->channel, (gchar *) data,
					count, &bytes_written, NULL);

	return bytes_written;
}

void g_at_mux_feed_dlc_data(GAtMux *mux, guint8 dlc,
				const void *data, int tofeed)
{
	GAtMuxChannel *channel;

	int written;
	int offset;
	int bit;

	debug(mux, "deliver_data: dlc: %hu", dlc);

	if (dlc < 1 || dlc > MAX_CHANNELS)
		return;

	channel = mux->dlcs[dlc-1];

	if (channel == NULL)
		return;

	written = ring_buffer_write(channel->buffer, data, tofeed);

	if (written < 0)
		return;

	offset = dlc / 8;
	bit = dlc % 8;

	mux->newdata[offset] |= 1 << bit;
	channel->condition |= G_IO_IN;
}

void g_at_mux_set_dlc_status(GAtMux *mux, guint8 dlc, int status)
{
	GAtMuxChannel *channel;

	debug(mux, "got status %d, for channel %hu", status, dlc);

	if (dlc < 1 || dlc > MAX_CHANNELS)
		return;

	channel = mux->dlcs[dlc-1];
	if (channel == NULL)
		return;

	if (status & G_AT_MUX_DLC_STATUS_RTR) {
		GSList *l;

		mux->dlcs[dlc-1]->throttled = FALSE;
		debug(mux, "setting throttled to FALSE");

		for (l = mux->dlcs[dlc-1]->sources; l; l = l->next) {
			GAtMuxWatch *source = l->data;

			if (source->condition & G_IO_OUT) {
				wakeup_writer(mux);
				break;
			}
		}
	} else
		mux->dlcs[dlc-1]->throttled = TRUE;
}

void g_at_mux_set_data(GAtMux *mux, void *data)
{
	if (mux == NULL)
		return;

	mux->driver_data = data;
}

void *g_at_mux_get_data(GAtMux *mux)
{
	if (mux == NULL)
		return NULL;

	return mux->driver_data;
}

static gboolean watch_check(GSource *source)
{
	return FALSE;
}

static gboolean watch_prepare(GSource *source, gint *timeout)
{
	*timeout = -1;
	return FALSE;
}

static gboolean watch_dispatch(GSource *source, GSourceFunc callback,
							gpointer user_data)
{
	GIOFunc func = (GIOFunc) callback;
	GAtMuxWatch *watch = (GAtMuxWatch *) source;
	GAtMuxChannel *channel = (GAtMuxChannel *) watch->channel;

	if (func == NULL)
		return FALSE;

	return func(watch->channel, channel->condition & watch->condition,
								user_data);
}

static void watch_finalize(GSource *source)
{
	GAtMuxWatch *watch = (GAtMuxWatch *) source;
	GAtMuxChannel *dlc = (GAtMuxChannel *) watch->channel;

	dlc->sources = g_slist_remove(dlc->sources, watch);
	g_io_channel_unref(watch->channel);
}

static GSourceFuncs watch_funcs = {
	watch_prepare,
	watch_check,
	watch_dispatch,
	watch_finalize
};

static GIOStatus channel_read(GIOChannel *channel, gchar *buf, gsize count,
					gsize *bytes_read, GError **err)
{
	GAtMuxChannel *mux_channel = (GAtMuxChannel *) channel;
	unsigned int avail = ring_buffer_len_no_wrap(mux_channel->buffer);

	if (avail > count)
		avail = count;

	*bytes_read = ring_buffer_read(mux_channel->buffer, buf, avail);

	if (*bytes_read == 0)
		return G_IO_STATUS_AGAIN;

	return G_IO_STATUS_NORMAL;
}

static GIOStatus channel_write(GIOChannel *channel, const gchar *buf,
				gsize count, gsize *bytes_written, GError **err)
{
	GAtMuxChannel *mux_channel = (GAtMuxChannel *) channel;
	GAtMux *mux = mux_channel->mux;

	if (mux->driver->write)
		mux->driver->write(mux, mux_channel->dlc, buf, count);
	*bytes_written = count;

	return G_IO_STATUS_NORMAL;
}

static GIOStatus channel_seek(GIOChannel *channel, gint64 offset,
						GSeekType type, GError **err)
{
	return G_IO_STATUS_NORMAL;
}

static GIOStatus channel_close(GIOChannel *channel, GError **err)
{
	GAtMuxChannel *mux_channel = (GAtMuxChannel *) channel;
	GAtMux *mux = mux_channel->mux;

	debug(mux, "closing channel: %d", mux_channel->dlc);

	dispatch_sources(mux_channel, G_IO_NVAL);

	if (mux->driver->close_dlc)
		mux->driver->close_dlc(mux, mux_channel->dlc);

	mux->dlcs[mux_channel->dlc - 1] = NULL;

	return G_IO_STATUS_NORMAL;
}

static void channel_free(GIOChannel *channel)
{
	GAtMuxChannel *mux_channel = (GAtMuxChannel *) channel;

	ring_buffer_free(mux_channel->buffer);

	g_free(channel);
}

static GSource *channel_create_watch(GIOChannel *channel,
						GIOCondition condition)
{
	GSource *source;
	GAtMuxWatch *watch;
	GAtMuxChannel *dlc = (GAtMuxChannel *) channel;
	GAtMux *mux = dlc->mux;

	source = g_source_new(&watch_funcs, sizeof(GAtMuxWatch));
	watch = (GAtMuxWatch *) source;

	watch->channel = channel;
	g_io_channel_ref(channel);

	watch->condition = condition;

	if ((watch->condition & G_IO_OUT) && dlc->throttled == FALSE)
		wakeup_writer(mux);

	debug(mux, "creating source: %p, channel: %p, writer: %d, reader: %d",
			watch, channel,
			condition & G_IO_OUT,
			condition & G_IO_IN);

	dlc->sources = g_slist_prepend(dlc->sources, watch);

	return source;
}

static GIOStatus channel_set_flags(GIOChannel *channel, GIOFlags flags,
								GError **err)
{
	return G_IO_STATUS_NORMAL;
}

static GIOFlags channel_get_flags(GIOChannel *channel)
{
	GIOFlags flags = 0;

	return flags;
}

static GIOFuncs channel_funcs = {
	channel_read,
	channel_write,
	channel_seek,
	channel_close,
	channel_create_watch,
	channel_free,
	channel_set_flags,
	channel_get_flags,
};

GAtMux *g_at_mux_new(GIOChannel *channel, const GAtMuxDriver *driver)
{
	GAtMux *mux;

	if (channel == NULL)
		return NULL;

	mux = g_try_new0(GAtMux, 1);
	if (mux == NULL)
		return NULL;

	mux->ref_count = 1;
	mux->driver = driver;
	mux->shutdown = TRUE;

	mux->channel = channel;
	g_io_channel_ref(channel);

	g_io_channel_set_close_on_unref(channel, TRUE);

	return mux;
}

GAtMux *g_at_mux_ref(GAtMux *mux)
{
	if (mux == NULL)
		return NULL;

	g_atomic_int_inc(&mux->ref_count);

	return mux;
}

void g_at_mux_unref(GAtMux *mux)
{
	if (mux == NULL)
		return;

	if (g_atomic_int_dec_and_test(&mux->ref_count)) {
		g_at_mux_shutdown(mux);

		g_io_channel_unref(mux->channel);

		if (mux->driver->remove)
			mux->driver->remove(mux);

		g_free(mux);
	}
}

gboolean g_at_mux_start(GAtMux *mux)
{
	if (mux->channel == NULL)
		return FALSE;

	if (mux->driver->startup == NULL)
		return FALSE;

	if (mux->driver->startup(mux) == FALSE)
		return FALSE;

	mux->read_watch = g_io_add_watch_full(mux->channel, G_PRIORITY_DEFAULT,
				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
						received_data, mux, NULL);

	mux->shutdown = FALSE;

	return TRUE;
}

gboolean g_at_mux_shutdown(GAtMux *mux)
{
	int i;

	if (mux->shutdown == TRUE)
		return FALSE;

	if (mux->channel == NULL)
		return FALSE;

	if (mux->read_watch > 0) {
		g_source_remove(mux->read_watch);
		mux->read_watch = 0;
	}

	if (mux->write_watch > 0)
		g_source_remove(mux->write_watch);

	for (i = 0; i < MAX_CHANNELS; i++) {
		if (mux->dlcs[i] == NULL)
			continue;

		channel_close((GIOChannel *) mux->dlcs[i], NULL);
	}

	if (mux->driver->shutdown)
		mux->driver->shutdown(mux);

	mux->shutdown = TRUE;

	return TRUE;
}

gboolean g_at_mux_set_disconnect_function(GAtMux *mux,
			GAtDisconnectFunc disconnect, gpointer user_data)
{
	if (mux == NULL)
		return FALSE;

	mux->user_disconnect = disconnect;
	mux->user_disconnect_data = user_data;

	return TRUE;
}

gboolean g_at_mux_set_debug(GAtMux *mux, GAtDebugFunc func, gpointer user_data)
{
	if (mux == NULL)
		return FALSE;

	mux->debugf = func;
	mux->debug_data = user_data;

	return TRUE;
}

GIOChannel *g_at_mux_create_channel(GAtMux *mux)
{
	GAtMuxChannel *mux_channel;
	GIOChannel *channel;
	int i;

	for (i = 0; i < MAX_CHANNELS; i++) {
		if (mux->dlcs[i] == NULL)
			break;
	}

	if (i == MAX_CHANNELS)
		return NULL;

	mux_channel = g_try_new0(GAtMuxChannel, 1);
	if (mux_channel == NULL)
		return NULL;

	if (mux->driver->open_dlc)
		mux->driver->open_dlc(mux, i+1);

	channel = (GIOChannel *) mux_channel;

	g_io_channel_init(channel);
	channel->close_on_unref = TRUE;
	channel->funcs = &channel_funcs;

	channel->is_seekable = FALSE;
	channel->is_readable = TRUE;
	channel->is_writeable = TRUE;

	channel->do_encode = FALSE;

	mux_channel->mux = mux;
	mux_channel->dlc = i+1;
	mux_channel->buffer = ring_buffer_new(MUX_CHANNEL_BUFFER_SIZE);
	mux_channel->throttled = FALSE;

	mux->dlcs[i] = mux_channel;

	debug(mux, "created channel %p, dlc: %d", channel, i+1);

	return channel;
}

static void msd_free(gpointer user_data)
{
	struct mux_setup_data *msd = user_data;

	if (msd->chat)
		g_at_chat_unref(msd->chat);

	g_free(msd);
}

static void mux_setup_cb(gboolean ok, GAtResult *result, gpointer user_data)
{
	struct mux_setup_data *msd = user_data;
	GIOFlags flags;
	GIOChannel *channel;
	GAtMux *mux = NULL;

	if (!ok)
		goto error;

	channel = g_at_chat_get_channel(msd->chat);
	channel = g_io_channel_ref(channel);

	g_at_chat_unref(msd->chat);
	msd->chat = NULL;

	flags = g_io_channel_get_flags(channel) | G_IO_FLAG_NONBLOCK;
	g_io_channel_set_flags(channel, flags, NULL);

	g_io_channel_set_encoding(channel, NULL, NULL);
	g_io_channel_set_buffered(channel, FALSE);

	if (msd->mode == 0)
		mux = g_at_mux_new_gsm0710_basic(channel, msd->frame_size);
	else
		mux = g_at_mux_new_gsm0710_advanced(channel, msd->frame_size);

	g_io_channel_unref(channel);

error:
	msd->func(mux, msd->user);

	if (msd->destroy)
		msd->destroy(msd->user);
}

static void mux_query_cb(gboolean ok, GAtResult *result, gpointer user_data)
{
	struct mux_setup_data *msd = user_data;
	struct mux_setup_data *nmsd;
	GAtResultIter iter;
	int min, max;
	int speed;
	char buf[64];

	/* CMUX query not supported, abort */
	if (!ok)
		goto error;

	g_at_result_iter_init(&iter, result);

	if (!g_at_result_iter_next(&iter, "+CMUX:"))
		goto error;

	/* Mode */
	if (!g_at_result_iter_open_list(&iter))
		goto error;

	if (!g_at_result_iter_next_range(&iter, &min, &max))
		goto error;

	if (!g_at_result_iter_close_list(&iter))
		goto error;

	if (min <= 1 && 1 <= max)
		msd->mode = 1;
	else if (min <= 0 && 0 <= max)
		msd->mode = 0;
	else
		goto error;

	/* Subset */
	if (!g_at_result_iter_open_list(&iter))
		goto error;

	if (!g_at_result_iter_next_range(&iter, &min, &max))
		goto error;

	if (!g_at_result_iter_close_list(&iter))
		goto error;

	if (min > 0)
		goto error;

	/* Speed, pick highest */
	if (g_at_result_iter_open_list(&iter)) {
		if (!g_at_result_iter_next_range(&iter, &min, &max))
			goto error;

		if (!g_at_result_iter_close_list(&iter))
			goto error;

		speed = max;
	} else {
		if (!g_at_result_iter_skip_next(&iter))
			goto error;

		/* not available/used */
		speed = -1;
	}

	/* Frame size, pick defaults */
	if (!g_at_result_iter_open_list(&iter))
		goto error;

	if (!g_at_result_iter_next_range(&iter, &min, &max))
		goto error;

	if (!g_at_result_iter_close_list(&iter))
		goto error;

	if (msd->mode == 0) {
		if (min > 31 || max < 31)
			goto error;

		msd->frame_size = 31;
	} else if (msd->mode == 1) {
		if (min > 64 || max < 64)
			goto error;

		msd->frame_size = 64;
	} else
		goto error;

	nmsd = g_memdup(msd, sizeof(struct mux_setup_data));
	g_at_chat_ref(nmsd->chat);

	if (speed < 0)
		sprintf(buf, "AT+CMUX=%u,0,,%u", msd->mode, msd->frame_size);
	else
		sprintf(buf, "AT+CMUX=%u,0,%u,%u", msd->mode, speed,
							msd->frame_size);

	if (g_at_chat_send(msd->chat, buf, none_prefix,
				mux_setup_cb, nmsd, msd_free) > 0)
		return;

	msd_free(nmsd);

error:
	msd->func(NULL, msd->user);

	if (msd->destroy)
		msd->destroy(msd->user);
}

gboolean g_at_mux_setup_gsm0710(GAtChat *chat,
				GAtMuxSetupFunc notify, gpointer user_data,
				GDestroyNotify destroy)
{
	struct mux_setup_data *msd;

	if (chat == NULL)
		return FALSE;

	if (notify == NULL)
		return FALSE;

	msd = g_new0(struct mux_setup_data, 1);

	msd->chat = g_at_chat_ref(chat);
	msd->func = notify;
	msd->user = user_data;
	msd->destroy = destroy;

	if (g_at_chat_send(chat, "AT+CMUX=?", cmux_prefix,
				mux_query_cb, msd, msd_free) > 0)
		return TRUE;

	msd_free(msd);

	return FALSE;
}

#define GSM0710_BUFFER_SIZE 4096

struct gsm0710_data {
	int frame_size;
};

/* Process an incoming GSM 07.10 packet */
static gboolean gsm0710_packet(GAtMux *mux, int dlc, guint8 control,
				const unsigned char *data, int len,
				GAtMuxWriteFrame write_frame)
{
	if (control == 0xEF || control == 0x03) {
		if (dlc >= 1 && dlc <= 63) {
			g_at_mux_feed_dlc_data(mux, dlc, data, len);
			return TRUE;
		}

		if (dlc == 0) {
			/* An embedded command or response on channel 0 */
			if (len >= 2 && data[0] == GSM0710_STATUS_SET) {
				return gsm0710_packet(mux, dlc,
							GSM0710_STATUS_ACK,
							data + 2, len - 2,
							write_frame);
			} else if (len >= 2 && data[0] == 0x43) {
				/* Test command from other side - send the same bytes back */
				unsigned char *resp = alloca(len);
				memcpy(resp, data, len);
				resp[0] = 0x41;	/* Clear the C/R bit in the response */
				write_frame(mux, 0, GSM0710_DATA, resp, len);
			}
		}
	} else if (control == GSM0710_STATUS_ACK && dlc == 0) {
		unsigned char resp[33];

		/* Status change message */
		if (len >= 2) {
			/* Handle status changes on other channels */
			dlc = ((data[0] & 0xFC) >> 2);

			if (dlc >= 1 && dlc <= 63)
				g_at_mux_set_dlc_status(mux, dlc, data[1]);
		}

		/* Send the response to the status change request to ACK it */
		debug(mux, "received status line signal, sending response");
		if (len > 31)
			len = 31;
		resp[0] = GSM0710_STATUS_ACK;
		resp[1] = ((len << 1) | 0x01);
		memcpy(resp + 2, data, len);
		write_frame(mux, 0, GSM0710_DATA, resp, len + 2);
	}

	return TRUE;
}

static void gsm0710_basic_write_frame(GAtMux *mux, guint8 dlc, guint8 control,
					const guint8 *data, int towrite)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size + 7);
	int frame_size;

	frame_size = gsm0710_basic_fill_frame(frame, dlc, control,
						data, towrite);
	g_at_mux_raw_write(mux, frame, frame_size);
}

#define COMPOSE_STATUS_FRAME(data, dlc, status)	\
	guint8 data[4];				\
	data[0] = GSM0710_STATUS_SET;		\
	data[1] = 0x03;				\
	data[2] = ((dlc << 2) | 0x03);		\
	data[3] = status

static void gsm0710_basic_remove(GAtMux *mux)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);

	g_free(gd);
	g_at_mux_set_data(mux, NULL);
}

static gboolean gsm0710_basic_startup(GAtMux *mux)
{
	guint8 frame[6];
	int frame_size;

	frame_size = gsm0710_basic_fill_frame(frame, 0, GSM0710_OPEN_CHANNEL,
						NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_basic_shutdown(GAtMux *mux)
{
	guint8 frame[6];
	int frame_size;

	frame_size = gsm0710_basic_fill_frame(frame, 0, GSM0710_CLOSE_CHANNEL,
						NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_basic_open_dlc(GAtMux *mux, guint8 dlc)
{
	guint8 frame[6];
	int frame_size;

	frame_size = gsm0710_basic_fill_frame(frame, dlc, GSM0710_OPEN_CHANNEL,
						NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_basic_close_dlc(GAtMux *mux, guint8 dlc)
{
	guint8 frame[6];
	int frame_size;

	frame_size = gsm0710_basic_fill_frame(frame, dlc, GSM0710_CLOSE_CHANNEL,
						NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static int gsm0710_basic_feed_data(GAtMux *mux, void *data, int len)
{
	int total = 0;
	int nread;
	guint8 dlc;
	guint8 ctrl;
	guint8 *frame;
	int frame_len;

	do {
		frame = NULL;
		nread = gsm0710_basic_extract_frame(data, len, &dlc, &ctrl,
							&frame, &frame_len);

		total += nread;
		data += nread;
		len -= nread;

		if (frame == NULL)
			break;

		gsm0710_packet(mux, dlc, ctrl, frame, frame_len,
				gsm0710_basic_write_frame);
	} while (nread > 0);

	return total;
}

static void gsm0710_basic_set_status(GAtMux *mux, guint8 dlc, guint8 status)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size + 7);
	int frame_size;

	COMPOSE_STATUS_FRAME(data, dlc, status);
	frame_size = gsm0710_basic_fill_frame(frame, 0, GSM0710_DATA, data, 4);
	g_at_mux_raw_write(mux, frame, frame_size);
}

static void gsm0710_basic_write(GAtMux *mux, guint8 dlc,
				const void *data, int towrite)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size + 7);
	int max;
	int frame_size;

	while (towrite > 0) {
		max = MIN(towrite, gd->frame_size);
		frame_size = gsm0710_basic_fill_frame(frame, dlc,
						GSM0710_DATA, data, max);
		g_at_mux_raw_write(mux, frame, frame_size);
		data = data + max;
		towrite -= max;
	}
}

static GAtMuxDriver gsm0710_basic_driver = {
	.remove = gsm0710_basic_remove,
	.startup = gsm0710_basic_startup,
	.shutdown = gsm0710_basic_shutdown,
	.open_dlc = gsm0710_basic_open_dlc,
	.close_dlc = gsm0710_basic_close_dlc,
	.feed_data = gsm0710_basic_feed_data,
	.set_status = gsm0710_basic_set_status,
	.write = gsm0710_basic_write,
};

GAtMux *g_at_mux_new_gsm0710_basic(GIOChannel *channel, int frame_size)
{
	GAtMux *mux;
	struct gsm0710_data *gd;

	mux = g_at_mux_new(channel, &gsm0710_basic_driver);

	if (mux == NULL)
		return NULL;

	gd = g_new0(struct gsm0710_data, 1);
	gd->frame_size = frame_size;

	g_at_mux_set_data(mux, gd);

	return mux;
}

static void gsm0710_advanced_write_frame(GAtMux *mux, guint8 dlc, guint8 control,
					const guint8 *data, int towrite)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size * 2 + 7);
	int frame_size;

	frame_size = gsm0710_advanced_fill_frame(frame, dlc, control,
							data, towrite);
	g_at_mux_raw_write(mux, frame, frame_size);
}

static void gsm0710_advanced_remove(GAtMux *mux)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);

	g_free(gd);
	g_at_mux_set_data(mux, NULL);
}

static gboolean gsm0710_advanced_startup(GAtMux *mux)
{
	guint8 frame[8]; /* Account for escapes */
	int frame_size;

	frame_size = gsm0710_advanced_fill_frame(frame, 0,
						GSM0710_OPEN_CHANNEL, NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_advanced_shutdown(GAtMux *mux)
{
	guint8 frame[8]; /* Account for escapes */
	int frame_size;

	frame_size = gsm0710_advanced_fill_frame(frame, 0,
						GSM0710_CLOSE_CHANNEL, NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_advanced_open_dlc(GAtMux *mux, guint8 dlc)
{
	guint8 frame[8]; /* Account for escapes */
	int frame_size;

	frame_size = gsm0710_advanced_fill_frame(frame, dlc,
						GSM0710_OPEN_CHANNEL, NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static gboolean gsm0710_advanced_close_dlc(GAtMux *mux, guint8 dlc)
{
	guint8 frame[8]; /* Account for escapes */
	int frame_size;

	frame_size = gsm0710_advanced_fill_frame(frame, dlc,
						GSM0710_CLOSE_CHANNEL, NULL, 0);
	g_at_mux_raw_write(mux, frame, frame_size);

	return TRUE;
}

static int gsm0710_advanced_feed_data(GAtMux *mux, void *data, int len)
{
	int total = 0;
	int nread;
	guint8 dlc;
	guint8 ctrl;
	guint8 *frame;
	int frame_len;

	do {
		frame = NULL;
		nread = gsm0710_advanced_extract_frame(data, len, &dlc, &ctrl,
							&frame, &frame_len);

		total += nread;
		data += nread;
		len -= nread;

		if (frame == NULL)
			break;

		gsm0710_packet(mux, dlc, ctrl, frame, frame_len,
				gsm0710_advanced_write_frame);
	} while (nread > 0);

	return total;
}

static void gsm0710_advanced_set_status(GAtMux *mux, guint8 dlc, guint8 status)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size * 2 + 7);
	int frame_size;

	COMPOSE_STATUS_FRAME(data, dlc, status);
	frame_size = gsm0710_advanced_fill_frame(frame, 0,
							GSM0710_DATA, data, 4);
	g_at_mux_raw_write(mux, frame, frame_size);
}

static void gsm0710_advanced_write(GAtMux *mux, guint8 dlc,
					const void *data, int towrite)
{
	struct gsm0710_data *gd = g_at_mux_get_data(mux);
	guint8 *frame = alloca(gd->frame_size * 2 + 7);
	int max;
	int frame_size;

	while (towrite > 0) {
		max = MIN(towrite, gd->frame_size);
		frame_size = gsm0710_advanced_fill_frame(frame, dlc,
						GSM0710_DATA, data, max);
		g_at_mux_raw_write(mux, frame, frame_size);
		data = data + max;
		towrite -= max;
	}
}

static GAtMuxDriver gsm0710_advanced_driver = {
	.remove = gsm0710_advanced_remove,
	.startup = gsm0710_advanced_startup,
	.shutdown = gsm0710_advanced_shutdown,
	.open_dlc = gsm0710_advanced_open_dlc,
	.close_dlc = gsm0710_advanced_close_dlc,
	.feed_data = gsm0710_advanced_feed_data,
	.set_status = gsm0710_advanced_set_status,
	.write = gsm0710_advanced_write,
};

GAtMux *g_at_mux_new_gsm0710_advanced(GIOChannel *channel, int frame_size)
{
	GAtMux *mux;
	struct gsm0710_data *gd;

	mux = g_at_mux_new(channel, &gsm0710_advanced_driver);

	if (mux == NULL)
		return NULL;

	gd = g_new0(struct gsm0710_data, 1);
	gd->frame_size = frame_size;

	g_at_mux_set_data(mux, gd);

	return mux;
}
