/*
 * 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_manager_internals.h"
#include "sml_transport_internals.h"
#include "sml_command_internals.h"
#include "sml_elements_internals.h"
#include "sml_session_internals.h"

typedef struct managerSession {
	SmlLink *link;
	SmlSession *session;
	unsigned int finalLock;
	SmlTransport *transport;
	GList *objects;
} managerSession;

managerSession *_smlManagerGetManagerSession(SmlManager *manager, SmlSession *session);

SmlObject *smlManagerObjectFindInternal(
		SmlManager *manager,
		SmlSession *session,
		SmlCommandType type,
		SmlLocation *target,
		SmlLocation *source,
		const char* contentType);

static managerSession *_manager_session_find(SmlManager *manager, SmlSession *session)
{
	smlAssert(session);
	smlAssert(manager);
	
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *sess = s->data;
		if (sess->session == session)
			return sess;
	}
	return NULL;
}

static void _manager_session_free(managerSession *sess)
{
	smlAssert(sess);
	
	smlSessionUnref(sess->session);
	
	if (sess->link)
		smlLinkDeref(sess->link);
	
	while (sess->objects) {
		SmlObject *object = sess->objects->data;
		smlManagerObjectFree(object);
		sess->objects = g_list_delete_link(sess->objects, sess->objects);
	}
	
	smlSafeFree((gpointer *)&sess);
}

static gboolean _manager_prepare_internal(GSource *source, gint *timeout_)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p)", __func__, source, timeout_);
	*timeout_ = 1;
	return FALSE;
}

static gboolean _manager_check_internal(GSource *source)
{
	SmlManager *manager = *((SmlManager **)(source + 1));
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		if (smlSessionCheck(session->session))
			return TRUE;
	}
	return FALSE;
}

static gboolean _manager_dispatch_internal(GSource *source, GSourceFunc callback, gpointer user_data)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p)", __func__, source, callback, user_data);
	int max = 100;
	
	SmlManager *manager = user_data;
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		while (smlSessionCheck(session->session) && max) {
			/* A dispatch function should never block on a
			 * locked resource. This is like a busy wait
			 * because the thread is not available for this
			 * time which can block the whole system if two
			 * dispatchers use the same thread.
			 */
			if (smlSessionTryLock(session->session))
			{
				smlSessionDispatch(session->session);
				smlSessionUnlock(session->session);
			}
			max--;
		}
	}
	
	return TRUE;
}

static void _smlManagerSendEvent(SmlManager *manager, SmlManagerEventType type, SmlSession *session, SmlCommand *command, SmlCommand *parent, SmlError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p)", __func__, manager, type, session, command, parent, error);

	SmlError *lerr = NULL;
	SmlManagerEvent *event = smlTryMalloc0(sizeof(SmlManagerEvent), &lerr);
	if (event) {
		event->type = type;
		
		if (session) {
			event->session = session;
			smlSessionRef(session);
		} else {
			event->session = NULL;
		}
		
		if (command) {
			event->command = command;
			smlCommandRef(command);
		} else {
			event->command = NULL;
		}
		
		if (parent) {
			event->parent = parent;
			smlCommandRef(parent);
		} else {
			event->parent = NULL;
		}
		
		if (error) {
			event->error = error;
			smlErrorRef(&error);
		}
		
		smlQueueSend(manager->userEventQueue, event);
	} else {
		/* error handling is not possible here :( */
		g_error("%s", smlErrorPrint(&lerr));
		smlErrorDeref(&lerr);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void _smlManagerEventFree(SmlManagerEvent *event)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, event);
	smlAssert(event);
	
	if (event->session)
		smlSessionUnref(event->session);
	
	if (event->command)
		smlCommandUnref(event->command);
	
	if (event->parent)
		smlCommandUnref(event->parent);
	
	if (event->error)
		smlErrorDeref(&(event->error));
	
	smlSafeFree((gpointer *)&event);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/* If we return FALSE in this function, the current request will get
 * aborted */
