/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008-2009  Michael Bell <michael.bell@opensync.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include <config.h>
 
#include <libsyncml/sml_support.h>
#include <libsyncml/sml_error_internals.h>

#include <libsyncml/sml_transport_internals.h>
#include "../data_sync_api/sml_data_sync_defines.h"

#include "obex_internals.h"
#include "obex_server_internals.h"

#include <errno.h>
#include <string.h>
#include <unistd.h>

#ifndef WIN32
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#endif

static gboolean _dispatch_obex(gpointer data)
{
	SmlLinkObexServerEnv *linkenv = data;

	int result = OBEX_HandleInput(linkenv->handle, 0);
	if (result < 0) {
		GError *error = NULL;
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
			"The handling of the obex input failed (%i).",
			result);
		smlTransportReceiveEvent(linkenv->env->tsp, linkenv->link, SML_TRANSPORT_EVENT_ERROR, NULL, error);
		smlTrace(TRACE_ERROR, "%s: %s", __func__, error->message);
		g_error_free(error);
		return FALSE;
	}
	
	if (linkenv->disconnect) {
		smlTrace(TRACE_INTERNAL, "%s: disconnecting link", __func__);
		OBEX_TransportDisconnect(linkenv->handle);
		// close(linkenv->fd);
		linkenv->destroy = TRUE;
	}
	
	
	if (linkenv->destroy) {
		smlTrace(TRACE_INTERNAL, "%s: Destroying link %p %p", __func__, linkenv, linkenv->link);

		/* preserve the link and the transport for the disconnect event */
		SmlTransport *tsp = linkenv->env->tsp;
		SmlLink *link_ = linkenv->link;
		smlLinkRef(link_);

		/* the environment must be cleaned up before the event is sent */
		smlLinkDeref(linkenv->link);
		g_source_unref(linkenv->source);
		OBEX_Cleanup(linkenv->handle);
		if (tsp->context)
			g_main_context_unref(tsp->context);
		smlSafeFree((gpointer *)&linkenv);
		link_->link_data = NULL;

		/* send the event */
		smlTransportReceiveEvent(tsp, link_, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
		smlLinkDeref(link_);

		return FALSE;
	}
	return TRUE;
}

