/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2007-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 "syncml.h"

#include "syncml_internals.h"
#include "sml_error_internals.h"
#include "sml_transport_internals.h"

#ifdef ENABLE_HTTP
#include "transports/http_client_internals.h"
#include "transports/http_server_internals.h"
#endif

#ifdef ENABLE_OBEX
#include "transports/obex_client_internals.h"
#include "transports/obex_server_internals.h"
#endif

/**
 * @defgroup SmlTransportPrivate SyncML Transport Private API
 * @ingroup PrivateLowLevelAPI
 * @brief Private Interfaces to manage transports
 * 
 */
/*@{*/

static SmlBool smlTransportIsServer(SmlTransport *tsp)
{
	smlAssert(tsp);
	if (tsp->type == SML_TRANSPORT_HTTP_SERVER)
		return TRUE;
	if (tsp->type == SML_TRANSPORT_OBEX_SERVER)
		return TRUE;
	return FALSE;
}

void smlTransportSetEventCallback(SmlTransport *tsp, SmlTransportEventCb callback, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, callback, userdata);
	smlAssert(tsp);
	
	tsp->event_callback = callback;
	tsp->event_callback_userdata = userdata;

	/* Do not return until all running callbacks finished.
	 * This is a busy wait and yes it can happen that new
	 * callbacks are started during sleep but the setter
	 * must get a warranty that the old callback is no
	 * longer in use.
	 *
	 * This is acceptable because the callback is only set
	 * two times. First it is set when the manager registers
	 * the transport and second it is set when the manager
	 * is freed. So this is a protection against sending an
	 * event to an already freed manager.
	 */
	while (g_atomic_int_get(&(tsp->event_callback_ref_count)) > 0)
	{
		usleep(50);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlBool smlTransportSend(SmlTransport *tsp, SmlLink *link_, SmlTransportData *data, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, tsp, link_, data, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(data);

	if (tsp->cached_error != NULL)
	{
		// the most parameters should be NULL because the cached error
		// has nothing to do with the actual send request
		smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, NULL);
		goto error;
	}
	
	SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
	if (!cmd) {
		smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_ERROR, NULL, *error);
		goto error;
	}
	
	cmd->type = SML_TRANSPORT_CMD_SEND;
	cmd->data = data;
	if (link_) {
		cmd->link = link_;
		smlLinkRef(cmd->link);
	}
	
	smlTransportDataRef(cmd->data);
	
	//Call the fin function
	smlQueueSend(tsp->command_queue, cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

void smlTransportWorkerHandler(void *message, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, message, userdata);
	smlAssert(message);
	smlAssert(userdata);
	SmlTransportCommand *cmd = message;
	SmlTransport *tsp = userdata;
	
	switch (cmd->type) {
		case SML_TRANSPORT_CMD_SEND:
			tsp->functions.send(tsp->transport_data, cmd->link ? cmd->link->link_data : NULL, cmd->data, cmd->error);
			break;
		case SML_TRANSPORT_CMD_CONNECT:
			if (!tsp->functions.connect) {
				smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_CONNECT_DONE, NULL, NULL);
				smlTrace(TRACE_INTERNAL, "%s: No connect function", __func__);
				break;
			}
			tsp->functions.connect(tsp->transport_data);
			break;
		case SML_TRANSPORT_CMD_DISCONNECT:	
			if (!tsp->functions.disconnect) {
				smlTransportReceiveEvent(tsp, NULL, SML_TRANSPORT_EVENT_DISCONNECT_DONE, NULL, NULL);
				smlTrace(TRACE_INTERNAL, "%s: No disconnect function", __func__);
				break;
			}
			/* If there is a disconnect request
			 * but the connection is already down
			 * then an earlier disconnect happened
			 * (perhaps initiated by the remote peer)
			 * and we can ignore the disconnect.
			 */
			if (smlTransportIsServer(tsp) && !cmd->link->link_data) {
				/* The SERVER is already disconnected. */
				smlTrace(TRACE_INTERNAL,
					"%s: The server link was already disconnected.",
					__func__);
				break;
			}
			if (!smlTransportIsServer(tsp) && !tsp->connected) {
				/* The CLIENT is already disconnected. */
				smlTrace(TRACE_INTERNAL,
					"%s: The client was already disconnected.",
					__func__);
				break;
			}
			tsp->functions.disconnect(tsp->transport_data, cmd->link ? cmd->link->link_data : NULL);
			break;
	}
	
	if (cmd->link)
		smlLinkDeref(cmd->link);
	
	if (cmd->data)
		smlTransportDataDeref(cmd->data);
	
	smlSafeFree((gpointer *)&cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
}