static SmlBool _smlManagerDataHandler(SmlTransport *tsp, SmlLink *link_, SmlTransportEventType type, SmlTransportData *data, SmlError *error, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p, %p)", __func__, tsp, link_, type, data, error, userdata);
	smlAssert(userdata);
	SmlManager *manager = userdata;
	SmlParser *parser = NULL;
	SmlHeader *header = NULL;
	SmlCred *cred = NULL;
	SmlSession *session = NULL;
	SmlError *locerror = NULL;

	switch (type) {
		case SML_TRANSPORT_EVENT_CONNECT_DONE:
			/* Pass the connect through */
			_smlManagerSendEvent(manager, SML_MANAGER_CONNECT_DONE, NULL, NULL, NULL, NULL);
			break;
		case SML_TRANSPORT_EVENT_DISCONNECT_DONE:
			/* Pass the disconnect through */
			_smlManagerSendEvent(manager, SML_MANAGER_DISCONNECT_DONE, NULL, NULL, NULL, NULL);
			break;
		case SML_TRANSPORT_EVENT_DATA:
	
			parser = smlParserNew(data->type, 0, &locerror);
			if (!parser)
				goto session_error;
			/* add manager to parser for OMA DS 1.2 SAN handling */
			smlParserSetManager(parser, manager);
			
			/* start to parse the data */
			if (!smlParserStart(parser, data->data, data->size, &locerror))
				goto error_free_parser;

			/* Get the header of the message*/
			header = NULL;
			cred = NULL;
			if (!smlParserGetHeader(parser, &header, &cred, &locerror))
				goto error_free_parser;
			
			/* Find the session if available otherwise register it as new */
			session = smlManagerSessionFind(manager, header->sessionID);
			if (!session) {
				/* If we use the target and source from the header of the received message
				 * then we MUST interchange source and target */
				SmlSessionType sessionType;
				switch(smlTransportGetType(tsp))
				{
					case SML_TRANSPORT_OBEX_CLIENT:
						/* this can happen if the remote
						 * client creates a new session ID.
						 */
						sessionType = SML_SESSION_TYPE_SERVER;
						break;
					case SML_TRANSPORT_OBEX_SERVER:
						sessionType = SML_SESSION_TYPE_CLIENT;
						break;
					case SML_TRANSPORT_HTTP_SERVER:
						sessionType = SML_SESSION_TYPE_SERVER;
						break;
					default:
						smlErrorSet(&locerror,
							SML_ERROR_NOT_IMPLEMENTED,
							"New session was unexpectedly received for transport %d.",
							smlTransportGetType(tsp));
						goto error_free_header;
						break;
				}
				if (!(session = smlSessionNew(
							sessionType, data->type,
							header->version, header->protocol,
							header->source, header->target,
							header->sessionID, 1, &locerror)))
					goto error_free_header;
				if (link_)
				{
					char *responseURI = smlTransportGetResponseURI(link_, session, &locerror);
					if (!responseURI && locerror)
						goto error_free_header;
					if (responseURI)
					{
						if (!smlSessionSetResponseURI(session, responseURI, &locerror))
						{
							smlSafeCFree(&responseURI);
							goto error_free_header;
						}
						smlSafeCFree(&responseURI);
					}
				}
				if (!smlManagerSessionAdd(manager, session, link_, &locerror))
					goto error_free_header;
				smlSessionUnref(session);
			} else {
				/* SECURITY: let's check ResponseURI if supported
				 * SECURITY: this is important to avoid replay attacks
				 */
				if (link_) {
					char *responseUri = smlTransportGetResponseURI(link_, session, &locerror);
					if (!responseUri && locerror) {
						goto error_free_header;
					}
					if (responseUri)
						smlSafeCFree(&responseUri);
				}
			}

			/* This function is called by the tranport layer which
			 * runs in an own thread. Therefore we must lock the
			 * according session because otherwise we can get into
			 * trouble with parallel access to a SmlSession which is
			 * not safe against parallel use by multiple threads.
	 		 */
			smlSessionLock(session);

			/* Find the manager session so that we can update the link. We
			 * have to do this since each time a transport with links receives
			 * a new data packets, it might create a new link (connectionless
			 * protocols). So we have to update the link to be able to answer
			 * the data */
			GList *s;
			for (s = manager->sessions; s; s = s->next) {
				managerSession *sess = s->data;
				if (sess->session == session) {
					if (sess->link)
						smlLinkDeref(sess->link);
					sess->link = link_;
					if (link_)
						smlLinkRef(sess->link);
					break;
				}
			}
			
			/* Now check if the header is valid etc */
			if (!smlSessionReceiveHeader(session, header, &locerror))
				goto error_free_header;
			
			/* Then check if we are allowed to authenticate */
			if (!smlManagerDispatchHeader(manager, session, header, cred, &locerror))
				goto error_free_header;
			
			smlHeaderFree(header);
			if (cred)
				smlCredUnref(cred);
			
			/* Now let the session handle the commands etc */
			if (!smlSessionReceiveBody(session, parser, &locerror))
				goto error_free_parser;
			
			/* Free the parser */
			smlParserFree(parser);

			/* make the session accessible for others */
			smlSessionUnlock(session);

			break;
		case SML_TRANSPORT_EVENT_ERROR:
			/* Pass the error through */
			smlErrorDuplicate(&locerror, &error);
			goto transport_error;
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error_free_header:
	if (header)
		smlHeaderFree(header);
	if (cred)
		smlCredUnref(cred);
error_free_parser:
	smlParserFree(parser);
	if (session)
		smlSessionUnlock(session);
session_error:
	if (session == NULL && link_ && type == SML_TRANSPORT_EVENT_DATA) {
		/* If there is no session and a link then this means
		 * that there is a failing server connection.
		 * It is a good idea to close the connection because
		 * the session has no chance to signal a clean
		 * disconnect to the link.
		 */
		SmlError *herror = NULL;
		if (!smlTransportDisconnect(manager->transport, link_, &herror)) {
			smlTrace(TRACE_ERROR, "%s: Disconnect on error failed. %s", 
				smlErrorPrint(&herror));
			smlErrorDeref(&herror);
		}
	}
	_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, locerror);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&locerror));
	smlErrorDeref(&locerror);
	return FALSE;
	