static void _smlTransportObexServerLinkEvent(obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %i, %i, %i)", __func__, handle, object, mode, event, obex_cmd, obex_rsp);
	SmlLinkObexServerEnv *linkenv = OBEX_GetUserData(handle);
	GError *error = NULL;
	obex_headerdata_t header;
	uint8_t headertype = 0;
	uint32_t len = 0;
	uint32_t conid = 0;		
	SmlMimeType mimetype = SML_MIMETYPE_UNKNOWN;
	
	if (linkenv->disconnect) {
		OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
		smlTrace(TRACE_EXIT, "%s: This link was disconnected", __func__);
		return;
	}
	
	switch (event)  {
		case OBEX_EV_PROGRESS:
			smlTrace(TRACE_INTERNAL, "%s: Progress", __func__);
			break;
		case OBEX_EV_REQDONE:
			smlTrace(TRACE_INTERNAL, "%s: Done", __func__);
			if (obex_cmd == OBEX_CMD_DISCONNECT) {
				linkenv->destroy = TRUE;
			}
			break;
		case OBEX_EV_REQHINT:
			smlTrace(TRACE_INTERNAL, "%s: Hint", __func__);
			/* Comes BEFORE the lib parses anything. */
			switch (obex_cmd) {
				case OBEX_CMD_GET:
					/* At this point we received a get request. If we dont have any data
					 * to send yet, we iterate the main context until the data arrived. */
					while (!linkenv->send_data && !linkenv->disconnect && !linkenv->error) {
						if (!g_main_context_pending(linkenv->env->tsp->context)) {
							usleep(100);
							continue;
						}
						smlTrace(TRACE_INTERNAL, "%s: Dispatching command queue since we dont have data to send", __func__);
						g_main_context_iteration(linkenv->env->tsp->context, TRUE);
					}
					
					/* We received a disconnect event so we reply with an error */
					if (linkenv->disconnect) {
						OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
						smlTrace(TRACE_INTERNAL, "%s: Get was aborted", __func__);
						break;
					}
				case OBEX_CMD_PUT:
				case OBEX_CMD_CONNECT:
				case OBEX_CMD_DISCONNECT:
					OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
					break;
				default:
					OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_IMPLEMENTED, OBEX_RSP_NOT_IMPLEMENTED);
					break;
			}
			break;
 		case OBEX_EV_REQ:
			switch (obex_cmd) {
				case OBEX_CMD_SETPATH:
					smlTrace(TRACE_INTERNAL, "%s: Got SETPATH command", __func__);
					OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
					break;
				case OBEX_CMD_PUT:;
					smlTrace(TRACE_INTERNAL, "%s: Got PUT command", __func__);
					uint32_t length = 0;
					char *body = NULL;
					
					while (OBEX_ObjectGetNextHeader(handle, object, &headertype, &header, &len)) {
						smlTrace(TRACE_INTERNAL, "%s: Next header %i, %d, %p", __func__, headertype, header.bq4, header.bs);
						switch (headertype) {
							case OBEX_HDR_CONNECTION:
								smlTrace(TRACE_INTERNAL, "%s: Found connection number: %d", __func__, header.bq4);
								conid = header.bq4;
								break;
							case OBEX_HDR_TYPE:;
								char *mimetypestr = g_strndup((char *)header.bs, len);
								/*char *mimetypestr = g_malloc0(len);// / 2 + 1);
								if (OBEX_UnicodeToChar((unsigned char *)mimetypestr, header.bs, len / 2 + 1) == -1) {
									smlSafeCFree(&mimetypestr);
									g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "unable to convert from unicode");
									goto error;
								}*/
								
								smlTrace(TRACE_INTERNAL, "%s: Found type: %s", __func__, VA_STRING(mimetypestr));
								
								if (!strcmp(mimetypestr, SML_ELEMENT_WBXML)) {
									mimetype = SML_MIMETYPE_WBXML;
								} else if (!strcmp(mimetypestr, SML_ELEMENT_XML)) {
									mimetype = SML_MIMETYPE_XML;
								} else if (!strcmp(mimetypestr, SML_ELEMENT_SAN)) {
									mimetype = SML_MIMETYPE_SAN;
								} else {
									smlSafeCFree(&mimetypestr);
									g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Unknown mime type");
									goto error;
								}
								
								smlSafeCFree(&mimetypestr);
								break;
							case OBEX_HDR_LENGTH:
								smlTrace(TRACE_INTERNAL, "%s: Found length: %d", __func__, header.bq4);
								length = header.bq4;
								break;
							case OBEX_HDR_BODY:
								if (!length) {
									g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Length must come before the body");
									goto error;
								}
								
								/* We need one byte more than the response length
								 * because the data can be a native XML message.
								 * If the data is a native XML message then it is
								 * sometimes directly used as string.
								 *
								 * The string is automatically NULL terminated
								 * because smlTryMalloc0 fills the new memory with NULLs.
								 */
								body = smlTryMalloc0(length + 1, &error);
								if (!body)
									goto error;
								
								memcpy(body, header.bs, length);
								break;
							default:
								smlTrace(TRACE_INTERNAL, "%s: Unknown header", __func__);
						}
					}
					
					if (!conid) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing connection id");
						goto error;
					}
					
					if (conid != linkenv->conid) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Wrong connection id");
						goto error;
					}
					
					if (mimetype == SML_MIMETYPE_UNKNOWN) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing mime type");
						goto error;
					}
					
					if (!length) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing length");
						goto error;
					}
					
					if (!body) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing body");
						goto error;
					}
					
					SmlTransportData *tspdata = smlTransportDataNew(body, length, mimetype, TRUE, &error);
					if (!tspdata)
						goto error;
					
					if (smlTransportReceiveEvent(linkenv->env->tsp, linkenv->link, SML_TRANSPORT_EVENT_DATA, tspdata, NULL))
						OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
					else
						OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
					
					smlTransportDataDeref(tspdata);
					break;
				case OBEX_CMD_GET:;
					smlTrace(TRACE_INTERNAL, "%s: Got GET command", __func__);
					
					while (OBEX_ObjectGetNextHeader(handle, object, &headertype, &header, &len)) {
						smlTrace(TRACE_INTERNAL, "%s: Next header %i, %d, %p", __func__, headertype, header.bq4, header.bs);
						switch (headertype) {
							case OBEX_HDR_CONNECTION:
								smlTrace(TRACE_INTERNAL, "%s: Found connection number: %d", __func__, header.bq4);
								conid = header.bq4;
								break;
							case OBEX_HDR_TYPE:;
								char *mimetypestr = g_strndup((char *)header.bs, len);
								/*char *mimetypestr = g_malloc0(len / 2 + 1);
								if (OBEX_UnicodeToChar((unsigned char *)mimetypestr, header.bs, len / 2 + 1) == -1) {
									smlSafeCFree(&mimetypestr);
									g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "unable to convert from unicode");
									goto error;
								}*/
								
								smlTrace(TRACE_INTERNAL, "%s: Found type: %s", __func__, VA_STRING(mimetypestr));
								
								if (!strcmp(mimetypestr, SML_ELEMENT_WBXML)) {
									mimetype = SML_MIMETYPE_WBXML;
								} else if (!strcmp(mimetypestr, SML_ELEMENT_XML)) {
									mimetype = SML_MIMETYPE_XML;
								} else if (!strcmp(mimetypestr, SML_ELEMENT_SAN)) {
									mimetype = SML_MIMETYPE_SAN;
								} else {
									smlSafeCFree(&mimetypestr);
									g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Unknown mime type");
									goto error;
								}
								
								smlSafeCFree(&mimetypestr);
								break;
							default:
								smlTrace(TRACE_INTERNAL, "%s: Unknown header", __func__);
						}
					}
					
					if (!conid) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing connection id");
						goto error;
					}
					
					if (conid != linkenv->conid) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Wrong connection id");
						goto error;
					}
					
					if (mimetype == SML_MIMETYPE_UNKNOWN) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing mime type");
						goto error;
					}
					
					if (linkenv->error) {
						if (smlErrorGetClass(&(linkenv->error)) <= SML_ERRORCLASS_RETRY)
							OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
						else
							OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
						
						g_error_free(linkenv->error);
						linkenv->error = NULL;
						smlTrace(TRACE_INTERNAL, "%s: Sent error in response to get", __func__);
						break;
					}
					
					if (!linkenv->send_data) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "No data to send");
						goto error;
					}
					
					if (mimetype != linkenv->send_data->type) {
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
							"Wrong mimetype %d requested. %d was expected.",
							mimetype, linkenv->send_data->type);
						goto error;
					}
					
					/* Now the data and size */
					header.bq4 = (uint32_t)linkenv->send_data->size;
					OBEX_ObjectAddHeader(handle, object, OBEX_HDR_LENGTH, header, sizeof(uint32_t), 0);
					
					header.bs = (unsigned char *)linkenv->send_data->data;
					OBEX_ObjectAddHeader(handle, object, OBEX_HDR_BODY, header, linkenv->send_data->size, 0);
					
					smlTransportDataDeref(linkenv->send_data);
					linkenv->send_data = NULL;
					
					OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
					break;
				case OBEX_CMD_CONNECT:;
					smlTrace(TRACE_INTERNAL, "%s: Got CONNECT command", __func__);
					char *target = NULL;
					
					while (OBEX_ObjectGetNextHeader(handle, object, &headertype, &header, &len)) {
						smlTrace(TRACE_INTERNAL, "%s: Next header %i, %d, %p", __func__, headertype, header.bq4, header.bs);
						switch (headertype) {
							case OBEX_HDR_TARGET:
								target = g_strndup((char *)header.bs, len);
								smlTrace(TRACE_INTERNAL, "%s: Found target: %s", __func__, VA_STRING(target));
								break;
							default:
								smlTrace(TRACE_INTERNAL, "%s: Unknown header", __func__);
						}
					}
					
					if (!target || strcmp(target, "SYNCML-SYNC")) {
						smlSafeCFree(&target);
						g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC, "Missing target");
						goto error;
					}
					smlSafeCFree(&target);
					
					header.bq4 = linkenv->conid;
					OBEX_ObjectAddHeader(handle, object, OBEX_HDR_CONNECTION, header, sizeof(linkenv->conid), OBEX_FL_FIT_ONE_PACKET);
					
					header.bs = (unsigned char *)"SYNCML-SYNC";
					OBEX_ObjectAddHeader(handle, object, OBEX_HDR_WHO, header, strlen((char *)header.bs), OBEX_FL_FIT_ONE_PACKET);
 
					
					linkenv->link = smlLinkNew(linkenv->env->tsp, linkenv, &error);
					if (!linkenv->link)
						goto error;
					
					if (smlTransportReceiveEvent(linkenv->env->tsp, linkenv->link, SML_TRANSPORT_EVENT_CONNECT_DONE, NULL, NULL))
						OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
					else {
						OBEX_ObjectSetRsp(object, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
						linkenv->disconnect = TRUE;
					}
					break;
				case OBEX_CMD_DISCONNECT:
					smlTrace(TRACE_INTERNAL, "%s: Got DISCONNECT command", __func__);
					OBEX_ObjectSetRsp(object, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
					break;
				default:
					smlTrace(TRACE_INTERNAL, "%s: Denied %02x request", __func__, obex_cmd);
					OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_IMPLEMENTED, OBEX_RSP_NOT_IMPLEMENTED);
					break;
			}

			break;
		case OBEX_EV_LINKERR:
			smlTrace(TRACE_INTERNAL, "%s: Link broken (this does not have to be an error)!", __func__);
			break;
		default:
			smlTrace(TRACE_INTERNAL, "%s: Unknown event!", __func__);
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error:
	OBEX_ObjectSetRsp(object, OBEX_RSP_BAD_REQUEST, OBEX_RSP_BAD_REQUEST);
	smlTransportReceiveEvent(linkenv->env->tsp, linkenv->link, SML_TRANSPORT_EVENT_ERROR, NULL, error);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, error->message);
	g_error_free(error);
}

