/*
 *
 *  oFono - Open Source Telephony
 *
 *  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 <unistd.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <errno.h>

#include <glib.h>
#include <utmp.h>
#include <pty.h>

#include <sys/types.h>
#include <sys/stat.h>

#include "gatserver.h"
#include "gatppp.h"
#include "ringbuffer.h"

#define DEFAULT_TCP_PORT 12346
#define DEFAULT_SOCK_PATH "./server_sock"
#define IFCONFIG_PATH "/sbin/ifconfig"

static gboolean data_mode = FALSE;
static int modem_mode = 0;
static int modem_creg = 0;
static int modem_cgreg = 0;
static int network_status = 4;
static int network_attach = 0;

struct sock_server{
	int server_sock;
};

static GMainLoop *mainloop;
static GAtServer *main_server;
static GAtPPP *ppp;
unsigned int server_watch;

static gboolean server_cleanup(void)
{
	if (server_watch)
		g_source_remove(server_watch);

	if (ppp) {
		g_at_ppp_unref(ppp);
		ppp = NULL;
	}

	g_at_server_unref(main_server);
	main_server = NULL;

	unlink(DEFAULT_SOCK_PATH);

	g_main_loop_quit(mainloop);

	return FALSE;
}

static void server_debug(const char *str, void *data)
{
	g_print("%s: %s\n", (char *) data, str);
}

static gboolean execute(const char *cmd)
{
	int status;

	status = system(cmd);
	if (status < 0) {
		g_print("Failed to execute command: %s\n", strerror(errno));
		return FALSE;
	}

	return TRUE;
}

static void ppp_connect(const char *iface, const char *local, const char *peer,
			const char *dns1, const char *dns2,
			gpointer user)
{
	char buf[512];

	g_print("Network Device: %s\n", iface);
	g_print("IP Address: %s\n", local);
	g_print("Peer IP Address: %s\n", peer);
	g_print("Primary DNS Server: %s\n", dns1);
	g_print("Secondary DNS Server: %s\n", dns2);

	snprintf(buf, sizeof(buf), "%s %s up", IFCONFIG_PATH, iface);
	execute(buf);

	snprintf(buf, sizeof(buf), "%s %s %s pointopoint %s", IFCONFIG_PATH,
				iface, local, peer);
	execute(buf);

	snprintf(buf, sizeof(buf), "echo 1 > /proc/sys/net/ipv4/ip_forward");
	execute(buf);
}

static void ppp_disconnect(GAtPPPDisconnectReason reason, gpointer user)
{
	GAtServer *server = user;

	g_print("PPP Link down: %d\n", reason);

	g_at_ppp_unref(ppp);
	ppp = NULL;

	if (reason == G_AT_PPP_REASON_LINK_DEAD) {
		g_at_server_unref(server);
		server = NULL;
		return;
	}

	g_at_server_resume(server);
	g_at_server_set_debug(server, server_debug, "Server");

	g_at_server_send_final(server, G_AT_SERVER_RESULT_NO_CARRIER);
	data_mode = FALSE;
}

static void open_ppp(gpointer user)
{
	GAtIO *io = g_at_server_get_io(main_server);

	g_at_server_suspend(main_server);
	g_at_ppp_listen(ppp, io);
}

static gboolean setup_ppp(GAtServer *server)
{
	/* open ppp */
	ppp = g_at_ppp_server_new("192.168.1.1");
	if (ppp == NULL)
		return FALSE;

	g_at_ppp_set_debug(ppp, server_debug, "PPP");

	g_at_ppp_set_credentials(ppp, "", "");

	/* set connect and disconnect callbacks */
	g_at_ppp_set_connect_function(ppp, ppp_connect, server);
	g_at_ppp_set_disconnect_function(ppp, ppp_disconnect, server);
	g_at_ppp_set_server_info(ppp, "192.168.1.2",
					"10.10.10.10", "10.10.10.11");

	return TRUE;
}

