/*
 * 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 "sml_transport_internals.h"

#include "sml_error_internals.h"
#include "sml_support.h"

#include <config.h>
#include <unistd.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 gboolean
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__);
}

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

	if (!tsp->async)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The transport is not in asynchronous mode.");
		goto error;
	}

	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__, (*error)->message);
	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;
	smlTrace(TRACE_INTERNAL, "%s: type %d", __func__, cmd->type);
	
	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 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;
		case SML_TRANSPORT_CMD_UNKNOWN:
			/* This is definitive a bug. */
			smlAssert(FALSE);
			break;
	}
	
	if (cmd->link)
		smlLinkDeref(cmd->link);
	
	if (cmd->data)
		smlTransportDataDeref(cmd->data);
	
	smlSafeFree((gpointer *)&cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
}

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

	gboolean 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",
				error->message);
			// 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__, error->message);
			tsp->state = SML_TRANSPORT_ERROR;
			tsp->cached_error = g_error_copy(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__,
				 tsp->cached_error->message);
			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", __func__);
		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", __func__);
		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", __func__);
		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", __func__);
		g_mutex_lock(tsp->connections_mutex);
		tsp->connections--;
		g_mutex_unlock(tsp->connections_mutex);
	}

	if (tsp->event_callback != NULL)
	{
		/* 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) {
			g_error_free(tsp->cached_error);
			tsp->cached_error = NULL;
		}
	} /* end of tsp->event_callback */

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

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__);
}

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

	if (!tsp->async)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The transport is not in asynchronous mode.");
		goto error;
	}

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

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

	if (tsp->connected)
	{
		g_set_error(error, SML_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__, (*error)->message);
	return FALSE;
}

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

	if (!tsp->async)
	{
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The transport is not in asynchronous mode.");
		goto error;
	}

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

	if (!tsp->connected)
	{
		g_set_error(error, SML_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))
	{
		g_set_error(error, SML_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)
	{
		g_set_error(error, SML_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)
		{
			g_set_error(error, SML_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);
			g_set_error(error, SML_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__, (*error));
	return FALSE;
}

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

gboolean
smlTransportIsConnected (SmlTransport *tsp)
{
	return tsp->connected;
}

SmlTransportData*
smlTransportDataNew (gchar *data,
                     gsize size,
                     SmlMimeType mimetype,
                     gboolean ownsData,
                     GError **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__, (*error)->message);
	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__);
}

const gchar*
smlTransportDataGetData (SmlTransportData *data)
{
	return data->data;
}

gsize
smlTransportDataGetSize (SmlTransportData *data)
{
	return data->size;
}

SmlMimeType
smlTransportDataGetType (SmlTransportData *data)
{
	return data->type;
}

void
smlTransportDataSetRequestedType (SmlTransportData *data,
                                  SmlMimeType mimetype)
{
	data->type_get = mimetype;
}

void
smlTransportDataSetNeedsAnswer (SmlTransportData *data,
                                gboolean needsAnswer)
{
	data->needsAnswer = needsAnswer;
}

void
smlTransportSendError (SmlTransport *tsp,
                       SmlLink *link_,
                       const GError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, tsp, link_, error);
	/* Do no use CHECK_ERROR_REF here.
	 * GError *error contains an error which should be send.
	 */
	smlAssert(tsp);
	
	GError *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) {
		cmd->error = g_error_copy(error);
	}
	
	//Call the fin function
	smlQueueSend(tsp->command_queue, cmd);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlLink*
smlLinkNew (SmlTransport *tsp,
            void *link_data,
            GError **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__, (*error)->message);
	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,
                 GError **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;
	tsp->async = TRUE;
	
	switch (type) {
#ifdef ENABLE_HTTP
		case SML_TRANSPORT_HTTP_SERVER:
			if (!smlTransportHttpServerNew(tsp, error))
				goto error;
			break;
		case SML_TRANSPORT_HTTP_CLIENT:
			if (!smlTransportHttpClientNew(tsp, error))
				goto error;
			break;
#else
		case SML_TRANSPORT_HTTP_SERVER:
		case SML_TRANSPORT_HTTP_CLIENT:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "HTTP Transport not enabled in this build");
			goto error;