static void _smlTransportObexServerMainEvent(obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %i, %i, %i)",
		__func__, handle, object, mode, event, obex_cmd, obex_rsp);
	SmlTransportObexServerEnv *env = OBEX_GetUserData(handle);
	GError *error = NULL;
	SmlLinkObexServerEnv *linkenv = NULL;

	switch (event)  {
		case OBEX_EV_ACCEPTHINT:
			/* This event means that there is a new connection. */

			/* Create the env that handles this link */
			linkenv = smlTryMalloc0(sizeof(SmlLinkObexServerEnv), &error);
			if (!linkenv)
				goto error;
			linkenv->env = env;
	
			linkenv->handle = OBEX_ServerAccept(env->handle, _smlTransportObexServerLinkEvent, linkenv);
			if (!linkenv->handle) {
				g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
					"OBEX_ServerAccept failed.");
				smlSafeFree((gpointer *)&linkenv);
				goto error;
			}
			smlTrace(TRACE_INTERNAL, "%s: New obex_t * handle %p", __func__, linkenv->handle);
	
			/* Now we create a source that watches the new obex connection */
			linkenv->source = g_idle_source_new();
			g_source_set_callback(linkenv->source, _dispatch_obex, linkenv, NULL);
			g_source_attach(linkenv->source, env->tsp->context);
			if (env->tsp->context)
				g_main_context_ref(env->tsp->context);
	
			/* We first have to add the connection id which we generate for this transport */
			env->lastConId++;
			linkenv->conid = env->lastConId;

			// the user data was already ste accept
			// OBEX_SetUserData(linkenv->handle, linkenv);

			break;
		default:
			g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
				"Unknown OBEX event %d.", event);
			goto error;
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;