static void cgmi_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		g_at_server_send_info(server, "oFono", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static void cgmm_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		g_at_server_send_info(server, "oFono pre-1.0", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static void cgmr_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[256];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		sprintf(buf, "oFono pre-1.0 version: %s", VERSION);
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static void cgsn_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		g_at_server_send_info(server, "123456789", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
	}
}

static gboolean send_ok(gpointer user)
{
	GAtServer *server = user;

	g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);

	return FALSE;
}

static void cfun_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[12];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CFUN: (0-1)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		snprintf(buf, sizeof(buf), "+CFUN: %d", modem_mode);
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int mode;

		g_at_result_iter_init(&iter, cmd);
		g_at_result_iter_next(&iter, "");

		if (g_at_result_iter_next_number(&iter, &mode) == FALSE)
			goto error;

		if (mode != 0 && mode != 1)
			goto error;

		if (modem_mode == mode) {
			g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
			break;
		}

		modem_mode = mode;
		g_timeout_add_seconds(1, send_ok, server);
		break;
	}
	default:
		goto error;
	}

	return;

error:
	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void cpin_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CPIN: (READY)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_info(server, "+CPIN: READY", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static gboolean do_netreg(gpointer user)
{
	GAtServer *server = user;
	char buf[32];

	if (data_mode)
		return FALSE;

	network_status = 1;

	switch (modem_creg) {
	case 1:
	case 2:
		snprintf(buf, sizeof(buf), "+CREG: %d", network_status);
		g_at_server_send_unsolicited(server, buf);
		break;
	}

	return FALSE;
}

static void cops_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_timeout_add_seconds(3, send_ok, server);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_info(server, "+COPS: 0", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtServerResult result;
		GAtResultIter iter;
		int mode;

		g_at_result_iter_init(&iter, cmd);
		g_at_result_iter_next(&iter, "");

		if (g_at_result_iter_next_number(&iter, &mode) == TRUE) {
			if (mode == 0) {
				g_timeout_add_seconds(2, do_netreg, server);
				result = G_AT_SERVER_RESULT_OK;
			} else
				result = G_AT_SERVER_RESULT_ERROR;
		} else
			result = G_AT_SERVER_RESULT_ERROR;

		g_at_server_send_final(server, result);
		break;
	}
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void creg_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[20];

	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CREG: (0-2)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		snprintf(buf, sizeof(buf), "+CREG: %d,%d",
						modem_creg, network_status);
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int mode;

		g_at_result_iter_init(&iter, cmd);
		g_at_result_iter_next(&iter, "");

		if (g_at_result_iter_next_number(&iter, &mode) == FALSE)
			goto error;

		if (mode != 0 && mode != 1 && mode != 2)
			goto error;

		modem_creg = mode;
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	}
	default:
		goto error;
	}

	return;

error:
	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void cgreg_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[20];

	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CGREG: (0-2)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		snprintf(buf, sizeof(buf), "+CGREG: %d,%d",
						modem_cgreg, network_status);
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int mode;

		g_at_result_iter_init(&iter, cmd);
		g_at_result_iter_next(&iter, "");

		if (g_at_result_iter_next_number(&iter, &mode) == FALSE)
			goto error;

		if (mode != 0 && mode != 1 && mode != 2)
			goto error;

		modem_cgreg = mode;
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	}
	default:
		goto error;
	}

	return;

error:
	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void cgatt_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[12];

	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CGATT: (0-1)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		snprintf(buf, sizeof(buf), "+CGATT: %d", network_attach);
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
	{
		GAtResultIter iter;
		int mode;

		g_at_result_iter_init(&iter, cmd);
		g_at_result_iter_next(&iter, "");

		if (g_at_result_iter_next_number(&iter, &mode) == FALSE)
			goto error;

		if (mode != 0 && mode != 1)
			goto error;

		if (network_attach == mode) {
			g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
			break;
		}

		network_attach = mode;
		g_timeout_add_seconds(1, send_ok, server);
		break;
	}
	default:
		goto error;
	}

	return;