SmlBool smlTransportReceiveEvent(SmlTransport *tsp, SmlLink *link_, SmlTransportEventType type, SmlTransportData *data, SmlError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p)", __func__, tsp, link_, type, data, error);
	smlAssert(tsp);

	SmlBool ret = TRUE;
	if (tsp->event_callback == NULL)
	{
		smlTrace(TRACE_INTERNAL, "%s: no callback available", __func__);
		if (type != SML_TRANSPORT_EVENT_ERROR &&
		    type != SML_TRANSPORT_EVENT_DISCONNECT_DONE)
		{
			/* Somebody forget to set the event callback.
			 * This is a heavy bug which cannot be compensated.
			 * The whole state management will fail.
			 */
			g_error("The transport layer of type %d " \
				"must send a normal event %d " \
				"but there is no event callback set.",
				tsp->type, type);
			/* abort if g_error is not fatal */
			exit(1);
		}
		/* If there is a cached error then the transport layer
		 * uses the error event without an error as a trigger
		 * to send the original error.
		 */
		if (tsp->cached_error && error != NULL)
		{
			/* Wow, this is the second error and
			 * there is still no event callback.
			 */
			g_warning("The transport layer already caches an error " \
				"because the event callback is not present until now." \
				"The received error is ignored. %s",
				smlErrorPrint(&error));
			// do not free the error here because you are not the owner of the error
		}
		if (!tsp->cached_error && error) {
			// we stop all communications here
			smlTrace(TRACE_INTERNAL, "%s: init failed in transport protocol -> %s", __func__, smlErrorPrint(&error));
			tsp->state = SML_TRANSPORT_ERROR;
			tsp->cached_error = error;
			smlErrorRef(&error);
		}
	} else {
		smlTrace(TRACE_INTERNAL, "%s: callback available", __func__);
		if (tsp->cached_error != NULL)
		{
			/* First we have to send the cached error. */
			smlTrace(TRACE_INTERNAL, "%s: cached error detected - %s", __func__,
				 smlErrorPrint(&(tsp->cached_error)));
			g_atomic_int_inc(&(tsp->event_callback_ref_count));
			ret = tsp->event_callback(
					tsp, NULL,
					SML_TRANSPORT_EVENT_ERROR, NULL,
					tsp->cached_error, tsp->event_callback_userdata);
			smlTrace(TRACE_INTERNAL, "%s: %d event callbacks",
				__func__, g_atomic_int_dec_and_test(&(tsp->event_callback_ref_count)));

			/* Second we have to prepare the internal states. */

			/* Third we have to send the original event. */
		}

		/* If a connect or disconnect is received without a link
		 * then this means that this is a new connection or a
		 * closed connection of a client. If such a constallation
		 * happens with a server then this is a fatal error.
		 *
		 * If a connect is reveived with a link
		 * then this must be a server and
		 * the link_data must be set.
		 *
		 * If a disconnect is received with a link
		 * then this must be a server and
		 * the link_data must not be present
		 * because the link is down now.
		 */

		if (type == SML_TRANSPORT_EVENT_CONNECT_DONE && !link_)
		{
			if (smlTransportIsServer(tsp))
				g_error("A connect event without a link was received " \
					"but the transport layer is a server.");
			if (tsp->connected)
				g_error("A connect event was received " \
					"but the transport layer is already connected.");
			smlTrace(TRACE_INTERNAL, "%s: connect + no link");
			tsp->connected = TRUE;
		}

		if (type == SML_TRANSPORT_EVENT_DISCONNECT_DONE && !link_)
		{
			if (smlTransportIsServer(tsp))
				g_error("A disconnect event without a link was received " \
					"but the transport layer is a server.");
			if (!tsp->connected)
				g_error("A disconnect event was received " \
					"but there is no connected transport.");
			smlTrace(TRACE_INTERNAL, "%s: disconnect + no link");
			tsp->connected = FALSE;
		}

		if (type == SML_TRANSPORT_EVENT_CONNECT_DONE && link_)
		{
			if (!smlTransportIsServer(tsp))
				g_error("A connect event with a link was received " \
					"but the transport layer is a server.");
			if (!link_->link_data)
				g_error("A connect event with a link was received " \
					"but the link does not contain the required " \
					"transport environment.");
			smlTrace(TRACE_INTERNAL, "%s: connect + link");
			g_mutex_lock(tsp->connections_mutex);
			tsp->connections++;
			g_mutex_unlock(tsp->connections_mutex);
		}

		if (type == SML_TRANSPORT_EVENT_DISCONNECT_DONE && link_)
		{
			if (!smlTransportIsServer(tsp))
				g_error("A disconnect event with a link was received " \
					"but the transport layer is not a server.");
			if (link_->link_data)
				g_error("A disconnect event with a link was received " \
					"but the link still contains the " \
					"transport environment.");
			smlTrace(TRACE_INTERNAL, "%s: disconnect + link");
			g_mutex_lock(tsp->connections_mutex);
			tsp->connections--;
			g_mutex_unlock(tsp->connections_mutex);
		}

		/* now execute the callback */

		if (!(tsp->cached_error &&
		    type == SML_TRANSPORT_EVENT_ERROR &&
		    error == NULL))
		{
			/* send the event */
			g_atomic_int_inc(&(tsp->event_callback_ref_count));
			ret = tsp->event_callback(tsp, link_, type, data, error, tsp->event_callback_userdata);
			smlTrace(TRACE_INTERNAL, "%s: %d event callbacks",
				__func__, g_atomic_int_dec_and_test(&(tsp->event_callback_ref_count)));
		}
		if (tsp->cached_error) {
			smlErrorDeref(&(tsp->cached_error));
			tsp->cached_error = NULL;
		}
	} /* end of tsp->event_callback */

	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
}