transport_error:
	_smlManagerSendEvent(manager, SML_MANAGER_TRANSPORT_ERROR, NULL, NULL, NULL, locerror);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&locerror));
	smlErrorDeref(&locerror);
	return FALSE;
}

SmlManager *smlManagerNew(SmlTransport *tsp, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, tsp, error);
	smlAssert(tsp);
	CHECK_ERROR_REF
	
	SmlManager *manager = smlTryMalloc0(sizeof(SmlManager), error);
	if (!manager)
		goto error;
		
	smlTransportSetEventCallback(tsp, _smlManagerDataHandler, manager);
	manager->transport = tsp;
	
	manager->running_mutex = g_mutex_new();
	manager->running = g_cond_new();
	
	manager->userEventQueue = smlQueueNew(error);
	if (!manager->userEventQueue)
		goto error_free_manager;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return manager;

error_free_manager:
	smlSafeFree((gpointer *)&manager);	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

void smlManagerFree(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);

	/* stop automatic session dispatch environment if necessary */
	if (manager->context)
		smlManagerStop(manager);

	/* Deactivate the event callback of the transport layer
	 * because the manager is no longer available
	 */

	if (manager->transport)
		smlTransportSetEventCallback(manager->transport, NULL, NULL);

	while (manager->sessions) {
		managerSession *sess = manager->sessions->data;

		_manager_session_free(sess);
	
		manager->sessions = g_list_delete_link(manager->sessions, manager->sessions);
	}
		
	while (manager->objects) {
		SmlObject *object = manager->objects->data;
		
		smlManagerObjectFree(object);
		manager->objects = g_list_remove(manager->objects, object);
	}
	
	if (manager->userEventQueue) {
		SmlManagerEvent *event = NULL;
		while ((event = smlQueueTryPop(manager->userEventQueue)))
			_smlManagerEventFree(event);
		
		smlQueueFree(manager->userEventQueue);
		manager->userEventQueue = NULL;
	}

	g_cond_free(manager->running);
	g_mutex_free(manager->running_mutex);
	
	smlSafeFree((gpointer *)&manager);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlManagerSetEventCallback(SmlManager *manager, SmlManagerEventCb callback, void *userdata)
{
	smlAssert(manager);
	smlAssert(callback);
	
	manager->eventCallback = callback;
	manager->eventCallbackUserdata = userdata;
}

SmlBool smlManagerStart(SmlManager *manager, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, error);
	smlAssert(manager);
	CHECK_ERROR_REF
    
	manager->context = g_main_context_new();
	if (!manager->context) {
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Cannot create new main context.");
		goto error;
	}

	manager->functions = smlTryMalloc0(sizeof(GSourceFuncs), error);
	if (!manager->functions)
		goto error;

	manager->functions->prepare = _manager_prepare_internal;
	manager->functions->check = _manager_check_internal;
	manager->functions->dispatch = _manager_dispatch_internal;
	manager->functions->finalize = NULL;
	
	manager->thread = smlThreadNew(manager->context, error);
	if (!manager->thread)
		goto error;
	smlThreadStart(manager->thread);
	
	manager->source = g_source_new(manager->functions, sizeof(GSource) + sizeof(SmlManager *));
	SmlManager **managerptr = (SmlManager **)(manager->source + 1);
	*managerptr = manager;
	g_source_set_callback(manager->source, NULL, manager, NULL);
	g_source_attach(manager->source, manager->context);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	if (manager->context) {
		g_main_context_unref(manager->context);
		manager->context = NULL;
	}
	if (manager->functions)
		smlSafeFree((gpointer *)&(manager->functions));
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Stops the active manager part.
 * 
 * The active part of the manager is the automatic dispatching
 * of the SmlSession objects. The session dispatching consists
 * of the status and command dispatching. If the commands and
 * status are no longer dispatched then the session is no longer
 * in an active state.
 *
 * If this happens the manager should shoulds down the external
 * communication. In fact the manager disconnects all active
 * transport connections.
 *
 * You must still call smlManagerDispatch until you got all
 * required SML_MANAGER_DISCONNECT_DONE events. After this you
 * can safely call smlManagerFree without getting warnings or
 * errors from the transport layer because of forgotten
 * connections.
 *
 * Errors especially from the transport layer are ignored
 * because it is not possible to react on these errors.
 * Therefore it is correct to ignore the errors. Actually it is
 * a problem that one or more failed disconnects result in a
 * deadlock if somebody waits correctly for all disconnect
 * events. Additionally it is impossible to signal a user the
 * number of failed disconnect via a clean way.
 *
 * If a disconnect fails then g_warning is called with an
 * appropriate message. If there is a (G)UI which reconfigures
 * the glib to display warnings and errors to the user then
 * this is the only chance to communicate such errors.
 *
 * @param manager The manager
 * 
 */