error:
	OBEX_ObjectSetRsp(object, OBEX_RSP_BAD_REQUEST, OBEX_RSP_BAD_REQUEST);
	smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, error->message);
	g_error_free(error);
}

/* These function supervise the file descriptor where we are listening for incoming
 * connections */
static gboolean _fd_prepare(GSource *source, gint *timeout_)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, source, timeout_);
	/* There is no need to dispatch this too fast since
	 * it does not influence the transmission of data, only
	 * the connection */
	*timeout_ = 50;
	return FALSE;
}

static gboolean _fd_check(GSource *source)
{
	SmlTransportObexServerEnv *env = *((SmlTransportObexServerEnv **)(source + 1));

	fd_set rfds;
	FD_ZERO(&rfds);
	int fd = OBEX_GetFD(env->handle);
	FD_SET(fd, &rfds);

	struct timeval tv;
	tv.tv_sec = 0;
	tv.tv_usec = 1;

	int retval = select(fd + 1, &rfds, NULL, NULL, &tv);

	if (retval == -1)
		smlTrace(TRACE_ERROR, "%s: Unable to select()", __func__);
	else if (retval)
		return TRUE;

	return FALSE;
}

static gboolean _fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, source, callback, user_data);
	SmlTransportObexServerEnv *env = user_data;
	GError *error = NULL;

	/* this is a non-locking function */
	int result = OBEX_HandleInput(env->handle, 0);
	if (result < 0) {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
			"OBEX_HandleInput on main handle returned %d.",
			result);
		goto error;
	}
	if (result == 0) {
		g_set_error(&error, SML_ERROR, SML_ERROR_GENERIC,
			"OBEX_HandleInput on main handle get a timeout.");
		goto error;
	}

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTransportReceiveEvent(env->tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, error);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, error->message);
	g_error_free(error);
	return FALSE;
}