error:
	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void cgdata_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	GAtIO *io;

	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		if (setup_ppp(server) == FALSE)
			goto error;

		g_at_server_send_intermediate(server, "CONNECT");
		data_mode = TRUE;

		io = g_at_server_get_io(server);
		g_at_io_set_write_done(io, open_ppp, server);
		break;
	default:
error:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cgdcont_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	if (modem_mode == 0) {
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		return;
	}

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cimi_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_COMMAND_ONLY:
		g_at_server_send_info(server, "246813579", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void csms_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_info(server, "+CSMS: 0,1,1,1", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CSMS: (0)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cmgf_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_info(server, "+CMGF: 0", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_info(server, "+CMGF: (0,1)", TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cpms_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[2048];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+CPMS: (\"SM\",\"ME\"),(\"SM\",\"ME\"),(\"SM\")");
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cnmi_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[2048];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+CNMI: (0,1,2,3),(0,1),(0,1,2),(0),(0,1)");
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cscs_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[2048];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+CSCS: \"GSM\",\"IRA\",\"UCS2\"");
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cmgl_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void cpbs_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	char buf[2048];

	switch (type) {
	case G_AT_SERVER_REQUEST_TYPE_SET:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_QUERY:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	case G_AT_SERVER_REQUEST_TYPE_SUPPORT:
		sprintf(buf, "+CPBS: (\"FD\",\"SM\",\"SN\")");
		g_at_server_send_info(server, buf, TRUE);
		g_at_server_send_final(server, G_AT_SERVER_RESULT_OK);
		break;
	default:
		g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
		break;
	}
}

static void dial_cb(GAtServer *server, GAtServerRequestType type,
			GAtResult *cmd, gpointer user)
{
	GAtResultIter iter;
	const char *dial_str;
	char c;

	if (type != G_AT_SERVER_REQUEST_TYPE_SET)
		goto error;

	g_at_result_iter_init(&iter, cmd);
	g_at_result_iter_next(&iter, "");

	dial_str = g_at_result_iter_raw_line(&iter);
	if (dial_str == NULL)
		goto error;

	g_print("dial call %s\n", dial_str);

	c = *dial_str;
	if (c == '*' || c == '#' || c == 'T' || c == 't') {
		GAtIO *io = g_at_server_get_io(server);

		if (setup_ppp(server) == FALSE)
			goto error;

		g_at_server_send_intermediate(server, "CONNECT");
		data_mode = TRUE;
		g_at_io_set_write_done(io, open_ppp, server);
	}

	return;

error:
	g_at_server_send_final(server, G_AT_SERVER_RESULT_ERROR);
}

static void add_handler(GAtServer *server)
{
	g_at_server_set_debug(server, server_debug, "Server");

	g_at_server_register(server, "+CGMI",    cgmi_cb,    NULL, NULL);
	g_at_server_register(server, "+CGMM",    cgmm_cb,    NULL, NULL);
	g_at_server_register(server, "+CGMR",    cgmr_cb,    NULL, NULL);
	g_at_server_register(server, "+CGSN",    cgsn_cb,    NULL, NULL);
	g_at_server_register(server, "+CFUN",    cfun_cb,    NULL, NULL);
	g_at_server_register(server, "+CPIN",    cpin_cb,    NULL, NULL);
	g_at_server_register(server, "+COPS",    cops_cb,    NULL, NULL);
	g_at_server_register(server, "+CREG",    creg_cb,    NULL, NULL);
	g_at_server_register(server, "+CGREG",   cgreg_cb,   NULL, NULL);
	g_at_server_register(server, "+CGATT",   cgatt_cb,   NULL, NULL);
	g_at_server_register(server, "+CGDATA",  cgdata_cb,  NULL, NULL);
	g_at_server_register(server, "+CGDCONT", cgdcont_cb, NULL, NULL);
	g_at_server_register(server, "+CIMI",    cimi_cb,    NULL, NULL);
	g_at_server_register(server, "+CSMS",    csms_cb,    NULL, NULL);
	g_at_server_register(server, "+CMGF",    cmgf_cb,    NULL, NULL);
	g_at_server_register(server, "+CPMS",    cpms_cb,    NULL, NULL);
	g_at_server_register(server, "+CNMI",    cnmi_cb,    NULL, NULL);
	g_at_server_register(server, "+CSCS",    cscs_cb,    NULL, NULL);
	g_at_server_register(server, "+CMGL",    cmgl_cb,    NULL, NULL);
	g_at_server_register(server, "+CPBS",    cpbs_cb,    NULL, NULL);
	g_at_server_register(server, "D",        dial_cb,    NULL, NULL);
}

static void server_destroy(gpointer user)
{
	struct sock_server *data = user;

	g_free(data);
}

static void set_raw_mode(int fd)
{
	struct termios ti;

	memset(&ti, 0, sizeof(ti));
	tcgetattr(fd, &ti);
	tcflush(fd, TCIOFLUSH);
	cfmakeraw(&ti);
	tcsetattr(fd, TCSANOW, &ti);
}

static gboolean create_tty(const char *modem_path)
{
	int master, slave;
	char pty_name[256];
	GIOChannel *server_io;

	if (modem_path == NULL)
		return FALSE;

	if (openpty(&master, &slave, pty_name, NULL, NULL) < 0)
		return FALSE;

	set_raw_mode(slave);

	g_print("new pty is created at %s\n", pty_name);

	server_io = g_io_channel_unix_new(master);

	main_server = g_at_server_new(server_io);
	if (main_server == NULL) {
		g_io_channel_shutdown(server_io, FALSE, NULL);
		g_io_channel_unref(server_io);

		return FALSE;
	}

	g_io_channel_unref(server_io);

	return TRUE;
}

static gboolean on_socket_connected(GIOChannel *chan, GIOCondition cond,
							gpointer user)
{
	struct sockaddr saddr;
	unsigned int len = sizeof(saddr);
	int fd;
	GIOChannel *client_io = NULL;
	struct sock_server *data = user;

	if (cond != G_IO_IN)
		goto error;

	fd = accept(data->server_sock, &saddr, &len);
	if (fd == -1)
		goto error;

	client_io = g_io_channel_unix_new(fd);

	main_server = g_at_server_new(client_io);
	g_io_channel_unref(client_io);

	if (main_server == NULL)
		goto error;

	add_handler(main_server);

	return TRUE;

error:
	g_free(data);

	return FALSE;
}

static struct sock_server *socket_common(int sk, struct sockaddr *addr,
						const char *modem_path)
{
	struct sock_server *sock;
	int reuseaddr = 1;

	setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));

	if (bind(sk, addr, sizeof(struct sockaddr)) < 0) {
		g_print("Can't bind socket: %s (%d)", strerror(errno), errno);

		close(sk);

		return NULL;
	}

	if (listen(sk, 1) < 0) {
		g_print("Can't listen on socket: %s (%d)",
						strerror(errno), errno);

		close(sk);

		return NULL;
	}

	sock = g_try_new0(struct sock_server, 1);
	if (sock == NULL)
		return FALSE;

	sock->server_sock = sk;

	return sock;
}