void smlManagerStop(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	smlAssert(manager->thread);
	
	/* cleanup automatic session dispatch environment 
	 * the order is highly important
	 */

	/* stop thread */
	smlThreadStop(manager->thread);
	smlThreadFree(manager->thread);
	manager->thread = NULL;
	/* detach source */
	g_source_unref(manager->source);
	manager->source = NULL;
	/* free context */
	g_main_context_unref(manager->context);
	manager->context = NULL;
	/* free functions */
	smlSafeFree((gpointer *)&(manager->functions));
	manager->functions = NULL;

	/* close all open connections */
	
	GList *sessionItem = manager->sessions;
	while (sessionItem) {
		managerSession *sess = sessionItem->data;

		/* disconnect if necessary
		 * 
		 * only do this if:
		 *   1. the transport layer is still connected
		 *   and
		 *   2.1. this is a client connection or the server itself
		 *   or
		 *   2.2. this is an active server connection
		 */
		SmlError *error = NULL;
		if (manager->transport->connected &&
		    (!sess->link || sess->link->link_data) &&
		    !smlTransportDisconnect(manager->transport, sess->link, &error)) {
			g_warning("Errors from the transport layer " \
				"cannot be handled while freeing the manager. %s",
				smlErrorPrint(&error));
			smlErrorDeref(&error);
		}

		sessionItem = g_list_next(sessionItem);
	}

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


void smlManagerRun(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	
	g_mutex_lock(manager->running_mutex);
	g_cond_wait(manager->running, manager->running_mutex);
	g_mutex_unlock(manager->running_mutex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlManagerQuit(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	
	g_mutex_lock(manager->running_mutex);
	g_cond_signal(manager->running);
	g_mutex_unlock(manager->running_mutex);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlSession *smlManagerSessionFind(SmlManager *manager, const char *sessionID)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s)", __func__, manager, VA_STRING(sessionID));
	GList *s = NULL;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *session = s->data;
		if (!strcmp(smlSessionGetSessionID(session->session), sessionID)) {
			smlTrace(TRACE_EXIT, "%s: FOUND %p", __func__, session);
			return session->session;
		}
	}
	
	smlTrace(TRACE_EXIT, "%s: NOT FOUND", __func__);
	return NULL;
}

void smlManagerDispatch(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssert(manager);
	SmlManagerEvent *event = NULL;
	while ((event = smlQueueTryPop(manager->userEventQueue))) {
		smlTrace(TRACE_INTERNAL, "%s: type ::= %d", __func__, event->type);
		/* Check that this is not a locked final event. */
		if (event->session != NULL &&
		    event->type == SML_MANAGER_SESSION_FINAL)
		{
			managerSession *sess = _smlManagerGetManagerSession(manager, event->session);
			if (sess && sess->finalLock)
			{
				smlTrace(TRACE_INTERNAL, "%s - locked final (%i)",
					__func__, sess->finalLock);
				smlQueuePushHead(manager->userEventQueue, event);
				/* It is necessary to stop the dispatching here.
				 * The events MUST BE dispatched in the same
				 * order like they were received. If the while
				 * loop does not break here then the loop tries
				 * to dispatch the locked event again and again.
				 */
				break;
			}
		}
		/* Execute the event callback. */
		smlAssert(manager->eventCallback);
		manager->eventCallback(manager, event->type, event->session, event->error, manager->eventCallbackUserdata);
		_smlManagerEventFree(event);
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlBool smlManagerCheck(SmlManager *manager)
{
	return smlQueueCheck(manager->userEventQueue);
}

static void _event_callback(SmlSession *session, SmlSessionEventType type, SmlCommand *command, SmlCommand *parent, SmlStatus *reply, SmlError *error, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %p, %p)", __func__, session, type, command, parent, reply, error, userdata);
	SmlManager *manager = userdata;
	SmlError *locerror = NULL;
	
	switch (type) {
		case SML_SESSION_EVENT_ESTABLISHED:
			/* Pass the established through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ESTABLISHED, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_HEADER_REPLY:
			break;
		case SML_SESSION_EVENT_RESPONSE_URI:
			if (!smlTransportSetResponseURI(
				manager->transport,
				smlLocationGetURI(smlSessionGetTarget(session)),
				&locerror))
			{
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_COMMAND_START:
		case SML_SESSION_EVENT_COMMAND_END:
			if (!smlManagerDispatchCommand(manager, session, command, &locerror)) {
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_WARNING, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_CHILD_COMMAND:
			if (!smlManagerDispatchChildCommand(manager, session, parent, command, &locerror)) {
				_smlManagerSendEvent(manager, SML_MANAGER_SESSION_WARNING, session, NULL, NULL, locerror);
				goto error;
			}
			break;
		case SML_SESSION_EVENT_FINAL:
			/* Pass the final through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_FINAL, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_END:
			/* Pass the end through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_END, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_FLUSH:
			/* Pass the flush through */
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_FLUSH, session, NULL, NULL, NULL);
			break;
		case SML_SESSION_EVENT_ERROR:
			/* Pass the error through */
			smlErrorDuplicate(&locerror, &error);
			_smlManagerSendEvent(manager, SML_MANAGER_SESSION_ERROR, session, NULL, NULL, locerror);
			goto error;
			break;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return;
		
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&locerror));
	smlErrorDeref(&locerror);
}