/* FIXME: DEPRECATED */
SmlBool smlTransportRunAsync(SmlTransport *tsp, SmlError **error)
{
	smlTrace(TRACE_ERROR, "%s(%p,%p)", __func__, tsp, error);
	CHECK_ERROR_REF
	/* All transports run in asynchronous mode.
	 * Therefore this function should never be called.
	 * The functions always returns TRUE to avoid any complications.
	 */
	return TRUE;
}

static void _smlTransportStop(SmlTransport *tsp)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, tsp);
	
	smlAssert(tsp->thread);

	smlThreadStop(tsp->thread);
	
	smlThreadFree(tsp->thread);
	tsp->thread = NULL;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/* FIXME: DEPRECATED */
void smlTransportStop(SmlTransport *tsp)
{
	_smlTransportStop(tsp);
}

SmlBool smlTransportConnect(SmlTransport *tsp, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);

	/* Only clients can actively connect and
	 * a client can only connect once.
	 */

	if (smlTransportIsServer(tsp))
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Only a transport client can be actively connected.");
		goto error;
	}

	if (tsp->connected)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"A transport client can be connected only once.");
		goto error;
	}

	/* put the command into the queue */

	SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
	if (!cmd)
		goto error;
	
	cmd->type = SML_TRANSPORT_CMD_CONNECT;
	
	//Call the connect function
	smlQueueSend(tsp->command_queue, cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlBool smlTransportDisconnect(SmlTransport *tsp, SmlLink *link_, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, link_, error);
	CHECK_ERROR_REF
	smlAssert(tsp);

	/* The transport must (still) be connected. */

	if (!tsp->connected)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"The transport is not connected and so it cannot be disconnected.");
		goto error;
	}

	/* If this is a client then there must not be a link
	 * because every client can have only one connection.
	 */

	if (link_ && !smlTransportIsServer(tsp))
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"A transport client has no link " \
			"because there is only one connection.");
		goto error;
	}
	
	/* If this is a server then there are two types of disconnects.
	 * 
	 * If there is link then this is a shutdown for a connection.
	 * If a connection is closed then there must be link_data in
	 * the link because otherwise the connection is already closed.
	 *
	 * If there is no link then this is a shutdown for the server.
	 * This means that all links must be shut down.
	 */

	if (link_ && !link_->link_data)
	{
		/* If there is a very fast or aggressive client
		 * then it can happen that the client closes
		 * the connection and the server registers this
		 * event before the disconnect function was
		 * called.
		 *
		 * If this happens then the link_data is perhaps
		 * already empty and the disconnect event was
		 * already triggered.
		 */
		smlTrace(TRACE_EXIT,
			"%s: A server connection should be closed " \
			"but the connection is already closed.",
			__func__);
		return TRUE;
	}

	if (!link_ && smlTransportIsServer(tsp) && tsp->connections)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"A server shutdown is requested " \
			"but there are still open connections (%d).",
			tsp->connections);
		goto error;
	}

	/* check that the link is used at the correct transport layer
	 * this is also a check for the correct registration of the link
	 */

	if (link_)
	{
		if (tsp != link_->tsp)
		{
			smlErrorSet(error, SML_ERROR_GENERIC,
				"The link %p is registered at another transport layer %p than this one %p.",
				link_, link_->tsp, tsp);
			goto error;
		}

		g_mutex_lock(tsp->links_mutex);
		if (!g_hash_table_lookup(tsp->links, link_)) {
			g_mutex_unlock(tsp->links_mutex);
			smlErrorSet(error, SML_ERROR_GENERIC,
				"The link %p is not registered at the transport layer %p",
				link_, tsp);
			goto error;
		}
		g_mutex_unlock(tsp->links_mutex);
	}

	/* send the command */

	SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), error);
	if (!cmd)
		goto error;
	
	cmd->type = SML_TRANSPORT_CMD_DISCONNECT;
	if (link_) {
		cmd->link = link_;
		smlLinkRef(cmd->link);
	}
	
	//Call the disconnect function
	smlQueueSend(tsp->command_queue, cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

SmlTransportType smlTransportGetType(SmlTransport *tsp)
{
	smlAssert(tsp);
	return tsp->type;
}

SmlTransportData *smlTransportDataNew(char *data, unsigned long size, SmlMimeType mimetype, SmlBool ownsData, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %d, %i, %i, %p)", __func__, data, size, mimetype, ownsData, error);
	CHECK_ERROR_REF
	
	//Append the data to the outgoing queue
	SmlTransportData *cmd = smlTryMalloc0(sizeof(SmlTransportData), error);
	if (!cmd)
		goto error;
	
	cmd->type = mimetype;
	cmd->data = data;
	cmd->size = size;
	cmd->ownsData = ownsData;
	cmd->refCount = 1;
	cmd->needsAnswer = TRUE;
	cmd->type_get = SML_MIMETYPE_UNKNOWN; 
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlTransportData *smlTransportDataRef(SmlTransportData *data)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
	smlAssert(data);
	
	g_atomic_int_inc(&(data->refCount));
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return data;
}