#endif
#ifdef ENABLE_OBEX
		case SML_TRANSPORT_OBEX_CLIENT:
			if (!smlTransportObexClientNew(tsp, error))
				goto error;
			break;
		case SML_TRANSPORT_OBEX_SERVER:
			if (!smlTransportObexServerNew(tsp, error))
				goto error;
			break;
#else
		case SML_TRANSPORT_OBEX_SERVER:
		case SML_TRANSPORT_OBEX_CLIENT:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "OBEX Transport not enabled in this build");
			goto error;
#endif
		case SML_TRANSPORT_UNKNOWN:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "The transport type must be specified.");
			goto error;
	}
	smlAssert(tsp->functions.disconnect);
	
	tsp->command_queue = smlQueueNew(error);
	if (!tsp->command_queue)
		goto error;

	/* 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:
	if (tsp)
		smlTransportFree(tsp);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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);
	GError *error = NULL;

	/* If somebody forgets to finalize the transport after an error happened
	 * then the finalization is done automatically here.
	 */

	if (tsp->state == SML_TRANSPORT_ERROR &&
	    tsp->transport_data)
	{
		g_warning("smlTransportFree will finalize the transport because of a poor error handling.");
		if (!smlTransportFinalize(tsp, &error))
		{
			g_warning("The library libsyncml cannot finalize the transport implementation. %s",
				error->message);
			g_error_free(error);
			error = NULL;
		}
	}

	/* smlTransportFree can only be called if
	 *
	 *     1. the transport was never initialized or
	 *     2. the transport was already finalized.
	 *
	 * All other calls to smlTransportFree are invalid.
	 * So the following states are correct:
	 *
	 *     1. UNKNOWN   (new but never initialized)
	 *     2. FINALIZED (ready to cleanup)
	 *
	 * If there is an error state then the transport must
	 * still be finalized.
	 */

	smlAssert(tsp->state == SML_TRANSPORT_UNKNOWN || tsp->state == SML_TRANSPORT_FINALIZED);

	if (tsp->command_queue)
		smlQueueFree(tsp->command_queue);

	/* If transport_data is present
	 * then the transport implementation was not finalized.
	 * This means that the transport was never initialized.
	 * If the transport was never initialized
	 * then the transport implementation was never run
	 * by a special thread.
	 */

	if (tsp->transport_data)
	{
		/* Check the correct state - never initialized. */
		smlAssert(!tsp->thread);
		if (!tsp->functions.finalize(tsp->transport_data, &error))
		{
			g_warning("The library libsyncml cannot finalize the transport implementation. %s",
				error->message);
			g_error_free(error);
			error = NULL;
		}
	}

	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",
			tsp->cached_error->message);
		g_error_free(tsp->cached_error);
		tsp->cached_error = NULL;
	}

	/* 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
 * 
 */