static void _data_send_callback(SmlSession *session, SmlTransportData *data, void *userdata)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, session, data, userdata);
	managerSession *sess = userdata;

	/* The transport layer is able to signal the error via an event. */
	SmlError *error = NULL;
	if (!smlTransportSend(sess->transport, sess->link, data, &error))
	{
		smlTrace(TRACE_ERROR, "%s - %s", __func__, smlErrorPrint(&error));
		smlErrorDeref(&error);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlBool smlManagerSessionAdd(SmlManager *manager, SmlSession *session, SmlLink *link_, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, manager, session, link_, error);
	CHECK_ERROR_REF
	
	smlSessionRef(session);

	if (smlSessionGetSessionID(session)) {
		//Check if sessionID exists
		smlTrace(TRACE_INTERNAL, "Checking if session ID %s already exists", smlSessionGetSessionID(session));
		
		if (smlManagerSessionFind(manager, smlSessionGetSessionID(session))) {
			smlErrorSet(error, SML_ERROR_GENERIC, "Session ID already exists");
			goto error;
		}
	} else {
		char *lastid = smlManagerGetNewSessionID(manager);
		smlSessionSetSessionID(session, lastid);
		smlSafeCFree(&lastid);
	}
	
	managerSession *sess = smlTryMalloc0(sizeof(managerSession), error);
	if (!sess)
		goto error;
	
	sess->session = session;
	sess->finalLock = 0;
	
	if (link_) {
		sess->link = link_;
		smlLinkRef(link_);
	}
	
	sess->transport = manager->transport;
	
	manager->sessions = g_list_append(manager->sessions, sess);

	/* prepare large object support */
	if (0 < manager->localMaxMsgSize &&
	    (
	     manager->localMaxMsgSize < smlSessionGetLocalMaxMsgSize(session) ||
	     smlSessionGetLocalMaxMsgSize(session) <= 0
	    ))
		smlSessionSetLocalMaxMsgSize(session, manager->localMaxMsgSize);
	if (0 < manager->localMaxObjSize &&
	    (
	     manager->localMaxObjSize < smlSessionGetLocalMaxObjSize(session) ||
	     smlSessionGetLocalMaxObjSize(session) <= 0
	    ))
		smlSessionSetLocalMaxObjSize(session, manager->localMaxObjSize);
	if (smlSessionGetLocalMaxObjSize(session) &&
	    smlSessionGetLocalMaxMsgSize(session))
		smlSessionUseLargeObjects(session, TRUE);
	else
		smlSessionUseLargeObjects(session, FALSE);

	smlSessionSetEventCallback(session, _event_callback, manager);
	smlSessionSetDataCallback(session, _data_send_callback, sess);
	
	_smlManagerSendEvent(manager, SML_MANAGER_SESSION_NEW, session, NULL, NULL, NULL);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
		
error:
	smlSessionUnref(session);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

managerSession *_smlManagerGetManagerSession(SmlManager *manager, SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	GList *s;
	for (s = manager->sessions; s; s = s->next) {
		managerSession *sess = s->data;
		if (sess->session == session) {
			smlTrace(TRACE_EXIT, "%s - %p", __func__, sess);
			return sess;
		}
	}
	
	smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	return NULL;
}

void smlManagerSessionRemove(SmlManager *manager, SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);

	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		manager->sessions = g_list_remove(manager->sessions, sess);
		_manager_session_free(sess);
		smlTrace(TRACE_EXIT, "%s", __func__);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

void smlManagerSessionFinalLockRef(SmlManager *manager, SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		sess->finalLock++;
		smlTrace(TRACE_EXIT, "%s", __func__);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

void smlManagerSessionFinalLockUnref(SmlManager *manager, SmlSession *session)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager, session);
	
	managerSession *sess = _smlManagerGetManagerSession(manager, session);
	if (sess) {
		sess->finalLock--;
		smlTrace(TRACE_EXIT, "%s - %i", __func__, sess->finalLock);
	} else {
		smlTrace(TRACE_EXIT, "%s: Not Found", __func__);
	}
}