void smlTransportDataDeref(SmlTransportData *data)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, data);
	smlAssert(data);
	
	if (!g_atomic_int_dec_and_test(&(data->refCount))) {
		smlTrace(TRACE_EXIT, "%s: refCount > 0", __func__);
		return;
	}
	
	if (data->ownsData && data->data != NULL)
		smlSafeCFree(&(data->data));
	
	smlSafeFree((gpointer *)&data);
		
	smlTrace(TRACE_EXIT, "%s: Freed", __func__);
}

void smlTransportSetError(SmlTransport *tsp, SmlLink *link_, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p(%p))", __func__, tsp, link_, error, error ? *error : NULL);
	/* Do no use CHECK_ERROR_REF here.
	 * SmlError **error contains an error which should be send.
	 */
	smlAssert(tsp);
	
	SmlError *locerror = NULL;
	SmlTransportCommand *cmd = smlTryMalloc0(sizeof(SmlTransportCommand), &locerror);
	if (!cmd)
		return;
	
	cmd->type = SML_TRANSPORT_CMD_SEND;
	cmd->data = NULL;
	if (link_) {
		cmd->link = link_;
		smlLinkRef(cmd->link);
	}
	
	if (error && *error) {
		cmd->error = *error;
		smlErrorRef(error);
	}
	
	//Call the fin function
	smlQueueSend(tsp->command_queue, cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlLink *smlLinkNew(SmlTransport *tsp, void *link_data, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, link_data, error);
	CHECK_ERROR_REF
	smlAssert(link_data);
	
	SmlLink *link_ = smlTryMalloc0(sizeof(SmlLink), error);
	if (!link_)
		goto error;
	link_->tsp = tsp;
	link_->link_data = link_data;
	link_->refCount = 1;

	/* register the link at the transport */
	
	g_mutex_lock(tsp->links_mutex);
	g_hash_table_insert(tsp->links, link_, GINT_TO_POINTER(1));
	g_mutex_unlock(tsp->links_mutex);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, link_);
	return link_;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