gboolean
smlTransportSetConfigOption (SmlTransport *tsp,
                             const gchar *name,
                             const gchar *value,
                             GError **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)
	{
		g_set_error(error, SML_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__, (*error)->message);
	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
 * 
 */
gboolean
smlTransportSetConnectionType (SmlTransport *tsp,
                               SmlTransportConnectionType type,
                               GError **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
 * 
 */
gboolean
smlTransportInitialize (SmlTransport *tsp,
                        GError **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) {
		g_set_error(error, SML_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);
	tsp->async = TRUE;
	
	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__, (*error)->message);
	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
 * 
 */
gboolean
smlTransportSetResponseURI (SmlTransport *tsp,
                            const gchar *uri,
                            GError **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 gboolean
smlTransportDetachQueueCallback (gpointer data,
                                 GError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	CHECK_ERROR_REF
	smlAssert(data);
	SmlQueue *queue = data;
	smlQueueDetach(queue);
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

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

static void
smlTransportCleanupHandler (void *message,
                           void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, message, userdata);
	smlAssert(message);
	SmlTransportCommand *cmd = message;

	if (cmd->type == SML_TRANSPORT_CMD_DISCONNECT)
	{
		/* disconnects will always be executed */
		smlTransportWorkerHandler(message, userdata);
	} else {
		/* remove the command */
		if (cmd->link)
			smlLinkDeref(cmd->link);
		if (cmd->data)
			smlTransportDataDeref(cmd->data);
		smlSafeFree((gpointer *)&cmd);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
}

static gboolean
smlTransportDisconnectLink (gpointer key,
                            gpointer value,
                            gpointer userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, key, value, userdata);

	SmlTransport *tsp = userdata;
	SmlLink *link_ = key;

	if (link_->link_data)
	{
		/* connected */
		tsp->functions.disconnect(tsp->transport_data, link_);
	}
	smlLinkDeref(link_);

	smlTrace(TRACE_EXIT, "%s - TRUE", __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
 * 
 */
gboolean
smlTransportFinalize (SmlTransport *tsp,
                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	CHECK_ERROR_REF
	smlAssert(tsp);
	smlAssert(tsp->functions.finalize);

	/* Only an initialized transport can be finalized. */

	if (tsp->state < SML_TRANSPORT_INITIALIZED)
	{
		g_set_error (error, SML_ERROR, SML_ERROR_GENERIC,
		             "Transport was never in the state \"Initialized\".");
		goto error;
	}

	/* mark the transport as synchronous
	 *
	 * This means that no more commands are accepted.
	 */

	tsp->async = FALSE;

	/* smlTransportFinalize is a synchronous function.
	 * It requires that the transport is no longer connected.
	 * smlTransportDisconnect is an asynchronous function.
	 * This means that the transport must be synchronized.
	 * So before the real checking and finalization starts
	 * the command queue must be stopped and all pending
	 * commands must have the chance to be executed.
	 */

	/* 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);
	}

	/* If the transport is in an error state
	 * then the only useful command is a disconnect.
	 * All other commands of the command queue
	 * will be ignored.
	 */
	if (tsp->state == SML_TRANSPORT_ERROR)
		smlQueueSetHandler(tsp->command_queue, (SmlQueueHandler)smlTransportCleanupHandler, tsp);

	/* 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);
		}
	}

	/* NOTICE: The transport is now synchronized and clean. */

	/* All open connections must be disconnected. */

	if (tsp->connected)
	{
		if (smlTransportIsServer(tsp))
		{
			/* All open server connections will now be closed. */
			g_hash_table_foreach_remove(tsp->links, smlTransportDisconnectLink, tsp);
		} else {
			/* This is a client. */
			smlTrace(TRACE_INTERNAL, "%s: try automatic disconnect for a client", __func__);
			tsp->functions.disconnect(tsp->transport_data, NULL);

			if (tsp->connected) {
				g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
					"Automatic client disconnect during finalization failed.");
				goto error;
			}
		}
	}

	/* stop and finalize the transport */

	/* OBEX */
	if (tsp->type != SML_TRANSPORT_HTTP_CLIENT &&
	    tsp->type != SML_TRANSPORT_HTTP_SERVER && tsp->thread)
		_smlTransportStop(tsp);
	
	if (!tsp->functions.finalize(tsp->transport_data, error))
		goto error;

	/* HTTP */
	if (tsp->thread)
		_smlTransportStop(tsp);
	
	tsp->transport_data = NULL;

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

gchar*
smlLinkGetResponseURI (SmlLink *link_,
                       SmlSession *session,
                       GError **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;
	}
}

gboolean
smlLinkIsConnected (SmlLink *link)
{
	smlAssert(link);
	if (link->link_data)
		return TRUE;
	else
		return FALSE;
}

/*@}*/

/*@}*/