/** @brief Register an object with a session
 * 
 * A object waits at a certain location for incoming commands. So you can for example
 * register a object that listens for Get commands on the /test location.
 * 
 * Note that you cannot deregister an object from a already running session
 * 
 * @param session The session on which to listen
 * @param type The command type to listen to or SML_COMMAND_TYPE_UNKNOWN to listen for all
 * @param session If given, only listens for the commands on the given session. NULL for all
 * @param location The location where the object listens (This must match the Target of the command). NULL to listen for all targets
 * @param source The source of the command we want (This must match the Source of the command). NULL to listen for all sources
 * @param callback The callback that will receive the incoming commands.
 * @param childCallback The call that will receive the commands from the child commands. Set to NULL if the expected command has no childs
 * @param userdata The userdata for the callbacks
 * @param error A error struct
 * @return TRUE if successful, FALSE otherwise
 * 
 */
SmlBool smlManagerObjectRegister(SmlManager *manager, SmlCommandType type, SmlSession *session, SmlLocation *location, SmlLocation *source, const char *contentType, SmlCommandCb callback, SmlCommandCb childCallback, void *userdata, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p, %p, %s, %p, %p, %p, %p)", __func__, manager, type, session, location, source, VA_STRING(contentType), callback, childCallback, userdata, error);
	smlAssert(manager);
	smlAssert(callback);
	CHECK_ERROR_REF

	/* first we have to check if this an update for an already registered object */
	/* this is important for actions which require more than one DsSession */
	
        SmlObject *object = smlManagerObjectFindInternal(
				manager, session, type,
				location, source, contentType);
	if (object != NULL && session != NULL)
	{
		/* check that it is not a global manager object */
		SmlObject *hObject = smlManagerObjectFindInternal(
                                	manager, NULL, type,
                                	location, source, contentType);
		if (hObject != NULL && hObject == object)
		{
			/* the found object is a global manager object */
			/* so we have to register a new object */
			object = NULL;
		}
	}
	if (object)
	{
		/* there is an object and so we reconfigure it */
		smlTrace(TRACE_INTERNAL, "%s: prepare a reusable object", __func__);

		if (object->location)
			smlLocationUnref(object->location);
		if (object->source)
			smlLocationUnref(object->source);
		if (object->contentType)
			smlSafeCFree(&(object->contentType));
	}
	else
	{
		/* let's create and register a new object */
		smlTrace(TRACE_INTERNAL, "%s: create and register a new object", __func__);

		object = smlTryMalloc0(sizeof(SmlObject), error);
		if (!object)
			goto error;

		if (session) {
			managerSession *sess = _manager_session_find(manager, session);
			if (!sess) {
				smlErrorSet(error, SML_ERROR_GENERIC, "Session not found");
				goto error_free_object;
			}
		
			sess->objects = g_list_append(sess->objects, object);
		} else {
			manager->objects = g_list_append(manager->objects, object);
		}
	}

	smlTrace(TRACE_INTERNAL, "%s: configure registered object", __func__);

	object->type = type;
	
	if (location) {
		object->location = location;
		smlLocationRef(location);
	}
	
	if (source) {
		object->source = source;
		smlLocationRef(source);
	}
	
	if (contentType) {
		object->contentType = g_strdup(contentType);
	}
	
	object->commandCallback = callback;
	object->childCallback = childCallback;
	object->commandCallbackUserdata = userdata;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error_free_object:
	smlManagerObjectFree(object);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

void smlManagerObjectDeregister(SmlManager *manager, SmlCommandType type, SmlLocation *location, SmlLocation *source)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p)", __func__, manager, type, location, source);
	smlAssert(manager);
	
	GList *o2 = g_list_copy(manager->objects);
	GList *o = NULL;
	for (o = o2; o; o = o->next) {
		SmlObject *object = o->data;
		
		
		if (object->type != type)
			continue;
		
		if (!smlLocationCompare(NULL, object->location, NULL, location))
			continue;
		
		if (!smlLocationCompare(NULL, object->source, NULL, source))
			continue;
		
		smlManagerObjectFree(object);
		manager->objects = g_list_remove(manager->objects, object);
	}
	g_list_free(o2);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