static gboolean create_tcp(const char *modem_path, int port)
{
	struct sockaddr_in addr;
	int sk;
	struct sock_server *server;
	GIOChannel *server_io;

	if (modem_path == NULL)
		return FALSE;

	sk = socket(PF_INET, SOCK_STREAM, 0);
	if (sk < 0) {
		g_print("Can't create tcp/ip socket: %s (%d)\n",
						strerror(errno), errno);
		return FALSE;
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(port);

	server = socket_common(sk, (struct sockaddr *) &addr, modem_path);
	if (server == NULL)
		return FALSE;

	g_print("new tcp is created at tcp port %d\n", port);

	server_io = g_io_channel_unix_new(sk);

	g_io_channel_set_close_on_unref(server_io, TRUE);

	server_watch = g_io_add_watch_full(server_io,
				G_PRIORITY_DEFAULT,
				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
				on_socket_connected, server, server_destroy);

	g_io_channel_unref(server_io);

	return TRUE;
}

static gboolean create_unix(const char *modem_path, const char *sock_path)
{
	struct sockaddr_un addr;
	int sk;
	struct sock_server *server;
	GIOChannel *server_io;

	if (modem_path == NULL)
		return FALSE;

	sk = socket(AF_UNIX, SOCK_STREAM, 0);
	if (sk < 0) {
		g_print("Can't create unix socket: %s (%d)\n",
						strerror(errno), errno);

		return FALSE;
	}

	memset(&addr, 0, sizeof(addr));

	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);

	/* Unlink any existing socket for this session */
	unlink(addr.sun_path);

	server = socket_common(sk, (struct sockaddr *) &addr, modem_path);
	if (server == NULL)
		return FALSE;

	g_print("new unix socket is created at %s\n", sock_path);

	server_io = g_io_channel_unix_new(sk);

	g_io_channel_set_close_on_unref(server_io, TRUE);

	server_watch = g_io_add_watch_full(server_io,
				G_PRIORITY_DEFAULT,
				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
				on_socket_connected, server, server_destroy);

	g_io_channel_unref(server_io);

	return TRUE;
}