static gboolean
smlTransportObexServerSetConfigOption (SmlTransport *tsp,
                                       const gchar *name,
                                       const gchar *value,
                                       GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, tsp, VA_STRING(name), VA_STRING(value), error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->transport_data);
	SmlTransportObexServerEnv *env = tsp->transport_data;	

	if (!strcmp(name, SML_TRANSPORT_CONFIG_PORT)) {
		env->port = atoi(value);
		smlTrace(TRACE_INTERNAL, "%s: Port %i detected", __func__, env->port);
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_BLUETOOTH_ADDRESS)) {
#ifdef ENABLE_BLUETOOTH
		int code = str2ba(value, env->bdaddr);
		if (code != 0) {
			g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
				"Bluetooth MAC cannot be interpreted (%d).", code);
			goto error;
		} 
		smlTrace(TRACE_INTERNAL, "%s: Bluetooth MAC %s detected",
			__func__, VA_STRING(value));
#else
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Bluetooth not enabled");
		goto error;
#endif
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL)) {
		env->port = atoi(value);
		smlTrace(TRACE_INTERNAL, "%s: Bluetooth channel %i detected",
			__func__, env->port);
	} else if (!strcmp(name, SML_TRANSPORT_CONFIG_IRDA_SERVICE)) {
		env->irda_service = g_strdup(value);
		smlTrace(TRACE_INTERNAL, "%s: IRDA service %s detected",
			__func__, VA_STRING(env->irda_service));
	} else {
		g_set_error(error, SML_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION,
			"Unknown parameter %s found.", name);
		goto error;
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static gboolean
smlTransportObexServerSetConnectionType (SmlTransport *tsp,
                                         SmlTransportConnectionType type,
                                         GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, tsp, type, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->transport_data);
	SmlTransportObexServerEnv *env = tsp->transport_data;

	env->type = type;

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static gboolean
smlTransportObexServerInit (SmlTransport *tsp,
                            GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->transport_data);
	SmlTransportObexServerEnv *env = tsp->transport_data;
	smlAssert(env->handle == NULL);

	switch (env->type) {
#ifdef ENABLE_OPENOBEX_TCP
		case SML_TRANSPORT_CONNECTION_TYPE_NET:;
			/* init object */
			env->handle = OBEX_Init(OBEX_TRANS_INET,
						_smlTransportObexServerMainEvent,
						OBEX_FL_KEEPSERVER);
			if (!env->handle) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to create OBEX NET transport.");
				goto error;
			}
			OBEX_SetUserData(env->handle, env);
			/* prepare IP address etc. */
			if (!env->port)
				g_warning("Using default INET port.");
			size_t size = sizeof(struct sockaddr_in6);
			struct sockaddr_in6 *addr = smlTryMalloc0(size, error);
			if (!addr)
				goto error;
			addr->sin6_family = PF_INET6;
			addr->sin6_port = htons(env->port);
			memcpy(&(addr->sin6_addr), &in6addr_any, sizeof(addr->sin6_addr));

			/* register server */
			if (GET_OBEX_RESULT(TcpOBEX_ServerRegister(env->handle, (struct sockaddr *) addr, size)) < 0)
			{
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to register INET OBEX server. %s (%i).",
					strerror(errno), errno);
				smlSafeFree((gpointer *)&addr);
				goto error;
			}
			smlSafeFree((gpointer *)&addr);
			break;