/* FIXME: DEPRECATED */
SmlLink *smlLinkFind(SmlTransport *tsp, void *link_data)
{
	/* The function always returns NULL.
	 * The function is not removed to protect the intergrity of the library.
	 * Actually no user of this function is known.
	 */
	smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, tsp, link_data);
	return NULL;
}

SmlLink *smlLinkRef(SmlLink *link_)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, link_);
	smlAssert(link_);
	
	g_atomic_int_inc(&(link_->refCount));
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return link_;
}

void smlLinkDeref(SmlLink *link_)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, link_);
	smlAssert(link_);
	
	if (!g_atomic_int_dec_and_test(&(link_->refCount))) {
		smlTrace(TRACE_EXIT, "%s: refCount > 0", __func__);
		return;
	}

	/* deregister the link from the transport */

	g_mutex_lock(link_->tsp->links_mutex);
	if (!g_hash_table_remove(link_->tsp->links, link_))
		g_warning("The link %p was never registered.", link_);
	g_mutex_unlock(link_->tsp->links_mutex);

	/* free the memory */
	
	smlSafeFree((gpointer *)&link_);
		
	smlTrace(TRACE_EXIT, "%s: Freed", __func__);
}

/*@}*/

/**
 * @defgroup SmlTransport SyncML Transport API
 * @ingroup PublicLowLevelAPI
 * @brief Transports can be used to connect to other syncml capable devices and servers
 * 
 */
/*@{*/

/**
 * @name Transport Management
 * These functions allow to create, delete, initialize and finalize transports
 */
/*@{*/

/** @brief Creates a new transport
 * 
 * A transport is a abstraction of a transport type like http or obex
 * 
 * @param type The type of the transport
 * @param error Return location if an error occured
 * @returns The new transport or NULL in the case of an error
 * 
 */