void smlManagerObjectFree(SmlObject *object)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, object);
	smlAssert(object);
	
	if (object->location)
		smlLocationUnref(object->location);
		
	if (object->source)
		smlLocationUnref(object->source);
	
	if (object->contentType)
		smlSafeCFree(&(object->contentType));
	
	smlSafeFree((gpointer *)&object);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlObject *smlManagerObjectFind(SmlManager *manager, SmlSession *session, SmlCommand *cmd)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, manager, session, cmd);
	smlAssert(manager);
	smlAssert(cmd);

	SmlObject *object = smlManagerObjectFindInternal(
				manager,
				session,
				cmd->type,
				cmd->target, cmd->source,
				cmd->private.alert.contentType);
	smlTrace(TRACE_EXIT, "%s(%p)", __func__, object);
	return object;
}

SmlObject *smlManagerObjectFindInternal(
		SmlManager *manager,
		SmlSession *session,
		SmlCommandType type,
		SmlLocation *target,
		SmlLocation *source,
		const char* contentType)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p, %p)", __func__, manager, session, type, target, source);
	smlAssert(manager);
	SmlObject *object = NULL;
	GList *o = NULL;
	
	/* We first search for the object in the session specific objects */
	if (session) {
		managerSession *sess = _manager_session_find(manager, session);
		if (sess) {
			for (o = sess->objects; o; o = o->next) {
				object = o->data;
				
				if (object->type != SML_COMMAND_TYPE_UNKNOWN && type != object->type)
					continue;
				
				if (!smlLocationCompare(NULL, object->location, NULL, target))
					continue;
				
				if (!smlLocationCompare(NULL, object->source, NULL, source))
					continue;
				
				smlTrace(TRACE_EXIT, "%s: FOUND (session): %p", __func__, object);
				return object;
			}
		}
	}
	
	for (o = manager->objects; o; o = o->next) {
		object = o->data;
		
		if (object->type != SML_COMMAND_TYPE_UNKNOWN && type != object->type)
			continue;
		
		if (type == SML_COMMAND_TYPE_ALERT && contentType) {
			if (object->contentType) {
				/* This item is a san 11 alert */
				if (!strcmp(contentType, object->contentType)) {
					smlTrace(TRACE_EXIT, "%s: FOUND SAN TARGET: %p", __func__, object);
					return object;
				}
			}
			
			continue;
		}
		
		if (!smlLocationCompare(NULL, object->location, NULL, target))
			continue;
		
		if (!smlLocationCompare(NULL, object->source, NULL, source))
			continue;
		
		/* Check if the target is a san target */
		if (object->contentType)
			continue;
		
		smlTrace(TRACE_EXIT, "%s: FOUND: %p", __func__, object);
		return object;
	}
	
	smlTrace(TRACE_EXIT, "%s: NOT FOUND", __func__);
	return NULL;
}

SmlTransport *smlManagerGetTransport(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, manager);
	smlAssert(manager);
	
	SmlTransport *tsp = manager->transport;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, tsp);
	return tsp;
}