#endif /* ENABLE_OPENOBEX_TCP */
		case SML_TRANSPORT_CONNECTION_TYPE_SERIAL:
			/* init object */
			env->handle = OBEX_Init(OBEX_TRANS_IRDA,
						_smlTransportObexServerMainEvent,
						OBEX_FL_KEEPSERVER);
			if (!env->handle) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to create OBEX IRDA transport.");
				goto error;
			}
			OBEX_SetUserData(env->handle, env);
			/* register server */
			if (GET_OBEX_RESULT(IrOBEX_ServerRegister(env->handle, env->irda_service)))
			{
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to register IRDA OBEX server. %s (%i).",
					strerror(errno), errno);
				goto error;
			}
			break;
		case SML_TRANSPORT_CONNECTION_TYPE_BLUETOOTH:
#ifdef ENABLE_BLUETOOTH
			/* init object */
			env->handle = OBEX_Init(OBEX_TRANS_BLUETOOTH,
						_smlTransportObexServerMainEvent,
						OBEX_FL_KEEPSERVER);
			if (!env->handle) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to create OBEX Bluetooth transport.");
				goto error;
			}
			OBEX_SetUserData(env->handle, env);
			/* register server */
			if (GET_OBEX_RESULT(BtOBEX_ServerRegister(env->handle, env->bdaddr, env->port)) < 0)
			{
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Unable to register Bluetooth OBEX server. %s (%i).",
					strerror(errno), errno);
				goto error;
			}
#else
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Bluetooth not enabled");
			goto error;
#endif
			break;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown obex type");
			goto error;
	}
	smlTrace(TRACE_INTERNAL, "%s: server registered successfully", __func__);

	/* Now we create a source that watches for incoming connection requests */
	env->functions = smlTryMalloc0(sizeof(GSourceFuncs), error);
	if (!env->functions)
		goto error;
	env->functions->prepare = _fd_prepare;
	env->functions->check = _fd_check;
	env->functions->dispatch = _fd_dispatch;
	env->functions->finalize = NULL;

	env->source = g_source_new(env->functions, sizeof(GSource) + sizeof(SmlTransportObexServerEnv *));
	SmlTransportObexServerEnv **envptr = (SmlTransportObexServerEnv **)(env->source + 1);
	*envptr = env;
	g_source_set_callback(env->source, NULL, env, NULL);
	g_source_attach(env->source, tsp->context);
	if (tsp->context)
		g_main_context_ref(tsp->context);

	smlTrace(TRACE_EXIT, "%s: %p", __func__, env);
	return TRUE;