SmlTransport *smlTransportNew(SmlTransportType type, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %p)", __func__, type, error);
	CHECK_ERROR_REF

	/* Initialize GLib thread system. */
	if (!g_thread_supported ()) g_thread_init (NULL);
	g_type_init();

	SmlTransport *tsp = smlTryMalloc0(sizeof(SmlTransport), error);
	if (!tsp)
		goto error;
	tsp->type = type;
	tsp->cached_error = NULL;
	tsp->event_callback = NULL;
	tsp->context = NULL;
	
	switch (type) {
#ifdef ENABLE_HTTP
		case SML_TRANSPORT_HTTP_SERVER:
			if (!smlTransportHttpServerNew(tsp, error))
				goto error_free_tsp;
			break;
		case SML_TRANSPORT_HTTP_CLIENT:
			if (!smlTransportHttpClientNew(tsp, error))
				goto error_free_tsp;
			break;
#else
		case SML_TRANSPORT_HTTP_SERVER:
		case SML_TRANSPORT_HTTP_CLIENT:
			smlErrorSet(error, SML_ERROR_GENERIC, "HTTP Transport not enabled in this build");
			goto error_free_tsp;
#endif
#ifdef ENABLE_OBEX
		case SML_TRANSPORT_OBEX_CLIENT:
			if (!smlTransportObexClientNew(tsp, error))
				goto error_free_tsp;
			break;
		case SML_TRANSPORT_OBEX_SERVER:
			if (!smlTransportObexServerNew(tsp, error))
				goto error_free_tsp;
			break;
#else
		case SML_TRANSPORT_OBEX_SERVER:
		case SML_TRANSPORT_OBEX_CLIENT:
			smlErrorSet(error, SML_ERROR_GENERIC, "OBEX Transport not enabled in this build");
			goto error_free_tsp;
#endif
	}
	
	tsp->command_queue = smlQueueNew(error);
	if (!tsp->command_queue)
		goto error_free_tsp;

	/* links must be tracked to detect server connections
	 * which are not cleaned up before server finalization
	 *
	 * connections must be tracked to detect open server connections
	 * which were not closed before server shutdown
	 */
	if (smlTransportIsServer(tsp))
	{
		tsp->links = g_hash_table_new(g_direct_hash, g_direct_equal);
		tsp->links_mutex = g_mutex_new();
		tsp->connections = 0;
		tsp->connections_mutex = g_mutex_new();
	} else {
		tsp->links = NULL;
		tsp->links_mutex = NULL;
		tsp->connections = 0;
		tsp->connections_mutex = NULL;
	}
	tsp->connected = FALSE;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, tsp);
	return tsp;

error_free_tsp:
	smlTransportFree(tsp);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

/** @brief Frees the given transport
 * 
 * @param tsp The transport to free
 * 
 */
void smlTransportFree(SmlTransport *tsp)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, tsp);
	smlAssert(tsp);
	
	if (tsp->command_queue)
		smlQueueFree(tsp->command_queue);

	SmlError *error = NULL;
	if (tsp->transport_data &&
	    !tsp->functions.finalize(tsp->transport_data, &error))
	{
		g_warning("The library libsyncml cannot free the transport. %s",
			smlErrorPrint(&error));
		smlErrorDeref(&error);
	}

	if (tsp->context)
		g_main_context_unref(tsp->context);
	tsp->context = NULL;

	/* check for open connections */

	if (tsp->connections)
	{
		g_warning("The transport layer of libsyncml is freed " \
			"but not all connections were close (%d).",
			tsp->connections);
	}
	if (tsp->connections_mutex)
		g_mutex_free(tsp->connections_mutex);

	/* check for forgotten connections */

	if (tsp->links && g_hash_table_size(tsp->links))
		g_warning("The transport layer of libsyncml is freed " \
			"but not all connections were cleaned up (%d).",
			g_hash_table_size(tsp->links));
	if (tsp->links)
		g_hash_table_unref(tsp->links);
	if (tsp->links_mutex)
		g_mutex_free(tsp->links_mutex);

	/* check for forgotten errors */

	if (tsp->cached_error) {
		g_warning("The transport layer is cleaned up and an error is ignored. %s",
			smlErrorPrint(&(tsp->cached_error)));
		smlErrorDeref(&(tsp->cached_error));
	}

	/* free the memory */

	smlSafeFree((gpointer *)&tsp);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Sets a configuration parameter.
 *
 * This function sets the configuration option "name" to
 * the value which you specified. The option is checked by
 * the according transport layer implementation. 
 * The transport must be in the state "Uninitialized" to use this functions.
 * The state will then is not switched.
 * 
 * @param tsp The transport
 * @param name The name of the configuration option
 * @param value The value of the configuration option
 * @param error Return location if an error occured
 * @returns TRUE if the call succeded or FALSE in the case of an error
 * 
 */