SmlBool smlManagerDispatchHeader(SmlManager *manager, SmlSession *session, SmlHeader *header, SmlCred *cred, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, manager, session, header, cred, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(header);
	CHECK_ERROR_REF
	
	if (manager->headerCallback &&
	    session->sessionType == SML_SESSION_TYPE_SERVER) {
		/* Authentication is only specified for OMA DS server. */
		manager->headerCallback(session, header, cred, manager->headerCallbackUserdata);
	} else {
		smlTrace(TRACE_INTERNAL, "%s: Header not handled!", __func__);
		if (session->sessionType == SML_SESSION_TYPE_SERVER &&
		    !session->established) {
			/* An OMA DS server must handle the header for security
			 * reasons - especially authentication.
			 *
			 * Question: can we detect such a bug in an earlier phase?
			 */
			g_warning("%s: This is an OMA DS server. " \
				"The header callback is missing. " \
				"All requests will be accepted without authentication.",
				__func__);
			session->established = TRUE;
			smlSessionDispatchEvent(
                        	session, SML_SESSION_EVENT_ESTABLISHED,
                        	NULL, NULL, NULL, NULL);
		}
		SmlStatus *status = smlStatusNew(
					SML_NO_ERROR, 0,
					header->messageID,
					header->source, header->target,
					SML_COMMAND_TYPE_HEADER, error);
		if (!status)
			goto error;
			
		if (!smlSessionSendReply(session, status, error)) {
			smlStatusUnref(status);
			goto error;
		}
		
		smlStatusUnref(status);
	}

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

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

SmlBool smlManagerDispatchChildCommand(SmlManager *manager, SmlSession *session, SmlCommand *parent, SmlCommand *cmd, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, manager, session, parent, cmd, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(parent);
	CHECK_ERROR_REF
	
	//Check if a handler for this object at this path has been installed.
	SmlObject *object = smlManagerObjectFind(manager, session, parent);
	if (object) {
		//Check if a handler for this object at this path has been installed.
		if (!object->childCallback) {
			smlErrorSet(error, SML_ERROR_GENERIC, "No handler for the child was installed");
			goto error;
		}
		
		object->childCallback(session, cmd, object->commandCallbackUserdata);
	} else {
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_FOUND, error);
		if (!reply)
			goto error;
		
		if (!smlSessionSendReply(session, reply, error)) {
			smlStatusUnref(reply);
			goto error;
		}
		
		smlStatusUnref(reply);
		
		smlErrorSet(error, SML_ERROR_GENERIC, "Unable to find child command handler");
		
		goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

SmlBool smlManagerDispatchCommand(SmlManager *manager, SmlSession *session, SmlCommand *cmd, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, manager, session, cmd, error);
	smlAssert(manager);
	smlAssert(session);
	smlAssert(cmd);
	CHECK_ERROR_REF
	
	//Check if a handler for this object at this path has been installed.
	SmlObject *object = smlManagerObjectFind(manager, session, cmd);
	if (object) {
		//Check if a handler for this object at this path has been installed.
		if (!object->commandCallback) {
			smlErrorSet(error, SML_ERROR_GENERIC, "No handler for the child was installed");
			goto error;
		}
		
		object->commandCallback(session, cmd, object->commandCallbackUserdata);
	} else {
		const char *type = smlCommandTypeToString(cmd->type, error);
		/* If the type is unknown then this can be part of the error. */
		smlErrorDeref(error);
		const char *srcuri = (cmd->source && cmd->source->locURI) ? cmd->source->locURI : "NULL";
		const char *dsturi = (cmd->target && cmd->target->locURI) ? cmd->target->locURI : "NULL";
		smlErrorSet(error, SML_ERROR_NOT_FOUND, "Unable to find command handler (%s: %s -> %s)", (type) ? type : "UNKNOWN", srcuri, dsturi);

		SmlError *locerror = NULL;
		SmlStatus *reply = smlCommandNewReply(cmd, SML_ERROR_NOT_FOUND, &locerror);
		if (!reply) {
			smlErrorSet(error, SML_ERROR_NOT_FOUND, "%s %s",
				smlErrorPrint(error),
				smlErrorPrint(&locerror));
			goto error;
		}
		
		if (!smlSessionSendReply(session, reply, &locerror)) {
			smlStatusUnref(reply);
			smlErrorSet(error, SML_ERROR_NOT_FOUND, "%s %s",
				smlErrorPrint(error),
				smlErrorPrint(&locerror));
			goto error;
		}
		
		smlStatusUnref(reply);
		
		goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

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

void smlManagerRegisterHeaderHandler(SmlManager *manager, SmlHeaderCb callback, SmlStatusReplyCb statuscb, void *userdata)
{
	smlAssert(manager);

	manager->headerCallback = callback;
	manager->headerStatusCallback = statuscb;
	manager->headerCallbackUserdata = userdata;
}

void smlManagerSetLocalMaxMsgSize(SmlManager *manager, unsigned int size)
{
	smlAssert(manager);
	manager->localMaxMsgSize = size;
}

void smlManagerSetLocalMaxObjSize(SmlManager *manager, unsigned int size)
{
	smlAssert(manager);
	manager->localMaxObjSize = size;
}

char *smlManagerGetNewSessionID(SmlManager *manager)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, manager);
	smlAssertMsg(manager, "The manager is required to avoid duplicated session IDs.");

	char *sessionString = NULL;
	while (sessionString == NULL)
	{
		/* WARNING: only 4 byte session IDs are theoretically allowed !!! */
    		/* create random session ID - glib only supports 32 bit random numbers */
		unsigned int sessionID = (unsigned int) g_random_int_range (0, 0xFFFF);
		sessionString = g_strdup_printf("%u", sessionID);
		smlTrace(TRACE_INTERNAL, "%s: new potential session ID is %lu.", __func__, sessionID);
		if (smlManagerSessionFind(manager, sessionString))
			smlSafeCFree(&sessionString);
	}
	smlTrace(TRACE_EXIT, "%s - %s", __func__, VA_STRING(sessionString));
	return sessionString;
}

SmlLink *smlManagerSessionGetLink(
		SmlManager *manager,
		SmlSession *session,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, manager, session, error);
	CHECK_ERROR_REF

	managerSession *_msession = _manager_session_find(manager, session);
	if (!_msession)
	{
		smlErrorSet(error, SML_ERROR_GENERIC,
			"The session %d is not registered at the manager.",
			smlSessionGetSessionID(session));
		goto error;
	}

	if (_msession->link)
		smlLinkRef(_msession->link);

	smlTrace(TRACE_EXIT, "%s - %p", __func__, _msession->link);
	return _msession->link;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return NULL;
}