error:
	if (env->handle) {
		OBEX_Cleanup(env->handle);
		env->handle = NULL;
	}
	if (env)
		smlSafeFree((gpointer *)&env);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
	return FALSE;
}

static gboolean
smlTransportObexServerFinalize (void *data,
                                GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, error);
	CHECK_ERROR_REF
	smlAssert(data);
	SmlTransportObexServerEnv *env = data;
	
	smlAssert(env->tsp);
	
	if (env->handle)
		OBEX_Cleanup(env->handle);

	/* detach source */
	if (env->source) {
		g_source_destroy(env->source);
		g_source_unref(env->source);
	}

	/* free context */
	if (env->tsp->context)
		g_main_context_unref(env->tsp->context);

	/* free functions */
	if (env->functions)
		smlSafeFree((gpointer *)&(env->functions));


	if (env->irda_service)
		smlSafeCFree(&(env->irda_service));
	
	smlSafeFree((gpointer *)&env);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static void
smlTransportObexServerSend (void *userdata,
                            void *linkdata,
                            SmlTransportData *data,
                            GError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, userdata, linkdata, data, error);
	smlAssert(data || error);
	smlAssert(userdata);
	// Unused: SmlTransportObexServerEnv *env = userdata;
	SmlLinkObexServerEnv *linkenv = linkdata;
	smlAssert(linkenv);
	GError *local_error = NULL;

	if (linkdata == NULL) {
		/* send is called before connect succeeded or
		 * the user of the library ignored the link
		 */
		g_set_error(&local_error, SML_ERROR, SML_ERROR_GENERIC,
			"The OBEX server tries to send before a connection with a client was established or the link was ignored.");
		goto error;
	}
	
	if (error) {
		smlAssert(!data);
		linkenv->error = error;
		
		smlTrace(TRACE_EXIT, "%s: Error set", __func__);
		return;
	}
	
	if (linkenv->send_data) {
		g_set_error(&local_error, SML_ERROR, SML_ERROR_GENERIC, "We already have waiting data");
		goto error;
	}
	
	linkenv->send_data = data;
	smlTransportDataRef(linkenv->send_data);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, local_error->message);
	smlTransportReceiveEvent(linkenv->env->tsp, linkenv->link, SML_TRANSPORT_EVENT_ERROR, NULL, local_error);
	g_error_free(local_error);
	return;
}

static void
smlTransportObexServerDisconnect(void *data, void *linkdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, data, linkdata);
	smlAssert(data);

	/* The OBEX client usually performs the disconnect.
	 * If the server disconnects first then the client
	 * interprets this as an error. So if we want to
	 * abort a connection then this is the correct way.
	 */
	if (linkdata) {
		SmlLinkObexServerEnv *linkenv = linkdata;
		linkenv->disconnect = TRUE;
		smlTrace(TRACE_INTERNAL, "%s - explicit disconnect by OBEX server", __func__);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
}

gboolean
smlTransportObexServerNew (SmlTransport *tsp,
                           GError **error)
{
	CHECK_ERROR_REF
	smlAssert(tsp);

	tsp->functions.set_config_option = smlTransportObexServerSetConfigOption;
	tsp->functions.set_connection_type = smlTransportObexServerSetConnectionType;
	tsp->functions.initialize = smlTransportObexServerInit;
	tsp->functions.finalize = smlTransportObexServerFinalize;
	tsp->functions.send = smlTransportObexServerSend;
	tsp->functions.disconnect = smlTransportObexServerDisconnect;

	SmlTransportObexServerEnv *env = smlTryMalloc0(sizeof(SmlTransportObexServerEnv), error);
	if (!env)
		return FALSE;
	tsp->transport_data = env;
	env->tsp = tsp;
	env->lastConId = 0;
	env->port = 0;

	return TRUE;
}