SmlBool smlTransportSetConfigOption(
		SmlTransport *tsp,
		const char *name,
		const char *value,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s, %p)", __func__, tsp, VA_STRING(name), strcmp(name, "PASSWORD") ? VA_STRING(value) : "***sensitive***", error);
	/* Do not change the assertion to simple return FALSE stuff
	 * because to many developers incl. me ignore returned FALSE
	 * if it is only configuration.
	 */
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);
	smlAssert(tsp->functions.set_config_option);

	if (!name)
	{
		smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
			"The name of the configuration option is missing.");
		goto error;
	}

	if (!tsp->functions.set_config_option(tsp, name, value, error))
		goto error;

	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Sets a configuration parameter.
 *
 * This function sets the configuration option "name" to
 * the value which you specified. The option is checked by
 * the according transport layer implementation. 
 * The transport must be in the state "Uninitialized" to use this functions.
 * The state will then is not switched.
 * 
 * @param tsp The transport
 * @param name The name of the configuration option
 * @param value The value of the configuration option
 * @param error Return location if an error occured
 * @returns TRUE if the call succeded or FALSE in the case of an error
 * 
 */
SmlBool smlTransportSetConnectionType(
		SmlTransport *tsp,
		SmlTransportConnectionType type,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, tsp, type, error);
	/* Do not change the assertion to simple return FALSE stuff
	 * because to many developers incl. me ignore returned FALSE
	 * if it is only configuration.
	 */
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(type != SML_TRANSPORT_CONNECTION_TYPE_UNKNOWN);
	smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);
	smlAssert(tsp->functions.set_connection_type);

	if (tsp->functions.set_connection_type(tsp, type, error))
	{
		smlTrace(TRACE_EXIT, "%s", __func__);
		return TRUE;
	} else {
		smlTrace(TRACE_EXIT_ERROR, "%s", __func__);
		return FALSE;
	}
}

/** @brief Initializes the transport with the given config
 * 
 * This function will init the transport with the options that you specify
 * in the options. The options that are available depend on the transport used.
 * The transport must be in the state "Uninitialized" to use this functions. The state will then
 * switch to "Initialized".
 * 
 * @param tsp The transport
 * @param error Return location if an error occured
 * @returns TRUE if the call succeded or FALSE in the case of an error
 * 
 */
SmlBool smlTransportInitialize(SmlTransport *tsp, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->state == SML_TRANSPORT_UNINITIALIZED);

	/* Intialize the context and thread for the g_main_loop. */

	tsp->context = g_main_context_new();
	if (!tsp->context) {
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Cannot create new GMainContext for asynchronous transport.");
		goto error;
	}
	
	tsp->thread = smlThreadNew(tsp->context, error);
	if (!tsp->thread)
		goto error_free_loop;
	
	/* The transport always needs a context based loop for the
	 * command queue handling. Never attach this queue to
	 * another context because this can block the transport.
	 */

	/* start the queue - e.g. to handle errors */
	smlQueueSetHandler(tsp->command_queue, (SmlQueueHandler)smlTransportWorkerHandler, tsp);
	smlQueueAttach(tsp->command_queue, tsp->context);
	
	if (tsp->functions.initialize && !tsp->functions.initialize(tsp, error))
		goto error_detach;

	/* Now start the GMainLoop.
	 * Do not start earlier to avoid confusion with libsoup
	 * because of registering a context at a running main loop. */
	smlThreadStart(tsp->thread);

	tsp->state = SML_TRANSPORT_INITIALIZED;

	/* If this is a server then the server is running now. */
	if (smlTransportIsServer(tsp))
		tsp->connected = TRUE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error_detach:
	smlQueueDetach(tsp->command_queue);
error_free_loop:
	if (tsp->context) {
		g_main_context_unref(tsp->context);
		tsp->context = NULL;
	}
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Sets the response URI after initialization.
 *
 * This function sets the response URI after initialization
 * if the transport layer supports this. If the transport
 * layer does not support this feature then this is a fatal
 * error. The URI must be an absolute URI according to
 * OMA DS 1.2 Representation Protocol
 * 
 * @param tsp The transport
 * @param uri The response URI
 * @param error Return location if an error occured
 * @returns TRUE if the call succeded or FALSE in the case of an error
 * 
 */