static void test_server(int type)
{
	switch (type) {
	case 0:
		if (create_tty("/phonesim1") == FALSE)
			exit(1);

		add_handler(main_server);
		break;
	case 1:
		if (create_tcp("/phonesim1", DEFAULT_TCP_PORT) == FALSE)
			exit(1);
		break;
	case 2:
		if (create_unix("/phonesim1", DEFAULT_SOCK_PATH) == FALSE)
			exit(1);
		break;
	}
}

static gboolean signal_cb(GIOChannel *channel, GIOCondition cond, gpointer data)
{
	int signal_fd = GPOINTER_TO_INT(data);
	struct signalfd_siginfo si;
	ssize_t res;

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

	res = read(signal_fd, &si, sizeof(si));
	if (res != sizeof(si))
		return FALSE;

	switch (si.ssi_signo) {
	case SIGINT:
		server_cleanup();
		break;
	case SIGTERM:
		server_cleanup();
		break;
	case SIGUSR1:
		g_at_ppp_shutdown(ppp);
		break;
	default:
		break;
	}

	return TRUE;
}

static int create_signal_io(void)
{
	sigset_t mask;
	GIOChannel *signal_io;
	int signal_fd, signal_source;

	sigemptyset(&mask);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGINT);
	sigaddset(&mask, SIGUSR1);

	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
		g_error("Can't set signal mask");
		return 1;
	}

	signal_fd = signalfd(-1, &mask, 0);
	if (signal_fd < 0) {
		g_error("Can't create signal filedescriptor");
		return 1;
	}

	signal_io = g_io_channel_unix_new(signal_fd);

	g_io_channel_set_close_on_unref(signal_io, TRUE);

	signal_source = g_io_add_watch(signal_io,
			G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
			signal_cb, GINT_TO_POINTER(signal_fd));

	g_io_channel_unref(signal_io);

	return signal_source;
}

static void usage(void)
{
	g_print("test-server - AT Server testing\n"
		"Usage:\n");
	g_print("\ttest-server [-t type]\n");
	g_print("Types:\n"
		"\t0: Pseudo TTY port (default)\n"
		"\t1: TCP sock at port 12346)\n"
		"\t2: Unix sock at ./server_sock\n");
}

int main(int argc, char **argv)
{
	int opt, signal_source;
	int type = 0;

	while ((opt = getopt(argc, argv, "ht:")) != EOF) {
		switch (opt) {
		case 't':
			type = atoi(optarg);
			break;
		case 'h':
			usage();
			exit(1);
			break;
		default:
			break;
		}
	}

	test_server(type);

	signal_source = create_signal_io();

	mainloop = g_main_loop_new(NULL, FALSE);

	g_main_loop_run(mainloop);

	g_main_loop_unref(mainloop);

	g_source_remove(signal_source);

	return 0;
}