SmlBool smlTransportSetResponseURI(
		SmlTransport *tsp,
		const char *uri,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, tsp, VA_STRING(uri), error);
	/* Do not change the assertion to simple return FALSE stuff
	 * because to many developers incl. me ignore returned FALSE
	 * if it is only configuration.
	 */
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(uri);
	smlAssert(tsp->state == SML_TRANSPORT_INITIALIZED ||
		  tsp->state == SML_TRANSPORT_CONNECTED);
	smlAssert(tsp->functions.set_response_uri);

	if (tsp->functions.set_response_uri(tsp, uri, error))
	{
		smlTrace(TRACE_EXIT, "%s", __func__);
		return TRUE;
	} else {
		smlTrace(TRACE_EXIT_ERROR, "%s", __func__);
		return FALSE;
	}
}

static SmlBool smlTransportDetachQueueCallback(
			gpointer data,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(data);
	SmlQueue *queue = data;
	smlQueueDetach(queue);
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static SmlBool smlTransportDispatchQueueCallback(
			gpointer data,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(data);
	SmlQueue *queue = data;
	smlQueueDispatch(queue);
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

/** @brief Finalizes the transport 
 * 
 * This function will finalize the transport . The transport must be in the state "Initialized" 
 * to use this functions. The state will then switch to "Uninitialized".
 * 
 * @param tsp The transport
 * @param error Return location if an error occured
 * @returns TRUE if the call succeded or FALSE in the case of an error
 * 
 */
SmlBool smlTransportFinalize(SmlTransport *tsp, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->functions.finalize);

	if (tsp->connected && !smlTransportIsServer(tsp))
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"If a client transport is connected then it cannot be finalized.");
		goto error;
	}

	if (tsp->state != SML_TRANSPORT_INITIALIZED &&
	    tsp->state != SML_TRANSPORT_ERROR) {
		smlErrorSet(error, SML_ERROR_GENERIC, "Transport was not in the state \"Initialized\"");
		goto error;
	}

	/* FIXME: Is there a situation where no thread is present? */

	/* OBEX */
	if (tsp->type != SML_TRANSPORT_HTTP_CLIENT &&
	    tsp->type != SML_TRANSPORT_HTTP_SERVER && tsp->thread)
		_smlTransportStop(tsp);

	/* stop the command queue */
	if (tsp->thread) {
		/* HTTP - thread safe destroy */
		if (!smlThreadCallFunction(
		         tsp->thread,
		         smlTransportDetachQueueCallback,
		         tsp->command_queue,
		         error))
			goto error;
	} else {
		/* OBEX */
		smlQueueDetach(tsp->command_queue);
	}

	/* give all jobs a chance to finish cleanly */
	unsigned int i = 0;
	unsigned int queueLength = smlQueueLength(tsp->command_queue);
	for (; i < queueLength; i++) {
		if (tsp->thread) {
			/* HTTP */
			if (!smlThreadCallFunction(
			         tsp->thread,
			         smlTransportDispatchQueueCallback,
			         tsp->command_queue,
			         error))
				goto error;
		} else {
			/* OBEX */
			smlQueueDispatch(tsp->command_queue);
		}
	}
	
	if (!tsp->functions.finalize(tsp->transport_data, error))
		goto error;

	/* HTTP */
	if (tsp->thread)
		_smlTransportStop(tsp);
	
	tsp->transport_data = NULL;
	
	tsp->state = SML_TRANSPORT_UNINITIALIZED;
	
	/* If this is a server then the server is shutdown now. */
	if (smlTransportIsServer(tsp))
		tsp->connected = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

char * smlTransportGetResponseURI(SmlLink *link_, SmlSession *session, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, link_, session, error);
	/* Do not change the assertion to simple return FALSE stuff
	 * because to many developers incl. me ignore returned FALSE
	 * if it is only configuration.
	 */
	CHECK_ERROR_REF
	smlAssert(link_);
	smlAssert(link_->tsp);
	smlAssert(session);

	if (link_->tsp->functions.get_response_uri)
	{
		char *result = link_->tsp->functions.get_response_uri(link_, session, error);
		smlTrace(TRACE_EXIT, "%s - %s", __func__, VA_STRING(result));
		return result;
	} else {
		smlTrace(TRACE_EXIT, "%s - unsupported feature", __func__);
		return NULL;
	}
}

/*@}*/

/*@}*/
